From c669048fb7fba84fdc43f137e79611f464989ee1 Mon Sep 17 00:00:00 2001 From: Paul Cento Date: Sun, 18 Jun 2023 23:12:41 -0400 Subject: [PATCH 01/60] rendering terrain --- C7/Game.cs | 8 ++- C7/Map/Corners.cs | 150 +++++++++++++++++++++++++++++++++++++++++ C7/MapView.cs | 6 +- QueryCiv3/QueryCiv3.cs | 11 +-- 4 files changed, 160 insertions(+), 15 deletions(-) create mode 100644 C7/Map/Corners.cs diff --git a/C7/Game.cs b/C7/Game.cs index 7c973592..8bc4c9cf 100644 --- a/C7/Game.cs +++ b/C7/Game.cs @@ -5,6 +5,7 @@ using C7Engine; using C7GameData; using Serilog; +using C7.Map; public partial class Game : Node2D { [Signal] public delegate void TurnStartedEventHandler(); @@ -22,7 +23,7 @@ enum GameState { } public Player controller; // Player that's controlling the UI. - + private Corners corners; private MapView mapView; public AnimationManager civ3AnimData; public AnimationTracker animTracker; @@ -88,9 +89,12 @@ public override void _Ready() { if (startingSettler != null) mapView.centerCameraOnTile(startingSettler.location); } + corners = new Corners(map); } - Toolbar = GetNode("CanvasLayer/Control/ToolBar/MarginContainer/HBoxContainer"); + AddChild(corners); + + Toolbar = GetNode("CanvasLayer/ToolBar/MarginContainer/HBoxContainer"); //TODO: What was this supposed to do? It throws errors and occasinally causes crashes now, because _OnViewportSizeChanged doesn't exist // GetTree().Root.Connect("size_changed",new Callable(this,"_OnViewportSizeChanged")); diff --git a/C7/Map/Corners.cs b/C7/Map/Corners.cs new file mode 100644 index 00000000..0c49981c --- /dev/null +++ b/C7/Map/Corners.cs @@ -0,0 +1,150 @@ +using Godot; +using System.Collections.Generic; +using System.Linq; +using System; + +namespace C7.Map { + + class TerrainPcx { + private static Random prng = new Random(); + private string name; + // abc refers to the layout of the terrain tiles in the pcx based on + // the positions of each terrain at the corner of 4 tiles. + // - https://forums.civfanatics.com/threads/terrain-editing.622999/ + // - https://forums.civfanatics.com/threads/editing-terrain-pcx-files.102840/ + private string[] abc; + public int atlas; + public TerrainPcx(string name, string[] abc, int atlas) { + this.name = name; + this.abc = abc; + this.atlas = atlas; + } + public bool validFor(string[] corner) { + return corner.All(tile => abc.Contains(tile)); + } + private int abcIndex(string terrain) { + List indices = new List(); + for (int i = 0; i < abc.Count(); i++) { + if (abc[i] == terrain) { + indices.Add(i); + } + } + return indices[prng.Next(indices.Count)]; + } + + // getTextureCoords looks up the correct texture index in the pcx + // for the given position of each corner terrain type + public Vector2I getTextureCoords(string[] corner) { + int top = abcIndex(corner[0]); + int right = abcIndex(corner[1]); + int bottom = abcIndex(corner[2]); + int left = abcIndex(corner[3]); + int index = top + (left * 3) + (right * 9) + (bottom * 27); + return new Vector2I(index % 9, index / 9); + } + } + + partial class Corners : Node2D { + private List terrainPcxFiles = new List { + "Art/Terrain/xtgc.pcx", + "Art/Terrain/xpgc.pcx", + "Art/Terrain/xdgc.pcx", + "Art/Terrain/xdpc.pcx", + "Art/Terrain/xdgp.pcx", + "Art/Terrain/xggc.pcx", + "Art/Terrain/wCSO.pcx", + "Art/Terrain/wSSS.pcx", + "Art/Terrain/wOOO.pcx", + }; + private List terrainPcxList; + private string[,]terrain; + private TileMap tilemap; + private TileSet tileset; + private List textures; + private Vector2I tileSize = new Vector2I(128, 64); + private int width; + private int height; + + private void initializeTileMap() { + this.tilemap = new TileMap(); + this.tileset = new TileSet(); + + this.tileset.TileShape = TileSet.TileShapeEnum.Isometric; + this.tileset.TileLayout = TileSet.TileLayoutEnum.Stacked; + this.tileset.TileOffsetAxis = TileSet.TileOffsetAxisEnum.Horizontal; + this.tileset.TileSize = this.tileSize; + + foreach (ImageTexture texture in this.textures) { + TileSetAtlasSource source = new TileSetAtlasSource(); + source.Texture = texture; + source.TextureRegionSize = this.tileSize; + for (int x = 0; x < 9; x++) { + for (int y = 0; y < 9; y++) { + source.CreateTile(new Vector2I(x, y)); + } + } + this.tileset.AddSource(source); + } + this.tilemap.TileSet = tileset; + + this.terrainPcxList = new List() { + new TerrainPcx("tgc", new string[]{"tundra", "grassland", "coast"}, 0), + new TerrainPcx("pgc", new string[]{"plains", "grassland", "coast"}, 1), + new TerrainPcx("dgc", new string[]{"desert", "grassland", "coast"}, 2), + new TerrainPcx("dpc", new string[]{"desert", "plains", "coast"}, 3), + new TerrainPcx("dgp", new string[]{"desert", "grassland", "plains"}, 4), + // new TerrainPcx("ggc", new string[]{"grassland", "grassland", "coast"}, 5), + new TerrainPcx("cso", new string[]{"coast", "sea", "ocean"}, 6), + new TerrainPcx("sss", new string[]{"sea", "sea", "sea"}, 7), + new TerrainPcx("ooo", new string[]{"ocean", "ocean", "ocean"}, 8), + }; + AddChild(this.tilemap); + } + + private TerrainPcx getPcxForCorner(string[] corner) { + return terrainPcxList.Find(tpcx => tpcx.validFor(corner)); + } + + void fill(Vector2I cell, int atlas, Vector2I texCoords) { + this.tilemap.SetCell(0, cell, atlas, texCoords); + } + + public Corners(C7GameData.GameMap gameMap) { + this.textures = terrainPcxFiles.ConvertAll(path => Util.LoadTextureFromPCX(path)); + this.initializeTileMap(); + this.width = gameMap.numTilesWide / 2; + this.height = gameMap.numTilesTall; + this.terrain = new string[width, height]; + + foreach (C7GameData.Tile t in gameMap.tiles) { + int x = t.xCoordinate; + int y = t.yCoordinate; + // stacked coordinates + x = y % 2 == 0 ? x / 2 : (x - 1) / 2; + this.terrain[x, y] = t.baseTerrainTypeKey; + } + + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + Vector2I cell = new Vector2I(x, y); + string left = terrain[x, y]; + string right = terrain[(x + 1) % width, y]; + bool even = y % 2 == 0; + string top = "coast"; + if (y > 0) { + top = even ? terrain[x, y-1] : terrain[(x + 1) % width, y - 1]; + } + string bottom = "coast"; + if (y < height - 1) { + bottom = even ? terrain[x, y+1] : terrain[(x + 1) % width, y + 1]; + } + string[] corner = new string[4]{top, right, bottom, left}; + TerrainPcx pcx = getPcxForCorner(corner); + Vector2I texCoords = pcx.getTextureCoords(corner); + fill(cell, pcx.atlas, texCoords); + } + } + + } + } +} diff --git a/C7/MapView.cs b/C7/MapView.cs index 2202fb6b..8447934b 100644 --- a/C7/MapView.cs +++ b/C7/MapView.cs @@ -606,7 +606,7 @@ public int getRowStartX(int y) public GridLayer gridLayer { get; private set; } public ImageTexture civColorWhitePalette = null; - + private Corners corners; public MapView(Game game, int mapWidth, int mapHeight, bool wrapHorizontally, bool wrapVertically) { this.game = game; @@ -616,7 +616,7 @@ public MapView(Game game, int mapWidth, int mapHeight, bool wrapHorizontally, bo this.wrapVertically = wrapVertically; looseView = new LooseView(this); - looseView.layers.Add(new TerrainLayer()); + // looseView.layers.Add(new TerrainLayer()); looseView.layers.Add(new RiverLayer()); looseView.layers.Add(new ForestLayer()); looseView.layers.Add(new MarshLayer()); @@ -629,7 +629,7 @@ public MapView(Game game, int mapWidth, int mapHeight, bool wrapHorizontally, bo looseView.layers.Add(new BuildingLayer()); looseView.layers.Add(new UnitLayer()); looseView.layers.Add(new CityLayer()); - looseView.layers.Add(new FogOfWarLayer()); + // looseView.layers.Add(new FogOfWarLayer()); (civColorWhitePalette, _) = Util.loadPalettizedPCX("Art/Units/Palettes/ntp00.pcx"); diff --git a/QueryCiv3/QueryCiv3.cs b/QueryCiv3/QueryCiv3.cs index 1bfaa072..24d2450f 100644 --- a/QueryCiv3/QueryCiv3.cs +++ b/QueryCiv3/QueryCiv3.cs @@ -35,16 +35,7 @@ public Civ3File(byte[] fileBytes) } public Boolean SectionExists(string sectionName) { - bool result = false; - foreach (Civ3Section section in Sections) - { - if (section.Name == sectionName) - { - result = true; - break; - } - } - return result; + return Array.Exists(Sections, section => section.Name == sectionName); } protected internal Civ3Section[] PopulateSections(byte[] Data) { From c47f8a8370a16be603d1942f19346f9f29267927 Mon Sep 17 00:00:00 2001 From: Paul Cento Date: Sun, 18 Jun 2023 23:58:06 -0400 Subject: [PATCH 02/60] add Camera2D --- C7/C7Game.tscn | 7 ++++++- C7/Game.cs | 4 ++-- C7/Map/{Corners.cs => TerrainTileMap.cs} | 4 ++-- C7/MapView.cs | 1 - C7/PlayerCamera.gd | 15 +++++++++++++++ 5 files changed, 25 insertions(+), 6 deletions(-) rename C7/Map/{Corners.cs => TerrainTileMap.cs} (98%) create mode 100644 C7/PlayerCamera.gd diff --git a/C7/C7Game.tscn b/C7/C7Game.tscn index 501f8f06..164d98a9 100644 --- a/C7/C7Game.tscn +++ b/C7/C7Game.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=14 format=3 uid="uid://cldl5nk4n61m2"] +[gd_scene load_steps=15 format=3 uid="uid://cldl5nk4n61m2"] [ext_resource type="Script" path="res://Game.cs" id="1"] [ext_resource type="Script" path="res://UIElements/Advisors/Advisors.cs" id="3"] @@ -9,6 +9,7 @@ [ext_resource type="Script" path="res://UIElements/UnitButtons/RenameButton.cs" id="8"] [ext_resource type="Script" path="res://UIElements/Popups/PopupOverlay.cs" id="9"] [ext_resource type="Theme" uid="uid://c1jpmssnhvodi" path="res://C7Theme.tres" id="10"] +[ext_resource type="Script" path="res://PlayerCamera.gd" id="11_ab5st"] [ext_resource type="Script" path="res://UIElements/UnitButtons/UnitButtons.cs" id="12"] [sub_resource type="Animation" id="2"] @@ -239,10 +240,14 @@ libraries = { "": SubResource("AnimationLibrary_bowxq") } +[node name="PlayerCamera" type="Camera2D" parent="CanvasLayer"] +script = ExtResource("11_ab5st") + [connection signal="NewAutoselectedUnit" from="." to="CanvasLayer/Control/GameStatus" method="OnNewUnitSelected"] [connection signal="NewAutoselectedUnit" from="." to="CanvasLayer/Control/UnitButtons" method="OnNewUnitSelected"] [connection signal="NoMoreAutoselectableUnits" from="." to="CanvasLayer/Control/GameStatus" method="OnNoMoreAutoselectableUnits"] [connection signal="NoMoreAutoselectableUnits" from="." to="CanvasLayer/Control/UnitButtons" method="OnNoMoreAutoselectableUnits"] + [connection signal="ShowSpecificAdvisor" from="." to="CanvasLayer/Advisor" method="OnShowSpecificAdvisor"] [connection signal="TurnEnded" from="." to="CanvasLayer/Control/GameStatus" method="OnTurnEnded"] [connection signal="TurnStarted" from="." to="CanvasLayer/Control/GameStatus" method="OnTurnStarted"] diff --git a/C7/Game.cs b/C7/Game.cs index 8bc4c9cf..d9edc6b7 100644 --- a/C7/Game.cs +++ b/C7/Game.cs @@ -23,7 +23,7 @@ enum GameState { } public Player controller; // Player that's controlling the UI. - private Corners corners; + private TerrainTileMap corners; private MapView mapView; public AnimationManager civ3AnimData; public AnimationTracker animTracker; @@ -89,7 +89,7 @@ public override void _Ready() { if (startingSettler != null) mapView.centerCameraOnTile(startingSettler.location); } - corners = new Corners(map); + corners = new TerrainTileMap(map); } AddChild(corners); diff --git a/C7/Map/Corners.cs b/C7/Map/TerrainTileMap.cs similarity index 98% rename from C7/Map/Corners.cs rename to C7/Map/TerrainTileMap.cs index 0c49981c..2398e786 100644 --- a/C7/Map/Corners.cs +++ b/C7/Map/TerrainTileMap.cs @@ -44,7 +44,7 @@ public Vector2I getTextureCoords(string[] corner) { } } - partial class Corners : Node2D { + partial class TerrainTileMap : Node2D { private List terrainPcxFiles = new List { "Art/Terrain/xtgc.pcx", "Art/Terrain/xpgc.pcx", @@ -109,7 +109,7 @@ void fill(Vector2I cell, int atlas, Vector2I texCoords) { this.tilemap.SetCell(0, cell, atlas, texCoords); } - public Corners(C7GameData.GameMap gameMap) { + public TerrainTileMap(C7GameData.GameMap gameMap) { this.textures = terrainPcxFiles.ConvertAll(path => Util.LoadTextureFromPCX(path)); this.initializeTileMap(); this.width = gameMap.numTilesWide / 2; diff --git a/C7/MapView.cs b/C7/MapView.cs index 8447934b..e7327072 100644 --- a/C7/MapView.cs +++ b/C7/MapView.cs @@ -606,7 +606,6 @@ public int getRowStartX(int y) public GridLayer gridLayer { get; private set; } public ImageTexture civColorWhitePalette = null; - private Corners corners; public MapView(Game game, int mapWidth, int mapHeight, bool wrapHorizontally, bool wrapVertically) { this.game = game; diff --git a/C7/PlayerCamera.gd b/C7/PlayerCamera.gd new file mode 100644 index 00000000..e7fc49c6 --- /dev/null +++ b/C7/PlayerCamera.gd @@ -0,0 +1,15 @@ +extends Camera2D + +const MAX_ZOOM: float = 2.0 +const MIN_ZOOM: float = 0.1 + +var zoom_factor: float = 1.0 + +func _unhandled_input(event: InputEvent): + if event is InputEventMouseMotion: + if event.button_mask == MOUSE_BUTTON_MASK_LEFT: + self.position -= (event.relative / self.zoom) + + if event is InputEventMagnifyGesture: + zoom_factor = clampf(zoom_factor * event.factor, MIN_ZOOM, MAX_ZOOM) + self.zoom = Vector2.ONE * zoom_factor From a3f0a4e383b8daa897bfce308e33c38a336a1265 Mon Sep 17 00:00:00 2001 From: Paul Cento Date: Mon, 19 Jun 2023 16:29:15 -0400 Subject: [PATCH 03/60] test drawing units in word coordinates --- C7/C7Game.tscn | 4 +- C7/Game.cs | 2 +- C7/Map/TerrainTileMap.cs | 99 +++++++++++++++++++++++++++++----------- C7/Map/UnitLayer.cs | 10 ++-- C7/MapView.cs | 2 +- C7/PlayerCamera.cs | 24 ++++++++++ C7/PlayerCamera.gd | 15 ------ 7 files changed, 106 insertions(+), 50 deletions(-) create mode 100644 C7/PlayerCamera.cs delete mode 100644 C7/PlayerCamera.gd diff --git a/C7/C7Game.tscn b/C7/C7Game.tscn index 164d98a9..30758051 100644 --- a/C7/C7Game.tscn +++ b/C7/C7Game.tscn @@ -9,7 +9,7 @@ [ext_resource type="Script" path="res://UIElements/UnitButtons/RenameButton.cs" id="8"] [ext_resource type="Script" path="res://UIElements/Popups/PopupOverlay.cs" id="9"] [ext_resource type="Theme" uid="uid://c1jpmssnhvodi" path="res://C7Theme.tres" id="10"] -[ext_resource type="Script" path="res://PlayerCamera.gd" id="11_ab5st"] +[ext_resource type="Script" path="res://PlayerCamera.cs" id="11_pkhac"] [ext_resource type="Script" path="res://UIElements/UnitButtons/UnitButtons.cs" id="12"] [sub_resource type="Animation" id="2"] @@ -241,7 +241,7 @@ libraries = { } [node name="PlayerCamera" type="Camera2D" parent="CanvasLayer"] -script = ExtResource("11_ab5st") +script = ExtResource("11_pkhac") [connection signal="NewAutoselectedUnit" from="." to="CanvasLayer/Control/GameStatus" method="OnNewUnitSelected"] [connection signal="NewAutoselectedUnit" from="." to="CanvasLayer/Control/UnitButtons" method="OnNewUnitSelected"] diff --git a/C7/Game.cs b/C7/Game.cs index d9edc6b7..67f239f6 100644 --- a/C7/Game.cs +++ b/C7/Game.cs @@ -89,7 +89,7 @@ public override void _Ready() { if (startingSettler != null) mapView.centerCameraOnTile(startingSettler.location); } - corners = new TerrainTileMap(map); + corners = new TerrainTileMap(this, gameDataAccess.gameData); } AddChild(corners); diff --git a/C7/Map/TerrainTileMap.cs b/C7/Map/TerrainTileMap.cs index 2398e786..b134ae61 100644 --- a/C7/Map/TerrainTileMap.cs +++ b/C7/Map/TerrainTileMap.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System; +using C7GameData; namespace C7.Map { @@ -58,47 +59,72 @@ partial class TerrainTileMap : Node2D { }; private List terrainPcxList; private string[,]terrain; + private TileMap terrainTilemap; + private TileSet terrainTileset; private TileMap tilemap; private TileSet tileset; private List textures; private Vector2I tileSize = new Vector2I(128, 64); private int width; private int height; + private GameMap gameMap; - private void initializeTileMap() { - this.tilemap = new TileMap(); - this.tileset = new TileSet(); + private TileSet makeTileSet() { + TileSet ts = new TileSet(); + ts.TileShape = TileSet.TileShapeEnum.Isometric; + ts.TileLayout = TileSet.TileLayoutEnum.Stacked; + ts.TileOffsetAxis = TileSet.TileOffsetAxisEnum.Horizontal; + ts.TileSize = tileSize; + return ts; + } - this.tileset.TileShape = TileSet.TileShapeEnum.Isometric; - this.tileset.TileLayout = TileSet.TileLayoutEnum.Stacked; - this.tileset.TileOffsetAxis = TileSet.TileOffsetAxisEnum.Horizontal; - this.tileset.TileSize = this.tileSize; + private void initializeTileMap() { + this.terrainTilemap = new TileMap(); + terrainTileset = makeTileSet(); - foreach (ImageTexture texture in this.textures) { + foreach (ImageTexture texture in textures) { TileSetAtlasSource source = new TileSetAtlasSource(); source.Texture = texture; - source.TextureRegionSize = this.tileSize; + source.TextureRegionSize = tileSize; for (int x = 0; x < 9; x++) { for (int y = 0; y < 9; y++) { source.CreateTile(new Vector2I(x, y)); } } - this.tileset.AddSource(source); + terrainTileset.AddSource(source); } - this.tilemap.TileSet = tileset; + terrainTilemap.TileSet = terrainTileset; - this.terrainPcxList = new List() { + terrainPcxList = new List() { new TerrainPcx("tgc", new string[]{"tundra", "grassland", "coast"}, 0), new TerrainPcx("pgc", new string[]{"plains", "grassland", "coast"}, 1), new TerrainPcx("dgc", new string[]{"desert", "grassland", "coast"}, 2), new TerrainPcx("dpc", new string[]{"desert", "plains", "coast"}, 3), new TerrainPcx("dgp", new string[]{"desert", "grassland", "plains"}, 4), - // new TerrainPcx("ggc", new string[]{"grassland", "grassland", "coast"}, 5), + new TerrainPcx("ggc", new string[]{"grassland", "grassland", "coast"}, 5), new TerrainPcx("cso", new string[]{"coast", "sea", "ocean"}, 6), new TerrainPcx("sss", new string[]{"sea", "sea", "sea"}, 7), new TerrainPcx("ooo", new string[]{"ocean", "ocean", "ocean"}, 8), }; - AddChild(this.tilemap); + terrainTilemap.Position += Vector2I.Right * (width / 2); + + tilemap = new TileMap(); + tileset = makeTileSet(); + tilemap.TileSet = tileset; + + AddChild(terrainTilemap); + AddChild(tilemap); + } + + public override void _UnhandledInput(InputEvent @event) { + base._UnhandledInput(@event); + if (@event is InputEventMouseButton mb && mb.IsPressed()) { + Vector2 mousePosition = GetGlobalMousePosition(); + Vector2I tile = tilemap.LocalToMap(ToLocal(mousePosition)); + GD.Print($"clicked on tile {tile.ToString()}"); + string terrainName = terrain[tile.X, tile.Y]; + GD.Print($"terrain type is {terrainName}"); + } } private TerrainPcx getPcxForCorner(string[] corner) { @@ -106,22 +132,25 @@ private TerrainPcx getPcxForCorner(string[] corner) { } void fill(Vector2I cell, int atlas, Vector2I texCoords) { - this.tilemap.SetCell(0, cell, atlas, texCoords); + terrainTilemap.SetCell(0, cell, atlas, texCoords); + } + + private Vector2I stackedCoords(int x, int y) { + x = y % 2 == 0 ? x / 2 : (x - 1) / 2; + return new Vector2I(x, y); } - public TerrainTileMap(C7GameData.GameMap gameMap) { - this.textures = terrainPcxFiles.ConvertAll(path => Util.LoadTextureFromPCX(path)); - this.initializeTileMap(); - this.width = gameMap.numTilesWide / 2; - this.height = gameMap.numTilesTall; - this.terrain = new string[width, height]; + public TerrainTileMap(Game game, GameData data) { + textures = terrainPcxFiles.ConvertAll(path => Util.LoadTextureFromPCX(path)); + gameMap = data.map; + width = gameMap.numTilesWide / 2; + height = gameMap.numTilesTall; + initializeTileMap(); + terrain = new string[width, height]; foreach (C7GameData.Tile t in gameMap.tiles) { - int x = t.xCoordinate; - int y = t.yCoordinate; - // stacked coordinates - x = y % 2 == 0 ? x / 2 : (x - 1) / 2; - this.terrain[x, y] = t.baseTerrainTypeKey; + Vector2I coords = stackedCoords(t.xCoordinate, t.yCoordinate); + terrain[coords.X, coords.Y] = t.baseTerrainTypeKey; } for (int x = 0; x < width; x++) { @@ -145,6 +174,24 @@ public TerrainTileMap(C7GameData.GameMap gameMap) { } } + foreach (Tile tile in gameMap.tiles) { + if (tile.unitsOnTile.Count > 0) { + MapUnit unit = tile.unitsOnTile[0]; + var ai = new UnitLayer.AnimationInstance(game.civ3AnimData); + MapUnit.Appearance appearance = game.animTracker.getUnitAppearance(unit); + + var coords = stackedCoords(tile.xCoordinate, tile.yCoordinate); + var worldCoords = tilemap.MapToLocal(coords); + ai.SetPosition(worldCoords); + + game.civ3AnimData.forUnit(unit.unitType, appearance.action).loadSpriteAnimation(); + string animName = AnimationManager.AnimationKey(unit.unitType, appearance.action, appearance.direction); + ai.SetAnimation(animName); + ai.SetFrame(0); + AddChild(ai.sprite); + AddChild(ai.spriteTint); + } + } } } } diff --git a/C7/Map/UnitLayer.cs b/C7/Map/UnitLayer.cs index 4cc35faa..bddc793d 100644 --- a/C7/Map/UnitLayer.cs +++ b/C7/Map/UnitLayer.cs @@ -106,8 +106,8 @@ public Vector2 FrameSize(string animation) { return this.sprite.SpriteFrames.GetFrameTexture(animation, 0).GetSize(); } - public AnimationInstance(LooseView looseView) { - AnimationManager manager = looseView.mapView.game.civ3AnimData; + public AnimationInstance(AnimationManager manager) { + // AnimationManager manager = looseView.mapView.game.civ3AnimData; this.sprite = new AnimatedSprite2D(); this.sprite.ZIndex = unitAnimZIndex; @@ -121,8 +121,8 @@ public AnimationInstance(LooseView looseView) { this.material.Shader = GD.Load("res://UnitTint.gdshader"); this.spriteTint.Material = this.material; - looseView.AddChild(sprite); - looseView.AddChild(spriteTint); + // looseView.AddChild(sprite); + // looseView.AddChild(spriteTint); } } @@ -132,7 +132,7 @@ public AnimationInstance(LooseView looseView) { // Returns the next unused AnimationInstance or creates & returns a new one if none are available. public AnimationInstance getBlankAnimationInstance(LooseView looseView) { if (nextBlankAnimInst >= animInsts.Count) { - animInsts.Add(new AnimationInstance(looseView)); + // animInsts.Add(new AnimationInstance(looseView)); } AnimationInstance inst = animInsts[nextBlankAnimInst]; nextBlankAnimInst++; diff --git a/C7/MapView.cs b/C7/MapView.cs index e7327072..91ea7d0f 100644 --- a/C7/MapView.cs +++ b/C7/MapView.cs @@ -626,7 +626,7 @@ public MapView(Game game, int mapWidth, int mapHeight, bool wrapHorizontally, bo this.gridLayer = new GridLayer(); looseView.layers.Add(this.gridLayer); looseView.layers.Add(new BuildingLayer()); - looseView.layers.Add(new UnitLayer()); + // looseView.layers.Add(new UnitLayer()); looseView.layers.Add(new CityLayer()); // looseView.layers.Add(new FogOfWarLayer()); diff --git a/C7/PlayerCamera.cs b/C7/PlayerCamera.cs new file mode 100644 index 00000000..abf96221 --- /dev/null +++ b/C7/PlayerCamera.cs @@ -0,0 +1,24 @@ +using Godot; +using System; + +public partial class PlayerCamera : Camera2D +{ + private readonly float maxZoom = 2.0f; + private readonly float minZoom = 0.2f; + private float zoomFactor = 1.0f; + + public override void _Ready() { + base._Ready(); + Zoom = Vector2.One * zoomFactor; + } + + public override void _UnhandledInput(InputEvent @event) { + if (@event is InputEventMouseMotion mm && mm.ButtonMask == MouseButtonMask.Left) { + Position -= mm.Relative / Zoom; + } + if (@event is InputEventMagnifyGesture mg) { + zoomFactor = Mathf.Clamp(zoomFactor * mg.Factor, minZoom, maxZoom); + Zoom = Vector2.One * zoomFactor; + } + } +} diff --git a/C7/PlayerCamera.gd b/C7/PlayerCamera.gd deleted file mode 100644 index e7fc49c6..00000000 --- a/C7/PlayerCamera.gd +++ /dev/null @@ -1,15 +0,0 @@ -extends Camera2D - -const MAX_ZOOM: float = 2.0 -const MIN_ZOOM: float = 0.1 - -var zoom_factor: float = 1.0 - -func _unhandled_input(event: InputEvent): - if event is InputEventMouseMotion: - if event.button_mask == MOUSE_BUTTON_MASK_LEFT: - self.position -= (event.relative / self.zoom) - - if event is InputEventMagnifyGesture: - zoom_factor = clampf(zoom_factor * event.factor, MIN_ZOOM, MAX_ZOOM) - self.zoom = Vector2.ONE * zoom_factor From 37e9b9530923a64b2006fa7ff6066b2c980ff12f Mon Sep 17 00:00:00 2001 From: Paul Cento Date: Mon, 19 Jun 2023 17:42:10 -0400 Subject: [PATCH 04/60] wip --- C7/Map/TerrainPcx.cs | 47 +++++++++++++ C7/Map/TerrainTileMap.cs | 51 ++------------ C7/Map/UnitLayer.cs | 142 ++++++++++++++++----------------------- C7/PlayerCamera.cs | 5 ++ C7/tests/TestUnit.cs | 5 +- 5 files changed, 119 insertions(+), 131 deletions(-) create mode 100644 C7/Map/TerrainPcx.cs diff --git a/C7/Map/TerrainPcx.cs b/C7/Map/TerrainPcx.cs new file mode 100644 index 00000000..29f22303 --- /dev/null +++ b/C7/Map/TerrainPcx.cs @@ -0,0 +1,47 @@ +using Godot; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace C7.Map { + + class TerrainPcx { + private static Random prng = new Random(); + private string name; + // abc refers to the layout of the terrain tiles in the pcx based on + // the positions of each terrain at the corner of 4 tiles. + // - https://forums.civfanatics.com/threads/terrain-editing.622999/ + // - https://forums.civfanatics.com/threads/editing-terrain-pcx-files.102840/ + private string[] abc; + public int atlas; + public TerrainPcx(string name, string[] abc, int atlas) { + this.name = name; + this.abc = abc; + this.atlas = atlas; + } + public bool validFor(string[] corner) { + return corner.All(tile => abc.Contains(tile)); + } + private int abcIndex(string terrain) { + List indices = new List(); + for (int i = 0; i < abc.Count(); i++) { + if (abc[i] == terrain) { + indices.Add(i); + } + } + return indices[prng.Next(indices.Count)]; + } + + // getTextureCoords looks up the correct texture index in the pcx + // for the given position of each corner terrain type + public Vector2I getTextureCoords(string[] corner) { + int top = abcIndex(corner[0]); + int right = abcIndex(corner[1]); + int bottom = abcIndex(corner[2]); + int left = abcIndex(corner[3]); + int index = top + (left * 3) + (right * 9) + (bottom * 27); + return new Vector2I(index % 9, index / 9); + } + } + +} diff --git a/C7/Map/TerrainTileMap.cs b/C7/Map/TerrainTileMap.cs index b134ae61..c0b28d10 100644 --- a/C7/Map/TerrainTileMap.cs +++ b/C7/Map/TerrainTileMap.cs @@ -6,45 +6,6 @@ namespace C7.Map { - class TerrainPcx { - private static Random prng = new Random(); - private string name; - // abc refers to the layout of the terrain tiles in the pcx based on - // the positions of each terrain at the corner of 4 tiles. - // - https://forums.civfanatics.com/threads/terrain-editing.622999/ - // - https://forums.civfanatics.com/threads/editing-terrain-pcx-files.102840/ - private string[] abc; - public int atlas; - public TerrainPcx(string name, string[] abc, int atlas) { - this.name = name; - this.abc = abc; - this.atlas = atlas; - } - public bool validFor(string[] corner) { - return corner.All(tile => abc.Contains(tile)); - } - private int abcIndex(string terrain) { - List indices = new List(); - for (int i = 0; i < abc.Count(); i++) { - if (abc[i] == terrain) { - indices.Add(i); - } - } - return indices[prng.Next(indices.Count)]; - } - - // getTextureCoords looks up the correct texture index in the pcx - // for the given position of each corner terrain type - public Vector2I getTextureCoords(string[] corner) { - int top = abcIndex(corner[0]); - int right = abcIndex(corner[1]); - int bottom = abcIndex(corner[2]); - int left = abcIndex(corner[3]); - int index = top + (left * 3) + (right * 9) + (bottom * 27); - return new Vector2I(index % 9, index / 9); - } - } - partial class TerrainTileMap : Node2D { private List terrainPcxFiles = new List { "Art/Terrain/xtgc.pcx", @@ -177,19 +138,17 @@ public TerrainTileMap(Game game, GameData data) { foreach (Tile tile in gameMap.tiles) { if (tile.unitsOnTile.Count > 0) { MapUnit unit = tile.unitsOnTile[0]; - var ai = new UnitLayer.AnimationInstance(game.civ3AnimData); + UnitSprite sprite = new UnitSprite(game.civ3AnimData); MapUnit.Appearance appearance = game.animTracker.getUnitAppearance(unit); var coords = stackedCoords(tile.xCoordinate, tile.yCoordinate); - var worldCoords = tilemap.MapToLocal(coords); - ai.SetPosition(worldCoords); + sprite.Position = tilemap.MapToLocal(coords); game.civ3AnimData.forUnit(unit.unitType, appearance.action).loadSpriteAnimation(); string animName = AnimationManager.AnimationKey(unit.unitType, appearance.action, appearance.direction); - ai.SetAnimation(animName); - ai.SetFrame(0); - AddChild(ai.sprite); - AddChild(ai.spriteTint); + sprite.SetAnimation(animName); + sprite.SetFrame(0); + AddChild(sprite); } } } diff --git a/C7/Map/UnitLayer.cs b/C7/Map/UnitLayer.cs index bddc793d..e7e6a18b 100644 --- a/C7/Map/UnitLayer.cs +++ b/C7/Map/UnitLayer.cs @@ -5,17 +5,64 @@ using ConvertCiv3Media; using Godot; +// UnitSprite represents an animated unit. It's specific to a unit, action, and direction. +// UnitSprite comprises two sprites: a base sprite and a civ color-tinted sprite. The +// shading is done in the UnitTint.gdshader shader. +public partial class UnitSprite : Node2D { + + private readonly int unitAnimZIndex = 100; + private readonly string unitShaderPath = "res://UnitTint.gdshader"; + private Shader unitShader; + + public AnimatedSprite2D sprite; + public AnimatedSprite2D spriteTint; + public ShaderMaterial material; + + public int GetNextFrameByProgress(string animation, float progress) { + int frameCount = sprite.SpriteFrames.GetFrameCount(animation); + int nextFrame = (int)((float)frameCount * progress); + return Mathf.Clamp(nextFrame, 0, frameCount - 1); + } + + public void SetFrame(int frame) { + sprite.Frame = frame; + spriteTint.Frame = frame; + } + + public void SetAnimation(string name) { + sprite.Animation = name; + spriteTint.Animation = name; + } + + public Vector2 FrameSize(string animation) { + return sprite.SpriteFrames.GetFrameTexture(animation, 0).GetSize(); + } + + public UnitSprite(AnimationManager manager) { + sprite = new AnimatedSprite2D{ + ZIndex = unitAnimZIndex, + SpriteFrames = manager.spriteFrames, + }; + spriteTint = new AnimatedSprite2D{ + ZIndex = unitAnimZIndex, + SpriteFrames= manager.tintFrames, + }; + + material = new ShaderMaterial(); + unitShader = GD.Load(unitShaderPath); + material.Shader = unitShader; + spriteTint.Material = material; + + AddChild(sprite); + AddChild(spriteTint); + } +} + public partial class UnitLayer : LooseLayer { private ImageTexture unitIcons; private int unitIconsWidth; private ImageTexture unitMovementIndicators; - // The unit animations, effect animations, and cursor are all drawn as children attached to the looseView but aren't created and attached in - // any particular order so we must use the ZIndex property to ensure they're properly layered. - public const int effectAnimZIndex = 150; - public const int unitAnimZIndex = 100; - public const int cursorZIndex = 50; - public UnitLayer() { var iconPCX = new Pcx(Util.Civ3MediaPath("Art/Units/units_32.pcx")); unitIcons = PCXToGodot.getImageTextureFromPCX(iconPCX); @@ -53,101 +100,28 @@ public Color getHPColor(float fractionRemaining) { } } - // AnimationInstance represents an animation appearing on the screen. It's specific to a unit, action, and direction. AnimationInstances have - // two components: a ShaderMaterial and a MeshInstance2D. The ShaderMaterial runs the unit shader (created by UnitLayer.getShader) with all - // the parameters set to a particular texture, civ color, direction, etc. The MeshInstance2D is what's actually drawn by Godot, i.e., what's - // added to the node tree. AnimationInstances are only active for one frame at a time but they live as long as the UnitLayer. They are - // retrieved or created as needed by getBlankAnimationInstance during the drawing of units and are hidden & requeued for use at the beginning - // of each frame. - - // should hold animation players instead of animations - public partial class AnimationInstance { - - public AnimatedSprite2D sprite; - public AnimatedSprite2D spriteTint; - public ShaderMaterial material; - - public void SetPosition(Vector2 position) { - this.sprite.Position = position; - this.spriteTint.Position = position; - } - - public int GetNextFrameByProgress(string animation, float progress) { - // AnimatedSprite2D has a settable FrameProgress field, which I expected to - // update the current frame of the animation upon setting, but it did not - // when I tried it, so instead, calculate what the next frame should be - // based on the progress. - int frameCount = this.sprite.SpriteFrames.GetFrameCount(animation); - int nextFrame = (int)((float)frameCount * progress); - return nextFrame >= frameCount ? frameCount - 1 : (nextFrame < 0 ? 0 : nextFrame); - } - - public void SetFrame(int frame) { - this.sprite.Frame = frame; - this.spriteTint.Frame = frame; - } - - public void SetAnimation(string name) { - this.sprite.Animation = name; - this.spriteTint.Animation = name; - } - - public void Show() { - this.sprite.Show(); - this.spriteTint.Show(); - } - - public void Hide() { - this.sprite.Hide(); - this.spriteTint.Hide(); - } - - public Vector2 FrameSize(string animation) { - return this.sprite.SpriteFrames.GetFrameTexture(animation, 0).GetSize(); - } - - public AnimationInstance(AnimationManager manager) { - // AnimationManager manager = looseView.mapView.game.civ3AnimData; - - this.sprite = new AnimatedSprite2D(); - this.sprite.ZIndex = unitAnimZIndex; - this.sprite.SpriteFrames = manager.spriteFrames; - - this.spriteTint = new AnimatedSprite2D(); - this.spriteTint.ZIndex = unitAnimZIndex; - this.spriteTint.SpriteFrames = manager.tintFrames; - - this.material = new ShaderMaterial(); - this.material.Shader = GD.Load("res://UnitTint.gdshader"); - this.spriteTint.Material = this.material; - - // looseView.AddChild(sprite); - // looseView.AddChild(spriteTint); - } - } - - private List animInsts = new List(); + private List animInsts = new List(); private int nextBlankAnimInst = 0; // Returns the next unused AnimationInstance or creates & returns a new one if none are available. - public AnimationInstance getBlankAnimationInstance(LooseView looseView) { + public UnitSprite getBlankAnimationInstance(LooseView looseView) { if (nextBlankAnimInst >= animInsts.Count) { // animInsts.Add(new AnimationInstance(looseView)); } - AnimationInstance inst = animInsts[nextBlankAnimInst]; + UnitSprite inst = animInsts[nextBlankAnimInst]; nextBlankAnimInst++; return inst; } public void drawUnitAnimFrame(LooseView looseView, MapUnit unit, MapUnit.Appearance appearance, Vector2 tileCenter) { - AnimationInstance inst = getBlankAnimationInstance(looseView); + UnitSprite inst = getBlankAnimationInstance(looseView); looseView.mapView.game.civ3AnimData.forUnit(unit.unitType, appearance.action).loadSpriteAnimation(); string animName = AnimationManager.AnimationKey(unit.unitType, appearance.action, appearance.direction); // Need to move the sprites upward a bit so that their feet are at the center of the tile. I don't know if spriteHeight/4 is the right var animOffset = MapView.cellSize * new Vector2(appearance.offsetX, appearance.offsetY); Vector2 position = tileCenter + animOffset - new Vector2(0, inst.FrameSize(animName).Y / 4); - inst.SetPosition(position); + inst.Position = position; var civColor = new Color(unit.owner.color); int nextFrame = inst.GetNextFrameByProgress(animName, appearance.progress); diff --git a/C7/PlayerCamera.cs b/C7/PlayerCamera.cs index abf96221..5cb29686 100644 --- a/C7/PlayerCamera.cs +++ b/C7/PlayerCamera.cs @@ -21,4 +21,9 @@ public override void _UnhandledInput(InputEvent @event) { Zoom = Vector2.One * zoomFactor; } } + + public Rect2 getVisibleWorld() { + Transform2D vpToGlobal = (GetViewport().GlobalCanvasTransform * this.GetCanvasTransform()).AffineInverse(); + return vpToGlobal * GetViewportRect(); + } } diff --git a/C7/tests/TestUnit.cs b/C7/tests/TestUnit.cs index 64254d3d..06cc328e 100644 --- a/C7/tests/TestUnit.cs +++ b/C7/tests/TestUnit.cs @@ -1,6 +1,7 @@ using Godot; using System; using ConvertCiv3Media; +using C7GameData; public partial class TestUnit : Node2D { @@ -8,7 +9,9 @@ public partial class TestUnit : Node2D // Called when the node enters the scene tree for the first time. public override void _Ready() { - //AudioStreamPlayer player = GetNode("CanvasLayer/SoundEffectPlayer"); + AnimationManager manager = new AnimationManager(null); + UnitSprite sprite = new UnitSprite(manager); + manager.forUnit(new UnitPrototype{name="warrior"}, MapUnit.AnimatedAction.RUN); AnimatedSprite2D sprite = new AnimatedSprite2D(); SpriteFrames frames = new SpriteFrames(); From 8bc8951560bcff0d05841ee95c569cc16297f264 Mon Sep 17 00:00:00 2001 From: Paul Cento Date: Mon, 19 Jun 2023 17:53:42 -0400 Subject: [PATCH 05/60] simplify unit sprite test demo - AnimationManager could be simpler but trying to keep diff small --- C7/Map/UnitLayer.cs | 5 +++++ C7/tests/TestUnit.cs | 33 ++++++++------------------------- 2 files changed, 13 insertions(+), 25 deletions(-) diff --git a/C7/Map/UnitLayer.cs b/C7/Map/UnitLayer.cs index e7e6a18b..2adb7233 100644 --- a/C7/Map/UnitLayer.cs +++ b/C7/Map/UnitLayer.cs @@ -34,6 +34,11 @@ public void SetAnimation(string name) { spriteTint.Animation = name; } + public void Play(string name) { + sprite.Play(name); + spriteTint.Play(name); + } + public Vector2 FrameSize(string animation) { return sprite.SpriteFrames.GetFrameTexture(animation, 0).GetSize(); } diff --git a/C7/tests/TestUnit.cs b/C7/tests/TestUnit.cs index 06cc328e..f5ac535a 100644 --- a/C7/tests/TestUnit.cs +++ b/C7/tests/TestUnit.cs @@ -11,34 +11,17 @@ public override void _Ready() { AnimationManager manager = new AnimationManager(null); UnitSprite sprite = new UnitSprite(manager); - manager.forUnit(new UnitPrototype{name="warrior"}, MapUnit.AnimatedAction.RUN); - - AnimatedSprite2D sprite = new AnimatedSprite2D(); - SpriteFrames frames = new SpriteFrames(); - sprite.SpriteFrames = frames; - - AnimatedSprite2D spriteTint = new AnimatedSprite2D(); - SpriteFrames framesTint = new SpriteFrames(); - spriteTint.SpriteFrames = framesTint; - - AnimationManager.loadFlicAnimation("Art/Units/warrior/warriorRun.flc", "run", ref frames, ref framesTint); - - ShaderMaterial material = new ShaderMaterial(); - material.Shader = GD.Load("res://UnitTint.gdshader"); - material.SetShaderParameter("tintColor", new Vector3(1f,1f,1f)); - spriteTint.Material = material; - + UnitPrototype prototype = new UnitPrototype{name="warrior"}; + manager.forUnit(prototype, MapUnit.AnimatedAction.RUN).loadSpriteAnimation(); + string name = AnimationManager.AnimationKey(prototype, MapUnit.AnimatedAction.RUN, TileDirection.EAST); AddChild(sprite); - AddChild(spriteTint); - - sprite.Play("run_EAST"); - spriteTint.Play("run_EAST"); - sprite.Position = new Vector2(30, 30); - spriteTint.Position = new Vector2(30, 30); - float SCALE = 6; - this.Scale = new Vector2(SCALE, SCALE); + float scale = 6; + this.Scale = new Vector2(scale, scale); + sprite.material.SetShaderParameter("tintColor", new Vector3(1f,1f,1f)); + sprite.Position = new Vector2(30, 30); + sprite.Play(name); AnimatedSprite2D cursor = new AnimatedSprite2D(); SpriteFrames cursorFrames = new SpriteFrames(); From 487ef48f7513a09ff293791e07b53d476f57ce72 Mon Sep 17 00:00:00 2001 From: Paul Cento Date: Mon, 19 Jun 2023 19:14:48 -0400 Subject: [PATCH 06/60] enable pixel snapping - removes world seams --- C7/C7Game.tscn | 4 + C7/Game.cs | 100 +++++------------ C7/Map/{TerrainTileMap.cs => MapView.cs} | 131 ++++++++++++----------- C7/Map/UnitLayer.cs | 4 +- C7/{MapView.cs => OldMapView.cs} | 18 ++-- C7/PlayerCamera.cs | 16 ++- C7/project.godot | 1 + C7/tests/TestUnit.cs | 9 -- 8 files changed, 127 insertions(+), 156 deletions(-) rename C7/Map/{TerrainTileMap.cs => MapView.cs} (57%) rename C7/{MapView.cs => OldMapView.cs} (98%) diff --git a/C7/C7Game.tscn b/C7/C7Game.tscn index 30758051..9592d9d3 100644 --- a/C7/C7Game.tscn +++ b/C7/C7Game.tscn @@ -68,6 +68,10 @@ script = ExtResource("1") anchors_preset = 15 anchor_right = 1.0 anchor_bottom = 1.0 +offset_left = -574.0 +offset_top = -322.0 +offset_right = -574.0 +offset_bottom = -322.0 grow_horizontal = 2 grow_vertical = 2 theme = ExtResource("10") diff --git a/C7/Game.cs b/C7/Game.cs index 67f239f6..e34b4dec 100644 --- a/C7/Game.cs +++ b/C7/Game.cs @@ -23,8 +23,8 @@ enum GameState { } public Player controller; // Player that's controlling the UI. - private TerrainTileMap corners; private MapView mapView; + private OldMapView oldMapView; public AnimationManager civ3AnimData; public AnimationTracker animTracker; @@ -42,12 +42,10 @@ enum GameState { public bool KeepCSUWhenFortified = false; Control Toolbar; - private bool IsMovingCamera; - private Vector2 OldPosition; Stopwatch loadTimer = new Stopwatch(); GlobalSingleton Global; - + private PlayerCamera camera; bool errorOnLoad = false; public override void _EnterTree() { @@ -68,31 +66,33 @@ public override void _Ready() { controller = CreateGame.createGame(Global.LoadGamePath, Global.DefaultBicPath); // Spawns engine thread Global.ResetLoadGamePath(); + camera = GetNode("./CanvasLayer/PlayerCamera") as PlayerCamera; + using (var gameDataAccess = new UIGameDataAccess()) { GameMap map = gameDataAccess.gameData.map; Util.setModPath(gameDataAccess.gameData.scenarioSearchPath); log.Debug("RelativeModPath ", map.RelativeModPath); - mapView = new MapView(this, map.numTilesWide, map.numTilesTall, map.wrapHorizontally, map.wrapVertically); - AddChild(mapView); + oldMapView = new OldMapView(this, map.numTilesWide, map.numTilesTall, map.wrapHorizontally, map.wrapVertically); + AddChild(oldMapView); - mapView.cameraZoom = (float)1.0; - mapView.gridLayer.visible = false; + oldMapView.cameraZoom = (float)1.0; + oldMapView.gridLayer.visible = false; // Set initial camera location. If the UI controller has any cities, focus on their capital. Otherwise, focus on their // starting settler. if (controller.cities.Count > 0) { City capital = controller.cities.Find(c => c.IsCapital()); if (capital != null) - mapView.centerCameraOnTile(capital.location); + oldMapView.centerCameraOnTile(capital.location); } else { MapUnit startingSettler = controller.units.Find(u => u.unitType.actions.Contains(C7Action.UnitBuildCity)); if (startingSettler != null) - mapView.centerCameraOnTile(startingSettler.location); + oldMapView.centerCameraOnTile(startingSettler.location); } - corners = new TerrainTileMap(this, gameDataAccess.gameData); + mapView = new MapView(this, gameDataAccess.gameData); } - AddChild(corners); + AddChild(mapView); Toolbar = GetNode("CanvasLayer/ToolBar/MarginContainer/HBoxContainer"); @@ -192,9 +192,7 @@ public override void _Process(double delta) { (CurrentlySelectedUnit.isFortified && !KeepCSUWhenFortified))) GetNextAutoselectedUnit(gameData); } - //Listen to keys. There is a C# Mono Godot bug where e.g. Godot.Key.F1 (etc.) doesn't work - //without a manual cast to int. - //https://github.com/godotengine/godot/issues/16388 + // Listen to keys. TODO: move this if (Input.IsKeyPressed(Godot.Key.F1)) { EmitSignal("ShowSpecificAdvisor", "F1"); } @@ -223,9 +221,9 @@ public override void _Process(double delta) { // If "location" is not already near the center of the screen, moves the camera to bring it into view. public void ensureLocationIsInView(Tile location) { if (controller.tileKnowledge.isTileKnown(location) && location != Tile.NONE) { - Vector2 relativeScreenLocation = mapView.screenLocationOfTile(location, true) / mapView.getVisibleAreaSize(); + Vector2 relativeScreenLocation = oldMapView.screenLocationOfTile(location, true) / oldMapView.getVisibleAreaSize(); if (relativeScreenLocation.DistanceTo(new Vector2((float)0.5, (float)0.5)) > 0.30) - mapView.centerCameraOnTile(location); + oldMapView.centerCameraOnTile(location); } } @@ -293,42 +291,23 @@ private void OnPlayerEndTurn() { } public void _on_QuitButton_pressed() { - // This apparently exits the whole program - // GetTree().Quit(); - // ChangeSceneToFile deletes the current scene and frees its memory, so this is quitting to main menu GetTree().ChangeSceneToFile("res://MainMenu.tscn"); } - public void _on_Zoom_value_changed(float value) { - mapView.setCameraZoomFromMiddle(value); - } - public void AdjustZoomSlider(int numSteps, Vector2 zoomCenter) { VSlider slider = GetNode("CanvasLayer/Control/SlideOutBar/VBoxContainer/Zoom"); double newScale = slider.Value + slider.Step * (double)numSteps; - if (newScale < slider.MinValue) - newScale = slider.MinValue; - else if (newScale > slider.MaxValue) - newScale = slider.MaxValue; + newScale = Mathf.Clamp(newScale, slider.MinValue, slider.MaxValue); // Note we must set the camera zoom before setting the new slider value since setting the value will trigger the callback which will // adjust the zoom around a center we don't want. - mapView.setCameraZoom((float)newScale, zoomCenter); + // camera.scaleZoom(zoom) slider.Value = newScale; } - public void _on_RightButton_pressed() { - mapView.cameraLocation += new Vector2(128, 0); - } - public void _on_LeftButton_pressed() { - mapView.cameraLocation += new Vector2(-128, 0); - } - public void _on_UpButton_pressed() { - mapView.cameraLocation += new Vector2(0, -64); - } - public void _on_DownButton_pressed() { - mapView.cameraLocation += new Vector2(0, 64); + private void onSliderZoomChanged(float value) { + camera.setZoom(value); } public override void _Input(InputEvent @event) { @@ -340,13 +319,14 @@ public override void _Input(InputEvent @event) { public override void _UnhandledInput(InputEvent @event) { // Control node must not be in the way and/or have mouse pass enabled if (@event is InputEventMouseButton eventMouseButton) { + Vector2 globalMousePosition = GetGlobalMousePosition(); if (eventMouseButton.ButtonIndex == MouseButton.Left) { GetViewport().SetInputAsHandled(); if (eventMouseButton.IsPressed()) { if (inUnitGoToMode) { setGoToMode(false); using (var gameDataAccess = new UIGameDataAccess()) { - var tile = mapView.tileOnScreenAt(gameDataAccess.gameData.map, eventMouseButton.Position); + var tile = mapView.tileAt(gameDataAccess.gameData.map, globalMousePosition); if (tile != null) { new MsgSetUnitPath(CurrentlySelectedUnit.guid, tile).send(); } @@ -354,19 +334,14 @@ public override void _UnhandledInput(InputEvent @event) { } else { // Select unit on tile at mouse location using (var gameDataAccess = new UIGameDataAccess()) { - var tile = mapView.tileOnScreenAt(gameDataAccess.gameData.map, eventMouseButton.Position); + var tile = mapView.tileAt(gameDataAccess.gameData.map, globalMousePosition); if (tile != null) { MapUnit to_select = tile.unitsOnTile.Find(u => u.movementPoints.canMove); if (to_select != null && to_select.owner == controller) setSelectedUnit(to_select); } } - - OldPosition = eventMouseButton.Position; - IsMovingCamera = true; } - } else { - IsMovingCamera = false; } } else if (eventMouseButton.ButtonIndex == MouseButton.WheelUp) { GetViewport().SetInputAsHandled(); @@ -374,10 +349,10 @@ public override void _UnhandledInput(InputEvent @event) { } else if (eventMouseButton.ButtonIndex == MouseButton.WheelDown) { GetViewport().SetInputAsHandled(); AdjustZoomSlider(-1, GetViewport().GetMousePosition()); - } else if ((eventMouseButton.ButtonIndex == MouseButton.Right) && (!eventMouseButton.IsPressed())) { + } else if (eventMouseButton.ButtonIndex == MouseButton.Right && !eventMouseButton.IsPressed()) { setGoToMode(false); using (var gameDataAccess = new UIGameDataAccess()) { - var tile = mapView.tileOnScreenAt(gameDataAccess.gameData.map, eventMouseButton.Position); + var tile = mapView.tileAt(gameDataAccess.gameData.map, globalMousePosition); if (tile != null) { bool shiftDown = Input.IsKeyPressed(Godot.Key.Shift); if (shiftDown && tile.cityAtTile?.owner == controller) @@ -407,10 +382,8 @@ public override void _UnhandledInput(InputEvent @event) { } } } - } else if (@event is InputEventMouseMotion eventMouseMotion && IsMovingCamera) { + } else if (@event is InputEventMouseMotion eventMouseMotion) { GetViewport().SetInputAsHandled(); - mapView.cameraLocation += OldPosition - eventMouseMotion.Position; - OldPosition = eventMouseMotion.Position; } else if (@event is InputEventKey eventKeyDown && eventKeyDown.Pressed) { if (eventKeyDown.Keycode == Godot.Key.O && eventKeyDown.ShiftPressed && eventKeyDown.IsCommandOrControlPressed() && eventKeyDown.AltPressed) { using (UIGameDataAccess gameDataAccess = new UIGameDataAccess()) { @@ -428,17 +401,6 @@ public override void _UnhandledInput(InputEvent @event) { } } } - } else if (@event is InputEventMagnifyGesture magnifyGesture) { - // UI slider has the min/max zoom settings for now - VSlider slider = GetNode("CanvasLayer/Control/SlideOutBar/VBoxContainer/Zoom"); - double newScale = mapView.cameraZoom * magnifyGesture.Factor; - if (newScale < slider.MinValue) - newScale = slider.MinValue; - else if (newScale > slider.MaxValue) - newScale = slider.MaxValue; - mapView.setCameraZoom((float)newScale, magnifyGesture.Position); - // Update the UI slider - slider.Value = newScale; } } @@ -480,7 +442,7 @@ private void processActions() { } if (Input.IsActionJustPressed(C7Action.ToggleGrid)) { - this.mapView.gridLayer.visible = !this.mapView.gridLayer.visible; + this.oldMapView.gridLayer.visible = !this.oldMapView.gridLayer.visible; } if (Input.IsActionJustPressed(C7Action.Escape) && !this.inUnitGoToMode) { @@ -490,15 +452,7 @@ private void processActions() { } if (Input.IsActionJustPressed(C7Action.ToggleZoom)) { - if (mapView.cameraZoom != 1) { - mapView.setCameraZoomFromMiddle(1.0f); - VSlider slider = GetNode("CanvasLayer/Control/SlideOutBar/VBoxContainer/Zoom"); - slider.Value = 1.0f; - } else { - mapView.setCameraZoomFromMiddle(0.5f); - VSlider slider = GetNode("CanvasLayer/Control/SlideOutBar/VBoxContainer/Zoom"); - slider.Value = 0.5f; - } + camera.setZoom(camera.zoomFactor != 1 ? 1.0f : 0.5f); } if (Input.IsActionJustPressed(C7Action.ToggleAnimations)) { diff --git a/C7/Map/TerrainTileMap.cs b/C7/Map/MapView.cs similarity index 57% rename from C7/Map/TerrainTileMap.cs rename to C7/Map/MapView.cs index c0b28d10..9fbca04d 100644 --- a/C7/Map/TerrainTileMap.cs +++ b/C7/Map/MapView.cs @@ -1,24 +1,28 @@ using Godot; using System.Collections.Generic; -using System.Linq; -using System; using C7GameData; namespace C7.Map { - partial class TerrainTileMap : Node2D { + partial class MapView : Node2D { + // same order as terrainPcxList private List terrainPcxFiles = new List { - "Art/Terrain/xtgc.pcx", - "Art/Terrain/xpgc.pcx", - "Art/Terrain/xdgc.pcx", - "Art/Terrain/xdpc.pcx", - "Art/Terrain/xdgp.pcx", - "Art/Terrain/xggc.pcx", - "Art/Terrain/wCSO.pcx", - "Art/Terrain/wSSS.pcx", - "Art/Terrain/wOOO.pcx", + "Art/Terrain/xtgc.pcx", "Art/Terrain/xpgc.pcx", "Art/Terrain/xdgc.pcx", + "Art/Terrain/xdpc.pcx", "Art/Terrain/xdgp.pcx", "Art/Terrain/xggc.pcx", + "Art/Terrain/wCSO.pcx", "Art/Terrain/wSSS.pcx", "Art/Terrain/wOOO.pcx", + }; + // same order as terrainPcxFiles + private List terrainPcxList = new List() { + new TerrainPcx("tgc", new string[]{"tundra", "grassland", "coast"}, 0), + new TerrainPcx("pgc", new string[]{"plains", "grassland", "coast"}, 1), + new TerrainPcx("dgc", new string[]{"desert", "grassland", "coast"}, 2), + new TerrainPcx("dpc", new string[]{"desert", "plains", "coast"}, 3), + new TerrainPcx("dgp", new string[]{"desert", "grassland", "plains"}, 4), + new TerrainPcx("ggc", new string[]{"grassland", "grassland", "coast"}, 5), + new TerrainPcx("cso", new string[]{"coast", "sea", "ocean"}, 6), + new TerrainPcx("sss", new string[]{"sea", "sea", "sea"}, 7), + new TerrainPcx("ooo", new string[]{"ocean", "ocean", "ocean"}, 8), }; - private List terrainPcxList; private string[,]terrain; private TileMap terrainTilemap; private TileSet terrainTileset; @@ -31,18 +35,20 @@ partial class TerrainTileMap : Node2D { private GameMap gameMap; private TileSet makeTileSet() { - TileSet ts = new TileSet(); - ts.TileShape = TileSet.TileShapeEnum.Isometric; - ts.TileLayout = TileSet.TileLayoutEnum.Stacked; - ts.TileOffsetAxis = TileSet.TileOffsetAxisEnum.Horizontal; - ts.TileSize = tileSize; - return ts; + return new TileSet { + TileShape = TileSet.TileShapeEnum.Isometric, + TileLayout = TileSet.TileLayoutEnum.Stacked, + TileOffsetAxis = TileSet.TileOffsetAxisEnum.Horizontal, + TileSize = tileSize, + }; } private void initializeTileMap() { this.terrainTilemap = new TileMap(); + terrainTilemap.TextureFilter = TextureFilterEnum.Nearest; terrainTileset = makeTileSet(); + // register 9 x 9 layout of tiles in each terrain pcx foreach (ImageTexture texture in textures) { TileSetAtlasSource source = new TileSetAtlasSource(); source.Texture = texture; @@ -55,18 +61,6 @@ private void initializeTileMap() { terrainTileset.AddSource(source); } terrainTilemap.TileSet = terrainTileset; - - terrainPcxList = new List() { - new TerrainPcx("tgc", new string[]{"tundra", "grassland", "coast"}, 0), - new TerrainPcx("pgc", new string[]{"plains", "grassland", "coast"}, 1), - new TerrainPcx("dgc", new string[]{"desert", "grassland", "coast"}, 2), - new TerrainPcx("dpc", new string[]{"desert", "plains", "coast"}, 3), - new TerrainPcx("dgp", new string[]{"desert", "grassland", "plains"}, 4), - new TerrainPcx("ggc", new string[]{"grassland", "grassland", "coast"}, 5), - new TerrainPcx("cso", new string[]{"coast", "sea", "ocean"}, 6), - new TerrainPcx("sss", new string[]{"sea", "sea", "sea"}, 7), - new TerrainPcx("ooo", new string[]{"ocean", "ocean", "ocean"}, 8), - }; terrainTilemap.Position += Vector2I.Right * (width / 2); tilemap = new TileMap(); @@ -77,15 +71,38 @@ private void initializeTileMap() { AddChild(tilemap); } + private void setTerrainTiles() { + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + Vector2I cell = new Vector2I(x, y); + string left = terrain[x, y]; + string right = terrain[(x + 1) % width, y]; + bool even = y % 2 == 0; + string top = "coast"; + if (y > 0) { + top = even ? terrain[x, y - 1] : terrain[(x + 1) % width, y - 1]; + } + string bottom = "coast"; + if (y < height - 1) { + bottom = even ? terrain[x, y + 1] : terrain[(x + 1) % width, y + 1]; + } + string[] corner = new string[4]{top, right, bottom, left}; + TerrainPcx pcx = getPcxForCorner(corner); + Vector2I texCoords = pcx.getTextureCoords(corner); + fill(cell, pcx.atlas, texCoords); + } + } + } + public override void _UnhandledInput(InputEvent @event) { base._UnhandledInput(@event); - if (@event is InputEventMouseButton mb && mb.IsPressed()) { - Vector2 mousePosition = GetGlobalMousePosition(); - Vector2I tile = tilemap.LocalToMap(ToLocal(mousePosition)); - GD.Print($"clicked on tile {tile.ToString()}"); - string terrainName = terrain[tile.X, tile.Y]; - GD.Print($"terrain type is {terrainName}"); - } + // if (@event is InputEventMouseButton mb && mb.IsPressed()) { + // Vector2 mousePosition = GetGlobalMousePosition(); + // Vector2I tile = tilemap.LocalToMap(ToLocal(mousePosition)); + // GD.Print($"clicked on tile {tile.ToString()}"); + // string terrainName = terrain[tile.X, tile.Y]; + // GD.Print($"terrain type is {terrainName}"); + // } } private TerrainPcx getPcxForCorner(string[] corner) { @@ -101,7 +118,14 @@ private Vector2I stackedCoords(int x, int y) { return new Vector2I(x, y); } - public TerrainTileMap(Game game, GameData data) { + private (int, int) unstackedCoords(Vector2I stacked) { + int x = stacked.X; + int y = stacked.Y; + x = y % 2 == 0 ? x * 2 : (x * 2) + 1; + return (x, y); + } + + public MapView(Game game, GameData data) { textures = terrainPcxFiles.ConvertAll(path => Util.LoadTextureFromPCX(path)); gameMap = data.map; width = gameMap.numTilesWide / 2; @@ -113,28 +137,9 @@ public TerrainTileMap(Game game, GameData data) { Vector2I coords = stackedCoords(t.xCoordinate, t.yCoordinate); terrain[coords.X, coords.Y] = t.baseTerrainTypeKey; } + setTerrainTiles(); - for (int x = 0; x < width; x++) { - for (int y = 0; y < height; y++) { - Vector2I cell = new Vector2I(x, y); - string left = terrain[x, y]; - string right = terrain[(x + 1) % width, y]; - bool even = y % 2 == 0; - string top = "coast"; - if (y > 0) { - top = even ? terrain[x, y-1] : terrain[(x + 1) % width, y - 1]; - } - string bottom = "coast"; - if (y < height - 1) { - bottom = even ? terrain[x, y+1] : terrain[(x + 1) % width, y + 1]; - } - string[] corner = new string[4]{top, right, bottom, left}; - TerrainPcx pcx = getPcxForCorner(corner); - Vector2I texCoords = pcx.getTextureCoords(corner); - fill(cell, pcx.atlas, texCoords); - } - } - + // temp but place units in current position foreach (Tile tile in gameMap.tiles) { if (tile.unitsOnTile.Count > 0) { MapUnit unit = tile.unitsOnTile[0]; @@ -152,5 +157,11 @@ public TerrainTileMap(Game game, GameData data) { } } } + + public Tile tileAt(GameMap gameMap, Vector2 globalMousePosition) { + Vector2I tilemapCoord = tilemap.LocalToMap(ToLocal(globalMousePosition)); + (int x, int y) = unstackedCoords(tilemapCoord); + return gameMap.tileAt(x, y); + } } } diff --git a/C7/Map/UnitLayer.cs b/C7/Map/UnitLayer.cs index 2adb7233..4a6b042d 100644 --- a/C7/Map/UnitLayer.cs +++ b/C7/Map/UnitLayer.cs @@ -124,7 +124,7 @@ public void drawUnitAnimFrame(LooseView looseView, MapUnit unit, MapUnit.Appeara string animName = AnimationManager.AnimationKey(unit.unitType, appearance.action, appearance.direction); // Need to move the sprites upward a bit so that their feet are at the center of the tile. I don't know if spriteHeight/4 is the right - var animOffset = MapView.cellSize * new Vector2(appearance.offsetX, appearance.offsetY); + var animOffset = OldMapView.cellSize * new Vector2(appearance.offsetX, appearance.offsetY); Vector2 position = tileCenter + animOffset - new Vector2(0, inst.FrameSize(animName).Y / 4); inst.Position = position; @@ -221,7 +221,7 @@ public override void drawObject(LooseView looseView, GameData gameData, Tile til MapUnit unit = selectUnitToDisplay(looseView, tile.unitsOnTile); MapUnit.Appearance appearance = looseView.mapView.game.animTracker.getUnitAppearance(unit); - Vector2 animOffset = new Vector2(appearance.offsetX, appearance.offsetY) * MapView.cellSize; + Vector2 animOffset = new Vector2(appearance.offsetX, appearance.offsetY) * OldMapView.cellSize; // If the unit we're about to draw is currently selected, draw the cursor first underneath it if ((unit != MapUnit.NONE) && (unit == looseView.mapView.game.CurrentlySelectedUnit)) { diff --git a/C7/MapView.cs b/C7/OldMapView.cs similarity index 98% rename from C7/MapView.cs rename to C7/OldMapView.cs index 91ea7d0f..6c7ce410 100644 --- a/C7/MapView.cs +++ b/C7/OldMapView.cs @@ -101,7 +101,7 @@ public override void onEndDraw(LooseView looseView, GameData gameData) { if (tTD.tile != Tile.NONE) { int xSheet = tTD.tile.ExtraInfo.BaseTerrainImageID % 9, ySheet = tTD.tile.ExtraInfo.BaseTerrainImageID / 9; Rect2 texRect = new Rect2(new Vector2(xSheet, ySheet) * terrainSpriteSize, terrainSpriteSize); - Vector2 terrainOffset = new Vector2(0, -1 * MapView.cellSize.Y); + Vector2 terrainOffset = new Vector2(0, -1 * OldMapView.cellSize.Y); // Multiply size by 100.1% so avoid "seams" in the map. See issue #106. // Jim's option of a whole-map texture is less hacky, but this is quicker and seems to be working well. Rect2 screenRect = new Rect2(tTD.tileCenter - (float)0.5 * terrainSpriteSize + terrainOffset, terrainSpriteSize * 1.001f); @@ -457,7 +457,7 @@ public GridLayer() {} public override void drawObject(LooseView looseView, GameData gameData, Tile tile, Vector2 tileCenter) { - Vector2 cS = MapView.cellSize; + Vector2 cS = OldMapView.cellSize; Vector2 left = tileCenter + new Vector2(-cS.X, 0 ); Vector2 top = tileCenter + new Vector2( 0 , -cS.Y); Vector2 right = tileCenter + new Vector2( cS.X, 0 ); @@ -490,10 +490,10 @@ public override void drawObject(LooseView looseView, GameData gameData, Tile til } public partial class LooseView : Node2D { - public MapView mapView; + public OldMapView mapView; public List layers = new List(); - public LooseView(MapView mapView) + public LooseView(OldMapView mapView) { this.mapView = mapView; } @@ -513,14 +513,14 @@ public override void _Draw() // Iterating over visible tiles is unfortunately pretty expensive. Assemble a list of Tile references and centers first so we don't // have to reiterate for each layer. Doing this improves framerate significantly. - MapView.VisibleRegion visRegion = mapView.getVisibleRegion(); + OldMapView.VisibleRegion visRegion = mapView.getVisibleRegion(); List visibleTiles = new List(); for (int y = visRegion.upperLeftY; y < visRegion.lowerRightY; y++) { if (gD.map.isRowAt(y)) { for (int x = visRegion.getRowStartX(y); x < visRegion.lowerRightX; x += 2) { Tile tile = gD.map.tileAt(x, y); if (IsTileKnown(tile, gameDataAccess)) { - visibleTiles.Add(new VisibleTile { tile = tile, tileCenter = MapView.cellSize * new Vector2(x + 1, y + 1) }); + visibleTiles.Add(new VisibleTile { tile = tile, tileCenter = OldMapView.cellSize * new Vector2(x + 1, y + 1) }); } } } @@ -541,7 +541,7 @@ public override void _Draw() for (int x = visRegion.getRowStartX(y); x < visRegion.lowerRightX; x += 2) { Tile tile = gD.map.tileAt(x, y); if (tile != Tile.NONE) { - VisibleTile invisibleTile = new VisibleTile { tile = tile, tileCenter = MapView.cellSize * new Vector2(x + 1, y + 1) }; + VisibleTile invisibleTile = new VisibleTile { tile = tile, tileCenter = OldMapView.cellSize * new Vector2(x + 1, y + 1) }; layer.drawObject(this, gD, tile, invisibleTile.tileCenter); } } @@ -557,7 +557,7 @@ private static bool IsTileKnown(Tile tile, UIGameDataAccess gameDataAccess) { } } -public partial class MapView : Node2D { +public partial class OldMapView : Node2D { // cellSize is half the size of the tile sprites, or the amount of space each tile takes up when they are packed on the grid (note tiles are // staggered and half overlap). public static readonly Vector2 cellSize = new Vector2(64, 32); @@ -606,7 +606,7 @@ public int getRowStartX(int y) public GridLayer gridLayer { get; private set; } public ImageTexture civColorWhitePalette = null; - public MapView(Game game, int mapWidth, int mapHeight, bool wrapHorizontally, bool wrapVertically) + public OldMapView(Game game, int mapWidth, int mapHeight, bool wrapHorizontally, bool wrapVertically) { this.game = game; this.mapWidth = mapWidth; diff --git a/C7/PlayerCamera.cs b/C7/PlayerCamera.cs index 5cb29686..dc029a4a 100644 --- a/C7/PlayerCamera.cs +++ b/C7/PlayerCamera.cs @@ -5,10 +5,21 @@ public partial class PlayerCamera : Camera2D { private readonly float maxZoom = 2.0f; private readonly float minZoom = 0.2f; - private float zoomFactor = 1.0f; + public float zoomFactor {get; private set; } = 1.0f; public override void _Ready() { base._Ready(); + scaleZoom(zoomFactor); + } + + public void scaleZoom(float factor) { + zoomFactor = zoomFactor * factor; + zoomFactor = Mathf.Clamp(zoomFactor, minZoom, maxZoom); + Zoom = Vector2.One * zoomFactor; + } + + public void setZoom(float factor) { + zoomFactor = Mathf.Clamp(factor, minZoom, maxZoom); Zoom = Vector2.One * zoomFactor; } @@ -17,8 +28,7 @@ public override void _UnhandledInput(InputEvent @event) { Position -= mm.Relative / Zoom; } if (@event is InputEventMagnifyGesture mg) { - zoomFactor = Mathf.Clamp(zoomFactor * mg.Factor, minZoom, maxZoom); - Zoom = Vector2.One * zoomFactor; + scaleZoom(mg.Factor); } } diff --git a/C7/project.godot b/C7/project.godot index 3caefaa0..e9954fa5 100644 --- a/C7/project.godot +++ b/C7/project.godot @@ -196,4 +196,5 @@ limits/debugger_stdout/max_chars_per_second=65535 textures/canvas_textures/default_texture_filter=0 environment/defaults/default_clear_color=Color(0.301961, 0.301961, 0.301961, 1) +2d/snap/snap_2d_vertices_to_pixel=true environment/default_environment="res://default_env.tres" diff --git a/C7/tests/TestUnit.cs b/C7/tests/TestUnit.cs index f5ac535a..cba9a7e1 100644 --- a/C7/tests/TestUnit.cs +++ b/C7/tests/TestUnit.cs @@ -1,12 +1,8 @@ using Godot; -using System; -using ConvertCiv3Media; using C7GameData; public partial class TestUnit : Node2D { - - // Called when the node enters the scene tree for the first time. public override void _Ready() { AnimationManager manager = new AnimationManager(null); @@ -31,9 +27,4 @@ public override void _Ready() cursor.Play("cursor"); AddChild(cursor); } - - // Called every frame. 'delta' is the elapsed time since the previous frame. - public override void _Process(double delta) - { - } } From 46427a3ca5b2e371bde9eeb8e0ea58de8c4b290e Mon Sep 17 00:00:00 2001 From: Paul Cento Date: Tue, 20 Jun 2023 19:53:42 -0400 Subject: [PATCH 07/60] render roads in tilemap --- C7/Game.cs | 5 +++ C7/Map/MapView.cs | 86 ++++++++++++++++++++++++++++++++------------- C7/Map/RoadLayer.cs | 2 +- 3 files changed, 67 insertions(+), 26 deletions(-) diff --git a/C7/Game.cs b/C7/Game.cs index e34b4dec..05168198 100644 --- a/C7/Game.cs +++ b/C7/Game.cs @@ -340,6 +340,11 @@ public override void _UnhandledInput(InputEvent @event) { if (to_select != null && to_select.owner == controller) setSelectedUnit(to_select); } + if (!tile.IsWater()) { + tile.overlays.road = true; + mapView.updateTile(tile); + } + GD.Print("tile updated"); } } } diff --git a/C7/Map/MapView.cs b/C7/Map/MapView.cs index 9fbca04d..71eaccc5 100644 --- a/C7/Map/MapView.cs +++ b/C7/Map/MapView.cs @@ -1,6 +1,7 @@ using Godot; using System.Collections.Generic; using C7GameData; +using System; namespace C7.Map { @@ -43,6 +44,14 @@ private TileSet makeTileSet() { }; } + private void addUniformTilesToAtlasSource(ref TileSetAtlasSource source, int width, int height) { + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + source.CreateTile(new Vector2I(x, y)); + } + } + } + private void initializeTileMap() { this.terrainTilemap = new TileMap(); terrainTilemap.TextureFilter = TextureFilterEnum.Nearest; @@ -53,11 +62,7 @@ private void initializeTileMap() { TileSetAtlasSource source = new TileSetAtlasSource(); source.Texture = texture; source.TextureRegionSize = tileSize; - for (int x = 0; x < 9; x++) { - for (int y = 0; y < 9; y++) { - source.CreateTile(new Vector2I(x, y)); - } - } + addUniformTilesToAtlasSource(ref source, 9, 9); terrainTileset.AddSource(source); } terrainTilemap.TileSet = terrainTileset; @@ -66,9 +71,16 @@ private void initializeTileMap() { tilemap = new TileMap(); tileset = makeTileSet(); tilemap.TileSet = tileset; + TileSetAtlasSource roads = new TileSetAtlasSource{ + Texture = Util.LoadTextureFromPCX("Art/Terrain/roads.pcx"), + TextureRegionSize = tileSize, + }; + addUniformTilesToAtlasSource(ref roads, 16, 16); + tileset.AddSource(roads); - AddChild(terrainTilemap); + tilemap.ZIndex = 100; AddChild(tilemap); + AddChild(terrainTilemap); } private void setTerrainTiles() { @@ -87,30 +99,19 @@ private void setTerrainTiles() { bottom = even ? terrain[x, y + 1] : terrain[(x + 1) % width, y + 1]; } string[] corner = new string[4]{top, right, bottom, left}; - TerrainPcx pcx = getPcxForCorner(corner); + TerrainPcx pcx = terrainPcxList.Find(pcx => pcx.validFor(corner)); Vector2I texCoords = pcx.getTextureCoords(corner); - fill(cell, pcx.atlas, texCoords); + setTerrainTile(cell, pcx.atlas, texCoords); } } } - public override void _UnhandledInput(InputEvent @event) { - base._UnhandledInput(@event); - // if (@event is InputEventMouseButton mb && mb.IsPressed()) { - // Vector2 mousePosition = GetGlobalMousePosition(); - // Vector2I tile = tilemap.LocalToMap(ToLocal(mousePosition)); - // GD.Print($"clicked on tile {tile.ToString()}"); - // string terrainName = terrain[tile.X, tile.Y]; - // GD.Print($"terrain type is {terrainName}"); - // } - } - - private TerrainPcx getPcxForCorner(string[] corner) { - return terrainPcxList.Find(tpcx => tpcx.validFor(corner)); + void setTerrainTile(Vector2I cell, int atlas, Vector2I texCoords) { + terrainTilemap.SetCell(0, cell, atlas, texCoords); } - void fill(Vector2I cell, int atlas, Vector2I texCoords) { - terrainTilemap.SetCell(0, cell, atlas, texCoords); + void setTile(Vector2I cell, int atlas, Vector2I texCoords) { + tilemap.SetCell(0, cell, atlas, texCoords); } private Vector2I stackedCoords(int x, int y) { @@ -119,8 +120,7 @@ private Vector2I stackedCoords(int x, int y) { } private (int, int) unstackedCoords(Vector2I stacked) { - int x = stacked.X; - int y = stacked.Y; + (int x, int y) = (stacked.X, stacked.Y); x = y % 2 == 0 ? x * 2 : (x * 2) + 1; return (x, y); } @@ -163,5 +163,41 @@ public Tile tileAt(GameMap gameMap, Vector2 globalMousePosition) { (int x, int y) = unstackedCoords(tilemapCoord); return gameMap.tileAt(x, y); } + + public void updateTile(Tile tile, bool center = true) { + // update terrain ? + if (tile.overlays.road) { + int index = 0; + foreach (KeyValuePair neighbor in tile.neighbors) { + if (neighbor.Value.overlays.road) { + index |= roadFlag(neighbor.Key); + } + } + int row = index >> 4; + int column = index & 0xF; + Vector2I texCoords = new Vector2I(column, row); + setTile(stackedCoords(tile.xCoordinate, tile.yCoordinate), 0, texCoords); + if (center) { + foreach (Tile neighbor in tile.neighbors.Values) { + updateTile(neighbor, false); + } + } + } + } + + private static int roadFlag(TileDirection direction) { + return direction switch { + TileDirection.NORTHEAST => 0x1, + TileDirection.EAST => 0x2, + TileDirection.SOUTHEAST => 0x4, + TileDirection.SOUTH => 0x8, + TileDirection.SOUTHWEST => 0x10, + TileDirection.WEST => 0x20, + TileDirection.NORTHWEST => 0x40, + TileDirection.NORTH => 0x80, + _ => throw new ArgumentOutOfRangeException("Invalid TileDirection") + }; + } + } } diff --git a/C7/Map/RoadLayer.cs b/C7/Map/RoadLayer.cs index 9fe2cfe9..a9dfb31c 100644 --- a/C7/Map/RoadLayer.cs +++ b/C7/Map/RoadLayer.cs @@ -26,7 +26,7 @@ public override void drawObject(LooseView looseView, GameData gameData, Tile til if (!hasRailRoad(tile)) { int roadIndex = 0; foreach (KeyValuePair dirToTile in tile.neighbors) { - if (hasRoad(dirToTile.Value)) { + if (dirToTile.Value.overlays.road) { roadIndex |= getFlag(dirToTile.Key); } } From 1b9bf26699a3b9632545bbe5424338bbf0a8a7de Mon Sep 17 00:00:00 2001 From: Paul Cento Date: Tue, 20 Jun 2023 20:12:56 -0400 Subject: [PATCH 08/60] add logic for drawing rails on tilemap - missing separate layer --- C7/Game.cs | 1 - C7/Map/MapView.cs | 64 +++++++++++++++++++++++++++++++++++---------- C7/Map/RoadLayer.cs | 2 +- 3 files changed, 51 insertions(+), 16 deletions(-) diff --git a/C7/Game.cs b/C7/Game.cs index 05168198..d30af0e1 100644 --- a/C7/Game.cs +++ b/C7/Game.cs @@ -344,7 +344,6 @@ public override void _UnhandledInput(InputEvent @event) { tile.overlays.road = true; mapView.updateTile(tile); } - GD.Print("tile updated"); } } } diff --git a/C7/Map/MapView.cs b/C7/Map/MapView.cs index 71eaccc5..3d06c51b 100644 --- a/C7/Map/MapView.cs +++ b/C7/Map/MapView.cs @@ -76,9 +76,14 @@ private void initializeTileMap() { TextureRegionSize = tileSize, }; addUniformTilesToAtlasSource(ref roads, 16, 16); + TileSetAtlasSource rails = new TileSetAtlasSource{ + Texture = Util.LoadTextureFromPCX("Art/Terrain/railroads.pcx"), + TextureRegionSize = tileSize, + }; tileset.AddSource(roads); + tileset.AddSource(rails); - tilemap.ZIndex = 100; + tilemap.ZIndex = 10; // need to figure out a good way to order z indices AddChild(tilemap); AddChild(terrainTilemap); } @@ -164,24 +169,48 @@ public Tile tileAt(GameMap gameMap, Vector2 globalMousePosition) { return gameMap.tileAt(x, y); } - public void updateTile(Tile tile, bool center = true) { - // update terrain ? - if (tile.overlays.road) { + private Vector2I roadIndexTo2D(int index) { + int column = index & 0xF; + int row = index >> 4; + return new Vector2I(column, row); + } + + private void updateRoadLayer(Tile tile, bool center) { + if (!tile.overlays.road) { + return; + } + if (!tile.overlays.railroad) { + // road int index = 0; - foreach (KeyValuePair neighbor in tile.neighbors) { - if (neighbor.Value.overlays.road) { - index |= roadFlag(neighbor.Key); + foreach ((TileDirection direction, Tile neighbor) in tile.neighbors) { + if (neighbor.overlays.road) { + index |= roadFlag(direction); } } - int row = index >> 4; - int column = index & 0xF; - Vector2I texCoords = new Vector2I(column, row); - setTile(stackedCoords(tile.xCoordinate, tile.yCoordinate), 0, texCoords); - if (center) { - foreach (Tile neighbor in tile.neighbors.Values) { - updateTile(neighbor, false); + setTile(stackedCoords(tile.xCoordinate, tile.yCoordinate), 0, roadIndexTo2D(index)); + } else { + // railroad + int roadIndex = 0; + int railIndex = 0; + foreach ((TileDirection direction, Tile neighbor) in tile.neighbors) { + if (neighbor.overlays.railroad) { + railIndex |= roadFlag(direction); + } else if (neighbor.overlays.road) { + roadIndex |= roadFlag(direction); } } + if (roadIndex != 0) { + setTile(stackedCoords(tile.xCoordinate, tile.yCoordinate), 0, roadIndexTo2D(roadIndex)); + } + // TODO: this needs to go on a different layer in the tilemap + setTile(stackedCoords(tile.xCoordinate, tile.yCoordinate), 1, roadIndexTo2D(railIndex)); + } + + if (center) { + // updating a tile may change neighboring tiles + foreach (Tile neighbor in tile.neighbors.Values) { + updateRoadLayer(neighbor, false); + } } } @@ -199,5 +228,12 @@ private static int roadFlag(TileDirection direction) { }; } + public void updateTile(Tile tile) { + // update terrain ? + if (tile.overlays.road) { + updateRoadLayer(tile, true); + } + } + } } diff --git a/C7/Map/RoadLayer.cs b/C7/Map/RoadLayer.cs index a9dfb31c..9fe2cfe9 100644 --- a/C7/Map/RoadLayer.cs +++ b/C7/Map/RoadLayer.cs @@ -26,7 +26,7 @@ public override void drawObject(LooseView looseView, GameData gameData, Tile til if (!hasRailRoad(tile)) { int roadIndex = 0; foreach (KeyValuePair dirToTile in tile.neighbors) { - if (dirToTile.Value.overlays.road) { + if (hasRoad(dirToTile.Value)) { roadIndex |= getFlag(dirToTile.Key); } } From 6af39d63872292a1c636bfd115afc4a63a3f1635 Mon Sep 17 00:00:00 2001 From: Paul Cento Date: Tue, 20 Jun 2023 21:31:53 -0400 Subject: [PATCH 09/60] working rails layer --- C7/Game.cs | 3 + C7/Map/Civ3TerrainTileset.cs | 103 +++++++++++++++++++++++++++++++++++ C7/Map/MapView.cs | 77 +++++++++++++------------- C7/Map/TerrainPcx.cs | 47 ---------------- 4 files changed, 146 insertions(+), 84 deletions(-) create mode 100644 C7/Map/Civ3TerrainTileset.cs delete mode 100644 C7/Map/TerrainPcx.cs diff --git a/C7/Game.cs b/C7/Game.cs index d30af0e1..3764bb9d 100644 --- a/C7/Game.cs +++ b/C7/Game.cs @@ -342,6 +342,9 @@ public override void _UnhandledInput(InputEvent @event) { } if (!tile.IsWater()) { tile.overlays.road = true; + if (eventMouseButton.ShiftPressed) { + tile.overlays.railroad = true; + } mapView.updateTile(tile); } } diff --git a/C7/Map/Civ3TerrainTileset.cs b/C7/Map/Civ3TerrainTileset.cs new file mode 100644 index 00000000..ba344cc6 --- /dev/null +++ b/C7/Map/Civ3TerrainTileset.cs @@ -0,0 +1,103 @@ +using Godot; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace C7.Map { + + class TerrainPcx { + private static Random prng = new Random(); + private string name; + // abc refers to the layout of the terrain tiles in the pcx based on + // the positions of each terrain at the corner of 4 tiles. + // - https://forums.civfanatics.com/threads/terrain-editing.622999/ + // - https://forums.civfanatics.com/threads/editing-terrain-pcx-files.102840/ + private string[] abc; + public int atlas; + public TerrainPcx(string name, string[] abc, int atlas) { + this.name = name; + this.abc = abc; + this.atlas = atlas; + } + public bool validFor(string[] corner) { + return corner.All(tile => abc.Contains(tile)); + } + private int abcIndex(string terrain) { + List indices = new List(); + for (int i = 0; i < abc.Count(); i++) { + if (abc[i] == terrain) { + indices.Add(i); + } + } + return indices[prng.Next(indices.Count)]; + } + + // getTextureCoords looks up the correct texture index in the pcx + // for the given position of each corner terrain type + public Vector2I getTextureCoords(string[] corner) { + int top = abcIndex(corner[0]); + int right = abcIndex(corner[1]); + int bottom = abcIndex(corner[2]); + int left = abcIndex(corner[3]); + int index = top + (left * 3) + (right * 9) + (bottom * 27); + return new Vector2I(index % 9, index / 9); + } + } + + // Civ3TerrainTileSet loads civ3 terrain pcx files and generates a tileset + class Civ3TerrainTileSet { + // same order as terrainPcxList + private static readonly List terrainPcxFiles = new List { + "Art/Terrain/xtgc.pcx", "Art/Terrain/xpgc.pcx", "Art/Terrain/xdgc.pcx", + "Art/Terrain/xdpc.pcx", "Art/Terrain/xdgp.pcx", "Art/Terrain/xggc.pcx", + "Art/Terrain/wCSO.pcx", "Art/Terrain/wSSS.pcx", "Art/Terrain/wOOO.pcx", + }; + + // same order as terrainPcxFiles + private static readonly List terrainPcxList = new List() { + new TerrainPcx("tgc", new string[]{"tundra", "grassland", "coast"}, 0), + new TerrainPcx("pgc", new string[]{"plains", "grassland", "coast"}, 1), + new TerrainPcx("dgc", new string[]{"desert", "grassland", "coast"}, 2), + new TerrainPcx("dpc", new string[]{"desert", "plains", "coast"}, 3), + new TerrainPcx("dgp", new string[]{"desert", "grassland", "plains"}, 4), + new TerrainPcx("ggc", new string[]{"grassland", "grassland", "coast"}, 5), + new TerrainPcx("cso", new string[]{"coast", "sea", "ocean"}, 6), + new TerrainPcx("sss", new string[]{"sea", "sea", "sea"}, 7), + new TerrainPcx("ooo", new string[]{"ocean", "ocean", "ocean"}, 8), + }; + + private static readonly Vector2I terrainTileSize = new Vector2I(128, 64); + + public static TileSet Generate() { + List textures = terrainPcxFiles.ConvertAll(path => Util.LoadTextureFromPCX(path)); + TileSet tileset = new TileSet{ + TileShape = TileSet.TileShapeEnum.Isometric, + TileLayout = TileSet.TileLayoutEnum.Stacked, + TileOffsetAxis = TileSet.TileOffsetAxisEnum.Horizontal, + TileSize = terrainTileSize, + }; + foreach (ImageTexture texture in textures) { + TileSetAtlasSource source = new TileSetAtlasSource{ + Texture = texture, + TextureRegionSize = terrainTileSize, + }; + for (int x = 0; x < 9; x++) { + for (int y = 0; y < 9; y++) { + source.CreateTile(new Vector2I(x, y)); + } + } + tileset.AddSource(source); + } + return tileset; + } + + public static TerrainPcx GetPcxFor(string[] corners) { + if (corners.Length != 4) { + throw new ArgumentException($"terrain corner must be of 4 tiles but got {corners.Length}"); + } + return terrainPcxList.Find(pcx => pcx.validFor(corners)); + } + + } + +} diff --git a/C7/Map/MapView.cs b/C7/Map/MapView.cs index 3d06c51b..b2946fd1 100644 --- a/C7/Map/MapView.cs +++ b/C7/Map/MapView.cs @@ -5,31 +5,33 @@ namespace C7.Map { + public enum MapLayer { + Road, + Rail, + Invalid, + }; + + public static class MapLayerMethods { + public static (int, int) LayerAndAtlas(this MapLayer mapLayer) { + return mapLayer switch { + MapLayer.Road => (0, 0), + MapLayer.Rail => (1, 1), + MapLayer.Invalid => throw new ArgumentException("MapLayer.Invalid has no tilemap layer"), + _ => throw new ArgumentException($"unknown MapLayer enum value: ${mapLayer}"), + }; + } + public static int Layer(this MapLayer mapLayer) { + (int layer, _) = mapLayer.LayerAndAtlas(); + return layer; + } + }; + partial class MapView : Node2D { - // same order as terrainPcxList - private List terrainPcxFiles = new List { - "Art/Terrain/xtgc.pcx", "Art/Terrain/xpgc.pcx", "Art/Terrain/xdgc.pcx", - "Art/Terrain/xdpc.pcx", "Art/Terrain/xdgp.pcx", "Art/Terrain/xggc.pcx", - "Art/Terrain/wCSO.pcx", "Art/Terrain/wSSS.pcx", "Art/Terrain/wOOO.pcx", - }; - // same order as terrainPcxFiles - private List terrainPcxList = new List() { - new TerrainPcx("tgc", new string[]{"tundra", "grassland", "coast"}, 0), - new TerrainPcx("pgc", new string[]{"plains", "grassland", "coast"}, 1), - new TerrainPcx("dgc", new string[]{"desert", "grassland", "coast"}, 2), - new TerrainPcx("dpc", new string[]{"desert", "plains", "coast"}, 3), - new TerrainPcx("dgp", new string[]{"desert", "grassland", "plains"}, 4), - new TerrainPcx("ggc", new string[]{"grassland", "grassland", "coast"}, 5), - new TerrainPcx("cso", new string[]{"coast", "sea", "ocean"}, 6), - new TerrainPcx("sss", new string[]{"sea", "sea", "sea"}, 7), - new TerrainPcx("ooo", new string[]{"ocean", "ocean", "ocean"}, 8), - }; private string[,]terrain; private TileMap terrainTilemap; private TileSet terrainTileset; private TileMap tilemap; private TileSet tileset; - private List textures; private Vector2I tileSize = new Vector2I(128, 64); private int width; private int height; @@ -53,20 +55,10 @@ private void addUniformTilesToAtlasSource(ref TileSetAtlasSource source, int wid } private void initializeTileMap() { - this.terrainTilemap = new TileMap(); - terrainTilemap.TextureFilter = TextureFilterEnum.Nearest; - terrainTileset = makeTileSet(); - - // register 9 x 9 layout of tiles in each terrain pcx - foreach (ImageTexture texture in textures) { - TileSetAtlasSource source = new TileSetAtlasSource(); - source.Texture = texture; - source.TextureRegionSize = tileSize; - addUniformTilesToAtlasSource(ref source, 9, 9); - terrainTileset.AddSource(source); - } + terrainTilemap = new TileMap(); + terrainTileset = Civ3TerrainTileSet.Generate(); terrainTilemap.TileSet = terrainTileset; - terrainTilemap.Position += Vector2I.Right * (width / 2); + terrainTilemap.Position += Vector2I.Right * (tileSize.X / 2); tilemap = new TileMap(); tileset = makeTileSet(); @@ -80,8 +72,16 @@ private void initializeTileMap() { Texture = Util.LoadTextureFromPCX("Art/Terrain/railroads.pcx"), TextureRegionSize = tileSize, }; + addUniformTilesToAtlasSource(ref rails, 16, 16); tileset.AddSource(roads); tileset.AddSource(rails); + // create tilemap layers + foreach (MapLayer mapLayer in Enum.GetValues(typeof(MapLayer))) { + if (mapLayer != MapLayer.Invalid) { + GD.Print($"layer {mapLayer.Layer()}"); + tilemap.AddLayer(mapLayer.Layer()); + } + } tilemap.ZIndex = 10; // need to figure out a good way to order z indices AddChild(tilemap); @@ -104,7 +104,7 @@ private void setTerrainTiles() { bottom = even ? terrain[x, y + 1] : terrain[(x + 1) % width, y + 1]; } string[] corner = new string[4]{top, right, bottom, left}; - TerrainPcx pcx = terrainPcxList.Find(pcx => pcx.validFor(corner)); + TerrainPcx pcx = Civ3TerrainTileSet.GetPcxFor(corner); Vector2I texCoords = pcx.getTextureCoords(corner); setTerrainTile(cell, pcx.atlas, texCoords); } @@ -131,7 +131,6 @@ private Vector2I stackedCoords(int x, int y) { } public MapView(Game game, GameData data) { - textures = terrainPcxFiles.ConvertAll(path => Util.LoadTextureFromPCX(path)); gameMap = data.map; width = gameMap.numTilesWide / 2; height = gameMap.numTilesTall; @@ -175,6 +174,11 @@ private Vector2I roadIndexTo2D(int index) { return new Vector2I(column, row); } + private void setCell(MapLayer mapLayer, Vector2I coords, Vector2I atlasCoords) { + (int layer, int atlas) = mapLayer.LayerAndAtlas(); + tilemap.SetCell(layer, coords, atlas, atlasCoords); + } + private void updateRoadLayer(Tile tile, bool center) { if (!tile.overlays.road) { return; @@ -200,10 +204,9 @@ private void updateRoadLayer(Tile tile, bool center) { } } if (roadIndex != 0) { - setTile(stackedCoords(tile.xCoordinate, tile.yCoordinate), 0, roadIndexTo2D(roadIndex)); + setCell(MapLayer.Road, stackedCoords(tile.xCoordinate, tile.yCoordinate), roadIndexTo2D(roadIndex)); } - // TODO: this needs to go on a different layer in the tilemap - setTile(stackedCoords(tile.xCoordinate, tile.yCoordinate), 1, roadIndexTo2D(railIndex)); + setCell(MapLayer.Rail, stackedCoords(tile.xCoordinate, tile.yCoordinate), roadIndexTo2D(railIndex)); } if (center) { diff --git a/C7/Map/TerrainPcx.cs b/C7/Map/TerrainPcx.cs deleted file mode 100644 index 29f22303..00000000 --- a/C7/Map/TerrainPcx.cs +++ /dev/null @@ -1,47 +0,0 @@ -using Godot; -using System; -using System.Collections.Generic; -using System.Linq; - -namespace C7.Map { - - class TerrainPcx { - private static Random prng = new Random(); - private string name; - // abc refers to the layout of the terrain tiles in the pcx based on - // the positions of each terrain at the corner of 4 tiles. - // - https://forums.civfanatics.com/threads/terrain-editing.622999/ - // - https://forums.civfanatics.com/threads/editing-terrain-pcx-files.102840/ - private string[] abc; - public int atlas; - public TerrainPcx(string name, string[] abc, int atlas) { - this.name = name; - this.abc = abc; - this.atlas = atlas; - } - public bool validFor(string[] corner) { - return corner.All(tile => abc.Contains(tile)); - } - private int abcIndex(string terrain) { - List indices = new List(); - for (int i = 0; i < abc.Count(); i++) { - if (abc[i] == terrain) { - indices.Add(i); - } - } - return indices[prng.Next(indices.Count)]; - } - - // getTextureCoords looks up the correct texture index in the pcx - // for the given position of each corner terrain type - public Vector2I getTextureCoords(string[] corner) { - int top = abcIndex(corner[0]); - int right = abcIndex(corner[1]); - int bottom = abcIndex(corner[2]); - int left = abcIndex(corner[3]); - int index = top + (left * 3) + (right * 9) + (bottom * 27); - return new Vector2I(index % 9, index / 9); - } - } - -} From d5031ea8a143fed7f2d9d95495305af19ee04c11 Mon Sep 17 00:00:00 2001 From: Paul Cento Date: Tue, 20 Jun 2023 21:44:32 -0400 Subject: [PATCH 10/60] simple abstraction for loading atlas sources --- C7/Map/MapView.cs | 44 ++++++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/C7/Map/MapView.cs b/C7/Map/MapView.cs index b2946fd1..20feb1c3 100644 --- a/C7/Map/MapView.cs +++ b/C7/Map/MapView.cs @@ -54,6 +54,15 @@ private void addUniformTilesToAtlasSource(ref TileSetAtlasSource source, int wid } } + private TileSetAtlasSource loadAtlasSource(string relPath, Vector2I tileSize, int width, int height) { + TileSetAtlasSource source = new TileSetAtlasSource{ + Texture = Util.LoadTextureFromPCX(relPath), + TextureRegionSize = tileSize, + }; + addUniformTilesToAtlasSource(ref source, width, height); + return source; + } + private void initializeTileMap() { terrainTilemap = new TileMap(); terrainTileset = Civ3TerrainTileSet.Generate(); @@ -63,22 +72,17 @@ private void initializeTileMap() { tilemap = new TileMap(); tileset = makeTileSet(); tilemap.TileSet = tileset; - TileSetAtlasSource roads = new TileSetAtlasSource{ - Texture = Util.LoadTextureFromPCX("Art/Terrain/roads.pcx"), - TextureRegionSize = tileSize, - }; - addUniformTilesToAtlasSource(ref roads, 16, 16); - TileSetAtlasSource rails = new TileSetAtlasSource{ - Texture = Util.LoadTextureFromPCX("Art/Terrain/railroads.pcx"), - TextureRegionSize = tileSize, - }; - addUniformTilesToAtlasSource(ref rails, 16, 16); + TileSetAtlasSource roads = loadAtlasSource("Art/Terrain/roads.pcx", tileSize, 16, 16); + TileSetAtlasSource rails = loadAtlasSource("Art/Terrain/railroads.pcx", tileSize, 16, 16); + + // the order in which these are added determines their atlas ID + // TODO: associate pcx file(s) with MapLayer enum to ensure they + // are added in the correct order wrt their atlas ID tileset.AddSource(roads); tileset.AddSource(rails); // create tilemap layers foreach (MapLayer mapLayer in Enum.GetValues(typeof(MapLayer))) { if (mapLayer != MapLayer.Invalid) { - GD.Print($"layer {mapLayer.Layer()}"); tilemap.AddLayer(mapLayer.Layer()); } } @@ -119,7 +123,9 @@ void setTile(Vector2I cell, int atlas, Vector2I texCoords) { tilemap.SetCell(0, cell, atlas, texCoords); } - private Vector2I stackedCoords(int x, int y) { + private Vector2I stackedCoords(Tile tile) { + int x = tile.xCoordinate; + int y = tile.yCoordinate; x = y % 2 == 0 ? x / 2 : (x - 1) / 2; return new Vector2I(x, y); } @@ -138,7 +144,7 @@ public MapView(Game game, GameData data) { terrain = new string[width, height]; foreach (C7GameData.Tile t in gameMap.tiles) { - Vector2I coords = stackedCoords(t.xCoordinate, t.yCoordinate); + Vector2I coords = stackedCoords(t); terrain[coords.X, coords.Y] = t.baseTerrainTypeKey; } setTerrainTiles(); @@ -150,7 +156,7 @@ public MapView(Game game, GameData data) { UnitSprite sprite = new UnitSprite(game.civ3AnimData); MapUnit.Appearance appearance = game.animTracker.getUnitAppearance(unit); - var coords = stackedCoords(tile.xCoordinate, tile.yCoordinate); + var coords = stackedCoords(tile); sprite.Position = tilemap.MapToLocal(coords); game.civ3AnimData.forUnit(unit.unitType, appearance.action).loadSpriteAnimation(); @@ -169,9 +175,7 @@ public Tile tileAt(GameMap gameMap, Vector2 globalMousePosition) { } private Vector2I roadIndexTo2D(int index) { - int column = index & 0xF; - int row = index >> 4; - return new Vector2I(column, row); + return new Vector2I(index & 0xF, index >> 4); } private void setCell(MapLayer mapLayer, Vector2I coords, Vector2I atlasCoords) { @@ -191,7 +195,7 @@ private void updateRoadLayer(Tile tile, bool center) { index |= roadFlag(direction); } } - setTile(stackedCoords(tile.xCoordinate, tile.yCoordinate), 0, roadIndexTo2D(index)); + setCell(MapLayer.Road, stackedCoords(tile), roadIndexTo2D(index)); } else { // railroad int roadIndex = 0; @@ -204,9 +208,9 @@ private void updateRoadLayer(Tile tile, bool center) { } } if (roadIndex != 0) { - setCell(MapLayer.Road, stackedCoords(tile.xCoordinate, tile.yCoordinate), roadIndexTo2D(roadIndex)); + setCell(MapLayer.Road, stackedCoords(tile), roadIndexTo2D(roadIndex)); } - setCell(MapLayer.Rail, stackedCoords(tile.xCoordinate, tile.yCoordinate), roadIndexTo2D(railIndex)); + setCell(MapLayer.Rail, stackedCoords(tile), roadIndexTo2D(railIndex)); } if (center) { From 7bcdafb7cdad6bcc459c93cc97235850ec7e3b80 Mon Sep 17 00:00:00 2001 From: Paul Cento Date: Tue, 20 Jun 2023 22:12:14 -0400 Subject: [PATCH 11/60] add resources and clear cells when empty --- C7/Map/MapView.cs | 54 +++++++++++++++++++++++++++--- C7/Map/RoadLayer.cs | 80 --------------------------------------------- C7/OldMapView.cs | 3 -- 3 files changed, 49 insertions(+), 88 deletions(-) delete mode 100644 C7/Map/RoadLayer.cs diff --git a/C7/Map/MapView.cs b/C7/Map/MapView.cs index 20feb1c3..9804d2c8 100644 --- a/C7/Map/MapView.cs +++ b/C7/Map/MapView.cs @@ -8,14 +8,17 @@ namespace C7.Map { public enum MapLayer { Road, Rail, + Resource, Invalid, }; public static class MapLayerMethods { public static (int, int) LayerAndAtlas(this MapLayer mapLayer) { return mapLayer switch { + // (layer, atlas) MapLayer.Road => (0, 0), MapLayer.Rail => (1, 1), + MapLayer.Resource => (2, 2), MapLayer.Invalid => throw new ArgumentException("MapLayer.Invalid has no tilemap layer"), _ => throw new ArgumentException($"unknown MapLayer enum value: ${mapLayer}"), }; @@ -33,6 +36,7 @@ partial class MapView : Node2D { private TileMap tilemap; private TileSet tileset; private Vector2I tileSize = new Vector2I(128, 64); + private Vector2I resourceSize = new Vector2I(50, 50); private int width; private int height; private GameMap gameMap; @@ -75,11 +79,25 @@ private void initializeTileMap() { TileSetAtlasSource roads = loadAtlasSource("Art/Terrain/roads.pcx", tileSize, 16, 16); TileSetAtlasSource rails = loadAtlasSource("Art/Terrain/railroads.pcx", tileSize, 16, 16); + TileSetAtlasSource resources = new TileSetAtlasSource{ + Texture = Util.LoadTextureFromPCX("Art/resources.pcx"), + TextureRegionSize = resourceSize, + }; + for (int y = 0; y < 4; y++) { + for (int x = 0; x < 6; x++) { + if (x == 4 && y == 3) { + break; + } + resources.CreateTile(new Vector2I(x, y)); + } + } + // the order in which these are added determines their atlas ID // TODO: associate pcx file(s) with MapLayer enum to ensure they // are added in the correct order wrt their atlas ID tileset.AddSource(roads); tileset.AddSource(rails); + tileset.AddSource(resources); // create tilemap layers foreach (MapLayer mapLayer in Enum.GetValues(typeof(MapLayer))) { if (mapLayer != MapLayer.Invalid) { @@ -149,6 +167,10 @@ public MapView(Game game, GameData data) { } setTerrainTiles(); + foreach (Tile tile in gameMap.tiles) { + updateTile(tile); + } + // temp but place units in current position foreach (Tile tile in gameMap.tiles) { if (tile.unitsOnTile.Count > 0) { @@ -183,6 +205,19 @@ private void setCell(MapLayer mapLayer, Vector2I coords, Vector2I atlasCoords) { tilemap.SetCell(layer, coords, atlas, atlasCoords); } + private void setCell(MapLayer mapLayer, Tile tile, Vector2I atlasCoords) { + setCell(mapLayer, stackedCoords(tile), atlasCoords); + } + + private void eraseCell(MapLayer mapLayer, Vector2I coords) { + int layer = mapLayer.Layer(); + tilemap.EraseCell(layer, coords); + } + + private void eraseCell(MapLayer mapLayer, Tile tile) { + eraseCell(mapLayer, stackedCoords(tile)); + } + private void updateRoadLayer(Tile tile, bool center) { if (!tile.overlays.road) { return; @@ -195,7 +230,8 @@ private void updateRoadLayer(Tile tile, bool center) { index |= roadFlag(direction); } } - setCell(MapLayer.Road, stackedCoords(tile), roadIndexTo2D(index)); + setCell(MapLayer.Road, tile, roadIndexTo2D(index)); + eraseCell(MapLayer.Rail, tile); } else { // railroad int roadIndex = 0; @@ -208,9 +244,11 @@ private void updateRoadLayer(Tile tile, bool center) { } } if (roadIndex != 0) { - setCell(MapLayer.Road, stackedCoords(tile), roadIndexTo2D(roadIndex)); + setCell(MapLayer.Road, tile, roadIndexTo2D(roadIndex)); + } else { + eraseCell(MapLayer.Road, tile); } - setCell(MapLayer.Rail, stackedCoords(tile), roadIndexTo2D(railIndex)); + setCell(MapLayer.Rail, tile, roadIndexTo2D(railIndex)); } if (center) { @@ -237,8 +275,14 @@ private static int roadFlag(TileDirection direction) { public void updateTile(Tile tile) { // update terrain ? - if (tile.overlays.road) { - updateRoadLayer(tile, true); + updateRoadLayer(tile, true); + + if (tile.Resource != C7GameData.Resource.NONE) { + int index = tile.Resource.Index; + Vector2I texCoord = new Vector2I(index % 6, index / 6); + setCell(MapLayer.Resource, tile, texCoord); + } else { + eraseCell(MapLayer.Resource, tile); } } diff --git a/C7/Map/RoadLayer.cs b/C7/Map/RoadLayer.cs deleted file mode 100644 index 9fe2cfe9..00000000 --- a/C7/Map/RoadLayer.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System; -using System.Collections.Generic; -using C7GameData; -using Godot; - -namespace C7.Map -{ - public partial class RoadLayer : LooseLayer { - private readonly ImageTexture roadTexture; - private readonly ImageTexture railroadTexture; - private readonly Vector2 tileSize; - - public RoadLayer() { - roadTexture = Util.LoadTextureFromPCX("Art/Terrain/roads.pcx"); - railroadTexture = Util.LoadTextureFromPCX("Art/Terrain/railroads.pcx"); - tileSize = roadTexture.GetSize() / 16; - // grid 16x16 tiles - // assume that roads and railroads textures have the same size - } - - public override void drawObject(LooseView looseView, GameData gameData, Tile tile, Vector2 tileCenter) { - if (!hasRoad(tile)) return; - - Rect2 screenTarget = new Rect2(tileCenter - tileSize / 2, tileSize); - - if (!hasRailRoad(tile)) { - int roadIndex = 0; - foreach (KeyValuePair dirToTile in tile.neighbors) { - if (hasRoad(dirToTile.Value)) { - roadIndex |= getFlag(dirToTile.Key); - } - } - looseView.DrawTextureRectRegion(roadTexture, screenTarget, getRect(roadIndex)); - } else { - // has railroad - int roadIndex = 0; - int railroadIndex = 0; - foreach (KeyValuePair dirToTile in tile.neighbors) { - if (hasRailRoad(dirToTile.Value)) { - railroadIndex |= getFlag(dirToTile.Key); - } else if (hasRoad(dirToTile.Value)) { - roadIndex |= getFlag(dirToTile.Key); - } - } - if (roadIndex != 0) { - looseView.DrawTextureRectRegion(roadTexture, screenTarget, getRect(roadIndex)); - } - looseView.DrawTextureRectRegion(railroadTexture, screenTarget, getRect(railroadIndex)); - } - } - - private Rect2 getRect(int index) { - int row = index >> 4; - int column = index & 0xF; - return new Rect2(column * tileSize.X, row * tileSize.Y, tileSize); - } - - private static int getFlag(TileDirection direction) { - return direction switch { - TileDirection.NORTHEAST => 0x1, - TileDirection.EAST => 0x2, - TileDirection.SOUTHEAST => 0x4, - TileDirection.SOUTH => 0x8, - TileDirection.SOUTHWEST => 0x10, - TileDirection.WEST => 0x20, - TileDirection.NORTHWEST => 0x40, - TileDirection.NORTH => 0x80, - _ => throw new ArgumentOutOfRangeException("Invalid TileDirection") - }; - } - - private static bool hasRoad(Tile tile) { - return tile.overlays.road; - } - - private static bool hasRailRoad(Tile tile) { - return tile.overlays.railroad; - } - } -} diff --git a/C7/OldMapView.cs b/C7/OldMapView.cs index 6c7ce410..cc46a71a 100644 --- a/C7/OldMapView.cs +++ b/C7/OldMapView.cs @@ -615,18 +615,15 @@ public OldMapView(Game game, int mapWidth, int mapHeight, bool wrapHorizontally, this.wrapVertically = wrapVertically; looseView = new LooseView(this); - // looseView.layers.Add(new TerrainLayer()); looseView.layers.Add(new RiverLayer()); looseView.layers.Add(new ForestLayer()); looseView.layers.Add(new MarshLayer()); looseView.layers.Add(new HillsLayer()); looseView.layers.Add(new TntLayer()); - looseView.layers.Add(new RoadLayer()); looseView.layers.Add(new ResourceLayer()); this.gridLayer = new GridLayer(); looseView.layers.Add(this.gridLayer); looseView.layers.Add(new BuildingLayer()); - // looseView.layers.Add(new UnitLayer()); looseView.layers.Add(new CityLayer()); // looseView.layers.Add(new FogOfWarLayer()); From e633c15aae925bfc97a62ae0dc1ef0cd547231d1 Mon Sep 17 00:00:00 2001 From: Paul Cento Date: Tue, 20 Jun 2023 22:12:58 -0400 Subject: [PATCH 12/60] track net lines changed --- C7/Map/ResourceLayer.cs | 38 -------------------------------------- 1 file changed, 38 deletions(-) delete mode 100644 C7/Map/ResourceLayer.cs diff --git a/C7/Map/ResourceLayer.cs b/C7/Map/ResourceLayer.cs deleted file mode 100644 index 75454f9a..00000000 --- a/C7/Map/ResourceLayer.cs +++ /dev/null @@ -1,38 +0,0 @@ -using C7GameData; -using Godot; -using Resource = C7GameData.Resource; -using Serilog; - -namespace C7.Map -{ - public partial class ResourceLayer : LooseLayer - { - private ILogger log = LogManager.ForContext(); - - private static readonly Vector2 resourceSize = new Vector2(50, 50); - private int maxRow; - private ImageTexture resourceTexture; - - public ResourceLayer() - { - resourceTexture = Util.LoadTextureFromPCX("Art/resources.pcx"); - maxRow = (resourceTexture.GetHeight() / 50) - 1; - } - public override void drawObject(LooseView looseView, GameData gameData, Tile tile, Vector2 tileCenter) - { - Resource resource = tile.Resource; - if (resource != Resource.NONE) { - int resourceIcon = tile.Resource.Icon; - int row = resourceIcon / 6; - int col = resourceIcon % 6; - if (row > maxRow) { - log.Warning("Resource icon for " + resource.Name + " is too high"); - return; - } - Rect2 resourceRectangle = new Rect2(col * resourceSize.X, row * resourceSize.Y, resourceSize); - Rect2 screenTarget = new Rect2(tileCenter - 0.5f * resourceSize, resourceSize); - looseView.DrawTextureRectRegion(resourceTexture, screenTarget, resourceRectangle); - } - } - } -} From 0c3a0e7d798c138c09a3473c58338253466af07b Mon Sep 17 00:00:00 2001 From: Paul Cento Date: Wed, 21 Jun 2023 02:07:03 -0400 Subject: [PATCH 13/60] working rivers --- C7/Map/MapView.cs | 84 +++++++++++++++++++++++++++++++++++++++------- C7/Map/TntLayer.cs | 44 ------------------------ C7/OldMapView.cs | 49 --------------------------- 3 files changed, 72 insertions(+), 105 deletions(-) delete mode 100644 C7/Map/TntLayer.cs diff --git a/C7/Map/MapView.cs b/C7/Map/MapView.cs index 9804d2c8..d55a4ae7 100644 --- a/C7/Map/MapView.cs +++ b/C7/Map/MapView.cs @@ -1,7 +1,7 @@ using Godot; -using System.Collections.Generic; using C7GameData; using System; +using Serilog; namespace C7.Map { @@ -9,6 +9,8 @@ public enum MapLayer { Road, Rail, Resource, + TerrainYield, + River, Invalid, }; @@ -16,9 +18,12 @@ public static class MapLayerMethods { public static (int, int) LayerAndAtlas(this MapLayer mapLayer) { return mapLayer switch { // (layer, atlas) - MapLayer.Road => (0, 0), - MapLayer.Rail => (1, 1), - MapLayer.Resource => (2, 2), + // TODO: figure out how many can go on the same layer? + MapLayer.River => (0, 0), + MapLayer.Road => (1, 1), + MapLayer.Rail => (2, 2), + MapLayer.Resource => (3, 3), + MapLayer.TerrainYield => (4, 4), MapLayer.Invalid => throw new ArgumentException("MapLayer.Invalid has no tilemap layer"), _ => throw new ArgumentException($"unknown MapLayer enum value: ${mapLayer}"), }; @@ -27,6 +32,11 @@ public static int Layer(this MapLayer mapLayer) { (int layer, _) = mapLayer.LayerAndAtlas(); return layer; } + + public static int Atlas(this MapLayer mapLayer) { + (_, int atlas) = mapLayer.LayerAndAtlas(); + return atlas; + } }; partial class MapView : Node2D { @@ -37,6 +47,8 @@ partial class MapView : Node2D { private TileSet tileset; private Vector2I tileSize = new Vector2I(128, 64); private Vector2I resourceSize = new Vector2I(50, 50); + private ILogger log = LogManager.ForContext(); + private int width; private int height; private GameMap gameMap; @@ -92,12 +104,15 @@ private void initializeTileMap() { } } - // the order in which these are added determines their atlas ID - // TODO: associate pcx file(s) with MapLayer enum to ensure they - // are added in the correct order wrt their atlas ID - tileset.AddSource(roads); - tileset.AddSource(rails); - tileset.AddSource(resources); + TileSetAtlasSource tnt = loadAtlasSource("Art/Terrain/tnt.pcx", tileSize, 3, 6); + TileSetAtlasSource rivers = loadAtlasSource("Art/Terrain/mtnRivers.pcx", tileSize, 4, 4); + + tileset.AddSource(roads, MapLayer.Road.Atlas()); + tileset.AddSource(rails, MapLayer.Rail.Atlas()); + tileset.AddSource(resources, MapLayer.Resource.Atlas()); + tileset.AddSource(tnt, MapLayer.TerrainYield.Atlas()); + tileset.AddSource(rivers, MapLayer.River.Atlas()); + // create tilemap layers foreach (MapLayer mapLayer in Enum.GetValues(typeof(MapLayer))) { if (mapLayer != MapLayer.Invalid) { @@ -161,6 +176,12 @@ public MapView(Game game, GameData data) { initializeTileMap(); terrain = new string[width, height]; + // Convert coordinates from current save coordinates to + // stacked coordinates used by Godot's TileMap, and + // write terrain types to 2D array for generating corners + // TODO in the future convert civ3 coordinates to stacked + // coordinates when reading from the civ3 save so Tile has + // stacked coordinates foreach (C7GameData.Tile t in gameMap.tiles) { Vector2I coords = stackedCoords(t); terrain[coords.X, coords.Y] = t.baseTerrainTypeKey; @@ -202,6 +223,9 @@ private Vector2I roadIndexTo2D(int index) { private void setCell(MapLayer mapLayer, Vector2I coords, Vector2I atlasCoords) { (int layer, int atlas) = mapLayer.LayerAndAtlas(); + if (!tileset.HasSource(atlas)) { + log.Warning($"atlas id {atlas} is not a valid tileset source"); + } tilemap.SetCell(layer, coords, atlas, atlasCoords); } @@ -220,6 +244,8 @@ private void eraseCell(MapLayer mapLayer, Tile tile) { private void updateRoadLayer(Tile tile, bool center) { if (!tile.overlays.road) { + eraseCell(MapLayer.Road, tile); + eraseCell(MapLayer.Rail, tile); return; } if (!tile.overlays.railroad) { @@ -230,8 +256,8 @@ private void updateRoadLayer(Tile tile, bool center) { index |= roadFlag(direction); } } - setCell(MapLayer.Road, tile, roadIndexTo2D(index)); eraseCell(MapLayer.Rail, tile); + setCell(MapLayer.Road, tile, roadIndexTo2D(index)); } else { // railroad int roadIndex = 0; @@ -273,8 +299,34 @@ private static int roadFlag(TileDirection direction) { }; } + private void updateRiverLayer(Tile tile) { + // The "point" is the easternmost point of the current tile. + // The river graphic is determined by the tiles neighboring that point. + Tile northOfPoint = tile.neighbors[TileDirection.NORTHEAST]; + Tile eastOfPoint = tile.neighbors[TileDirection.EAST]; + Tile westOfPoint = tile; + Tile southOfPoint = tile.neighbors[TileDirection.SOUTHEAST]; + + int index = 0; + index += northOfPoint.riverSouthwest ? 1 : 0; + index += eastOfPoint.riverNorthwest ? 2 : 0; + index += westOfPoint.riverSoutheast ? 4 : 0; + index += southOfPoint.riverNortheast ? 8 : 0; + + if (index == 0) { + eraseCell(MapLayer.River, tile); + } else { + setCell(MapLayer.River, tile, new Vector2I(index % 4, index / 4)); + } + } + public void updateTile(Tile tile) { - // update terrain ? + if (tile == Tile.NONE || tile is null) { + string msg = tile is null ? "null tile" : "Tile.NONE"; + log.Warning($"attempting to update {msg}"); + return; + } + updateRoadLayer(tile, true); if (tile.Resource != C7GameData.Resource.NONE) { @@ -284,6 +336,14 @@ public void updateTile(Tile tile) { } else { eraseCell(MapLayer.Resource, tile); } + + if (tile.baseTerrainType.Key == "grassland" && tile.isBonusShield) { + setCell(MapLayer.TerrainYield, tile, new Vector2I(0, 3)); + } else { + eraseCell(MapLayer.TerrainYield, tile); + } + + updateRiverLayer(tile); } } diff --git a/C7/Map/TntLayer.cs b/C7/Map/TntLayer.cs deleted file mode 100644 index cfd0a20a..00000000 --- a/C7/Map/TntLayer.cs +++ /dev/null @@ -1,44 +0,0 @@ -using C7GameData; -using Godot; -using Resource = C7GameData.Resource; -using Serilog; - -namespace C7.Map -{ - /// - /// Displays terrain yield overlays (from the tnt.pcx file). These are most well known for letting you know where - /// there are bonus grasslands. - /// Note: I don't know why it's called tnt. - /// - public partial class TntLayer : LooseLayer - { - private ILogger log = LogManager.ForContext(); - - private static readonly Vector2 tntSize = new Vector2(128, 64); - private ImageTexture tntTexture; - - //Each row corresponds to a terrain. For now we're only adding one, maybe someday we'll add full TNT support -#pragma warning disable CS0414 - private readonly int GRASSLAND_ROW = 0; - private readonly int BONUS_GRASSLAND_ROW = 1; - private readonly int PLAINS_ROW = 2; - private readonly int DESERT_ROW = 3; - private readonly int BONUS_GRASSLAND_TNT_OFF_ROW = 3; - private readonly int TUNDRA_ROW = 4; - private readonly int FLOOD_PLAIN_ROW = 5; -#pragma warning restore CS0414 - - public TntLayer() - { - tntTexture = Util.LoadTextureFromPCX("Art/Terrain/tnt.pcx"); - } - public override void drawObject(LooseView looseView, GameData gameData, Tile tile, Vector2 tileCenter) - { - if (tile.overlayTerrainType.Key == "grassland" && tile.isBonusShield) { - Rect2 tntRectangle = new Rect2(0, BONUS_GRASSLAND_TNT_OFF_ROW * tntSize.Y, tntSize); - Rect2 screenTarget = new Rect2(tileCenter - 0.5f * tntSize, tntSize); - looseView.DrawTextureRectRegion(tntTexture, screenTarget, tntRectangle); - } - } - } -} diff --git a/C7/OldMapView.cs b/C7/OldMapView.cs index cc46a71a..b22930d4 100644 --- a/C7/OldMapView.cs +++ b/C7/OldMapView.cs @@ -403,52 +403,6 @@ public override void drawObject(LooseView looseView, GameData gameData, Tile til } } -public partial class RiverLayer : LooseLayer -{ - public static readonly Vector2 riverSize = new Vector2(128, 64); - public static readonly Vector2 riverCenterOffset = new Vector2(riverSize.X / 2, 0); - private ImageTexture riverTexture; - - public RiverLayer() { - riverTexture = Util.LoadTextureFromPCX("Art/Terrain/mtnRivers.pcx"); - } - - public override void drawObject(LooseView looseView, GameData gameData, Tile tile, Vector2 tileCenter) - { - //The "point" is the easternmost point of the tile for which we are drawing rivers. - //Which river graphics to used is calculated by evaluating the tiles that neighbor - //that point. - Tile northOfPoint = tile.neighbors[TileDirection.NORTHEAST]; - Tile eastOfPoint = tile.neighbors[TileDirection.EAST]; - Tile westOfPoint = tile; - Tile southOfPoint = tile.neighbors[TileDirection.SOUTHEAST]; - - int riverGraphicsIndex = 0; - - if (northOfPoint.riverSouthwest) { - riverGraphicsIndex++; - } - if (eastOfPoint.riverNorthwest) { - riverGraphicsIndex+=2; - } - if (westOfPoint.riverSoutheast) { - riverGraphicsIndex+=4; - } - if (southOfPoint.riverNortheast) { - riverGraphicsIndex+=8; - } - if (riverGraphicsIndex == 0) { - return; - } - int riverRow = riverGraphicsIndex / 4; - int riverColumn = riverGraphicsIndex % 4; - - Rect2 riverRectangle = new Rect2(riverColumn * riverSize.X, riverRow * riverSize.Y, riverSize); - Rect2 screenTarget = new Rect2(tileCenter - (float)0.5 * riverSize + riverCenterOffset, riverSize); - looseView.DrawTextureRectRegion(riverTexture, screenTarget, riverRectangle); - } -} - public partial class GridLayer : LooseLayer { public Color color = Color.Color8(50, 50, 50, 150); public float lineWidth = (float)1.0; @@ -615,12 +569,9 @@ public OldMapView(Game game, int mapWidth, int mapHeight, bool wrapHorizontally, this.wrapVertically = wrapVertically; looseView = new LooseView(this); - looseView.layers.Add(new RiverLayer()); looseView.layers.Add(new ForestLayer()); looseView.layers.Add(new MarshLayer()); looseView.layers.Add(new HillsLayer()); - looseView.layers.Add(new TntLayer()); - looseView.layers.Add(new ResourceLayer()); this.gridLayer = new GridLayer(); looseView.layers.Add(this.gridLayer); looseView.layers.Add(new BuildingLayer()); From e9395cb81b5f19d9531b3f79d0764f2238c885ba Mon Sep 17 00:00:00 2001 From: Paul Cento Date: Wed, 21 Jun 2023 02:25:03 -0400 Subject: [PATCH 14/60] separate notion of layer and atlas in MapView --- C7/Map/MapView.cs | 116 +++++++++++++++++++++------------------------- 1 file changed, 53 insertions(+), 63 deletions(-) diff --git a/C7/Map/MapView.cs b/C7/Map/MapView.cs index d55a4ae7..8771e7e9 100644 --- a/C7/Map/MapView.cs +++ b/C7/Map/MapView.cs @@ -5,39 +5,35 @@ namespace C7.Map { - public enum MapLayer { + public enum Layer { + River, Road, Rail, Resource, TerrainYield, - River, Invalid, }; - public static class MapLayerMethods { - public static (int, int) LayerAndAtlas(this MapLayer mapLayer) { - return mapLayer switch { - // (layer, atlas) - // TODO: figure out how many can go on the same layer? - MapLayer.River => (0, 0), - MapLayer.Road => (1, 1), - MapLayer.Rail => (2, 2), - MapLayer.Resource => (3, 3), - MapLayer.TerrainYield => (4, 4), - MapLayer.Invalid => throw new ArgumentException("MapLayer.Invalid has no tilemap layer"), - _ => throw new ArgumentException($"unknown MapLayer enum value: ${mapLayer}"), - }; - } - public static int Layer(this MapLayer mapLayer) { - (int layer, _) = mapLayer.LayerAndAtlas(); - return layer; + public static class LayerExtensions { + public static int Index(this Layer layer) { + return (int)layer; } + } + + public enum Atlas { + River, + Road, + Rail, + Resource, + TerrainYield, + Invalid, + } - public static int Atlas(this MapLayer mapLayer) { - (_, int atlas) = mapLayer.LayerAndAtlas(); - return atlas; + public static class AtlasExtensions { + public static int Index(this Atlas atlas) { + return (int)atlas; } - }; + } partial class MapView : Node2D { private string[,]terrain; @@ -107,16 +103,16 @@ private void initializeTileMap() { TileSetAtlasSource tnt = loadAtlasSource("Art/Terrain/tnt.pcx", tileSize, 3, 6); TileSetAtlasSource rivers = loadAtlasSource("Art/Terrain/mtnRivers.pcx", tileSize, 4, 4); - tileset.AddSource(roads, MapLayer.Road.Atlas()); - tileset.AddSource(rails, MapLayer.Rail.Atlas()); - tileset.AddSource(resources, MapLayer.Resource.Atlas()); - tileset.AddSource(tnt, MapLayer.TerrainYield.Atlas()); - tileset.AddSource(rivers, MapLayer.River.Atlas()); + tileset.AddSource(roads, Atlas.Road.Index()); + tileset.AddSource(rails, Atlas.Rail.Index()); + tileset.AddSource(resources, Atlas.Resource.Index()); + tileset.AddSource(tnt, Atlas.TerrainYield.Index()); + tileset.AddSource(rivers, Atlas.River.Index()); // create tilemap layers - foreach (MapLayer mapLayer in Enum.GetValues(typeof(MapLayer))) { - if (mapLayer != MapLayer.Invalid) { - tilemap.AddLayer(mapLayer.Layer()); + foreach (Layer layer in Enum.GetValues(typeof(Layer))) { + if (layer != Layer.Invalid) { + tilemap.AddLayer(layer.Index()); } } @@ -152,10 +148,6 @@ void setTerrainTile(Vector2I cell, int atlas, Vector2I texCoords) { terrainTilemap.SetCell(0, cell, atlas, texCoords); } - void setTile(Vector2I cell, int atlas, Vector2I texCoords) { - tilemap.SetCell(0, cell, atlas, texCoords); - } - private Vector2I stackedCoords(Tile tile) { int x = tile.xCoordinate; int y = tile.yCoordinate; @@ -217,35 +209,29 @@ public Tile tileAt(GameMap gameMap, Vector2 globalMousePosition) { return gameMap.tileAt(x, y); } - private Vector2I roadIndexTo2D(int index) { - return new Vector2I(index & 0xF, index >> 4); - } - - private void setCell(MapLayer mapLayer, Vector2I coords, Vector2I atlasCoords) { - (int layer, int atlas) = mapLayer.LayerAndAtlas(); - if (!tileset.HasSource(atlas)) { + private void setCell(Layer layer, Atlas atlas, Vector2I coords, Vector2I atlasCoords) { + if (!tileset.HasSource(atlas.Index())) { log.Warning($"atlas id {atlas} is not a valid tileset source"); } - tilemap.SetCell(layer, coords, atlas, atlasCoords); + tilemap.SetCell(layer.Index(), coords, atlas.Index(), atlasCoords); } - private void setCell(MapLayer mapLayer, Tile tile, Vector2I atlasCoords) { - setCell(mapLayer, stackedCoords(tile), atlasCoords); + private void setCell(Layer layer, Atlas atlas, Tile tile, Vector2I atlasCoords) { + setCell(layer, atlas, stackedCoords(tile), atlasCoords); } - private void eraseCell(MapLayer mapLayer, Vector2I coords) { - int layer = mapLayer.Layer(); - tilemap.EraseCell(layer, coords); + private void eraseCell(Layer layer, Vector2I coords) { + tilemap.EraseCell(layer.Index(), coords); } - private void eraseCell(MapLayer mapLayer, Tile tile) { - eraseCell(mapLayer, stackedCoords(tile)); + private void eraseCell(Layer layer, Tile tile) { + eraseCell(layer, stackedCoords(tile)); } private void updateRoadLayer(Tile tile, bool center) { if (!tile.overlays.road) { - eraseCell(MapLayer.Road, tile); - eraseCell(MapLayer.Rail, tile); + eraseCell(Layer.Road, tile); + eraseCell(Layer.Rail, tile); return; } if (!tile.overlays.railroad) { @@ -256,8 +242,8 @@ private void updateRoadLayer(Tile tile, bool center) { index |= roadFlag(direction); } } - eraseCell(MapLayer.Rail, tile); - setCell(MapLayer.Road, tile, roadIndexTo2D(index)); + eraseCell(Layer.Rail, tile); + setCell(Layer.Road, Atlas.Road, tile, roadIndexTo2D(index)); } else { // railroad int roadIndex = 0; @@ -270,11 +256,11 @@ private void updateRoadLayer(Tile tile, bool center) { } } if (roadIndex != 0) { - setCell(MapLayer.Road, tile, roadIndexTo2D(roadIndex)); + setCell(Layer.Road, Atlas.Road, tile, roadIndexTo2D(roadIndex)); } else { - eraseCell(MapLayer.Road, tile); + eraseCell(Layer.Road, tile); } - setCell(MapLayer.Rail, tile, roadIndexTo2D(railIndex)); + setCell(Layer.Rail, Atlas.Rail, tile, roadIndexTo2D(railIndex)); } if (center) { @@ -285,6 +271,10 @@ private void updateRoadLayer(Tile tile, bool center) { } } + private Vector2I roadIndexTo2D(int index) { + return new Vector2I(index & 0xF, index >> 4); + } + private static int roadFlag(TileDirection direction) { return direction switch { TileDirection.NORTHEAST => 0x1, @@ -314,9 +304,9 @@ private void updateRiverLayer(Tile tile) { index += southOfPoint.riverNortheast ? 8 : 0; if (index == 0) { - eraseCell(MapLayer.River, tile); + eraseCell(Layer.River, tile); } else { - setCell(MapLayer.River, tile, new Vector2I(index % 4, index / 4)); + setCell(Layer.River, Atlas.River, tile, new Vector2I(index % 4, index / 4)); } } @@ -332,15 +322,15 @@ public void updateTile(Tile tile) { if (tile.Resource != C7GameData.Resource.NONE) { int index = tile.Resource.Index; Vector2I texCoord = new Vector2I(index % 6, index / 6); - setCell(MapLayer.Resource, tile, texCoord); + setCell(Layer.Resource, Atlas.Resource, tile, texCoord); } else { - eraseCell(MapLayer.Resource, tile); + eraseCell(Layer.Resource, tile); } if (tile.baseTerrainType.Key == "grassland" && tile.isBonusShield) { - setCell(MapLayer.TerrainYield, tile, new Vector2I(0, 3)); + setCell(Layer.TerrainYield, Atlas.TerrainYield, tile, new Vector2I(0, 3)); } else { - eraseCell(MapLayer.TerrainYield, tile); + eraseCell(Layer.TerrainYield, tile); } updateRiverLayer(tile); From 623ae5acfb134034e636317a6ddd58d5f684b43c Mon Sep 17 00:00:00 2001 From: Paul Cento Date: Wed, 21 Jun 2023 03:48:31 -0400 Subject: [PATCH 15/60] add mountains --- C7/Map/MapView.cs | 136 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 124 insertions(+), 12 deletions(-) diff --git a/C7/Map/MapView.cs b/C7/Map/MapView.cs index 8771e7e9..b2dc8752 100644 --- a/C7/Map/MapView.cs +++ b/C7/Map/MapView.cs @@ -2,10 +2,12 @@ using C7GameData; using System; using Serilog; +using System.Linq; namespace C7.Map { public enum Layer { + Hill, River, Road, Rail, @@ -21,6 +23,14 @@ public static int Index(this Layer layer) { } public enum Atlas { + Hill, + ForestHill, + JungleHill, + Mountain, + SnowMountain, + ForestMountain, + JungleMountain, + Volcano, River, Road, Rail, @@ -42,6 +52,8 @@ partial class MapView : Node2D { private TileMap tilemap; private TileSet tileset; private Vector2I tileSize = new Vector2I(128, 64); + private Vector2I hillSize = new Vector2I(128, 72); + private Vector2I mountainSize = new Vector2I(128, 88); private Vector2I resourceSize = new Vector2I(50, 50); private ILogger log = LogManager.ForContext(); @@ -75,13 +87,21 @@ private TileSetAtlasSource loadAtlasSource(string relPath, Vector2I tileSize, in return source; } + private void addUniformOffsetsToAtlasSource(ref TileSetAtlasSource source, int width, int height, Vector2I offset) { + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + source.GetTileData(new Vector2I(x, y), 0).TextureOrigin = offset; + } + } + } + private void initializeTileMap() { terrainTilemap = new TileMap(); terrainTileset = Civ3TerrainTileSet.Generate(); terrainTilemap.TileSet = terrainTileset; terrainTilemap.Position += Vector2I.Right * (tileSize.X / 2); - tilemap = new TileMap(); + tilemap = new TileMap{ YSortEnabled = true }; tileset = makeTileSet(); tilemap.TileSet = tileset; TileSetAtlasSource roads = loadAtlasSource("Art/Terrain/roads.pcx", tileSize, 16, 16); @@ -101,13 +121,37 @@ private void initializeTileMap() { } TileSetAtlasSource tnt = loadAtlasSource("Art/Terrain/tnt.pcx", tileSize, 3, 6); + TileSetAtlasSource rivers = loadAtlasSource("Art/Terrain/mtnRivers.pcx", tileSize, 4, 4); + TileSetAtlasSource hills = loadAtlasSource("Art/Terrain/xhills.pcx", hillSize, 4, 4); + addUniformOffsetsToAtlasSource(ref hills, 4, 4, new Vector2I(0, 4)); + TileSetAtlasSource forestHills = loadAtlasSource("Art/Terrain/hill forests.pcx", hillSize, 4, 4); + addUniformOffsetsToAtlasSource(ref forestHills, 4, 4, new Vector2I(0, 4)); + TileSetAtlasSource jungleHills = loadAtlasSource("Art/Terrain/hill jungle.pcx", hillSize, 4, 4); + addUniformOffsetsToAtlasSource(ref jungleHills, 4, 4, new Vector2I(0, 4)); + + TileSetAtlasSource mountain = loadAtlasSource("Art/Terrain/Mountains.pcx", mountainSize, 4, 4); + addUniformOffsetsToAtlasSource(ref mountain, 4, 4, new Vector2I(0, 12)); + TileSetAtlasSource snowMountain = loadAtlasSource("Art/Terrain/Mountains-snow.pcx", mountainSize, 4, 4); + addUniformOffsetsToAtlasSource(ref snowMountain, 4, 4, new Vector2I(0, 12)); + TileSetAtlasSource forestMountain = loadAtlasSource("Art/Terrain/mountain forests.pcx", mountainSize, 4, 4); + addUniformOffsetsToAtlasSource(ref forestMountain, 4, 4, new Vector2I(0, 12)); + TileSetAtlasSource jungleMountain = loadAtlasSource("Art/Terrain/mountain jungles.pcx", mountainSize, 4, 4); + addUniformOffsetsToAtlasSource(ref jungleMountain, 4, 4, new Vector2I(0, 12)); + tileset.AddSource(roads, Atlas.Road.Index()); tileset.AddSource(rails, Atlas.Rail.Index()); tileset.AddSource(resources, Atlas.Resource.Index()); tileset.AddSource(tnt, Atlas.TerrainYield.Index()); tileset.AddSource(rivers, Atlas.River.Index()); + tileset.AddSource(hills, Atlas.Hill.Index()); + tileset.AddSource(forestHills, Atlas.ForestHill.Index()); + tileset.AddSource(jungleHills, Atlas.JungleHill.Index()); + tileset.AddSource(mountain, Atlas.Mountain.Index()); + tileset.AddSource(snowMountain, Atlas.SnowMountain.Index()); + tileset.AddSource(forestMountain, Atlas.ForestMountain.Index()); + tileset.AddSource(jungleMountain, Atlas.JungleMountain.Index()); // create tilemap layers foreach (Layer layer in Enum.GetValues(typeof(Layer))) { @@ -180,6 +224,7 @@ public MapView(Game game, GameData data) { } setTerrainTiles(); + // update each tile once to add all initial layers foreach (Tile tile in gameMap.tiles) { updateTile(tile); } @@ -209,23 +254,15 @@ public Tile tileAt(GameMap gameMap, Vector2 globalMousePosition) { return gameMap.tileAt(x, y); } - private void setCell(Layer layer, Atlas atlas, Vector2I coords, Vector2I atlasCoords) { + private void setCell(Layer layer, Atlas atlas, Tile tile, Vector2I atlasCoords) { if (!tileset.HasSource(atlas.Index())) { log.Warning($"atlas id {atlas} is not a valid tileset source"); } - tilemap.SetCell(layer.Index(), coords, atlas.Index(), atlasCoords); - } - - private void setCell(Layer layer, Atlas atlas, Tile tile, Vector2I atlasCoords) { - setCell(layer, atlas, stackedCoords(tile), atlasCoords); - } - - private void eraseCell(Layer layer, Vector2I coords) { - tilemap.EraseCell(layer.Index(), coords); + tilemap.SetCell(layer.Index(), stackedCoords(tile), atlas.Index(), atlasCoords); } private void eraseCell(Layer layer, Tile tile) { - eraseCell(layer, stackedCoords(tile)); + tilemap.EraseCell(layer.Index(), stackedCoords(tile)); } private void updateRoadLayer(Tile tile, bool center) { @@ -310,6 +347,79 @@ private void updateRiverLayer(Tile tile) { } } + private void updateHillLayer(Tile tile) { + if (!tile.overlayTerrainType.isHilly()) { + eraseCell(Layer.Hill, tile); + return; + } + Vector2I texCoord = getHillTextureCoordinate(tile); + TerrainType nearbyVegitation = getDominantVegetationNearHillyTile(tile); + switch (tile.overlayTerrainType.Key) { + case "hills": + Atlas hillAtlas = nearbyVegitation.Key switch { + "forest" => Atlas.ForestHill, + "jungle" => Atlas.JungleHill, + _ => Atlas.Hill, + }; + setCell(Layer.Hill, hillAtlas, tile, texCoord); + break; + case "mountains": + Atlas mountainAtlas = nearbyVegitation.Key switch { + "forest" => Atlas.ForestMountain, + "jungle" => Atlas.JungleMountain, + _ => tile.isSnowCapped ? Atlas.SnowMountain : Atlas.Mountain, + }; + setCell(Layer.Hill, mountainAtlas, tile, texCoord); + break; + default: + break; + } + } + + private Vector2I getHillTextureCoordinate(Tile tile) { + int index = 0; + if (tile.neighbors[TileDirection.NORTHWEST].overlayTerrainType.isHilly()) { + index++; + } + if (tile.neighbors[TileDirection.NORTHEAST].overlayTerrainType.isHilly()) { + index+=2; + } + if (tile.neighbors[TileDirection.SOUTHWEST].overlayTerrainType.isHilly()) { + index+=4; + } + if (tile.neighbors[TileDirection.SOUTHEAST].overlayTerrainType.isHilly()) { + index+=8; + } + return new Vector2I(index % 4, index / 4); + } + + private TerrainType getDominantVegetationNearHillyTile(Tile center) { + TerrainType northeastType = center.neighbors[TileDirection.NORTHEAST].overlayTerrainType; + TerrainType northwestType = center.neighbors[TileDirection.NORTHWEST].overlayTerrainType; + TerrainType southeastType = center.neighbors[TileDirection.SOUTHEAST].overlayTerrainType; + TerrainType southwestType = center.neighbors[TileDirection.SOUTHWEST].overlayTerrainType; + + TerrainType[] neighborTerrains = { northeastType, northwestType, southeastType, southwestType }; + + int hills = neighborTerrains.Where(tt => tt.isHilly()).Count(); + TerrainType forest = neighborTerrains.FirstOrDefault(tt => tt.Key == "forest", null); + int forests = neighborTerrains.Where(tt => tt.Key == "forest").Count(); + TerrainType jungle = neighborTerrains.FirstOrDefault(tt => tt.Key == "jungle", null); + int jungles = neighborTerrains.Where(tt => tt.Key == "jungle").Count(); + + if (hills + forests + jungles < 4) { // some surrounding tiles are neither forested nor hilly + return TerrainType.NONE; + } + if (forests == 0 && jungles == 0) { + return TerrainType.NONE; // all hills + } + if (forests == jungles) { + // deterministically choose one on a tie so it doesn't change if the tile is updated + return center.xCoordinate % 2 == 0 ? forest : jungle; + } + return forests > jungles ? forest : jungle; + } + public void updateTile(Tile tile) { if (tile == Tile.NONE || tile is null) { string msg = tile is null ? "null tile" : "Tile.NONE"; @@ -334,6 +444,8 @@ public void updateTile(Tile tile) { } updateRiverLayer(tile); + + updateHillLayer(tile); } } From 4d203f5621125d69a8c99262efb79464e7a9ea59 Mon Sep 17 00:00:00 2001 From: Paul Cento Date: Wed, 21 Jun 2023 16:57:34 -0400 Subject: [PATCH 16/60] get rid of incorrect y offsets (tiles are already centered) --- C7/Game.cs | 8 +----- C7/Map/MapView.cs | 70 +++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 60 insertions(+), 18 deletions(-) diff --git a/C7/Game.cs b/C7/Game.cs index 3764bb9d..16350b33 100644 --- a/C7/Game.cs +++ b/C7/Game.cs @@ -340,13 +340,7 @@ public override void _UnhandledInput(InputEvent @event) { if (to_select != null && to_select.owner == controller) setSelectedUnit(to_select); } - if (!tile.IsWater()) { - tile.overlays.road = true; - if (eventMouseButton.ShiftPressed) { - tile.overlays.railroad = true; - } - mapView.updateTile(tile); - } + GD.Print($"tile: {tile.xCoordinate}, {tile.yCoordinate}: {tile.baseTerrainType.Key} - {tile.overlayTerrainType.Key}"); } } } diff --git a/C7/Map/MapView.cs b/C7/Map/MapView.cs index b2dc8752..1afd029d 100644 --- a/C7/Map/MapView.cs +++ b/C7/Map/MapView.cs @@ -7,6 +7,7 @@ namespace C7.Map { public enum Layer { + Forest, Hill, River, Road, @@ -31,6 +32,8 @@ public enum Atlas { ForestMountain, JungleMountain, Volcano, + PlainsForest, + GrasslandsForest, River, Road, Rail, @@ -54,6 +57,7 @@ partial class MapView : Node2D { private Vector2I tileSize = new Vector2I(128, 64); private Vector2I hillSize = new Vector2I(128, 72); private Vector2I mountainSize = new Vector2I(128, 88); + private Vector2I forestSize = new Vector2I(128, 88); private Vector2I resourceSize = new Vector2I(50, 50); private ILogger log = LogManager.ForContext(); @@ -87,12 +91,28 @@ private TileSetAtlasSource loadAtlasSource(string relPath, Vector2I tileSize, in return source; } - private void addUniformOffsetsToAtlasSource(ref TileSetAtlasSource source, int width, int height, Vector2I offset) { - for (int x = 0; x < width; x++) { - for (int y = 0; y < height; y++) { - source.GetTileData(new Vector2I(x, y), 0).TextureOrigin = offset; + // private void addUniformOffsetsToAtlasSource(ref TileSetAtlasSource source, int width, int height, Vector2I offset) { + // for (int x = 0; x < width; x++) { + // for (int y = 0; y < height; y++) { + // source.GetTileData(new Vector2I(x, y), 0).TextureOrigin = offset; + // } + // } + // } + + private TileSetAtlasSource loadForestSource(string relPath) { + TileSetAtlasSource source = new TileSetAtlasSource{ + Texture = Util.LoadTextureFromPCX(relPath), + TextureRegionSize = forestSize, + }; + for (int x = 0; x < 6; x++) { + for (int y = 4; y < 10; y++) { + if ((y < 6 && x > 3) || (y < 8 && x > 4)) { + continue; // forest tilemap is shaped like this + } + source.CreateTile(new Vector2I(x, y)); } } + return source; } private void initializeTileMap() { @@ -125,20 +145,16 @@ private void initializeTileMap() { TileSetAtlasSource rivers = loadAtlasSource("Art/Terrain/mtnRivers.pcx", tileSize, 4, 4); TileSetAtlasSource hills = loadAtlasSource("Art/Terrain/xhills.pcx", hillSize, 4, 4); - addUniformOffsetsToAtlasSource(ref hills, 4, 4, new Vector2I(0, 4)); TileSetAtlasSource forestHills = loadAtlasSource("Art/Terrain/hill forests.pcx", hillSize, 4, 4); - addUniformOffsetsToAtlasSource(ref forestHills, 4, 4, new Vector2I(0, 4)); TileSetAtlasSource jungleHills = loadAtlasSource("Art/Terrain/hill jungle.pcx", hillSize, 4, 4); - addUniformOffsetsToAtlasSource(ref jungleHills, 4, 4, new Vector2I(0, 4)); TileSetAtlasSource mountain = loadAtlasSource("Art/Terrain/Mountains.pcx", mountainSize, 4, 4); - addUniformOffsetsToAtlasSource(ref mountain, 4, 4, new Vector2I(0, 12)); TileSetAtlasSource snowMountain = loadAtlasSource("Art/Terrain/Mountains-snow.pcx", mountainSize, 4, 4); - addUniformOffsetsToAtlasSource(ref snowMountain, 4, 4, new Vector2I(0, 12)); TileSetAtlasSource forestMountain = loadAtlasSource("Art/Terrain/mountain forests.pcx", mountainSize, 4, 4); - addUniformOffsetsToAtlasSource(ref forestMountain, 4, 4, new Vector2I(0, 12)); TileSetAtlasSource jungleMountain = loadAtlasSource("Art/Terrain/mountain jungles.pcx", mountainSize, 4, 4); - addUniformOffsetsToAtlasSource(ref jungleMountain, 4, 4, new Vector2I(0, 12)); + + TileSetAtlasSource plainsForest = loadForestSource("Art/Terrain/plains forests.pcx"); + TileSetAtlasSource grasslandsForest = loadForestSource("Art/Terrain/grassland forests.pcx"); tileset.AddSource(roads, Atlas.Road.Index()); tileset.AddSource(rails, Atlas.Rail.Index()); @@ -152,6 +168,8 @@ private void initializeTileMap() { tileset.AddSource(snowMountain, Atlas.SnowMountain.Index()); tileset.AddSource(forestMountain, Atlas.ForestMountain.Index()); tileset.AddSource(jungleMountain, Atlas.JungleMountain.Index()); + tileset.AddSource(plainsForest, Atlas.PlainsForest.Index()); + tileset.AddSource(grasslandsForest, Atlas.GrasslandsForest.Index()); // create tilemap layers foreach (Layer layer in Enum.GetValues(typeof(Layer))) { @@ -420,6 +438,34 @@ private TerrainType getDominantVegetationNearHillyTile(Tile center) { return forests > jungles ? forest : jungle; } + private void updateForestLayer(Tile tile) { + if (tile.overlayTerrainType.Key == "forest") { + (int row, int col) = (0, 0); + if (tile.isPineForest) { + row = 8 + tile.xCoordinate % 2; // pine starts at row 8 in atlas + col = tile.xCoordinate % 7; // pine has 6 columns + } else { + bool small = tile.getEdgeNeighbors().Any(t => t.IsWater()); + // this technically omits one large and one small tile but the math is simpler + if (small) { + row = 6 + tile.xCoordinate % 2; + col = 1 + tile.xCoordinate % 4; + } else { + row = 4 + tile.xCoordinate % 2; + col = tile.xCoordinate % 4; + } + } + Atlas atlas = tile.baseTerrainType.Key switch { + "plains" => Atlas.PlainsForest, + "grassland" => Atlas.GrasslandsForest, + _ => Atlas.PlainsForest, + }; + setCell(Layer.Forest, atlas, tile, new Vector2I(col, row)); + } else { + eraseCell(Layer.Forest, tile); + } + } + public void updateTile(Tile tile) { if (tile == Tile.NONE || tile is null) { string msg = tile is null ? "null tile" : "Tile.NONE"; @@ -446,6 +492,8 @@ public void updateTile(Tile tile) { updateRiverLayer(tile); updateHillLayer(tile); + + updateForestLayer(tile); } } From 70fceadf68759c38ca40f718ecfeaa74b6b3c21f Mon Sep 17 00:00:00 2001 From: Paul Cento Date: Wed, 21 Jun 2023 17:21:04 -0400 Subject: [PATCH 17/60] offset is necessary for oversized tiles... --- C7/Map/MapView.cs | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/C7/Map/MapView.cs b/C7/Map/MapView.cs index 1afd029d..93dfd382 100644 --- a/C7/Map/MapView.cs +++ b/C7/Map/MapView.cs @@ -91,13 +91,13 @@ private TileSetAtlasSource loadAtlasSource(string relPath, Vector2I tileSize, in return source; } - // private void addUniformOffsetsToAtlasSource(ref TileSetAtlasSource source, int width, int height, Vector2I offset) { - // for (int x = 0; x < width; x++) { - // for (int y = 0; y < height; y++) { - // source.GetTileData(new Vector2I(x, y), 0).TextureOrigin = offset; - // } - // } - // } + private void addUniformOffsetsToAtlasSource(ref TileSetAtlasSource source, int width, int height, Vector2I offset) { + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + source.GetTileData(new Vector2I(x, y), 0).TextureOrigin = offset; + } + } + } private TileSetAtlasSource loadForestSource(string relPath) { TileSetAtlasSource source = new TileSetAtlasSource{ @@ -145,13 +145,20 @@ private void initializeTileMap() { TileSetAtlasSource rivers = loadAtlasSource("Art/Terrain/mtnRivers.pcx", tileSize, 4, 4); TileSetAtlasSource hills = loadAtlasSource("Art/Terrain/xhills.pcx", hillSize, 4, 4); + addUniformOffsetsToAtlasSource(ref hills, 4, 4, new Vector2I(0, 4)); TileSetAtlasSource forestHills = loadAtlasSource("Art/Terrain/hill forests.pcx", hillSize, 4, 4); + addUniformOffsetsToAtlasSource(ref forestHills, 4, 4, new Vector2I(0, 4)); TileSetAtlasSource jungleHills = loadAtlasSource("Art/Terrain/hill jungle.pcx", hillSize, 4, 4); + addUniformOffsetsToAtlasSource(ref jungleHills, 4, 4, new Vector2I(0, 4)); TileSetAtlasSource mountain = loadAtlasSource("Art/Terrain/Mountains.pcx", mountainSize, 4, 4); + addUniformOffsetsToAtlasSource(ref mountain, 4, 4, new Vector2I(0, 12)); TileSetAtlasSource snowMountain = loadAtlasSource("Art/Terrain/Mountains-snow.pcx", mountainSize, 4, 4); + addUniformOffsetsToAtlasSource(ref snowMountain, 4, 4, new Vector2I(0, 12)); TileSetAtlasSource forestMountain = loadAtlasSource("Art/Terrain/mountain forests.pcx", mountainSize, 4, 4); + addUniformOffsetsToAtlasSource(ref forestMountain, 4, 4, new Vector2I(0, 12)); TileSetAtlasSource jungleMountain = loadAtlasSource("Art/Terrain/mountain jungles.pcx", mountainSize, 4, 4); + addUniformOffsetsToAtlasSource(ref jungleMountain, 4, 4, new Vector2I(0, 12)); TileSetAtlasSource plainsForest = loadForestSource("Art/Terrain/plains forests.pcx"); TileSetAtlasSource grasslandsForest = loadForestSource("Art/Terrain/grassland forests.pcx"); From 1ca5aacd250fe58276a225b65c91287bfd94999a Mon Sep 17 00:00:00 2001 From: Paul Cento Date: Wed, 21 Jun 2023 17:39:58 -0400 Subject: [PATCH 18/60] add volcanos --- C7/Map/MapView.cs | 41 +++++++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/C7/Map/MapView.cs b/C7/Map/MapView.cs index 93dfd382..a2510e93 100644 --- a/C7/Map/MapView.cs +++ b/C7/Map/MapView.cs @@ -32,6 +32,8 @@ public enum Atlas { ForestMountain, JungleMountain, Volcano, + ForestVolcano, + JungleVolcano, PlainsForest, GrasslandsForest, River, @@ -159,6 +161,12 @@ private void initializeTileMap() { addUniformOffsetsToAtlasSource(ref forestMountain, 4, 4, new Vector2I(0, 12)); TileSetAtlasSource jungleMountain = loadAtlasSource("Art/Terrain/mountain jungles.pcx", mountainSize, 4, 4); addUniformOffsetsToAtlasSource(ref jungleMountain, 4, 4, new Vector2I(0, 12)); + TileSetAtlasSource volcano = loadAtlasSource("Art/Terrain/Volcanos.pcx", mountainSize, 4, 4); + addUniformOffsetsToAtlasSource(ref volcano, 4, 4, new Vector2I(0, 12)); + TileSetAtlasSource forestVolcano = loadAtlasSource("Art/Terrain/Volcanos forests.pcx", mountainSize, 4, 4); + addUniformOffsetsToAtlasSource(ref forestVolcano, 4, 4, new Vector2I(0, 12)); + TileSetAtlasSource jungleVolcano = loadAtlasSource("Art/Terrain/Volcanos jungles.pcx", mountainSize, 4, 4); + addUniformOffsetsToAtlasSource(ref jungleVolcano, 4, 4, new Vector2I(0, 12)); TileSetAtlasSource plainsForest = loadForestSource("Art/Terrain/plains forests.pcx"); TileSetAtlasSource grasslandsForest = loadForestSource("Art/Terrain/grassland forests.pcx"); @@ -177,6 +185,9 @@ private void initializeTileMap() { tileset.AddSource(jungleMountain, Atlas.JungleMountain.Index()); tileset.AddSource(plainsForest, Atlas.PlainsForest.Index()); tileset.AddSource(grasslandsForest, Atlas.GrasslandsForest.Index()); + tileset.AddSource(volcano, Atlas.Volcano.Index()); + tileset.AddSource(forestVolcano, Atlas.ForestVolcano.Index()); + tileset.AddSource(jungleVolcano, Atlas.JungleVolcano.Index()); // create tilemap layers foreach (Layer layer in Enum.GetValues(typeof(Layer))) { @@ -379,25 +390,27 @@ private void updateHillLayer(Tile tile) { } Vector2I texCoord = getHillTextureCoordinate(tile); TerrainType nearbyVegitation = getDominantVegetationNearHillyTile(tile); - switch (tile.overlayTerrainType.Key) { - case "hills": - Atlas hillAtlas = nearbyVegitation.Key switch { + Atlas atlas = tile.overlayTerrainType.Key switch { + "hills" => nearbyVegitation.Key switch { "forest" => Atlas.ForestHill, "jungle" => Atlas.JungleHill, _ => Atlas.Hill, - }; - setCell(Layer.Hill, hillAtlas, tile, texCoord); - break; - case "mountains": - Atlas mountainAtlas = nearbyVegitation.Key switch { + }, + "mountains" => nearbyVegitation.Key switch { + _ when tile.isSnowCapped => Atlas.SnowMountain, "forest" => Atlas.ForestMountain, "jungle" => Atlas.JungleMountain, - _ => tile.isSnowCapped ? Atlas.SnowMountain : Atlas.Mountain, - }; - setCell(Layer.Hill, mountainAtlas, tile, texCoord); - break; - default: - break; + _ => Atlas.Mountain, + }, + "volcano" => nearbyVegitation.Key switch { + "forest" => Atlas.ForestVolcano, + "jungle" => Atlas.JungleVolcano, + _ => Atlas.Volcano, + }, + _ => Atlas.Invalid, + }; + if (atlas != Atlas.Invalid) { + setCell(Layer.Hill, atlas, tile, texCoord); } } From 0c7aab2e7e18f7ed22f072ae0de08a4ee653cf2e Mon Sep 17 00:00:00 2001 From: Paul Cento Date: Wed, 21 Jun 2023 17:59:49 -0400 Subject: [PATCH 19/60] single terrain overlay --- C7/Map/MapView.cs | 35 ++++++---- C7/OldMapView.cs | 159 ---------------------------------------------- 2 files changed, 23 insertions(+), 171 deletions(-) diff --git a/C7/Map/MapView.cs b/C7/Map/MapView.cs index a2510e93..6ba56c8d 100644 --- a/C7/Map/MapView.cs +++ b/C7/Map/MapView.cs @@ -7,8 +7,7 @@ namespace C7.Map { public enum Layer { - Forest, - Hill, + TerrainOverlay, River, Road, Rail, @@ -112,6 +111,10 @@ private TileSetAtlasSource loadForestSource(string relPath) { continue; // forest tilemap is shaped like this } source.CreateTile(new Vector2I(x, y)); + if (y == 4 || y == 5) { + // offset big textures by 12 pixels + source.GetTileData(new Vector2I(x, y), 0).TextureOrigin = new Vector2I(0, 12); + } } } return source; @@ -385,7 +388,6 @@ private void updateRiverLayer(Tile tile) { private void updateHillLayer(Tile tile) { if (!tile.overlayTerrainType.isHilly()) { - eraseCell(Layer.Hill, tile); return; } Vector2I texCoord = getHillTextureCoordinate(tile); @@ -410,7 +412,7 @@ private void updateHillLayer(Tile tile) { _ => Atlas.Invalid, }; if (atlas != Atlas.Invalid) { - setCell(Layer.Hill, atlas, tile, texCoord); + setCell(Layer.TerrainOverlay, atlas, tile, texCoord); } } @@ -480,9 +482,23 @@ private void updateForestLayer(Tile tile) { "grassland" => Atlas.GrasslandsForest, _ => Atlas.PlainsForest, }; - setCell(Layer.Forest, atlas, tile, new Vector2I(col, row)); + setCell(Layer.TerrainOverlay, atlas, tile, new Vector2I(col, row)); + } + } + + private bool isForest(Tile tile) { + return tile.overlayTerrainType.Key == "forest" || tile.overlayTerrainType.Key == "jungle"; + } + + private void updateTerrainOverlayLayer(Tile tile) { + if (!tile.overlayTerrainType.isHilly() && !isForest(tile)) { + eraseCell(Layer.TerrainOverlay, tile); + return; + } + if (tile.overlayTerrainType.isHilly()) { + updateHillLayer(tile); } else { - eraseCell(Layer.Forest, tile); + updateForestLayer(tile); } } @@ -509,12 +525,7 @@ public void updateTile(Tile tile) { eraseCell(Layer.TerrainYield, tile); } - updateRiverLayer(tile); - - updateHillLayer(tile); - - updateForestLayer(tile); + updateTerrainOverlayLayer(tile); } - } } diff --git a/C7/OldMapView.cs b/C7/OldMapView.cs index b22930d4..34f11a23 100644 --- a/C7/OldMapView.cs +++ b/C7/OldMapView.cs @@ -112,164 +112,6 @@ public override void onEndDraw(LooseView looseView, GameData gameData) { } } -public partial class HillsLayer : LooseLayer { - public static readonly Vector2 mountainSize = new Vector2(128, 88); - public static readonly Vector2 volcanoSize = new Vector2(128, 88); //same as mountain - public static readonly Vector2 hillsSize = new Vector2(128, 72); - private ImageTexture mountainTexture; - private ImageTexture snowMountainTexture; - private ImageTexture forestMountainTexture; - private ImageTexture jungleMountainTexture; - private ImageTexture hillsTexture; - private ImageTexture forestHillsTexture; - private ImageTexture jungleHillsTexture; - private ImageTexture volcanosTexture; - private ImageTexture forestVolcanoTexture; - private ImageTexture jungleVolcanoTexture; - - public HillsLayer() { - mountainTexture = Util.LoadTextureFromPCX("Art/Terrain/Mountains.pcx"); - snowMountainTexture = Util.LoadTextureFromPCX("Art/Terrain/Mountains-snow.pcx"); - forestMountainTexture = Util.LoadTextureFromPCX("Art/Terrain/mountain forests.pcx"); - jungleMountainTexture = Util.LoadTextureFromPCX("Art/Terrain/mountain jungles.pcx"); - hillsTexture = Util.LoadTextureFromPCX("Art/Terrain/xhills.pcx"); - forestHillsTexture = Util.LoadTextureFromPCX("Art/Terrain/hill forests.pcx"); - jungleHillsTexture = Util.LoadTextureFromPCX("Art/Terrain/hill jungle.pcx"); - volcanosTexture = Util.LoadTextureFromPCX("Art/Terrain/Volcanos.pcx"); - forestVolcanoTexture = Util.LoadTextureFromPCX("Art/Terrain/Volcanos forests.pcx"); - jungleVolcanoTexture = Util.LoadTextureFromPCX("Art/Terrain/Volcanos jungles.pcx"); - } - - public override void drawObject(LooseView looseView, GameData gameData, Tile tile, Vector2 tileCenter) - { - if (tile.overlayTerrainType.isHilly()) { - int pcxIndex = getMountainIndex(tile); - int row = pcxIndex/4; - int column = pcxIndex % 4; - if (tile.overlayTerrainType.Key == "mountains") { - Rect2 mountainRectangle = new Rect2(column * mountainSize.X, row * mountainSize.Y, mountainSize); - Rect2 screenTarget = new Rect2(tileCenter - (float)0.5 * mountainSize + new Vector2(0, -12), mountainSize); - ImageTexture mountainGraphics; - if (tile.isSnowCapped) { - mountainGraphics = snowMountainTexture; - } - else { - TerrainType dominantVegetation = getDominantVegetationNearHillyTile(tile); - if (dominantVegetation.Key == "forest") { - mountainGraphics = forestMountainTexture; - } - else if (dominantVegetation.Key == "jungle") { - mountainGraphics = jungleMountainTexture; - } - else { - mountainGraphics = mountainTexture; - } - } - looseView.DrawTextureRectRegion(mountainGraphics, screenTarget, mountainRectangle); - } - else if (tile.overlayTerrainType.Key == "hills") { - Rect2 hillsRectangle = new Rect2(column * hillsSize.X, row * hillsSize.Y, hillsSize); - Rect2 screenTarget = new Rect2(tileCenter - (float)0.5 * hillsSize + new Vector2(0, -4), hillsSize); - ImageTexture hillGraphics; - TerrainType dominantVegetation = getDominantVegetationNearHillyTile(tile); - if (dominantVegetation.Key == "forest") { - hillGraphics = forestHillsTexture; - } - else if (dominantVegetation.Key == "jungle") { - hillGraphics = jungleHillsTexture; - } - else { - hillGraphics = hillsTexture; - } - looseView.DrawTextureRectRegion(hillGraphics, screenTarget, hillsRectangle); - } - else if (tile.overlayTerrainType.Key == "volcano") { - Rect2 volcanoRectangle = new Rect2(column * volcanoSize.X, row * volcanoSize.Y, volcanoSize); - Rect2 screenTarget = new Rect2(tileCenter - (float)0.5 * volcanoSize + new Vector2(0, -12), volcanoSize); - ImageTexture volcanoGraphics; - TerrainType dominantVegetation = getDominantVegetationNearHillyTile(tile); - if (dominantVegetation.Key == "forest") { - volcanoGraphics = forestVolcanoTexture; - } - else if (dominantVegetation.Key == "jungle") { - volcanoGraphics = jungleVolcanoTexture; - } - else { - volcanoGraphics = volcanosTexture; - } - looseView.DrawTextureRectRegion(volcanoGraphics, screenTarget, volcanoRectangle); - } - } - } - - private TerrainType getDominantVegetationNearHillyTile(Tile center) - { - TerrainType northeastType = center.neighbors[TileDirection.NORTHEAST].overlayTerrainType; - TerrainType northwestType = center.neighbors[TileDirection.NORTHWEST].overlayTerrainType; - TerrainType southeastType = center.neighbors[TileDirection.SOUTHEAST].overlayTerrainType; - TerrainType southwestType = center.neighbors[TileDirection.SOUTHWEST].overlayTerrainType; - - TerrainType[] neighborTerrains = { northeastType, northwestType, southeastType, southwestType }; - - int hills = 0; - int forests = 0; - int jungles = 0; - //These references are so we can return the appropriate type, and because we don't have a good way - //to grab them directly at this point in time. - TerrainType forest = null; - TerrainType jungle = null; - foreach (TerrainType type in neighborTerrains) { - if (type.isHilly()) { - hills++; - } - else if (type.Key == "forest") { - forests++; - forest = type; - } - else if (type.Key == "jungle") { - jungles++; - jungle = type; - } - } - - if (hills + forests + jungles < 4) { //some surrounding tiles are neither forested nor hilly - return TerrainType.NONE; - } - if (forests == 0 && jungles == 0) { - return TerrainType.NONE; //all hills - } - if (forests > jungles) { - return forest; - } - if (jungles > forests) { - return jungle; - } - - //If we get here, it's a tie between forest and jungle. Deterministically choose one so it doesn't change on every render - if (center.xCoordinate % 2 == 0) { - return forest; - } - return jungle; - } - - private int getMountainIndex(Tile tile) { - int index = 0; - if (tile.neighbors[TileDirection.NORTHWEST].overlayTerrainType.isHilly()) { - index++; - } - if (tile.neighbors[TileDirection.NORTHEAST].overlayTerrainType.isHilly()) { - index+=2; - } - if (tile.neighbors[TileDirection.SOUTHWEST].overlayTerrainType.isHilly()) { - index+=4; - } - if (tile.neighbors[TileDirection.SOUTHEAST].overlayTerrainType.isHilly()) { - index+=8; - } - return index; - } -} - public partial class ForestLayer : LooseLayer { public static readonly Vector2 forestJungleSize = new Vector2(128, 88); @@ -571,7 +413,6 @@ public OldMapView(Game game, int mapWidth, int mapHeight, bool wrapHorizontally, looseView = new LooseView(this); looseView.layers.Add(new ForestLayer()); looseView.layers.Add(new MarshLayer()); - looseView.layers.Add(new HillsLayer()); this.gridLayer = new GridLayer(); looseView.layers.Add(this.gridLayer); looseView.layers.Add(new BuildingLayer()); From 58e8743d4d815c1f71a68c362a91f22983944b80 Mon Sep 17 00:00:00 2001 From: Paul Cento Date: Wed, 21 Jun 2023 19:27:01 -0400 Subject: [PATCH 20/60] add full left skirt to terrain layer --- C7/Map/MapView.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/C7/Map/MapView.cs b/C7/Map/MapView.cs index 6ba56c8d..f7957e84 100644 --- a/C7/Map/MapView.cs +++ b/C7/Map/MapView.cs @@ -225,6 +225,25 @@ private void setTerrainTiles() { setTerrainTile(cell, pcx.atlas, texCoords); } } + for (int y = 0; y < height; y++) { + Vector2I cell = new Vector2I(-1, y); + int x = width - 1; + string left = terrain[x, y]; + string right = terrain[(x + 1) % width, y]; + bool even = y % 2 == 0; + string top = "coast"; + if (y > 0) { + top = even ? terrain[x, y - 1] : terrain[(x + 1) % width, y - 1]; + } + string bottom = "coast"; + if (y < height - 1) { + bottom = even ? terrain[x, y + 1] : terrain[(x + 1) % width, y + 1]; + } + string[] corner = new string[4]{top, right, bottom, left}; + TerrainPcx pcx = Civ3TerrainTileSet.GetPcxFor(corner); + Vector2I texCoords = pcx.getTextureCoords(corner); + setTerrainTile(cell, pcx.atlas, texCoords); + } } void setTerrainTile(Vector2I cell, int atlas, Vector2I texCoords) { From f414884b7b544966ce19bcf27ba35eae0f72ce60 Mon Sep 17 00:00:00 2001 From: Paul Cento Date: Wed, 21 Jun 2023 23:13:34 -0400 Subject: [PATCH 21/60] detect edges of map in world space --- C7/C7Game.tscn | 3 +++ C7/Game.cs | 13 ++++++++++++- C7/Map/MapView.cs | 5 ++++- C7/PlayerCamera.cs | 2 +- 4 files changed, 20 insertions(+), 3 deletions(-) diff --git a/C7/C7Game.tscn b/C7/C7Game.tscn index 9592d9d3..792fdf10 100644 --- a/C7/C7Game.tscn +++ b/C7/C7Game.tscn @@ -62,6 +62,9 @@ _data = { [node name="C7Game" type="Node2D"] script = ExtResource("1") +[node name="PlayerCamera" type="Camera2D" parent="."] +script = ExtResource("11_pkhac") + [node name="CanvasLayer" type="CanvasLayer" parent="."] [node name="Advisor" type="CenterContainer" parent="CanvasLayer"] diff --git a/C7/Game.cs b/C7/Game.cs index 16350b33..df5ed9c3 100644 --- a/C7/Game.cs +++ b/C7/Game.cs @@ -66,7 +66,7 @@ public override void _Ready() { controller = CreateGame.createGame(Global.LoadGamePath, Global.DefaultBicPath); // Spawns engine thread Global.ResetLoadGamePath(); - camera = GetNode("./CanvasLayer/PlayerCamera") as PlayerCamera; + camera = GetNode("PlayerCamera") as PlayerCamera; using (var gameDataAccess = new UIGameDataAccess()) { GameMap map = gameDataAccess.gameData.map; @@ -341,6 +341,17 @@ public override void _UnhandledInput(InputEvent @event) { setSelectedUnit(to_select); } GD.Print($"tile: {tile.xCoordinate}, {tile.yCoordinate}: {tile.baseTerrainType.Key} - {tile.overlayTerrainType.Key}"); + int left = mapView.worldEdgeLeft; + int right = mapView.worldEdgeRight; + int camLeft = (int)camera.getVisibleWorld().Position.X; + int camRight = (int)camera.getVisibleWorld().End.X; + GD.Print(camera.getVisibleWorld().End); + if (camLeft <= left) { + GD.Print($"left is visible - world: {left}, cam: {camLeft}"); + } + if (camRight >= right) { + GD.Print($"right is visible - world: {right}, cam: {camRight}"); + } } } } diff --git a/C7/Map/MapView.cs b/C7/Map/MapView.cs index f7957e84..e131b3a5 100644 --- a/C7/Map/MapView.cs +++ b/C7/Map/MapView.cs @@ -61,7 +61,8 @@ partial class MapView : Node2D { private Vector2I forestSize = new Vector2I(128, 88); private Vector2I resourceSize = new Vector2I(50, 50); private ILogger log = LogManager.ForContext(); - + public int worldEdgeRight {get; private set;} + public int worldEdgeLeft {get; private set;} private int width; private int height; private GameMap gameMap; @@ -269,6 +270,8 @@ public MapView(Game game, GameData data) { height = gameMap.numTilesTall; initializeTileMap(); terrain = new string[width, height]; + worldEdgeRight = (int)ToGlobal(tilemap.MapToLocal(new Vector2I(width - 1, 1))).X + tileSize.X / 2; + worldEdgeLeft = (int)ToGlobal(tilemap.MapToLocal(new Vector2I(0, 0))).X - tileSize.X / 2; // Convert coordinates from current save coordinates to // stacked coordinates used by Godot's TileMap, and diff --git a/C7/PlayerCamera.cs b/C7/PlayerCamera.cs index dc029a4a..28fedb8a 100644 --- a/C7/PlayerCamera.cs +++ b/C7/PlayerCamera.cs @@ -33,7 +33,7 @@ public override void _UnhandledInput(InputEvent @event) { } public Rect2 getVisibleWorld() { - Transform2D vpToGlobal = (GetViewport().GlobalCanvasTransform * this.GetCanvasTransform()).AffineInverse(); + Transform2D vpToGlobal = (GetViewport().GlobalCanvasTransform * GetCanvasTransform()).AffineInverse(); return vpToGlobal * GetViewportRect(); } } From 9c26362ba1d216219b30c385f846b71551761810 Mon Sep 17 00:00:00 2001 From: Paul Cento Date: Thu, 22 Jun 2023 00:47:20 -0400 Subject: [PATCH 22/60] fix bad bounds for forest tiles and use full resource tileset --- C7/Game.cs | 2 +- C7/Map/MapView.cs | 51 +++++++++++++++++++++++++++++++++---------- C7GameData/GameMap.cs | 15 ++++++------- 3 files changed, 48 insertions(+), 20 deletions(-) diff --git a/C7/Game.cs b/C7/Game.cs index df5ed9c3..2a09564f 100644 --- a/C7/Game.cs +++ b/C7/Game.cs @@ -553,7 +553,7 @@ private void OnUnitDisbanded() { } /** - * User quit. We *may* want to do some things here like make a back-up save, or call the server and let it know we're bailing (esp. in MP). + * User quit. We *may* want to do some things here like make a back-up save, or call the server and let it know we're bailing (esp. in MP). **/ private void OnQuitTheGame() { log.Information("Goodbye!"); diff --git a/C7/Map/MapView.cs b/C7/Map/MapView.cs index e131b3a5..1dbab572 100644 --- a/C7/Map/MapView.cs +++ b/C7/Map/MapView.cs @@ -35,6 +35,7 @@ public enum Atlas { JungleVolcano, PlainsForest, GrasslandsForest, + TundraForest, River, Road, Rail, @@ -52,6 +53,7 @@ public static int Index(this Atlas atlas) { partial class MapView : Node2D { private string[,]terrain; private TileMap terrainTilemap; + private TileMap terrainTilemapShadow; private TileSet terrainTileset; private TileMap tilemap; private TileSet tileset; @@ -101,18 +103,21 @@ private void addUniformOffsetsToAtlasSource(ref TileSetAtlasSource source, int w } } - private TileSetAtlasSource loadForestSource(string relPath) { + private TileSetAtlasSource loadForestSource(string relPath, bool jungle = false) { TileSetAtlasSource source = new TileSetAtlasSource{ Texture = Util.LoadTextureFromPCX(relPath), TextureRegionSize = forestSize, }; for (int x = 0; x < 6; x++) { - for (int y = 4; y < 10; y++) { - if ((y < 6 && x > 3) || (y < 8 && x > 4)) { + for (int y = 0; y < 10; y++) { + if ((y < 4 && !jungle) || (y < 2 && x > 3)) { + continue; // first 4 rows are for jungle tiles + } + if ((y > 3 && y < 6 && x > 3) || (y > 5 && y < 8 && x > 4)) { continue; // forest tilemap is shaped like this } source.CreateTile(new Vector2I(x, y)); - if (y == 4 || y == 5) { + if (y == 1 || y == 2 || y == 4 || y == 5) { // offset big textures by 12 pixels source.GetTileData(new Vector2I(x, y), 0).TextureOrigin = new Vector2I(0, 12); } @@ -121,11 +126,18 @@ private TileSetAtlasSource loadForestSource(string relPath) { return source; } + public override void _Process(double delta) { + base._Process(delta); + } + private void initializeTileMap() { terrainTilemap = new TileMap(); + terrainTilemapShadow = new TileMap(); terrainTileset = Civ3TerrainTileSet.Generate(); terrainTilemap.TileSet = terrainTileset; terrainTilemap.Position += Vector2I.Right * (tileSize.X / 2); + terrainTilemapShadow.TileSet = terrainTileset; + terrainTilemapShadow.Position = terrainTilemap.Position + (Vector2I.Left * tileSize.X * width); tilemap = new TileMap{ YSortEnabled = true }; tileset = makeTileSet(); @@ -134,13 +146,13 @@ private void initializeTileMap() { TileSetAtlasSource rails = loadAtlasSource("Art/Terrain/railroads.pcx", tileSize, 16, 16); TileSetAtlasSource resources = new TileSetAtlasSource{ - Texture = Util.LoadTextureFromPCX("Art/resources.pcx"), + Texture = Util.LoadTextureFromPCX("Conquests/Art/resources.pcx"), TextureRegionSize = resourceSize, }; - for (int y = 0; y < 4; y++) { + for (int y = 0; y < 5; y++) { for (int x = 0; x < 6; x++) { - if (x == 4 && y == 3) { - break; + if (y == 4 && x > 1) { + continue; } resources.CreateTile(new Vector2I(x, y)); } @@ -173,7 +185,8 @@ private void initializeTileMap() { addUniformOffsetsToAtlasSource(ref jungleVolcano, 4, 4, new Vector2I(0, 12)); TileSetAtlasSource plainsForest = loadForestSource("Art/Terrain/plains forests.pcx"); - TileSetAtlasSource grasslandsForest = loadForestSource("Art/Terrain/grassland forests.pcx"); + TileSetAtlasSource grasslandsForest = loadForestSource("Art/Terrain/grassland forests.pcx", true); + TileSetAtlasSource tundraForest = loadForestSource("Art/Terrain/tundra forests.pcx"); tileset.AddSource(roads, Atlas.Road.Index()); tileset.AddSource(rails, Atlas.Rail.Index()); @@ -189,6 +202,7 @@ private void initializeTileMap() { tileset.AddSource(jungleMountain, Atlas.JungleMountain.Index()); tileset.AddSource(plainsForest, Atlas.PlainsForest.Index()); tileset.AddSource(grasslandsForest, Atlas.GrasslandsForest.Index()); + tileset.AddSource(tundraForest, Atlas.TundraForest.Index()); tileset.AddSource(volcano, Atlas.Volcano.Index()); tileset.AddSource(forestVolcano, Atlas.ForestVolcano.Index()); tileset.AddSource(jungleVolcano, Atlas.JungleVolcano.Index()); @@ -203,6 +217,7 @@ private void initializeTileMap() { tilemap.ZIndex = 10; // need to figure out a good way to order z indices AddChild(tilemap); AddChild(terrainTilemap); + AddChild(terrainTilemapShadow); } private void setTerrainTiles() { @@ -249,6 +264,7 @@ private void setTerrainTiles() { void setTerrainTile(Vector2I cell, int atlas, Vector2I texCoords) { terrainTilemap.SetCell(0, cell, atlas, texCoords); + terrainTilemapShadow.SetCell(0, cell, atlas, texCoords); } private Vector2I stackedCoords(Tile tile) { @@ -319,6 +335,9 @@ private void setCell(Layer layer, Atlas atlas, Tile tile, Vector2I atlasCoords) if (!tileset.HasSource(atlas.Index())) { log.Warning($"atlas id {atlas} is not a valid tileset source"); } + if (!tileset.GetSource(atlas.Index()).HasTile(atlasCoords)) { + log.Warning($"atlas id {atlas} does not have tile at {atlasCoords}"); + } tilemap.SetCell(layer.Index(), stackedCoords(tile), atlas.Index(), atlasCoords); } @@ -487,7 +506,7 @@ private void updateForestLayer(Tile tile) { (int row, int col) = (0, 0); if (tile.isPineForest) { row = 8 + tile.xCoordinate % 2; // pine starts at row 8 in atlas - col = tile.xCoordinate % 7; // pine has 6 columns + col = tile.xCoordinate % 6; // pine has 6 columns } else { bool small = tile.getEdgeNeighbors().Any(t => t.IsWater()); // this technically omits one large and one small tile but the math is simpler @@ -502,9 +521,19 @@ private void updateForestLayer(Tile tile) { Atlas atlas = tile.baseTerrainType.Key switch { "plains" => Atlas.PlainsForest, "grassland" => Atlas.GrasslandsForest, + "tundra" => Atlas.TundraForest, _ => Atlas.PlainsForest, }; setCell(Layer.TerrainOverlay, atlas, tile, new Vector2I(col, row)); + } else if (tile.overlayTerrainType.Key == "jungle") { + // Randomly, but predictably, choose a large jungle graphic + // More research is needed on when to use large vs small jungles. Probably, small is used when neighboring fewer jungles. + // For the first pass, we're just always using large jungles. + (int row, int col) = (tile.xCoordinate % 2, tile.xCoordinate % 4); + if (tile.getEdgeNeighbors().Any(t => t.IsWater())) { + (row, col) = (2 + tile.xCoordinate % 2, 1 + (tile.xCoordinate % 5)); + } + setCell(Layer.TerrainOverlay, Atlas.GrasslandsForest, tile, new Vector2I(col, row)); } } @@ -534,7 +563,7 @@ public void updateTile(Tile tile) { updateRoadLayer(tile, true); if (tile.Resource != C7GameData.Resource.NONE) { - int index = tile.Resource.Index; + int index = tile.Resource.Icon; Vector2I texCoord = new Vector2I(index % 6, index / 6); setCell(Layer.Resource, Atlas.Resource, tile, texCoord); } else { diff --git a/C7GameData/GameMap.cs b/C7GameData/GameMap.cs index 30fa3a88..756c3905 100644 --- a/C7GameData/GameMap.cs +++ b/C7GameData/GameMap.cs @@ -87,14 +87,13 @@ public bool isRowAt(int y) public bool isTileAt(int x, int y) { bool evenRow = y%2 == 0; - bool xInBounds; { - if (wrapHorizontally) - xInBounds = true; - else if (evenRow) - xInBounds = (x >= 0) && (x <= numTilesWide - 2); - else - xInBounds = (x >= 1) && (x <= numTilesWide - 1); - } + bool xInBounds; + if (wrapHorizontally) + xInBounds = true; + else if (evenRow) + xInBounds = (x >= 0) && (x <= numTilesWide - 2); + else + xInBounds = (x >= 1) && (x <= numTilesWide - 1); return xInBounds && isRowAt(y) && (evenRow ? (x%2 == 0) : (x%2 != 0)); } From f4ed915b327313f330dd05cb8fb01dbae7fd6ac8 Mon Sep 17 00:00:00 2001 From: Paul Cento Date: Thu, 22 Jun 2023 01:45:28 -0400 Subject: [PATCH 23/60] wip --- C7/Map/MapView.cs | 54 +----------- C7/Map/TileSetLoader.cs | 189 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 192 insertions(+), 51 deletions(-) create mode 100644 C7/Map/TileSetLoader.cs diff --git a/C7/Map/MapView.cs b/C7/Map/MapView.cs index 1dbab572..8c87b8e5 100644 --- a/C7/Map/MapView.cs +++ b/C7/Map/MapView.cs @@ -6,50 +6,6 @@ namespace C7.Map { - public enum Layer { - TerrainOverlay, - River, - Road, - Rail, - Resource, - TerrainYield, - Invalid, - }; - - public static class LayerExtensions { - public static int Index(this Layer layer) { - return (int)layer; - } - } - - public enum Atlas { - Hill, - ForestHill, - JungleHill, - Mountain, - SnowMountain, - ForestMountain, - JungleMountain, - Volcano, - ForestVolcano, - JungleVolcano, - PlainsForest, - GrasslandsForest, - TundraForest, - River, - Road, - Rail, - Resource, - TerrainYield, - Invalid, - } - - public static class AtlasExtensions { - public static int Index(this Atlas atlas) { - return (int)atlas; - } - } - partial class MapView : Node2D { private string[,]terrain; private TileMap terrainTilemap; @@ -58,10 +14,6 @@ partial class MapView : Node2D { private TileMap tilemap; private TileSet tileset; private Vector2I tileSize = new Vector2I(128, 64); - private Vector2I hillSize = new Vector2I(128, 72); - private Vector2I mountainSize = new Vector2I(128, 88); - private Vector2I forestSize = new Vector2I(128, 88); - private Vector2I resourceSize = new Vector2I(50, 50); private ILogger log = LogManager.ForContext(); public int worldEdgeRight {get; private set;} public int worldEdgeLeft {get; private set;} @@ -140,10 +92,10 @@ private void initializeTileMap() { terrainTilemapShadow.Position = terrainTilemap.Position + (Vector2I.Left * tileSize.X * width); tilemap = new TileMap{ YSortEnabled = true }; - tileset = makeTileSet(); + tileset = TileSetLoader.LoadCiv3TileSet(); tilemap.TileSet = tileset; - TileSetAtlasSource roads = loadAtlasSource("Art/Terrain/roads.pcx", tileSize, 16, 16); - TileSetAtlasSource rails = loadAtlasSource("Art/Terrain/railroads.pcx", tileSize, 16, 16); + // TileSetAtlasSource roads = loadAtlasSource("Art/Terrain/roads.pcx", tileSize, 16, 16); + // TileSetAtlasSource rails = loadAtlasSource("Art/Terrain/railroads.pcx", tileSize, 16, 16); TileSetAtlasSource resources = new TileSetAtlasSource{ Texture = Util.LoadTextureFromPCX("Conquests/Art/resources.pcx"), diff --git a/C7/Map/TileSetLoader.cs b/C7/Map/TileSetLoader.cs new file mode 100644 index 00000000..71726c06 --- /dev/null +++ b/C7/Map/TileSetLoader.cs @@ -0,0 +1,189 @@ +using Godot; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace C7.Map { + + public enum Layer { + TerrainOverlay, + River, + Road, + Rail, + Resource, + TerrainYield, + Invalid, + }; + + public static class LayerExtensions { + public static int Index(this Layer layer) { + return (int)layer; + } + } + + public enum Atlas { + Hill, + ForestHill, + JungleHill, + Mountain, + SnowMountain, + ForestMountain, + JungleMountain, + Volcano, + ForestVolcano, + JungleVolcano, + PlainsForest, + GrasslandsForest, + TundraForest, + River, + Road, + Rail, + Resource, + TerrainYield, + Invalid, + } + + public static class AtlasExtensions { + public static int Index(this Atlas atlas) { + return (int)atlas; + } + } + + class AtlasLoader { + string path; + int width; + int height; + Vector2I regionSize; + Vector2I textureOrigin; + protected TileSetAtlasSource source; + bool loaded = false; + + public AtlasLoader(string p, int w, int h, Vector2I rs, int y = 0) { + path = p; + width = w; + height = h; + regionSize = rs; + textureOrigin = new Vector2I(0, y); + source = new TileSetAtlasSource{ + Texture = Util.LoadTextureFromPCX(path), + TextureRegionSize = regionSize, + }; + } + + protected void createTile(int x, int y, bool doOffset = true) { + Vector2I atlasCoords = new Vector2I(x, y); + source.CreateTile(atlasCoords); + if (doOffset && textureOrigin.Y != 0) { + source.GetTileData(atlasCoords, 0).TextureOrigin = textureOrigin; + } + } + + public TileSetAtlasSource Load() { + if (!loaded) { + load(); + loaded = true; + } + return source; + } + + protected virtual void load() { + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + createTile(x, y); + } + } + } + } + + class ForestAtlasLoader : AtlasLoader { + bool jungle; + public ForestAtlasLoader(string p, Vector2I rs, bool j = false) : base(p, -1, -1, rs, 12) { + jungle = j; + } + + protected override void load() { + for (int x = 0; x < 6; x++) { + for (int y = 0; y < 10; y++) { + if ((y < 4 && !jungle) || (y < 2 && x > 3)) { + continue; // first 4 rows are for jungle tiles + } + if ((y > 3 && y < 6 && x > 3) || (y > 5 && y < 8 && x > 4)) { + continue; // forest tilemap is shaped like this + } + bool shouldDoOffset = y == 1 || y == 2 || y == 4 || y == 5; + createTile(x, y, shouldDoOffset); + } + } + } + }; + + class ResourceAtlasLoader : AtlasLoader { + public ResourceAtlasLoader(string p, Vector2I rs) : base(p, -1, -1, rs) {} + + protected override void load() { + for (int y = 0; y < 5; y++) { + for (int x = 0; x < 6; x++) { + if (y == 4 && x > 1) { + continue; + } + createTile(x, y); + } + } + } + } + + // TileSetLoader loads tileset atlas sources + class TileSetLoader { + private static readonly Vector2I tileSize = new Vector2I(128, 64); + private static readonly Vector2I hillSize = new Vector2I(128, 72); + private static readonly Vector2I mountainSize = new Vector2I(128, 88); + private static readonly Vector2I volcanoSize = new Vector2I(128, 88); + + private static readonly Vector2I forestSize = new Vector2I(128, 88); + private static readonly Vector2I resourceSize = new Vector2I(50, 50); + + private static readonly Dictionary civ3PcxForAtlas = new Dictionary { + {Atlas.Resource, new ResourceAtlasLoader("Conquests/Art/resources.pcx", resourceSize)}, + + {Atlas.Road, new AtlasLoader("Art/Terrain/roads.pcx", 16, 16, tileSize)}, + {Atlas.Rail, new AtlasLoader("Art/Terrain/railroads.pcx", 16, 16, tileSize)}, + + {Atlas.TerrainYield, new AtlasLoader("Art/Terrain/tnt.pcx", 3, 6, tileSize)}, + + {Atlas.River, new AtlasLoader("Art/Terrain/mtnRivers.pcx", 4, 4, tileSize)}, + + {Atlas.Hill, new AtlasLoader("Art/Terrain/xhills.pcx", 4, 4, hillSize, 4)}, + {Atlas.ForestHill, new AtlasLoader("Art/Terrain/hill forests.pcx", 4, 4, hillSize, 4)}, + {Atlas.JungleHill, new AtlasLoader("Art/Terrain/hill jungle.pcx", 4, 4, hillSize, 4)}, + + {Atlas.Mountain, new AtlasLoader("Art/Terrain/Mountains.pcx", 4, 4, mountainSize, 12)}, + {Atlas.SnowMountain, new AtlasLoader("Art/Terrain/Mountains-snow.pcx", 4, 4, mountainSize, 12)}, + {Atlas.ForestMountain, new AtlasLoader("Art/Terrain/mountain forests.pcx", 4, 4, mountainSize, 12)}, + {Atlas.JungleMountain, new AtlasLoader("Art/Terrain/mountain jungles.pcx", 4, 4, mountainSize, 12)}, + + {Atlas.Volcano, new AtlasLoader("Art/Terrain/Volcanos.pcx", 4, 4, mountainSize, 12)}, + {Atlas.ForestVolcano, new AtlasLoader("Art/Terrain/Volcanos forests.pcx", 4, 4, mountainSize, 12)}, + {Atlas.JungleVolcano, new AtlasLoader("Art/Terrain/Volcanos jungles.pcx", 4, 4, mountainSize, 12)}, + + {Atlas.PlainsForest, new ForestAtlasLoader("Art/Terrain/plains forests.pcx", forestSize)}, + {Atlas.GrasslandsForest, new ForestAtlasLoader("Art/Terrain/plains forests.pcx", forestSize, true)}, + {Atlas.TundraForest, new ForestAtlasLoader("Art/Terrain/tundra forests.pcx", forestSize)}, + }; + + public static TileSet LoadCiv3TileSet() { + TileSet tileset = new TileSet { + TileShape = TileSet.TileShapeEnum.Isometric, + TileLayout = TileSet.TileLayoutEnum.Stacked, + TileOffsetAxis = TileSet.TileOffsetAxisEnum.Horizontal, + TileSize = tileSize, + }; + + foreach ((Atlas atlas, AtlasLoader loader) in civ3PcxForAtlas) { + TileSetAtlasSource source = loader.Load(); + tileset.AddSource(source, atlas.Index()); + } + + return tileset; + } + } +} From 2eaefdd254ac074ad3c12580ade35cb932d66cc7 Mon Sep 17 00:00:00 2001 From: Paul Cento Date: Thu, 22 Jun 2023 01:49:46 -0400 Subject: [PATCH 24/60] tile set loader so specific assets can be overwritten in the future --- C7/Map/MapView.cs | 123 +--------------------------------------- C7/Map/TileSetLoader.cs | 2 +- 2 files changed, 2 insertions(+), 123 deletions(-) diff --git a/C7/Map/MapView.cs b/C7/Map/MapView.cs index 8c87b8e5..0bbfd614 100644 --- a/C7/Map/MapView.cs +++ b/C7/Map/MapView.cs @@ -21,63 +21,6 @@ partial class MapView : Node2D { private int height; private GameMap gameMap; - private TileSet makeTileSet() { - return new TileSet { - TileShape = TileSet.TileShapeEnum.Isometric, - TileLayout = TileSet.TileLayoutEnum.Stacked, - TileOffsetAxis = TileSet.TileOffsetAxisEnum.Horizontal, - TileSize = tileSize, - }; - } - - private void addUniformTilesToAtlasSource(ref TileSetAtlasSource source, int width, int height) { - for (int x = 0; x < width; x++) { - for (int y = 0; y < height; y++) { - source.CreateTile(new Vector2I(x, y)); - } - } - } - - private TileSetAtlasSource loadAtlasSource(string relPath, Vector2I tileSize, int width, int height) { - TileSetAtlasSource source = new TileSetAtlasSource{ - Texture = Util.LoadTextureFromPCX(relPath), - TextureRegionSize = tileSize, - }; - addUniformTilesToAtlasSource(ref source, width, height); - return source; - } - - private void addUniformOffsetsToAtlasSource(ref TileSetAtlasSource source, int width, int height, Vector2I offset) { - for (int x = 0; x < width; x++) { - for (int y = 0; y < height; y++) { - source.GetTileData(new Vector2I(x, y), 0).TextureOrigin = offset; - } - } - } - - private TileSetAtlasSource loadForestSource(string relPath, bool jungle = false) { - TileSetAtlasSource source = new TileSetAtlasSource{ - Texture = Util.LoadTextureFromPCX(relPath), - TextureRegionSize = forestSize, - }; - for (int x = 0; x < 6; x++) { - for (int y = 0; y < 10; y++) { - if ((y < 4 && !jungle) || (y < 2 && x > 3)) { - continue; // first 4 rows are for jungle tiles - } - if ((y > 3 && y < 6 && x > 3) || (y > 5 && y < 8 && x > 4)) { - continue; // forest tilemap is shaped like this - } - source.CreateTile(new Vector2I(x, y)); - if (y == 1 || y == 2 || y == 4 || y == 5) { - // offset big textures by 12 pixels - source.GetTileData(new Vector2I(x, y), 0).TextureOrigin = new Vector2I(0, 12); - } - } - } - return source; - } - public override void _Process(double delta) { base._Process(delta); } @@ -94,70 +37,6 @@ private void initializeTileMap() { tilemap = new TileMap{ YSortEnabled = true }; tileset = TileSetLoader.LoadCiv3TileSet(); tilemap.TileSet = tileset; - // TileSetAtlasSource roads = loadAtlasSource("Art/Terrain/roads.pcx", tileSize, 16, 16); - // TileSetAtlasSource rails = loadAtlasSource("Art/Terrain/railroads.pcx", tileSize, 16, 16); - - TileSetAtlasSource resources = new TileSetAtlasSource{ - Texture = Util.LoadTextureFromPCX("Conquests/Art/resources.pcx"), - TextureRegionSize = resourceSize, - }; - for (int y = 0; y < 5; y++) { - for (int x = 0; x < 6; x++) { - if (y == 4 && x > 1) { - continue; - } - resources.CreateTile(new Vector2I(x, y)); - } - } - - TileSetAtlasSource tnt = loadAtlasSource("Art/Terrain/tnt.pcx", tileSize, 3, 6); - - TileSetAtlasSource rivers = loadAtlasSource("Art/Terrain/mtnRivers.pcx", tileSize, 4, 4); - - TileSetAtlasSource hills = loadAtlasSource("Art/Terrain/xhills.pcx", hillSize, 4, 4); - addUniformOffsetsToAtlasSource(ref hills, 4, 4, new Vector2I(0, 4)); - TileSetAtlasSource forestHills = loadAtlasSource("Art/Terrain/hill forests.pcx", hillSize, 4, 4); - addUniformOffsetsToAtlasSource(ref forestHills, 4, 4, new Vector2I(0, 4)); - TileSetAtlasSource jungleHills = loadAtlasSource("Art/Terrain/hill jungle.pcx", hillSize, 4, 4); - addUniformOffsetsToAtlasSource(ref jungleHills, 4, 4, new Vector2I(0, 4)); - - TileSetAtlasSource mountain = loadAtlasSource("Art/Terrain/Mountains.pcx", mountainSize, 4, 4); - addUniformOffsetsToAtlasSource(ref mountain, 4, 4, new Vector2I(0, 12)); - TileSetAtlasSource snowMountain = loadAtlasSource("Art/Terrain/Mountains-snow.pcx", mountainSize, 4, 4); - addUniformOffsetsToAtlasSource(ref snowMountain, 4, 4, new Vector2I(0, 12)); - TileSetAtlasSource forestMountain = loadAtlasSource("Art/Terrain/mountain forests.pcx", mountainSize, 4, 4); - addUniformOffsetsToAtlasSource(ref forestMountain, 4, 4, new Vector2I(0, 12)); - TileSetAtlasSource jungleMountain = loadAtlasSource("Art/Terrain/mountain jungles.pcx", mountainSize, 4, 4); - addUniformOffsetsToAtlasSource(ref jungleMountain, 4, 4, new Vector2I(0, 12)); - TileSetAtlasSource volcano = loadAtlasSource("Art/Terrain/Volcanos.pcx", mountainSize, 4, 4); - addUniformOffsetsToAtlasSource(ref volcano, 4, 4, new Vector2I(0, 12)); - TileSetAtlasSource forestVolcano = loadAtlasSource("Art/Terrain/Volcanos forests.pcx", mountainSize, 4, 4); - addUniformOffsetsToAtlasSource(ref forestVolcano, 4, 4, new Vector2I(0, 12)); - TileSetAtlasSource jungleVolcano = loadAtlasSource("Art/Terrain/Volcanos jungles.pcx", mountainSize, 4, 4); - addUniformOffsetsToAtlasSource(ref jungleVolcano, 4, 4, new Vector2I(0, 12)); - - TileSetAtlasSource plainsForest = loadForestSource("Art/Terrain/plains forests.pcx"); - TileSetAtlasSource grasslandsForest = loadForestSource("Art/Terrain/grassland forests.pcx", true); - TileSetAtlasSource tundraForest = loadForestSource("Art/Terrain/tundra forests.pcx"); - - tileset.AddSource(roads, Atlas.Road.Index()); - tileset.AddSource(rails, Atlas.Rail.Index()); - tileset.AddSource(resources, Atlas.Resource.Index()); - tileset.AddSource(tnt, Atlas.TerrainYield.Index()); - tileset.AddSource(rivers, Atlas.River.Index()); - tileset.AddSource(hills, Atlas.Hill.Index()); - tileset.AddSource(forestHills, Atlas.ForestHill.Index()); - tileset.AddSource(jungleHills, Atlas.JungleHill.Index()); - tileset.AddSource(mountain, Atlas.Mountain.Index()); - tileset.AddSource(snowMountain, Atlas.SnowMountain.Index()); - tileset.AddSource(forestMountain, Atlas.ForestMountain.Index()); - tileset.AddSource(jungleMountain, Atlas.JungleMountain.Index()); - tileset.AddSource(plainsForest, Atlas.PlainsForest.Index()); - tileset.AddSource(grasslandsForest, Atlas.GrasslandsForest.Index()); - tileset.AddSource(tundraForest, Atlas.TundraForest.Index()); - tileset.AddSource(volcano, Atlas.Volcano.Index()); - tileset.AddSource(forestVolcano, Atlas.ForestVolcano.Index()); - tileset.AddSource(jungleVolcano, Atlas.JungleVolcano.Index()); // create tilemap layers foreach (Layer layer in Enum.GetValues(typeof(Layer))) { @@ -169,7 +48,7 @@ private void initializeTileMap() { tilemap.ZIndex = 10; // need to figure out a good way to order z indices AddChild(tilemap); AddChild(terrainTilemap); - AddChild(terrainTilemapShadow); + // AddChild(terrainTilemapShadow); } private void setTerrainTiles() { diff --git a/C7/Map/TileSetLoader.cs b/C7/Map/TileSetLoader.cs index 71726c06..67478330 100644 --- a/C7/Map/TileSetLoader.cs +++ b/C7/Map/TileSetLoader.cs @@ -166,7 +166,7 @@ class TileSetLoader { {Atlas.JungleVolcano, new AtlasLoader("Art/Terrain/Volcanos jungles.pcx", 4, 4, mountainSize, 12)}, {Atlas.PlainsForest, new ForestAtlasLoader("Art/Terrain/plains forests.pcx", forestSize)}, - {Atlas.GrasslandsForest, new ForestAtlasLoader("Art/Terrain/plains forests.pcx", forestSize, true)}, + {Atlas.GrasslandsForest, new ForestAtlasLoader("Art/Terrain/grassland forests.pcx", forestSize, true)}, {Atlas.TundraForest, new ForestAtlasLoader("Art/Terrain/tundra forests.pcx", forestSize)}, }; From 871471be2fe652e7586a84fdd5da977bf7eeb887 Mon Sep 17 00:00:00 2001 From: Paul Cento Date: Thu, 22 Jun 2023 02:13:17 -0400 Subject: [PATCH 25/60] add marsh to tilemap terrain overlay layer --- C7/Map/MapView.cs | 25 ++++-- C7/Map/TileSetLoader.cs | 24 ++++++ C7/OldMapView.cs | 182 ---------------------------------------- C7GameData/Tile.cs | 4 + 4 files changed, 47 insertions(+), 188 deletions(-) diff --git a/C7/Map/MapView.cs b/C7/Map/MapView.cs index 0bbfd614..b2fca370 100644 --- a/C7/Map/MapView.cs +++ b/C7/Map/MapView.cs @@ -339,7 +339,7 @@ private void updateForestLayer(Tile tile) { row = 8 + tile.xCoordinate % 2; // pine starts at row 8 in atlas col = tile.xCoordinate % 6; // pine has 6 columns } else { - bool small = tile.getEdgeNeighbors().Any(t => t.IsWater()); + bool small = tile.numWaterEdges() > 0; // this technically omits one large and one small tile but the math is simpler if (small) { row = 6 + tile.xCoordinate % 2; @@ -361,26 +361,39 @@ private void updateForestLayer(Tile tile) { // More research is needed on when to use large vs small jungles. Probably, small is used when neighboring fewer jungles. // For the first pass, we're just always using large jungles. (int row, int col) = (tile.xCoordinate % 2, tile.xCoordinate % 4); - if (tile.getEdgeNeighbors().Any(t => t.IsWater())) { - (row, col) = (2 + tile.xCoordinate % 2, 1 + (tile.xCoordinate % 5)); + if (tile.numWaterEdges() > 0) { + (row, col) = (2 + tile.xCoordinate % 2, 1 + tile.xCoordinate % 5); } setCell(Layer.TerrainOverlay, Atlas.GrasslandsForest, tile, new Vector2I(col, row)); } } - private bool isForest(Tile tile) { + private void updateMarshLayer(Tile tile) { + if (tile.overlayTerrainType.Key != "marsh") { + return; + } + (int row, int col) = (tile.xCoordinate % 2, tile.xCoordinate % 4); + if (tile.numWaterEdges() > 0) { + (row, col) = (2 + tile.xCoordinate % 2, 1 + tile.xCoordinate % 4); + } + setCell(Layer.TerrainOverlay, Atlas.Marsh, tile, new Vector2I(col, row)); + } + + private static bool isForest(Tile tile) { return tile.overlayTerrainType.Key == "forest" || tile.overlayTerrainType.Key == "jungle"; } private void updateTerrainOverlayLayer(Tile tile) { - if (!tile.overlayTerrainType.isHilly() && !isForest(tile)) { + if (!tile.overlayTerrainType.isHilly() && !isForest(tile) && tile.overlayTerrainType.Key != "marsh") { eraseCell(Layer.TerrainOverlay, tile); return; } if (tile.overlayTerrainType.isHilly()) { updateHillLayer(tile); - } else { + } else if (isForest(tile)) { updateForestLayer(tile); + } else { + updateMarshLayer(tile); } } diff --git a/C7/Map/TileSetLoader.cs b/C7/Map/TileSetLoader.cs index 67478330..c30ae1be 100644 --- a/C7/Map/TileSetLoader.cs +++ b/C7/Map/TileSetLoader.cs @@ -35,6 +35,7 @@ public enum Atlas { PlainsForest, GrasslandsForest, TundraForest, + Marsh, River, Road, Rail, @@ -132,7 +133,26 @@ protected override void load() { } } + class MarshAtlasLoader : AtlasLoader { + public MarshAtlasLoader(string p, Vector2I rs) : base(p, -1, -1, rs, 12) {} + + protected override void load() { + // TODO: incomplete + for (int y = 0; y < 4; y++) { + for (int x = 0; x < 5; x++) { + if (y < 2 && x > 3) { + continue; + } + bool shouldDoOffset = y == 0 || y == 1; + createTile(x, y, shouldDoOffset); + } + } + } + } + // TileSetLoader loads tileset atlas sources + // In the future, it will be configured to set the path property of each + // atlas loader depending on which custom terrain or graphics are used. class TileSetLoader { private static readonly Vector2I tileSize = new Vector2I(128, 64); private static readonly Vector2I hillSize = new Vector2I(128, 72); @@ -140,6 +160,8 @@ class TileSetLoader { private static readonly Vector2I volcanoSize = new Vector2I(128, 88); private static readonly Vector2I forestSize = new Vector2I(128, 88); + private static readonly Vector2I marshSize = new Vector2I(128, 88); + private static readonly Vector2I resourceSize = new Vector2I(50, 50); private static readonly Dictionary civ3PcxForAtlas = new Dictionary { @@ -168,6 +190,8 @@ class TileSetLoader { {Atlas.PlainsForest, new ForestAtlasLoader("Art/Terrain/plains forests.pcx", forestSize)}, {Atlas.GrasslandsForest, new ForestAtlasLoader("Art/Terrain/grassland forests.pcx", forestSize, true)}, {Atlas.TundraForest, new ForestAtlasLoader("Art/Terrain/tundra forests.pcx", forestSize)}, + + {Atlas.Marsh, new MarshAtlasLoader("Art/Terrain/marsh.pcx", marshSize)}, }; public static TileSet LoadCiv3TileSet() { diff --git a/C7/OldMapView.cs b/C7/OldMapView.cs index 34f11a23..87753e8a 100644 --- a/C7/OldMapView.cs +++ b/C7/OldMapView.cs @@ -31,186 +31,6 @@ public virtual void onEndDraw(LooseView looseView, GameData gameData) {} public bool visible = true; } -public partial class TerrainLayer : LooseLayer { - - public static readonly Vector2 terrainSpriteSize = new Vector2(128, 64); - - // A triple sheet is a sprite sheet containing sprites for three different terrain types including transitions between. - private List tripleSheets; - - // TileToDraw stores the arguments passed to drawObject so the draws can be sorted by texture before being submitted. This significantly - // reduces the number of draw calls Godot must generate (1483 to 312 when fully zoomed out on our test map) and modestly improves framerate - // (by about 14% on my system). - private class TileToDraw : IComparable - { - public Tile tile; - public Vector2 tileCenter; - - public TileToDraw(Tile tile, Vector2 tileCenter) - { - this.tile = tile; - this.tileCenter = tileCenter; - } - - public int CompareTo(TileToDraw other) - { - // "other" might be null, in which case we should return a positive value. CompareTo(null) will do this. - try { - return this.tile.ExtraInfo.BaseTerrainFileID.CompareTo(other?.tile.ExtraInfo.BaseTerrainFileID); - } catch (Exception) { - //It also could be Tile.NONE. In which case, also return a positive value. - return 1; - } - } - } - - private List tilesToDraw = new List(); - - public TerrainLayer() - { - tripleSheets = loadTerrainTripleSheets(); - } - - public List loadTerrainTripleSheets() - { - List fileNames = new List { - "Art/Terrain/xtgc.pcx", - "Art/Terrain/xpgc.pcx", - "Art/Terrain/xdgc.pcx", - "Art/Terrain/xdpc.pcx", - "Art/Terrain/xdgp.pcx", - "Art/Terrain/xggc.pcx", - "Art/Terrain/wCSO.pcx", - "Art/Terrain/wSSS.pcx", - "Art/Terrain/wOOO.pcx", - }; - return fileNames.ConvertAll(name => Util.LoadTextureFromPCX(name)); - } - - public override void drawObject(LooseView looseView, GameData gameData, Tile tile, Vector2 tileCenter) - { - tilesToDraw.Add(new TileToDraw(tile, tileCenter)); - tilesToDraw.Add(new TileToDraw(tile.neighbors[TileDirection.SOUTH], tileCenter + new Vector2(0, 64))); - tilesToDraw.Add(new TileToDraw(tile.neighbors[TileDirection.SOUTHWEST], tileCenter + new Vector2(-64, 32))); - tilesToDraw.Add(new TileToDraw(tile.neighbors[TileDirection.SOUTHEAST], tileCenter + new Vector2(64, 32))); - } - - public override void onEndDraw(LooseView looseView, GameData gameData) { - tilesToDraw.Sort(); - foreach (TileToDraw tTD in tilesToDraw) { - if (tTD.tile != Tile.NONE) { - int xSheet = tTD.tile.ExtraInfo.BaseTerrainImageID % 9, ySheet = tTD.tile.ExtraInfo.BaseTerrainImageID / 9; - Rect2 texRect = new Rect2(new Vector2(xSheet, ySheet) * terrainSpriteSize, terrainSpriteSize); - Vector2 terrainOffset = new Vector2(0, -1 * OldMapView.cellSize.Y); - // Multiply size by 100.1% so avoid "seams" in the map. See issue #106. - // Jim's option of a whole-map texture is less hacky, but this is quicker and seems to be working well. - Rect2 screenRect = new Rect2(tTD.tileCenter - (float)0.5 * terrainSpriteSize + terrainOffset, terrainSpriteSize * 1.001f); - looseView.DrawTextureRectRegion(tripleSheets[tTD.tile.ExtraInfo.BaseTerrainFileID], screenRect, texRect); - } - } - tilesToDraw.Clear(); - } -} - -public partial class ForestLayer : LooseLayer { - public static readonly Vector2 forestJungleSize = new Vector2(128, 88); - - private ImageTexture largeJungleTexture; - private ImageTexture smallJungleTexture; - private ImageTexture largeForestTexture; - private ImageTexture largePlainsForestTexture; - private ImageTexture largeTundraForestTexture; - private ImageTexture smallForestTexture; - private ImageTexture smallPlainsForestTexture; - private ImageTexture smallTundraForestTexture; - private ImageTexture pineForestTexture; - private ImageTexture pinePlainsTexture; - private ImageTexture pineTundraTexture; - - public ForestLayer() { - largeJungleTexture = Util.LoadTextureFromPCX("Art/Terrain/grassland forests.pcx", 0, 0, 512, 176); - smallJungleTexture = Util.LoadTextureFromPCX("Art/Terrain/grassland forests.pcx", 0, 176, 768, 176); - largeForestTexture = Util.LoadTextureFromPCX("Art/Terrain/grassland forests.pcx", 0, 352, 512, 176); - largePlainsForestTexture = Util.LoadTextureFromPCX("Art/Terrain/plains forests.pcx", 0, 352, 512, 176); - largeTundraForestTexture = Util.LoadTextureFromPCX("Art/Terrain/tundra forests.pcx", 0, 352, 512, 176); - smallForestTexture = Util.LoadTextureFromPCX("Art/Terrain/grassland forests.pcx", 0, 528, 640, 176); - smallPlainsForestTexture = Util.LoadTextureFromPCX("Art/Terrain/plains forests.pcx", 0, 528, 640, 176); - smallTundraForestTexture = Util.LoadTextureFromPCX("Art/Terrain/tundra forests.pcx", 0, 528, 640, 176); - pineForestTexture = Util.LoadTextureFromPCX("Art/Terrain/grassland forests.pcx", 0, 704, 768, 176); - pinePlainsTexture = Util.LoadTextureFromPCX("Art/Terrain/plains forests.pcx" , 0, 704, 768, 176); - pineTundraTexture = Util.LoadTextureFromPCX("Art/Terrain/tundra forests.pcx" , 0, 704, 768, 176); - } - - public override void drawObject(LooseView looseView, GameData gameData, Tile tile, Vector2 tileCenter) { - if (tile.overlayTerrainType.Key == "jungle") { - //Randomly, but predictably, choose a large jungle graphic - //More research is needed on when to use large vs small jungles. Probably, small is used when neighboring fewer jungles. - //For the first pass, we're just always using large jungles. - int randomJungleRow = tile.yCoordinate % 2; - int randomJungleColumn; - ImageTexture jungleTexture; - if (tile.getEdgeNeighbors().Any(t => t.IsWater())) { - randomJungleColumn = tile.xCoordinate % 6; - jungleTexture = smallJungleTexture; - } - else { - randomJungleColumn = tile.xCoordinate % 4; - jungleTexture = largeJungleTexture; - } - Rect2 jungleRectangle = new Rect2(randomJungleColumn * forestJungleSize.X, randomJungleRow * forestJungleSize.Y, forestJungleSize); - Rect2 screenTarget = new Rect2(tileCenter - (float)0.5 * forestJungleSize + new Vector2(0, -12), forestJungleSize); - looseView.DrawTextureRectRegion(jungleTexture, screenTarget, jungleRectangle); - } - if (tile.overlayTerrainType.Key == "forest") { - int forestRow = 0; - int forestColumn = 0; - ImageTexture forestTexture; - if (tile.isPineForest) { - forestRow = tile.yCoordinate % 2; - forestColumn = tile.xCoordinate % 6; - if (tile.baseTerrainType.Key == "grassland") { - forestTexture = pineForestTexture; - } - else if (tile.baseTerrainType.Key == "plains") { - forestTexture = pinePlainsTexture; - } - else { //Tundra - forestTexture = pineTundraTexture; - } - } - else { - forestRow = tile.yCoordinate % 2; - if (tile.getEdgeNeighbors().Any(t => t.IsWater())) { - forestColumn = tile.xCoordinate % 5; - if (tile.baseTerrainType.Key == "grassland") { - forestTexture = smallForestTexture; - } - else if (tile.baseTerrainType.Key == "plains") { - forestTexture = smallPlainsForestTexture; - } - else { //tundra - forestTexture = smallTundraForestTexture; - } - } - else { - forestColumn = tile.xCoordinate % 4; - if (tile.baseTerrainType.Key == "grassland") { - forestTexture = largeForestTexture; - } - else if (tile.baseTerrainType.Key == "plains") { - forestTexture = largePlainsForestTexture; - } - else { //tundra - forestTexture = largeTundraForestTexture; - } - } - } - Rect2 forestRectangle = new Rect2(forestColumn * forestJungleSize.X, forestRow * forestJungleSize.Y, forestJungleSize); - Rect2 screenTarget = new Rect2(tileCenter - (float)0.5 * forestJungleSize + new Vector2(0, -12), forestJungleSize); - looseView.DrawTextureRectRegion(forestTexture, screenTarget, forestRectangle); - } - } -} public partial class MarshLayer : LooseLayer { public static readonly Vector2 marshSize = new Vector2(128, 88); //Because the marsh graphics are 88 pixels tall instead of the 64 of a tile, we also need an addition 12 pixel offset to the top @@ -411,13 +231,11 @@ public OldMapView(Game game, int mapWidth, int mapHeight, bool wrapHorizontally, this.wrapVertically = wrapVertically; looseView = new LooseView(this); - looseView.layers.Add(new ForestLayer()); looseView.layers.Add(new MarshLayer()); this.gridLayer = new GridLayer(); looseView.layers.Add(this.gridLayer); looseView.layers.Add(new BuildingLayer()); looseView.layers.Add(new CityLayer()); - // looseView.layers.Add(new FogOfWarLayer()); (civColorWhitePalette, _) = Util.loadPalettizedPCX("Art/Units/Palettes/ntp00.pcx"); diff --git a/C7GameData/Tile.cs b/C7GameData/Tile.cs index d62b85d2..61e202aa 100644 --- a/C7GameData/Tile.cs +++ b/C7GameData/Tile.cs @@ -97,6 +97,10 @@ public Tile[] getEdgeNeighbors() { return edgeNeighbors; } + public int numWaterEdges() { + return getEdgeNeighbors().Count(t => t.IsWater()); + } + public override string ToString() { return "[" + xCoordinate + ", " + yCoordinate + "] (" + overlayTerrainType.DisplayName + " on " + baseTerrainType.DisplayName + ")"; From 2d3e98e1e0b0b73820469249e0926f4c123506e6 Mon Sep 17 00:00:00 2001 From: Paul Cento Date: Thu, 22 Jun 2023 02:27:35 -0400 Subject: [PATCH 26/60] camera can center on tiles --- C7/Game.cs | 26 ++++++++++++-------------- C7/Map/MapView.cs | 14 ++++++++++---- C7/PlayerCamera.cs | 8 +++++++- 3 files changed, 29 insertions(+), 19 deletions(-) diff --git a/C7/Game.cs b/C7/Game.cs index 2a09564f..9cb34e07 100644 --- a/C7/Game.cs +++ b/C7/Game.cs @@ -24,7 +24,6 @@ enum GameState { public Player controller; // Player that's controlling the UI. private MapView mapView; - private OldMapView oldMapView; public AnimationManager civ3AnimData; public AnimationTracker animTracker; @@ -72,24 +71,22 @@ public override void _Ready() { GameMap map = gameDataAccess.gameData.map; Util.setModPath(gameDataAccess.gameData.scenarioSearchPath); log.Debug("RelativeModPath ", map.RelativeModPath); - oldMapView = new OldMapView(this, map.numTilesWide, map.numTilesTall, map.wrapHorizontally, map.wrapVertically); - AddChild(oldMapView); - oldMapView.cameraZoom = (float)1.0; - oldMapView.gridLayer.visible = false; + mapView = new MapView(this, gameDataAccess.gameData); // Set initial camera location. If the UI controller has any cities, focus on their capital. Otherwise, focus on their // starting settler. if (controller.cities.Count > 0) { City capital = controller.cities.Find(c => c.IsCapital()); - if (capital != null) - oldMapView.centerCameraOnTile(capital.location); + if (capital is not null) { + camera.centerOnTile(capital.location, mapView); + } } else { MapUnit startingSettler = controller.units.Find(u => u.unitType.actions.Contains(C7Action.UnitBuildCity)); - if (startingSettler != null) - oldMapView.centerCameraOnTile(startingSettler.location); + if (startingSettler is not null) { + camera.centerOnTile(startingSettler.location, mapView); + } } - mapView = new MapView(this, gameDataAccess.gameData); } AddChild(mapView); @@ -220,10 +217,11 @@ public override void _Process(double delta) { // If "location" is not already near the center of the screen, moves the camera to bring it into view. public void ensureLocationIsInView(Tile location) { + // TODO: implement if (controller.tileKnowledge.isTileKnown(location) && location != Tile.NONE) { - Vector2 relativeScreenLocation = oldMapView.screenLocationOfTile(location, true) / oldMapView.getVisibleAreaSize(); - if (relativeScreenLocation.DistanceTo(new Vector2((float)0.5, (float)0.5)) > 0.30) - oldMapView.centerCameraOnTile(location); + // Vector2 relativeScreenLocation = oldMapView.screenLocationOfTile(location, true) / oldMapView.getVisibleAreaSize(); + // if (relativeScreenLocation.DistanceTo(new Vector2((float)0.5, (float)0.5)) > 0.30) + // oldMapView.centerCameraOnTile(location); } } @@ -454,7 +452,7 @@ private void processActions() { } if (Input.IsActionJustPressed(C7Action.ToggleGrid)) { - this.oldMapView.gridLayer.visible = !this.oldMapView.gridLayer.visible; + mapView.showGrid = !mapView.showGrid; } if (Input.IsActionJustPressed(C7Action.Escape) && !this.inUnitGoToMode) { diff --git a/C7/Map/MapView.cs b/C7/Map/MapView.cs index b2fca370..3a24d596 100644 --- a/C7/Map/MapView.cs +++ b/C7/Map/MapView.cs @@ -6,7 +6,7 @@ namespace C7.Map { - partial class MapView : Node2D { + public partial class MapView : Node2D { private string[,]terrain; private TileMap terrainTilemap; private TileMap terrainTilemapShadow; @@ -19,6 +19,12 @@ partial class MapView : Node2D { public int worldEdgeLeft {get; private set;} private int width; private int height; + public bool showGrid { + get => showGrid; + set { + showGrid = value; + } + } private GameMap gameMap; public override void _Process(double delta) { @@ -162,6 +168,8 @@ public Tile tileAt(GameMap gameMap, Vector2 globalMousePosition) { return gameMap.tileAt(x, y); } + public Vector2 tileToLocal(Tile tile) => tilemap.MapToLocal(stackedCoords(tile)); + private void setCell(Layer layer, Atlas atlas, Tile tile, Vector2I atlasCoords) { if (!tileset.HasSource(atlas.Index())) { log.Warning($"atlas id {atlas} is not a valid tileset source"); @@ -219,9 +227,7 @@ private void updateRoadLayer(Tile tile, bool center) { } } - private Vector2I roadIndexTo2D(int index) { - return new Vector2I(index & 0xF, index >> 4); - } + private Vector2I roadIndexTo2D(int index) => new Vector2I(index & 0xF, index >> 4); private static int roadFlag(TileDirection direction) { return direction switch { diff --git a/C7/PlayerCamera.cs b/C7/PlayerCamera.cs index 28fedb8a..7c96afe4 100644 --- a/C7/PlayerCamera.cs +++ b/C7/PlayerCamera.cs @@ -1,5 +1,6 @@ using Godot; -using System; +using C7.Map; +using C7GameData; public partial class PlayerCamera : Camera2D { @@ -36,4 +37,9 @@ public Rect2 getVisibleWorld() { Transform2D vpToGlobal = (GetViewport().GlobalCanvasTransform * GetCanvasTransform()).AffineInverse(); return vpToGlobal * GetViewportRect(); } + + public void centerOnTile(Tile tile, MapView map) { + Vector2 target = map.tileToLocal(tile); + Position = target; + } } From a28145b36c46cbce626dff83d6b4f4a58fee9f42 Mon Sep 17 00:00:00 2001 From: Paul Cento Date: Thu, 22 Jun 2023 02:42:17 -0400 Subject: [PATCH 27/60] buildings on tilemap --- C7/Map/MapView.cs | 10 ++++++++++ C7/Map/TileSetLoader.cs | 5 +++++ C7/OldMapView.cs | 39 --------------------------------------- 3 files changed, 15 insertions(+), 39 deletions(-) diff --git a/C7/Map/MapView.cs b/C7/Map/MapView.cs index 3a24d596..1c5593ff 100644 --- a/C7/Map/MapView.cs +++ b/C7/Map/MapView.cs @@ -403,6 +403,14 @@ private void updateTerrainOverlayLayer(Tile tile) { } } + private void updateBuildingLayer(Tile tile) { + if (tile.hasBarbarianCamp) { + setCell(Layer.Building, Atlas.TerrainBuilding, tile, new Vector2I(2, 0)); + } else { + eraseCell(Layer.Building, tile); + } + } + public void updateTile(Tile tile) { if (tile == Tile.NONE || tile is null) { string msg = tile is null ? "null tile" : "Tile.NONE"; @@ -427,6 +435,8 @@ public void updateTile(Tile tile) { } updateTerrainOverlayLayer(tile); + + updateBuildingLayer(tile); } } } diff --git a/C7/Map/TileSetLoader.cs b/C7/Map/TileSetLoader.cs index c30ae1be..4f2396e8 100644 --- a/C7/Map/TileSetLoader.cs +++ b/C7/Map/TileSetLoader.cs @@ -12,6 +12,7 @@ public enum Layer { Rail, Resource, TerrainYield, + Building, Invalid, }; @@ -41,6 +42,7 @@ public enum Atlas { Rail, Resource, TerrainYield, + TerrainBuilding, Invalid, } @@ -163,6 +165,7 @@ class TileSetLoader { private static readonly Vector2I marshSize = new Vector2I(128, 88); private static readonly Vector2I resourceSize = new Vector2I(50, 50); + private static readonly Vector2I buildingSize = new Vector2I(128, 64); private static readonly Dictionary civ3PcxForAtlas = new Dictionary { {Atlas.Resource, new ResourceAtlasLoader("Conquests/Art/resources.pcx", resourceSize)}, @@ -192,6 +195,8 @@ class TileSetLoader { {Atlas.TundraForest, new ForestAtlasLoader("Art/Terrain/tundra forests.pcx", forestSize)}, {Atlas.Marsh, new MarshAtlasLoader("Art/Terrain/marsh.pcx", marshSize)}, + + {Atlas.TerrainBuilding, new AtlasLoader("Art/Terrain/TerrainBuildings.pcx", 4, 4, buildingSize)}, }; public static TileSet LoadCiv3TileSet() { diff --git a/C7/OldMapView.cs b/C7/OldMapView.cs index 87753e8a..15dbb213 100644 --- a/C7/OldMapView.cs +++ b/C7/OldMapView.cs @@ -1,13 +1,9 @@ using System.Collections.Generic; -using System; -using System.Linq; using C7.Map; using Godot; using ConvertCiv3Media; using C7GameData; using C7Engine; -using Serilog; -using Serilog.Events; // Loose layers are for drawing things on the map on a per-tile basis. (Historical aside: There used to be another kind of layer called a TileLayer // that was intended to draw regularly tiled objects like terrain sprites but using LooseLayers for everything was found to be a prefereable @@ -31,40 +27,6 @@ public virtual void onEndDraw(LooseView looseView, GameData gameData) {} public bool visible = true; } -public partial class MarshLayer : LooseLayer { - public static readonly Vector2 marshSize = new Vector2(128, 88); - //Because the marsh graphics are 88 pixels tall instead of the 64 of a tile, we also need an addition 12 pixel offset to the top - //88 - 64 = 24; 24/2 = 12. This keeps the marsh centered with half the extra 24 pixels above the tile and half below. - readonly Vector2 MARSH_OFFSET = (float)0.5 * marshSize + new Vector2(0, -12); - - private ImageTexture largeMarshTexture; - private ImageTexture smallMarshTexture; - - public MarshLayer() { - largeMarshTexture = Util.LoadTextureFromPCX("Art/Terrain/marsh.pcx", 0, 0, 512, 176); - smallMarshTexture = Util.LoadTextureFromPCX("Art/Terrain/marsh.pcx", 0, 176, 640, 176); - } - - public override void drawObject(LooseView looseView, GameData gameData, Tile tile, Vector2 tileCenter) { - if (tile.overlayTerrainType.Key == "marsh") { - int randomJungleRow = tile.yCoordinate % 2; - int randomMarshColumn; - ImageTexture marshTexture; - if (tile.getEdgeNeighbors().Any(t => t.IsWater())) { - randomMarshColumn = tile.xCoordinate % 5; - marshTexture = smallMarshTexture; - } - else { - randomMarshColumn = tile.xCoordinate % 4; - marshTexture = largeMarshTexture; - } - Rect2 jungleRectangle = new Rect2(randomMarshColumn * marshSize.X, randomJungleRow * marshSize.Y, marshSize); - Rect2 screenTarget = new Rect2(tileCenter - MARSH_OFFSET, marshSize); - looseView.DrawTextureRectRegion(marshTexture, screenTarget, jungleRectangle); - } - } -} - public partial class GridLayer : LooseLayer { public Color color = Color.Color8(50, 50, 50, 150); public float lineWidth = (float)1.0; @@ -231,7 +193,6 @@ public OldMapView(Game game, int mapWidth, int mapHeight, bool wrapHorizontally, this.wrapVertically = wrapVertically; looseView = new LooseView(this); - looseView.layers.Add(new MarshLayer()); this.gridLayer = new GridLayer(); looseView.layers.Add(this.gridLayer); looseView.layers.Add(new BuildingLayer()); From 08b85f3557bde93e91498b85b2e0fa787b6bb234 Mon Sep 17 00:00:00 2001 From: Paul Cento Date: Thu, 22 Jun 2023 02:48:15 -0400 Subject: [PATCH 28/60] remove ported code --- C7/OldMapView.cs | 162 ++--------------------------------------------- 1 file changed, 4 insertions(+), 158 deletions(-) diff --git a/C7/OldMapView.cs b/C7/OldMapView.cs index 15dbb213..f6a88424 100644 --- a/C7/OldMapView.cs +++ b/C7/OldMapView.cs @@ -1,137 +1,21 @@ -using System.Collections.Generic; using C7.Map; using Godot; -using ConvertCiv3Media; using C7GameData; -using C7Engine; -// Loose layers are for drawing things on the map on a per-tile basis. (Historical aside: There used to be another kind of layer called a TileLayer -// that was intended to draw regularly tiled objects like terrain sprites but using LooseLayers for everything was found to be a prefereable -// approach.) LooseLayer is effectively the standard map layer. The MapView contains a list of loose layers, inside a LooseView object. Right now to -// add a new layer you must modify the MapView constructor to add it to the list, but (TODO) eventually that will be made moddable. -public abstract class LooseLayer { - // drawObject draws the things this layer is supposed to draw that are associated with the given tile. Its parameters are: - // looseView: The Node2D to actually draw to, e.g., use looseView.DrawCircle(...) to draw a circle. This object also contains a reference to - // the MapView in case you need it. - // gameData: A reference to the game data so each layer doesn't have to redundantly request access. - // tile: The game tile whose contents are to be drawn. This function gets called for each tile in view of the camera and none out of - // view. The same tile may be drawn multiple times at different locations due to edge wrapping. - // tileCenter: The location to draw to. You should draw around this location without adjusting for the camera location or zoom since the - // MapView already transforms the looseView node to account for those things. - public abstract void drawObject(LooseView looseView, GameData gameData, Tile tile, Vector2 tileCenter); - - public virtual void onBeginDraw(LooseView looseView, GameData gameData) {} - public virtual void onEndDraw(LooseView looseView, GameData gameData) {} - - // The layer will be skipped during map drawing if visible is false - public bool visible = true; -} - -public partial class GridLayer : LooseLayer { +public partial class GridLayer { public Color color = Color.Color8(50, 50, 50, 150); public float lineWidth = (float)1.0; public GridLayer() {} - public override void drawObject(LooseView looseView, GameData gameData, Tile tile, Vector2 tileCenter) + public void drawObject(Tile tile, Vector2 tileCenter) { Vector2 cS = OldMapView.cellSize; Vector2 left = tileCenter + new Vector2(-cS.X, 0 ); Vector2 top = tileCenter + new Vector2( 0 , -cS.Y); Vector2 right = tileCenter + new Vector2( cS.X, 0 ); - looseView.DrawLine(left, top , color, lineWidth); - looseView.DrawLine(top , right, color, lineWidth); - } -} - -public partial class BuildingLayer : LooseLayer { - private ImageTexture buildingsTex; - private Vector2 buildingSpriteSize; - - public BuildingLayer() - { - var buildingsPCX = new Pcx(Util.Civ3MediaPath("Art/Terrain/TerrainBuildings.PCX")); - buildingsTex = PCXToGodot.getImageTextureFromPCX(buildingsPCX); - //In Conquests, this graphic is 4x4, and the search path will now find the Conquests one first - buildingSpriteSize = new Vector2((float)buildingsTex.GetWidth() / 4, (float)buildingsTex.GetHeight() / 4); - } - - public override void drawObject(LooseView looseView, GameData gameData, Tile tile, Vector2 tileCenter) - { - if (tile.hasBarbarianCamp) { - var texRect = new Rect2(buildingSpriteSize * new Vector2 (2, 0), buildingSpriteSize); //(2, 0) is the offset in the TerrainBuildings.PCX file (top row, third in) - // TODO: Modify this calculation so it doesn't assume buildingSpriteSize is the same as the size of the terrain tiles - var screenRect = new Rect2(tileCenter - (float)0.5 * buildingSpriteSize, buildingSpriteSize); - looseView.DrawTextureRectRegion(buildingsTex, screenRect, texRect); - } - } -} - -public partial class LooseView : Node2D { - public OldMapView mapView; - public List layers = new List(); - - public LooseView(OldMapView mapView) - { - this.mapView = mapView; - } - - private struct VisibleTile - { - public Tile tile; - public Vector2 tileCenter; - } - - public override void _Draw() - { - base._Draw(); - - using (var gameDataAccess = new UIGameDataAccess()) { - GameData gD = gameDataAccess.gameData; - - // Iterating over visible tiles is unfortunately pretty expensive. Assemble a list of Tile references and centers first so we don't - // have to reiterate for each layer. Doing this improves framerate significantly. - OldMapView.VisibleRegion visRegion = mapView.getVisibleRegion(); - List visibleTiles = new List(); - for (int y = visRegion.upperLeftY; y < visRegion.lowerRightY; y++) { - if (gD.map.isRowAt(y)) { - for (int x = visRegion.getRowStartX(y); x < visRegion.lowerRightX; x += 2) { - Tile tile = gD.map.tileAt(x, y); - if (IsTileKnown(tile, gameDataAccess)) { - visibleTiles.Add(new VisibleTile { tile = tile, tileCenter = OldMapView.cellSize * new Vector2(x + 1, y + 1) }); - } - } - } - } - - foreach (LooseLayer layer in layers.FindAll(L => L.visible && !(L is FogOfWarLayer))) { - layer.onBeginDraw(this, gD); - foreach (VisibleTile vT in visibleTiles) { - layer.drawObject(this, gD, vT.tile, vT.tileCenter); - } - layer.onEndDraw(this, gD); - } - - if (!gD.observerMode) { - foreach (LooseLayer layer in layers.FindAll(layer => layer is FogOfWarLayer)) { - for (int y = visRegion.upperLeftY; y < visRegion.lowerRightY; y++) - if (gD.map.isRowAt(y)) - for (int x = visRegion.getRowStartX(y); x < visRegion.lowerRightX; x += 2) { - Tile tile = gD.map.tileAt(x, y); - if (tile != Tile.NONE) { - VisibleTile invisibleTile = new VisibleTile { tile = tile, tileCenter = OldMapView.cellSize * new Vector2(x + 1, y + 1) }; - layer.drawObject(this, gD, tile, invisibleTile.tileCenter); - } - } - } - } - } - } - private static bool IsTileKnown(Tile tile, UIGameDataAccess gameDataAccess) { - if (gameDataAccess.gameData.observerMode) { - return true; - } - return tile != Tile.NONE && gameDataAccess.gameData.GetHumanPlayers()[0].tileKnowledge.isTileKnown(tile); + // DrawLine(left, top , color, lineWidth); + // DrawLine(top , right, color, lineWidth); } } @@ -165,12 +49,6 @@ public float cameraZoom { set { setCameraZoomFromMiddle(value); } } - private LooseView looseView; - - // Specifies a rectangular block of tiles that are currently potentially on screen. Accessible through getVisibleRegion(). Tile coordinates - // are "virtual", i.e. "unwrapped", so there isn't necessarily a tile at each location. The region is intended to include the upper left - // coordinates but not the lower right ones. When iterating over all tiles in the region you must account for the fact that map rows are - // staggered, see LooseView._Draw for an example. public struct VisibleRegion { public int upperLeftX, upperLeftY; public int lowerRightX, lowerRightY; @@ -181,36 +59,6 @@ public int getRowStartX(int y) } } - public GridLayer gridLayer { get; private set; } - - public ImageTexture civColorWhitePalette = null; - public OldMapView(Game game, int mapWidth, int mapHeight, bool wrapHorizontally, bool wrapVertically) - { - this.game = game; - this.mapWidth = mapWidth; - this.mapHeight = mapHeight; - this.wrapHorizontally = wrapHorizontally; - this.wrapVertically = wrapVertically; - - looseView = new LooseView(this); - this.gridLayer = new GridLayer(); - looseView.layers.Add(this.gridLayer); - looseView.layers.Add(new BuildingLayer()); - looseView.layers.Add(new CityLayer()); - - (civColorWhitePalette, _) = Util.loadPalettizedPCX("Art/Units/Palettes/ntp00.pcx"); - - AddChild(looseView); - } - - public override void _Process(double delta) - { - // Redraw everything. This is necessary so that animations play. Maybe we could only update the unit layer but long term I think it's - // better to redraw everything every frame like a typical modern video game. - looseView.QueueRedraw(); - } - - // Returns the size in pixels of the area in which the map will be drawn. This is the viewport size or, if that's null, the window size. public Vector2 getVisibleAreaSize() { return GetViewport() != null ? GetViewportRect().Size : DisplayServer.WindowGetSize(); @@ -232,7 +80,6 @@ public void setCameraZoom(float newScale, Vector2 center) Vector2 v2OldZoom = new Vector2(cameraZoom, cameraZoom); if (v2NewZoom != v2OldZoom) { internalCameraZoom = newScale; - looseView.Scale = v2NewZoom; setCameraLocation ((v2NewZoom / v2OldZoom) * (cameraLocation + center) - center); } } @@ -292,7 +139,6 @@ public void setCameraLocation(Vector2 location) } internalCameraLocation = location; - looseView.Position = -location; } public Vector2 screenLocationOfTileCoords(int x, int y, bool center = true) From 6b78afa3240db0a3f3964000910eb64349b233b3 Mon Sep 17 00:00:00 2001 From: Paul Cento Date: Thu, 22 Jun 2023 16:24:40 -0400 Subject: [PATCH 29/60] load goodyhut pcx --- C7/C7Game.tscn | 1 - C7/Game.cs | 9 ++- C7/LogManager.cs | 1 - C7/Map/CityLayer.cs | 5 +- C7/Map/FogOfWarLayer.cs | 6 +- C7/Map/MapView.cs | 1 + C7/Map/TileSetLoader.cs | 21 ++++--- C7/Map/UnitLayer.cs | 129 ++++++++++++++++++++-------------------- 8 files changed, 89 insertions(+), 84 deletions(-) diff --git a/C7/C7Game.tscn b/C7/C7Game.tscn index 792fdf10..42b69ea4 100644 --- a/C7/C7Game.tscn +++ b/C7/C7Game.tscn @@ -260,7 +260,6 @@ script = ExtResource("11_pkhac") [connection signal="TurnStarted" from="." to="CanvasLayer/Control/GameStatus" method="OnTurnStarted"] [connection signal="BuildCity" from="CanvasLayer/PopupOverlay" to="." method="OnBuildCity"] [connection signal="HidePopup" from="CanvasLayer/PopupOverlay" to="CanvasLayer/PopupOverlay" method="OnHidePopup"] -[connection signal="Quit" from="CanvasLayer/PopupOverlay" to="." method="OnQuitTheGame"] [connection signal="UnitDisbanded" from="CanvasLayer/PopupOverlay" to="." method="OnUnitDisbanded"] [connection signal="BlinkyEndTurnButtonPressed" from="CanvasLayer/Control/GameStatus" to="." method="OnPlayerEndTurn"] [connection signal="pressed" from="CanvasLayer/Control/ToolBar/MarginContainer/HBoxContainer/AdvisorButton" to="CanvasLayer/Advisor" method="ShowLatestAdvisor"] diff --git a/C7/Game.cs b/C7/Game.cs index 9cb34e07..5b39fe50 100644 --- a/C7/Game.cs +++ b/C7/Game.cs @@ -55,6 +55,7 @@ public override void _EnterTree() { // The catch should always catch any error, as it's the general catch // that gives an error if we fail to load for some reason. public override void _Ready() { + GetTree().AutoAcceptQuit = false; Global = GetNode("/root/GlobalSingleton"); try { var animSoundPlayer = new AudioStreamPlayer(); @@ -553,9 +554,11 @@ private void OnUnitDisbanded() { /** * User quit. We *may* want to do some things here like make a back-up save, or call the server and let it know we're bailing (esp. in MP). **/ - private void OnQuitTheGame() { - log.Information("Goodbye!"); - GetTree().Quit(); + public override void _Notification(int what) { + if (what == NotificationWMCloseRequest) { + log.Information("Goodbye!"); + GetTree().Quit(); + } } private void OnBuildCity(string name) { diff --git a/C7/LogManager.cs b/C7/LogManager.cs index a686286a..e18f80b2 100644 --- a/C7/LogManager.cs +++ b/C7/LogManager.cs @@ -38,7 +38,6 @@ public override void _Notification(int what) { GD.Print("Goodbye logger!"); Log.ForContext().Debug("Goodbye!"); Log.CloseAndFlush(); - GetTree().Quit(); } } diff --git a/C7/Map/CityLayer.cs b/C7/Map/CityLayer.cs index 074454ed..c84f714e 100644 --- a/C7/Map/CityLayer.cs +++ b/C7/Map/CityLayer.cs @@ -4,7 +4,7 @@ using Serilog; namespace C7.Map { - public class CityLayer : LooseLayer { + public class CityLayer { private ILogger log = LogManager.ForContext(); @@ -18,7 +18,7 @@ public CityLayer() { } - public override void drawObject(LooseView looseView, GameData gameData, Tile tile, Vector2 tileCenter) + public void drawObject(GameData gameData, Tile tile, Vector2 tileCenter) { if (tile.cityAtTile is null) { return; @@ -27,7 +27,6 @@ public override void drawObject(LooseView looseView, GameData gameData, Tile til City city = tile.cityAtTile; if (!citySceneLookup.ContainsKey(city)) { CityScene cityScene = new CityScene(city, tile, new Vector2I((int)tileCenter.X, (int)tileCenter.Y)); - looseView.AddChild(cityScene); citySceneLookup[city] = cityScene; } else { CityScene scene = citySceneLookup[city]; diff --git a/C7/Map/FogOfWarLayer.cs b/C7/Map/FogOfWarLayer.cs index eb88eab9..0ead7d18 100644 --- a/C7/Map/FogOfWarLayer.cs +++ b/C7/Map/FogOfWarLayer.cs @@ -3,7 +3,7 @@ using Godot; namespace C7.Map { - public partial class FogOfWarLayer : LooseLayer { + public partial class FogOfWarLayer { private readonly ImageTexture fogOfWarTexture; private readonly Vector2 tileSize; @@ -14,7 +14,7 @@ public FogOfWarLayer() { tileSize = fogOfWarTexture.GetSize() / 9; } - public override void drawObject(LooseView looseView, GameData gameData, Tile tile, Vector2 tileCenter) { + public void drawObject(GameData gameData, Tile tile, Vector2 tileCenter) { Rect2 screenTarget = new Rect2(tileCenter - tileSize / 2, tileSize); TileKnowledge tileKnowledge = gameData.GetHumanPlayers()[0].tileKnowledge; //N.B. FogOfWar.pcx handles both totally unknown and fogged tiles, indexed in the same file. @@ -30,7 +30,7 @@ public override void drawObject(LooseView looseView, GameData gameData, Tile til if (tileKnowledge.isTileKnown(tile.neighbors[TileDirection.SOUTH]) || tileKnowledge.isTileKnown(tile.neighbors[TileDirection.SOUTHWEST]) || tileKnowledge.isTileKnown(tile.neighbors[TileDirection.SOUTHEAST])) sum += 27 * 2; if (sum != 0) { - looseView.DrawTextureRectRegion(fogOfWarTexture, screenTarget, getRect(sum)); + // DrawTextureRectRegion(fogOfWarTexture, screenTarget, getRect(sum)); } } //do nothing if the tile is known (equiv to the lower-right) diff --git a/C7/Map/MapView.cs b/C7/Map/MapView.cs index 1c5593ff..d2c47960 100644 --- a/C7/Map/MapView.cs +++ b/C7/Map/MapView.cs @@ -404,6 +404,7 @@ private void updateTerrainOverlayLayer(Tile tile) { } private void updateBuildingLayer(Tile tile) { + // TODO: add goody huts here once they are stored in the save and Tile class if (tile.hasBarbarianCamp) { setCell(Layer.Building, Atlas.TerrainBuilding, tile, new Vector2I(2, 0)); } else { diff --git a/C7/Map/TileSetLoader.cs b/C7/Map/TileSetLoader.cs index 4f2396e8..ed20e13b 100644 --- a/C7/Map/TileSetLoader.cs +++ b/C7/Map/TileSetLoader.cs @@ -43,6 +43,7 @@ public enum Atlas { Resource, TerrainYield, TerrainBuilding, + GoodyHut, Invalid, } @@ -54,8 +55,8 @@ public static int Index(this Atlas atlas) { class AtlasLoader { string path; - int width; - int height; + protected int width; + protected int height; Vector2I regionSize; Vector2I textureOrigin; protected TileSetAtlasSource source; @@ -120,13 +121,16 @@ protected override void load() { } }; - class ResourceAtlasLoader : AtlasLoader { - public ResourceAtlasLoader(string p, Vector2I rs) : base(p, -1, -1, rs) {} + class NonSquareAtlasLoader : AtlasLoader { + int lastRowWidth; + public NonSquareAtlasLoader(string p, int w, int h, int lastRowWidth, Vector2I rs) : base(p, w, h, rs) { + this.lastRowWidth = lastRowWidth; + } protected override void load() { - for (int y = 0; y < 5; y++) { - for (int x = 0; x < 6; x++) { - if (y == 4 && x > 1) { + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + if (y == height - 1 && x >= lastRowWidth) { continue; } createTile(x, y); @@ -168,7 +172,7 @@ class TileSetLoader { private static readonly Vector2I buildingSize = new Vector2I(128, 64); private static readonly Dictionary civ3PcxForAtlas = new Dictionary { - {Atlas.Resource, new ResourceAtlasLoader("Conquests/Art/resources.pcx", resourceSize)}, + {Atlas.Resource, new NonSquareAtlasLoader("Conquests/Art/resources.pcx", 6, 4, 4, resourceSize)}, {Atlas.Road, new AtlasLoader("Art/Terrain/roads.pcx", 16, 16, tileSize)}, {Atlas.Rail, new AtlasLoader("Art/Terrain/railroads.pcx", 16, 16, tileSize)}, @@ -197,6 +201,7 @@ class TileSetLoader { {Atlas.Marsh, new MarshAtlasLoader("Art/Terrain/marsh.pcx", marshSize)}, {Atlas.TerrainBuilding, new AtlasLoader("Art/Terrain/TerrainBuildings.pcx", 4, 4, buildingSize)}, + {Atlas.GoodyHut, new NonSquareAtlasLoader("Art/Terrain/goodyhuts.pcx", 3, 3, 2, buildingSize)}, }; public static TileSet LoadCiv3TileSet() { diff --git a/C7/Map/UnitLayer.cs b/C7/Map/UnitLayer.cs index 4a6b042d..16fa3aca 100644 --- a/C7/Map/UnitLayer.cs +++ b/C7/Map/UnitLayer.cs @@ -63,7 +63,7 @@ public UnitSprite(AnimationManager manager) { } } -public partial class UnitLayer : LooseLayer { +public partial class UnitLayer { private ImageTexture unitIcons; private int unitIconsWidth; private ImageTexture unitMovementIndicators; @@ -109,7 +109,7 @@ public Color getHPColor(float fractionRemaining) { private int nextBlankAnimInst = 0; // Returns the next unused AnimationInstance or creates & returns a new one if none are available. - public UnitSprite getBlankAnimationInstance(LooseView looseView) { + public UnitSprite getBlankAnimationInstance() { if (nextBlankAnimInst >= animInsts.Count) { // animInsts.Add(new AnimationInstance(looseView)); } @@ -118,9 +118,9 @@ public UnitSprite getBlankAnimationInstance(LooseView looseView) { return inst; } - public void drawUnitAnimFrame(LooseView looseView, MapUnit unit, MapUnit.Appearance appearance, Vector2 tileCenter) { - UnitSprite inst = getBlankAnimationInstance(looseView); - looseView.mapView.game.civ3AnimData.forUnit(unit.unitType, appearance.action).loadSpriteAnimation(); + public void drawUnitAnimFrame(MapUnit unit, MapUnit.Appearance appearance, Vector2 tileCenter) { + UnitSprite inst = getBlankAnimationInstance(); + // mapView.game.civ3AnimData.forUnit(unit.unitType, appearance.action).loadSpriteAnimation(); string animName = AnimationManager.AnimationKey(unit.unitType, appearance.action, appearance.direction); // Need to move the sprites upward a bit so that their feet are at the center of the tile. I don't know if spriteHeight/4 is the right @@ -137,7 +137,7 @@ public void drawUnitAnimFrame(LooseView looseView, MapUnit unit, MapUnit.Appeara inst.Show(); } - public void drawEffectAnimFrame(LooseView looseView, C7Animation anim, float progress, Vector2 tileCenter) { + public void drawEffectAnimFrame(C7Animation anim, float progress, Vector2 tileCenter) { // var flicSheet = anim.getFlicSheet(); // var inst = getBlankAnimationInstance(looseView); // setFlicShaderParams(inst.shaderMat, flicSheet, 0, progress); @@ -149,7 +149,7 @@ public void drawEffectAnimFrame(LooseView looseView, C7Animation anim, float pro private AnimatedSprite2D cursorSprite = null; - public void drawCursor(LooseView looseView, Vector2 position) { + public void drawCursor(Vector2 position) { // Initialize cursor if necessary if (cursorSprite == null) { cursorSprite = new AnimatedSprite2D(); @@ -157,7 +157,6 @@ public void drawCursor(LooseView looseView, Vector2 position) { cursorSprite.SpriteFrames = frames; AnimationManager.loadCursorAnimation("Art/Animations/Cursor/Cursor.flc", ref frames); cursorSprite.Animation = "cursor"; // hardcoded in loadCursorAnimation - looseView.AddChild(cursorSprite); } const double period = 2.5; // TODO: Just eyeballing this for now. Read the actual period from the INI or something. @@ -171,7 +170,7 @@ public void drawCursor(LooseView looseView, Vector2 position) { cursorSprite.Show(); } - public override void onBeginDraw(LooseView looseView, GameData gameData) { + public void onBeginDraw(GameData gameData) { // Reset animation instances for (int n = 0; n < nextBlankAnimInst; n++) { animInsts[n].Hide(); @@ -181,37 +180,37 @@ public override void onBeginDraw(LooseView looseView, GameData gameData) { // Hide cursor if it's been initialized cursorSprite?.Hide(); - looseView.mapView.game.updateAnimations(gameData); + // looseView.mapView.game.updateAnimations(gameData); } // Returns which unit should be drawn from among a list of units. The list is assumed to be non-empty. - public MapUnit selectUnitToDisplay(LooseView looseView, List units) { + public MapUnit selectUnitToDisplay(List units) { // From the list, pick out which units are (1) the strongest defender vs the currently selected unit, (2) the currently selected unit // itself if it's in the list, and (3) any unit that is playing an animation that the player would want to see. MapUnit bestDefender = units[0], selected = null, doingInterestingAnimation = null; - var currentlySelectedUnit = looseView.mapView.game.CurrentlySelectedUnit; + MapUnit currentlySelectedUnit = null; // looseView.mapView.game.CurrentlySelectedUnit; foreach (var u in units) { if (u == currentlySelectedUnit) selected = u; if (u.HasPriorityAsDefender(bestDefender, currentlySelectedUnit)) bestDefender = u; - if (looseView.mapView.game.animTracker.getUnitAppearance(u).DeservesPlayerAttention()) - doingInterestingAnimation = u; + // if (looseView.mapView.game.animTracker.getUnitAppearance(u).DeservesPlayerAttention()) + // doingInterestingAnimation = u; } // Prefer showing the selected unit, secondly show one doing a relevant animation, otherwise show the top defender return selected != null ? selected : (doingInterestingAnimation != null ? doingInterestingAnimation : bestDefender); } - public override void drawObject(LooseView looseView, GameData gameData, Tile tile, Vector2 tileCenter) { + public void drawObject(GameData gameData, Tile tile, Vector2 tileCenter) { // First draw animated effects. These will always appear over top of units regardless of draw order due to z-index. - C7Animation tileEffect = looseView.mapView.game.animTracker.getTileEffect(tile); - if (tileEffect != null) { - (_, float progress) = looseView.mapView.game.animTracker.getCurrentActionAndProgress(tile); - drawEffectAnimFrame(looseView, tileEffect, progress, tileCenter); - } + // C7Animation tileEffect = looseView.mapView.game.animTracker.getTileEffect(tile); + // if (tileEffect != null) { + // (_, float progress) = looseView.mapView.game.animTracker.getCurrentActionAndProgress(tile); + // drawEffectAnimFrame(looseView, tileEffect, progress, tileCenter); + // } if (tile.unitsOnTile.Count == 0) { return; @@ -219,50 +218,50 @@ public override void drawObject(LooseView looseView, GameData gameData, Tile til var white = Color.Color8(255, 255, 255); - MapUnit unit = selectUnitToDisplay(looseView, tile.unitsOnTile); - MapUnit.Appearance appearance = looseView.mapView.game.animTracker.getUnitAppearance(unit); - Vector2 animOffset = new Vector2(appearance.offsetX, appearance.offsetY) * OldMapView.cellSize; - - // If the unit we're about to draw is currently selected, draw the cursor first underneath it - if ((unit != MapUnit.NONE) && (unit == looseView.mapView.game.CurrentlySelectedUnit)) { - drawCursor(looseView, tileCenter + animOffset); - } - - drawUnitAnimFrame(looseView, unit, appearance, tileCenter); - - Vector2 indicatorLoc = tileCenter - new Vector2(26, 40) + animOffset; - - int moveIndIndex = (!unit.movementPoints.canMove) ? 4 : ((unit.movementPoints.remaining >= unit.unitType.movement) ? 0 : 2); - Vector2 moveIndUpperLeft = new Vector2(1 + 7 * moveIndIndex, 1); - Rect2 moveIndRect = new Rect2(moveIndUpperLeft, new Vector2(6, 6)); - var screenRect = new Rect2(indicatorLoc, new Vector2(6, 6)); - looseView.DrawTextureRectRegion(unitMovementIndicators, screenRect, moveIndRect); - - int hpIndHeight = 6 * (unit.maxHitPoints <= 5 ? unit.maxHitPoints : 5), hpIndWidth = 6; - Rect2 hpIndBackgroundRect = new Rect2(indicatorLoc + new Vector2(-1, 8), new Vector2(hpIndWidth, hpIndHeight)); - if ((unit.unitType.attack > 0) || (unit.unitType.defense > 0)) { - float hpFraction = (float)unit.hitPointsRemaining / unit.maxHitPoints; - looseView.DrawRect(hpIndBackgroundRect, Color.Color8(0, 0, 0)); - float hpHeight = hpFraction * (hpIndHeight - 2); - if (hpHeight < 1) - hpHeight = 1; - var hpContentsRect = new Rect2(hpIndBackgroundRect.Position + new Vector2(1, hpIndHeight - 1 - hpHeight), // position - new Vector2(hpIndWidth - 2, hpHeight)); // size - looseView.DrawRect(hpContentsRect, getHPColor(hpFraction)); - if (unit.isFortified) - looseView.DrawRect(hpIndBackgroundRect, white, false); - } - - // Draw lines to show that there are more units on this tile - if (tile.unitsOnTile.Count > 1) { - int lineCount = tile.unitsOnTile.Count; - if (lineCount > 5) - lineCount = 5; - for (int n = 0; n < lineCount; n++) { - var lineStart = indicatorLoc + new Vector2(-2, hpIndHeight + 12 + 4 * n); - looseView.DrawLine(lineStart, lineStart + new Vector2(8, 0), white); - looseView.DrawLine(lineStart + new Vector2(0, 1), lineStart + new Vector2(8, 1), Color.Color8(75, 75, 75)); - } - } + // MapUnit unit = selectUnitToDisplay(looseView, tile.unitsOnTile); + // MapUnit.Appearance appearance = looseView.mapView.game.animTracker.getUnitAppearance(unit); + // Vector2 animOffset = new Vector2(appearance.offsetX, appearance.offsetY) * OldMapView.cellSize; + + // // If the unit we're about to draw is currently selected, draw the cursor first underneath it + // if ((unit != MapUnit.NONE) && (unit == looseView.mapView.game.CurrentlySelectedUnit)) { + // drawCursor(looseView, tileCenter + animOffset); + // } + + // drawUnitAnimFrame(looseView, unit, appearance, tileCenter); + + // Vector2 indicatorLoc = tileCenter - new Vector2(26, 40) + animOffset; + + // int moveIndIndex = (!unit.movementPoints.canMove) ? 4 : ((unit.movementPoints.remaining >= unit.unitType.movement) ? 0 : 2); + // Vector2 moveIndUpperLeft = new Vector2(1 + 7 * moveIndIndex, 1); + // Rect2 moveIndRect = new Rect2(moveIndUpperLeft, new Vector2(6, 6)); + // var screenRect = new Rect2(indicatorLoc, new Vector2(6, 6)); + // looseView.DrawTextureRectRegion(unitMovementIndicators, screenRect, moveIndRect); + + // int hpIndHeight = 6 * (unit.maxHitPoints <= 5 ? unit.maxHitPoints : 5), hpIndWidth = 6; + // Rect2 hpIndBackgroundRect = new Rect2(indicatorLoc + new Vector2(-1, 8), new Vector2(hpIndWidth, hpIndHeight)); + // if ((unit.unitType.attack > 0) || (unit.unitType.defense > 0)) { + // float hpFraction = (float)unit.hitPointsRemaining / unit.maxHitPoints; + // looseView.DrawRect(hpIndBackgroundRect, Color.Color8(0, 0, 0)); + // float hpHeight = hpFraction * (hpIndHeight - 2); + // if (hpHeight < 1) + // hpHeight = 1; + // var hpContentsRect = new Rect2(hpIndBackgroundRect.Position + new Vector2(1, hpIndHeight - 1 - hpHeight), // position + // new Vector2(hpIndWidth - 2, hpHeight)); // size + // looseView.DrawRect(hpContentsRect, getHPColor(hpFraction)); + // if (unit.isFortified) + // looseView.DrawRect(hpIndBackgroundRect, white, false); + // } + + // // Draw lines to show that there are more units on this tile + // if (tile.unitsOnTile.Count > 1) { + // int lineCount = tile.unitsOnTile.Count; + // if (lineCount > 5) + // lineCount = 5; + // for (int n = 0; n < lineCount; n++) { + // var lineStart = indicatorLoc + new Vector2(-2, hpIndHeight + 12 + 4 * n); + // looseView.DrawLine(lineStart, lineStart + new Vector2(8, 0), white); + // looseView.DrawLine(lineStart + new Vector2(0, 1), lineStart + new Vector2(8, 1), Color.Color8(75, 75, 75)); + // } + // } } } From 57867f5fce781e9889653da84cd573312795b224 Mon Sep 17 00:00:00 2001 From: Paul Cento Date: Thu, 22 Jun 2023 16:39:38 -0400 Subject: [PATCH 30/60] pixel perfect transforms --- C7/project.godot | 2 ++ 1 file changed, 2 insertions(+) diff --git a/C7/project.godot b/C7/project.godot index e9954fa5..023bff70 100644 --- a/C7/project.godot +++ b/C7/project.godot @@ -196,5 +196,7 @@ limits/debugger_stdout/max_chars_per_second=65535 textures/canvas_textures/default_texture_filter=0 environment/defaults/default_clear_color=Color(0.301961, 0.301961, 0.301961, 1) +2d/snap/snap_2d_transforms_to_pixel=true 2d/snap/snap_2d_vertices_to_pixel=true +lights_and_shadows/positional_shadow/atlas_quadrant_2_subdiv=0 environment/default_environment="res://default_env.tres" From 697f910f46c1ef9b737c92304e462a72cad92bfc Mon Sep 17 00:00:00 2001 From: Paul Cento Date: Thu, 22 Jun 2023 16:45:52 -0400 Subject: [PATCH 31/60] dedupe code --- C7/Map/MapView.cs | 38 ++++++++++++++------------------------ 1 file changed, 14 insertions(+), 24 deletions(-) diff --git a/C7/Map/MapView.cs b/C7/Map/MapView.cs index d2c47960..415e329a 100644 --- a/C7/Map/MapView.cs +++ b/C7/Map/MapView.cs @@ -58,29 +58,7 @@ private void initializeTileMap() { } private void setTerrainTiles() { - for (int x = 0; x < width; x++) { - for (int y = 0; y < height; y++) { - Vector2I cell = new Vector2I(x, y); - string left = terrain[x, y]; - string right = terrain[(x + 1) % width, y]; - bool even = y % 2 == 0; - string top = "coast"; - if (y > 0) { - top = even ? terrain[x, y - 1] : terrain[(x + 1) % width, y - 1]; - } - string bottom = "coast"; - if (y < height - 1) { - bottom = even ? terrain[x, y + 1] : terrain[(x + 1) % width, y + 1]; - } - string[] corner = new string[4]{top, right, bottom, left}; - TerrainPcx pcx = Civ3TerrainTileSet.GetPcxFor(corner); - Vector2I texCoords = pcx.getTextureCoords(corner); - setTerrainTile(cell, pcx.atlas, texCoords); - } - } - for (int y = 0; y < height; y++) { - Vector2I cell = new Vector2I(-1, y); - int x = width - 1; + string[] corners(int x, int y) { string left = terrain[x, y]; string right = terrain[(x + 1) % width, y]; bool even = y % 2 == 0; @@ -92,11 +70,23 @@ private void setTerrainTiles() { if (y < height - 1) { bottom = even ? terrain[x, y + 1] : terrain[(x + 1) % width, y + 1]; } - string[] corner = new string[4]{top, right, bottom, left}; + return new string[4]{top, right, bottom, left}; + } + void lookupAndSetTerrainTile(int x, int y, int cellX, int cellY) { + Vector2I cell = new Vector2I(cellX, cellY); + string[] corner = corners(x, y); TerrainPcx pcx = Civ3TerrainTileSet.GetPcxFor(corner); Vector2I texCoords = pcx.getTextureCoords(corner); setTerrainTile(cell, pcx.atlas, texCoords); } + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + lookupAndSetTerrainTile(x, y, x, y); + } + } + for (int y = 0; y < height; y++) { + lookupAndSetTerrainTile(width - 1, y, -1, y); + } } void setTerrainTile(Vector2I cell, int atlas, Vector2I texCoords) { From 74c354c96025a1ad2cc4d2c3d1d63cb4f08be91e Mon Sep 17 00:00:00 2001 From: Paul Cento Date: Sun, 25 Jun 2023 00:46:43 -0400 Subject: [PATCH 32/60] enable Y sort for each tilemap layer --- C7/AnimationTracker.cs | 11 ++++------- C7/Map/MapView.cs | 12 ++++++++++++ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/C7/AnimationTracker.cs b/C7/AnimationTracker.cs index 17ac100c..881015d9 100644 --- a/C7/AnimationTracker.cs +++ b/C7/AnimationTracker.cs @@ -4,7 +4,6 @@ using System.Threading; using System.Linq; using C7GameData; -using C7Engine; public partial class AnimationTracker { private AnimationManager civ3AnimData; @@ -22,7 +21,7 @@ public struct ActiveAnimation { public C7Animation anim; } - private Dictionary activeAnims = new Dictionary(); + public Dictionary activeAnims = new Dictionary(); public long getCurrentTimeMS() { @@ -45,8 +44,9 @@ private void startAnimation(string id, C7Animation anim, AutoResetEvent completi if (activeAnims.TryGetValue(id, out aa)) { // If there's already an animation playing for this unit, end it first before replacing it // TODO: Consider instead queueing up the new animation until after the first one is completed - if (aa.completionEvent != null) + if (aa.completionEvent is not null) { aa.completionEvent.Set(); + } } aa = new ActiveAnimation { startTimeMS = currentTimeMS, endTimeMS = currentTimeMS + animDurationMS, completionEvent = completionEvent, ending = ending, anim = anim }; @@ -158,9 +158,6 @@ public MapUnit.Appearance getUnitAppearance(MapUnit unit) public C7Animation getTileEffect(Tile tile) { ActiveAnimation aa; - if (activeAnims.TryGetValue(getTileID(tile), out aa)) - return aa.anim; - else - return null; + return activeAnims.TryGetValue(getTileID(tile), out aa) ? aa.anim : null; } } diff --git a/C7/Map/MapView.cs b/C7/Map/MapView.cs index 415e329a..991f9c96 100644 --- a/C7/Map/MapView.cs +++ b/C7/Map/MapView.cs @@ -25,8 +25,18 @@ public bool showGrid { showGrid = value; } } + private Game game; private GameMap gameMap; + public override void _Draw() { + GD.Print("draw..."); + game.animTracker.update(); + foreach ((string id, AnimationTracker.ActiveAnimation anim) in game.animTracker.activeAnims) { + GD.Print($"{id}: {anim.ToString()}"); + } + base._Draw(); + } + public override void _Process(double delta) { base._Process(delta); } @@ -48,6 +58,7 @@ private void initializeTileMap() { foreach (Layer layer in Enum.GetValues(typeof(Layer))) { if (layer != Layer.Invalid) { tilemap.AddLayer(layer.Index()); + tilemap.SetLayerYSortEnabled(layer.Index(), true); } } @@ -108,6 +119,7 @@ private Vector2I stackedCoords(Tile tile) { } public MapView(Game game, GameData data) { + this.game = game; gameMap = data.map; width = gameMap.numTilesWide / 2; height = gameMap.numTilesTall; From 6ebfcab92b8f24bd85bec2644d41b907798f2294 Mon Sep 17 00:00:00 2001 From: Paul Cento Date: Sun, 9 Jul 2023 13:57:18 -0400 Subject: [PATCH 33/60] fix rebase issue --- C7/C7Game.tscn | 13 ++++++------- C7/Game.cs | 2 +- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/C7/C7Game.tscn b/C7/C7Game.tscn index 42b69ea4..16b0e10e 100644 --- a/C7/C7Game.tscn +++ b/C7/C7Game.tscn @@ -94,6 +94,7 @@ script = ExtResource("9") [node name="Control" type="Control" parent="CanvasLayer"] layout_mode = 3 +anchors_preset = 15 anchor_right = 1.0 anchor_bottom = 1.0 offset_left = 10.0 @@ -181,12 +182,14 @@ text = "End Turn" [node name="SlideOutBar" type="Control" parent="CanvasLayer/Control"] layout_mode = 1 +anchors_preset = 11 anchor_left = 1.0 anchor_right = 1.0 anchor_bottom = 1.0 -offset_top = 50.0 -offset_right = -10.0 -offset_bottom = -200.0 +offset_left = -108.0 +offset_top = 80.0 +offset_right = -108.0 +offset_bottom = -170.0 grow_horizontal = 0 grow_vertical = 2 mouse_filter = 1 @@ -247,14 +250,10 @@ libraries = { "": SubResource("AnimationLibrary_bowxq") } -[node name="PlayerCamera" type="Camera2D" parent="CanvasLayer"] -script = ExtResource("11_pkhac") - [connection signal="NewAutoselectedUnit" from="." to="CanvasLayer/Control/GameStatus" method="OnNewUnitSelected"] [connection signal="NewAutoselectedUnit" from="." to="CanvasLayer/Control/UnitButtons" method="OnNewUnitSelected"] [connection signal="NoMoreAutoselectableUnits" from="." to="CanvasLayer/Control/GameStatus" method="OnNoMoreAutoselectableUnits"] [connection signal="NoMoreAutoselectableUnits" from="." to="CanvasLayer/Control/UnitButtons" method="OnNoMoreAutoselectableUnits"] - [connection signal="ShowSpecificAdvisor" from="." to="CanvasLayer/Advisor" method="OnShowSpecificAdvisor"] [connection signal="TurnEnded" from="." to="CanvasLayer/Control/GameStatus" method="OnTurnEnded"] [connection signal="TurnStarted" from="." to="CanvasLayer/Control/GameStatus" method="OnTurnStarted"] diff --git a/C7/Game.cs b/C7/Game.cs index 5b39fe50..93b066c5 100644 --- a/C7/Game.cs +++ b/C7/Game.cs @@ -92,7 +92,7 @@ public override void _Ready() { AddChild(mapView); - Toolbar = GetNode("CanvasLayer/ToolBar/MarginContainer/HBoxContainer"); + Toolbar = GetNode("CanvasLayer/Control/ToolBar/MarginContainer/HBoxContainer"); //TODO: What was this supposed to do? It throws errors and occasinally causes crashes now, because _OnViewportSizeChanged doesn't exist // GetTree().Root.Connect("size_changed",new Callable(this,"_OnViewportSizeChanged")); From e46a094a7f34b5b076d5e275234a74d70e20df48 Mon Sep 17 00:00:00 2001 From: Paul Cento Date: Mon, 31 Jul 2023 19:20:16 +0200 Subject: [PATCH 34/60] implement grid layer --- .gitignore | 1 - C7/Art/grid.png | Bin 0 -> 756 bytes C7/Game.cs | 2 +- C7/Map/MapView.cs | 31 ++++++++++++++++++++++++------- C7/Map/TileSetLoader.cs | 13 +++++++++++-- 5 files changed, 36 insertions(+), 11 deletions(-) create mode 100644 C7/Art/grid.png diff --git a/.gitignore b/.gitignore index f6c4cef6..7a982616 100644 --- a/.gitignore +++ b/.gitignore @@ -20,7 +20,6 @@ data_*/ # Below here are ignores imported from Puppeteer's project sav/ -*.png *.pcx *.flc *.mp4 diff --git a/C7/Art/grid.png b/C7/Art/grid.png new file mode 100644 index 0000000000000000000000000000000000000000..8c3e5f58544b55656379ab0823ea0b58d88e9231 GIT binary patch literal 756 zcmVPx#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91fS>~a1ONa40RR91KmY&$0HK@gtN;K4TuDShRCodHomo=DFc3r&P8m)} zZW+ofDVM8c%hD{}J?1OK)=c+%hCjmnR`l!h@p!GPe?6b~_4n6nQ38wjt^B2G>wQrS z^=kDB?!B)bLDI&fLPyV$V$zFkCD3Y)9%0Z%Uc$WQf@0*06(uNY51)ag#92bJ_9kJ* zTZm(gikCdcI!##vAgh7oR#NCVg0&KoC z>C8Qu52QiK9dz2;^Z;&tGU-oyJbge0m3EOsuZK5i^C{DA>-oOCv9!PbJi5#?@Kf~P z`$w(XSKB6m?XQOBjn@Tg0*9UtZ=kMUH~eX@rw`O+3*CI$G|2@RB4?J}PkZ962uC_`O1V@6#nE-mE3K^}aZc mTiJh^z6f8-t@`;0-_bqwof$YOzW@LL07*qoM6N<$f&c(JJ62=> literal 0 HcmV?d00001 diff --git a/C7/Game.cs b/C7/Game.cs index 93b066c5..34c757e4 100644 --- a/C7/Game.cs +++ b/C7/Game.cs @@ -453,7 +453,7 @@ private void processActions() { } if (Input.IsActionJustPressed(C7Action.ToggleGrid)) { - mapView.showGrid = !mapView.showGrid; + mapView.toggleGrid(); } if (Input.IsActionJustPressed(C7Action.Escape) && !this.inUnitGoToMode) { diff --git a/C7/Map/MapView.cs b/C7/Map/MapView.cs index 991f9c96..3841263f 100644 --- a/C7/Map/MapView.cs +++ b/C7/Map/MapView.cs @@ -19,13 +19,19 @@ public partial class MapView : Node2D { public int worldEdgeLeft {get; private set;} private int width; private int height; - public bool showGrid { - get => showGrid; - set { - showGrid = value; - } - } + private bool showGrid = false; + private void setShowGrid(bool value) { + bool update = showGrid != value; + showGrid = value; + if (update) { + updateGridLayer(); + } + } + public void toggleGrid() { + setShowGrid(!showGrid); + } private Game game; + private GameData data; private GameMap gameMap; public override void _Draw() { @@ -119,8 +125,9 @@ private Vector2I stackedCoords(Tile tile) { } public MapView(Game game, GameData data) { + this.data = data; this.game = game; - gameMap = data.map; + this.gameMap = data.map; width = gameMap.numTilesWide / 2; height = gameMap.numTilesTall; initializeTileMap(); @@ -441,5 +448,15 @@ public void updateTile(Tile tile) { updateBuildingLayer(tile); } + + private void updateGridLayer() { + if (showGrid) { + foreach (Tile tile in data.map.tiles) { + setCell(Layer.Grid, Atlas.Grid, tile, Vector2I.Zero); + } + } else { + tilemap.ClearLayer(Layer.Grid.Index()); + } + } } } diff --git a/C7/Map/TileSetLoader.cs b/C7/Map/TileSetLoader.cs index ed20e13b..c2296985 100644 --- a/C7/Map/TileSetLoader.cs +++ b/C7/Map/TileSetLoader.cs @@ -13,6 +13,7 @@ public enum Layer { Resource, TerrainYield, Building, + Grid, Invalid, }; @@ -44,6 +45,7 @@ public enum Atlas { TerrainYield, TerrainBuilding, GoodyHut, + Grid, Invalid, } @@ -68,7 +70,7 @@ public AtlasLoader(string p, int w, int h, Vector2I rs, int y = 0) { height = h; regionSize = rs; textureOrigin = new Vector2I(0, y); - source = new TileSetAtlasSource{ + source = new TileSetAtlasSource { Texture = Util.LoadTextureFromPCX(path), TextureRegionSize = regionSize, }; @@ -140,7 +142,7 @@ protected override void load() { } class MarshAtlasLoader : AtlasLoader { - public MarshAtlasLoader(string p, Vector2I rs) : base(p, -1, -1, rs, 12) {} + public MarshAtlasLoader(string p, Vector2I rs) : base(p, -1, -1, rs, 12) { } protected override void load() { // TODO: incomplete @@ -217,6 +219,13 @@ public static TileSet LoadCiv3TileSet() { tileset.AddSource(source, atlas.Index()); } + TileSetAtlasSource gridSource = new TileSetAtlasSource{ + Texture = Util.LoadTextureFromC7JPG("Art/grid.png"), + TextureRegionSize = tileSize, + }; + gridSource.CreateTile(Vector2I.Zero); + tileset.AddSource(gridSource, Atlas.Grid.Index()); + return tileset; } } From eec545710f2ba6a022293343d7a1b81df4122911 Mon Sep 17 00:00:00 2001 From: Paul Cento Date: Sun, 18 Jun 2023 23:12:41 -0400 Subject: [PATCH 35/60] rendering terrain add Camera2D test drawing units in word coordinates wip simplify unit sprite test demo - AnimationManager could be simpler but trying to keep diff small enable pixel snapping - removes world seams render roads in tilemap add logic for drawing rails on tilemap - missing separate layer working rails layer simple abstraction for loading atlas sources add resources and clear cells when empty track net lines changed working rivers separate notion of layer and atlas in MapView add mountains get rid of incorrect y offsets (tiles are already centered) offset is necessary for oversized tiles... add volcanos single terrain overlay add full left skirt to terrain layer detect edges of map in world space fix bad bounds for forest tiles and use full resource tileset wip tile set loader so specific assets can be overwritten in the future add marsh to tilemap terrain overlay layer camera can center on tiles buildings on tilemap remove ported code load goodyhut pcx pixel perfect transforms dedupe code enable Y sort for each tilemap layer kinda working animations wip wip working sprite animations render best defender on top of unit stack significantly faster animation loop when only doing visible tiles draw cursor remove ported code --- C7/AnimationManager.cs | 29 ++-- C7/AnimationTracker.cs | 15 +-- C7/Art/Title_Screen.jpg.import | 2 +- C7/C7Game.tscn | 170 ++++++++++++++++++++++++ C7/Game.cs | 16 ++- C7/Map/MapView.cs | 118 ++++++++++++---- C7/Map/{UnitLayer.cs => UnitSprites.cs} | 150 ++++++--------------- C7/PlayerCamera.cs | 14 +- C7/icon.png.import | 2 +- C7/tests/TestUnit.cs | 10 +- C7Engine/MapUnitExtensions.cs | 2 +- 11 files changed, 348 insertions(+), 180 deletions(-) rename C7/Map/{UnitLayer.cs => UnitSprites.cs} (58%) diff --git a/C7/AnimationManager.cs b/C7/AnimationManager.cs index d60376be..ec953894 100644 --- a/C7/AnimationManager.cs +++ b/C7/AnimationManager.cs @@ -90,19 +90,19 @@ public string getUnitFlicFilepath(UnitPrototype unit, MapUnit.AnimatedAction act // The flic loading code parses the animations into a 2D array, where each row is an animation // corresponding to a tile direction. flicRowToAnimationDirection maps row number -> direction. private static TileDirection flicRowToAnimationDirection(int row) { - switch (row) { - case 0: return TileDirection.SOUTHWEST; - case 1: return TileDirection.SOUTH; - case 2: return TileDirection.SOUTHEAST; - case 3: return TileDirection.EAST; - case 4: return TileDirection.NORTHEAST; - case 5: return TileDirection.NORTH; - case 6: return TileDirection.NORTHWEST; - case 7: return TileDirection.WEST; - } // TODO: I wanted to add a TileDirection.INVALID enum value when implementing this, // but adding an INVALID value broke stuff: https://github.com/C7-Game/Prototype/issues/397 - return TileDirection.NORTH; + return row switch { + 0 => TileDirection.SOUTHWEST, + 1 => TileDirection.SOUTH, + 2 => TileDirection.SOUTHEAST, + 3 => TileDirection.EAST, + 4 => TileDirection.NORTHEAST, + 5 => TileDirection.NORTH, + 6 => TileDirection.NORTHWEST, + 7 => TileDirection.WEST, + _ => TileDirection.NORTH, + }; } public static void loadFlicAnimation(string path, string name, ref SpriteFrames frames, ref SpriteFrames tint) { @@ -123,10 +123,9 @@ public static void loadFlicAnimation(string path, string name, ref SpriteFrames } } - public static void loadCursorAnimation(string path, ref SpriteFrames frames) { + public static void loadCursorAnimation(string path, string name, ref SpriteFrames frames) { Flic flic = Util.LoadFlic(path); int row = 0; - string name = "cursor"; frames.AddAnimation(name); for (int col = 0; col < flic.Images.GetLength(1); col++) { @@ -247,4 +246,8 @@ public double getDuration() { double frameCount = flicSheet.indices.GetWidth() / flicSheet.spriteWidth; return frameCount / 20.0; // Civ 3 anims often run at 20 FPS TODO: Do they all? How could we tell? Is it exactly 20 FPS? } + + public override string ToString() { + return $"{unit.name}: {action}"; + } } diff --git a/C7/AnimationTracker.cs b/C7/AnimationTracker.cs index 881015d9..1bc266de 100644 --- a/C7/AnimationTracker.cs +++ b/C7/AnimationTracker.cs @@ -23,10 +23,7 @@ public struct ActiveAnimation { public Dictionary activeAnims = new Dictionary(); - public long getCurrentTimeMS() - { - return DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond; - } + public long getCurrentTimeMS() => DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond; private string getTileID(Tile tile) { @@ -110,19 +107,19 @@ public bool hasCurrentAction(MapUnit unit) public void update() { - long currentTimeMS = (! endAllImmediately) ? getCurrentTimeMS() : long.MaxValue; + long currentTimeMS = !endAllImmediately ? getCurrentTimeMS() : long.MaxValue; var keysToRemove = new List(); foreach (var guidAAPair in activeAnims.Where(guidAAPair => guidAAPair.Value.endTimeMS <= currentTimeMS)) { var (id, aa) = (guidAAPair.Key, guidAAPair.Value); - if (aa.completionEvent != null) { + if (aa.completionEvent is not null) { aa.completionEvent.Set(); aa.completionEvent = null; // So event is only triggered once } - if (aa.ending == AnimationEnding.Stop) + if (aa.ending == AnimationEnding.Stop) { keysToRemove.Add(id); + } } - foreach (var key in keysToRemove) - activeAnims.Remove(key); + keysToRemove.ForEach(key => activeAnims.Remove(key)); } public MapUnit.Appearance getUnitAppearance(MapUnit unit) diff --git a/C7/Art/Title_Screen.jpg.import b/C7/Art/Title_Screen.jpg.import index 5f577051..869beee3 100644 --- a/C7/Art/Title_Screen.jpg.import +++ b/C7/Art/Title_Screen.jpg.import @@ -2,7 +2,7 @@ importer="texture" type="CompressedTexture2D" -uid="uid://ds3dwrouk7g55" +uid="uid://bkxkefpbld468" path="res://.godot/imported/Title_Screen.jpg-067f940f7a89fae79632e2159c786062.ctex" metadata={ "vram_texture": false diff --git a/C7/C7Game.tscn b/C7/C7Game.tscn index 16b0e10e..cfd9cd31 100644 --- a/C7/C7Game.tscn +++ b/C7/C7Game.tscn @@ -67,6 +67,161 @@ script = ExtResource("11_pkhac") [node name="CanvasLayer" type="CanvasLayer" parent="."] +<<<<<<< HEAD +======= +[node name="GameStatus" type="MarginContainer" parent="CanvasLayer"] +anchors_preset = 3 +anchor_left = 1.0 +anchor_top = 1.0 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = -619.0 +offset_top = -357.0 +offset_right = -579.0 +offset_bottom = -317.0 +grow_horizontal = 0 +grow_vertical = 0 +script = ExtResource("7") + +[node name="SlideOutBar" type="Control" parent="CanvasLayer"] +layout_mode = 3 +anchors_preset = 11 +anchor_left = 1.0 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = 1024.0 +offset_top = 80.0 +offset_right = 1024.0 +offset_bottom = 728.0 +grow_horizontal = 0 +grow_vertical = 2 + +[node name="SlideToggle" type="Button" parent="CanvasLayer/SlideOutBar"] +layout_mode = 0 +offset_left = -124.27 +offset_right = -91.27 +offset_bottom = 20.0 +toggle_mode = true +text = "<->" + +[node name="VBoxContainer" type="VBoxContainer" parent="CanvasLayer/SlideOutBar"] +layout_mode = 1 +anchors_preset = -1 +anchor_right = 1.0 +anchor_bottom = 0.958 +offset_right = 1.0 +grow_horizontal = 0 +grow_vertical = 2 + +[node name="DownButton" type="Button" parent="CanvasLayer/SlideOutBar/VBoxContainer"] +layout_mode = 2 +text = "Down" + +[node name="RightButton" type="Button" parent="CanvasLayer/SlideOutBar/VBoxContainer"] +layout_mode = 2 +text = "Right" + +[node name="LeftButton" type="Button" parent="CanvasLayer/SlideOutBar/VBoxContainer"] +layout_mode = 2 +text = "Left" + +[node name="UpButton" type="Button" parent="CanvasLayer/SlideOutBar/VBoxContainer"] +layout_mode = 2 +text = "Up" + +[node name="Label" type="Label" parent="CanvasLayer/SlideOutBar/VBoxContainer"] +layout_mode = 2 +text = "Zoom" + +[node name="Zoom" type="VSlider" parent="CanvasLayer/SlideOutBar/VBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 1 +size_flags_vertical = 3 +min_value = 0.1 +max_value = 1.0 +step = 0.1 +value = 1.0 + +[node name="QuitButton" type="Button" parent="CanvasLayer/SlideOutBar/VBoxContainer"] +layout_mode = 2 +text = "Quit" + +[node name="AnimationPlayer" type="AnimationPlayer" parent="CanvasLayer/SlideOutBar"] +libraries = { +"": SubResource("AnimationLibrary_bowxq") +} + +[node name="ToolBar" type="Control" parent="CanvasLayer"] +layout_mode = 3 +anchors_preset = 10 +anchor_right = 1.0 +offset_left = -579.0 +offset_top = -317.0 +offset_right = -579.0 +offset_bottom = -317.0 +grow_horizontal = 2 +mouse_filter = 1 + +[node name="MarginContainer" type="MarginContainer" parent="CanvasLayer/ToolBar"] +layout_mode = 1 +anchors_preset = 10 +anchor_right = 1.0 +offset_bottom = 80.0 +grow_horizontal = 2 + +[node name="HBoxContainer" type="HBoxContainer" parent="CanvasLayer/ToolBar/MarginContainer"] +layout_mode = 2 + +[node name="MenuButton" type="TextureButton" parent="CanvasLayer/ToolBar/MarginContainer/HBoxContainer"] +layout_mode = 2 +script = ExtResource("4") + +[node name="CivilopediaButton" type="TextureButton" parent="CanvasLayer/ToolBar/MarginContainer/HBoxContainer"] +layout_mode = 2 +script = ExtResource("5") + +[node name="AdvisorButton" type="TextureButton" parent="CanvasLayer/ToolBar/MarginContainer/HBoxContainer"] +layout_mode = 2 +script = ExtResource("6") + +[node name="UiBarEndTurnButton" type="Button" parent="CanvasLayer/ToolBar/MarginContainer/HBoxContainer"] +layout_mode = 2 +text = "End Turn" + +[node name="UnitButtons" type="VBoxContainer" parent="CanvasLayer"] +anchors_preset = 12 +anchor_top = 1.0 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = -579.0 +offset_top = -429.0 +offset_right = -579.0 +offset_bottom = -317.0 +grow_horizontal = 2 +grow_vertical = 0 +size_flags_vertical = 0 +script = ExtResource("12") + +[node name="AdvancedUnitControls" type="HBoxContainer" parent="CanvasLayer/UnitButtons"] +layout_mode = 2 +size_flags_vertical = 0 +alignment = 1 + +[node name="RenameButton" type="TextureButton" parent="CanvasLayer/UnitButtons/AdvancedUnitControls"] +layout_mode = 2 +script = ExtResource("8") + +[node name="SpecializedUnitControls" type="HBoxContainer" parent="CanvasLayer/UnitButtons"] +layout_mode = 2 +size_flags_vertical = 0 +alignment = 1 + +[node name="PrimaryUnitControls" type="HBoxContainer" parent="CanvasLayer/UnitButtons"] +layout_mode = 2 +size_flags_vertical = 0 +alignment = 1 + +>>>>>>> 83be7c1 (rendering terrain) [node name="Advisor" type="CenterContainer" parent="CanvasLayer"] anchors_preset = 15 anchor_right = 1.0 @@ -255,8 +410,23 @@ libraries = { [connection signal="NoMoreAutoselectableUnits" from="." to="CanvasLayer/Control/GameStatus" method="OnNoMoreAutoselectableUnits"] [connection signal="NoMoreAutoselectableUnits" from="." to="CanvasLayer/Control/UnitButtons" method="OnNoMoreAutoselectableUnits"] [connection signal="ShowSpecificAdvisor" from="." to="CanvasLayer/Advisor" method="OnShowSpecificAdvisor"] +<<<<<<< HEAD [connection signal="TurnEnded" from="." to="CanvasLayer/Control/GameStatus" method="OnTurnEnded"] [connection signal="TurnStarted" from="." to="CanvasLayer/Control/GameStatus" method="OnTurnStarted"] +======= +[connection signal="TurnEnded" from="." to="CanvasLayer/GameStatus" method="OnTurnEnded"] +[connection signal="TurnStarted" from="." to="CanvasLayer/GameStatus" method="OnTurnStarted"] +[connection signal="BlinkyEndTurnButtonPressed" from="CanvasLayer/GameStatus" to="." method="OnPlayerEndTurn"] +[connection signal="toggled" from="CanvasLayer/SlideOutBar/SlideToggle" to="." method="_on_SlideToggle_toggled"] +[connection signal="pressed" from="CanvasLayer/SlideOutBar/VBoxContainer/DownButton" to="." method="_on_DownButton_pressed"] +[connection signal="pressed" from="CanvasLayer/SlideOutBar/VBoxContainer/RightButton" to="." method="_on_RightButton_pressed"] +[connection signal="pressed" from="CanvasLayer/SlideOutBar/VBoxContainer/LeftButton" to="." method="_on_LeftButton_pressed"] +[connection signal="pressed" from="CanvasLayer/SlideOutBar/VBoxContainer/UpButton" to="." method="_on_UpButton_pressed"] +[connection signal="value_changed" from="CanvasLayer/SlideOutBar/VBoxContainer/Zoom" to="." method="onSliderZoomChanged"] +[connection signal="pressed" from="CanvasLayer/SlideOutBar/VBoxContainer/QuitButton" to="." method="_on_QuitButton_pressed"] +[connection signal="pressed" from="CanvasLayer/ToolBar/MarginContainer/HBoxContainer/AdvisorButton" to="CanvasLayer/Advisor" method="ShowLatestAdvisor"] +[connection signal="pressed" from="CanvasLayer/ToolBar/MarginContainer/HBoxContainer/UiBarEndTurnButton" to="." method="_onEndTurnButtonPressed"] +>>>>>>> 83be7c1 (rendering terrain) [connection signal="BuildCity" from="CanvasLayer/PopupOverlay" to="." method="OnBuildCity"] [connection signal="HidePopup" from="CanvasLayer/PopupOverlay" to="CanvasLayer/PopupOverlay" method="OnHidePopup"] [connection signal="UnitDisbanded" from="CanvasLayer/PopupOverlay" to="." method="OnUnitDisbanded"] diff --git a/C7/Game.cs b/C7/Game.cs index 34c757e4..6981b2bc 100644 --- a/C7/Game.cs +++ b/C7/Game.cs @@ -44,7 +44,7 @@ enum GameState { Stopwatch loadTimer = new Stopwatch(); GlobalSingleton Global; - private PlayerCamera camera; + public PlayerCamera camera; bool errorOnLoad = false; public override void _EnterTree() { @@ -158,17 +158,17 @@ public void processEngineMessages(GameData gameData) { } } - // Instead of Game calling animTracker.update periodically (this used to happen in _Process), this method gets called as necessary to bring - // the animations up to date. Right now it's called from UnitLayer right before it draws the units on the map. This method also processes all - // waiting messages b/c some of them might pertain to animations. TODO: Consider processing only the animation messages here. - // Must only be called while holding the game data mutex + // updateAnimations updates animation states in the tracker and then their corresponding + // sprites in the MapView. It must be called when holding the game data mutex. + // TODO: before switching to tilemap, this was only called by the old UnitLayer _Draw + // method. It only really needs to be called as frequently as animations update... public void updateAnimations(GameData gameData) { - processEngineMessages(gameData); animTracker.update(); + mapView.updateAnimations(); } public override void _Process(double delta) { - this.processActions(); + processActions(); // TODO: Is it necessary to keep the game data mutex locked for this entire method? using (var gameDataAccess = new UIGameDataAccess()) { @@ -195,6 +195,8 @@ public override void _Process(double delta) { EmitSignal("ShowSpecificAdvisor", "F1"); } } + + updateAnimations(gameDataAccess.gameData); } } diff --git a/C7/Map/MapView.cs b/C7/Map/MapView.cs index 3841263f..fd3f0fb4 100644 --- a/C7/Map/MapView.cs +++ b/C7/Map/MapView.cs @@ -3,6 +3,8 @@ using System; using Serilog; using System.Linq; +using System.Collections.Generic; +using C7Engine; namespace C7.Map { @@ -34,17 +36,96 @@ public void toggleGrid() { private GameData data; private GameMap gameMap; - public override void _Draw() { - GD.Print("draw..."); - game.animTracker.update(); - foreach ((string id, AnimationTracker.ActiveAnimation anim) in game.animTracker.activeAnims) { - GD.Print($"{id}: {anim.ToString()}"); + private Dictionary unitSprites = new Dictionary(); + private CursorSprite cursor; + + private UnitSprite spriteFor(MapUnit unit) { + UnitSprite sprite = unitSprites.GetValueOrDefault(unit, null); + if (sprite is null) { + sprite = new UnitSprite(game.civ3AnimData); + unitSprites.Add(unit, sprite); + AddChild(sprite); } - base._Draw(); + return sprite; + } + + private Vector2 getSpriteLocalPosition(Tile tile, MapUnit.Appearance appearance) { + Vector2 position = tilemap.MapToLocal(stackedCoords(tile)); + Vector2 offset = tileSize * new Vector2(appearance.offsetX, appearance.offsetY) / 2; + return position + offset; } - public override void _Process(double delta) { - base._Process(delta); + private void animateUnit(Tile tile, MapUnit unit) { + // TODO: simplify AnimationManager and drawing animations it is unnecessarily complex + // - also investigate if the custom offset tracking and SetFrame can be replaced by + // engine functionality + MapUnit.Appearance appearance = game.animTracker.getUnitAppearance(unit); + string name = AnimationManager.AnimationKey(unit.unitType, appearance.action, appearance.direction); + C7Animation animation = game.civ3AnimData.forUnit(unit.unitType, appearance.action); + animation.loadSpriteAnimation(); + UnitSprite sprite = spriteFor(unit); + int frame = sprite.GetNextFrameByProgress(name, appearance.progress); + float yOffset = sprite.FrameSize(name).Y / 4f; // TODO: verify actual value + Vector2 position = getSpriteLocalPosition(tile, appearance); + sprite.Position = position - new Vector2(0, yOffset); + Color civColor = new Color(unit.owner.color); + sprite.SetColor(civColor); + sprite.SetAnimation(name); + sprite.SetFrame(frame); + sprite.Show(); + + if (unit == game.CurrentlySelectedUnit) { + cursor.Position = position; + cursor.Show(); + } + } + + private MapUnit selectUnitToDisplay(List units) { + if (units.Count == 0) { + return MapUnit.NONE; + } + MapUnit bestDefender = units[0], selected = null, interesting = null; + MapUnit currentlySelected = game.CurrentlySelectedUnit; + foreach (MapUnit unit in units) { + if (unit == currentlySelected) { + selected = unit; + } + if (unit.HasPriorityAsDefender(bestDefender, currentlySelected)) { + bestDefender = unit; + } + if (game.animTracker.getUnitAppearance(unit).DeservesPlayerAttention()) { + interesting = unit; + } + } + // Prefer showing the selected unit, secondly show one doing a relevant animation, otherwise show the top defender + return selected is not null ? selected : (interesting is not null ? interesting : bestDefender); + } + + public List getVisibleTiles() { + List tiles = new List(); + Rect2 bounds = game.camera.getVisibleWorld(); + Vector2I topLeft = tilemap.LocalToMap(ToLocal(bounds.Position)); + Vector2I bottomRight = tilemap.LocalToMap(ToLocal(bounds.End)); + for (int x = topLeft.X - 1; x < bottomRight.X + 1; x++) { + for (int y = topLeft.Y - 1; y < bottomRight.Y + 1; y++) { + (int usX, int usY) = unstackedCoords(new Vector2I(x, y)); + tiles.Add(data.map.tileAt(usX, usY)); + } + } + return tiles; + } + + public void updateAnimations() { + foreach (UnitSprite s in unitSprites.Values) { + s.Hide(); + } + cursor.Hide(); + foreach (Tile tile in getVisibleTiles()) { + MapUnit unit = selectUnitToDisplay(tile.unitsOnTile); + if (unit != MapUnit.NONE) { + animateUnit(tile, unit); + } + } } private void initializeTileMap() { @@ -127,7 +208,10 @@ private Vector2I stackedCoords(Tile tile) { public MapView(Game game, GameData data) { this.data = data; this.game = game; + this.data = data; this.gameMap = data.map; + cursor = new CursorSprite(); + AddChild(cursor); width = gameMap.numTilesWide / 2; height = gameMap.numTilesTall; initializeTileMap(); @@ -151,24 +235,6 @@ public MapView(Game game, GameData data) { foreach (Tile tile in gameMap.tiles) { updateTile(tile); } - - // temp but place units in current position - foreach (Tile tile in gameMap.tiles) { - if (tile.unitsOnTile.Count > 0) { - MapUnit unit = tile.unitsOnTile[0]; - UnitSprite sprite = new UnitSprite(game.civ3AnimData); - MapUnit.Appearance appearance = game.animTracker.getUnitAppearance(unit); - - var coords = stackedCoords(tile); - sprite.Position = tilemap.MapToLocal(coords); - - game.civ3AnimData.forUnit(unit.unitType, appearance.action).loadSpriteAnimation(); - string animName = AnimationManager.AnimationKey(unit.unitType, appearance.action, appearance.direction); - sprite.SetAnimation(animName); - sprite.SetFrame(0); - AddChild(sprite); - } - } } public Tile tileAt(GameMap gameMap, Vector2 globalMousePosition) { diff --git a/C7/Map/UnitLayer.cs b/C7/Map/UnitSprites.cs similarity index 58% rename from C7/Map/UnitLayer.cs rename to C7/Map/UnitSprites.cs index 16fa3aca..29445465 100644 --- a/C7/Map/UnitLayer.cs +++ b/C7/Map/UnitSprites.cs @@ -8,10 +8,13 @@ // UnitSprite represents an animated unit. It's specific to a unit, action, and direction. // UnitSprite comprises two sprites: a base sprite and a civ color-tinted sprite. The // shading is done in the UnitTint.gdshader shader. +// TODO: once https://github.com/godotengine/godot/issues/62943 is solved, UnitSprite should +// use a single instance of a material and UnitSprite use a per instance uniform public partial class UnitSprite : Node2D { private readonly int unitAnimZIndex = 100; private readonly string unitShaderPath = "res://UnitTint.gdshader"; + private readonly string unitColorShaderParameter = "tintColor"; private Shader unitShader; public AnimatedSprite2D sprite; @@ -19,6 +22,9 @@ public partial class UnitSprite : Node2D { public ShaderMaterial material; public int GetNextFrameByProgress(string animation, float progress) { + if (!sprite.SpriteFrames.HasAnimation(animation)) { + throw new ArgumentException($"no such animation: {animation}"); + } int frameCount = sprite.SpriteFrames.GetFrameCount(animation); int nextFrame = (int)((float)frameCount * progress); return Mathf.Clamp(nextFrame, 0, frameCount - 1); @@ -39,17 +45,20 @@ public void Play(string name) { spriteTint.Play(name); } + public void SetColor(Color color) { + material.SetShaderParameter(unitColorShaderParameter, new Vector3(color.R, color.G, color.B)); + } + public Vector2 FrameSize(string animation) { return sprite.SpriteFrames.GetFrameTexture(animation, 0).GetSize(); } public UnitSprite(AnimationManager manager) { + ZIndex = unitAnimZIndex; sprite = new AnimatedSprite2D{ - ZIndex = unitAnimZIndex, SpriteFrames = manager.spriteFrames, }; spriteTint = new AnimatedSprite2D{ - ZIndex = unitAnimZIndex, SpriteFrames= manager.tintFrames, }; @@ -63,6 +72,36 @@ public UnitSprite(AnimationManager manager) { } } +public partial class CursorSprite : Node2D { + private readonly string animationPath = "Art/Animations/Cursor/Cursor.flc"; + private readonly string animationName = "cursor"; + private readonly double period = 2.5; + private readonly int cursorAnimZIndex = 50; + private AnimatedSprite2D sprite; + private int frameCount; + + public CursorSprite() { + ZIndex = cursorAnimZIndex; + SpriteFrames frames = new SpriteFrames(); + AnimationManager.loadCursorAnimation(animationPath, animationName, ref frames); + sprite = new AnimatedSprite2D{ + SpriteFrames = frames, + Animation = animationName, + }; + frameCount = sprite.SpriteFrames.GetFrameCount(animationName); + AddChild(sprite); + } + + public override void _Process(double delta) { + double repCount = (double)Time.GetTicksMsec() / 1000.0 / period; + float progress = (float)(repCount - Math.Floor(repCount)); + int nextFrame = (int)((float)frameCount * progress); + nextFrame = Mathf.Clamp(nextFrame, 0, frameCount - 1); + sprite.Frame = nextFrame; + base._Process(delta); + } +} + public partial class UnitLayer { private ImageTexture unitIcons; private int unitIconsWidth; @@ -77,24 +116,6 @@ public UnitLayer() { unitMovementIndicators = PCXToGodot.getImageTextureFromPCX(moveIndPCX); } - // Creates a plane mesh facing the positive Z-axis with the given shader attached. The quad is 1.0 units long on both sides, - // intended to be scaled to the appropriate size when used. - public static (ShaderMaterial, MeshInstance2D) createShadedQuad(Shader shader) { - PlaneMesh mesh = new PlaneMesh(); - mesh.SubdivideDepth = 1; - mesh.Orientation = PlaneMesh.OrientationEnum.Z; - mesh.Size = new Vector2(1, 1); - - ShaderMaterial shaderMat = new ShaderMaterial(); - shaderMat.Shader = shader; - - MeshInstance2D meshInst = new MeshInstance2D(); - meshInst.Material = shaderMat; - meshInst.Mesh = mesh; - - return (shaderMat, meshInst); - } - public Color getHPColor(float fractionRemaining) { if (fractionRemaining >= 0.67f) { return Color.Color8(0, 255, 0); @@ -105,38 +126,6 @@ public Color getHPColor(float fractionRemaining) { } } - private List animInsts = new List(); - private int nextBlankAnimInst = 0; - - // Returns the next unused AnimationInstance or creates & returns a new one if none are available. - public UnitSprite getBlankAnimationInstance() { - if (nextBlankAnimInst >= animInsts.Count) { - // animInsts.Add(new AnimationInstance(looseView)); - } - UnitSprite inst = animInsts[nextBlankAnimInst]; - nextBlankAnimInst++; - return inst; - } - - public void drawUnitAnimFrame(MapUnit unit, MapUnit.Appearance appearance, Vector2 tileCenter) { - UnitSprite inst = getBlankAnimationInstance(); - // mapView.game.civ3AnimData.forUnit(unit.unitType, appearance.action).loadSpriteAnimation(); - string animName = AnimationManager.AnimationKey(unit.unitType, appearance.action, appearance.direction); - - // Need to move the sprites upward a bit so that their feet are at the center of the tile. I don't know if spriteHeight/4 is the right - var animOffset = OldMapView.cellSize * new Vector2(appearance.offsetX, appearance.offsetY); - Vector2 position = tileCenter + animOffset - new Vector2(0, inst.FrameSize(animName).Y / 4); - inst.Position = position; - - var civColor = new Color(unit.owner.color); - int nextFrame = inst.GetNextFrameByProgress(animName, appearance.progress); - inst.material.SetShaderParameter("tintColor", new Vector3(civColor.R, civColor.G, civColor.B)); - - inst.SetAnimation(animName); - inst.SetFrame(nextFrame); - inst.Show(); - } - public void drawEffectAnimFrame(C7Animation anim, float progress, Vector2 tileCenter) { // var flicSheet = anim.getFlicSheet(); // var inst = getBlankAnimationInstance(looseView); @@ -147,63 +136,6 @@ public void drawEffectAnimFrame(C7Animation anim, float progress, Vector2 tileCe // inst.meshInst.ZIndex = effectAnimZIndex; } - private AnimatedSprite2D cursorSprite = null; - - public void drawCursor(Vector2 position) { - // Initialize cursor if necessary - if (cursorSprite == null) { - cursorSprite = new AnimatedSprite2D(); - SpriteFrames frames = new SpriteFrames(); - cursorSprite.SpriteFrames = frames; - AnimationManager.loadCursorAnimation("Art/Animations/Cursor/Cursor.flc", ref frames); - cursorSprite.Animation = "cursor"; // hardcoded in loadCursorAnimation - } - - const double period = 2.5; // TODO: Just eyeballing this for now. Read the actual period from the INI or something. - double repCount = (double)Time.GetTicksMsec() / 1000.0 / period; - float progress = (float)(repCount - Math.Floor(repCount)); - cursorSprite.Position = position; - int frameCount = cursorSprite.SpriteFrames.GetFrameCount("cursor"); - int nextFrame = (int)((float)frameCount * progress); - nextFrame = nextFrame >= frameCount ? frameCount - 1 : (nextFrame < 0 ? 0 : nextFrame); - cursorSprite.Frame = nextFrame; - cursorSprite.Show(); - } - - public void onBeginDraw(GameData gameData) { - // Reset animation instances - for (int n = 0; n < nextBlankAnimInst; n++) { - animInsts[n].Hide(); - } - nextBlankAnimInst = 0; - - // Hide cursor if it's been initialized - cursorSprite?.Hide(); - - // looseView.mapView.game.updateAnimations(gameData); - } - - // Returns which unit should be drawn from among a list of units. The list is assumed to be non-empty. - public MapUnit selectUnitToDisplay(List units) { - // From the list, pick out which units are (1) the strongest defender vs the currently selected unit, (2) the currently selected unit - // itself if it's in the list, and (3) any unit that is playing an animation that the player would want to see. - MapUnit bestDefender = units[0], - selected = null, - doingInterestingAnimation = null; - MapUnit currentlySelectedUnit = null; // looseView.mapView.game.CurrentlySelectedUnit; - foreach (var u in units) { - if (u == currentlySelectedUnit) - selected = u; - if (u.HasPriorityAsDefender(bestDefender, currentlySelectedUnit)) - bestDefender = u; - // if (looseView.mapView.game.animTracker.getUnitAppearance(u).DeservesPlayerAttention()) - // doingInterestingAnimation = u; - } - - // Prefer showing the selected unit, secondly show one doing a relevant animation, otherwise show the top defender - return selected != null ? selected : (doingInterestingAnimation != null ? doingInterestingAnimation : bestDefender); - } - public void drawObject(GameData gameData, Tile tile, Vector2 tileCenter) { // First draw animated effects. These will always appear over top of units regardless of draw order due to z-index. // C7Animation tileEffect = looseView.mapView.game.animTracker.getTileEffect(tile); diff --git a/C7/PlayerCamera.cs b/C7/PlayerCamera.cs index 7c96afe4..bb16c14d 100644 --- a/C7/PlayerCamera.cs +++ b/C7/PlayerCamera.cs @@ -4,7 +4,7 @@ public partial class PlayerCamera : Camera2D { - private readonly float maxZoom = 2.0f; + private readonly float maxZoom = 3.0f; private readonly float minZoom = 0.2f; public float zoomFactor {get; private set; } = 1.0f; @@ -25,11 +25,13 @@ public void setZoom(float factor) { } public override void _UnhandledInput(InputEvent @event) { - if (@event is InputEventMouseMotion mm && mm.ButtonMask == MouseButtonMask.Left) { - Position -= mm.Relative / Zoom; - } - if (@event is InputEventMagnifyGesture mg) { - scaleZoom(mg.Factor); + switch (@event) { + case InputEventMouseMotion mouseDrag when mouseDrag.ButtonMask == MouseButtonMask.Left: + Position -= mouseDrag.Relative / Zoom; + break; + case InputEventMagnifyGesture magnifyGesture: + scaleZoom(magnifyGesture.Factor); + break; } } diff --git a/C7/icon.png.import b/C7/icon.png.import index 0265838e..06f7374a 100644 --- a/C7/icon.png.import +++ b/C7/icon.png.import @@ -2,7 +2,7 @@ importer="texture" type="CompressedTexture2D" -uid="uid://bk71ywmbfvg35" +uid="uid://cg00nurhpvuk5" path="res://.godot/imported/icon.png-487276ed1e3a0c39cad0279d744ee560.ctex" metadata={ "vram_texture": false diff --git a/C7/tests/TestUnit.cs b/C7/tests/TestUnit.cs index cba9a7e1..97520381 100644 --- a/C7/tests/TestUnit.cs +++ b/C7/tests/TestUnit.cs @@ -11,20 +11,16 @@ public override void _Ready() manager.forUnit(prototype, MapUnit.AnimatedAction.RUN).loadSpriteAnimation(); string name = AnimationManager.AnimationKey(prototype, MapUnit.AnimatedAction.RUN, TileDirection.EAST); AddChild(sprite); + sprite.Play(name); float scale = 6; this.Scale = new Vector2(scale, scale); - sprite.material.SetShaderParameter("tintColor", new Vector3(1f,1f,1f)); + sprite.SetColor(new Color(1, 1, 1)); sprite.Position = new Vector2(30, 30); - sprite.Play(name); - AnimatedSprite2D cursor = new AnimatedSprite2D(); - SpriteFrames cursorFrames = new SpriteFrames(); - cursor.SpriteFrames = cursorFrames; - AnimationManager.loadCursorAnimation("Art/Animations/Cursor/Cursor.flc", ref cursorFrames); + CursorSprite cursor = new CursorSprite(); cursor.Position = new Vector2(120, 30); - cursor.Play("cursor"); AddChild(cursor); } } diff --git a/C7Engine/MapUnitExtensions.cs b/C7Engine/MapUnitExtensions.cs index 82dbbc4f..47f2118e 100644 --- a/C7Engine/MapUnitExtensions.cs +++ b/C7Engine/MapUnitExtensions.cs @@ -366,9 +366,9 @@ public static bool move(this MapUnit unit, TileDirection dir, bool wait = false) throw new System.Exception("Failed to remove unit from tile it's supposed to be on"); unit.OnEnterTile(newLoc); newLoc.unitsOnTile.Add(unit); + unit.animate(MapUnit.AnimatedAction.RUN, wait); unit.location = newLoc; unit.movementPoints.onUnitMove(movementCost); - unit.animate(MapUnit.AnimatedAction.RUN, wait); } return true; } From 74a9208eb1781f4adc1187bf0d10f76b9705adfb Mon Sep 17 00:00:00 2001 From: Paul Cento Date: Mon, 31 Jul 2023 18:48:03 +0200 Subject: [PATCH 36/60] fix rebase --- C7/C7Game.tscn | 160 ------------------------------------------------- 1 file changed, 160 deletions(-) diff --git a/C7/C7Game.tscn b/C7/C7Game.tscn index cfd9cd31..241cfd44 100644 --- a/C7/C7Game.tscn +++ b/C7/C7Game.tscn @@ -67,161 +67,6 @@ script = ExtResource("11_pkhac") [node name="CanvasLayer" type="CanvasLayer" parent="."] -<<<<<<< HEAD -======= -[node name="GameStatus" type="MarginContainer" parent="CanvasLayer"] -anchors_preset = 3 -anchor_left = 1.0 -anchor_top = 1.0 -anchor_right = 1.0 -anchor_bottom = 1.0 -offset_left = -619.0 -offset_top = -357.0 -offset_right = -579.0 -offset_bottom = -317.0 -grow_horizontal = 0 -grow_vertical = 0 -script = ExtResource("7") - -[node name="SlideOutBar" type="Control" parent="CanvasLayer"] -layout_mode = 3 -anchors_preset = 11 -anchor_left = 1.0 -anchor_right = 1.0 -anchor_bottom = 1.0 -offset_left = 1024.0 -offset_top = 80.0 -offset_right = 1024.0 -offset_bottom = 728.0 -grow_horizontal = 0 -grow_vertical = 2 - -[node name="SlideToggle" type="Button" parent="CanvasLayer/SlideOutBar"] -layout_mode = 0 -offset_left = -124.27 -offset_right = -91.27 -offset_bottom = 20.0 -toggle_mode = true -text = "<->" - -[node name="VBoxContainer" type="VBoxContainer" parent="CanvasLayer/SlideOutBar"] -layout_mode = 1 -anchors_preset = -1 -anchor_right = 1.0 -anchor_bottom = 0.958 -offset_right = 1.0 -grow_horizontal = 0 -grow_vertical = 2 - -[node name="DownButton" type="Button" parent="CanvasLayer/SlideOutBar/VBoxContainer"] -layout_mode = 2 -text = "Down" - -[node name="RightButton" type="Button" parent="CanvasLayer/SlideOutBar/VBoxContainer"] -layout_mode = 2 -text = "Right" - -[node name="LeftButton" type="Button" parent="CanvasLayer/SlideOutBar/VBoxContainer"] -layout_mode = 2 -text = "Left" - -[node name="UpButton" type="Button" parent="CanvasLayer/SlideOutBar/VBoxContainer"] -layout_mode = 2 -text = "Up" - -[node name="Label" type="Label" parent="CanvasLayer/SlideOutBar/VBoxContainer"] -layout_mode = 2 -text = "Zoom" - -[node name="Zoom" type="VSlider" parent="CanvasLayer/SlideOutBar/VBoxContainer"] -layout_mode = 2 -size_flags_horizontal = 1 -size_flags_vertical = 3 -min_value = 0.1 -max_value = 1.0 -step = 0.1 -value = 1.0 - -[node name="QuitButton" type="Button" parent="CanvasLayer/SlideOutBar/VBoxContainer"] -layout_mode = 2 -text = "Quit" - -[node name="AnimationPlayer" type="AnimationPlayer" parent="CanvasLayer/SlideOutBar"] -libraries = { -"": SubResource("AnimationLibrary_bowxq") -} - -[node name="ToolBar" type="Control" parent="CanvasLayer"] -layout_mode = 3 -anchors_preset = 10 -anchor_right = 1.0 -offset_left = -579.0 -offset_top = -317.0 -offset_right = -579.0 -offset_bottom = -317.0 -grow_horizontal = 2 -mouse_filter = 1 - -[node name="MarginContainer" type="MarginContainer" parent="CanvasLayer/ToolBar"] -layout_mode = 1 -anchors_preset = 10 -anchor_right = 1.0 -offset_bottom = 80.0 -grow_horizontal = 2 - -[node name="HBoxContainer" type="HBoxContainer" parent="CanvasLayer/ToolBar/MarginContainer"] -layout_mode = 2 - -[node name="MenuButton" type="TextureButton" parent="CanvasLayer/ToolBar/MarginContainer/HBoxContainer"] -layout_mode = 2 -script = ExtResource("4") - -[node name="CivilopediaButton" type="TextureButton" parent="CanvasLayer/ToolBar/MarginContainer/HBoxContainer"] -layout_mode = 2 -script = ExtResource("5") - -[node name="AdvisorButton" type="TextureButton" parent="CanvasLayer/ToolBar/MarginContainer/HBoxContainer"] -layout_mode = 2 -script = ExtResource("6") - -[node name="UiBarEndTurnButton" type="Button" parent="CanvasLayer/ToolBar/MarginContainer/HBoxContainer"] -layout_mode = 2 -text = "End Turn" - -[node name="UnitButtons" type="VBoxContainer" parent="CanvasLayer"] -anchors_preset = 12 -anchor_top = 1.0 -anchor_right = 1.0 -anchor_bottom = 1.0 -offset_left = -579.0 -offset_top = -429.0 -offset_right = -579.0 -offset_bottom = -317.0 -grow_horizontal = 2 -grow_vertical = 0 -size_flags_vertical = 0 -script = ExtResource("12") - -[node name="AdvancedUnitControls" type="HBoxContainer" parent="CanvasLayer/UnitButtons"] -layout_mode = 2 -size_flags_vertical = 0 -alignment = 1 - -[node name="RenameButton" type="TextureButton" parent="CanvasLayer/UnitButtons/AdvancedUnitControls"] -layout_mode = 2 -script = ExtResource("8") - -[node name="SpecializedUnitControls" type="HBoxContainer" parent="CanvasLayer/UnitButtons"] -layout_mode = 2 -size_flags_vertical = 0 -alignment = 1 - -[node name="PrimaryUnitControls" type="HBoxContainer" parent="CanvasLayer/UnitButtons"] -layout_mode = 2 -size_flags_vertical = 0 -alignment = 1 - ->>>>>>> 83be7c1 (rendering terrain) [node name="Advisor" type="CenterContainer" parent="CanvasLayer"] anchors_preset = 15 anchor_right = 1.0 @@ -410,12 +255,8 @@ libraries = { [connection signal="NoMoreAutoselectableUnits" from="." to="CanvasLayer/Control/GameStatus" method="OnNoMoreAutoselectableUnits"] [connection signal="NoMoreAutoselectableUnits" from="." to="CanvasLayer/Control/UnitButtons" method="OnNoMoreAutoselectableUnits"] [connection signal="ShowSpecificAdvisor" from="." to="CanvasLayer/Advisor" method="OnShowSpecificAdvisor"] -<<<<<<< HEAD [connection signal="TurnEnded" from="." to="CanvasLayer/Control/GameStatus" method="OnTurnEnded"] [connection signal="TurnStarted" from="." to="CanvasLayer/Control/GameStatus" method="OnTurnStarted"] -======= -[connection signal="TurnEnded" from="." to="CanvasLayer/GameStatus" method="OnTurnEnded"] -[connection signal="TurnStarted" from="." to="CanvasLayer/GameStatus" method="OnTurnStarted"] [connection signal="BlinkyEndTurnButtonPressed" from="CanvasLayer/GameStatus" to="." method="OnPlayerEndTurn"] [connection signal="toggled" from="CanvasLayer/SlideOutBar/SlideToggle" to="." method="_on_SlideToggle_toggled"] [connection signal="pressed" from="CanvasLayer/SlideOutBar/VBoxContainer/DownButton" to="." method="_on_DownButton_pressed"] @@ -426,7 +267,6 @@ libraries = { [connection signal="pressed" from="CanvasLayer/SlideOutBar/VBoxContainer/QuitButton" to="." method="_on_QuitButton_pressed"] [connection signal="pressed" from="CanvasLayer/ToolBar/MarginContainer/HBoxContainer/AdvisorButton" to="CanvasLayer/Advisor" method="ShowLatestAdvisor"] [connection signal="pressed" from="CanvasLayer/ToolBar/MarginContainer/HBoxContainer/UiBarEndTurnButton" to="." method="_onEndTurnButtonPressed"] ->>>>>>> 83be7c1 (rendering terrain) [connection signal="BuildCity" from="CanvasLayer/PopupOverlay" to="." method="OnBuildCity"] [connection signal="HidePopup" from="CanvasLayer/PopupOverlay" to="CanvasLayer/PopupOverlay" method="OnHidePopup"] [connection signal="UnitDisbanded" from="CanvasLayer/PopupOverlay" to="." method="OnUnitDisbanded"] From aa7877fdde60c498b6ecf52daa6cb97b5302d962 Mon Sep 17 00:00:00 2001 From: Paul Cento Date: Sun, 20 Aug 2023 12:45:45 -0400 Subject: [PATCH 37/60] simplify unit selection with null coalescing operator --- C7/Map/MapView.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/C7/Map/MapView.cs b/C7/Map/MapView.cs index fd3f0fb4..7d5541f9 100644 --- a/C7/Map/MapView.cs +++ b/C7/Map/MapView.cs @@ -98,7 +98,7 @@ private MapUnit selectUnitToDisplay(List units) { } } // Prefer showing the selected unit, secondly show one doing a relevant animation, otherwise show the top defender - return selected is not null ? selected : (interesting is not null ? interesting : bestDefender); + return selected ?? interesting ?? bestDefender; } public List getVisibleTiles() { From 1ffb61aea8d672f5ce57464929db22529e3ae416 Mon Sep 17 00:00:00 2001 From: Paul Cento Date: Sun, 18 Jun 2023 23:12:41 -0400 Subject: [PATCH 38/60] rendering terrain add Camera2D test drawing units in word coordinates wip simplify unit sprite test demo - AnimationManager could be simpler but trying to keep diff small enable pixel snapping - removes world seams render roads in tilemap add logic for drawing rails on tilemap - missing separate layer working rails layer simple abstraction for loading atlas sources add resources and clear cells when empty track net lines changed working rivers separate notion of layer and atlas in MapView add mountains get rid of incorrect y offsets (tiles are already centered) offset is necessary for oversized tiles... add volcanos single terrain overlay add full left skirt to terrain layer detect edges of map in world space fix bad bounds for forest tiles and use full resource tileset wip tile set loader so specific assets can be overwritten in the future add marsh to tilemap terrain overlay layer camera can center on tiles buildings on tilemap remove ported code load goodyhut pcx pixel perfect transforms dedupe code enable Y sort for each tilemap layer kinda working animations wip wip working sprite animations render best defender on top of unit stack significantly faster animation loop when only doing visible tiles draw cursor remove ported code --- C7/C7Game.tscn | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/C7/C7Game.tscn b/C7/C7Game.tscn index 241cfd44..16b0e10e 100644 --- a/C7/C7Game.tscn +++ b/C7/C7Game.tscn @@ -257,16 +257,6 @@ libraries = { [connection signal="ShowSpecificAdvisor" from="." to="CanvasLayer/Advisor" method="OnShowSpecificAdvisor"] [connection signal="TurnEnded" from="." to="CanvasLayer/Control/GameStatus" method="OnTurnEnded"] [connection signal="TurnStarted" from="." to="CanvasLayer/Control/GameStatus" method="OnTurnStarted"] -[connection signal="BlinkyEndTurnButtonPressed" from="CanvasLayer/GameStatus" to="." method="OnPlayerEndTurn"] -[connection signal="toggled" from="CanvasLayer/SlideOutBar/SlideToggle" to="." method="_on_SlideToggle_toggled"] -[connection signal="pressed" from="CanvasLayer/SlideOutBar/VBoxContainer/DownButton" to="." method="_on_DownButton_pressed"] -[connection signal="pressed" from="CanvasLayer/SlideOutBar/VBoxContainer/RightButton" to="." method="_on_RightButton_pressed"] -[connection signal="pressed" from="CanvasLayer/SlideOutBar/VBoxContainer/LeftButton" to="." method="_on_LeftButton_pressed"] -[connection signal="pressed" from="CanvasLayer/SlideOutBar/VBoxContainer/UpButton" to="." method="_on_UpButton_pressed"] -[connection signal="value_changed" from="CanvasLayer/SlideOutBar/VBoxContainer/Zoom" to="." method="onSliderZoomChanged"] -[connection signal="pressed" from="CanvasLayer/SlideOutBar/VBoxContainer/QuitButton" to="." method="_on_QuitButton_pressed"] -[connection signal="pressed" from="CanvasLayer/ToolBar/MarginContainer/HBoxContainer/AdvisorButton" to="CanvasLayer/Advisor" method="ShowLatestAdvisor"] -[connection signal="pressed" from="CanvasLayer/ToolBar/MarginContainer/HBoxContainer/UiBarEndTurnButton" to="." method="_onEndTurnButtonPressed"] [connection signal="BuildCity" from="CanvasLayer/PopupOverlay" to="." method="OnBuildCity"] [connection signal="HidePopup" from="CanvasLayer/PopupOverlay" to="CanvasLayer/PopupOverlay" method="OnHidePopup"] [connection signal="UnitDisbanded" from="CanvasLayer/PopupOverlay" to="." method="OnUnitDisbanded"] From 331463c9b5b66f35ff0c7669096d983fb307cc3f Mon Sep 17 00:00:00 2001 From: Paul Cento Date: Sun, 18 Jun 2023 23:12:41 -0400 Subject: [PATCH 39/60] rendering terrain --- C7/Map/Corners.cs | 150 +++++++++ C7/MapView.cs | 781 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 931 insertions(+) create mode 100644 C7/Map/Corners.cs create mode 100644 C7/MapView.cs diff --git a/C7/Map/Corners.cs b/C7/Map/Corners.cs new file mode 100644 index 00000000..0c49981c --- /dev/null +++ b/C7/Map/Corners.cs @@ -0,0 +1,150 @@ +using Godot; +using System.Collections.Generic; +using System.Linq; +using System; + +namespace C7.Map { + + class TerrainPcx { + private static Random prng = new Random(); + private string name; + // abc refers to the layout of the terrain tiles in the pcx based on + // the positions of each terrain at the corner of 4 tiles. + // - https://forums.civfanatics.com/threads/terrain-editing.622999/ + // - https://forums.civfanatics.com/threads/editing-terrain-pcx-files.102840/ + private string[] abc; + public int atlas; + public TerrainPcx(string name, string[] abc, int atlas) { + this.name = name; + this.abc = abc; + this.atlas = atlas; + } + public bool validFor(string[] corner) { + return corner.All(tile => abc.Contains(tile)); + } + private int abcIndex(string terrain) { + List indices = new List(); + for (int i = 0; i < abc.Count(); i++) { + if (abc[i] == terrain) { + indices.Add(i); + } + } + return indices[prng.Next(indices.Count)]; + } + + // getTextureCoords looks up the correct texture index in the pcx + // for the given position of each corner terrain type + public Vector2I getTextureCoords(string[] corner) { + int top = abcIndex(corner[0]); + int right = abcIndex(corner[1]); + int bottom = abcIndex(corner[2]); + int left = abcIndex(corner[3]); + int index = top + (left * 3) + (right * 9) + (bottom * 27); + return new Vector2I(index % 9, index / 9); + } + } + + partial class Corners : Node2D { + private List terrainPcxFiles = new List { + "Art/Terrain/xtgc.pcx", + "Art/Terrain/xpgc.pcx", + "Art/Terrain/xdgc.pcx", + "Art/Terrain/xdpc.pcx", + "Art/Terrain/xdgp.pcx", + "Art/Terrain/xggc.pcx", + "Art/Terrain/wCSO.pcx", + "Art/Terrain/wSSS.pcx", + "Art/Terrain/wOOO.pcx", + }; + private List terrainPcxList; + private string[,]terrain; + private TileMap tilemap; + private TileSet tileset; + private List textures; + private Vector2I tileSize = new Vector2I(128, 64); + private int width; + private int height; + + private void initializeTileMap() { + this.tilemap = new TileMap(); + this.tileset = new TileSet(); + + this.tileset.TileShape = TileSet.TileShapeEnum.Isometric; + this.tileset.TileLayout = TileSet.TileLayoutEnum.Stacked; + this.tileset.TileOffsetAxis = TileSet.TileOffsetAxisEnum.Horizontal; + this.tileset.TileSize = this.tileSize; + + foreach (ImageTexture texture in this.textures) { + TileSetAtlasSource source = new TileSetAtlasSource(); + source.Texture = texture; + source.TextureRegionSize = this.tileSize; + for (int x = 0; x < 9; x++) { + for (int y = 0; y < 9; y++) { + source.CreateTile(new Vector2I(x, y)); + } + } + this.tileset.AddSource(source); + } + this.tilemap.TileSet = tileset; + + this.terrainPcxList = new List() { + new TerrainPcx("tgc", new string[]{"tundra", "grassland", "coast"}, 0), + new TerrainPcx("pgc", new string[]{"plains", "grassland", "coast"}, 1), + new TerrainPcx("dgc", new string[]{"desert", "grassland", "coast"}, 2), + new TerrainPcx("dpc", new string[]{"desert", "plains", "coast"}, 3), + new TerrainPcx("dgp", new string[]{"desert", "grassland", "plains"}, 4), + // new TerrainPcx("ggc", new string[]{"grassland", "grassland", "coast"}, 5), + new TerrainPcx("cso", new string[]{"coast", "sea", "ocean"}, 6), + new TerrainPcx("sss", new string[]{"sea", "sea", "sea"}, 7), + new TerrainPcx("ooo", new string[]{"ocean", "ocean", "ocean"}, 8), + }; + AddChild(this.tilemap); + } + + private TerrainPcx getPcxForCorner(string[] corner) { + return terrainPcxList.Find(tpcx => tpcx.validFor(corner)); + } + + void fill(Vector2I cell, int atlas, Vector2I texCoords) { + this.tilemap.SetCell(0, cell, atlas, texCoords); + } + + public Corners(C7GameData.GameMap gameMap) { + this.textures = terrainPcxFiles.ConvertAll(path => Util.LoadTextureFromPCX(path)); + this.initializeTileMap(); + this.width = gameMap.numTilesWide / 2; + this.height = gameMap.numTilesTall; + this.terrain = new string[width, height]; + + foreach (C7GameData.Tile t in gameMap.tiles) { + int x = t.xCoordinate; + int y = t.yCoordinate; + // stacked coordinates + x = y % 2 == 0 ? x / 2 : (x - 1) / 2; + this.terrain[x, y] = t.baseTerrainTypeKey; + } + + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + Vector2I cell = new Vector2I(x, y); + string left = terrain[x, y]; + string right = terrain[(x + 1) % width, y]; + bool even = y % 2 == 0; + string top = "coast"; + if (y > 0) { + top = even ? terrain[x, y-1] : terrain[(x + 1) % width, y - 1]; + } + string bottom = "coast"; + if (y < height - 1) { + bottom = even ? terrain[x, y+1] : terrain[(x + 1) % width, y + 1]; + } + string[] corner = new string[4]{top, right, bottom, left}; + TerrainPcx pcx = getPcxForCorner(corner); + Vector2I texCoords = pcx.getTextureCoords(corner); + fill(cell, pcx.atlas, texCoords); + } + } + + } + } +} diff --git a/C7/MapView.cs b/C7/MapView.cs new file mode 100644 index 00000000..8447934b --- /dev/null +++ b/C7/MapView.cs @@ -0,0 +1,781 @@ +using System.Collections.Generic; +using System; +using System.Linq; +using C7.Map; +using Godot; +using ConvertCiv3Media; +using C7GameData; +using C7Engine; +using Serilog; +using Serilog.Events; + +// Loose layers are for drawing things on the map on a per-tile basis. (Historical aside: There used to be another kind of layer called a TileLayer +// that was intended to draw regularly tiled objects like terrain sprites but using LooseLayers for everything was found to be a prefereable +// approach.) LooseLayer is effectively the standard map layer. The MapView contains a list of loose layers, inside a LooseView object. Right now to +// add a new layer you must modify the MapView constructor to add it to the list, but (TODO) eventually that will be made moddable. +public abstract class LooseLayer { + // drawObject draws the things this layer is supposed to draw that are associated with the given tile. Its parameters are: + // looseView: The Node2D to actually draw to, e.g., use looseView.DrawCircle(...) to draw a circle. This object also contains a reference to + // the MapView in case you need it. + // gameData: A reference to the game data so each layer doesn't have to redundantly request access. + // tile: The game tile whose contents are to be drawn. This function gets called for each tile in view of the camera and none out of + // view. The same tile may be drawn multiple times at different locations due to edge wrapping. + // tileCenter: The location to draw to. You should draw around this location without adjusting for the camera location or zoom since the + // MapView already transforms the looseView node to account for those things. + public abstract void drawObject(LooseView looseView, GameData gameData, Tile tile, Vector2 tileCenter); + + public virtual void onBeginDraw(LooseView looseView, GameData gameData) {} + public virtual void onEndDraw(LooseView looseView, GameData gameData) {} + + // The layer will be skipped during map drawing if visible is false + public bool visible = true; +} + +public partial class TerrainLayer : LooseLayer { + + public static readonly Vector2 terrainSpriteSize = new Vector2(128, 64); + + // A triple sheet is a sprite sheet containing sprites for three different terrain types including transitions between. + private List tripleSheets; + + // TileToDraw stores the arguments passed to drawObject so the draws can be sorted by texture before being submitted. This significantly + // reduces the number of draw calls Godot must generate (1483 to 312 when fully zoomed out on our test map) and modestly improves framerate + // (by about 14% on my system). + private class TileToDraw : IComparable + { + public Tile tile; + public Vector2 tileCenter; + + public TileToDraw(Tile tile, Vector2 tileCenter) + { + this.tile = tile; + this.tileCenter = tileCenter; + } + + public int CompareTo(TileToDraw other) + { + // "other" might be null, in which case we should return a positive value. CompareTo(null) will do this. + try { + return this.tile.ExtraInfo.BaseTerrainFileID.CompareTo(other?.tile.ExtraInfo.BaseTerrainFileID); + } catch (Exception) { + //It also could be Tile.NONE. In which case, also return a positive value. + return 1; + } + } + } + + private List tilesToDraw = new List(); + + public TerrainLayer() + { + tripleSheets = loadTerrainTripleSheets(); + } + + public List loadTerrainTripleSheets() + { + List fileNames = new List { + "Art/Terrain/xtgc.pcx", + "Art/Terrain/xpgc.pcx", + "Art/Terrain/xdgc.pcx", + "Art/Terrain/xdpc.pcx", + "Art/Terrain/xdgp.pcx", + "Art/Terrain/xggc.pcx", + "Art/Terrain/wCSO.pcx", + "Art/Terrain/wSSS.pcx", + "Art/Terrain/wOOO.pcx", + }; + return fileNames.ConvertAll(name => Util.LoadTextureFromPCX(name)); + } + + public override void drawObject(LooseView looseView, GameData gameData, Tile tile, Vector2 tileCenter) + { + tilesToDraw.Add(new TileToDraw(tile, tileCenter)); + tilesToDraw.Add(new TileToDraw(tile.neighbors[TileDirection.SOUTH], tileCenter + new Vector2(0, 64))); + tilesToDraw.Add(new TileToDraw(tile.neighbors[TileDirection.SOUTHWEST], tileCenter + new Vector2(-64, 32))); + tilesToDraw.Add(new TileToDraw(tile.neighbors[TileDirection.SOUTHEAST], tileCenter + new Vector2(64, 32))); + } + + public override void onEndDraw(LooseView looseView, GameData gameData) { + tilesToDraw.Sort(); + foreach (TileToDraw tTD in tilesToDraw) { + if (tTD.tile != Tile.NONE) { + int xSheet = tTD.tile.ExtraInfo.BaseTerrainImageID % 9, ySheet = tTD.tile.ExtraInfo.BaseTerrainImageID / 9; + Rect2 texRect = new Rect2(new Vector2(xSheet, ySheet) * terrainSpriteSize, terrainSpriteSize); + Vector2 terrainOffset = new Vector2(0, -1 * MapView.cellSize.Y); + // Multiply size by 100.1% so avoid "seams" in the map. See issue #106. + // Jim's option of a whole-map texture is less hacky, but this is quicker and seems to be working well. + Rect2 screenRect = new Rect2(tTD.tileCenter - (float)0.5 * terrainSpriteSize + terrainOffset, terrainSpriteSize * 1.001f); + looseView.DrawTextureRectRegion(tripleSheets[tTD.tile.ExtraInfo.BaseTerrainFileID], screenRect, texRect); + } + } + tilesToDraw.Clear(); + } +} + +public partial class HillsLayer : LooseLayer { + public static readonly Vector2 mountainSize = new Vector2(128, 88); + public static readonly Vector2 volcanoSize = new Vector2(128, 88); //same as mountain + public static readonly Vector2 hillsSize = new Vector2(128, 72); + private ImageTexture mountainTexture; + private ImageTexture snowMountainTexture; + private ImageTexture forestMountainTexture; + private ImageTexture jungleMountainTexture; + private ImageTexture hillsTexture; + private ImageTexture forestHillsTexture; + private ImageTexture jungleHillsTexture; + private ImageTexture volcanosTexture; + private ImageTexture forestVolcanoTexture; + private ImageTexture jungleVolcanoTexture; + + public HillsLayer() { + mountainTexture = Util.LoadTextureFromPCX("Art/Terrain/Mountains.pcx"); + snowMountainTexture = Util.LoadTextureFromPCX("Art/Terrain/Mountains-snow.pcx"); + forestMountainTexture = Util.LoadTextureFromPCX("Art/Terrain/mountain forests.pcx"); + jungleMountainTexture = Util.LoadTextureFromPCX("Art/Terrain/mountain jungles.pcx"); + hillsTexture = Util.LoadTextureFromPCX("Art/Terrain/xhills.pcx"); + forestHillsTexture = Util.LoadTextureFromPCX("Art/Terrain/hill forests.pcx"); + jungleHillsTexture = Util.LoadTextureFromPCX("Art/Terrain/hill jungle.pcx"); + volcanosTexture = Util.LoadTextureFromPCX("Art/Terrain/Volcanos.pcx"); + forestVolcanoTexture = Util.LoadTextureFromPCX("Art/Terrain/Volcanos forests.pcx"); + jungleVolcanoTexture = Util.LoadTextureFromPCX("Art/Terrain/Volcanos jungles.pcx"); + } + + public override void drawObject(LooseView looseView, GameData gameData, Tile tile, Vector2 tileCenter) + { + if (tile.overlayTerrainType.isHilly()) { + int pcxIndex = getMountainIndex(tile); + int row = pcxIndex/4; + int column = pcxIndex % 4; + if (tile.overlayTerrainType.Key == "mountains") { + Rect2 mountainRectangle = new Rect2(column * mountainSize.X, row * mountainSize.Y, mountainSize); + Rect2 screenTarget = new Rect2(tileCenter - (float)0.5 * mountainSize + new Vector2(0, -12), mountainSize); + ImageTexture mountainGraphics; + if (tile.isSnowCapped) { + mountainGraphics = snowMountainTexture; + } + else { + TerrainType dominantVegetation = getDominantVegetationNearHillyTile(tile); + if (dominantVegetation.Key == "forest") { + mountainGraphics = forestMountainTexture; + } + else if (dominantVegetation.Key == "jungle") { + mountainGraphics = jungleMountainTexture; + } + else { + mountainGraphics = mountainTexture; + } + } + looseView.DrawTextureRectRegion(mountainGraphics, screenTarget, mountainRectangle); + } + else if (tile.overlayTerrainType.Key == "hills") { + Rect2 hillsRectangle = new Rect2(column * hillsSize.X, row * hillsSize.Y, hillsSize); + Rect2 screenTarget = new Rect2(tileCenter - (float)0.5 * hillsSize + new Vector2(0, -4), hillsSize); + ImageTexture hillGraphics; + TerrainType dominantVegetation = getDominantVegetationNearHillyTile(tile); + if (dominantVegetation.Key == "forest") { + hillGraphics = forestHillsTexture; + } + else if (dominantVegetation.Key == "jungle") { + hillGraphics = jungleHillsTexture; + } + else { + hillGraphics = hillsTexture; + } + looseView.DrawTextureRectRegion(hillGraphics, screenTarget, hillsRectangle); + } + else if (tile.overlayTerrainType.Key == "volcano") { + Rect2 volcanoRectangle = new Rect2(column * volcanoSize.X, row * volcanoSize.Y, volcanoSize); + Rect2 screenTarget = new Rect2(tileCenter - (float)0.5 * volcanoSize + new Vector2(0, -12), volcanoSize); + ImageTexture volcanoGraphics; + TerrainType dominantVegetation = getDominantVegetationNearHillyTile(tile); + if (dominantVegetation.Key == "forest") { + volcanoGraphics = forestVolcanoTexture; + } + else if (dominantVegetation.Key == "jungle") { + volcanoGraphics = jungleVolcanoTexture; + } + else { + volcanoGraphics = volcanosTexture; + } + looseView.DrawTextureRectRegion(volcanoGraphics, screenTarget, volcanoRectangle); + } + } + } + + private TerrainType getDominantVegetationNearHillyTile(Tile center) + { + TerrainType northeastType = center.neighbors[TileDirection.NORTHEAST].overlayTerrainType; + TerrainType northwestType = center.neighbors[TileDirection.NORTHWEST].overlayTerrainType; + TerrainType southeastType = center.neighbors[TileDirection.SOUTHEAST].overlayTerrainType; + TerrainType southwestType = center.neighbors[TileDirection.SOUTHWEST].overlayTerrainType; + + TerrainType[] neighborTerrains = { northeastType, northwestType, southeastType, southwestType }; + + int hills = 0; + int forests = 0; + int jungles = 0; + //These references are so we can return the appropriate type, and because we don't have a good way + //to grab them directly at this point in time. + TerrainType forest = null; + TerrainType jungle = null; + foreach (TerrainType type in neighborTerrains) { + if (type.isHilly()) { + hills++; + } + else if (type.Key == "forest") { + forests++; + forest = type; + } + else if (type.Key == "jungle") { + jungles++; + jungle = type; + } + } + + if (hills + forests + jungles < 4) { //some surrounding tiles are neither forested nor hilly + return TerrainType.NONE; + } + if (forests == 0 && jungles == 0) { + return TerrainType.NONE; //all hills + } + if (forests > jungles) { + return forest; + } + if (jungles > forests) { + return jungle; + } + + //If we get here, it's a tie between forest and jungle. Deterministically choose one so it doesn't change on every render + if (center.xCoordinate % 2 == 0) { + return forest; + } + return jungle; + } + + private int getMountainIndex(Tile tile) { + int index = 0; + if (tile.neighbors[TileDirection.NORTHWEST].overlayTerrainType.isHilly()) { + index++; + } + if (tile.neighbors[TileDirection.NORTHEAST].overlayTerrainType.isHilly()) { + index+=2; + } + if (tile.neighbors[TileDirection.SOUTHWEST].overlayTerrainType.isHilly()) { + index+=4; + } + if (tile.neighbors[TileDirection.SOUTHEAST].overlayTerrainType.isHilly()) { + index+=8; + } + return index; + } +} + +public partial class ForestLayer : LooseLayer { + public static readonly Vector2 forestJungleSize = new Vector2(128, 88); + + private ImageTexture largeJungleTexture; + private ImageTexture smallJungleTexture; + private ImageTexture largeForestTexture; + private ImageTexture largePlainsForestTexture; + private ImageTexture largeTundraForestTexture; + private ImageTexture smallForestTexture; + private ImageTexture smallPlainsForestTexture; + private ImageTexture smallTundraForestTexture; + private ImageTexture pineForestTexture; + private ImageTexture pinePlainsTexture; + private ImageTexture pineTundraTexture; + + public ForestLayer() { + largeJungleTexture = Util.LoadTextureFromPCX("Art/Terrain/grassland forests.pcx", 0, 0, 512, 176); + smallJungleTexture = Util.LoadTextureFromPCX("Art/Terrain/grassland forests.pcx", 0, 176, 768, 176); + largeForestTexture = Util.LoadTextureFromPCX("Art/Terrain/grassland forests.pcx", 0, 352, 512, 176); + largePlainsForestTexture = Util.LoadTextureFromPCX("Art/Terrain/plains forests.pcx", 0, 352, 512, 176); + largeTundraForestTexture = Util.LoadTextureFromPCX("Art/Terrain/tundra forests.pcx", 0, 352, 512, 176); + smallForestTexture = Util.LoadTextureFromPCX("Art/Terrain/grassland forests.pcx", 0, 528, 640, 176); + smallPlainsForestTexture = Util.LoadTextureFromPCX("Art/Terrain/plains forests.pcx", 0, 528, 640, 176); + smallTundraForestTexture = Util.LoadTextureFromPCX("Art/Terrain/tundra forests.pcx", 0, 528, 640, 176); + pineForestTexture = Util.LoadTextureFromPCX("Art/Terrain/grassland forests.pcx", 0, 704, 768, 176); + pinePlainsTexture = Util.LoadTextureFromPCX("Art/Terrain/plains forests.pcx" , 0, 704, 768, 176); + pineTundraTexture = Util.LoadTextureFromPCX("Art/Terrain/tundra forests.pcx" , 0, 704, 768, 176); + } + + public override void drawObject(LooseView looseView, GameData gameData, Tile tile, Vector2 tileCenter) { + if (tile.overlayTerrainType.Key == "jungle") { + //Randomly, but predictably, choose a large jungle graphic + //More research is needed on when to use large vs small jungles. Probably, small is used when neighboring fewer jungles. + //For the first pass, we're just always using large jungles. + int randomJungleRow = tile.yCoordinate % 2; + int randomJungleColumn; + ImageTexture jungleTexture; + if (tile.getEdgeNeighbors().Any(t => t.IsWater())) { + randomJungleColumn = tile.xCoordinate % 6; + jungleTexture = smallJungleTexture; + } + else { + randomJungleColumn = tile.xCoordinate % 4; + jungleTexture = largeJungleTexture; + } + Rect2 jungleRectangle = new Rect2(randomJungleColumn * forestJungleSize.X, randomJungleRow * forestJungleSize.Y, forestJungleSize); + Rect2 screenTarget = new Rect2(tileCenter - (float)0.5 * forestJungleSize + new Vector2(0, -12), forestJungleSize); + looseView.DrawTextureRectRegion(jungleTexture, screenTarget, jungleRectangle); + } + if (tile.overlayTerrainType.Key == "forest") { + int forestRow = 0; + int forestColumn = 0; + ImageTexture forestTexture; + if (tile.isPineForest) { + forestRow = tile.yCoordinate % 2; + forestColumn = tile.xCoordinate % 6; + if (tile.baseTerrainType.Key == "grassland") { + forestTexture = pineForestTexture; + } + else if (tile.baseTerrainType.Key == "plains") { + forestTexture = pinePlainsTexture; + } + else { //Tundra + forestTexture = pineTundraTexture; + } + } + else { + forestRow = tile.yCoordinate % 2; + if (tile.getEdgeNeighbors().Any(t => t.IsWater())) { + forestColumn = tile.xCoordinate % 5; + if (tile.baseTerrainType.Key == "grassland") { + forestTexture = smallForestTexture; + } + else if (tile.baseTerrainType.Key == "plains") { + forestTexture = smallPlainsForestTexture; + } + else { //tundra + forestTexture = smallTundraForestTexture; + } + } + else { + forestColumn = tile.xCoordinate % 4; + if (tile.baseTerrainType.Key == "grassland") { + forestTexture = largeForestTexture; + } + else if (tile.baseTerrainType.Key == "plains") { + forestTexture = largePlainsForestTexture; + } + else { //tundra + forestTexture = largeTundraForestTexture; + } + } + } + Rect2 forestRectangle = new Rect2(forestColumn * forestJungleSize.X, forestRow * forestJungleSize.Y, forestJungleSize); + Rect2 screenTarget = new Rect2(tileCenter - (float)0.5 * forestJungleSize + new Vector2(0, -12), forestJungleSize); + looseView.DrawTextureRectRegion(forestTexture, screenTarget, forestRectangle); + } + } +} +public partial class MarshLayer : LooseLayer { + public static readonly Vector2 marshSize = new Vector2(128, 88); + //Because the marsh graphics are 88 pixels tall instead of the 64 of a tile, we also need an addition 12 pixel offset to the top + //88 - 64 = 24; 24/2 = 12. This keeps the marsh centered with half the extra 24 pixels above the tile and half below. + readonly Vector2 MARSH_OFFSET = (float)0.5 * marshSize + new Vector2(0, -12); + + private ImageTexture largeMarshTexture; + private ImageTexture smallMarshTexture; + + public MarshLayer() { + largeMarshTexture = Util.LoadTextureFromPCX("Art/Terrain/marsh.pcx", 0, 0, 512, 176); + smallMarshTexture = Util.LoadTextureFromPCX("Art/Terrain/marsh.pcx", 0, 176, 640, 176); + } + + public override void drawObject(LooseView looseView, GameData gameData, Tile tile, Vector2 tileCenter) { + if (tile.overlayTerrainType.Key == "marsh") { + int randomJungleRow = tile.yCoordinate % 2; + int randomMarshColumn; + ImageTexture marshTexture; + if (tile.getEdgeNeighbors().Any(t => t.IsWater())) { + randomMarshColumn = tile.xCoordinate % 5; + marshTexture = smallMarshTexture; + } + else { + randomMarshColumn = tile.xCoordinate % 4; + marshTexture = largeMarshTexture; + } + Rect2 jungleRectangle = new Rect2(randomMarshColumn * marshSize.X, randomJungleRow * marshSize.Y, marshSize); + Rect2 screenTarget = new Rect2(tileCenter - MARSH_OFFSET, marshSize); + looseView.DrawTextureRectRegion(marshTexture, screenTarget, jungleRectangle); + } + } +} + +public partial class RiverLayer : LooseLayer +{ + public static readonly Vector2 riverSize = new Vector2(128, 64); + public static readonly Vector2 riverCenterOffset = new Vector2(riverSize.X / 2, 0); + private ImageTexture riverTexture; + + public RiverLayer() { + riverTexture = Util.LoadTextureFromPCX("Art/Terrain/mtnRivers.pcx"); + } + + public override void drawObject(LooseView looseView, GameData gameData, Tile tile, Vector2 tileCenter) + { + //The "point" is the easternmost point of the tile for which we are drawing rivers. + //Which river graphics to used is calculated by evaluating the tiles that neighbor + //that point. + Tile northOfPoint = tile.neighbors[TileDirection.NORTHEAST]; + Tile eastOfPoint = tile.neighbors[TileDirection.EAST]; + Tile westOfPoint = tile; + Tile southOfPoint = tile.neighbors[TileDirection.SOUTHEAST]; + + int riverGraphicsIndex = 0; + + if (northOfPoint.riverSouthwest) { + riverGraphicsIndex++; + } + if (eastOfPoint.riverNorthwest) { + riverGraphicsIndex+=2; + } + if (westOfPoint.riverSoutheast) { + riverGraphicsIndex+=4; + } + if (southOfPoint.riverNortheast) { + riverGraphicsIndex+=8; + } + if (riverGraphicsIndex == 0) { + return; + } + int riverRow = riverGraphicsIndex / 4; + int riverColumn = riverGraphicsIndex % 4; + + Rect2 riverRectangle = new Rect2(riverColumn * riverSize.X, riverRow * riverSize.Y, riverSize); + Rect2 screenTarget = new Rect2(tileCenter - (float)0.5 * riverSize + riverCenterOffset, riverSize); + looseView.DrawTextureRectRegion(riverTexture, screenTarget, riverRectangle); + } +} + +public partial class GridLayer : LooseLayer { + public Color color = Color.Color8(50, 50, 50, 150); + public float lineWidth = (float)1.0; + + public GridLayer() {} + + public override void drawObject(LooseView looseView, GameData gameData, Tile tile, Vector2 tileCenter) + { + Vector2 cS = MapView.cellSize; + Vector2 left = tileCenter + new Vector2(-cS.X, 0 ); + Vector2 top = tileCenter + new Vector2( 0 , -cS.Y); + Vector2 right = tileCenter + new Vector2( cS.X, 0 ); + looseView.DrawLine(left, top , color, lineWidth); + looseView.DrawLine(top , right, color, lineWidth); + } +} + +public partial class BuildingLayer : LooseLayer { + private ImageTexture buildingsTex; + private Vector2 buildingSpriteSize; + + public BuildingLayer() + { + var buildingsPCX = new Pcx(Util.Civ3MediaPath("Art/Terrain/TerrainBuildings.PCX")); + buildingsTex = PCXToGodot.getImageTextureFromPCX(buildingsPCX); + //In Conquests, this graphic is 4x4, and the search path will now find the Conquests one first + buildingSpriteSize = new Vector2((float)buildingsTex.GetWidth() / 4, (float)buildingsTex.GetHeight() / 4); + } + + public override void drawObject(LooseView looseView, GameData gameData, Tile tile, Vector2 tileCenter) + { + if (tile.hasBarbarianCamp) { + var texRect = new Rect2(buildingSpriteSize * new Vector2 (2, 0), buildingSpriteSize); //(2, 0) is the offset in the TerrainBuildings.PCX file (top row, third in) + // TODO: Modify this calculation so it doesn't assume buildingSpriteSize is the same as the size of the terrain tiles + var screenRect = new Rect2(tileCenter - (float)0.5 * buildingSpriteSize, buildingSpriteSize); + looseView.DrawTextureRectRegion(buildingsTex, screenRect, texRect); + } + } +} + +public partial class LooseView : Node2D { + public MapView mapView; + public List layers = new List(); + + public LooseView(MapView mapView) + { + this.mapView = mapView; + } + + private struct VisibleTile + { + public Tile tile; + public Vector2 tileCenter; + } + + public override void _Draw() + { + base._Draw(); + + using (var gameDataAccess = new UIGameDataAccess()) { + GameData gD = gameDataAccess.gameData; + + // Iterating over visible tiles is unfortunately pretty expensive. Assemble a list of Tile references and centers first so we don't + // have to reiterate for each layer. Doing this improves framerate significantly. + MapView.VisibleRegion visRegion = mapView.getVisibleRegion(); + List visibleTiles = new List(); + for (int y = visRegion.upperLeftY; y < visRegion.lowerRightY; y++) { + if (gD.map.isRowAt(y)) { + for (int x = visRegion.getRowStartX(y); x < visRegion.lowerRightX; x += 2) { + Tile tile = gD.map.tileAt(x, y); + if (IsTileKnown(tile, gameDataAccess)) { + visibleTiles.Add(new VisibleTile { tile = tile, tileCenter = MapView.cellSize * new Vector2(x + 1, y + 1) }); + } + } + } + } + + foreach (LooseLayer layer in layers.FindAll(L => L.visible && !(L is FogOfWarLayer))) { + layer.onBeginDraw(this, gD); + foreach (VisibleTile vT in visibleTiles) { + layer.drawObject(this, gD, vT.tile, vT.tileCenter); + } + layer.onEndDraw(this, gD); + } + + if (!gD.observerMode) { + foreach (LooseLayer layer in layers.FindAll(layer => layer is FogOfWarLayer)) { + for (int y = visRegion.upperLeftY; y < visRegion.lowerRightY; y++) + if (gD.map.isRowAt(y)) + for (int x = visRegion.getRowStartX(y); x < visRegion.lowerRightX; x += 2) { + Tile tile = gD.map.tileAt(x, y); + if (tile != Tile.NONE) { + VisibleTile invisibleTile = new VisibleTile { tile = tile, tileCenter = MapView.cellSize * new Vector2(x + 1, y + 1) }; + layer.drawObject(this, gD, tile, invisibleTile.tileCenter); + } + } + } + } + } + } + private static bool IsTileKnown(Tile tile, UIGameDataAccess gameDataAccess) { + if (gameDataAccess.gameData.observerMode) { + return true; + } + return tile != Tile.NONE && gameDataAccess.gameData.GetHumanPlayers()[0].tileKnowledge.isTileKnown(tile); + } +} + +public partial class MapView : Node2D { + // cellSize is half the size of the tile sprites, or the amount of space each tile takes up when they are packed on the grid (note tiles are + // staggered and half overlap). + public static readonly Vector2 cellSize = new Vector2(64, 32); + public Vector2 scaledCellSize { + get { return cellSize * new Vector2(cameraZoom, cameraZoom); } + } + + public Game game; + + public int mapWidth { get; private set; } + public int mapHeight { get; private set; } + public bool wrapHorizontally { get; private set; } + public bool wrapVertically { get; private set; } + + private Vector2 internalCameraLocation = new Vector2(0, 0); + public Vector2 cameraLocation { + get { + return internalCameraLocation; + } + set { + setCameraLocation(value); + } + } + public float internalCameraZoom = 1; + public float cameraZoom { + get { return internalCameraZoom; } + set { setCameraZoomFromMiddle(value); } + } + + private LooseView looseView; + + // Specifies a rectangular block of tiles that are currently potentially on screen. Accessible through getVisibleRegion(). Tile coordinates + // are "virtual", i.e. "unwrapped", so there isn't necessarily a tile at each location. The region is intended to include the upper left + // coordinates but not the lower right ones. When iterating over all tiles in the region you must account for the fact that map rows are + // staggered, see LooseView._Draw for an example. + public struct VisibleRegion { + public int upperLeftX, upperLeftY; + public int lowerRightX, lowerRightY; + + public int getRowStartX(int y) + { + return upperLeftX + (y - upperLeftY)%2; + } + } + + public GridLayer gridLayer { get; private set; } + + public ImageTexture civColorWhitePalette = null; + private Corners corners; + public MapView(Game game, int mapWidth, int mapHeight, bool wrapHorizontally, bool wrapVertically) + { + this.game = game; + this.mapWidth = mapWidth; + this.mapHeight = mapHeight; + this.wrapHorizontally = wrapHorizontally; + this.wrapVertically = wrapVertically; + + looseView = new LooseView(this); + // looseView.layers.Add(new TerrainLayer()); + looseView.layers.Add(new RiverLayer()); + looseView.layers.Add(new ForestLayer()); + looseView.layers.Add(new MarshLayer()); + looseView.layers.Add(new HillsLayer()); + looseView.layers.Add(new TntLayer()); + looseView.layers.Add(new RoadLayer()); + looseView.layers.Add(new ResourceLayer()); + this.gridLayer = new GridLayer(); + looseView.layers.Add(this.gridLayer); + looseView.layers.Add(new BuildingLayer()); + looseView.layers.Add(new UnitLayer()); + looseView.layers.Add(new CityLayer()); + // looseView.layers.Add(new FogOfWarLayer()); + + (civColorWhitePalette, _) = Util.loadPalettizedPCX("Art/Units/Palettes/ntp00.pcx"); + + AddChild(looseView); + } + + public override void _Process(double delta) + { + // Redraw everything. This is necessary so that animations play. Maybe we could only update the unit layer but long term I think it's + // better to redraw everything every frame like a typical modern video game. + looseView.QueueRedraw(); + } + + // Returns the size in pixels of the area in which the map will be drawn. This is the viewport size or, if that's null, the window size. + public Vector2 getVisibleAreaSize() + { + return GetViewport() != null ? GetViewportRect().Size : DisplayServer.WindowGetSize(); + } + + public VisibleRegion getVisibleRegion() + { + (int x0, int y0) = tileCoordsOnScreenAt(new Vector2(0, 0)); + Vector2 mapViewSize = new Vector2(2, 4) + getVisibleAreaSize() / scaledCellSize; + return new VisibleRegion { upperLeftX = x0 - 2, upperLeftY = y0 - 2, + lowerRightX = x0 + (int)mapViewSize.X, lowerRightY = y0 + (int)mapViewSize.Y }; + } + + // "center" is the screen location around which the zoom is centered, e.g., if center is (0, 0) the tile in the top left corner will be the + // same after the zoom level is changed, and if center is screenSize/2, the tile in the center of the window won't change. + public void setCameraZoom(float newScale, Vector2 center) + { + Vector2 v2NewZoom = new Vector2(newScale, newScale); + Vector2 v2OldZoom = new Vector2(cameraZoom, cameraZoom); + if (v2NewZoom != v2OldZoom) { + internalCameraZoom = newScale; + looseView.Scale = v2NewZoom; + setCameraLocation ((v2NewZoom / v2OldZoom) * (cameraLocation + center) - center); + } + } + + // Zooms in or out centered on the middle of the screen + public void setCameraZoomFromMiddle(float newScale) + { + setCameraZoom(newScale, getVisibleAreaSize() / 2); + } + + public void moveCamera(Vector2 offset) + { + setCameraLocation(cameraLocation + offset); + } + + public void setCameraLocation(Vector2 location) + { + // Prevent the camera from moving beyond an unwrapped edge of the map. One complication here is that the viewport might actually be + // larger than the map (if we're zoomed far out) so in that case we must apply the constraint the other way around, i.e. constrain the + // map to the viewport rather than the viewport to the map. + Vector2 visAreaSize = getVisibleAreaSize(); + Vector2 mapPixelSize = new Vector2(cameraZoom, cameraZoom) * (new Vector2(cellSize.X * (mapWidth + 1), cellSize.Y * (mapHeight + 1))); + if (!wrapHorizontally) { + float leftLim, rightLim; + { + if (mapPixelSize.X >= visAreaSize.X) { + leftLim = 0; + rightLim = mapPixelSize.X - visAreaSize.X; + } else { + leftLim = mapPixelSize.X - visAreaSize.X; + rightLim = 0; + } + } + if (location.X < leftLim) + location.X = leftLim; + else if (location.X > rightLim) + location.X = rightLim; + } + if (!wrapVertically) { + // These margins allow the player to move the camera that far off those map edges so that the UI controls don't cover up the + // map. TODO: These values should be read from the sizes of the UI elements instead of hardcoded. + float topMargin = 70, bottomMargin = 140; + float topLim, bottomLim; + { + if (mapPixelSize.Y >= visAreaSize.Y) { + topLim = -topMargin; + bottomLim = mapPixelSize.Y - visAreaSize.Y + bottomMargin; + } else { + topLim = mapPixelSize.Y - visAreaSize.Y; + bottomLim = 0; + } + } + if (location.Y < topLim) + location.Y = topLim; + else if (location.Y > bottomLim) + location.Y = bottomLim; + } + + internalCameraLocation = location; + looseView.Position = -location; + } + + public Vector2 screenLocationOfTileCoords(int x, int y, bool center = true) + { + // Add one to x & y to get the tile center b/c in Civ 3 the tile at (x, y) is a diamond centered on (x+1, y+1). + Vector2 centeringOffset = center ? new Vector2(1, 1) : new Vector2(0, 0); + + var mapLoc = (new Vector2(x, y) + centeringOffset) * cellSize; + return mapLoc * cameraZoom - cameraLocation; + } + + // Returns the location of tile (x, y) on the screen, if "center" is true returns the location of the tile center and otherwise returns the + // upper left. Works even if (x, y) is off screen or out of bounds. + public Vector2 screenLocationOfTile(Tile tile, bool center = true) + { + return screenLocationOfTileCoords(tile.xCoordinate, tile.yCoordinate, center); + } + + // Returns the virtual tile coordinates on screen at the given location. "Virtual" meaning the coordinates are unwrapped and there isn't + // necessarily a tile there at all. + public (int, int) tileCoordsOnScreenAt(Vector2 screenLocation) + { + Vector2 mapLoc = (screenLocation + cameraLocation) / scaledCellSize; + Vector2 intMapLoc = mapLoc.Floor(); + Vector2 fracMapLoc = mapLoc - intMapLoc; + int x = (int)intMapLoc.X, y = (int)intMapLoc.Y; + bool evenColumn = x%2 == 0, evenRow = y%2 == 0; + if (evenColumn ^ evenRow) { + if (fracMapLoc.Y > fracMapLoc.X) + x -= 1; + else + y -= 1; + } else { + if (fracMapLoc.Y < 1 - fracMapLoc.X) { + x -= 1; + y -= 1; + } + } + return (x, y); + } + + public Tile tileOnScreenAt(GameMap map, Vector2 screenLocation) + { + (int x, int y) = tileCoordsOnScreenAt(screenLocation); + return map.tileAt(x, y); + } + + public void centerCameraOnTile(Tile t) + { + var tileCenter = new Vector2(t.xCoordinate + 1, t.yCoordinate + 1) * scaledCellSize; + setCameraLocation(tileCenter - (float)0.5 * getVisibleAreaSize()); + } +} From af12135feeda9346148a8f5b27452da47a928905 Mon Sep 17 00:00:00 2001 From: Paul Cento Date: Sun, 18 Jun 2023 23:58:06 -0400 Subject: [PATCH 40/60] add Camera2D test drawing units in word coordinates wip simplify unit sprite test demo - AnimationManager could be simpler but trying to keep diff small enable pixel snapping - removes world seams render roads in tilemap add logic for drawing rails on tilemap - missing separate layer working rails layer simple abstraction for loading atlas sources add resources and clear cells when empty track net lines changed working rivers separate notion of layer and atlas in MapView add mountains get rid of incorrect y offsets (tiles are already centered) offset is necessary for oversized tiles... add volcanos single terrain overlay add full left skirt to terrain layer detect edges of map in world space fix bad bounds for forest tiles and use full resource tileset wip tile set loader so specific assets can be overwritten in the future add marsh to tilemap terrain overlay layer camera can center on tiles buildings on tilemap remove ported code load goodyhut pcx pixel perfect transforms dedupe code enable Y sort for each tilemap layer kinda working animations wip wip working sprite animations render best defender on top of unit stack significantly faster animation loop when only doing visible tiles draw cursor remove ported code wip render cities city labels text renders but at wrong offsets correct city label offsets only redraw city label scene when necessary redraw city scene if production name changes re-implement insure location is in view using player camera remove GD.Print statements emit city built message from engine interaction --- C7/Game.cs | 28 +- C7/Map/CityLabelScene.cs | 111 ++-- C7/Map/CityLayer.cs | 37 -- C7/Map/CityScene.cs | 19 +- C7/Map/Corners.cs | 150 ----- C7/Map/MapView.cs | 9 + C7/MapView.cs | 781 ----------------------- C7/PlayerCamera.cs | 9 + C7Engine/EntryPoints/CityInteractions.cs | 2 +- C7Engine/EntryPoints/MessageToUI.cs | 13 +- 10 files changed, 106 insertions(+), 1053 deletions(-) delete mode 100644 C7/Map/CityLayer.cs delete mode 100644 C7/Map/Corners.cs delete mode 100644 C7/MapView.cs diff --git a/C7/Game.cs b/C7/Game.cs index 6981b2bc..0a10be29 100644 --- a/C7/Game.cs +++ b/C7/Game.cs @@ -94,9 +94,6 @@ public override void _Ready() { Toolbar = GetNode("CanvasLayer/Control/ToolBar/MarginContainer/HBoxContainer"); - //TODO: What was this supposed to do? It throws errors and occasinally causes crashes now, because _OnViewportSizeChanged doesn't exist - // GetTree().Root.Connect("size_changed",new Callable(this,"_OnViewportSizeChanged")); - // Hide slideout bar on startup _on_SlideToggle_toggled(false); @@ -154,6 +151,11 @@ public void processEngineMessages(GameData gameData) { case MsgStartTurn mST: OnPlayerStartTurn(); break; + case MsgCityBuilt mBC: + Tile cityTile = gameData.map.tiles[mBC.tileIndex]; + City city = cityTile.cityAtTile; + mapView.addCity(city, cityTile); + break; } } } @@ -220,11 +222,10 @@ public override void _Process(double delta) { // If "location" is not already near the center of the screen, moves the camera to bring it into view. public void ensureLocationIsInView(Tile location) { - // TODO: implement if (controller.tileKnowledge.isTileKnown(location) && location != Tile.NONE) { - // Vector2 relativeScreenLocation = oldMapView.screenLocationOfTile(location, true) / oldMapView.getVisibleAreaSize(); - // if (relativeScreenLocation.DistanceTo(new Vector2((float)0.5, (float)0.5)) > 0.30) - // oldMapView.centerCameraOnTile(location); + if (!camera.isTileInView(location, mapView)) { + camera.centerOnTile(location, mapView); + } } } @@ -341,18 +342,6 @@ public override void _UnhandledInput(InputEvent @event) { if (to_select != null && to_select.owner == controller) setSelectedUnit(to_select); } - GD.Print($"tile: {tile.xCoordinate}, {tile.yCoordinate}: {tile.baseTerrainType.Key} - {tile.overlayTerrainType.Key}"); - int left = mapView.worldEdgeLeft; - int right = mapView.worldEdgeRight; - int camLeft = (int)camera.getVisibleWorld().Position.X; - int camRight = (int)camera.getVisibleWorld().End.X; - GD.Print(camera.getVisibleWorld().End); - if (camLeft <= left) { - GD.Print($"left is visible - world: {left}, cam: {camLeft}"); - } - if (camRight >= right) { - GD.Print($"right is visible - world: {right}, cam: {camRight}"); - } } } } @@ -529,7 +518,6 @@ private void processActions() { if (Input.IsActionJustPressed(C7Action.UnitBuildRoad) && CurrentlySelectedUnit.canBuildRoad()) { new MsgBuildRoad(CurrentlySelectedUnit.guid).send(); } - } private void GetNextAutoselectedUnit(GameData gameData) { diff --git a/C7/Map/CityLabelScene.cs b/C7/Map/CityLabelScene.cs index 86c8abc9..421c4230 100644 --- a/C7/Map/CityLabelScene.cs +++ b/C7/Map/CityLabelScene.cs @@ -11,7 +11,6 @@ public partial class CityLabelScene : Node2D { private City city; private Tile tile; - private Vector2I tileCenter; private ImageTexture cityTexture; @@ -37,6 +36,11 @@ public partial class CityLabelScene : Node2D { private static Theme popSizeTheme = new Theme(); private int lastLabelWidth = 0; + private int lastTurnsUntilGrowth; + private int lastTurnsUntilProductFinished; + private int lastCitySize; + private string lastProductionName; + private bool wasCapital; static CityLabelScene() { smallFontTheme.DefaultFont = smallFont; @@ -54,94 +58,105 @@ static CityLabelScene() { nonEmbassyStar = PCXToGodot.getImageFromPCX(cityIcons, 20, 1, 18, 18); } - public CityLabelScene(City city, Tile tile, Vector2I tileCenter) { + public CityLabelScene(City city, Tile tile) { this.city = city; this.tile = tile; - this.tileCenter = tileCenter; - labelTextureRect.MouseFilter = Control.MouseFilterEnum.Ignore; cityNameLabel.MouseFilter = Control.MouseFilterEnum.Ignore; productionLabel.MouseFilter = Control.MouseFilterEnum.Ignore; popSizeLabel.MouseFilter = Control.MouseFilterEnum.Ignore; + cityNameLabel.Text = city.name; AddChild(labelTextureRect); AddChild(cityNameLabel); AddChild(productionLabel); AddChild(popSizeLabel); + + cityNameLabel.Theme = smallFontTheme; + cityNameLabel.OffsetTop = 24; + + productionLabel.Theme = smallFontTheme; + productionLabel.OffsetTop = 36; + + popSizeLabel.Theme = popSizeTheme; + popSizeLabel.OffsetTop = 28; + + lastTurnsUntilGrowth = city.TurnsUntilGrowth(); + lastTurnsUntilProductFinished = city.TurnsUntilProductionFinished(); + lastCitySize = city.size; + lastProductionName = city.itemBeingProduced.name; + wasCapital = city.IsCapital(); + + resize(lastTurnsUntilGrowth, lastTurnsUntilProductFinished, lastCitySize, lastProductionName, wasCapital); } - public override void _Draw() { - base._Draw(); + private string getCityNameAndGrowthString(int turnsUntilGrowth) { + string turnsUntilGrowthText = turnsUntilGrowth == int.MaxValue || turnsUntilGrowth < 0 ? "- -" : turnsUntilGrowth.ToString(); + return $"{city.name} : {turnsUntilGrowthText}"; + } - int turnsUntilGrowth = city.TurnsUntilGrowth(); - string turnsUntilGrowthText = turnsUntilGrowth == int.MaxValue || turnsUntilGrowth < 0 ? "- -" : "" + turnsUntilGrowth; - string cityNameAndGrowth = $"{city.name} : {turnsUntilGrowthText}"; - string productionDescription = city.itemBeingProduced.name + " : " + city.TurnsUntilProductionFinished(); + private void resize(int turnsUntilGrowth, int turnsUntilProductionFinished, int citySize, string productionName, bool isCapital) { + string productionDescription = $"{productionName} : {turnsUntilProductionFinished}"; + int productionDescriptionWidth = (int)smallFont.GetStringSize(productionDescription).X; + string cityNameAndGrowth = getCityNameAndGrowthString(turnsUntilGrowth); int cityNameAndGrowthWidth = (int)smallFont.GetStringSize(cityNameAndGrowth).X; - int productionDescriptionWidth = (int)smallFont.GetStringSize(productionDescription).X; int maxTextWidth = Math.Max(cityNameAndGrowthWidth, productionDescriptionWidth); + int cityLabelWidth = maxTextWidth + (isCapital? 70 : 45); //TODO: Is 65 right? 70? Will depend on whether it's capital, too - int cityLabelWidth = maxTextWidth + (city.IsCapital()? 70 : 45); //TODO: Is 65 right? 70? Will depend on whether it's capital, too - int textAreaWidth = cityLabelWidth - (city.IsCapital() ? 50 : 25); + int textAreaWidth = cityLabelWidth - (isCapital ? 50 : 25); if (log.IsEnabled(LogEventLevel.Verbose)) { - log.Verbose("Width of city name = " + maxTextWidth); log.Verbose("City label width: " + cityLabelWidth); log.Verbose("Text area width: " + textAreaWidth); } - if (cityLabelWidth != lastLabelWidth) { Image labelBackground = CreateLabelBackground(cityLabelWidth, city, textAreaWidth); - cityLabel = ImageTexture.CreateFromImage(labelBackground); + cityLabel = ImageTexture.CreateFromImage(labelBackground); lastLabelWidth = cityLabelWidth; + labelTextureRect.Texture = cityLabel; + labelTextureRect.OffsetLeft = (cityLabelWidth / -2); + labelTextureRect.OffsetTop = 24; } - - DrawLabelOnScreen(tileCenter, cityLabelWidth, city, cityLabel); - DrawTextOnLabel(tileCenter, cityNameAndGrowthWidth, productionDescriptionWidth, city, cityNameAndGrowth, productionDescription, cityLabelWidth); - } - - private void DrawLabelOnScreen(Vector2I tileCenter, int cityLabelWidth, City city, ImageTexture cityLabel) - { - labelTextureRect.OffsetLeft = tileCenter.X + (cityLabelWidth / -2); - labelTextureRect.OffsetTop = tileCenter.Y + 24; - labelTextureRect.Texture = cityLabel; - } - - private void DrawTextOnLabel(Vector2I tileCenter, int cityNameAndGrowthWidth, int productionDescriptionWidth, City city, string cityNameAndGrowth, string productionDescription, int cityLabelWidth) { - - //Destination for font is based on lower-left of baseline of font, not upper left as for blitted rectangles - int cityNameOffset = cityNameAndGrowthWidth / -2; - int prodDescriptionOffset = productionDescriptionWidth / -2; - if (!city.IsCapital()) { + int cityNameOffset = cityNameAndGrowthWidth / -2 - 8; + int prodDescriptionOffset = productionDescriptionWidth / -2 - 8; + if (!isCapital) { cityNameOffset += 12; prodDescriptionOffset += 12; } - - cityNameLabel.Theme = smallFontTheme; cityNameLabel.Text = cityNameAndGrowth; - cityNameLabel.OffsetLeft = tileCenter.X + cityNameOffset; - cityNameLabel.OffsetTop = tileCenter.Y + 22; + cityNameLabel.OffsetLeft = cityNameOffset; - productionLabel.Theme = smallFontTheme; productionLabel.Text = productionDescription; - productionLabel.OffsetLeft = tileCenter.X + prodDescriptionOffset; - productionLabel.OffsetTop = tileCenter.Y + 32; + productionLabel.OffsetLeft = prodDescriptionOffset; - //City pop size - string popSizeString = "" + city.size; + string popSizeString = citySize.ToString(); int popSizeWidth = (int)midSizedFont.GetStringSize(popSizeString).X; int popSizeOffset = LEFT_RIGHT_BOXES_WIDTH / 2 - popSizeWidth / 2; - popSizeLabel.Theme = popSizeTheme; - - if (city.TurnsUntilGrowth() < 0) { + if (turnsUntilGrowth < 0) { popSizeLabel.Theme = popThemeRed; } popSizeLabel.Text = popSizeString; - popSizeLabel.OffsetLeft = tileCenter.X + cityLabelWidth / -2 + popSizeOffset; - popSizeLabel.OffsetTop = tileCenter.Y + 22; + popSizeLabel.OffsetLeft = cityLabelWidth / -2 + popSizeOffset; + } + + public override void _Process(double delta) { + int turnsUntilGrowth = city.TurnsUntilGrowth(); + int turnsUntilProductionFinished = city.TurnsUntilProductionFinished(); + int citySize = city.size; + string productionName = city.itemBeingProduced.name; + bool isCaptial = city.IsCapital(); + + if (turnsUntilGrowth != lastTurnsUntilGrowth || turnsUntilProductionFinished != lastTurnsUntilProductFinished || citySize != lastCitySize || productionName != lastProductionName || isCaptial != wasCapital) { + lastTurnsUntilGrowth = turnsUntilGrowth; + lastTurnsUntilProductFinished = turnsUntilProductionFinished; + lastCitySize = citySize; + lastProductionName = productionName; + wasCapital = isCaptial; + resize(turnsUntilGrowth, turnsUntilProductionFinished, citySize, productionName, isCaptial); + } } private Image CreateLabelBackground(int cityLabelWidth, City city, int textAreaWidth) diff --git a/C7/Map/CityLayer.cs b/C7/Map/CityLayer.cs deleted file mode 100644 index c84f714e..00000000 --- a/C7/Map/CityLayer.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Collections.Generic; -using C7GameData; -using Godot; -using Serilog; - -namespace C7.Map { - public class CityLayer { - - private ILogger log = LogManager.ForContext(); - - private ImageTexture cityTexture; - private Dictionary cityLabels = new Dictionary(); - - private List citiesWithScenes = new List(); - private Dictionary citySceneLookup = new Dictionary(); - - public CityLayer() - { - } - - public void drawObject(GameData gameData, Tile tile, Vector2 tileCenter) - { - if (tile.cityAtTile is null) { - return; - } - - City city = tile.cityAtTile; - if (!citySceneLookup.ContainsKey(city)) { - CityScene cityScene = new CityScene(city, tile, new Vector2I((int)tileCenter.X, (int)tileCenter.Y)); - citySceneLookup[city] = cityScene; - } else { - CityScene scene = citySceneLookup[city]; - scene._Draw(); - } - } - } -} diff --git a/C7/Map/CityScene.cs b/C7/Map/CityScene.cs index 6e5263dd..dd5579d3 100644 --- a/C7/Map/CityScene.cs +++ b/C7/Map/CityScene.cs @@ -4,17 +4,17 @@ using Serilog; namespace C7.Map { - public partial class CityScene : Node2D { + public partial class CityScene : Sprite2D { private ILogger log = LogManager.ForContext(); private readonly Vector2 citySpriteSize; private ImageTexture cityTexture; - private TextureRect cityGraphics = new TextureRect(); private CityLabelScene cityLabelScene; - public CityScene(City city, Tile tile, Vector2I tileCenter) { - cityLabelScene = new CityLabelScene(city, tile, tileCenter); + public CityScene(City city, Tile tile) { + ZIndex = 20; + cityLabelScene = new CityLabelScene(city, tile); //TODO: Generalize, support multiple city types, etc. Pcx pcx = Util.LoadPCX("Art/Cities/rMIDEAST.PCX"); @@ -23,18 +23,9 @@ public CityScene(City city, Tile tile, Vector2I tileCenter) { cityTexture = Util.LoadTextureFromPCX("Art/Cities/rMIDEAST.PCX", 0, 0, width, height); citySpriteSize = new Vector2(width, height); - cityGraphics.OffsetLeft = tileCenter.X - (float)0.5 * citySpriteSize.X; - cityGraphics.OffsetTop = tileCenter.Y - (float)0.5 * citySpriteSize.Y; - cityGraphics.MouseFilter = Control.MouseFilterEnum.Ignore; - cityGraphics.Texture = cityTexture; + Texture = cityTexture; - AddChild(cityGraphics); AddChild(cityLabelScene); } - - public override void _Draw() { - base._Draw(); - cityLabelScene._Draw(); - } } } diff --git a/C7/Map/Corners.cs b/C7/Map/Corners.cs deleted file mode 100644 index 0c49981c..00000000 --- a/C7/Map/Corners.cs +++ /dev/null @@ -1,150 +0,0 @@ -using Godot; -using System.Collections.Generic; -using System.Linq; -using System; - -namespace C7.Map { - - class TerrainPcx { - private static Random prng = new Random(); - private string name; - // abc refers to the layout of the terrain tiles in the pcx based on - // the positions of each terrain at the corner of 4 tiles. - // - https://forums.civfanatics.com/threads/terrain-editing.622999/ - // - https://forums.civfanatics.com/threads/editing-terrain-pcx-files.102840/ - private string[] abc; - public int atlas; - public TerrainPcx(string name, string[] abc, int atlas) { - this.name = name; - this.abc = abc; - this.atlas = atlas; - } - public bool validFor(string[] corner) { - return corner.All(tile => abc.Contains(tile)); - } - private int abcIndex(string terrain) { - List indices = new List(); - for (int i = 0; i < abc.Count(); i++) { - if (abc[i] == terrain) { - indices.Add(i); - } - } - return indices[prng.Next(indices.Count)]; - } - - // getTextureCoords looks up the correct texture index in the pcx - // for the given position of each corner terrain type - public Vector2I getTextureCoords(string[] corner) { - int top = abcIndex(corner[0]); - int right = abcIndex(corner[1]); - int bottom = abcIndex(corner[2]); - int left = abcIndex(corner[3]); - int index = top + (left * 3) + (right * 9) + (bottom * 27); - return new Vector2I(index % 9, index / 9); - } - } - - partial class Corners : Node2D { - private List terrainPcxFiles = new List { - "Art/Terrain/xtgc.pcx", - "Art/Terrain/xpgc.pcx", - "Art/Terrain/xdgc.pcx", - "Art/Terrain/xdpc.pcx", - "Art/Terrain/xdgp.pcx", - "Art/Terrain/xggc.pcx", - "Art/Terrain/wCSO.pcx", - "Art/Terrain/wSSS.pcx", - "Art/Terrain/wOOO.pcx", - }; - private List terrainPcxList; - private string[,]terrain; - private TileMap tilemap; - private TileSet tileset; - private List textures; - private Vector2I tileSize = new Vector2I(128, 64); - private int width; - private int height; - - private void initializeTileMap() { - this.tilemap = new TileMap(); - this.tileset = new TileSet(); - - this.tileset.TileShape = TileSet.TileShapeEnum.Isometric; - this.tileset.TileLayout = TileSet.TileLayoutEnum.Stacked; - this.tileset.TileOffsetAxis = TileSet.TileOffsetAxisEnum.Horizontal; - this.tileset.TileSize = this.tileSize; - - foreach (ImageTexture texture in this.textures) { - TileSetAtlasSource source = new TileSetAtlasSource(); - source.Texture = texture; - source.TextureRegionSize = this.tileSize; - for (int x = 0; x < 9; x++) { - for (int y = 0; y < 9; y++) { - source.CreateTile(new Vector2I(x, y)); - } - } - this.tileset.AddSource(source); - } - this.tilemap.TileSet = tileset; - - this.terrainPcxList = new List() { - new TerrainPcx("tgc", new string[]{"tundra", "grassland", "coast"}, 0), - new TerrainPcx("pgc", new string[]{"plains", "grassland", "coast"}, 1), - new TerrainPcx("dgc", new string[]{"desert", "grassland", "coast"}, 2), - new TerrainPcx("dpc", new string[]{"desert", "plains", "coast"}, 3), - new TerrainPcx("dgp", new string[]{"desert", "grassland", "plains"}, 4), - // new TerrainPcx("ggc", new string[]{"grassland", "grassland", "coast"}, 5), - new TerrainPcx("cso", new string[]{"coast", "sea", "ocean"}, 6), - new TerrainPcx("sss", new string[]{"sea", "sea", "sea"}, 7), - new TerrainPcx("ooo", new string[]{"ocean", "ocean", "ocean"}, 8), - }; - AddChild(this.tilemap); - } - - private TerrainPcx getPcxForCorner(string[] corner) { - return terrainPcxList.Find(tpcx => tpcx.validFor(corner)); - } - - void fill(Vector2I cell, int atlas, Vector2I texCoords) { - this.tilemap.SetCell(0, cell, atlas, texCoords); - } - - public Corners(C7GameData.GameMap gameMap) { - this.textures = terrainPcxFiles.ConvertAll(path => Util.LoadTextureFromPCX(path)); - this.initializeTileMap(); - this.width = gameMap.numTilesWide / 2; - this.height = gameMap.numTilesTall; - this.terrain = new string[width, height]; - - foreach (C7GameData.Tile t in gameMap.tiles) { - int x = t.xCoordinate; - int y = t.yCoordinate; - // stacked coordinates - x = y % 2 == 0 ? x / 2 : (x - 1) / 2; - this.terrain[x, y] = t.baseTerrainTypeKey; - } - - for (int x = 0; x < width; x++) { - for (int y = 0; y < height; y++) { - Vector2I cell = new Vector2I(x, y); - string left = terrain[x, y]; - string right = terrain[(x + 1) % width, y]; - bool even = y % 2 == 0; - string top = "coast"; - if (y > 0) { - top = even ? terrain[x, y-1] : terrain[(x + 1) % width, y - 1]; - } - string bottom = "coast"; - if (y < height - 1) { - bottom = even ? terrain[x, y+1] : terrain[(x + 1) % width, y + 1]; - } - string[] corner = new string[4]{top, right, bottom, left}; - TerrainPcx pcx = getPcxForCorner(corner); - Vector2I texCoords = pcx.getTextureCoords(corner); - fill(cell, pcx.atlas, texCoords); - } - } - - } - } -} diff --git a/C7/Map/MapView.cs b/C7/Map/MapView.cs index 7d5541f9..bf9c821e 100644 --- a/C7/Map/MapView.cs +++ b/C7/Map/MapView.cs @@ -37,6 +37,7 @@ public void toggleGrid() { private GameMap gameMap; private Dictionary unitSprites = new Dictionary(); + private Dictionary cityScenes = new Dictionary(); private CursorSprite cursor; private UnitSprite spriteFor(MapUnit unit) { @@ -55,6 +56,14 @@ private Vector2 getSpriteLocalPosition(Tile tile, MapUnit.Appearance appearance) return position + offset; } + public void addCity(City city, Tile tile) { + log.Debug($"adding city at tile ({tile.xCoordinate}, {tile.yCoordinate})"); + CityScene scene = new CityScene(city, tile); + scene.Position = tilemap.MapToLocal(stackedCoords(tile)); + AddChild(scene); + cityScenes.Add(city, scene); + } + private void animateUnit(Tile tile, MapUnit unit) { // TODO: simplify AnimationManager and drawing animations it is unnecessarily complex // - also investigate if the custom offset tracking and SetFrame can be replaced by diff --git a/C7/MapView.cs b/C7/MapView.cs deleted file mode 100644 index 8447934b..00000000 --- a/C7/MapView.cs +++ /dev/null @@ -1,781 +0,0 @@ -using System.Collections.Generic; -using System; -using System.Linq; -using C7.Map; -using Godot; -using ConvertCiv3Media; -using C7GameData; -using C7Engine; -using Serilog; -using Serilog.Events; - -// Loose layers are for drawing things on the map on a per-tile basis. (Historical aside: There used to be another kind of layer called a TileLayer -// that was intended to draw regularly tiled objects like terrain sprites but using LooseLayers for everything was found to be a prefereable -// approach.) LooseLayer is effectively the standard map layer. The MapView contains a list of loose layers, inside a LooseView object. Right now to -// add a new layer you must modify the MapView constructor to add it to the list, but (TODO) eventually that will be made moddable. -public abstract class LooseLayer { - // drawObject draws the things this layer is supposed to draw that are associated with the given tile. Its parameters are: - // looseView: The Node2D to actually draw to, e.g., use looseView.DrawCircle(...) to draw a circle. This object also contains a reference to - // the MapView in case you need it. - // gameData: A reference to the game data so each layer doesn't have to redundantly request access. - // tile: The game tile whose contents are to be drawn. This function gets called for each tile in view of the camera and none out of - // view. The same tile may be drawn multiple times at different locations due to edge wrapping. - // tileCenter: The location to draw to. You should draw around this location without adjusting for the camera location or zoom since the - // MapView already transforms the looseView node to account for those things. - public abstract void drawObject(LooseView looseView, GameData gameData, Tile tile, Vector2 tileCenter); - - public virtual void onBeginDraw(LooseView looseView, GameData gameData) {} - public virtual void onEndDraw(LooseView looseView, GameData gameData) {} - - // The layer will be skipped during map drawing if visible is false - public bool visible = true; -} - -public partial class TerrainLayer : LooseLayer { - - public static readonly Vector2 terrainSpriteSize = new Vector2(128, 64); - - // A triple sheet is a sprite sheet containing sprites for three different terrain types including transitions between. - private List tripleSheets; - - // TileToDraw stores the arguments passed to drawObject so the draws can be sorted by texture before being submitted. This significantly - // reduces the number of draw calls Godot must generate (1483 to 312 when fully zoomed out on our test map) and modestly improves framerate - // (by about 14% on my system). - private class TileToDraw : IComparable - { - public Tile tile; - public Vector2 tileCenter; - - public TileToDraw(Tile tile, Vector2 tileCenter) - { - this.tile = tile; - this.tileCenter = tileCenter; - } - - public int CompareTo(TileToDraw other) - { - // "other" might be null, in which case we should return a positive value. CompareTo(null) will do this. - try { - return this.tile.ExtraInfo.BaseTerrainFileID.CompareTo(other?.tile.ExtraInfo.BaseTerrainFileID); - } catch (Exception) { - //It also could be Tile.NONE. In which case, also return a positive value. - return 1; - } - } - } - - private List tilesToDraw = new List(); - - public TerrainLayer() - { - tripleSheets = loadTerrainTripleSheets(); - } - - public List loadTerrainTripleSheets() - { - List fileNames = new List { - "Art/Terrain/xtgc.pcx", - "Art/Terrain/xpgc.pcx", - "Art/Terrain/xdgc.pcx", - "Art/Terrain/xdpc.pcx", - "Art/Terrain/xdgp.pcx", - "Art/Terrain/xggc.pcx", - "Art/Terrain/wCSO.pcx", - "Art/Terrain/wSSS.pcx", - "Art/Terrain/wOOO.pcx", - }; - return fileNames.ConvertAll(name => Util.LoadTextureFromPCX(name)); - } - - public override void drawObject(LooseView looseView, GameData gameData, Tile tile, Vector2 tileCenter) - { - tilesToDraw.Add(new TileToDraw(tile, tileCenter)); - tilesToDraw.Add(new TileToDraw(tile.neighbors[TileDirection.SOUTH], tileCenter + new Vector2(0, 64))); - tilesToDraw.Add(new TileToDraw(tile.neighbors[TileDirection.SOUTHWEST], tileCenter + new Vector2(-64, 32))); - tilesToDraw.Add(new TileToDraw(tile.neighbors[TileDirection.SOUTHEAST], tileCenter + new Vector2(64, 32))); - } - - public override void onEndDraw(LooseView looseView, GameData gameData) { - tilesToDraw.Sort(); - foreach (TileToDraw tTD in tilesToDraw) { - if (tTD.tile != Tile.NONE) { - int xSheet = tTD.tile.ExtraInfo.BaseTerrainImageID % 9, ySheet = tTD.tile.ExtraInfo.BaseTerrainImageID / 9; - Rect2 texRect = new Rect2(new Vector2(xSheet, ySheet) * terrainSpriteSize, terrainSpriteSize); - Vector2 terrainOffset = new Vector2(0, -1 * MapView.cellSize.Y); - // Multiply size by 100.1% so avoid "seams" in the map. See issue #106. - // Jim's option of a whole-map texture is less hacky, but this is quicker and seems to be working well. - Rect2 screenRect = new Rect2(tTD.tileCenter - (float)0.5 * terrainSpriteSize + terrainOffset, terrainSpriteSize * 1.001f); - looseView.DrawTextureRectRegion(tripleSheets[tTD.tile.ExtraInfo.BaseTerrainFileID], screenRect, texRect); - } - } - tilesToDraw.Clear(); - } -} - -public partial class HillsLayer : LooseLayer { - public static readonly Vector2 mountainSize = new Vector2(128, 88); - public static readonly Vector2 volcanoSize = new Vector2(128, 88); //same as mountain - public static readonly Vector2 hillsSize = new Vector2(128, 72); - private ImageTexture mountainTexture; - private ImageTexture snowMountainTexture; - private ImageTexture forestMountainTexture; - private ImageTexture jungleMountainTexture; - private ImageTexture hillsTexture; - private ImageTexture forestHillsTexture; - private ImageTexture jungleHillsTexture; - private ImageTexture volcanosTexture; - private ImageTexture forestVolcanoTexture; - private ImageTexture jungleVolcanoTexture; - - public HillsLayer() { - mountainTexture = Util.LoadTextureFromPCX("Art/Terrain/Mountains.pcx"); - snowMountainTexture = Util.LoadTextureFromPCX("Art/Terrain/Mountains-snow.pcx"); - forestMountainTexture = Util.LoadTextureFromPCX("Art/Terrain/mountain forests.pcx"); - jungleMountainTexture = Util.LoadTextureFromPCX("Art/Terrain/mountain jungles.pcx"); - hillsTexture = Util.LoadTextureFromPCX("Art/Terrain/xhills.pcx"); - forestHillsTexture = Util.LoadTextureFromPCX("Art/Terrain/hill forests.pcx"); - jungleHillsTexture = Util.LoadTextureFromPCX("Art/Terrain/hill jungle.pcx"); - volcanosTexture = Util.LoadTextureFromPCX("Art/Terrain/Volcanos.pcx"); - forestVolcanoTexture = Util.LoadTextureFromPCX("Art/Terrain/Volcanos forests.pcx"); - jungleVolcanoTexture = Util.LoadTextureFromPCX("Art/Terrain/Volcanos jungles.pcx"); - } - - public override void drawObject(LooseView looseView, GameData gameData, Tile tile, Vector2 tileCenter) - { - if (tile.overlayTerrainType.isHilly()) { - int pcxIndex = getMountainIndex(tile); - int row = pcxIndex/4; - int column = pcxIndex % 4; - if (tile.overlayTerrainType.Key == "mountains") { - Rect2 mountainRectangle = new Rect2(column * mountainSize.X, row * mountainSize.Y, mountainSize); - Rect2 screenTarget = new Rect2(tileCenter - (float)0.5 * mountainSize + new Vector2(0, -12), mountainSize); - ImageTexture mountainGraphics; - if (tile.isSnowCapped) { - mountainGraphics = snowMountainTexture; - } - else { - TerrainType dominantVegetation = getDominantVegetationNearHillyTile(tile); - if (dominantVegetation.Key == "forest") { - mountainGraphics = forestMountainTexture; - } - else if (dominantVegetation.Key == "jungle") { - mountainGraphics = jungleMountainTexture; - } - else { - mountainGraphics = mountainTexture; - } - } - looseView.DrawTextureRectRegion(mountainGraphics, screenTarget, mountainRectangle); - } - else if (tile.overlayTerrainType.Key == "hills") { - Rect2 hillsRectangle = new Rect2(column * hillsSize.X, row * hillsSize.Y, hillsSize); - Rect2 screenTarget = new Rect2(tileCenter - (float)0.5 * hillsSize + new Vector2(0, -4), hillsSize); - ImageTexture hillGraphics; - TerrainType dominantVegetation = getDominantVegetationNearHillyTile(tile); - if (dominantVegetation.Key == "forest") { - hillGraphics = forestHillsTexture; - } - else if (dominantVegetation.Key == "jungle") { - hillGraphics = jungleHillsTexture; - } - else { - hillGraphics = hillsTexture; - } - looseView.DrawTextureRectRegion(hillGraphics, screenTarget, hillsRectangle); - } - else if (tile.overlayTerrainType.Key == "volcano") { - Rect2 volcanoRectangle = new Rect2(column * volcanoSize.X, row * volcanoSize.Y, volcanoSize); - Rect2 screenTarget = new Rect2(tileCenter - (float)0.5 * volcanoSize + new Vector2(0, -12), volcanoSize); - ImageTexture volcanoGraphics; - TerrainType dominantVegetation = getDominantVegetationNearHillyTile(tile); - if (dominantVegetation.Key == "forest") { - volcanoGraphics = forestVolcanoTexture; - } - else if (dominantVegetation.Key == "jungle") { - volcanoGraphics = jungleVolcanoTexture; - } - else { - volcanoGraphics = volcanosTexture; - } - looseView.DrawTextureRectRegion(volcanoGraphics, screenTarget, volcanoRectangle); - } - } - } - - private TerrainType getDominantVegetationNearHillyTile(Tile center) - { - TerrainType northeastType = center.neighbors[TileDirection.NORTHEAST].overlayTerrainType; - TerrainType northwestType = center.neighbors[TileDirection.NORTHWEST].overlayTerrainType; - TerrainType southeastType = center.neighbors[TileDirection.SOUTHEAST].overlayTerrainType; - TerrainType southwestType = center.neighbors[TileDirection.SOUTHWEST].overlayTerrainType; - - TerrainType[] neighborTerrains = { northeastType, northwestType, southeastType, southwestType }; - - int hills = 0; - int forests = 0; - int jungles = 0; - //These references are so we can return the appropriate type, and because we don't have a good way - //to grab them directly at this point in time. - TerrainType forest = null; - TerrainType jungle = null; - foreach (TerrainType type in neighborTerrains) { - if (type.isHilly()) { - hills++; - } - else if (type.Key == "forest") { - forests++; - forest = type; - } - else if (type.Key == "jungle") { - jungles++; - jungle = type; - } - } - - if (hills + forests + jungles < 4) { //some surrounding tiles are neither forested nor hilly - return TerrainType.NONE; - } - if (forests == 0 && jungles == 0) { - return TerrainType.NONE; //all hills - } - if (forests > jungles) { - return forest; - } - if (jungles > forests) { - return jungle; - } - - //If we get here, it's a tie between forest and jungle. Deterministically choose one so it doesn't change on every render - if (center.xCoordinate % 2 == 0) { - return forest; - } - return jungle; - } - - private int getMountainIndex(Tile tile) { - int index = 0; - if (tile.neighbors[TileDirection.NORTHWEST].overlayTerrainType.isHilly()) { - index++; - } - if (tile.neighbors[TileDirection.NORTHEAST].overlayTerrainType.isHilly()) { - index+=2; - } - if (tile.neighbors[TileDirection.SOUTHWEST].overlayTerrainType.isHilly()) { - index+=4; - } - if (tile.neighbors[TileDirection.SOUTHEAST].overlayTerrainType.isHilly()) { - index+=8; - } - return index; - } -} - -public partial class ForestLayer : LooseLayer { - public static readonly Vector2 forestJungleSize = new Vector2(128, 88); - - private ImageTexture largeJungleTexture; - private ImageTexture smallJungleTexture; - private ImageTexture largeForestTexture; - private ImageTexture largePlainsForestTexture; - private ImageTexture largeTundraForestTexture; - private ImageTexture smallForestTexture; - private ImageTexture smallPlainsForestTexture; - private ImageTexture smallTundraForestTexture; - private ImageTexture pineForestTexture; - private ImageTexture pinePlainsTexture; - private ImageTexture pineTundraTexture; - - public ForestLayer() { - largeJungleTexture = Util.LoadTextureFromPCX("Art/Terrain/grassland forests.pcx", 0, 0, 512, 176); - smallJungleTexture = Util.LoadTextureFromPCX("Art/Terrain/grassland forests.pcx", 0, 176, 768, 176); - largeForestTexture = Util.LoadTextureFromPCX("Art/Terrain/grassland forests.pcx", 0, 352, 512, 176); - largePlainsForestTexture = Util.LoadTextureFromPCX("Art/Terrain/plains forests.pcx", 0, 352, 512, 176); - largeTundraForestTexture = Util.LoadTextureFromPCX("Art/Terrain/tundra forests.pcx", 0, 352, 512, 176); - smallForestTexture = Util.LoadTextureFromPCX("Art/Terrain/grassland forests.pcx", 0, 528, 640, 176); - smallPlainsForestTexture = Util.LoadTextureFromPCX("Art/Terrain/plains forests.pcx", 0, 528, 640, 176); - smallTundraForestTexture = Util.LoadTextureFromPCX("Art/Terrain/tundra forests.pcx", 0, 528, 640, 176); - pineForestTexture = Util.LoadTextureFromPCX("Art/Terrain/grassland forests.pcx", 0, 704, 768, 176); - pinePlainsTexture = Util.LoadTextureFromPCX("Art/Terrain/plains forests.pcx" , 0, 704, 768, 176); - pineTundraTexture = Util.LoadTextureFromPCX("Art/Terrain/tundra forests.pcx" , 0, 704, 768, 176); - } - - public override void drawObject(LooseView looseView, GameData gameData, Tile tile, Vector2 tileCenter) { - if (tile.overlayTerrainType.Key == "jungle") { - //Randomly, but predictably, choose a large jungle graphic - //More research is needed on when to use large vs small jungles. Probably, small is used when neighboring fewer jungles. - //For the first pass, we're just always using large jungles. - int randomJungleRow = tile.yCoordinate % 2; - int randomJungleColumn; - ImageTexture jungleTexture; - if (tile.getEdgeNeighbors().Any(t => t.IsWater())) { - randomJungleColumn = tile.xCoordinate % 6; - jungleTexture = smallJungleTexture; - } - else { - randomJungleColumn = tile.xCoordinate % 4; - jungleTexture = largeJungleTexture; - } - Rect2 jungleRectangle = new Rect2(randomJungleColumn * forestJungleSize.X, randomJungleRow * forestJungleSize.Y, forestJungleSize); - Rect2 screenTarget = new Rect2(tileCenter - (float)0.5 * forestJungleSize + new Vector2(0, -12), forestJungleSize); - looseView.DrawTextureRectRegion(jungleTexture, screenTarget, jungleRectangle); - } - if (tile.overlayTerrainType.Key == "forest") { - int forestRow = 0; - int forestColumn = 0; - ImageTexture forestTexture; - if (tile.isPineForest) { - forestRow = tile.yCoordinate % 2; - forestColumn = tile.xCoordinate % 6; - if (tile.baseTerrainType.Key == "grassland") { - forestTexture = pineForestTexture; - } - else if (tile.baseTerrainType.Key == "plains") { - forestTexture = pinePlainsTexture; - } - else { //Tundra - forestTexture = pineTundraTexture; - } - } - else { - forestRow = tile.yCoordinate % 2; - if (tile.getEdgeNeighbors().Any(t => t.IsWater())) { - forestColumn = tile.xCoordinate % 5; - if (tile.baseTerrainType.Key == "grassland") { - forestTexture = smallForestTexture; - } - else if (tile.baseTerrainType.Key == "plains") { - forestTexture = smallPlainsForestTexture; - } - else { //tundra - forestTexture = smallTundraForestTexture; - } - } - else { - forestColumn = tile.xCoordinate % 4; - if (tile.baseTerrainType.Key == "grassland") { - forestTexture = largeForestTexture; - } - else if (tile.baseTerrainType.Key == "plains") { - forestTexture = largePlainsForestTexture; - } - else { //tundra - forestTexture = largeTundraForestTexture; - } - } - } - Rect2 forestRectangle = new Rect2(forestColumn * forestJungleSize.X, forestRow * forestJungleSize.Y, forestJungleSize); - Rect2 screenTarget = new Rect2(tileCenter - (float)0.5 * forestJungleSize + new Vector2(0, -12), forestJungleSize); - looseView.DrawTextureRectRegion(forestTexture, screenTarget, forestRectangle); - } - } -} -public partial class MarshLayer : LooseLayer { - public static readonly Vector2 marshSize = new Vector2(128, 88); - //Because the marsh graphics are 88 pixels tall instead of the 64 of a tile, we also need an addition 12 pixel offset to the top - //88 - 64 = 24; 24/2 = 12. This keeps the marsh centered with half the extra 24 pixels above the tile and half below. - readonly Vector2 MARSH_OFFSET = (float)0.5 * marshSize + new Vector2(0, -12); - - private ImageTexture largeMarshTexture; - private ImageTexture smallMarshTexture; - - public MarshLayer() { - largeMarshTexture = Util.LoadTextureFromPCX("Art/Terrain/marsh.pcx", 0, 0, 512, 176); - smallMarshTexture = Util.LoadTextureFromPCX("Art/Terrain/marsh.pcx", 0, 176, 640, 176); - } - - public override void drawObject(LooseView looseView, GameData gameData, Tile tile, Vector2 tileCenter) { - if (tile.overlayTerrainType.Key == "marsh") { - int randomJungleRow = tile.yCoordinate % 2; - int randomMarshColumn; - ImageTexture marshTexture; - if (tile.getEdgeNeighbors().Any(t => t.IsWater())) { - randomMarshColumn = tile.xCoordinate % 5; - marshTexture = smallMarshTexture; - } - else { - randomMarshColumn = tile.xCoordinate % 4; - marshTexture = largeMarshTexture; - } - Rect2 jungleRectangle = new Rect2(randomMarshColumn * marshSize.X, randomJungleRow * marshSize.Y, marshSize); - Rect2 screenTarget = new Rect2(tileCenter - MARSH_OFFSET, marshSize); - looseView.DrawTextureRectRegion(marshTexture, screenTarget, jungleRectangle); - } - } -} - -public partial class RiverLayer : LooseLayer -{ - public static readonly Vector2 riverSize = new Vector2(128, 64); - public static readonly Vector2 riverCenterOffset = new Vector2(riverSize.X / 2, 0); - private ImageTexture riverTexture; - - public RiverLayer() { - riverTexture = Util.LoadTextureFromPCX("Art/Terrain/mtnRivers.pcx"); - } - - public override void drawObject(LooseView looseView, GameData gameData, Tile tile, Vector2 tileCenter) - { - //The "point" is the easternmost point of the tile for which we are drawing rivers. - //Which river graphics to used is calculated by evaluating the tiles that neighbor - //that point. - Tile northOfPoint = tile.neighbors[TileDirection.NORTHEAST]; - Tile eastOfPoint = tile.neighbors[TileDirection.EAST]; - Tile westOfPoint = tile; - Tile southOfPoint = tile.neighbors[TileDirection.SOUTHEAST]; - - int riverGraphicsIndex = 0; - - if (northOfPoint.riverSouthwest) { - riverGraphicsIndex++; - } - if (eastOfPoint.riverNorthwest) { - riverGraphicsIndex+=2; - } - if (westOfPoint.riverSoutheast) { - riverGraphicsIndex+=4; - } - if (southOfPoint.riverNortheast) { - riverGraphicsIndex+=8; - } - if (riverGraphicsIndex == 0) { - return; - } - int riverRow = riverGraphicsIndex / 4; - int riverColumn = riverGraphicsIndex % 4; - - Rect2 riverRectangle = new Rect2(riverColumn * riverSize.X, riverRow * riverSize.Y, riverSize); - Rect2 screenTarget = new Rect2(tileCenter - (float)0.5 * riverSize + riverCenterOffset, riverSize); - looseView.DrawTextureRectRegion(riverTexture, screenTarget, riverRectangle); - } -} - -public partial class GridLayer : LooseLayer { - public Color color = Color.Color8(50, 50, 50, 150); - public float lineWidth = (float)1.0; - - public GridLayer() {} - - public override void drawObject(LooseView looseView, GameData gameData, Tile tile, Vector2 tileCenter) - { - Vector2 cS = MapView.cellSize; - Vector2 left = tileCenter + new Vector2(-cS.X, 0 ); - Vector2 top = tileCenter + new Vector2( 0 , -cS.Y); - Vector2 right = tileCenter + new Vector2( cS.X, 0 ); - looseView.DrawLine(left, top , color, lineWidth); - looseView.DrawLine(top , right, color, lineWidth); - } -} - -public partial class BuildingLayer : LooseLayer { - private ImageTexture buildingsTex; - private Vector2 buildingSpriteSize; - - public BuildingLayer() - { - var buildingsPCX = new Pcx(Util.Civ3MediaPath("Art/Terrain/TerrainBuildings.PCX")); - buildingsTex = PCXToGodot.getImageTextureFromPCX(buildingsPCX); - //In Conquests, this graphic is 4x4, and the search path will now find the Conquests one first - buildingSpriteSize = new Vector2((float)buildingsTex.GetWidth() / 4, (float)buildingsTex.GetHeight() / 4); - } - - public override void drawObject(LooseView looseView, GameData gameData, Tile tile, Vector2 tileCenter) - { - if (tile.hasBarbarianCamp) { - var texRect = new Rect2(buildingSpriteSize * new Vector2 (2, 0), buildingSpriteSize); //(2, 0) is the offset in the TerrainBuildings.PCX file (top row, third in) - // TODO: Modify this calculation so it doesn't assume buildingSpriteSize is the same as the size of the terrain tiles - var screenRect = new Rect2(tileCenter - (float)0.5 * buildingSpriteSize, buildingSpriteSize); - looseView.DrawTextureRectRegion(buildingsTex, screenRect, texRect); - } - } -} - -public partial class LooseView : Node2D { - public MapView mapView; - public List layers = new List(); - - public LooseView(MapView mapView) - { - this.mapView = mapView; - } - - private struct VisibleTile - { - public Tile tile; - public Vector2 tileCenter; - } - - public override void _Draw() - { - base._Draw(); - - using (var gameDataAccess = new UIGameDataAccess()) { - GameData gD = gameDataAccess.gameData; - - // Iterating over visible tiles is unfortunately pretty expensive. Assemble a list of Tile references and centers first so we don't - // have to reiterate for each layer. Doing this improves framerate significantly. - MapView.VisibleRegion visRegion = mapView.getVisibleRegion(); - List visibleTiles = new List(); - for (int y = visRegion.upperLeftY; y < visRegion.lowerRightY; y++) { - if (gD.map.isRowAt(y)) { - for (int x = visRegion.getRowStartX(y); x < visRegion.lowerRightX; x += 2) { - Tile tile = gD.map.tileAt(x, y); - if (IsTileKnown(tile, gameDataAccess)) { - visibleTiles.Add(new VisibleTile { tile = tile, tileCenter = MapView.cellSize * new Vector2(x + 1, y + 1) }); - } - } - } - } - - foreach (LooseLayer layer in layers.FindAll(L => L.visible && !(L is FogOfWarLayer))) { - layer.onBeginDraw(this, gD); - foreach (VisibleTile vT in visibleTiles) { - layer.drawObject(this, gD, vT.tile, vT.tileCenter); - } - layer.onEndDraw(this, gD); - } - - if (!gD.observerMode) { - foreach (LooseLayer layer in layers.FindAll(layer => layer is FogOfWarLayer)) { - for (int y = visRegion.upperLeftY; y < visRegion.lowerRightY; y++) - if (gD.map.isRowAt(y)) - for (int x = visRegion.getRowStartX(y); x < visRegion.lowerRightX; x += 2) { - Tile tile = gD.map.tileAt(x, y); - if (tile != Tile.NONE) { - VisibleTile invisibleTile = new VisibleTile { tile = tile, tileCenter = MapView.cellSize * new Vector2(x + 1, y + 1) }; - layer.drawObject(this, gD, tile, invisibleTile.tileCenter); - } - } - } - } - } - } - private static bool IsTileKnown(Tile tile, UIGameDataAccess gameDataAccess) { - if (gameDataAccess.gameData.observerMode) { - return true; - } - return tile != Tile.NONE && gameDataAccess.gameData.GetHumanPlayers()[0].tileKnowledge.isTileKnown(tile); - } -} - -public partial class MapView : Node2D { - // cellSize is half the size of the tile sprites, or the amount of space each tile takes up when they are packed on the grid (note tiles are - // staggered and half overlap). - public static readonly Vector2 cellSize = new Vector2(64, 32); - public Vector2 scaledCellSize { - get { return cellSize * new Vector2(cameraZoom, cameraZoom); } - } - - public Game game; - - public int mapWidth { get; private set; } - public int mapHeight { get; private set; } - public bool wrapHorizontally { get; private set; } - public bool wrapVertically { get; private set; } - - private Vector2 internalCameraLocation = new Vector2(0, 0); - public Vector2 cameraLocation { - get { - return internalCameraLocation; - } - set { - setCameraLocation(value); - } - } - public float internalCameraZoom = 1; - public float cameraZoom { - get { return internalCameraZoom; } - set { setCameraZoomFromMiddle(value); } - } - - private LooseView looseView; - - // Specifies a rectangular block of tiles that are currently potentially on screen. Accessible through getVisibleRegion(). Tile coordinates - // are "virtual", i.e. "unwrapped", so there isn't necessarily a tile at each location. The region is intended to include the upper left - // coordinates but not the lower right ones. When iterating over all tiles in the region you must account for the fact that map rows are - // staggered, see LooseView._Draw for an example. - public struct VisibleRegion { - public int upperLeftX, upperLeftY; - public int lowerRightX, lowerRightY; - - public int getRowStartX(int y) - { - return upperLeftX + (y - upperLeftY)%2; - } - } - - public GridLayer gridLayer { get; private set; } - - public ImageTexture civColorWhitePalette = null; - private Corners corners; - public MapView(Game game, int mapWidth, int mapHeight, bool wrapHorizontally, bool wrapVertically) - { - this.game = game; - this.mapWidth = mapWidth; - this.mapHeight = mapHeight; - this.wrapHorizontally = wrapHorizontally; - this.wrapVertically = wrapVertically; - - looseView = new LooseView(this); - // looseView.layers.Add(new TerrainLayer()); - looseView.layers.Add(new RiverLayer()); - looseView.layers.Add(new ForestLayer()); - looseView.layers.Add(new MarshLayer()); - looseView.layers.Add(new HillsLayer()); - looseView.layers.Add(new TntLayer()); - looseView.layers.Add(new RoadLayer()); - looseView.layers.Add(new ResourceLayer()); - this.gridLayer = new GridLayer(); - looseView.layers.Add(this.gridLayer); - looseView.layers.Add(new BuildingLayer()); - looseView.layers.Add(new UnitLayer()); - looseView.layers.Add(new CityLayer()); - // looseView.layers.Add(new FogOfWarLayer()); - - (civColorWhitePalette, _) = Util.loadPalettizedPCX("Art/Units/Palettes/ntp00.pcx"); - - AddChild(looseView); - } - - public override void _Process(double delta) - { - // Redraw everything. This is necessary so that animations play. Maybe we could only update the unit layer but long term I think it's - // better to redraw everything every frame like a typical modern video game. - looseView.QueueRedraw(); - } - - // Returns the size in pixels of the area in which the map will be drawn. This is the viewport size or, if that's null, the window size. - public Vector2 getVisibleAreaSize() - { - return GetViewport() != null ? GetViewportRect().Size : DisplayServer.WindowGetSize(); - } - - public VisibleRegion getVisibleRegion() - { - (int x0, int y0) = tileCoordsOnScreenAt(new Vector2(0, 0)); - Vector2 mapViewSize = new Vector2(2, 4) + getVisibleAreaSize() / scaledCellSize; - return new VisibleRegion { upperLeftX = x0 - 2, upperLeftY = y0 - 2, - lowerRightX = x0 + (int)mapViewSize.X, lowerRightY = y0 + (int)mapViewSize.Y }; - } - - // "center" is the screen location around which the zoom is centered, e.g., if center is (0, 0) the tile in the top left corner will be the - // same after the zoom level is changed, and if center is screenSize/2, the tile in the center of the window won't change. - public void setCameraZoom(float newScale, Vector2 center) - { - Vector2 v2NewZoom = new Vector2(newScale, newScale); - Vector2 v2OldZoom = new Vector2(cameraZoom, cameraZoom); - if (v2NewZoom != v2OldZoom) { - internalCameraZoom = newScale; - looseView.Scale = v2NewZoom; - setCameraLocation ((v2NewZoom / v2OldZoom) * (cameraLocation + center) - center); - } - } - - // Zooms in or out centered on the middle of the screen - public void setCameraZoomFromMiddle(float newScale) - { - setCameraZoom(newScale, getVisibleAreaSize() / 2); - } - - public void moveCamera(Vector2 offset) - { - setCameraLocation(cameraLocation + offset); - } - - public void setCameraLocation(Vector2 location) - { - // Prevent the camera from moving beyond an unwrapped edge of the map. One complication here is that the viewport might actually be - // larger than the map (if we're zoomed far out) so in that case we must apply the constraint the other way around, i.e. constrain the - // map to the viewport rather than the viewport to the map. - Vector2 visAreaSize = getVisibleAreaSize(); - Vector2 mapPixelSize = new Vector2(cameraZoom, cameraZoom) * (new Vector2(cellSize.X * (mapWidth + 1), cellSize.Y * (mapHeight + 1))); - if (!wrapHorizontally) { - float leftLim, rightLim; - { - if (mapPixelSize.X >= visAreaSize.X) { - leftLim = 0; - rightLim = mapPixelSize.X - visAreaSize.X; - } else { - leftLim = mapPixelSize.X - visAreaSize.X; - rightLim = 0; - } - } - if (location.X < leftLim) - location.X = leftLim; - else if (location.X > rightLim) - location.X = rightLim; - } - if (!wrapVertically) { - // These margins allow the player to move the camera that far off those map edges so that the UI controls don't cover up the - // map. TODO: These values should be read from the sizes of the UI elements instead of hardcoded. - float topMargin = 70, bottomMargin = 140; - float topLim, bottomLim; - { - if (mapPixelSize.Y >= visAreaSize.Y) { - topLim = -topMargin; - bottomLim = mapPixelSize.Y - visAreaSize.Y + bottomMargin; - } else { - topLim = mapPixelSize.Y - visAreaSize.Y; - bottomLim = 0; - } - } - if (location.Y < topLim) - location.Y = topLim; - else if (location.Y > bottomLim) - location.Y = bottomLim; - } - - internalCameraLocation = location; - looseView.Position = -location; - } - - public Vector2 screenLocationOfTileCoords(int x, int y, bool center = true) - { - // Add one to x & y to get the tile center b/c in Civ 3 the tile at (x, y) is a diamond centered on (x+1, y+1). - Vector2 centeringOffset = center ? new Vector2(1, 1) : new Vector2(0, 0); - - var mapLoc = (new Vector2(x, y) + centeringOffset) * cellSize; - return mapLoc * cameraZoom - cameraLocation; - } - - // Returns the location of tile (x, y) on the screen, if "center" is true returns the location of the tile center and otherwise returns the - // upper left. Works even if (x, y) is off screen or out of bounds. - public Vector2 screenLocationOfTile(Tile tile, bool center = true) - { - return screenLocationOfTileCoords(tile.xCoordinate, tile.yCoordinate, center); - } - - // Returns the virtual tile coordinates on screen at the given location. "Virtual" meaning the coordinates are unwrapped and there isn't - // necessarily a tile there at all. - public (int, int) tileCoordsOnScreenAt(Vector2 screenLocation) - { - Vector2 mapLoc = (screenLocation + cameraLocation) / scaledCellSize; - Vector2 intMapLoc = mapLoc.Floor(); - Vector2 fracMapLoc = mapLoc - intMapLoc; - int x = (int)intMapLoc.X, y = (int)intMapLoc.Y; - bool evenColumn = x%2 == 0, evenRow = y%2 == 0; - if (evenColumn ^ evenRow) { - if (fracMapLoc.Y > fracMapLoc.X) - x -= 1; - else - y -= 1; - } else { - if (fracMapLoc.Y < 1 - fracMapLoc.X) { - x -= 1; - y -= 1; - } - } - return (x, y); - } - - public Tile tileOnScreenAt(GameMap map, Vector2 screenLocation) - { - (int x, int y) = tileCoordsOnScreenAt(screenLocation); - return map.tileAt(x, y); - } - - public void centerCameraOnTile(Tile t) - { - var tileCenter = new Vector2(t.xCoordinate + 1, t.yCoordinate + 1) * scaledCellSize; - setCameraLocation(tileCenter - (float)0.5 * getVisibleAreaSize()); - } -} diff --git a/C7/PlayerCamera.cs b/C7/PlayerCamera.cs index bb16c14d..047de459 100644 --- a/C7/PlayerCamera.cs +++ b/C7/PlayerCamera.cs @@ -44,4 +44,13 @@ public void centerOnTile(Tile tile, MapView map) { Vector2 target = map.tileToLocal(tile); Position = target; } + + public bool isTileInView(Tile tile, MapView map) { + Rect2 visible = getVisibleWorld(); + Vector2 target = map.tileToLocal(tile); + float size = 30; + target -= Vector2.One * (size / 2); + Rect2 boundingBox = new Rect2(target, size, size); + return visible.Encloses(boundingBox); + } } diff --git a/C7Engine/EntryPoints/CityInteractions.cs b/C7Engine/EntryPoints/CityInteractions.cs index 62be1c62..fddf88b7 100644 --- a/C7Engine/EntryPoints/CityInteractions.cs +++ b/C7Engine/EntryPoints/CityInteractions.cs @@ -1,4 +1,3 @@ -using System.Linq; using C7Engine.AI; namespace C7Engine @@ -23,6 +22,7 @@ public static void BuildCity(int x, int y, string playerGuid, string name) owner.cities.Add(newCity); tileWithNewCity.cityAtTile = newCity; tileWithNewCity.overlays.road = true; + new MsgCityBuilt(tileWithNewCity).send(); // UI will add city to the map view } public static void DestroyCity(int x, int y) { diff --git a/C7Engine/EntryPoints/MessageToUI.cs b/C7Engine/EntryPoints/MessageToUI.cs index e4339c33..130152d6 100644 --- a/C7Engine/EntryPoints/MessageToUI.cs +++ b/C7Engine/EntryPoints/MessageToUI.cs @@ -1,7 +1,8 @@ +using System.Threading; +using C7GameData; + namespace C7Engine { - using System.Threading; - using C7GameData; public class MessageToUI { public void send() @@ -40,6 +41,14 @@ public MsgStartEffectAnimation(Tile tile, AnimatedEffect effect, AutoResetEvent } } + public class MsgCityBuilt : MessageToUI { + public int tileIndex; + + public MsgCityBuilt(Tile tile) { + this.tileIndex = EngineStorage.gameData.map.tileCoordsToIndex(tile.xCoordinate, tile.yCoordinate); + } + } + public class MsgStartTurn : MessageToUI {} } From e040293d1375dfbcc115f9bf5e7a18d04e21c10c Mon Sep 17 00:00:00 2001 From: Paul Cento Date: Tue, 1 Aug 2023 15:02:46 +0200 Subject: [PATCH 41/60] load fog of war pcx --- .gitignore | 2 +- C7/Map/MapView.cs | 40 ++++++++++++++++++++++++++++++++++++++-- C7/Map/TileSetLoader.cs | 8 ++++++-- C7/PCXToGodot.cs | 14 ++++++++++++++ C7/Util.cs | 2 ++ 5 files changed, 61 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 7a982616..3635d19d 100644 --- a/.gitignore +++ b/.gitignore @@ -42,7 +42,7 @@ project.lock.json *.sln.docstates C7.ini log.txt -*.csproj.old +*.csproj.old* # Build results [Dd]ebug/ diff --git a/C7/Map/MapView.cs b/C7/Map/MapView.cs index bf9c821e..95d18630 100644 --- a/C7/Map/MapView.cs +++ b/C7/Map/MapView.cs @@ -155,6 +155,11 @@ private void initializeTileMap() { if (layer != Layer.Invalid) { tilemap.AddLayer(layer.Index()); tilemap.SetLayerYSortEnabled(layer.Index(), true); + if (layer != Layer.FogOfWar) { + tilemap.SetLayerYSortEnabled(layer.Index(), true); + } else { + tilemap.SetLayerZIndex(layer.Index(), 15); + } } } @@ -241,8 +246,9 @@ public MapView(Game game, GameData data) { setTerrainTiles(); // update each tile once to add all initial layers + TileKnowledge tk = data.GetHumanPlayers().First()?.tileKnowledge; foreach (Tile tile in gameMap.tiles) { - updateTile(tile); + updateTile(tile, tk); } } @@ -496,13 +502,43 @@ private void updateBuildingLayer(Tile tile) { } } - public void updateTile(Tile tile) { + // updateFogOfWarLayer returns true if the tile is visible or + // semi-visible, indicating other layers should be updated. + private bool updateFogOfWarLayer(Tile tile, TileKnowledge tk) { + if (!tk.isTileKnown(tile)) { + int sum = 0; + if (tk.isTileKnown(tile.neighbors[TileDirection.NORTH]) || tk.isTileKnown(tile.neighbors[TileDirection.NORTHWEST]) || tk.isTileKnown(tile.neighbors[TileDirection.NORTHEAST])) { + sum += 1 * 2; + } + if (tk.isTileKnown(tile.neighbors[TileDirection.WEST]) || tk.isTileKnown(tile.neighbors[TileDirection.NORTHWEST]) || tk.isTileKnown(tile.neighbors[TileDirection.SOUTHWEST])) { + sum += 3 * 2; + } + if (tk.isTileKnown(tile.neighbors[TileDirection.EAST]) || tk.isTileKnown(tile.neighbors[TileDirection.NORTHEAST]) || tk.isTileKnown(tile.neighbors[TileDirection.SOUTHEAST])) { + sum += 9 * 2; + } + if (tk.isTileKnown(tile.neighbors[TileDirection.SOUTH]) || tk.isTileKnown(tile.neighbors[TileDirection.SOUTHWEST]) || tk.isTileKnown(tile.neighbors[TileDirection.SOUTHEAST])) { + sum += 27 * 2; + } + setCell(Layer.FogOfWar, Atlas.FogOfWar, tile, new Vector2I(sum % 9, sum / 9)); + return sum != 0; // if the sum is not 0, parts of the tile may be visible + } + return true; // no fog of war, tile + } + + public void updateTile(Tile tile, TileKnowledge tk) { if (tile == Tile.NONE || tile is null) { string msg = tile is null ? "null tile" : "Tile.NONE"; log.Warning($"attempting to update {msg}"); return; } + if (tk is not null) { + bool isTileVisibile = updateFogOfWarLayer(tile, tk); + if (!isTileVisibile) { + return; + } + } + updateRoadLayer(tile, true); if (tile.Resource != C7GameData.Resource.NONE) { diff --git a/C7/Map/TileSetLoader.cs b/C7/Map/TileSetLoader.cs index c2296985..2e60e0db 100644 --- a/C7/Map/TileSetLoader.cs +++ b/C7/Map/TileSetLoader.cs @@ -14,6 +14,7 @@ public enum Layer { TerrainYield, Building, Grid, + FogOfWar, Invalid, }; @@ -46,6 +47,7 @@ public enum Atlas { TerrainBuilding, GoodyHut, Grid, + FogOfWar, Invalid, } @@ -70,8 +72,8 @@ public AtlasLoader(string p, int w, int h, Vector2I rs, int y = 0) { height = h; regionSize = rs; textureOrigin = new Vector2I(0, y); - source = new TileSetAtlasSource { - Texture = Util.LoadTextureFromPCX(path), + source = new TileSetAtlasSource{ + Texture = p.EndsWith("FogOfWar.pcx") ? Util.LoadFogOfWarPCX(path) : Util.LoadTextureFromPCX(path), TextureRegionSize = regionSize, }; } @@ -204,6 +206,8 @@ class TileSetLoader { {Atlas.TerrainBuilding, new AtlasLoader("Art/Terrain/TerrainBuildings.pcx", 4, 4, buildingSize)}, {Atlas.GoodyHut, new NonSquareAtlasLoader("Art/Terrain/goodyhuts.pcx", 3, 3, 2, buildingSize)}, + + {Atlas.FogOfWar, new NonSquareAtlasLoader("Art/Terrain/FogOfWar.pcx", 9, 9, 8, tileSize)}, }; public static TileSet LoadCiv3TileSet() { diff --git a/C7/PCXToGodot.cs b/C7/PCXToGodot.cs index b3621c91..6c86dd7e 100644 --- a/C7/PCXToGodot.cs +++ b/C7/PCXToGodot.cs @@ -16,6 +16,20 @@ public static ImageTexture getImageTextureFromPCX(Pcx pcx, int leftStart, int to return getImageTextureFromImage(image); } + public static ImageTexture getImageTextureFromFogOfWarPCX(Pcx pcx) { + Image ImgTxtr = ByteArrayToImage(pcx.ColorIndices, pcx.Palette, pcx.Width, pcx.Height); + for (int x = 0; x < ImgTxtr.GetWidth(); x++) { + for (int y = 0; y < ImgTxtr.GetHeight(); y++) { + Color pixel = ImgTxtr.GetPixel(x, y); + if (pixel.A > 0) { + Color transparent = new Color(pixel.R, pixel.G, pixel.B, 1f - pixel.R); + ImgTxtr.SetPixel(x, y, transparent); + } + } + } + return getImageTextureFromImage(ImgTxtr); + } + /** * This method is for cases where we want to use components of multiple PCXs in a texture, such as for the popup background. **/ diff --git a/C7/Util.cs b/C7/Util.cs index d72cb60f..ba1a3e26 100644 --- a/C7/Util.cs +++ b/C7/Util.cs @@ -221,6 +221,8 @@ static public ImageTexture LoadTextureFromPCX(string relPath, int leftStart, int return texture; } + static public ImageTexture LoadFogOfWarPCX(string relPath) => PCXToGodot.getImageTextureFromFogOfWarPCX(LoadPCX(relPath)); + private static Dictionary PcxCache = new Dictionary(); /** From 224b613a609dbf926160f757d215a3cf33fc3007 Mon Sep 17 00:00:00 2001 From: Paul Cento Date: Tue, 1 Aug 2023 16:07:15 +0200 Subject: [PATCH 42/60] reveal fog when units move --- C7/Game.cs | 10 +++-- C7/Map/MapView.cs | 70 ++++++++++++++++++++--------- C7/Map/TileSetLoader.cs | 2 - C7Engine/EntryPoints/MessageToUI.cs | 8 ++++ C7Engine/MapUnitExtensions.cs | 4 +- C7GameData/AIData/TileKnowledge.cs | 7 +-- C7GameData/GameMap.cs | 11 +++-- 7 files changed, 77 insertions(+), 35 deletions(-) diff --git a/C7/Game.cs b/C7/Game.cs index 0a10be29..3023dc1b 100644 --- a/C7/Game.cs +++ b/C7/Game.cs @@ -138,9 +138,7 @@ public void processEngineMessages(GameData gameData) { } break; case MsgStartEffectAnimation mSEA: - int x, y; - gameData.map.tileIndexToCoords(mSEA.tileIndex, out x, out y); - Tile tile = gameData.map.tileAt(x, y); + Tile tile = gameData.map.tileAtIndex(mSEA.tileIndex); if (tile != Tile.NONE && controller.tileKnowledge.isTileKnown(tile)) animTracker.startAnimation(tile, mSEA.effect, mSEA.completionEvent, mSEA.ending); else { @@ -151,11 +149,17 @@ public void processEngineMessages(GameData gameData) { case MsgStartTurn mST: OnPlayerStartTurn(); break; + case MsgCityBuilt mBC: Tile cityTile = gameData.map.tiles[mBC.tileIndex]; City city = cityTile.cityAtTile; mapView.addCity(city, cityTile); break; + + case MsgTileDiscovered mTD: + Tile discoveredTile = gameData.map.tileAtIndex(mTD.tileIndex); + mapView.discoverTile(discoveredTile, controller.tileKnowledge); + break; } } } diff --git a/C7/Map/MapView.cs b/C7/Map/MapView.cs index 95d18630..79ce9213 100644 --- a/C7/Map/MapView.cs +++ b/C7/Map/MapView.cs @@ -504,26 +504,40 @@ private void updateBuildingLayer(Tile tile) { // updateFogOfWarLayer returns true if the tile is visible or // semi-visible, indicating other layers should be updated. - private bool updateFogOfWarLayer(Tile tile, TileKnowledge tk) { - if (!tk.isTileKnown(tile)) { - int sum = 0; - if (tk.isTileKnown(tile.neighbors[TileDirection.NORTH]) || tk.isTileKnown(tile.neighbors[TileDirection.NORTHWEST]) || tk.isTileKnown(tile.neighbors[TileDirection.NORTHEAST])) { - sum += 1 * 2; - } - if (tk.isTileKnown(tile.neighbors[TileDirection.WEST]) || tk.isTileKnown(tile.neighbors[TileDirection.NORTHWEST]) || tk.isTileKnown(tile.neighbors[TileDirection.SOUTHWEST])) { - sum += 3 * 2; - } - if (tk.isTileKnown(tile.neighbors[TileDirection.EAST]) || tk.isTileKnown(tile.neighbors[TileDirection.NORTHEAST]) || tk.isTileKnown(tile.neighbors[TileDirection.SOUTHEAST])) { - sum += 9 * 2; - } - if (tk.isTileKnown(tile.neighbors[TileDirection.SOUTH]) || tk.isTileKnown(tile.neighbors[TileDirection.SOUTHWEST]) || tk.isTileKnown(tile.neighbors[TileDirection.SOUTHEAST])) { - sum += 27 * 2; - } - setCell(Layer.FogOfWar, Atlas.FogOfWar, tile, new Vector2I(sum % 9, sum / 9)); - return sum != 0; // if the sum is not 0, parts of the tile may be visible - } - return true; // no fog of war, tile - } + + private static int fogOfWarIndex(Tile tile, TileKnowledge tk) { + if (tk.isTileKnown(tile)) { + return 0; + } + int sum = 0; + // HACK: edge tiles have missing directions in neighbors map + if (tile.neighbors.Values.Count != 8) { + return sum; + } + if (tk.isTileKnown(tile.neighbors[TileDirection.NORTH]) || tk.isTileKnown(tile.neighbors[TileDirection.NORTHWEST]) || tk.isTileKnown(tile.neighbors[TileDirection.NORTHEAST])) { + sum += 1 * 2; + } + if (tk.isTileKnown(tile.neighbors[TileDirection.WEST]) || tk.isTileKnown(tile.neighbors[TileDirection.NORTHWEST]) || tk.isTileKnown(tile.neighbors[TileDirection.SOUTHWEST])) { + sum += 3 * 2; + } + if (tk.isTileKnown(tile.neighbors[TileDirection.EAST]) || tk.isTileKnown(tile.neighbors[TileDirection.NORTHEAST]) || tk.isTileKnown(tile.neighbors[TileDirection.SOUTHEAST])) { + sum += 9 * 2; + } + if (tk.isTileKnown(tile.neighbors[TileDirection.SOUTH]) || tk.isTileKnown(tile.neighbors[TileDirection.SOUTHWEST]) || tk.isTileKnown(tile.neighbors[TileDirection.SOUTHEAST])) { + sum += 27 * 2; + } + return sum; + } + + private bool updateFogOfWarLayer(Tile tile, TileKnowledge tk) { + if (tk.isTileKnown(tile)) { + eraseCell(Layer.FogOfWar, tile); + return true; + } + int index = fogOfWarIndex(tile, tk); + setCell(Layer.FogOfWar, Atlas.FogOfWar, tile, new Vector2I(index % 9, index / 9)); + return index > 0; // partially visible + } public void updateTile(Tile tile, TileKnowledge tk) { if (tile == Tile.NONE || tile is null) { @@ -533,8 +547,8 @@ public void updateTile(Tile tile, TileKnowledge tk) { } if (tk is not null) { - bool isTileVisibile = updateFogOfWarLayer(tile, tk); - if (!isTileVisibile) { + bool visible = updateFogOfWarLayer(tile, tk); + if (!visible) { return; } } @@ -569,5 +583,17 @@ private void updateGridLayer() { tilemap.ClearLayer(Layer.Grid.Index()); } } + + public void discoverTile(Tile tile, TileKnowledge tk) { + HashSet update = new HashSet(tile.neighbors.Values); + foreach (Tile n in tile.neighbors.Values) { + foreach (Tile nn in n.neighbors.Values) { + update.Add(nn); + } + } + foreach (Tile t in update) { + updateTile(t, tk); + } + } } } diff --git a/C7/Map/TileSetLoader.cs b/C7/Map/TileSetLoader.cs index 2e60e0db..244134cc 100644 --- a/C7/Map/TileSetLoader.cs +++ b/C7/Map/TileSetLoader.cs @@ -1,7 +1,5 @@ using Godot; -using System; using System.Collections.Generic; -using System.Linq; namespace C7.Map { diff --git a/C7Engine/EntryPoints/MessageToUI.cs b/C7Engine/EntryPoints/MessageToUI.cs index 130152d6..7750863d 100644 --- a/C7Engine/EntryPoints/MessageToUI.cs +++ b/C7Engine/EntryPoints/MessageToUI.cs @@ -51,4 +51,12 @@ public MsgCityBuilt(Tile tile) { public class MsgStartTurn : MessageToUI {} + public class MsgTileDiscovered : MessageToUI { + public int tileIndex; + + public MsgTileDiscovered(Tile tile) { + this.tileIndex = EngineStorage.gameData.map.tileCoordsToIndex(tile.xCoordinate, tile.yCoordinate); + } + } + } diff --git a/C7Engine/MapUnitExtensions.cs b/C7Engine/MapUnitExtensions.cs index 47f2118e..f1e0d28d 100644 --- a/C7Engine/MapUnitExtensions.cs +++ b/C7Engine/MapUnitExtensions.cs @@ -274,7 +274,9 @@ public static void OnBeginTurn(this MapUnit unit) { public static void OnEnterTile(this MapUnit unit, Tile tile) { //Add to player knowledge of tiles - unit.owner.tileKnowledge.AddTilesToKnown(tile); + if (unit.owner.tileKnowledge.AddTilesToKnown(tile)) { + new MsgTileDiscovered(tile).send(); + } // Disperse barb camp if (tile.hasBarbarianCamp && (!unit.owner.isBarbarians)) { diff --git a/C7GameData/AIData/TileKnowledge.cs b/C7GameData/AIData/TileKnowledge.cs index cab2e024..a4db9f98 100644 --- a/C7GameData/AIData/TileKnowledge.cs +++ b/C7GameData/AIData/TileKnowledge.cs @@ -7,11 +7,12 @@ public class TileKnowledge HashSet knownTiles = new HashSet(); HashSet visibleTiles = new HashSet(); - public void AddTilesToKnown(Tile unitLocation) { - knownTiles.Add(unitLocation); + public bool AddTilesToKnown(Tile unitLocation) { + bool added = knownTiles.Add(unitLocation); foreach (Tile t in unitLocation.neighbors.Values) { - knownTiles.Add(t); + added |= knownTiles.Add(t); } + return added; } public bool isTileKnown(Tile t) { diff --git a/C7GameData/GameMap.cs b/C7GameData/GameMap.cs index 756c3905..a92fb8ca 100644 --- a/C7GameData/GameMap.cs +++ b/C7GameData/GameMap.cs @@ -117,10 +117,13 @@ public int wrapTileY(int y) public Tile tileAt(int x, int y) { - if (isTileAt(x, y)) - return tiles[tileCoordsToIndex(wrapTileX(x), wrapTileY(y))]; - else - return Tile.NONE; + return isTileAt(x, y) ? tiles[tileCoordsToIndex(wrapTileX(x), wrapTileY(y))] : Tile.NONE; + } + + public Tile tileAtIndex(int index) { + int x, y; + tileIndexToCoords(index, out x, out y); + return tileAt(x, y); } /** From fcf32a7db4bc683ccc27fc7b9d546a234c294c80 Mon Sep 17 00:00:00 2001 From: Paul Cento Date: Tue, 1 Aug 2023 16:14:57 +0200 Subject: [PATCH 43/60] z indices --- C7/Map/FogOfWarLayer.cs | 45 ----------------------------------------- C7/Map/MapView.cs | 12 +++++++++-- C7/Map/UnitSprites.cs | 7 +++---- 3 files changed, 13 insertions(+), 51 deletions(-) delete mode 100644 C7/Map/FogOfWarLayer.cs diff --git a/C7/Map/FogOfWarLayer.cs b/C7/Map/FogOfWarLayer.cs deleted file mode 100644 index 0ead7d18..00000000 --- a/C7/Map/FogOfWarLayer.cs +++ /dev/null @@ -1,45 +0,0 @@ -using C7GameData; -using ConvertCiv3Media; -using Godot; - -namespace C7.Map { - public partial class FogOfWarLayer { - - private readonly ImageTexture fogOfWarTexture; - private readonly Vector2 tileSize; - - public FogOfWarLayer() { - Pcx fogOfWarPcx = new Pcx(Util.Civ3MediaPath("Art/Terrain/FogOfWar.pcx")); - fogOfWarTexture = PCXToGodot.getPureAlphaFromPCX(fogOfWarPcx); - tileSize = fogOfWarTexture.GetSize() / 9; - } - - public void drawObject(GameData gameData, Tile tile, Vector2 tileCenter) { - Rect2 screenTarget = new Rect2(tileCenter - tileSize / 2, tileSize); - TileKnowledge tileKnowledge = gameData.GetHumanPlayers()[0].tileKnowledge; - //N.B. FogOfWar.pcx handles both totally unknown and fogged tiles, indexed in the same file. - //Hence the trinary math rather than the more commonplace binary. - if (!tileKnowledge.isTileKnown(tile)) { - int sum = 0; - if (tileKnowledge.isTileKnown(tile.neighbors[TileDirection.NORTH]) || tileKnowledge.isTileKnown(tile.neighbors[TileDirection.NORTHWEST]) || tileKnowledge.isTileKnown(tile.neighbors[TileDirection.NORTHEAST])) - sum += 1 * 2; - if (tileKnowledge.isTileKnown(tile.neighbors[TileDirection.WEST]) || tileKnowledge.isTileKnown(tile.neighbors[TileDirection.NORTHWEST]) || tileKnowledge.isTileKnown(tile.neighbors[TileDirection.SOUTHWEST])) - sum += 3 * 2; - if (tileKnowledge.isTileKnown(tile.neighbors[TileDirection.EAST]) || tileKnowledge.isTileKnown(tile.neighbors[TileDirection.NORTHEAST]) || tileKnowledge.isTileKnown(tile.neighbors[TileDirection.SOUTHEAST])) - sum += 9 * 2; - if (tileKnowledge.isTileKnown(tile.neighbors[TileDirection.SOUTH]) || tileKnowledge.isTileKnown(tile.neighbors[TileDirection.SOUTHWEST]) || tileKnowledge.isTileKnown(tile.neighbors[TileDirection.SOUTHEAST])) - sum += 27 * 2; - if (sum != 0) { - // DrawTextureRectRegion(fogOfWarTexture, screenTarget, getRect(sum)); - } - } - //do nothing if the tile is known (equiv to the lower-right) - } - - private Rect2 getRect(int sum) { - int row = sum / 9; - int col = sum % 9; - return new Rect2(col * tileSize.X, row * tileSize.Y, tileSize); - } - } -} diff --git a/C7/Map/MapView.cs b/C7/Map/MapView.cs index 79ce9213..169d4942 100644 --- a/C7/Map/MapView.cs +++ b/C7/Map/MapView.cs @@ -8,6 +8,14 @@ namespace C7.Map { + public static class MapZIndex { + public static readonly int Tiles = 10; + public static readonly int Cities = 20; + public static readonly int Cursor = 29; + public static readonly int Units = 30; + public static readonly int FogOfWar = 100; + } + public partial class MapView : Node2D { private string[,]terrain; private TileMap terrainTilemap; @@ -158,12 +166,12 @@ private void initializeTileMap() { if (layer != Layer.FogOfWar) { tilemap.SetLayerYSortEnabled(layer.Index(), true); } else { - tilemap.SetLayerZIndex(layer.Index(), 15); + tilemap.SetLayerZIndex(layer.Index(), MapZIndex.FogOfWar); } } } - tilemap.ZIndex = 10; // need to figure out a good way to order z indices + tilemap.ZIndex = MapZIndex.Tiles; AddChild(tilemap); AddChild(terrainTilemap); // AddChild(terrainTilemapShadow); diff --git a/C7/Map/UnitSprites.cs b/C7/Map/UnitSprites.cs index 29445465..247b421b 100644 --- a/C7/Map/UnitSprites.cs +++ b/C7/Map/UnitSprites.cs @@ -1,9 +1,8 @@ using System; -using System.Collections.Generic; using C7GameData; -using C7Engine; using ConvertCiv3Media; using Godot; +using C7.Map; // UnitSprite represents an animated unit. It's specific to a unit, action, and direction. // UnitSprite comprises two sprites: a base sprite and a civ color-tinted sprite. The @@ -12,7 +11,7 @@ // use a single instance of a material and UnitSprite use a per instance uniform public partial class UnitSprite : Node2D { - private readonly int unitAnimZIndex = 100; + private readonly int unitAnimZIndex = MapZIndex.Units; private readonly string unitShaderPath = "res://UnitTint.gdshader"; private readonly string unitColorShaderParameter = "tintColor"; private Shader unitShader; @@ -76,7 +75,7 @@ public partial class CursorSprite : Node2D { private readonly string animationPath = "Art/Animations/Cursor/Cursor.flc"; private readonly string animationName = "cursor"; private readonly double period = 2.5; - private readonly int cursorAnimZIndex = 50; + private readonly int cursorAnimZIndex = MapZIndex.Cursor; private AnimatedSprite2D sprite; private int frameCount; From 233c114b3d35203f80772b48fa48b126d3a30fb4 Mon Sep 17 00:00:00 2001 From: Paul Cento Date: Tue, 1 Aug 2023 16:17:00 +0200 Subject: [PATCH 44/60] use Z index readonly for city scene --- C7/Map/CityScene.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/C7/Map/CityScene.cs b/C7/Map/CityScene.cs index dd5579d3..ab47f2fb 100644 --- a/C7/Map/CityScene.cs +++ b/C7/Map/CityScene.cs @@ -13,7 +13,7 @@ public partial class CityScene : Sprite2D { private CityLabelScene cityLabelScene; public CityScene(City city, Tile tile) { - ZIndex = 20; + ZIndex = MapZIndex.Cities; cityLabelScene = new CityLabelScene(city, tile); //TODO: Generalize, support multiple city types, etc. From 00c21529174384b3ca3ba8a4e61ac62782b35068 Mon Sep 17 00:00:00 2001 From: Paul Cento Date: Sun, 18 Jun 2023 23:12:41 -0400 Subject: [PATCH 45/60] rendering terrain add Camera2D test drawing units in word coordinates wip simplify unit sprite test demo - AnimationManager could be simpler but trying to keep diff small enable pixel snapping - removes world seams render roads in tilemap add logic for drawing rails on tilemap - missing separate layer working rails layer simple abstraction for loading atlas sources add resources and clear cells when empty track net lines changed working rivers separate notion of layer and atlas in MapView add mountains get rid of incorrect y offsets (tiles are already centered) offset is necessary for oversized tiles... add volcanos single terrain overlay add full left skirt to terrain layer detect edges of map in world space fix bad bounds for forest tiles and use full resource tileset wip tile set loader so specific assets can be overwritten in the future add marsh to tilemap terrain overlay layer camera can center on tiles buildings on tilemap remove ported code load goodyhut pcx pixel perfect transforms dedupe code enable Y sort for each tilemap layer kinda working animations wip wip working sprite animations render best defender on top of unit stack significantly faster animation loop when only doing visible tiles draw cursor remove ported code --- C7/C7Game.tscn | 170 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 170 insertions(+) diff --git a/C7/C7Game.tscn b/C7/C7Game.tscn index 16b0e10e..cfd9cd31 100644 --- a/C7/C7Game.tscn +++ b/C7/C7Game.tscn @@ -67,6 +67,161 @@ script = ExtResource("11_pkhac") [node name="CanvasLayer" type="CanvasLayer" parent="."] +<<<<<<< HEAD +======= +[node name="GameStatus" type="MarginContainer" parent="CanvasLayer"] +anchors_preset = 3 +anchor_left = 1.0 +anchor_top = 1.0 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = -619.0 +offset_top = -357.0 +offset_right = -579.0 +offset_bottom = -317.0 +grow_horizontal = 0 +grow_vertical = 0 +script = ExtResource("7") + +[node name="SlideOutBar" type="Control" parent="CanvasLayer"] +layout_mode = 3 +anchors_preset = 11 +anchor_left = 1.0 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = 1024.0 +offset_top = 80.0 +offset_right = 1024.0 +offset_bottom = 728.0 +grow_horizontal = 0 +grow_vertical = 2 + +[node name="SlideToggle" type="Button" parent="CanvasLayer/SlideOutBar"] +layout_mode = 0 +offset_left = -124.27 +offset_right = -91.27 +offset_bottom = 20.0 +toggle_mode = true +text = "<->" + +[node name="VBoxContainer" type="VBoxContainer" parent="CanvasLayer/SlideOutBar"] +layout_mode = 1 +anchors_preset = -1 +anchor_right = 1.0 +anchor_bottom = 0.958 +offset_right = 1.0 +grow_horizontal = 0 +grow_vertical = 2 + +[node name="DownButton" type="Button" parent="CanvasLayer/SlideOutBar/VBoxContainer"] +layout_mode = 2 +text = "Down" + +[node name="RightButton" type="Button" parent="CanvasLayer/SlideOutBar/VBoxContainer"] +layout_mode = 2 +text = "Right" + +[node name="LeftButton" type="Button" parent="CanvasLayer/SlideOutBar/VBoxContainer"] +layout_mode = 2 +text = "Left" + +[node name="UpButton" type="Button" parent="CanvasLayer/SlideOutBar/VBoxContainer"] +layout_mode = 2 +text = "Up" + +[node name="Label" type="Label" parent="CanvasLayer/SlideOutBar/VBoxContainer"] +layout_mode = 2 +text = "Zoom" + +[node name="Zoom" type="VSlider" parent="CanvasLayer/SlideOutBar/VBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 1 +size_flags_vertical = 3 +min_value = 0.1 +max_value = 1.0 +step = 0.1 +value = 1.0 + +[node name="QuitButton" type="Button" parent="CanvasLayer/SlideOutBar/VBoxContainer"] +layout_mode = 2 +text = "Quit" + +[node name="AnimationPlayer" type="AnimationPlayer" parent="CanvasLayer/SlideOutBar"] +libraries = { +"": SubResource("AnimationLibrary_bowxq") +} + +[node name="ToolBar" type="Control" parent="CanvasLayer"] +layout_mode = 3 +anchors_preset = 10 +anchor_right = 1.0 +offset_left = -579.0 +offset_top = -317.0 +offset_right = -579.0 +offset_bottom = -317.0 +grow_horizontal = 2 +mouse_filter = 1 + +[node name="MarginContainer" type="MarginContainer" parent="CanvasLayer/ToolBar"] +layout_mode = 1 +anchors_preset = 10 +anchor_right = 1.0 +offset_bottom = 80.0 +grow_horizontal = 2 + +[node name="HBoxContainer" type="HBoxContainer" parent="CanvasLayer/ToolBar/MarginContainer"] +layout_mode = 2 + +[node name="MenuButton" type="TextureButton" parent="CanvasLayer/ToolBar/MarginContainer/HBoxContainer"] +layout_mode = 2 +script = ExtResource("4") + +[node name="CivilopediaButton" type="TextureButton" parent="CanvasLayer/ToolBar/MarginContainer/HBoxContainer"] +layout_mode = 2 +script = ExtResource("5") + +[node name="AdvisorButton" type="TextureButton" parent="CanvasLayer/ToolBar/MarginContainer/HBoxContainer"] +layout_mode = 2 +script = ExtResource("6") + +[node name="UiBarEndTurnButton" type="Button" parent="CanvasLayer/ToolBar/MarginContainer/HBoxContainer"] +layout_mode = 2 +text = "End Turn" + +[node name="UnitButtons" type="VBoxContainer" parent="CanvasLayer"] +anchors_preset = 12 +anchor_top = 1.0 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = -579.0 +offset_top = -429.0 +offset_right = -579.0 +offset_bottom = -317.0 +grow_horizontal = 2 +grow_vertical = 0 +size_flags_vertical = 0 +script = ExtResource("12") + +[node name="AdvancedUnitControls" type="HBoxContainer" parent="CanvasLayer/UnitButtons"] +layout_mode = 2 +size_flags_vertical = 0 +alignment = 1 + +[node name="RenameButton" type="TextureButton" parent="CanvasLayer/UnitButtons/AdvancedUnitControls"] +layout_mode = 2 +script = ExtResource("8") + +[node name="SpecializedUnitControls" type="HBoxContainer" parent="CanvasLayer/UnitButtons"] +layout_mode = 2 +size_flags_vertical = 0 +alignment = 1 + +[node name="PrimaryUnitControls" type="HBoxContainer" parent="CanvasLayer/UnitButtons"] +layout_mode = 2 +size_flags_vertical = 0 +alignment = 1 + +>>>>>>> 83be7c1 (rendering terrain) [node name="Advisor" type="CenterContainer" parent="CanvasLayer"] anchors_preset = 15 anchor_right = 1.0 @@ -255,8 +410,23 @@ libraries = { [connection signal="NoMoreAutoselectableUnits" from="." to="CanvasLayer/Control/GameStatus" method="OnNoMoreAutoselectableUnits"] [connection signal="NoMoreAutoselectableUnits" from="." to="CanvasLayer/Control/UnitButtons" method="OnNoMoreAutoselectableUnits"] [connection signal="ShowSpecificAdvisor" from="." to="CanvasLayer/Advisor" method="OnShowSpecificAdvisor"] +<<<<<<< HEAD [connection signal="TurnEnded" from="." to="CanvasLayer/Control/GameStatus" method="OnTurnEnded"] [connection signal="TurnStarted" from="." to="CanvasLayer/Control/GameStatus" method="OnTurnStarted"] +======= +[connection signal="TurnEnded" from="." to="CanvasLayer/GameStatus" method="OnTurnEnded"] +[connection signal="TurnStarted" from="." to="CanvasLayer/GameStatus" method="OnTurnStarted"] +[connection signal="BlinkyEndTurnButtonPressed" from="CanvasLayer/GameStatus" to="." method="OnPlayerEndTurn"] +[connection signal="toggled" from="CanvasLayer/SlideOutBar/SlideToggle" to="." method="_on_SlideToggle_toggled"] +[connection signal="pressed" from="CanvasLayer/SlideOutBar/VBoxContainer/DownButton" to="." method="_on_DownButton_pressed"] +[connection signal="pressed" from="CanvasLayer/SlideOutBar/VBoxContainer/RightButton" to="." method="_on_RightButton_pressed"] +[connection signal="pressed" from="CanvasLayer/SlideOutBar/VBoxContainer/LeftButton" to="." method="_on_LeftButton_pressed"] +[connection signal="pressed" from="CanvasLayer/SlideOutBar/VBoxContainer/UpButton" to="." method="_on_UpButton_pressed"] +[connection signal="value_changed" from="CanvasLayer/SlideOutBar/VBoxContainer/Zoom" to="." method="onSliderZoomChanged"] +[connection signal="pressed" from="CanvasLayer/SlideOutBar/VBoxContainer/QuitButton" to="." method="_on_QuitButton_pressed"] +[connection signal="pressed" from="CanvasLayer/ToolBar/MarginContainer/HBoxContainer/AdvisorButton" to="CanvasLayer/Advisor" method="ShowLatestAdvisor"] +[connection signal="pressed" from="CanvasLayer/ToolBar/MarginContainer/HBoxContainer/UiBarEndTurnButton" to="." method="_onEndTurnButtonPressed"] +>>>>>>> 83be7c1 (rendering terrain) [connection signal="BuildCity" from="CanvasLayer/PopupOverlay" to="." method="OnBuildCity"] [connection signal="HidePopup" from="CanvasLayer/PopupOverlay" to="CanvasLayer/PopupOverlay" method="OnHidePopup"] [connection signal="UnitDisbanded" from="CanvasLayer/PopupOverlay" to="." method="OnUnitDisbanded"] From e094e7200263b001796fc900879a2dc123496a38 Mon Sep 17 00:00:00 2001 From: Paul Cento Date: Wed, 2 Aug 2023 23:47:54 +0200 Subject: [PATCH 46/60] terrain and tiles wrap horizontally, not cities or units --- C7/Game.cs | 37 +++++++++++++++++++++++++++++++++++++ C7/Map/MapView.cs | 46 ++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 75 insertions(+), 8 deletions(-) diff --git a/C7/Game.cs b/C7/Game.cs index 3023dc1b..cd792236 100644 --- a/C7/Game.cs +++ b/C7/Game.cs @@ -173,7 +173,44 @@ public void updateAnimations(GameData gameData) { mapView.updateAnimations(); } + private bool onRight = false; + private bool onLeft = false; + + private void checkMapWrap() { + var w2d = camera.getVisibleWorld(); + + // detect leaving original map + if (!onRight && w2d.End.X >= mapView.worldEdgeRight) { + onRight = true; + mapView.setWrapSide(MapView.WrapSide.Right); + GD.Print("world edge right visible"); + } else if (onRight && w2d.End.X < mapView.worldEdgeRight) { + onRight = false; + GD.Print("world edge right no longer visible"); + } + if (!onLeft && w2d.Position.X <= mapView.worldEdgeLeft) { + onLeft = true; + mapView.setWrapSide(MapView.WrapSide.Left); + GD.Print("world edge left visible"); + } else if (onLeft && w2d.Position.X > mapView.worldEdgeLeft) { + onLeft = false; + GD.Print("world edge left no longer visible"); + } + + // detect teleporting back into original map + if (onRight && w2d.Position.X > mapView.worldEdgeRight) { + // completely off right side of map + GD.Print("jumping left back into map"); + camera.Translate(Vector2.Left * mapView.pixelWidth); + } else if (onLeft && w2d.End.X < mapView.worldEdgeLeft) { + // completely off left side of map + GD.Print("jumping right back into map"); + camera.Translate(Vector2.Right * mapView.pixelWidth); + } + } + public override void _Process(double delta) { + checkMapWrap(); processActions(); // TODO: Is it necessary to keep the game data mutex locked for this entire method? diff --git a/C7/Map/MapView.cs b/C7/Map/MapView.cs index 169d4942..873234d0 100644 --- a/C7/Map/MapView.cs +++ b/C7/Map/MapView.cs @@ -19,9 +19,10 @@ public static class MapZIndex { public partial class MapView : Node2D { private string[,]terrain; private TileMap terrainTilemap; - private TileMap terrainTilemapShadow; + private TileMap wrappingTerrainTilemap; private TileSet terrainTileset; private TileMap tilemap; + private TileMap wrappingTilemap; private TileSet tileset; private Vector2I tileSize = new Vector2I(128, 64); private ILogger log = LogManager.ForContext(); @@ -40,6 +41,7 @@ private void setShowGrid(bool value) { public void toggleGrid() { setShowGrid(!showGrid); } + public int pixelWidth {get; private set;} private Game game; private GameData data; private GameMap gameMap; @@ -145,36 +147,61 @@ public void updateAnimations() { } } + public enum WrapSide + { + Left, + Right, + } + + public void setWrapSide(WrapSide side) { + if (side == WrapSide.Left) { + wrappingTerrainTilemap.Position = terrainTilemap.Position + (Vector2I.Left * pixelWidth); + wrappingTilemap.Position = tilemap.Position + (Vector2I.Left * pixelWidth); + } else { + wrappingTerrainTilemap.Position = terrainTilemap.Position + (Vector2I.Right * pixelWidth); + wrappingTilemap.Position = tilemap.Position + (Vector2I.Right * pixelWidth); + } + } + private void initializeTileMap() { terrainTilemap = new TileMap(); - terrainTilemapShadow = new TileMap(); + wrappingTerrainTilemap = new TileMap(); terrainTileset = Civ3TerrainTileSet.Generate(); terrainTilemap.TileSet = terrainTileset; terrainTilemap.Position += Vector2I.Right * (tileSize.X / 2); - terrainTilemapShadow.TileSet = terrainTileset; - terrainTilemapShadow.Position = terrainTilemap.Position + (Vector2I.Left * tileSize.X * width); + wrappingTerrainTilemap.TileSet = terrainTileset; tilemap = new TileMap{ YSortEnabled = true }; + wrappingTilemap = new TileMap { YSortEnabled = true }; + tileset = TileSetLoader.LoadCiv3TileSet(); tilemap.TileSet = tileset; + wrappingTilemap.TileSet = tileset; // create tilemap layers foreach (Layer layer in Enum.GetValues(typeof(Layer))) { if (layer != Layer.Invalid) { tilemap.AddLayer(layer.Index()); - tilemap.SetLayerYSortEnabled(layer.Index(), true); + wrappingTilemap.AddLayer(layer.Index()); if (layer != Layer.FogOfWar) { tilemap.SetLayerYSortEnabled(layer.Index(), true); + wrappingTilemap.SetLayerYSortEnabled(layer.Index(), true); } else { tilemap.SetLayerZIndex(layer.Index(), MapZIndex.FogOfWar); + wrappingTilemap.SetLayerZIndex(layer.Index(), MapZIndex.FogOfWar); } } } - tilemap.ZIndex = MapZIndex.Tiles; + // put the wrapping tilemaps somewhere + setWrapSide(WrapSide.Right); + + tilemap.ZIndex = 10; // need to figure out a good way to order z indices + wrappingTilemap.ZIndex = 10; AddChild(tilemap); + AddChild(wrappingTilemap); AddChild(terrainTilemap); - // AddChild(terrainTilemapShadow); + AddChild(wrappingTerrainTilemap); } private void setTerrainTiles() { @@ -211,7 +238,7 @@ void lookupAndSetTerrainTile(int x, int y, int cellX, int cellY) { void setTerrainTile(Vector2I cell, int atlas, Vector2I texCoords) { terrainTilemap.SetCell(0, cell, atlas, texCoords); - terrainTilemapShadow.SetCell(0, cell, atlas, texCoords); + wrappingTerrainTilemap.SetCell(0, cell, atlas, texCoords); } private Vector2I stackedCoords(Tile tile) { @@ -235,6 +262,7 @@ public MapView(Game game, GameData data) { cursor = new CursorSprite(); AddChild(cursor); width = gameMap.numTilesWide / 2; + pixelWidth = width * tileSize.X; height = gameMap.numTilesTall; initializeTileMap(); terrain = new string[width, height]; @@ -276,10 +304,12 @@ private void setCell(Layer layer, Atlas atlas, Tile tile, Vector2I atlasCoords) log.Warning($"atlas id {atlas} does not have tile at {atlasCoords}"); } tilemap.SetCell(layer.Index(), stackedCoords(tile), atlas.Index(), atlasCoords); + wrappingTilemap.SetCell(layer.Index(), stackedCoords(tile), atlas.Index(), atlasCoords); } private void eraseCell(Layer layer, Tile tile) { tilemap.EraseCell(layer.Index(), stackedCoords(tile)); + wrappingTilemap.EraseCell(layer.Index(), stackedCoords(tile)); } private void updateRoadLayer(Tile tile, bool center) { From a41febe6cc7adbaa94bbb82384ba2d93fc82ad9e Mon Sep 17 00:00:00 2001 From: Paul Cento Date: Thu, 3 Aug 2023 00:01:34 +0200 Subject: [PATCH 47/60] clearer enum name, use enum values instead of booleans --- C7/Game.cs | 32 ++++++++++++++++++-------------- C7/Map/MapView.cs | 34 ++++++++++++++++++++-------------- 2 files changed, 38 insertions(+), 28 deletions(-) diff --git a/C7/Game.cs b/C7/Game.cs index cd792236..7cfc4d64 100644 --- a/C7/Game.cs +++ b/C7/Game.cs @@ -173,36 +173,40 @@ public void updateAnimations(GameData gameData) { mapView.updateAnimations(); } - private bool onRight = false; - private bool onLeft = false; + private HorizontalWrapState horizontalWrap; + // TODO: pass the camera's visible world rect into MapView and let MapView + // update its apperance... then MapView can move camera or return something + // indicating Game should move the camera? private void checkMapWrap() { var w2d = camera.getVisibleWorld(); // detect leaving original map - if (!onRight && w2d.End.X >= mapView.worldEdgeRight) { - onRight = true; - mapView.setWrapSide(MapView.WrapSide.Right); + // TODO: do we need to worry about both left and right sides of the + // map wrapping at the same time? (ie. on a huge monitor) + if (horizontalWrap != HorizontalWrapState.Right && w2d.End.X >= mapView.worldEdgeRight) { + horizontalWrap = HorizontalWrapState.Right; + mapView.setHorizontalWrap(horizontalWrap); GD.Print("world edge right visible"); - } else if (onRight && w2d.End.X < mapView.worldEdgeRight) { - onRight = false; + } else if (horizontalWrap == HorizontalWrapState.Right && w2d.End.X < mapView.worldEdgeRight) { + horizontalWrap = HorizontalWrapState.None; GD.Print("world edge right no longer visible"); } - if (!onLeft && w2d.Position.X <= mapView.worldEdgeLeft) { - onLeft = true; - mapView.setWrapSide(MapView.WrapSide.Left); + if (horizontalWrap != HorizontalWrapState.Left && w2d.Position.X <= mapView.worldEdgeLeft) { + horizontalWrap = HorizontalWrapState.Left; + mapView.setHorizontalWrap(horizontalWrap); GD.Print("world edge left visible"); - } else if (onLeft && w2d.Position.X > mapView.worldEdgeLeft) { - onLeft = false; + } else if (horizontalWrap == HorizontalWrapState.Left && w2d.Position.X > mapView.worldEdgeLeft) { + horizontalWrap = HorizontalWrapState.None; GD.Print("world edge left no longer visible"); } // detect teleporting back into original map - if (onRight && w2d.Position.X > mapView.worldEdgeRight) { + if (horizontalWrap == HorizontalWrapState.Right && w2d.Position.X > mapView.worldEdgeRight) { // completely off right side of map GD.Print("jumping left back into map"); camera.Translate(Vector2.Left * mapView.pixelWidth); - } else if (onLeft && w2d.End.X < mapView.worldEdgeLeft) { + } else if (horizontalWrap == HorizontalWrapState.Left && w2d.End.X < mapView.worldEdgeLeft) { // completely off left side of map GD.Print("jumping right back into map"); camera.Translate(Vector2.Right * mapView.pixelWidth); diff --git a/C7/Map/MapView.cs b/C7/Map/MapView.cs index 873234d0..d8ccb892 100644 --- a/C7/Map/MapView.cs +++ b/C7/Map/MapView.cs @@ -16,6 +16,12 @@ public static class MapZIndex { public static readonly int FogOfWar = 100; } + public enum HorizontalWrapState { + Left, // beyond left edge of the map is visible + Right, // beyond right edge of the map is visible + None, // camera is entirely over the map + } + public partial class MapView : Node2D { private string[,]terrain; private TileMap terrainTilemap; @@ -147,19 +153,20 @@ public void updateAnimations() { } } - public enum WrapSide - { - Left, - Right, - } - - public void setWrapSide(WrapSide side) { - if (side == WrapSide.Left) { - wrappingTerrainTilemap.Position = terrainTilemap.Position + (Vector2I.Left * pixelWidth); - wrappingTilemap.Position = tilemap.Position + (Vector2I.Left * pixelWidth); + public void setHorizontalWrap(HorizontalWrapState state) { + if (state == HorizontalWrapState.None) { + wrappingTerrainTilemap.Hide(); + wrappingTilemap.Hide(); } else { - wrappingTerrainTilemap.Position = terrainTilemap.Position + (Vector2I.Right * pixelWidth); - wrappingTilemap.Position = tilemap.Position + (Vector2I.Right * pixelWidth); + Vector2I offset = state switch { + HorizontalWrapState.Left => Vector2I.Left, + HorizontalWrapState.Right => Vector2I.Right, + _ => Vector2I.Right, // invalid but put them somewhere + } * pixelWidth; + wrappingTerrainTilemap.Show(); + wrappingTilemap.Show(); + wrappingTerrainTilemap.Position = terrainTilemap.Position + offset; + wrappingTilemap.Position = tilemap.Position + offset; } } @@ -193,8 +200,7 @@ private void initializeTileMap() { } } - // put the wrapping tilemaps somewhere - setWrapSide(WrapSide.Right); + setHorizontalWrap(HorizontalWrapState.None); tilemap.ZIndex = 10; // need to figure out a good way to order z indices wrappingTilemap.ZIndex = 10; From 5c2d5687cce59617f13ba56576f8adc450b54ee4 Mon Sep 17 00:00:00 2001 From: Paul Cento Date: Thu, 3 Aug 2023 00:32:24 +0200 Subject: [PATCH 48/60] wrap units --- C7/Map/MapView.cs | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/C7/Map/MapView.cs b/C7/Map/MapView.cs index d8ccb892..069c1a90 100644 --- a/C7/Map/MapView.cs +++ b/C7/Map/MapView.cs @@ -80,7 +80,7 @@ public void addCity(City city, Tile tile) { cityScenes.Add(city, scene); } - private void animateUnit(Tile tile, MapUnit unit) { + private void animateUnit(Tile tile, MapUnit unit, HorizontalWrapState wrap) { // TODO: simplify AnimationManager and drawing animations it is unnecessarily complex // - also investigate if the custom offset tracking and SetFrame can be replaced by // engine functionality @@ -99,8 +99,17 @@ private void animateUnit(Tile tile, MapUnit unit) { sprite.SetFrame(frame); sprite.Show(); + Vector2 wrapOffset = wrap switch { + HorizontalWrapState.Left => Vector2.Left, + HorizontalWrapState.Right => Vector2.Right, + _ => Vector2.Zero, + } * pixelWidth; + sprite.Translate(wrapOffset); + if (unit == game.CurrentlySelectedUnit) { - cursor.Position = position; + // TODO: just noticed cursor position maybe should not be + // on sprite position which has a potential offset? + cursor.Position = position + wrapOffset; cursor.Show(); } } @@ -126,15 +135,20 @@ private MapUnit selectUnitToDisplay(List units) { return selected ?? interesting ?? bestDefender; } - public List getVisibleTiles() { - List tiles = new List(); + public List<(Tile, HorizontalWrapState)> getVisibleTiles() { + List<(Tile, HorizontalWrapState)> tiles = new List<(Tile, HorizontalWrapState)>(); Rect2 bounds = game.camera.getVisibleWorld(); Vector2I topLeft = tilemap.LocalToMap(ToLocal(bounds.Position)); Vector2I bottomRight = tilemap.LocalToMap(ToLocal(bounds.End)); for (int x = topLeft.X - 1; x < bottomRight.X + 1; x++) { for (int y = topLeft.Y - 1; y < bottomRight.Y + 1; y++) { (int usX, int usY) = unstackedCoords(new Vector2I(x, y)); - tiles.Add(data.map.tileAt(usX, usY)); + HorizontalWrapState wrap = x switch { + _ when x < 0 => HorizontalWrapState.Left, + _ when x >= width => HorizontalWrapState.Right, + _ => HorizontalWrapState.None, + }; + tiles.Add((data.map.tileAt(usX, usY), wrap)); } } return tiles; @@ -145,11 +159,12 @@ public void updateAnimations() { s.Hide(); } cursor.Hide(); - foreach (Tile tile in getVisibleTiles()) { + foreach ((Tile tile, HorizontalWrapState wrap) in getVisibleTiles()) { MapUnit unit = selectUnitToDisplay(tile.unitsOnTile); if (unit != MapUnit.NONE) { - animateUnit(tile, unit); + animateUnit(tile, unit, wrap); } + // TODO: shift city scenes here? } } From 6802edd06393af1e507dc110a3da1e338a248bd5 Mon Sep 17 00:00:00 2001 From: Paul Cento Date: Thu, 3 Aug 2023 10:06:50 +0200 Subject: [PATCH 49/60] do wrap checks in Camera --- C7/C7Game.tscn | 176 +--------------------------------------- C7/Game.cs | 50 +----------- C7/Map/MapViewCamera.cs | 103 +++++++++++++++++++++++ C7/Map/notes.md | 14 ++++ C7/PlayerCamera.cs | 56 ------------- 5 files changed, 124 insertions(+), 275 deletions(-) create mode 100644 C7/Map/MapViewCamera.cs create mode 100644 C7/Map/notes.md delete mode 100644 C7/PlayerCamera.cs diff --git a/C7/C7Game.tscn b/C7/C7Game.tscn index cfd9cd31..e53baff9 100644 --- a/C7/C7Game.tscn +++ b/C7/C7Game.tscn @@ -1,6 +1,7 @@ [gd_scene load_steps=15 format=3 uid="uid://cldl5nk4n61m2"] [ext_resource type="Script" path="res://Game.cs" id="1"] +[ext_resource type="Script" path="res://Map/MapViewCamera.cs" id="2_xrlqh"] [ext_resource type="Script" path="res://UIElements/Advisors/Advisors.cs" id="3"] [ext_resource type="Script" path="res://UIElements/UpperLeftNav/MenuButton.cs" id="4"] [ext_resource type="Script" path="res://UIElements/UpperLeftNav/CivilopediaButton.cs" id="5"] @@ -9,7 +10,6 @@ [ext_resource type="Script" path="res://UIElements/UnitButtons/RenameButton.cs" id="8"] [ext_resource type="Script" path="res://UIElements/Popups/PopupOverlay.cs" id="9"] [ext_resource type="Theme" uid="uid://c1jpmssnhvodi" path="res://C7Theme.tres" id="10"] -[ext_resource type="Script" path="res://PlayerCamera.cs" id="11_pkhac"] [ext_resource type="Script" path="res://UIElements/UnitButtons/UnitButtons.cs" id="12"] [sub_resource type="Animation" id="2"] @@ -62,166 +62,11 @@ _data = { [node name="C7Game" type="Node2D"] script = ExtResource("1") -[node name="PlayerCamera" type="Camera2D" parent="."] -script = ExtResource("11_pkhac") +[node name="MapViewCamera" type="Camera2D" parent="."] +script = ExtResource("2_xrlqh") [node name="CanvasLayer" type="CanvasLayer" parent="."] -<<<<<<< HEAD -======= -[node name="GameStatus" type="MarginContainer" parent="CanvasLayer"] -anchors_preset = 3 -anchor_left = 1.0 -anchor_top = 1.0 -anchor_right = 1.0 -anchor_bottom = 1.0 -offset_left = -619.0 -offset_top = -357.0 -offset_right = -579.0 -offset_bottom = -317.0 -grow_horizontal = 0 -grow_vertical = 0 -script = ExtResource("7") - -[node name="SlideOutBar" type="Control" parent="CanvasLayer"] -layout_mode = 3 -anchors_preset = 11 -anchor_left = 1.0 -anchor_right = 1.0 -anchor_bottom = 1.0 -offset_left = 1024.0 -offset_top = 80.0 -offset_right = 1024.0 -offset_bottom = 728.0 -grow_horizontal = 0 -grow_vertical = 2 - -[node name="SlideToggle" type="Button" parent="CanvasLayer/SlideOutBar"] -layout_mode = 0 -offset_left = -124.27 -offset_right = -91.27 -offset_bottom = 20.0 -toggle_mode = true -text = "<->" - -[node name="VBoxContainer" type="VBoxContainer" parent="CanvasLayer/SlideOutBar"] -layout_mode = 1 -anchors_preset = -1 -anchor_right = 1.0 -anchor_bottom = 0.958 -offset_right = 1.0 -grow_horizontal = 0 -grow_vertical = 2 - -[node name="DownButton" type="Button" parent="CanvasLayer/SlideOutBar/VBoxContainer"] -layout_mode = 2 -text = "Down" - -[node name="RightButton" type="Button" parent="CanvasLayer/SlideOutBar/VBoxContainer"] -layout_mode = 2 -text = "Right" - -[node name="LeftButton" type="Button" parent="CanvasLayer/SlideOutBar/VBoxContainer"] -layout_mode = 2 -text = "Left" - -[node name="UpButton" type="Button" parent="CanvasLayer/SlideOutBar/VBoxContainer"] -layout_mode = 2 -text = "Up" - -[node name="Label" type="Label" parent="CanvasLayer/SlideOutBar/VBoxContainer"] -layout_mode = 2 -text = "Zoom" - -[node name="Zoom" type="VSlider" parent="CanvasLayer/SlideOutBar/VBoxContainer"] -layout_mode = 2 -size_flags_horizontal = 1 -size_flags_vertical = 3 -min_value = 0.1 -max_value = 1.0 -step = 0.1 -value = 1.0 - -[node name="QuitButton" type="Button" parent="CanvasLayer/SlideOutBar/VBoxContainer"] -layout_mode = 2 -text = "Quit" - -[node name="AnimationPlayer" type="AnimationPlayer" parent="CanvasLayer/SlideOutBar"] -libraries = { -"": SubResource("AnimationLibrary_bowxq") -} - -[node name="ToolBar" type="Control" parent="CanvasLayer"] -layout_mode = 3 -anchors_preset = 10 -anchor_right = 1.0 -offset_left = -579.0 -offset_top = -317.0 -offset_right = -579.0 -offset_bottom = -317.0 -grow_horizontal = 2 -mouse_filter = 1 - -[node name="MarginContainer" type="MarginContainer" parent="CanvasLayer/ToolBar"] -layout_mode = 1 -anchors_preset = 10 -anchor_right = 1.0 -offset_bottom = 80.0 -grow_horizontal = 2 - -[node name="HBoxContainer" type="HBoxContainer" parent="CanvasLayer/ToolBar/MarginContainer"] -layout_mode = 2 - -[node name="MenuButton" type="TextureButton" parent="CanvasLayer/ToolBar/MarginContainer/HBoxContainer"] -layout_mode = 2 -script = ExtResource("4") - -[node name="CivilopediaButton" type="TextureButton" parent="CanvasLayer/ToolBar/MarginContainer/HBoxContainer"] -layout_mode = 2 -script = ExtResource("5") - -[node name="AdvisorButton" type="TextureButton" parent="CanvasLayer/ToolBar/MarginContainer/HBoxContainer"] -layout_mode = 2 -script = ExtResource("6") - -[node name="UiBarEndTurnButton" type="Button" parent="CanvasLayer/ToolBar/MarginContainer/HBoxContainer"] -layout_mode = 2 -text = "End Turn" - -[node name="UnitButtons" type="VBoxContainer" parent="CanvasLayer"] -anchors_preset = 12 -anchor_top = 1.0 -anchor_right = 1.0 -anchor_bottom = 1.0 -offset_left = -579.0 -offset_top = -429.0 -offset_right = -579.0 -offset_bottom = -317.0 -grow_horizontal = 2 -grow_vertical = 0 -size_flags_vertical = 0 -script = ExtResource("12") - -[node name="AdvancedUnitControls" type="HBoxContainer" parent="CanvasLayer/UnitButtons"] -layout_mode = 2 -size_flags_vertical = 0 -alignment = 1 - -[node name="RenameButton" type="TextureButton" parent="CanvasLayer/UnitButtons/AdvancedUnitControls"] -layout_mode = 2 -script = ExtResource("8") - -[node name="SpecializedUnitControls" type="HBoxContainer" parent="CanvasLayer/UnitButtons"] -layout_mode = 2 -size_flags_vertical = 0 -alignment = 1 - -[node name="PrimaryUnitControls" type="HBoxContainer" parent="CanvasLayer/UnitButtons"] -layout_mode = 2 -size_flags_vertical = 0 -alignment = 1 - ->>>>>>> 83be7c1 (rendering terrain) [node name="Advisor" type="CenterContainer" parent="CanvasLayer"] anchors_preset = 15 anchor_right = 1.0 @@ -410,23 +255,8 @@ libraries = { [connection signal="NoMoreAutoselectableUnits" from="." to="CanvasLayer/Control/GameStatus" method="OnNoMoreAutoselectableUnits"] [connection signal="NoMoreAutoselectableUnits" from="." to="CanvasLayer/Control/UnitButtons" method="OnNoMoreAutoselectableUnits"] [connection signal="ShowSpecificAdvisor" from="." to="CanvasLayer/Advisor" method="OnShowSpecificAdvisor"] -<<<<<<< HEAD [connection signal="TurnEnded" from="." to="CanvasLayer/Control/GameStatus" method="OnTurnEnded"] [connection signal="TurnStarted" from="." to="CanvasLayer/Control/GameStatus" method="OnTurnStarted"] -======= -[connection signal="TurnEnded" from="." to="CanvasLayer/GameStatus" method="OnTurnEnded"] -[connection signal="TurnStarted" from="." to="CanvasLayer/GameStatus" method="OnTurnStarted"] -[connection signal="BlinkyEndTurnButtonPressed" from="CanvasLayer/GameStatus" to="." method="OnPlayerEndTurn"] -[connection signal="toggled" from="CanvasLayer/SlideOutBar/SlideToggle" to="." method="_on_SlideToggle_toggled"] -[connection signal="pressed" from="CanvasLayer/SlideOutBar/VBoxContainer/DownButton" to="." method="_on_DownButton_pressed"] -[connection signal="pressed" from="CanvasLayer/SlideOutBar/VBoxContainer/RightButton" to="." method="_on_RightButton_pressed"] -[connection signal="pressed" from="CanvasLayer/SlideOutBar/VBoxContainer/LeftButton" to="." method="_on_LeftButton_pressed"] -[connection signal="pressed" from="CanvasLayer/SlideOutBar/VBoxContainer/UpButton" to="." method="_on_UpButton_pressed"] -[connection signal="value_changed" from="CanvasLayer/SlideOutBar/VBoxContainer/Zoom" to="." method="onSliderZoomChanged"] -[connection signal="pressed" from="CanvasLayer/SlideOutBar/VBoxContainer/QuitButton" to="." method="_on_QuitButton_pressed"] -[connection signal="pressed" from="CanvasLayer/ToolBar/MarginContainer/HBoxContainer/AdvisorButton" to="CanvasLayer/Advisor" method="ShowLatestAdvisor"] -[connection signal="pressed" from="CanvasLayer/ToolBar/MarginContainer/HBoxContainer/UiBarEndTurnButton" to="." method="_onEndTurnButtonPressed"] ->>>>>>> 83be7c1 (rendering terrain) [connection signal="BuildCity" from="CanvasLayer/PopupOverlay" to="." method="OnBuildCity"] [connection signal="HidePopup" from="CanvasLayer/PopupOverlay" to="CanvasLayer/PopupOverlay" method="OnHidePopup"] [connection signal="UnitDisbanded" from="CanvasLayer/PopupOverlay" to="." method="OnUnitDisbanded"] diff --git a/C7/Game.cs b/C7/Game.cs index 7cfc4d64..b64ea34a 100644 --- a/C7/Game.cs +++ b/C7/Game.cs @@ -44,7 +44,7 @@ enum GameState { Stopwatch loadTimer = new Stopwatch(); GlobalSingleton Global; - public PlayerCamera camera; + public MapViewCamera camera; bool errorOnLoad = false; public override void _EnterTree() { @@ -65,8 +65,7 @@ public override void _Ready() { controller = CreateGame.createGame(Global.LoadGamePath, Global.DefaultBicPath); // Spawns engine thread Global.ResetLoadGamePath(); - - camera = GetNode("PlayerCamera") as PlayerCamera; + camera = GetNode("MapViewCamera"); using (var gameDataAccess = new UIGameDataAccess()) { GameMap map = gameDataAccess.gameData.map; @@ -74,6 +73,8 @@ public override void _Ready() { log.Debug("RelativeModPath ", map.RelativeModPath); mapView = new MapView(this, gameDataAccess.gameData); + AddChild(mapView); + camera.attachToMapView(mapView); // Set initial camera location. If the UI controller has any cities, focus on their capital. Otherwise, focus on their // starting settler. @@ -90,8 +91,6 @@ public override void _Ready() { } } - AddChild(mapView); - Toolbar = GetNode("CanvasLayer/Control/ToolBar/MarginContainer/HBoxContainer"); // Hide slideout bar on startup @@ -173,48 +172,7 @@ public void updateAnimations(GameData gameData) { mapView.updateAnimations(); } - private HorizontalWrapState horizontalWrap; - - // TODO: pass the camera's visible world rect into MapView and let MapView - // update its apperance... then MapView can move camera or return something - // indicating Game should move the camera? - private void checkMapWrap() { - var w2d = camera.getVisibleWorld(); - - // detect leaving original map - // TODO: do we need to worry about both left and right sides of the - // map wrapping at the same time? (ie. on a huge monitor) - if (horizontalWrap != HorizontalWrapState.Right && w2d.End.X >= mapView.worldEdgeRight) { - horizontalWrap = HorizontalWrapState.Right; - mapView.setHorizontalWrap(horizontalWrap); - GD.Print("world edge right visible"); - } else if (horizontalWrap == HorizontalWrapState.Right && w2d.End.X < mapView.worldEdgeRight) { - horizontalWrap = HorizontalWrapState.None; - GD.Print("world edge right no longer visible"); - } - if (horizontalWrap != HorizontalWrapState.Left && w2d.Position.X <= mapView.worldEdgeLeft) { - horizontalWrap = HorizontalWrapState.Left; - mapView.setHorizontalWrap(horizontalWrap); - GD.Print("world edge left visible"); - } else if (horizontalWrap == HorizontalWrapState.Left && w2d.Position.X > mapView.worldEdgeLeft) { - horizontalWrap = HorizontalWrapState.None; - GD.Print("world edge left no longer visible"); - } - - // detect teleporting back into original map - if (horizontalWrap == HorizontalWrapState.Right && w2d.Position.X > mapView.worldEdgeRight) { - // completely off right side of map - GD.Print("jumping left back into map"); - camera.Translate(Vector2.Left * mapView.pixelWidth); - } else if (horizontalWrap == HorizontalWrapState.Left && w2d.End.X < mapView.worldEdgeLeft) { - // completely off left side of map - GD.Print("jumping right back into map"); - camera.Translate(Vector2.Right * mapView.pixelWidth); - } - } - public override void _Process(double delta) { - checkMapWrap(); processActions(); // TODO: Is it necessary to keep the game data mutex locked for this entire method? diff --git a/C7/Map/MapViewCamera.cs b/C7/Map/MapViewCamera.cs new file mode 100644 index 00000000..81660cab --- /dev/null +++ b/C7/Map/MapViewCamera.cs @@ -0,0 +1,103 @@ +using Godot; +using C7GameData; + +namespace C7.Map { + + public partial class MapViewCamera : Camera2D { + private readonly float maxZoom = 3.0f; + private readonly float minZoom = 0.2f; + public float zoomFactor { get; private set; } = 1.0f; + private MapView map; + private HorizontalWrapState hwrap = HorizontalWrapState.None; + + public void attachToMapView(MapView map) { + this.map = map; + } + + public override void _Ready() { + base._Ready(); + scaleZoom(zoomFactor); + } + + public void scaleZoom(float factor) { + zoomFactor = zoomFactor * factor; + zoomFactor = Mathf.Clamp(zoomFactor, minZoom, maxZoom); + Zoom = Vector2.One * zoomFactor; + checkWorldWrap(); + } + + public void setZoom(float factor) { + zoomFactor = Mathf.Clamp(factor, minZoom, maxZoom); + Zoom = Vector2.One * zoomFactor; + checkWorldWrap(); + } + + private void checkWorldWrap() { + if (map is null) { + return; + } + Rect2 visible = getVisibleWorld(); + float rhs = visible.End.X; + float lhs = visible.Position.X; + if (hwrap != HorizontalWrapState.Right && rhs >= map.worldEdgeRight) { + hwrap = HorizontalWrapState.Right; + map.setHorizontalWrap(hwrap); + } else if (hwrap == HorizontalWrapState.Right && rhs < map.worldEdgeRight) { + hwrap = HorizontalWrapState.None; + } + if (hwrap != HorizontalWrapState.Left && lhs <= map.worldEdgeLeft) { + hwrap = HorizontalWrapState.Left; + map.setHorizontalWrap(hwrap); + } else if (hwrap == HorizontalWrapState.Left && lhs > map.worldEdgeLeft) { + hwrap = HorizontalWrapState.None; + } + + // detect jumping back into original map + if (hwrap == HorizontalWrapState.Right && lhs > map.worldEdgeRight) { + // completely off right side of map + map.setHorizontalWrap(HorizontalWrapState.Left); // will be on left edge after jump + Translate(Vector2.Left * map.pixelWidth); + } else if (hwrap == HorizontalWrapState.Left && rhs < map.worldEdgeLeft) { + // completely off left side of map + map.setHorizontalWrap(HorizontalWrapState.Right); // will be on right edge after jump + Translate(Vector2.Right * map.pixelWidth); + } + + } + + public void setPosition(Vector2 position) { + Position = position; + checkWorldWrap(); + } + + public override void _UnhandledInput(InputEvent @event) { + switch (@event) { + case InputEventMouseMotion mouseDrag when mouseDrag.ButtonMask == MouseButtonMask.Left: + setPosition(Position - mouseDrag.Relative / Zoom); + break; + case InputEventMagnifyGesture magnifyGesture: + scaleZoom(magnifyGesture.Factor); + break; + } + } + + public Rect2 getVisibleWorld() { + Transform2D vpToGlobal = (GetViewport().GlobalCanvasTransform * GetCanvasTransform()).AffineInverse(); + return vpToGlobal * GetViewportRect(); + } + + public void centerOnTile(Tile tile, MapView map) { + Vector2 target = map.tileToLocal(tile); + setPosition(target); + } + + public bool isTileInView(Tile tile, MapView map) { + Rect2 visible = getVisibleWorld(); + Vector2 target = map.tileToLocal(tile); + float size = 30; + target -= Vector2.One * (size / 2); + Rect2 boundingBox = new Rect2(target, size, size); + return visible.Encloses(boundingBox); + } + } +} diff --git a/C7/Map/notes.md b/C7/Map/notes.md new file mode 100644 index 00000000..09422529 --- /dev/null +++ b/C7/Map/notes.md @@ -0,0 +1,14 @@ +# state +currently the new MapView is stateful, for example when a city is built: + +build city in ui -> msg to engine -> city added to GameData -> msg to ui -> Game calls MapView.addCity + +This is hard to manage because Game must make the correct calls into MapView to ensure MapView is in sync with GameData. This pattern of updating MapView is also wherein lies the performance boost, for example when a road is built: MapView calculates what road texture to use, and it is added to TileMap. This is only done once when the road is built, then Godot manages drawing the TileMap contents instead of doing it every frame in c#. + +I think the current city approach will become too complex as more elements must be rendered, and will introduce bugs where MapView does not reflect GameData. Potential compromises: + +1. When GameData changes (the game map changes), recalculate everything in MapView - expensive, but not every frame. This may be worse than the non-TileMap implementation because updating TileMap might not be cheap... +2. When GameData changes, determine all tiles that could possibly have changed appearance (ie. creating or destroying a road changes the appearance of that tile and 0 or more of its neighbors visually, so recalculate everything for those 9 tiles) - this is roughly speaking what updateTile currently does for all things rendered in the TileMap (currently everything except for cities, units, and terrain which has its own TileMap instance). + +# wrapping +the easiest way to implement wrapping is to have 2 copies of the tilemap and translate the second copy to wherever the camera is "hanging" over the edge of the main tilemap. Depending on how expensive this is (mainly in memory consumption? need to profile) a better approach may be to split the TileMap in half (left/right for horizontally wrapping maps, top/bottom for vertically) or in four (corners for maps that wrap both ways) and move them around to cover the entire screen. neither approach accounts for the edge case where the camera is zoomed out far enough / display large enough that tiles are visible multiple times on screen. diff --git a/C7/PlayerCamera.cs b/C7/PlayerCamera.cs deleted file mode 100644 index 047de459..00000000 --- a/C7/PlayerCamera.cs +++ /dev/null @@ -1,56 +0,0 @@ -using Godot; -using C7.Map; -using C7GameData; - -public partial class PlayerCamera : Camera2D -{ - private readonly float maxZoom = 3.0f; - private readonly float minZoom = 0.2f; - public float zoomFactor {get; private set; } = 1.0f; - - public override void _Ready() { - base._Ready(); - scaleZoom(zoomFactor); - } - - public void scaleZoom(float factor) { - zoomFactor = zoomFactor * factor; - zoomFactor = Mathf.Clamp(zoomFactor, minZoom, maxZoom); - Zoom = Vector2.One * zoomFactor; - } - - public void setZoom(float factor) { - zoomFactor = Mathf.Clamp(factor, minZoom, maxZoom); - Zoom = Vector2.One * zoomFactor; - } - - public override void _UnhandledInput(InputEvent @event) { - switch (@event) { - case InputEventMouseMotion mouseDrag when mouseDrag.ButtonMask == MouseButtonMask.Left: - Position -= mouseDrag.Relative / Zoom; - break; - case InputEventMagnifyGesture magnifyGesture: - scaleZoom(magnifyGesture.Factor); - break; - } - } - - public Rect2 getVisibleWorld() { - Transform2D vpToGlobal = (GetViewport().GlobalCanvasTransform * GetCanvasTransform()).AffineInverse(); - return vpToGlobal * GetViewportRect(); - } - - public void centerOnTile(Tile tile, MapView map) { - Vector2 target = map.tileToLocal(tile); - Position = target; - } - - public bool isTileInView(Tile tile, MapView map) { - Rect2 visible = getVisibleWorld(); - Vector2 target = map.tileToLocal(tile); - float size = 30; - target -= Vector2.One * (size / 2); - Rect2 boundingBox = new Rect2(target, size, size); - return visible.Encloses(boundingBox); - } -} From 42cbaf3e507df6b1a60b572c659fb9e495f36fa3 Mon Sep 17 00:00:00 2001 From: Paul Cento Date: Thu, 3 Aug 2023 10:18:47 +0200 Subject: [PATCH 50/60] wrap horizontally with fewer map jumps --- C7/Map/MapView.cs | 2 ++ C7/Map/MapViewCamera.cs | 38 ++++++++++++++++++++------------------ 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/C7/Map/MapView.cs b/C7/Map/MapView.cs index 069c1a90..eab1654d 100644 --- a/C7/Map/MapView.cs +++ b/C7/Map/MapView.cs @@ -32,6 +32,7 @@ public partial class MapView : Node2D { private TileSet tileset; private Vector2I tileSize = new Vector2I(128, 64); private ILogger log = LogManager.ForContext(); + public bool wrapHorizontally {get; private set;} public int worldEdgeRight {get; private set;} public int worldEdgeLeft {get; private set;} private int width; @@ -286,6 +287,7 @@ public MapView(Game game, GameData data) { pixelWidth = width * tileSize.X; height = gameMap.numTilesTall; initializeTileMap(); + wrapHorizontally = gameMap.wrapHorizontally; terrain = new string[width, height]; worldEdgeRight = (int)ToGlobal(tilemap.MapToLocal(new Vector2I(width - 1, 1))).X + tileSize.X / 2; worldEdgeLeft = (int)ToGlobal(tilemap.MapToLocal(new Vector2I(0, 0))).X - tileSize.X / 2; diff --git a/C7/Map/MapViewCamera.cs b/C7/Map/MapViewCamera.cs index 81660cab..65144692 100644 --- a/C7/Map/MapViewCamera.cs +++ b/C7/Map/MapViewCamera.cs @@ -3,6 +3,13 @@ namespace C7.Map { + // MapViewCamera position and zoom should only be modified through the + // following methods: + // - scaleZoom + // - setZoom + // - setPosition + // This is because these methods will ensure that MapViewCamera handles + // world wrapping on the MapView automatically. public partial class MapViewCamera : Camera2D { private readonly float maxZoom = 3.0f; private readonly float minZoom = 0.2f; @@ -32,8 +39,13 @@ public void setZoom(float factor) { checkWorldWrap(); } + public void setPosition(Vector2 position) { + Position = position; + checkWorldWrap(); + } + private void checkWorldWrap() { - if (map is null) { + if (map is null || !map.wrapHorizontally) { return; } Rect2 visible = getVisibleWorld(); @@ -41,33 +53,23 @@ private void checkWorldWrap() { float lhs = visible.Position.X; if (hwrap != HorizontalWrapState.Right && rhs >= map.worldEdgeRight) { hwrap = HorizontalWrapState.Right; - map.setHorizontalWrap(hwrap); + map.setHorizontalWrap(hwrap); // move wrapping map } else if (hwrap == HorizontalWrapState.Right && rhs < map.worldEdgeRight) { hwrap = HorizontalWrapState.None; } if (hwrap != HorizontalWrapState.Left && lhs <= map.worldEdgeLeft) { hwrap = HorizontalWrapState.Left; - map.setHorizontalWrap(hwrap); + map.setHorizontalWrap(hwrap); // move wrapping map } else if (hwrap == HorizontalWrapState.Left && lhs > map.worldEdgeLeft) { hwrap = HorizontalWrapState.None; } - // detect jumping back into original map - if (hwrap == HorizontalWrapState.Right && lhs > map.worldEdgeRight) { - // completely off right side of map - map.setHorizontalWrap(HorizontalWrapState.Left); // will be on left edge after jump - Translate(Vector2.Left * map.pixelWidth); - } else if (hwrap == HorizontalWrapState.Left && rhs < map.worldEdgeLeft) { - // completely off left side of map - map.setHorizontalWrap(HorizontalWrapState.Right); // will be on right edge after jump - Translate(Vector2.Right * map.pixelWidth); + // jump back into original map + if (hwrap == HorizontalWrapState.Right && rhs >= map.worldEdgeRight + map.pixelWidth) { + Translate(Vector2.Left * map.pixelWidth); // at rhs of wrapping map + } else if (hwrap == HorizontalWrapState.Left && lhs <= map.worldEdgeLeft - map.pixelWidth) { + Translate(Vector2.Right * map.pixelWidth); // at lhs of wrapping map } - - } - - public void setPosition(Vector2 position) { - Position = position; - checkWorldWrap(); } public override void _UnhandledInput(InputEvent @event) { From e565a55ca66e94734732037e1b35797204e7f0d3 Mon Sep 17 00:00:00 2001 From: Paul Cento Date: Thu, 3 Aug 2023 10:24:05 +0200 Subject: [PATCH 51/60] horizontally wrap cities --- C7/Map/MapView.cs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/C7/Map/MapView.cs b/C7/Map/MapView.cs index eab1654d..d390fb4e 100644 --- a/C7/Map/MapView.cs +++ b/C7/Map/MapView.cs @@ -54,7 +54,7 @@ public void toggleGrid() { private GameMap gameMap; private Dictionary unitSprites = new Dictionary(); - private Dictionary cityScenes = new Dictionary(); + private Dictionary cityScenes = new Dictionary(); private CursorSprite cursor; private UnitSprite spriteFor(MapUnit unit) { @@ -78,7 +78,7 @@ public void addCity(City city, Tile tile) { CityScene scene = new CityScene(city, tile); scene.Position = tilemap.MapToLocal(stackedCoords(tile)); AddChild(scene); - cityScenes.Add(city, scene); + cityScenes.Add(tile, scene); } private void animateUnit(Tile tile, MapUnit unit, HorizontalWrapState wrap) { @@ -165,7 +165,17 @@ public void updateAnimations() { if (unit != MapUnit.NONE) { animateUnit(tile, unit, wrap); } - // TODO: shift city scenes here? + if (cityScenes.ContainsKey(tile)) { + CityScene scene = cityScenes[tile]; + Vector2 position = tilemap.MapToLocal(stackedCoords(tile)); + Vector2 wrapOffset = wrap switch { + HorizontalWrapState.Left => Vector2.Left, + HorizontalWrapState.Right => Vector2.Right, + _ => Vector2.Zero, + } * pixelWidth; + position += wrapOffset; + scene.Position = position; + } } } From 062391cd691230844206cf5dc94d8a82f8718264 Mon Sep 17 00:00:00 2001 From: Paul Cento Date: Thu, 3 Aug 2023 10:27:01 +0200 Subject: [PATCH 52/60] dry up wrap offset code --- C7/Map/MapView.cs | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/C7/Map/MapView.cs b/C7/Map/MapView.cs index d390fb4e..4dc04786 100644 --- a/C7/Map/MapView.cs +++ b/C7/Map/MapView.cs @@ -81,6 +81,14 @@ public void addCity(City city, Tile tile) { cityScenes.Add(tile, scene); } + private Vector2I horizontalWrapOffset(HorizontalWrapState wrap) { + return wrap switch { + HorizontalWrapState.Left => Vector2I.Left, + HorizontalWrapState.Right => Vector2I.Right, + _ => Vector2I.Zero, + } * pixelWidth; + } + private void animateUnit(Tile tile, MapUnit unit, HorizontalWrapState wrap) { // TODO: simplify AnimationManager and drawing animations it is unnecessarily complex // - also investigate if the custom offset tracking and SetFrame can be replaced by @@ -100,11 +108,7 @@ private void animateUnit(Tile tile, MapUnit unit, HorizontalWrapState wrap) { sprite.SetFrame(frame); sprite.Show(); - Vector2 wrapOffset = wrap switch { - HorizontalWrapState.Left => Vector2.Left, - HorizontalWrapState.Right => Vector2.Right, - _ => Vector2.Zero, - } * pixelWidth; + Vector2 wrapOffset = horizontalWrapOffset(wrap); sprite.Translate(wrapOffset); if (unit == game.CurrentlySelectedUnit) { @@ -167,13 +171,7 @@ public void updateAnimations() { } if (cityScenes.ContainsKey(tile)) { CityScene scene = cityScenes[tile]; - Vector2 position = tilemap.MapToLocal(stackedCoords(tile)); - Vector2 wrapOffset = wrap switch { - HorizontalWrapState.Left => Vector2.Left, - HorizontalWrapState.Right => Vector2.Right, - _ => Vector2.Zero, - } * pixelWidth; - position += wrapOffset; + Vector2 position = tilemap.MapToLocal(stackedCoords(tile)) + horizontalWrapOffset(wrap); scene.Position = position; } } @@ -184,11 +182,7 @@ public void setHorizontalWrap(HorizontalWrapState state) { wrappingTerrainTilemap.Hide(); wrappingTilemap.Hide(); } else { - Vector2I offset = state switch { - HorizontalWrapState.Left => Vector2I.Left, - HorizontalWrapState.Right => Vector2I.Right, - _ => Vector2I.Right, // invalid but put them somewhere - } * pixelWidth; + Vector2I offset = horizontalWrapOffset(state); wrappingTerrainTilemap.Show(); wrappingTilemap.Show(); wrappingTerrainTilemap.Position = terrainTilemap.Position + offset; From bfed72f8cdf249a87dcc94e67de1d4dbf0534875 Mon Sep 17 00:00:00 2001 From: Paul Cento Date: Thu, 3 Aug 2023 11:23:31 +0200 Subject: [PATCH 53/60] clean up --- C7/Game.cs | 4 ++-- C7/Map/MapViewCamera.cs | 26 +++++++++++++++----------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/C7/Game.cs b/C7/Game.cs index b64ea34a..8bb7efba 100644 --- a/C7/Game.cs +++ b/C7/Game.cs @@ -73,8 +73,6 @@ public override void _Ready() { log.Debug("RelativeModPath ", map.RelativeModPath); mapView = new MapView(this, gameDataAccess.gameData); - AddChild(mapView); - camera.attachToMapView(mapView); // Set initial camera location. If the UI controller has any cities, focus on their capital. Otherwise, focus on their // starting settler. @@ -89,6 +87,8 @@ public override void _Ready() { camera.centerOnTile(startingSettler.location, mapView); } } + camera.attachToMapView(mapView); + AddChild(mapView); } Toolbar = GetNode("CanvasLayer/Control/ToolBar/MarginContainer/HBoxContainer"); diff --git a/C7/Map/MapViewCamera.cs b/C7/Map/MapViewCamera.cs index 65144692..576c51ba 100644 --- a/C7/Map/MapViewCamera.cs +++ b/C7/Map/MapViewCamera.cs @@ -19,6 +19,8 @@ public partial class MapViewCamera : Camera2D { public void attachToMapView(MapView map) { this.map = map; + map.updateAnimations(); + checkWorldWrap(); } public override void _Ready() { @@ -46,6 +48,7 @@ public void setPosition(Vector2 position) { private void checkWorldWrap() { if (map is null || !map.wrapHorizontally) { + // TODO: for maps that do not wrap horizontally restrict movement return; } Rect2 visible = getVisibleWorld(); @@ -54,21 +57,22 @@ private void checkWorldWrap() { if (hwrap != HorizontalWrapState.Right && rhs >= map.worldEdgeRight) { hwrap = HorizontalWrapState.Right; map.setHorizontalWrap(hwrap); // move wrapping map - } else if (hwrap == HorizontalWrapState.Right && rhs < map.worldEdgeRight) { - hwrap = HorizontalWrapState.None; + } else if (hwrap == HorizontalWrapState.Right) { + if (rhs < map.worldEdgeRight) { + hwrap = HorizontalWrapState.None; // back within main map + } else if (rhs >= map.worldEdgeRight + map.pixelWidth) { + Translate(Vector2.Left * map.pixelWidth); // at rhs of wrapping map + } } if (hwrap != HorizontalWrapState.Left && lhs <= map.worldEdgeLeft) { hwrap = HorizontalWrapState.Left; map.setHorizontalWrap(hwrap); // move wrapping map - } else if (hwrap == HorizontalWrapState.Left && lhs > map.worldEdgeLeft) { - hwrap = HorizontalWrapState.None; - } - - // jump back into original map - if (hwrap == HorizontalWrapState.Right && rhs >= map.worldEdgeRight + map.pixelWidth) { - Translate(Vector2.Left * map.pixelWidth); // at rhs of wrapping map - } else if (hwrap == HorizontalWrapState.Left && lhs <= map.worldEdgeLeft - map.pixelWidth) { - Translate(Vector2.Right * map.pixelWidth); // at lhs of wrapping map + } else if (hwrap == HorizontalWrapState.Left) { + if (lhs > map.worldEdgeLeft) { + hwrap = HorizontalWrapState.None; // back within main map + } else if (rhs >= map.worldEdgeRight + map.pixelWidth) { + Translate(Vector2.Right * map.pixelWidth); // at lhs of wrapping map + } } } From c8002a22b07553a3fb603728f10573ea5f2f7a9a Mon Sep 17 00:00:00 2001 From: Paul Cento Date: Thu, 3 Aug 2023 11:32:05 +0200 Subject: [PATCH 54/60] fix left wrap --- C7/Map/MapViewCamera.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/C7/Map/MapViewCamera.cs b/C7/Map/MapViewCamera.cs index 576c51ba..46f69a7d 100644 --- a/C7/Map/MapViewCamera.cs +++ b/C7/Map/MapViewCamera.cs @@ -70,7 +70,7 @@ private void checkWorldWrap() { } else if (hwrap == HorizontalWrapState.Left) { if (lhs > map.worldEdgeLeft) { hwrap = HorizontalWrapState.None; // back within main map - } else if (rhs >= map.worldEdgeRight + map.pixelWidth) { + } else if (lhs <= map.worldEdgeLeft - map.pixelWidth) { Translate(Vector2.Right * map.pixelWidth); // at lhs of wrapping map } } From b881fe0567fd2adfe7c25f1edb028fc5cdad69c8 Mon Sep 17 00:00:00 2001 From: Paul Cento Date: Thu, 3 Aug 2023 11:53:13 +0200 Subject: [PATCH 55/60] simpler predicate functions, less error prone state in camera --- C7/Map/MapViewCamera.cs | 39 ++++++++++++++++++--------------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/C7/Map/MapViewCamera.cs b/C7/Map/MapViewCamera.cs index 46f69a7d..d487d99f 100644 --- a/C7/Map/MapViewCamera.cs +++ b/C7/Map/MapViewCamera.cs @@ -46,34 +46,31 @@ public void setPosition(Vector2 position) { checkWorldWrap(); } + private bool enteringRightWrap(Rect2 v) => hwrap != HorizontalWrapState.Right && v.End.X >= map.worldEdgeRight; + private bool enteringLeftWrap(Rect2 v) => hwrap != HorizontalWrapState.Left && v.Position.X <= map.worldEdgeLeft; + private bool atEdgeOfRightWrap(Rect2 v) => hwrap == HorizontalWrapState.Right && v.End.X >= map.worldEdgeRight + map.pixelWidth; + private bool atEdgeOfLeftWrap(Rect2 v) => hwrap == HorizontalWrapState.Left && v.Position.X <= map.worldEdgeLeft - map.pixelWidth; + private HorizontalWrapState currentHwrap(Rect2 v) { + return v.Position.X <= map.worldEdgeLeft ? HorizontalWrapState.Left : (v.End.X >= map.worldEdgeRight ? HorizontalWrapState.Right : HorizontalWrapState.None); + } + private void checkWorldWrap() { if (map is null || !map.wrapHorizontally) { // TODO: for maps that do not wrap horizontally restrict movement return; } - Rect2 visible = getVisibleWorld(); - float rhs = visible.End.X; - float lhs = visible.Position.X; - if (hwrap != HorizontalWrapState.Right && rhs >= map.worldEdgeRight) { - hwrap = HorizontalWrapState.Right; - map.setHorizontalWrap(hwrap); // move wrapping map - } else if (hwrap == HorizontalWrapState.Right) { - if (rhs < map.worldEdgeRight) { - hwrap = HorizontalWrapState.None; // back within main map - } else if (rhs >= map.worldEdgeRight + map.pixelWidth) { - Translate(Vector2.Left * map.pixelWidth); // at rhs of wrapping map - } + Rect2 visibleWorld = getVisibleWorld(); + if (enteringRightWrap(visibleWorld)) { + map.setHorizontalWrap(HorizontalWrapState.Right); + } else if (enteringLeftWrap(visibleWorld)) { + map.setHorizontalWrap(HorizontalWrapState.Left); } - if (hwrap != HorizontalWrapState.Left && lhs <= map.worldEdgeLeft) { - hwrap = HorizontalWrapState.Left; - map.setHorizontalWrap(hwrap); // move wrapping map - } else if (hwrap == HorizontalWrapState.Left) { - if (lhs > map.worldEdgeLeft) { - hwrap = HorizontalWrapState.None; // back within main map - } else if (lhs <= map.worldEdgeLeft - map.pixelWidth) { - Translate(Vector2.Right * map.pixelWidth); // at lhs of wrapping map - } + if (atEdgeOfRightWrap(visibleWorld)) { + Translate(Vector2.Left * map.pixelWidth); + } else if (atEdgeOfLeftWrap(visibleWorld)) { + Translate(Vector2.Right * map.pixelWidth); } + hwrap = currentHwrap(visibleWorld); } public override void _UnhandledInput(InputEvent @event) { From fc20ce415f6f7d2eb8a9ff2bd2d8cc11ed14b4db Mon Sep 17 00:00:00 2001 From: Paul Cento Date: Thu, 3 Aug 2023 13:27:19 +0200 Subject: [PATCH 56/60] add tile leeway for wrapping to hide temporary map tear --- C7/Map/MapView.cs | 11 +++-------- C7/Map/MapViewCamera.cs | 15 ++++++++++++--- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/C7/Map/MapView.cs b/C7/Map/MapView.cs index 4dc04786..5ef5869a 100644 --- a/C7/Map/MapView.cs +++ b/C7/Map/MapView.cs @@ -30,7 +30,7 @@ public partial class MapView : Node2D { private TileMap tilemap; private TileMap wrappingTilemap; private TileSet tileset; - private Vector2I tileSize = new Vector2I(128, 64); + public Vector2I tileSize {get; private set;} = new Vector2I(128, 64); private ILogger log = LogManager.ForContext(); public bool wrapHorizontally {get; private set;} public int worldEdgeRight {get; private set;} @@ -178,13 +178,8 @@ public void updateAnimations() { } public void setHorizontalWrap(HorizontalWrapState state) { - if (state == HorizontalWrapState.None) { - wrappingTerrainTilemap.Hide(); - wrappingTilemap.Hide(); - } else { + if (state != HorizontalWrapState.None) { Vector2I offset = horizontalWrapOffset(state); - wrappingTerrainTilemap.Show(); - wrappingTilemap.Show(); wrappingTerrainTilemap.Position = terrainTilemap.Position + offset; wrappingTilemap.Position = tilemap.Position + offset; } @@ -220,7 +215,7 @@ private void initializeTileMap() { } } - setHorizontalWrap(HorizontalWrapState.None); + setHorizontalWrap(HorizontalWrapState.Right); // just put it somewhere tilemap.ZIndex = 10; // need to figure out a good way to order z indices wrappingTilemap.ZIndex = 10; diff --git a/C7/Map/MapViewCamera.cs b/C7/Map/MapViewCamera.cs index d487d99f..7e77027b 100644 --- a/C7/Map/MapViewCamera.cs +++ b/C7/Map/MapViewCamera.cs @@ -15,10 +15,12 @@ public partial class MapViewCamera : Camera2D { private readonly float minZoom = 0.2f; public float zoomFactor { get; private set; } = 1.0f; private MapView map; + private int wrapLeeway = 2; // leeway in number of tiles to consider camera at map edge private HorizontalWrapState hwrap = HorizontalWrapState.None; public void attachToMapView(MapView map) { this.map = map; + worldWrapLeeway = wrapLeeway * map.tileSize.X; map.updateAnimations(); checkWorldWrap(); } @@ -45,13 +47,14 @@ public void setPosition(Vector2 position) { Position = position; checkWorldWrap(); } + private int worldWrapLeeway = 0; - private bool enteringRightWrap(Rect2 v) => hwrap != HorizontalWrapState.Right && v.End.X >= map.worldEdgeRight; - private bool enteringLeftWrap(Rect2 v) => hwrap != HorizontalWrapState.Left && v.Position.X <= map.worldEdgeLeft; + private bool enteringRightWrap(Rect2 v) => hwrap != HorizontalWrapState.Right && v.End.X >= map.worldEdgeRight - worldWrapLeeway; + private bool enteringLeftWrap(Rect2 v) => hwrap != HorizontalWrapState.Left && v.Position.X <= map.worldEdgeLeft + worldWrapLeeway; private bool atEdgeOfRightWrap(Rect2 v) => hwrap == HorizontalWrapState.Right && v.End.X >= map.worldEdgeRight + map.pixelWidth; private bool atEdgeOfLeftWrap(Rect2 v) => hwrap == HorizontalWrapState.Left && v.Position.X <= map.worldEdgeLeft - map.pixelWidth; private HorizontalWrapState currentHwrap(Rect2 v) { - return v.Position.X <= map.worldEdgeLeft ? HorizontalWrapState.Left : (v.End.X >= map.worldEdgeRight ? HorizontalWrapState.Right : HorizontalWrapState.None); + return v.Position.X <= map.worldEdgeLeft + worldWrapLeeway ? HorizontalWrapState.Left : (v.End.X >= map.worldEdgeRight - worldWrapLeeway ? HorizontalWrapState.Right : HorizontalWrapState.None); } private void checkWorldWrap() { @@ -61,8 +64,10 @@ private void checkWorldWrap() { } Rect2 visibleWorld = getVisibleWorld(); if (enteringRightWrap(visibleWorld)) { + GD.Print("moving wrap to right"); map.setHorizontalWrap(HorizontalWrapState.Right); } else if (enteringLeftWrap(visibleWorld)) { + GD.Print("moving wrap to left"); map.setHorizontalWrap(HorizontalWrapState.Left); } if (atEdgeOfRightWrap(visibleWorld)) { @@ -73,6 +78,10 @@ private void checkWorldWrap() { hwrap = currentHwrap(visibleWorld); } + public override void _Process(double delta) { + checkWorldWrap(); + } + public override void _UnhandledInput(InputEvent @event) { switch (@event) { case InputEventMouseMotion mouseDrag when mouseDrag.ButtonMask == MouseButtonMask.Left: From 6fc22f072f11b84125d59ddcb96d15ec1a242ccc Mon Sep 17 00:00:00 2001 From: Paul Cento Date: Thu, 3 Aug 2023 14:06:49 +0200 Subject: [PATCH 57/60] only check for wrapping when camera moves, better naming, comment main wrap function --- C7/Map/MapViewCamera.cs | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/C7/Map/MapViewCamera.cs b/C7/Map/MapViewCamera.cs index 7e77027b..a42348af 100644 --- a/C7/Map/MapViewCamera.cs +++ b/C7/Map/MapViewCamera.cs @@ -15,12 +15,12 @@ public partial class MapViewCamera : Camera2D { private readonly float minZoom = 0.2f; public float zoomFactor { get; private set; } = 1.0f; private MapView map; - private int wrapLeeway = 2; // leeway in number of tiles to consider camera at map edge + private int wrapEdgeTileMargin = 2; // margin in number of tiles to trigger map wrapping private HorizontalWrapState hwrap = HorizontalWrapState.None; public void attachToMapView(MapView map) { this.map = map; - worldWrapLeeway = wrapLeeway * map.tileSize.X; + wrapEdgeMargin = wrapEdgeTileMargin * map.tileSize.X; map.updateAnimations(); checkWorldWrap(); } @@ -47,16 +47,23 @@ public void setPosition(Vector2 position) { Position = position; checkWorldWrap(); } - private int worldWrapLeeway = 0; - - private bool enteringRightWrap(Rect2 v) => hwrap != HorizontalWrapState.Right && v.End.X >= map.worldEdgeRight - worldWrapLeeway; - private bool enteringLeftWrap(Rect2 v) => hwrap != HorizontalWrapState.Left && v.Position.X <= map.worldEdgeLeft + worldWrapLeeway; - private bool atEdgeOfRightWrap(Rect2 v) => hwrap == HorizontalWrapState.Right && v.End.X >= map.worldEdgeRight + map.pixelWidth; - private bool atEdgeOfLeftWrap(Rect2 v) => hwrap == HorizontalWrapState.Left && v.Position.X <= map.worldEdgeLeft - map.pixelWidth; + private float wrapEdgeMargin = 0; + float wrapRightEdge {get => map.worldEdgeRight - wrapEdgeMargin; } + float wrapLeftEdge {get => map.worldEdgeLeft + wrapEdgeMargin; } + private bool enteringRightWrap(Rect2 v) => hwrap != HorizontalWrapState.Right && v.End.X >= wrapRightEdge; + private bool enteringLeftWrap(Rect2 v) => hwrap != HorizontalWrapState.Left && v.Position.X <= wrapLeftEdge; + private bool atEdgeOfRightWrap(Rect2 v) => hwrap == HorizontalWrapState.Right && v.End.X >= wrapRightEdge + map.pixelWidth; + private bool atEdgeOfLeftWrap(Rect2 v) => hwrap == HorizontalWrapState.Left && v.Position.X <= wrapLeftEdge - map.pixelWidth; private HorizontalWrapState currentHwrap(Rect2 v) { - return v.Position.X <= map.worldEdgeLeft + worldWrapLeeway ? HorizontalWrapState.Left : (v.End.X >= map.worldEdgeRight - worldWrapLeeway ? HorizontalWrapState.Right : HorizontalWrapState.None); + return v.Position.X <= wrapLeftEdge ? HorizontalWrapState.Left : (v.End.X >= wrapRightEdge ? HorizontalWrapState.Right : HorizontalWrapState.None); } + // checkWorldWrap determines if the camera is about to spill over the world map and will: + // - move the second "wrap" tilemap to the appropriate edge + // to give the illusion of true wrapping tilemap + // - teleport the camera one world-width to the left or right when + // only the "wrap" tilemap is in view + private void checkWorldWrap() { if (map is null || !map.wrapHorizontally) { // TODO: for maps that do not wrap horizontally restrict movement @@ -64,10 +71,8 @@ private void checkWorldWrap() { } Rect2 visibleWorld = getVisibleWorld(); if (enteringRightWrap(visibleWorld)) { - GD.Print("moving wrap to right"); map.setHorizontalWrap(HorizontalWrapState.Right); } else if (enteringLeftWrap(visibleWorld)) { - GD.Print("moving wrap to left"); map.setHorizontalWrap(HorizontalWrapState.Left); } if (atEdgeOfRightWrap(visibleWorld)) { @@ -78,10 +83,6 @@ private void checkWorldWrap() { hwrap = currentHwrap(visibleWorld); } - public override void _Process(double delta) { - checkWorldWrap(); - } - public override void _UnhandledInput(InputEvent @event) { switch (@event) { case InputEventMouseMotion mouseDrag when mouseDrag.ButtonMask == MouseButtonMask.Left: From 289eae68b6d3df4e2b41b1aa174363aa1feef406 Mon Sep 17 00:00:00 2001 From: Paul Cento Date: Sun, 17 Sep 2023 15:40:26 -0400 Subject: [PATCH 58/60] remove unused code --- C7/Civ3Map/Civ3Map.cs | 98 ------------------------------------------- 1 file changed, 98 deletions(-) delete mode 100644 C7/Civ3Map/Civ3Map.cs diff --git a/C7/Civ3Map/Civ3Map.cs b/C7/Civ3Map/Civ3Map.cs deleted file mode 100644 index 883108df..00000000 --- a/C7/Civ3Map/Civ3Map.cs +++ /dev/null @@ -1,98 +0,0 @@ -using Godot; -using System.Collections; -using System.Collections.Generic; -using ConvertCiv3Media; -using C7GameData; - -public partial class Civ3Map : Node2D -{ - public List Civ3Tiles; - public int[,] Map { get; protected set; } - TileMap TM; - public TileSet TS { get; protected set; } - private int[,] TileIDLookup; - // NOTE: The following two must be set externally before running TerrainAsTileMap - public int MapWidth; - public int MapHeight; - // If a mod is in effect, set this, otherwise set to "" or "Conquests" - public string ModRelPath = ""; - public Civ3Map(int mapWidth, int mapHeight, string modRelPath = "") - { - MapWidth = mapWidth; - MapHeight = mapHeight; - ModRelPath = modRelPath; - } - public void TerrainAsTileMap() { - if (TM != null) { RemoveChild(TM); } - // Although tiles appear isometric, they are logically laid out as a checkerboard pattern on a square grid - TM = new TileMap(); - TM.TileSet.TileSize = new Vector2I(64,32); - // TM.CenteredTextures = true; - TS = TM.TileSet; - - TileIDLookup = new int[9,81]; - - // int id = TS.GetLastUnusedTileId(); - - // Make blank default tile - // TODO: Make red tile or similar - // NOTE: Need an unused tile at 0, anyway, to test to see if real tile has been loaded yet - - // TS.CreateTile(id); - // id++; - - Map = new int[MapWidth,MapHeight]; - - // Populate map values - if(Civ3Tiles != null) - { - foreach (Tile tile in Civ3Tiles) - { - // If tile media file not loaded yet - if (TileIDLookup[tile.ExtraInfo.BaseTerrainFileID, 1] == 0) { LoadTileSet(tile.ExtraInfo.BaseTerrainFileID); } - // var _ = TileIDLookup[tile.ExtraInfo.BaseTerrainFileID, tile.ExtraInfo.BaseTerrainImageID]; - Map[tile.xCoordinate, tile.yCoordinate] = 0; - Map[tile.xCoordinate, tile.yCoordinate] = TileIDLookup[tile.ExtraInfo.BaseTerrainFileID,tile.ExtraInfo.BaseTerrainImageID]; - } - } - /* This code sets the tiles for display, but that is being done by MapView now - for (int y = 0; y < MapHeight; y++) { - for (int x = y % 2; x < MapWidth; x+=2) { - TM.SetCellv(new Vector2(x, y), Map[x,y]); - } - } - AddChild(TM); - */ - } - private void LoadTileSet(int fileID) - { - Hashtable FileNameLookup = new Hashtable - { - { 0, "Art/Terrain/xtgc.pcx" }, - { 1, "Art/Terrain/xpgc.pcx" }, - { 2, "Art/Terrain/xdgc.pcx" }, - { 3, "Art/Terrain/xdpc.pcx" }, - { 4, "Art/Terrain/xdgp.pcx" }, - { 5, "Art/Terrain/xggc.pcx" }, - { 6, "Art/Terrain/wCSO.pcx" }, - { 7, "Art/Terrain/wSSS.pcx" }, - { 8, "Art/Terrain/wOOO.pcx" }, - }; - // int id = TS.GetLastUnusedTileId(); - // temp if - if (FileNameLookup[fileID] != null) { - Pcx PcxTxtr = new Pcx(Util.Civ3MediaPath(FileNameLookup[fileID].ToString()));//, ModRelPath)); - ImageTexture Txtr = PCXToGodot.getImageTextureFromPCX(PcxTxtr); - - for (int y = 0; y < PcxTxtr.Height; y += 64) { - for (int x = 0; x < PcxTxtr.Width; x+= 128/*, id++*/) { - // TS.Create - // TS.CreateTile(id); - // TS.TileSetTexture(id, Txtr); - // TS.TileSetRegion(id, new Rect2(x, y, 128, 64)); - // TileIDLookup[fileID, (x / 128) + (y / 64) * (PcxTxtr.Width / 128)] = id; - } - } - } - } -} From 48aecb47df507fc64cdaf8582083103d3af29c62 Mon Sep 17 00:00:00 2001 From: Paul Cento Date: Sun, 17 Sep 2023 17:49:33 -0400 Subject: [PATCH 59/60] fix issue where getVisibleWorld initially uses invalid GlobalCanvasTransform value --- C7/Map/MapView.cs | 6 +++--- C7/Map/MapViewCamera.cs | 18 ++++++++++++------ 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/C7/Map/MapView.cs b/C7/Map/MapView.cs index 5ef5869a..c5048e84 100644 --- a/C7/Map/MapView.cs +++ b/C7/Map/MapView.cs @@ -217,8 +217,8 @@ private void initializeTileMap() { setHorizontalWrap(HorizontalWrapState.Right); // just put it somewhere - tilemap.ZIndex = 10; // need to figure out a good way to order z indices - wrappingTilemap.ZIndex = 10; + tilemap.ZIndex = MapZIndex.Tiles; // need to figure out a good way to order z indices + wrappingTilemap.ZIndex = MapZIndex.Tiles; AddChild(tilemap); AddChild(wrappingTilemap); AddChild(terrainTilemap); @@ -297,7 +297,7 @@ public MapView(Game game, GameData data) { // TODO in the future convert civ3 coordinates to stacked // coordinates when reading from the civ3 save so Tile has // stacked coordinates - foreach (C7GameData.Tile t in gameMap.tiles) { + foreach (Tile t in gameMap.tiles) { Vector2I coords = stackedCoords(t); terrain[coords.X, coords.Y] = t.baseTerrainTypeKey; } diff --git a/C7/Map/MapViewCamera.cs b/C7/Map/MapViewCamera.cs index a42348af..403e7607 100644 --- a/C7/Map/MapViewCamera.cs +++ b/C7/Map/MapViewCamera.cs @@ -18,10 +18,18 @@ public partial class MapViewCamera : Camera2D { private int wrapEdgeTileMargin = 2; // margin in number of tiles to trigger map wrapping private HorizontalWrapState hwrap = HorizontalWrapState.None; - public void attachToMapView(MapView map) { + public async void attachToMapView(MapView map) { this.map = map; wrapEdgeMargin = wrapEdgeTileMargin * map.tileSize.X; map.updateAnimations(); + + // Awaiting a 0 second timer is a workaround to force GlobalCanvasTransform to be updated. + // This is necessary when the camera's starting position is close to the edge of the map. + // Without it, the GlobalCanvasTransform will not be updated until the camera is moved, + // resulting in broken map wrapping. I tried awaiting "process_frame" but it does not seem + // to work, although I believe that is what we want to do here. GPT-4 suggested waiting for + // a 0 second timer, which seems to work. + await ToSignal(GetTree().CreateTimer(0), "timeout"); checkWorldWrap(); } @@ -63,7 +71,6 @@ private HorizontalWrapState currentHwrap(Rect2 v) { // to give the illusion of true wrapping tilemap // - teleport the camera one world-width to the left or right when // only the "wrap" tilemap is in view - private void checkWorldWrap() { if (map is null || !map.wrapHorizontally) { // TODO: for maps that do not wrap horizontally restrict movement @@ -94,10 +101,9 @@ public override void _UnhandledInput(InputEvent @event) { } } - public Rect2 getVisibleWorld() { - Transform2D vpToGlobal = (GetViewport().GlobalCanvasTransform * GetCanvasTransform()).AffineInverse(); - return vpToGlobal * GetViewportRect(); - } + private Transform2D viewportToGlobalTransform => (GetViewport().GlobalCanvasTransform * GetCanvasTransform()).AffineInverse(); + + public Rect2 getVisibleWorld() => viewportToGlobalTransform * GetViewportRect(); public void centerOnTile(Tile tile, MapView map) { Vector2 target = map.tileToLocal(tile); From ae178c61b2f3fa6992c0373e7c0a3592ef3de51e Mon Sep 17 00:00:00 2001 From: Ben Bowles Date: Sat, 23 Sep 2023 14:11:13 -0400 Subject: [PATCH 60/60] Fixed issue with rivers not showing, added deltas --- C7/Map/MapView.cs | 19 ++++++++++++++++++- C7/Map/TileSetLoader.cs | 3 +++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/C7/Map/MapView.cs b/C7/Map/MapView.cs index c5048e84..13513324 100644 --- a/C7/Map/MapView.cs +++ b/C7/Map/MapView.cs @@ -396,11 +396,18 @@ private static int roadFlag(TileDirection direction) { private void updateRiverLayer(Tile tile) { // The "point" is the easternmost point of the current tile. // The river graphic is determined by the tiles neighboring that point. + + + Tile northOfPoint = tile.neighbors[TileDirection.NORTHEAST]; Tile eastOfPoint = tile.neighbors[TileDirection.EAST]; Tile westOfPoint = tile; Tile southOfPoint = tile.neighbors[TileDirection.SOUTHEAST]; + List riverNeighbors = new List () {northOfPoint, eastOfPoint, westOfPoint, southOfPoint}; + + int coastCount = riverNeighbors.Sum(tile => tile.IsWater()== true ? 1 : 0); + int index = 0; index += northOfPoint.riverSouthwest ? 1 : 0; index += eastOfPoint.riverNorthwest ? 2 : 0; @@ -410,7 +417,15 @@ private void updateRiverLayer(Tile tile) { if (index == 0) { eraseCell(Layer.River, tile); } else { - setCell(Layer.River, Atlas.River, tile, new Vector2I(index % 4, index / 4)); + // We might eventually want a more sophisticated delta algorithm. Maybe check if *all four* are coastal and then delete the river entirely? Maybe check if the river is *ending* at a coast, etc. Might also want deltas in wetland tiles like marshes. Just sticking with this as it was the issue spec. + if(coastCount >= 2) + { + setCell(Layer.RiverDelta, Atlas.RiverDelta, tile, new Vector2I(index % 4, index / 4)); + } + else + { + setCell(Layer.River, Atlas.River, tile, new Vector2I(index % 4, index / 4)); + } } } @@ -631,6 +646,8 @@ public void updateTile(Tile tile, TileKnowledge tk) { updateTerrainOverlayLayer(tile); + updateRiverLayer(tile); + updateBuildingLayer(tile); } diff --git a/C7/Map/TileSetLoader.cs b/C7/Map/TileSetLoader.cs index 244134cc..03abb55b 100644 --- a/C7/Map/TileSetLoader.cs +++ b/C7/Map/TileSetLoader.cs @@ -6,6 +6,7 @@ namespace C7.Map { public enum Layer { TerrainOverlay, River, + RiverDelta, Road, Rail, Resource, @@ -38,6 +39,7 @@ public enum Atlas { TundraForest, Marsh, River, + RiverDelta, Road, Rail, Resource, @@ -182,6 +184,7 @@ class TileSetLoader { {Atlas.TerrainYield, new AtlasLoader("Art/Terrain/tnt.pcx", 3, 6, tileSize)}, {Atlas.River, new AtlasLoader("Art/Terrain/mtnRivers.pcx", 4, 4, tileSize)}, + {Atlas.RiverDelta, new AtlasLoader("Art/Terrain/deltaRivers.pcx", 4, 4, tileSize)}, {Atlas.Hill, new AtlasLoader("Art/Terrain/xhills.pcx", 4, 4, hillSize, 4)}, {Atlas.ForestHill, new AtlasLoader("Art/Terrain/hill forests.pcx", 4, 4, hillSize, 4)},