From de605617bed13915d2d9aa98d8dc66291124d1a5 Mon Sep 17 00:00:00 2001 From: p-svacha Date: Sun, 12 Jan 2025 18:18:18 +0100 Subject: [PATCH] improved desert generator, fixed characters spawning in ground, fix movement speed bug, fix vision collider sizes on static entities --- .../Base/Def/DefOf/SurfaceDefOf.cs | 1 + .../BlockmapFramework/Base/Def/EntityDef.cs | 5 ++ .../BlockmapFramework/Base/SaveLoadManager.cs | 23 +++++++ .../Scripts/BlockmapFramework/Base/World.cs | 5 -- .../Defs/GlobalSurfaceDefs.cs | 8 +-- .../BlockmapFramework/Entity/Entity.cs | 4 +- .../BlockmapFramework/Entity/EntityManager.cs | 40 ++++++++--- .../MapGeneration/TerrainFunctions.cs | 31 +++++++-- .../WorldGenerators/WorldGenerator_Desert.cs | 69 ++++++++++++++++--- .../BlockmapFramework/Node/BlockmapNode.cs | 23 +++++++ .../BlockmapFramework/Node/GroundNode.cs | 12 ++++ Assets/Scripts/CaptureTheFlag/Base/CtfGame.cs | 2 +- .../CaptureTheFlag/Base/CtfMapGenerator.cs | 13 ++-- .../Scripts/CaptureTheFlag/Defs/EntityDefs.cs | 2 +- .../Scripts/CaptureTheFlag/Defs/StatDefs.cs | 14 +--- Assets/doc.txt | 15 ---- 16 files changed, 196 insertions(+), 71 deletions(-) diff --git a/Assets/Scripts/BlockmapFramework/Base/Def/DefOf/SurfaceDefOf.cs b/Assets/Scripts/BlockmapFramework/Base/Def/DefOf/SurfaceDefOf.cs index 6346e45b..c96fbed7 100644 --- a/Assets/Scripts/BlockmapFramework/Base/Def/DefOf/SurfaceDefOf.cs +++ b/Assets/Scripts/BlockmapFramework/Base/Def/DefOf/SurfaceDefOf.cs @@ -13,6 +13,7 @@ public static class SurfaceDefOf public static SurfaceDef Grass; public static SurfaceDef Sand; public static SurfaceDef SandSoft; + public static SurfaceDef Sandstone; public static SurfaceDef Sidewalk; public static SurfaceDef Street; public static SurfaceDef Tiles; diff --git a/Assets/Scripts/BlockmapFramework/Base/Def/EntityDef.cs b/Assets/Scripts/BlockmapFramework/Base/Def/EntityDef.cs index cae1d3ca..a9b018a7 100644 --- a/Assets/Scripts/BlockmapFramework/Base/Def/EntityDef.cs +++ b/Assets/Scripts/BlockmapFramework/Base/Def/EntityDef.cs @@ -112,6 +112,11 @@ public EntityDef(EntityDef orig) } + public bool HasCompProperties() + { + return Components != null && Components.Any(c => c is T); + } + /// /// Retrieve specific CompProperties of this def. Throws an error if it doesn't have it. /// diff --git a/Assets/Scripts/BlockmapFramework/Base/SaveLoadManager.cs b/Assets/Scripts/BlockmapFramework/Base/SaveLoadManager.cs index c8002883..215119dd 100644 --- a/Assets/Scripts/BlockmapFramework/Base/SaveLoadManager.cs +++ b/Assets/Scripts/BlockmapFramework/Base/SaveLoadManager.cs @@ -2,6 +2,7 @@ using System.Collections; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Reflection; using System.Text; using System.Xml; @@ -335,6 +336,28 @@ public static void SaveOrLoadList(ref List list, string label) where T : c } } + /// + /// Save and load a list. + /// + public static void SaveOrLoadStringHashSet(ref HashSet list, string label) + { + if (IsSaving) + { + string saveText = ""; + foreach (string s in list) saveText += s + ","; + saveText = saveText.TrimEnd(','); + writer.WriteElementString(label, saveText); + } + else if (IsLoading) + { + if (reader.ReadToFollowing(label)) + { + string value = reader.ReadElementContentAsString(); + list = value.Split(',').ToHashSet(); + } + } + } + /// /// Save and load an int-keyed dictionary with objects as values. /// diff --git a/Assets/Scripts/BlockmapFramework/Base/World.cs b/Assets/Scripts/BlockmapFramework/Base/World.cs index 2d6fbd30..29513295 100644 --- a/Assets/Scripts/BlockmapFramework/Base/World.cs +++ b/Assets/Scripts/BlockmapFramework/Base/World.cs @@ -1255,9 +1255,6 @@ public bool CanSpawnEntity(EntityDef def, BlockmapNode node, Direction rotation, return true; } - /// - /// Creates a new entity from a def, registers it in the world and updates the world, navmesh and vision around it. - /// public Entity SpawnEntity(EntityDef def, BlockmapNode node, Direction rotation, bool isMirrored, Actor actor, bool updateWorld, int height = -1, System.Action preInit = null, int variantIndex = 0) { if (actor == null) throw new System.Exception("Cannot spawn an entity without an actor"); @@ -1462,7 +1459,6 @@ public void AddWaterBody(GroundNode sourceNode, int shoreHeight, bool updateWorl ((checkDir == Direction.None || checkDir == Direction.S) && (checkNode.Altitude[Direction.SW] < shoreHeight || checkNode.Altitude[Direction.SE] < shoreHeight)) || ((checkDir == Direction.None || checkDir == Direction.W) && (checkNode.Altitude[Direction.SW] < shoreHeight || checkNode.Altitude[Direction.NW] < shoreHeight)) ); - Debug.Log($"node at {checkNode.WorldCoordinates} would be underwater? {isUnderwater}. shore height is {shoreHeight}"); if (isUnderwater) // underwater { // Remove drowned entities @@ -1483,7 +1479,6 @@ public void AddWaterBody(GroundNode sourceNode, int shoreHeight, bool updateWorl else { } // above water } - Debug.Log($"water body would have {waterBody.CoveredGroundNodes.Count} nodes."); if (waterBody.CoveredGroundNodes.Count > 0) AddWaterBody(waterBody, updateWorld); } diff --git a/Assets/Scripts/BlockmapFramework/Defs/GlobalSurfaceDefs.cs b/Assets/Scripts/BlockmapFramework/Defs/GlobalSurfaceDefs.cs index bf88c089..ed5a3ccf 100644 --- a/Assets/Scripts/BlockmapFramework/Defs/GlobalSurfaceDefs.cs +++ b/Assets/Scripts/BlockmapFramework/Defs/GlobalSurfaceDefs.cs @@ -19,7 +19,7 @@ public static class GlobalSurfaceDefs DefName = "Grass", Label = "grass", Description = "Short grass", - MovementSpeedModifier = 0.6f, + MovementSpeedModifier = 0.65f, RenderProperties = new SurfaceRenderProperties() { Type = SurfaceRenderType.Default_Blend, @@ -33,7 +33,7 @@ public static class GlobalSurfaceDefs DefName = "Sand", Label = "sand", Description = "Sand", - MovementSpeedModifier = 0.4f, + MovementSpeedModifier = 0.5f, RenderProperties = new SurfaceRenderProperties() { Type = SurfaceRenderType.Default_Blend, @@ -47,7 +47,7 @@ public static class GlobalSurfaceDefs DefName = "SandSoft", Label = "soft sand", Description = "Soft sand", - MovementSpeedModifier = 0.3f, + MovementSpeedModifier = 0.4f, RenderProperties = new SurfaceRenderProperties() { Type = SurfaceRenderType.Default_Blend, @@ -198,7 +198,7 @@ public static class GlobalSurfaceDefs DefName = "Sandstone", Label = "sandstone", Description = "", - MovementSpeedModifier = 1f, + MovementSpeedModifier = 0.9f, RenderProperties = new SurfaceRenderProperties() { Type = SurfaceRenderType.Default_Blend, diff --git a/Assets/Scripts/BlockmapFramework/Entity/Entity.cs b/Assets/Scripts/BlockmapFramework/Entity/Entity.cs index 5fb7646f..12715a37 100644 --- a/Assets/Scripts/BlockmapFramework/Entity/Entity.cs +++ b/Assets/Scripts/BlockmapFramework/Entity/Entity.cs @@ -332,10 +332,8 @@ protected virtual void CreateVisionCollider() if (Def.VisionColliderType == VisionColliderType.FullBox || (Def.VisionColliderType == VisionColliderType.EntityShape && Def.OverrideHeights.Count == 0)) // Create a single box collider with the bounds of the whole entity { - if (MeshObject != null) VisionColliderObject.transform.localScale = MeshObject.transform.localScale; BoxCollider collider = VisionColliderObject.AddComponent(); - if (MeshObject != null) collider.size = new Vector3(Dimensions.x / MeshObject.transform.localScale.x, (Dimensions.y * World.NodeHeight) / MeshObject.transform.localScale.y, Dimensions.z / MeshObject.transform.localScale.z); - else collider.size = new Vector3(Dimensions.x, (Dimensions.y * World.NodeHeight), Dimensions.z); + collider.size = new Vector3(Dimensions.x, (Dimensions.y * World.NodeHeight), Dimensions.z); collider.center = new Vector3(0f, collider.size.y / 2, 0f); WorldObjectCollider evc = VisionColliderObject.AddComponent(); diff --git a/Assets/Scripts/BlockmapFramework/Entity/EntityManager.cs b/Assets/Scripts/BlockmapFramework/Entity/EntityManager.cs index a0211c16..94e59ab4 100644 --- a/Assets/Scripts/BlockmapFramework/Entity/EntityManager.cs +++ b/Assets/Scripts/BlockmapFramework/Entity/EntityManager.cs @@ -111,14 +111,27 @@ public static Vector3 GetWorldPosition(EntityDef def, World world, BlockmapNode if (occupiedNodes == null) return new Vector3(basePosition.x, originNode.BaseWorldAltitude, basePosition.y); // Else calculate the exact y position - // Identify the lowest node of all occupied nodes. - float lowestY = occupiedNodes.Min(n => n.BaseWorldAltitude); - List lowestYNodes = occupiedNodes.Where(n => n.BaseWorldAltitude == lowestY).ToList(); - float y = lowestY; + float y = 0; + bool isInWater = false; + + // For moving characters (always 1x1, just take the center world position of the node) + if (def.HasCompProperties()) + { + y = occupiedNodes.First().MeshCenterWorldPosition.y; + isInWater = occupiedNodes.First() is WaterNode; + } + + // For static objects the lowest node of all occupied nodes. + else + { + float lowestY = occupiedNodes.Min(n => n.BaseWorldAltitude); + List lowestYNodes = occupiedNodes.Where(n => n.BaseWorldAltitude == lowestY).ToList(); + y = lowestY; + isInWater = lowestYNodes.Any(n => n is WaterNode || (n is GroundNode ground && ground.WaterNode != null && ground.IsCenterUnderWater)); + } // Move position halfway below water surface if required - bool placementIsInWater = lowestYNodes.Any(n => n is WaterNode || (n is GroundNode ground && ground.WaterNode != null && ground.IsCenterUnderWater)); - if (placementIsInWater && def.WaterBehaviour == WaterBehaviour.HalfBelowWaterSurface) + if (isInWater && def.WaterBehaviour == WaterBehaviour.HalfBelowWaterSurface) { y -= (entityHeight * World.NodeHeight) / 2; } @@ -209,7 +222,7 @@ public static Vector2Int GetTranslatedPosition(Vector2Int position, Vector2Int d /// /// Spawns an entity on a random node near the given point and returns the entity instance. /// - public static Entity SpawnEntityAround(World world, EntityDef def, Actor player, Vector2Int worldCoordinates, float standard_deviation, Direction forcedRotation = Direction.None, int maxAttempts = 1, int requiredRoamingArea = -1, List forbiddenNodes = null, string variantName = "", bool randomMirror = false) + public static Entity SpawnEntityAround(World world, EntityDef def, Actor player, Vector2Int worldCoordinates, float standard_deviation, Direction forcedRotation = Direction.None, int maxAttempts = 1, int requiredRoamingArea = -1, List forbiddenNodes = null, string variantName = "", bool randomMirror = false, List forbiddenTags = null) { if (standard_deviation == 0f) maxAttempts = 1; int numAttempts = 0; @@ -220,7 +233,7 @@ public static Entity SpawnEntityAround(World world, EntityDef def, Actor player, Direction rotation = forcedRotation == Direction.None ? HelperFunctions.GetRandomSide() : forcedRotation; bool isMirrored = randomMirror ? Random.value < 0.5f : false; - Entity spawnedEntity = TrySpawnEntity(world, def, player, targetCoordinates, rotation, isMirrored, variantName, requiredRoamingArea, forbiddenNodes); + Entity spawnedEntity = TrySpawnEntity(world, def, player, targetCoordinates, rotation, isMirrored, variantName, requiredRoamingArea, forbiddenNodes, forbiddenTags); if (spawnedEntity != null) return spawnedEntity; } @@ -231,7 +244,7 @@ public static Entity SpawnEntityAround(World world, EntityDef def, Actor player, /// /// Spawns an entity within a given area in the world and returns the entity instance. /// - public static Entity SpawnEntityWithin(World world, EntityDef def, Actor player, int minX, int maxX, int minY, int maxY, Direction forcedRotation = Direction.None, int maxAttempts = 1, int requiredRoamingArea = -1, List forbiddenNodes = null, string variantName = "", bool randomMirror = false) + public static Entity SpawnEntityWithin(World world, EntityDef def, Actor player, int minX, int maxX, int minY, int maxY, Direction forcedRotation = Direction.None, int maxAttempts = 1, int requiredRoamingArea = -1, List forbiddenNodes = null, string variantName = "", bool randomMirror = false, List forbiddenTags = null) { int numAttempts = 0; while (numAttempts++ < maxAttempts) // Keep searching until we find a suitable position @@ -240,7 +253,7 @@ public static Entity SpawnEntityWithin(World world, EntityDef def, Actor player, Direction rotation = forcedRotation == Direction.None ? HelperFunctions.GetRandomSide() : forcedRotation; bool isMirrored = randomMirror ? Random.value < 0.5f : false; - Entity spawnedEntity = TrySpawnEntity(world, def, player, targetCoordinates, rotation, isMirrored, variantName, requiredRoamingArea, forbiddenNodes); + Entity spawnedEntity = TrySpawnEntity(world, def, player, targetCoordinates, rotation, isMirrored, variantName, requiredRoamingArea, forbiddenNodes, forbiddenTags); if (spawnedEntity != null) return spawnedEntity; } @@ -251,12 +264,17 @@ public static Entity SpawnEntityWithin(World world, EntityDef def, Actor player, /// /// Tries spawning an entity on a random node on the given coordinates with all the given restrictions. ///
Returns the entity instance if successful or null of not successful. - private static Entity TrySpawnEntity(World world, EntityDef def, Actor player, Vector2Int worldCoordinates, Direction rotation, bool isMirrored, string variantName = "", int requiredRoamingArea = -1, List forbiddenNodes = null) + private static Entity TrySpawnEntity(World world, EntityDef def, Actor player, Vector2Int worldCoordinates, Direction rotation, bool isMirrored, string variantName = "", int requiredRoamingArea = -1, List forbiddenNodes = null, List forbiddenTags = null) { if (!world.IsInWorld(worldCoordinates)) return null; BlockmapNode targetNode = world.GetNodes(worldCoordinates).RandomElement(); if (forbiddenNodes != null && forbiddenNodes.Contains(targetNode)) return null; + if (forbiddenTags != null && targetNode.Tags.Any(t => forbiddenTags.Contains(t))) + { + Debug.Log($"Not spawning {def.LabelCap} on {targetNode} because it has a forbidden tag"); + return null; + } if (!world.CanSpawnEntity(def, targetNode, rotation, isMirrored, forceHeadspaceRecalc: true)) return null; int variantIndex = 0; diff --git a/Assets/Scripts/BlockmapFramework/MapGeneration/TerrainFunctions.cs b/Assets/Scripts/BlockmapFramework/MapGeneration/TerrainFunctions.cs index 617d0970..5a5f6c84 100644 --- a/Assets/Scripts/BlockmapFramework/MapGeneration/TerrainFunctions.cs +++ b/Assets/Scripts/BlockmapFramework/MapGeneration/TerrainFunctions.cs @@ -1,5 +1,6 @@ using System.Collections; using System.Collections.Generic; +using System.Linq; using UnityEngine; namespace BlockmapFramework.WorldGeneration @@ -7,7 +8,14 @@ namespace BlockmapFramework.WorldGeneration public static class TerrainFunctions { - public static void SmoothOutside(BlockmapNode node, int smoothStep, List ignoredSurfaces = null) => SmoothOutside(node.World, new Parcel(node.World, node.WorldCoordinates, Vector2Int.one), smoothStep, ignoredSurfaces); + public static void SmoothOutside(GroundNode node, int smoothStep, List ignoredSurfaces = null, List ignoredTags = null) + { + if (ignoredSurfaces != null && ignoredSurfaces.Contains(node.SurfaceDef)) return; + if (ignoredTags != null && node.HasAnyOfTags(ignoredTags)) return; // Skip if node has an ignored tag + if (ignoredTags != null && node.GetAdjacentGroundNodes8().Any(n => n.HasAnyOfTags(ignoredTags))) return; // Skip if any adjacent node has an ignored tag + + SmoothOutside(node.World, new Parcel(node.World, node.WorldCoordinates, Vector2Int.one), smoothStep, ignoredSurfaces, ignoredTags); + } /// /// Smooths the ground area outside the given parcel so there are no hard edges around the parcel. @@ -15,7 +23,7 @@ public static class TerrainFunctions ///
Smooth step defines the targeted steepness along the smoothed area. ///
Returns the modified area as a new parcel. ///
- public static Parcel SmoothOutside(World world, Parcel parcel, int smoothStep = 1, List ignoredSurfaces = null) + public static Parcel SmoothOutside(World world, Parcel parcel, int smoothStep = 1, List ignoredSurfaces = null, List ignoredTags = null) { bool anotherLayerRequired; @@ -44,8 +52,8 @@ public static Parcel SmoothOutside(World world, Parcel parcel, int smoothStep = Vector2Int worldCoords = new Vector2Int(x, y); GroundNode groundNode = world.GetGroundNode(worldCoords); - if (groundNode == null) continue; - if (ignoredSurfaces != null && ignoredSurfaces.Contains(groundNode.SurfaceDef)) continue; + + if (ShouldSkip(groundNode, ignoredSurfaces, ignoredTags)) continue; // East if (isEastEdge && !isSouthEdge && !isNorthEdge) @@ -196,8 +204,8 @@ public static Parcel SmoothOutside(World world, Parcel parcel, int smoothStep = Vector2Int worldCoords = new Vector2Int(x, y); GroundNode groundNode = world.GetGroundNode(worldCoords); - if (groundNode == null) continue; - if (ignoredSurfaces != null && ignoredSurfaces.Contains(groundNode.SurfaceDef)) continue; + + if (ShouldSkip(groundNode, ignoredSurfaces, ignoredTags)) continue; // SW if (isWestEdge && isSouthEdge) @@ -325,5 +333,16 @@ public static Parcel SmoothOutside(World world, Parcel parcel, int smoothStep = return new Parcel(world, new Vector2Int(startX - 1, startY - 1), new Vector2Int((endX - startX) + 2, (endY - startY) + 2)); } + + private static bool ShouldSkip(GroundNode node, List ignoredSurfaces, List ignoredTags) + { + if (node == null) return true; + if (ignoredSurfaces != null && ignoredSurfaces.Contains(node.SurfaceDef)) return true; // Skip if nose has an ignored surface + if (ignoredTags != null && node.HasAnyOfTags(ignoredTags)) return true; // Skip if node has an ignored tag + if (ignoredTags != null && node.GetAdjacentGroundNodes8().Any(n => n.HasAnyOfTags(ignoredTags))) return true; // Skip if any adjacent node has an ignored tag + + return false; + } + } } diff --git a/Assets/Scripts/BlockmapFramework/MapGeneration/WorldGenerators/WorldGenerator_Desert.cs b/Assets/Scripts/BlockmapFramework/MapGeneration/WorldGenerators/WorldGenerator_Desert.cs index af0a4b06..43e9c2f0 100644 --- a/Assets/Scripts/BlockmapFramework/MapGeneration/WorldGenerators/WorldGenerator_Desert.cs +++ b/Assets/Scripts/BlockmapFramework/MapGeneration/WorldGenerators/WorldGenerator_Desert.cs @@ -1,3 +1,4 @@ +using CaptureTheFlag; using System.Collections; using System.Collections.Generic; using System.Linq; @@ -9,7 +10,7 @@ namespace BlockmapFramework.WorldGeneration public class WorldGenerator_Desert : WorldGenerator { public override string Label => "Desert"; - public override string Description => "Very sparse, flat and empty map with lots of sand."; + public override string Description => "A mainly sparse and flat and map with lots of sand. Scattered mesas and oasis can help or hinder moving around the otherwise harsh desert landscape"; private HashSet DuneNodes; private HashSet MesaNodes; @@ -29,7 +30,6 @@ public class WorldGenerator_Desert : WorldGenerator { EntityDefOf.Palm_Tree_01, 0.01f }, { EntityDefOf.Palm_Tree_02, 0.01f }, }; - private static Dictionary OasisPlantProbabilities = new Dictionary() { { EntityDefOf.Shrub_01, 0.2f }, @@ -44,7 +44,6 @@ public class WorldGenerator_Desert : WorldGenerator { EntityDefOf.Palm_Tree_01, 2f }, { EntityDefOf.Palm_Tree_02, 2f }, }; - private static Dictionary MesaEdgeRockProbabilities = new Dictionary() { { EntityDefOf.Rock_01_Small, 1f }, @@ -55,7 +54,6 @@ public class WorldGenerator_Desert : WorldGenerator { EntityDefOf.Rock_06_Small, 1f }, { EntityDefOf.Rock_01_Medium, 0.1f }, }; - private static Dictionary ScatteredObjectsProbabilities = new Dictionary() { { EntityDefOf.Rock_01_Small, 0.1f }, @@ -178,12 +176,19 @@ public static void SetBaseSurfaces(World world) var basePerlin = new PerlinNoise(0.05f); var layeredPerlin = new ModularGradientNoise(new GradientNoise[] { basePerlin }, new LayerOperation(5, 2f, 0.5f)); + var sandstoneNoise = new ModularGradientNoise(new GradientNoise[] { new PerlinNoise(0.05f) }, new LayerOperation(4, 2f, 0.5f)); + foreach (GroundNode node in world.GetAllGroundNodes()) { float surfaceNoiseValue = layeredPerlin.GetValue(node.WorldCoordinates.x, node.WorldCoordinates.y); + float sandstoneNoiseValue = sandstoneNoise.GetValue(node.WorldCoordinates.x, node.WorldCoordinates.y); - if (surfaceNoiseValue < 0.35f) node.SetSurface(SurfaceDefOf.SandSoft); - else node.SetSurface(SurfaceDefOf.Sand); + if (sandstoneNoiseValue > 0.72f) node.SetSurface(SurfaceDefOf.Sandstone); + else + { + if (surfaceNoiseValue < 0.35f) node.SetSurface(SurfaceDefOf.SandSoft); + else node.SetSurface(SurfaceDefOf.Sand); + } } } public static void AddDunes(World world, HashSet affectedCoordinates) @@ -276,6 +281,9 @@ public static void AddMesas(World world, HashSet affectedCoordinates int MESA_MIN_ALTITUDE = 10; int MESA_MAX_ALTITUDE = 17; + int MIN_SANDSTONE_RADIUS = 0; + int MAX_SANDSTONE_RADIUS = 8; + // Generate mesa mask noise (binary noise deciding which ground nodes are affected) var noise2 = new PerlinNoise(0.02f); var noise1_op_layer = new LayerOperation(2, 4f, 0.1f); @@ -294,6 +302,22 @@ public static void AddMesas(World world, HashSet affectedCoordinates var mesaNoise = new PerlinNoise(0.01f); int[,] mesaHeightMap = GetHeightMapFromNoise(world, mesaNoise, MESA_MIN_ALTITUDE, MESA_MAX_ALTITUDE); + // Generate mask noise for sandstone on top of mesas + var ssMain = new PerlinNoise(0.25f); + var ssLayered = new ModularGradientNoise( + new GradientNoise[] { ssMain }, + new LayerOperation(3, 0.5f, 2f) + ); + var ssCutOff = new ModularGradientNoise( + new GradientNoise[] { ssLayered }, + new CutoffOperation(0.5f, 0.5f) + ); + var ssInverse = new ModularGradientNoise( + new GradientNoise[] { ssCutOff }, + new InverseOperation() + ); + GradientNoise sandStoneMaskNoise = ssInverse; + for (int x = 0; x < world.NumNodesPerSide; x++) { for (int y = 0; y < world.NumNodesPerSide; y++) @@ -308,6 +332,10 @@ public static void AddMesas(World world, HashSet affectedCoordinates affectedCoordinates.Add(groundNode.WorldCoordinates); + // Sandstone surface + if (sandStoneMaskNoise.GetValue(x, y) >= 1f) groundNode.SetSurface(SurfaceDefOf.Sandstone); + + // Mesa Edge if(isMesaEdge && changeMade) { // Chance to randomly lower a mesa edge @@ -316,6 +344,20 @@ public static void AddMesas(World world, HashSet affectedCoordinates // Spawn a rock nearby EntityDef rock = MesaEdgeRockProbabilities.GetWeightedRandomElement(); EntityManager.SpawnEntityAround(world, rock, world.Gaia, groundNode.WorldCoordinates, standard_deviation: 2f, randomMirror: true, variantName: "Desert"); + + // Set surface to sandstone around edge + int radius = Random.Range(MIN_SANDSTONE_RADIUS, MAX_SANDSTONE_RADIUS + 1); + for (int dx = -radius; dx < radius + 1; dx++) + { + for (int dy = -radius; dy < radius + 1; dy++) + { + if (Mathf.Abs(dx) + Mathf.Abs(dy) > radius) continue; + if (Random.value > 0.6f) + { + world.SetSurface(world.GetGroundNode(x + dx, y + dy), SurfaceDefOf.Sandstone, updateWorld: false); + } + } + } } } } @@ -358,6 +400,7 @@ public static void AddSideMountain(World world, Direction side, HashSet forbiddenCoordinates = null) { int MIN_GRASS_RADIUS = 2; - int MAX_GRASS_RADIUS = 5; + int MAX_GRASS_RADIUS = 6; // Generate oasis mask noise (binary noise deciding which ground nodes are affected) var noise2 = new PerlinNoise(0.02f); @@ -449,7 +495,6 @@ public static void AddOasis(World world, HashSet forbiddenCoordinate // Lower ground GroundNode groundNode = world.GetGroundNode(new Vector2Int(x, y)); groundNode.SetAltitude(1); - TerrainFunctions.SmoothOutside(groundNode, smoothStep: 5); oasisCoordinates.Add(groundNode.WorldCoordinates); // Check if we're on an edge @@ -457,11 +502,15 @@ public static void AddOasis(World world, HashSet forbiddenCoordinate foreach(Direction side in HelperFunctions.GetSides()) { Vector2Int coords = HelperFunctions.GetCoordinatesInDirection(new Vector2Int(x, y), side); - if (oasisMaskNoise.GetValue(coords) < 1) isOasisEdge = true; + if (oasisMaskNoise.GetValue(coords) < 1 || forbiddenCoordinates.Contains(coords)) isOasisEdge = true; } + // Smooth edge + List ignoredTags = new List() { CtfMapGenerator.NO_SPAWN_TAG }; + if (isOasisEdge && !groundNode.GetAdjacentGroundNodes8().Any(n => n.HasAnyOfTags(ignoredTags))) TerrainFunctions.SmoothOutside(groundNode, smoothStep: 5, ignoredTags: ignoredTags); + // Spawn plants if on an edge - if(isOasisEdge) + if (isOasisEdge) { for (int i = 0; i < 2; i++) { diff --git a/Assets/Scripts/BlockmapFramework/Node/BlockmapNode.cs b/Assets/Scripts/BlockmapFramework/Node/BlockmapNode.cs index bc84b5f6..bfa7b4c2 100644 --- a/Assets/Scripts/BlockmapFramework/Node/BlockmapNode.cs +++ b/Assets/Scripts/BlockmapFramework/Node/BlockmapNode.cs @@ -55,6 +55,11 @@ public abstract class BlockmapNode : WorldDatabaseObject, IVisionTarget, ISaveAn ///
public int Steepness { get; protected set; } + /// + /// What tags this node has. Can be anything and be used for anything. Often set by world generators to mark specific nodes/areas. + /// + public HashSet Tags; + /// /// Shapes with the format "1010" or "0101" have two possible variants (center high or center low). This flag decides which variant is used in that case. /// @@ -150,6 +155,8 @@ protected BlockmapNode(World world, Chunk chunk, int id, Vector2Int localCoordin Altitude = height; SurfaceDef = surfaceDef; + Tags = new HashSet(); + Init(); } @@ -863,6 +870,11 @@ public void RemoveZone(Zone z) Zones.Remove(z); } + public void AddTag(string tag) + { + Tags.Add(tag); + } + #endregion #region Getters @@ -917,6 +929,9 @@ public bool HasEntityConnection(Direction dir, EntityDef def, int entityHeight) return false; } + public bool HasTag(string tag) => Tags.Contains(tag); + public bool HasAnyOfTags(List tags) => tags != null && tags.Any(t => Tags.Contains(t)); + /// /// Returns the local altitude on the node on the given local position (0f-1f) purely based on the nodes shape. /// @@ -1124,6 +1139,13 @@ public string DebugInfoLong() text += "\nWorld Mesh Y: " + World.HoveredNode.GetWorldMeshAltitude(new Vector2(World.HoveredWorldPosition.x - World.HoveredWorldCoordinates.x, World.HoveredWorldPosition.z - World.HoveredWorldCoordinates.y)); text += "\n" + mph; text += "\n" + headspace; + + string tags = ""; + foreach (string tag in Tags) tags += tag + ", "; + tags = tags.TrimEnd(','); + text += "\nTags: " + tags; + + if (Entities.Count > 0) text += $"\nis origin node of {Entities.Count} entities"; return text; @@ -1227,6 +1249,7 @@ public void ExposeDataForSaveAndLoad() SaveLoadManager.SaveOrLoadVector2Int(ref LocalCoordinates, "localCoordinates"); SaveLoadManager.SaveOrLoadAltitudeDictionary(ref Altitude); SaveLoadManager.SaveOrLoadDef(ref SurfaceDef, "surface"); + SaveLoadManager.SaveOrLoadStringHashSet(ref Tags, "tags"); } #endregion diff --git a/Assets/Scripts/BlockmapFramework/Node/GroundNode.cs b/Assets/Scripts/BlockmapFramework/Node/GroundNode.cs index 13e37a4b..5c0b3c30 100644 --- a/Assets/Scripts/BlockmapFramework/Node/GroundNode.cs +++ b/Assets/Scripts/BlockmapFramework/Node/GroundNode.cs @@ -203,6 +203,18 @@ public override bool IsImpassable() public bool IsCenterUnderWater => (WaterNode != null && MeshCenterWorldPosition.y < WaterNode.WaterBody.WaterSurfaceWorldHeight); + public List GetAdjacentGroundNodes8() + { + List adjNodes = new List(); + + foreach(Direction dir in HelperFunctions.GetAllDirections8()) + { + GroundNode adjNode = World.GetAdjacentGroundNode(this, dir); + if (adjNode != null) adjNodes.Add(adjNode); + } + return adjNodes; + } + #endregion } diff --git a/Assets/Scripts/CaptureTheFlag/Base/CtfGame.cs b/Assets/Scripts/CaptureTheFlag/Base/CtfGame.cs index fe5a3e42..b85f1a48 100644 --- a/Assets/Scripts/CaptureTheFlag/Base/CtfGame.cs +++ b/Assets/Scripts/CaptureTheFlag/Base/CtfGame.cs @@ -14,7 +14,7 @@ namespace CaptureTheFlag /// public class CtfGame : MonoBehaviour { - public static string VERSION = "0.0.6-dev"; + public static string VERSION = "0.0.6"; [Header("UIs")] public UI_MainMenu MainMenuUI; diff --git a/Assets/Scripts/CaptureTheFlag/Base/CtfMapGenerator.cs b/Assets/Scripts/CaptureTheFlag/Base/CtfMapGenerator.cs index 99cf48e7..7408df14 100644 --- a/Assets/Scripts/CaptureTheFlag/Base/CtfMapGenerator.cs +++ b/Assets/Scripts/CaptureTheFlag/Base/CtfMapGenerator.cs @@ -30,6 +30,9 @@ public static class CtfMapGenerator public const int JAIL_ZONE_MAX_FLAG_DISTANCE = 11; // maximum distance from jail zone center to flag public const float FLAG_ZONE_RADIUS = 7.5f; // Amount of tiles around flag that can't be entered by own team + // Tags + public const string NO_SPAWN_TAG = "NO_SPAWN"; + /// /// List containing all EntityDefs of the characters that each player gets. /// @@ -96,6 +99,8 @@ private static int GetRandomFlagDistanceFromMapEdge() /// private static void CreatePlayerSpawn(Actor player, bool isPlayer1) { + List forbiddenTags = new List() { NO_SPAWN_TAG }; + int flagSpawnOffset = GetRandomFlagDistanceFromMapEdge(); int spawnX = isPlayer1 ? flagSpawnOffset : World.NumNodesPerSide - flagSpawnOffset - 1; @@ -106,13 +111,13 @@ private static void CreatePlayerSpawn(Actor player, bool isPlayer1) Vector2Int spawnAreaCenter = new Vector2Int(spawnX, spawnY); // Flag - Entity spawnedFlag = EntityManager.SpawnEntityAround(World, EntityDefOf.Flag, player, spawnAreaCenter, 0f, maxAttempts: 50); + Entity spawnedFlag = EntityManager.SpawnEntityAround(World, EntityDefOf.Flag, player, spawnAreaCenter, 0f, maxAttempts: 50, forbiddenTags: forbiddenTags); int numAttempts = 0; while(spawnedFlag == null && numAttempts++ < 50) // Keep searching if first position wasn't valid (i.e. occupied by a tree) { spawnY = Random.Range(MIN_FLAG_MAP_EDGE_OFFSET_ABSOLUTE, World.NumNodesPerSide - MIN_FLAG_MAP_EDGE_OFFSET_ABSOLUTE); spawnAreaCenter = new Vector2Int(spawnX, spawnY); - spawnedFlag = EntityManager.SpawnEntityAround(World, EntityDefOf.Flag, player, spawnAreaCenter, 0f, maxAttempts: 50); + spawnedFlag = EntityManager.SpawnEntityAround(World, EntityDefOf.Flag, player, spawnAreaCenter, 0f, maxAttempts: 50, forbiddenTags: forbiddenTags); } CreatedEntities.Add(spawnedFlag); @@ -163,7 +168,7 @@ private static void CreatePlayerSpawn(Actor player, bool isPlayer1) { if (SpawnType == CharacterSpawnType.AroundFlag) { - Entity spawnedCharacter = EntityManager.SpawnEntityAround(World, characterDef, player, spawnAreaCenter, CHARACTER_SPAWN_VARIATION_AROUND_FLAG, forcedRotation: faceDirection, requiredRoamingArea: REQUIRED_SPAWN_ROAMING_AREA, forbiddenNodes: flagZone.Nodes, maxAttempts: 50); + Entity spawnedCharacter = EntityManager.SpawnEntityAround(World, characterDef, player, spawnAreaCenter, CHARACTER_SPAWN_VARIATION_AROUND_FLAG, forcedRotation: faceDirection, requiredRoamingArea: REQUIRED_SPAWN_ROAMING_AREA, forbiddenNodes: flagZone.Nodes, maxAttempts: 50, forbiddenTags: forbiddenTags); if (spawnedCharacter == null) throw new System.Exception($"Failed to spawn character"); CreatedEntities.Add(spawnedCharacter); } @@ -176,7 +181,7 @@ private static void CreatePlayerSpawn(Actor player, bool isPlayer1) int minY = 0; int maxY = World.NumNodesPerSide - 1; - Entity spawnedCharacter = EntityManager.SpawnEntityWithin(World, characterDef, player, minX, maxX, minY, maxY, forcedRotation: faceDirection, requiredRoamingArea: REQUIRED_SPAWN_ROAMING_AREA, forbiddenNodes: flagZone.Nodes, maxAttempts: 50); + Entity spawnedCharacter = EntityManager.SpawnEntityWithin(World, characterDef, player, minX, maxX, minY, maxY, forcedRotation: faceDirection, requiredRoamingArea: REQUIRED_SPAWN_ROAMING_AREA, forbiddenNodes: flagZone.Nodes, maxAttempts: 50, forbiddenTags: forbiddenTags); if (spawnedCharacter == null) throw new System.Exception($"Failed to spawn character"); CreatedEntities.Add(spawnedCharacter); } diff --git a/Assets/Scripts/CaptureTheFlag/Defs/EntityDefs.cs b/Assets/Scripts/CaptureTheFlag/Defs/EntityDefs.cs index 262afdf3..9e1b16a0 100644 --- a/Assets/Scripts/CaptureTheFlag/Defs/EntityDefs.cs +++ b/Assets/Scripts/CaptureTheFlag/Defs/EntityDefs.cs @@ -150,7 +150,7 @@ public static List CharacterDefs DefName = "Dog1", Label = "chevap", Description = "Bigger dog that can run fast, but low vision and needs to rest for longer periods of time.", - Dimensions = new Vector3Int(1, 4, 1), + Dimensions = new Vector3Int(1, 2, 1), }; blotto.RenderProperties.ModelScale = new Vector3(1f, 1.8f, 1f); blotto.GetCompProperties().InitialSkillLevels["Vaulting"] = 10; diff --git a/Assets/Scripts/CaptureTheFlag/Defs/StatDefs.cs b/Assets/Scripts/CaptureTheFlag/Defs/StatDefs.cs index 887fd0cf..ab96ea71 100644 --- a/Assets/Scripts/CaptureTheFlag/Defs/StatDefs.cs +++ b/Assets/Scripts/CaptureTheFlag/Defs/StatDefs.cs @@ -13,16 +13,8 @@ public static class StatDefs { DefName = "MovementSpeed", Label = "movement speed", - Description = "How many tiles per second this character moves when running.", - BaseValue = 0f, - SkillOffsets = new List() - { - new SkillImpact() - { - SkillDefName = "Running", - LinearPerLevelValue = 0.2f, - } - }, + Description = "The base amount tiles per second this character moves when running.\nThe actual is speed is also influenced by the running aptitude.", + BaseValue = 2f }, new StatDef() @@ -37,7 +29,7 @@ public static class StatDefs new SkillImpact() { SkillDefName = "Running", - LinearPerLevelValue = 0.15f, + LinearPerLevelValue = 0.1f, } }, }, diff --git a/Assets/doc.txt b/Assets/doc.txt index 9c09c339..eef6a7f2 100644 --- a/Assets/doc.txt +++ b/Assets/doc.txt @@ -1,18 +1,3 @@ -------------------------- CTF v0.0.6 ------------------------- - -(/)make path cost take slopes into consideration, down *1.2, up *1.5 per cell -(/)rename stamina skill to endurance -(/)characters can run further in a turn - -desert: -no spawn on mountains -sandstone areas on mesas and rare otherwise -more random grass around oasis - -new screenshots and description to github - -------------------------- END CTF v0.0.6 ------------------------- - ------------------------- CTF v0.0.7 ------------------------- Add Items to CTF: