aboutsummaryrefslogtreecommitdiff
path: root/source/game/planets
diff options
context:
space:
mode:
Diffstat (limited to 'source/game/planets')
-rw-r--r--source/game/planets/BlockState.cs43
-rw-r--r--source/game/planets/Chunk.cs158
-rw-r--r--source/game/planets/ChunkMap.cs99
-rw-r--r--source/game/planets/GeneratedPlanet.cs35
-rw-r--r--source/game/planets/generation/IWorldGenerator.cs18
-rw-r--r--source/game/planets/generation/TerranPlanetGenerator.cs275
6 files changed, 628 insertions, 0 deletions
diff --git a/source/game/planets/BlockState.cs b/source/game/planets/BlockState.cs
new file mode 100644
index 0000000..e7d4be0
--- /dev/null
+++ b/source/game/planets/BlockState.cs
@@ -0,0 +1,43 @@
+using Celesteia.Game.Components.Items;
+using Celesteia.Resources;
+using Celesteia.Resources.Sprites;
+using Celesteia.Resources.Types;
+
+namespace Celesteia.Game.Planets {
+ public struct BlockState {
+ public static BlockState None = new BlockState() { Empty = true };
+
+ public bool Empty;
+
+ private byte _id;
+ public byte BlockID {
+ get => _id;
+ set {
+ _id = value;
+ Type = ResourceManager.Blocks.GetBlock(BlockID) as BlockType;
+
+ Empty = _id == 0;
+ Translucent = Type.Translucent;
+ Frames = Type.Frames;
+ }
+ }
+
+ public BlockType Type { get; private set; }
+ public bool Translucent { get; private set; }
+ public BlockFrames Frames { get; private set; }
+
+ public int BreakProgress;
+
+ public bool Draw;
+ public bool HasFrames() => Frames != null;
+
+ public BlockData Data;
+ public bool HasData() => Data != null;
+ }
+
+ public class BlockData {}
+ public class TileEntity : BlockData {}
+ public class Container : TileEntity {
+ public Inventory inventory;
+ }
+} \ No newline at end of file
diff --git a/source/game/planets/Chunk.cs b/source/game/planets/Chunk.cs
new file mode 100644
index 0000000..5fc7618
--- /dev/null
+++ b/source/game/planets/Chunk.cs
@@ -0,0 +1,158 @@
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using Celesteia.Resources;
+using Celesteia.Graphics;
+using System;
+using Celesteia.Resources.Sprites;
+using Celesteia.Game.Components.Items;
+
+namespace Celesteia.Game.Planets {
+ public class Chunk {
+ public const int CHUNK_SIZE = 16;
+
+ public Point TruePosition { get; private set; }
+ private ChunkVector _position;
+ public ChunkVector Position {
+ get => _position;
+ set { _position = value; TruePosition = _position.Resolve(); }
+ }
+
+ private BlockState[,] foreground;
+ private BlockState[,] background;
+
+ public Chunk(ChunkVector cv) {
+ Position = cv;
+
+ foreground = new BlockState[CHUNK_SIZE, CHUNK_SIZE];
+ background = new BlockState[CHUNK_SIZE, CHUNK_SIZE];
+ }
+
+ public BlockState GetForeground(int x, int y) => foreground[x, y];
+ public BlockState GetBackground(int x, int y) => background[x, y];
+
+ public void SetForeground(int x, int y, byte id) {
+ foreground[x, y].BlockID = id;
+ foreground[x, y].BreakProgress = 0;
+ UpdateDraws(x, y);
+ }
+
+ public void SetBackground(int x, int y, byte id) {
+ background[x, y].BlockID = id;
+ background[x, y].BreakProgress = 0;
+ UpdateDraws(x, y);
+ }
+
+ public void UpdateDraws(int x, int y) {
+ foreground[x, y].Draw = foreground[x, y].HasFrames();
+ background[x, y].Draw = background[x, y].HasFrames() && foreground[x, y].Translucent;
+ }
+
+ private NamespacedKey? dropKey;
+ public void AddBreakProgress(int x, int y, int power, bool wall, out ItemStack drops) {
+ dropKey = null;
+ drops = null;
+
+ if (wall) {
+ background[x, y].BreakProgress += power;
+ if (background[x, y].BreakProgress > background[x, y].Type.Strength) {
+ dropKey = background[x, y].Type.DropKey;
+ SetBackground(x, y, 0);
+ }
+ } else {
+ foreground[x, y].BreakProgress += power;
+ if (foreground[x, y].BreakProgress > foreground[x, y].Type.Strength) {
+ dropKey = foreground[x, y].Type.DropKey;
+ SetForeground(x, y, 0);
+ }
+ }
+
+ if (dropKey.HasValue) drops = new ItemStack(dropKey.Value, 1);
+ }
+
+ public void Draw(GameTime gameTime, SpriteBatch spriteBatch) {
+ for (int i = 0; i < CHUNK_SIZE; i++) {
+ for (int j = 0; j < CHUNK_SIZE; j++) {
+ DrawAllAt(i, j, gameTime, spriteBatch);
+ }
+ }
+ }
+
+ Vector2 v;
+ private void DrawAllAt(int x, int y, GameTime gameTime, SpriteBatch spriteBatch) {
+ v.X = TruePosition.X + x;
+ v.Y = TruePosition.Y + y;
+ v.Floor();
+
+ if (background[x, y].Draw) {
+ DrawWallTile(background[x, y].Frames.GetFrame(0), spriteBatch, v);
+ if (background[x, y].BreakProgress > 0) DrawWallTile(ResourceManager.Blocks.BreakAnimation.GetProgressFrame(
+ // Background block breaking progress.
+ background[x, y].BreakProgress / (float) background[x, y].Type.Strength
+ ), spriteBatch, v);
+ }
+ if (foreground[x, y].Draw) {
+ DrawTile(foreground[x, y].Frames.GetFrame(0), spriteBatch, v);
+ if (foreground[x, y].BreakProgress > 0) DrawTile(ResourceManager.Blocks.BreakAnimation.GetProgressFrame(
+ // Foreground block breaking progress.
+ foreground[x, y].BreakProgress / (float) foreground[x, y].Type.Strength
+ ), spriteBatch, v);
+ }
+ }
+
+ public void DrawTile(BlockFrame frame, SpriteBatch spriteBatch, Vector2 v) {
+ frame.Draw(0, spriteBatch, v, Color.White, 0.4f);
+ }
+
+ public void DrawWallTile(BlockFrame frame, SpriteBatch spriteBatch, Vector2 v) {
+ frame.Draw(0, spriteBatch, v, Color.DarkGray, 0.5f);
+ }
+ }
+
+ public struct ChunkVector {
+ public int X;
+ public int Y;
+
+ public ChunkVector(int x, int y) {
+ X = x;
+ Y = y;
+ }
+
+ public static ChunkVector FromVector2(Vector2 vector)
+ {
+ return new ChunkVector(
+ (int)MathF.Floor(vector.X / Chunk.CHUNK_SIZE),
+ (int)MathF.Floor(vector.Y / Chunk.CHUNK_SIZE)
+ );
+ }
+
+ public static ChunkVector FromPoint(Point point)
+ => new ChunkVector(point.X / Chunk.CHUNK_SIZE, point.Y/ Chunk.CHUNK_SIZE);
+
+ public Point Resolve() {
+ return new Point(X * Chunk.CHUNK_SIZE, Y * Chunk.CHUNK_SIZE);
+ }
+
+ public static int Distance(ChunkVector a, ChunkVector b) {
+ return MathHelper.Max(Math.Abs(b.X - a.X), Math.Abs(b.Y - a.Y));
+ }
+
+ public static bool operator ==(ChunkVector a, ChunkVector b) {
+ return a.Equals(b);
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (obj is ChunkVector) return ((ChunkVector)obj).X == this.X && ((ChunkVector)obj).Y == this.Y;
+ return false;
+ }
+
+ public static bool operator !=(ChunkVector a, ChunkVector b) {
+ return !a.Equals(b);
+ }
+
+ public override int GetHashCode()
+ {
+ return base.GetHashCode();
+ }
+ }
+} \ No newline at end of file
diff --git a/source/game/planets/ChunkMap.cs b/source/game/planets/ChunkMap.cs
new file mode 100644
index 0000000..234d034
--- /dev/null
+++ b/source/game/planets/ChunkMap.cs
@@ -0,0 +1,99 @@
+using System;
+using Celesteia.Game.Components.Items;
+using Celesteia.Game.Planets.Generation;
+using Celesteia.Resources;
+using Microsoft.Xna.Framework;
+using MonoGame.Extended;
+
+namespace Celesteia.Game.Planets {
+ public class ChunkMap {
+ public int Width, Height;
+ public int BlockWidth => Width * Chunk.CHUNK_SIZE;
+ public int BlockHeight => Height * Chunk.CHUNK_SIZE;
+
+ public Chunk[,] Map;
+
+ public ChunkMap(int w, int h) {
+ Width = w;
+ Height = h;
+
+ Map = new Chunk[w, h];
+ }
+
+ public Chunk GetChunk(int chunkX, int chunkY) => ChunkIsInMap(chunkX, chunkY) ? Map[chunkX, chunkY] : null;
+ public Chunk GetChunk(ChunkVector cv) => GetChunk(cv.X, cv.Y);
+ public Chunk GetChunkAtCoordinates(int x, int y) => GetChunk(x / Chunk.CHUNK_SIZE, y / Chunk.CHUNK_SIZE);
+ public Chunk GetChunkAtPoint(Point point) => GetChunkAtCoordinates(point.X, point.Y);
+
+ // BACKGROUND MANAGEMENT
+ public BlockState GetBackground(int blockX, int blockY) {
+ Chunk c = GetChunkAtCoordinates(blockX, blockY);
+ if (c != null) return c.GetBackground(blockX % Chunk.CHUNK_SIZE, blockY % Chunk.CHUNK_SIZE);
+
+ return BlockState.None;
+ }
+ public BlockState GetBackground(Point point) => GetBackground(point.X, point.Y);
+
+ public void SetBackgroundID(int blockX, int blockY, byte id) {
+ Chunk c = GetChunkAtCoordinates(blockX, blockY);
+ if (c != null) c.SetBackground(blockX % Chunk.CHUNK_SIZE, blockY % Chunk.CHUNK_SIZE, id);
+ }
+ public void SetBackgroundID(Point point, byte id) => SetBackgroundID(point.X, point.Y, id);
+
+ public void AddBackgroundBreakProgress(int blockX, int blockY, int power, out ItemStack drops) {
+ drops = null;
+ Chunk c = GetChunkAtCoordinates(blockX, blockY);
+ if (c != null) c.AddBreakProgress(blockX % Chunk.CHUNK_SIZE, blockY % Chunk.CHUNK_SIZE, power, true, out drops);
+ }
+ public void AddBackgroundBreakProgress(Point point, int power, out ItemStack drops) => AddBackgroundBreakProgress(point.X, point.Y, power, out drops);
+
+ // FOREGROUND MANAGEMENT
+ public BlockState GetForeground(int blockX, int blockY) {
+ Chunk c = GetChunkAtCoordinates(blockX, blockY);
+ if (c != null) return c.GetForeground(blockX % Chunk.CHUNK_SIZE, blockY % Chunk.CHUNK_SIZE);
+
+ return BlockState.None;
+ }
+ public BlockState GetForeground(Point point) => GetForeground(point.X, point.Y);
+
+ public void SetForegroundID(int blockX, int blockY, byte id) {
+ Chunk c = GetChunkAtCoordinates(blockX, blockY);
+ if (c != null) c.SetForeground(blockX % Chunk.CHUNK_SIZE, blockY % Chunk.CHUNK_SIZE, id);
+ }
+ public void SetForegroundID(Point point, byte id) => SetForegroundID(point.X, point.Y, id);
+
+ public void AddForegroundBreakProgress(int blockX, int blockY, int power, out ItemStack drops) {
+ drops = null;
+ Chunk c = GetChunkAtCoordinates(blockX, blockY);
+ if (c != null) c.AddBreakProgress(blockX % Chunk.CHUNK_SIZE, blockY % Chunk.CHUNK_SIZE, power, false, out drops);
+ }
+ public void AddForegroundBreakProgress(Point point, int power, out ItemStack drops) => AddForegroundBreakProgress(point.X, point.Y, power, out drops);
+
+ // FOR ADJACENCY CHECKS
+ public bool GetAny(int blockX, int blockY) {
+ Chunk c = GetChunkAtCoordinates(blockX, blockY);
+ return c != null && (
+ !c.GetForeground(blockX % Chunk.CHUNK_SIZE, blockY % Chunk.CHUNK_SIZE).Empty ||
+ !c.GetBackground(blockX % Chunk.CHUNK_SIZE, blockY % Chunk.CHUNK_SIZE).Empty
+ );
+ }
+
+ // COLLISION CHECKS
+
+ public RectangleF? TestBoundingBox(int x, int y, RectangleF? box) {
+ if (!box.HasValue) return null;
+
+ return new RectangleF(
+ x, y,
+ box.Value.Width, box.Value.Height
+ );
+ }
+ public RectangleF? TestBoundingBox(int x, int y) => TestBoundingBox(x, y, GetForeground(x, y).Type.BoundingBox);
+
+ // CHUNK IN MAP CHECKS
+ public bool ChunkIsInMap(int chunkX, int chunkY) => !(chunkX < 0 || chunkY < 0 || chunkX >= Width || chunkY >= Height);
+ public bool ChunkIsInMap(ChunkVector cv) => ChunkIsInMap(cv.X, cv.Y);
+
+ public virtual Vector2 GetSpawnpoint() => Vector2.Zero;
+ }
+} \ No newline at end of file
diff --git a/source/game/planets/GeneratedPlanet.cs b/source/game/planets/GeneratedPlanet.cs
new file mode 100644
index 0000000..c89a6c8
--- /dev/null
+++ b/source/game/planets/GeneratedPlanet.cs
@@ -0,0 +1,35 @@
+using System;
+using Celesteia.Game.Planets.Generation;
+using Microsoft.Xna.Framework;
+
+namespace Celesteia.Game.Planets {
+ public class GeneratedPlanet : ChunkMap
+ {
+ public int Seed { get; private set; }
+
+ private IChunkProvider _provider;
+
+ public GeneratedPlanet(int w, int h, int? seed = null) : base(w, h)
+ {
+ Seed = seed.HasValue ? seed.Value : (int) System.DateTime.Now.Ticks;
+ }
+
+ public void Generate(IChunkProvider provider, Action<string> progressReport = null) {
+ Action<string> doProgressReport = (s) => { if (progressReport != null) progressReport(s); };
+
+ _provider = provider;
+ doProgressReport("Generating chunks...");
+
+ ChunkVector _cv;
+ for (_cv.X = 0; _cv.X < Width; _cv.X++)
+ for (_cv.Y = 0; _cv.Y < Height; _cv.Y++)
+ provider.ProvideChunk(Map[_cv.X, _cv.Y] = new Chunk(_cv));
+
+ provider.GenerateStructures(doProgressReport);
+
+ doProgressReport("Planet generated.");
+ }
+
+ public override Vector2 GetSpawnpoint() => _provider.GetSpawnpoint();
+ }
+} \ No newline at end of file
diff --git a/source/game/planets/generation/IWorldGenerator.cs b/source/game/planets/generation/IWorldGenerator.cs
new file mode 100644
index 0000000..92b528b
--- /dev/null
+++ b/source/game/planets/generation/IWorldGenerator.cs
@@ -0,0 +1,18 @@
+using System;
+using Microsoft.Xna.Framework;
+
+namespace Celesteia.Game.Planets.Generation {
+ public interface IChunkProvider {
+ // Provide a chunk's tile map.
+ public void ProvideChunk(Chunk c);
+
+ // Get the natural block and wall at X and Y.
+ public byte[] GetNaturalBlocks(int x, int y);
+
+ // Get a safe spot to spawn the player.
+ public Vector2 GetSpawnpoint();
+
+ // Generate various structures in the world.
+ public void GenerateStructures(Action<string> progressReport = null);
+ }
+} \ No newline at end of file
diff --git a/source/game/planets/generation/TerranPlanetGenerator.cs b/source/game/planets/generation/TerranPlanetGenerator.cs
new file mode 100644
index 0000000..9507011
--- /dev/null
+++ b/source/game/planets/generation/TerranPlanetGenerator.cs
@@ -0,0 +1,275 @@
+using System;
+using Microsoft.Xna.Framework;
+using Celesteia.Resources;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Celesteia.Game.Planets.Generation {
+ public class TerranPlanetGenerator : IChunkProvider {
+ private ChunkMap _chunkMap;
+ private int _seed;
+ private FastNoiseLite _noise;
+
+ public TerranPlanetGenerator(GeneratedPlanet chunkMap) {
+ _chunkMap = chunkMap;
+ _noise = new FastNoiseLite(_seed = chunkMap.Seed);
+
+ LoadBlockIndices();
+ }
+
+ private byte top;
+ private byte soil;
+ private byte stone;
+ private byte deepstone;
+ private byte log;
+ private byte leaves;
+ private byte planks;
+ private byte coal_ore;
+ private byte copper_ore;
+ private byte iron_ore;
+ private byte[] foliage;
+ private byte landing_floor;
+ private byte cc_base;
+ private byte cc_frame;
+ private void LoadBlockIndices() {
+ top = ResourceManager.Blocks.GetResource(NamespacedKey.Base("grown_soil")).GetID();
+ soil = ResourceManager.Blocks.GetResource(NamespacedKey.Base("soil")).GetID();
+ stone = ResourceManager.Blocks.GetResource(NamespacedKey.Base("stone")).GetID();
+ deepstone = ResourceManager.Blocks.GetResource(NamespacedKey.Base("deepstone")).GetID();
+ log = ResourceManager.Blocks.GetResource(NamespacedKey.Base("log")).GetID();
+ leaves = ResourceManager.Blocks.GetResource(NamespacedKey.Base("leaves")).GetID();
+ planks = ResourceManager.Blocks.GetResource(NamespacedKey.Base("wooden_planks")).GetID();
+ coal_ore = ResourceManager.Blocks.GetResource(NamespacedKey.Base("coal_ore")).GetID();
+ copper_ore = ResourceManager.Blocks.GetResource(NamespacedKey.Base("copper_ore")).GetID();
+ iron_ore = ResourceManager.Blocks.GetResource(NamespacedKey.Base("iron_ore")).GetID();
+
+ foliage = new byte[5] {
+ 0,
+ ResourceManager.Blocks.GetResource(NamespacedKey.Base("grass")).GetID(),
+ ResourceManager.Blocks.GetResource(NamespacedKey.Base("blue_flower")).GetID(),
+ ResourceManager.Blocks.GetResource(NamespacedKey.Base("red_flower")).GetID(),
+ ResourceManager.Blocks.GetResource(NamespacedKey.Base("violet_flower")).GetID()
+ };
+
+ landing_floor = ResourceManager.Blocks.GetResource(NamespacedKey.Base("scorched_soil")).GetID();
+ cc_base = ResourceManager.Blocks.GetResource(NamespacedKey.Base("crashed_capsule_base")).GetID();
+ cc_frame = ResourceManager.Blocks.GetResource(NamespacedKey.Base("crashed_capsule_frame")).GetID();
+ }
+
+ public void ProvideChunk(Chunk c) {
+ byte[] natural;
+ for (int i = 0; i < Chunk.CHUNK_SIZE; i++)
+ for (int j = 0; j < Chunk.CHUNK_SIZE; j++) {
+ natural = GetNaturalBlocks(c.TruePosition.X + i, c.TruePosition.Y + j);
+ c.SetForeground(i, j, natural[0]);
+ c.SetBackground(i, j, natural[1]);
+ }
+ }
+
+ public byte[] GetNaturalBlocks(int x, int y) => ThirdPass(x, y, SecondPass(x, y, FirstPass(x, y)));
+
+ public void GenerateStructures(Action<string> progressReport) {
+ Random rand = new Random(_seed);
+
+ progressReport("Planting trees...");
+ GenerateTrees(rand);
+ progressReport("Abandoning houses...");
+ GenerateAbandonedHomes(rand);
+ progressReport("Planting foliage...");
+ GenerateFoliage(rand);
+ progressReport("Landing light...");
+ GenerateLanding();
+ }
+
+ public Vector2 GetSpawnpoint()
+ {
+ float x;
+ return new Vector2(
+ x = MathF.Floor(_chunkMap.BlockWidth / 2f) + 0.5f,
+ (_chunkMap.BlockHeight) - GetHeightValue((int)MathF.Floor(x)) - 2f
+ );
+ }
+
+ public byte[] FirstPass(int x, int y) {
+ if (y > _chunkMap.BlockHeight - 5) return new byte[2] { deepstone, deepstone };
+
+ byte[] values = new byte[2] { 0, 0 };
+
+ int h = GetHeightValue(x);
+
+ if (_chunkMap.BlockHeight - y <= h) {
+ if (_chunkMap.BlockHeight - y == h) { values[0] = top; values[1] = soil; }
+ else if (_chunkMap.BlockHeight - y >= h - 3) { values[0] = soil; values[1] = soil; }
+ else { values[0] = stone; values[1] = stone; }
+ }
+
+ return values;
+ }
+ public byte[] SecondPass(int x, int y, byte[] values) {
+ float threshold = 0.667f;
+
+ if (values[0] == 0 || values[0] == deepstone) return values;
+ if (values[0] == soil || values[0] == top) threshold += .2f;
+
+ float c = GetCaveValue(x, y);
+
+ if (c > threshold) values[0] = 0;
+
+ return values;
+ }
+
+ public byte[] ThirdPass(int x, int y, byte[] values) {
+ if (values[0] != stone) return values;
+
+ float coalValue = GetOreValue(x, y, 498538f, 985898f);
+ if (coalValue > 0.95f) values[0] = coal_ore;
+ else {
+ float copperValue = GetOreValue(x, y, 3089279f, 579486f);
+ if (copperValue > 0.95f) values[0] = copper_ore;
+
+ else
+ {
+ float ironValue = GetOreValue(x, y, 243984f, 223957f);
+ if (ironValue > 0.95f) values[0] = iron_ore;
+ }
+ }
+
+ return values;
+ }
+
+ private int defaultOffset => _chunkMap.BlockHeight / 3;
+ public int GetHeightValue(int x) => (int)Math.Round((_noise.GetNoise(x / 1f, 0f) * 24f) + defaultOffset);
+ public float GetCaveValue(int x, int y) => _noise.GetNoise(x / 0.6f, y / 0.7f);
+ public float GetOreValue(int x, int y, float offsetX, float offsetY) => (_noise.GetNoise((x + offsetX) * 5f, (y + offsetY) * 5f) + 1) / 2f;
+
+ private int blocksBetweenTrees = 5;
+ private int treeGrowthSteps = 15;
+ public void GenerateTrees(Random rand) {
+ int j = 0;
+ int randomNumber = 0;
+ int lastTree = 0;
+ for (int i = 0; i < _chunkMap.BlockWidth; i++) {
+ j = _chunkMap.BlockHeight - GetHeightValue(i);
+
+ if (MathF.Abs(i - GetSpawnpoint().X) < 10f) continue; // Don't grow trees too close to spawn.
+ if (i < 10 || i > _chunkMap.BlockWidth - 10) continue; // Don't grow trees too close to world borders.
+ if (_chunkMap.GetForeground(i, j).BlockID != top) continue; // Only grow trees on grass.
+ if (i - lastTree < blocksBetweenTrees) continue; // Force a certain number of blocks between trees.
+
+ lastTree = i;
+
+ randomNumber = rand.Next(0, 6);
+
+ if (randomNumber == 1) GrowTreeRecursively(i, j - 1, treeGrowthSteps - rand.Next(0, 7), rand, false);
+ }
+ }
+
+ public void GrowTreeRecursively(int x, int y, int steps, Random rand, bool branch) {
+ if (steps == 0) {
+ for (int i = -2; i <= 2; i++)
+ for (int j = -2; j <= 2; j++) {
+ if (_chunkMap.GetForeground(x + i, y + j).Empty) _chunkMap.SetForegroundID(x + i, y + j, leaves);
+ }
+ return;
+ }
+
+ if (!_chunkMap.GetForeground(x, y).Empty) return;
+
+ _chunkMap.SetForegroundID(x, y, log);
+
+ if (!branch) GrowTreeRecursively(x, y - 1, steps - 1, rand, false); // Grow upwards.
+ if (rand.Next(0, 6) > steps) GrowTreeRecursively(x - 1, y, steps - 1, rand, true); // Grow to the left.
+ if (rand.Next(0, 6) > steps) GrowTreeRecursively(x + 1, y, steps - 1, rand, true); // Grow to the right.
+ }
+
+ private int blocksBetweenHomes = 150;
+ public void GenerateAbandonedHomes(Random rand) {
+ int j = 0;
+ int randomNumber = 0;
+ int lastHome = 0;
+ for (int i = 0; i < _chunkMap.BlockWidth; i++) {
+ j = _chunkMap.BlockHeight - GetHeightValue(i);
+
+ if (MathF.Abs(i - GetSpawnpoint().X) < 10f) continue; // Don't grow trees too close to spawn.
+ if (i < 10 || i > _chunkMap.BlockWidth - 10) continue; // Don't grow trees too close to world borders.
+ if (i - lastHome < blocksBetweenHomes) continue; // Force a certain number of blocks between trees.
+
+ int homeWidth = rand.Next(10, 15);
+ int homeHeight = rand.Next(6, 10);
+ int buryAmount = rand.Next(15, 40);
+
+ j -= homeHeight; // Raise the home to be built on the ground first.
+ j += buryAmount; // Bury the home by a random amount.
+
+ lastHome = i;
+
+ randomNumber = rand.Next(0, 5);
+
+ if (randomNumber == 1) BuildAbandonedHome(i, j, homeWidth, homeHeight, rand);
+ }
+ }
+
+ public void BuildAbandonedHome(int originX, int originY, int homeWidth, int homeHeight, Random rand) {
+ int maxX = originX + homeWidth;
+
+ for (int i = originX; i < maxX; i++) originY = Math.Max(originY, _chunkMap.BlockHeight - GetHeightValue(i));
+ int maxY = originY + homeHeight;
+
+ for (int i = originX; i < maxX; i++)
+ for (int j = originY; j < maxY; j++) {
+ _chunkMap.SetBackgroundID(i, j, planks);
+ _chunkMap.SetForegroundID(i, j, 0);
+
+ // Apply some random decay by skipping tiles at random.
+ if (rand.Next(0, 5) > 3) {
+ continue;
+ }
+
+ if (j == originY || j == maxY - 1) _chunkMap.SetForegroundID(i, j, planks);
+ if (i == originX || i == maxX - 1) _chunkMap.SetForegroundID(i, j, log);
+ }
+ }
+
+ private Dictionary<double, int> foliageDistribution = new Dictionary<double, int> {
+ { 0.3, 0 },
+ { 0.6, 1 },
+ { 0.7, 2 },
+ { 0.85, 4 },
+ { 0.99, 3 },
+ };
+
+ public void GenerateFoliage(Random rand) {
+ int j = 0;
+
+ double randomNumber = 0;
+ int foliageIndex = 0;
+ for (int i = 0; i < _chunkMap.BlockWidth; i++) {
+ j = _chunkMap.BlockHeight - GetHeightValue(i);
+
+ if (_chunkMap.GetForeground(i, j).BlockID != top) continue; // If there is anything but foreground grown soil, continue.
+ if (!_chunkMap.GetForeground(i, j - 1).Empty) continue; // If the foreground is already taken, continue.
+
+ randomNumber = rand.NextDouble();
+ for (int f = 0; f < foliageDistribution.Keys.Count; f++) {
+ if (randomNumber > foliageDistribution.Keys.ElementAt(f)) foliageIndex = foliageDistribution[foliageDistribution.Keys.ElementAt(f)];
+ }
+
+ _chunkMap.SetForegroundID(i, j - 1, foliage[foliageIndex]);
+ }
+ }
+
+ public void GenerateLanding() {
+ int x = GetSpawnpoint().ToPoint().X;
+ int j = _chunkMap.BlockHeight - GetHeightValue(x);
+ for (int i = -1; i <= 1; i++) {
+ _chunkMap.SetForegroundID(x + i, j + 1, soil);
+ _chunkMap.SetForegroundID(x + i, j, landing_floor);
+ for (int h = 1; h <= 3; h++) {
+ _chunkMap.SetForegroundID(x + i, j - h, cc_frame);
+ _chunkMap.SetBackgroundID(x + i, j - h, 0);
+ }
+ }
+ _chunkMap.SetForegroundID(x, j - 1, cc_base);
+ }
+ }
+} \ No newline at end of file