diff --git a/gdd.md b/gdd.md index fa2b769..b780dcd 100644 --- a/gdd.md +++ b/gdd.md @@ -11,15 +11,14 @@ ## Clock Ticks can be applied manually or they can be applied over time. -Add a tick counter as a UI element. +There is a tick counter UI debug element. -## Grid +## Diffusion + +### Grid Add a 10x10 grid. Tiles are highlighted a different colour when the player hovers over them with the cursor. - -### Diffusion - Tiles in the grid contain floating point values. These values propogate to neighbouring cells a tick happens. diff --git a/half-earth/resources/overlays/no_overlay.tres b/half-earth/resources/overlays/no_overlay.tres index d4a783f..5f79807 100644 --- a/half-earth/resources/overlays/no_overlay.tres +++ b/half-earth/resources/overlays/no_overlay.tres @@ -4,3 +4,5 @@ [resource] script = ExtResource( 1 ) +_healthy = Color( 0.0431373, 0.541176, 0.0235294, 1 ) +_depleted = Color( 0.670588, 0.572549, 0.27451, 1 ) diff --git a/half-earth/resources/tiles/tile_types/developed.tres b/half-earth/resources/tiles/tile_types/developed.tres index 8f5f1d1..d9ffdac 100644 --- a/half-earth/resources/tiles/tile_types/developed.tres +++ b/half-earth/resources/tiles/tile_types/developed.tres @@ -7,4 +7,4 @@ script = ExtResource( 1 ) Name = "Developed" Key = 68 Color = Color( 0.372549, 0.372549, 0.372549, 1 ) -HeatGeneration = 0.4 +HeatGeneration = 0.15 diff --git a/half-earth/resources/tiles/tile_types/tile_type_collections/buildable_tiles.tres b/half-earth/resources/tiles/tile_types/tile_type_collections/buildable_tiles.tres index af140a6..1656e8c 100644 --- a/half-earth/resources/tiles/tile_types/tile_type_collections/buildable_tiles.tres +++ b/half-earth/resources/tiles/tile_types/tile_type_collections/buildable_tiles.tres @@ -1,8 +1,9 @@ -[gd_resource type="Resource" load_steps=3 format=2] +[gd_resource type="Resource" load_steps=4 format=2] [ext_resource path="res://scripts/tile/TileTypeCollection.cs" type="Script" id=1] [ext_resource path="res://resources/tiles/tile_types/developed.tres" type="Resource" id=2] +[ext_resource path="res://resources/tiles/tile_types/wild.tres" type="Resource" id=3] [resource] script = ExtResource( 1 ) -_resources = [ ExtResource( 2 ) ] +_resources = [ ExtResource( 2 ), ExtResource( 3 ) ] diff --git a/half-earth/resources/tiles/tile_types/wild.tres b/half-earth/resources/tiles/tile_types/wild.tres index 370bb83..ed76163 100644 --- a/half-earth/resources/tiles/tile_types/wild.tres +++ b/half-earth/resources/tiles/tile_types/wild.tres @@ -1,10 +1,12 @@ [gd_resource type="Resource" load_steps=2 format=2] -[ext_resource path="res://scripts/tile/TileType.cs" type="Script" id=1] +[ext_resource path="res://scripts/tile/Wild.cs" type="Script" id=1] [resource] script = ExtResource( 1 ) Name = "Wild" Key = 87 -Color = Color( 0, 0.545098, 0.0196078, 1 ) -HeatGeneration = -0.2 +Color = Color( 0.14902, 1, 0, 0 ) +HeatGeneration = -0.02 +HealRate = 0.01 +Threshold = 0.6 diff --git a/half-earth/scripts/EnvironmentSystem.cs b/half-earth/scripts/EnvironmentSystem.cs new file mode 100644 index 0000000..5fabf8b --- /dev/null +++ b/half-earth/scripts/EnvironmentSystem.cs @@ -0,0 +1,117 @@ +using Godot; + +public class EnvironmentSystem +{ + private readonly TileGrid _tiles; + private readonly float _diffusionCoefficient; + + private float[] _nextTemperatures = null; + + public EnvironmentSystem(TileGrid tiles, float diffusionCoefficient) + { + _tiles = tiles; + _diffusionCoefficient = diffusionCoefficient; + _nextTemperatures = new float[tiles.Count]; + } + + public void Process() + { + GenerateHeat(); + Diffuse(); + } + + private float TransferHeat(float t0, float alpha, float nx, float ny, float px, float py) + { + float d2tdx2 = nx - 2 * t0 + px; + float d2tdy2 = ny - 2 * t0 + py; + return t0 + alpha * (d2tdx2 + d2tdy2); + } + + private void GetNeighbourTemperatures(int x, int y, out float nx, out float ny, out float px, out float py) + { + // default value + var t = _tiles[x, y].temperature; + + nx = x > 0 ? _tiles[x - 1, y].temperature : t; + px = x < _tiles.Size - 1 ? _tiles[x + 1, y].temperature : t; + + ny = y > 0 ? _tiles[x, y - 1].temperature : t; + py = y < _tiles.Size - 1 ? _tiles[x, y + 1].temperature : t; + } + private void GenerateHeat() + { + for (int i = 0; i < _tiles.Count; i++) + { + var tile = _tiles[i]; + var type = tile.type; + tile.temperature += type.HeatGeneration; + _tiles[i] = tile; + } + + for (int i = 0; i < _tiles.Count; i++) + { + ApplyHeatDamage(i); + } + } + + private void ApplyHeatDamage(int i) + { + _tiles.MapIndex(i, out var x, out var y); + var tile = _tiles[x, y]; + if (!(tile.type is IDamageable damageable)) + return; + + var delta = tile.temperature - damageable.Threshold; + if (delta < 0) + { + // we want to heal based on how many wild tiles surround us + float wild = _tiles.GetNeighbourProportion(x, y); + float heal = damageable.HealRate * wild; + + tile.currentHealth += heal; + } + else + { + // TODO: parameterised balancing + + // take damage + tile.currentHealth -= delta; + } + + tile.currentHealth = Mathf.Clamp(tile.currentHealth, 0, 1); + + _tiles[x, y] = tile; + } + + private void Diffuse() + { + var D = _diffusionCoefficient; + + for (int i = 0; i < _tiles.Count; i++) + { + float t = _tiles[i].temperature; + + _tiles.MapIndex(i, out var x, out var y); + + GetNeighbourTemperatures( + x, y, + out var nx, + out var ny, + out var px, + out var py); + + var temperature = TransferHeat(t, D, nx, ny, px, py); + + // TODO: what if it's really really cold out? + temperature = Mathf.Max(0, temperature); + _nextTemperatures[i] = temperature; + } + + for (int i = 0; i < _tiles.Count; i++) + { + var tile = _tiles[i]; + tile.temperature = _nextTemperatures[i]; + _tiles[i] = tile; + } + } +} diff --git a/half-earth/scripts/WorldGrid.cs b/half-earth/scripts/WorldGrid.cs index 6a7b01a..10a7250 100644 --- a/half-earth/scripts/WorldGrid.cs +++ b/half-earth/scripts/WorldGrid.cs @@ -16,13 +16,12 @@ public class WorldGrid : Node2D [Export] public float DiffusionCoefficient { get; set; } = 0.01f; - private float[] _nextValues = null; - public struct TileView { public ShaderMaterial material; } private TileView[] _tileViews; + private EnvironmentSystem _environment; // Called when the node enters the scene tree for the first time. public override void _Ready() @@ -30,6 +29,8 @@ public class WorldGrid : Node2D _tileGrid = (TileGrid)_tileGridResource; GenerateCanvasItems(_tileGrid); + + _environment = new EnvironmentSystem(_tileGrid, DiffusionCoefficient); } #region Positioning @@ -52,9 +53,8 @@ public class WorldGrid : Node2D var tile = _tileGrid[idx]; tile.type = tileType; + tile = tileType.ApplySpawneffect(tile); _tileGrid[x, y] = tile; - - _tileViews[idx].material.SetShaderParam("lowColor", tileType.Color); } #endregion @@ -76,7 +76,6 @@ public class WorldGrid : Node2D this.Clear(); _tileViews = new TileView[grid.Count]; - _nextValues = new float[grid.Count]; for (int i = 0; i < grid.Count; i++) { @@ -96,80 +95,11 @@ public class WorldGrid : Node2D TileView view = default; view.material = (ShaderMaterial)canvasItem.Material; - view.material.SetShaderParam("lowColor", _tileGrid.StartTileType.Color); _tileViews[i] = view; } } public ShaderMaterial GetTileMaterial(int idx) => _tileViews[idx].material; - #region Simulation - public void _on_Clock_OnTick(int ticks) - { - GenerateHeat(); - Diffuse(); - } - - private float TransferHeat(float t0, float alpha, float nx, float ny, float px, float py) - { - float d2tdx2 = nx - 2 * t0 + px; - float d2tdy2 = ny - 2 * t0 + py; - return t0 + alpha * (d2tdx2 + d2tdy2); - } - - private void GetNeighbourTemperatures(int x, int y, out float nx, out float ny, out float px, out float py) - { - // default value - var t = _tileGrid[x, y].temperature; - - nx = x > 0 ? _tileGrid[x - 1, y].temperature : t; - px = x < _tileGrid.Size - 1 ? _tileGrid[x + 1, y].temperature : t; - - ny = y > 0 ? _tileGrid[x, y - 1].temperature : t; - py = y < _tileGrid.Size - 1 ? _tileGrid[x, y + 1].temperature : t; - } - - private void GenerateHeat() - { - for (int i = 0; i < _tileGrid.Count; i++) - { - var tile = _tileGrid[i]; - var type = tile.type; - tile.temperature += type.HeatGeneration; - _tileGrid[i] = tile; - } - } - - private void Diffuse() - { - var D = DiffusionCoefficient; - - for (int i = 0; i < _tileGrid.Count; i++) - { - float t = _tileGrid[i].temperature; - - _tileGrid.MapIndex(i, out var x, out var y); - - GetNeighbourTemperatures( - x, y, - out var nx, - out var ny, - out var px, - out var py); - - var temperature = TransferHeat(t, D, nx, ny, px, py); - - // TODO: what if it's really really cold out? - temperature = Mathf.Max(0, temperature); - _nextValues[i] = temperature; - } - - for (int i = 0; i < _tileGrid.Count; i++) - { - var tile = _tileGrid[i]; - tile.temperature = _nextValues[i]; - _tileGrid[i] = tile; - } - } - #endregion + public void _on_Clock_OnTick(int ticks) => _environment.Process(); } diff --git a/half-earth/scripts/overlays/NoOverlay.cs b/half-earth/scripts/overlays/NoOverlay.cs index 49f8b1b..1222cec 100644 --- a/half-earth/scripts/overlays/NoOverlay.cs +++ b/half-earth/scripts/overlays/NoOverlay.cs @@ -2,10 +2,25 @@ using Godot; public class NoOverlay : Overlay { + [Export] + private Color _healthy; + + [Export] + private Color _depleted; + // in this view we want to draw tiles with their normal colour public override void Apply(Tile tile, ShaderMaterial material) { var type = tile.type; + + if (type is Wild wild) + { + material.SetShaderParam("lowColor", _depleted); + material.SetShaderParam("highColor", _healthy); + material.SetShaderParam("t", tile.currentHealth); + return; + } + material.SetShaderParam("lowColor", type.Color); material.SetShaderParam("t", 0); } diff --git a/half-earth/scripts/tile/IDamageable.cs b/half-earth/scripts/tile/IDamageable.cs new file mode 100644 index 0000000..ba606f2 --- /dev/null +++ b/half-earth/scripts/tile/IDamageable.cs @@ -0,0 +1,5 @@ +public interface IDamageable +{ + float HealRate { get; } + float Threshold { get; } +} \ No newline at end of file diff --git a/half-earth/scripts/tile/Tile.cs b/half-earth/scripts/tile/Tile.cs index 662c72c..3a51b55 100644 --- a/half-earth/scripts/tile/Tile.cs +++ b/half-earth/scripts/tile/Tile.cs @@ -5,4 +5,5 @@ public struct Tile public bool isHighlighted; public float temperature; public TileType type; + public float currentHealth; } \ No newline at end of file diff --git a/half-earth/scripts/tile/TileGrid.cs b/half-earth/scripts/tile/TileGrid.cs index 7a2eb08..7ea685e 100644 --- a/half-earth/scripts/tile/TileGrid.cs +++ b/half-earth/scripts/tile/TileGrid.cs @@ -10,7 +10,7 @@ public class TileGrid : Resource, IReadOnlyList [Export] private Resource _startTileTypeResource; - public TileType StartTileType => (TileType)_startTileTypeResource; + private TileType StartTileType => (TileType)_startTileTypeResource; public int Count => Size * Size; @@ -58,6 +58,7 @@ public class TileGrid : Resource, IReadOnlyList tile.isHighlighted = false; tile.temperature = 0.0f; tile.type = StartTileType; + tile.currentHealth = 1.0f; tiles.Add(tile); } @@ -88,5 +89,64 @@ public class TileGrid : Resource, IReadOnlyList { return x >= 0 && x < Size && y >= 0 && y < Size; } + + private readonly int[] _neighbours = new int[] + { + -1, 0, + 0, 1, + 1, 0, + 0, -1 + }; + + private void GetCoordinate(int dir, out int x, out int y) + { + dir %= 4; + int idx = dir * 2; + x = _neighbours[idx]; + y = _neighbours[idx + 1]; + } + + public float GetNeighbourProportion(int x, int y) + { + int typed = GetNeighbourCount(x, y); + int all = GetNeighbourCount(x, y); + return (float)typed / (float)all; + } + + public int GetNeighbourCount(int x, int y) + { + int count = 0; + for (int i = 0; i < 4; i++) + { + GetCoordinate(i, out var nx, out var ny); + + if (!IsInBounds(x + nx, y + ny)) + continue; + + if (!(this[x + nx, y + ny].type is T)) + continue; + + count++; + } + + return count; + } + + public int GetNeighbourCount(int x, int y) + { + int count = 0; + + for (int i = 0; i < 4; i++) + { + GetCoordinate(i, out var nx, out var ny); + + if (!IsInBounds(x + nx, y + ny)) + continue; + + count++; + } + + return count; + } } diff --git a/half-earth/scripts/tile/TileType.cs b/half-earth/scripts/tile/TileType.cs index c2824a1..f682ed1 100644 --- a/half-earth/scripts/tile/TileType.cs +++ b/half-earth/scripts/tile/TileType.cs @@ -17,4 +17,6 @@ public class TileType : Resource public float HeatGeneration { get; private set; } public override string ToString() => Name; + + public virtual Tile ApplySpawneffect(Tile tile) => tile; } \ No newline at end of file diff --git a/half-earth/scripts/tile/Wild.cs b/half-earth/scripts/tile/Wild.cs new file mode 100644 index 0000000..b058b44 --- /dev/null +++ b/half-earth/scripts/tile/Wild.cs @@ -0,0 +1,16 @@ +using Godot; + +public class Wild : TileType, IDamageable +{ + [Export] + public float HealRate { get; private set; } = 0.01f; + + [Export] + public float Threshold { get; private set; } + + public override Tile ApplySpawneffect(Tile tile) + { + tile.currentHealth = 0; + return tile; + } +} \ No newline at end of file diff --git a/half-earth/scripts/ui/BuildModeUI.cs b/half-earth/scripts/ui/BuildModeUI.cs index 92ddfa0..16461c1 100644 --- a/half-earth/scripts/ui/BuildModeUI.cs +++ b/half-earth/scripts/ui/BuildModeUI.cs @@ -20,10 +20,14 @@ public class BuildModeUI : Control _buildMode = GetNode(_buildModePath); var buttonHeight = 40; - var tileType = _buildMode.BuildableTiles[0]; - int x = 50; - SpawnButton(tileType, buttonHeight, ref x); + int x = 50; + int margin = 5; + foreach (var tt in _buildMode.BuildableTiles) + { + SpawnButton(tt, buttonHeight, ref x); + x += margin; + } } private void UpdateButtonToggleState(TileType tileType)