Skip to content

Commit

Permalink
improved desert generator, fixed characters spawning in ground, fix m…
Browse files Browse the repository at this point in the history
…ovement speed bug, fix vision collider sizes on static entities
  • Loading branch information
p-svacha committed Jan 12, 2025
1 parent 19c758d commit de60561
Show file tree
Hide file tree
Showing 16 changed files with 196 additions and 71 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
5 changes: 5 additions & 0 deletions Assets/Scripts/BlockmapFramework/Base/Def/EntityDef.cs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,11 @@ public EntityDef(EntityDef orig)
}


public bool HasCompProperties<T>()
{
return Components != null && Components.Any(c => c is T);
}

/// <summary>
/// Retrieve specific CompProperties of this def. Throws an error if it doesn't have it.
/// </summary>
Expand Down
23 changes: 23 additions & 0 deletions Assets/Scripts/BlockmapFramework/Base/SaveLoadManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -335,6 +336,28 @@ public static void SaveOrLoadList<T>(ref List<T> list, string label) where T : c
}
}

/// <summary>
/// Save and load a list.
/// </summary>
public static void SaveOrLoadStringHashSet(ref HashSet<string> 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();
}
}
}

/// <summary>
/// Save and load an int-keyed dictionary with objects as values.
/// </summary>
Expand Down
5 changes: 0 additions & 5 deletions Assets/Scripts/BlockmapFramework/Base/World.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1255,9 +1255,6 @@ public bool CanSpawnEntity(EntityDef def, BlockmapNode node, Direction rotation,

return true;
}
/// <summary>
/// Creates a new entity from a def, registers it in the world and updates the world, navmesh and vision around it.
/// </summary>
public Entity SpawnEntity(EntityDef def, BlockmapNode node, Direction rotation, bool isMirrored, Actor actor, bool updateWorld, int height = -1, System.Action<Entity> preInit = null, int variantIndex = 0)
{
if (actor == null) throw new System.Exception("Cannot spawn an entity without an actor");
Expand Down Expand Up @@ -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
Expand All @@ -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);

}
Expand Down
8 changes: 4 additions & 4 deletions Assets/Scripts/BlockmapFramework/Defs/GlobalSurfaceDefs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
4 changes: 1 addition & 3 deletions Assets/Scripts/BlockmapFramework/Entity/Entity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<BoxCollider>();
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<WorldObjectCollider>();
Expand Down
40 changes: 29 additions & 11 deletions Assets/Scripts/BlockmapFramework/Entity/EntityManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<BlockmapNode> 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<CompProperties_Movement>())
{
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<BlockmapNode> 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;
}
Expand Down Expand Up @@ -209,7 +222,7 @@ public static Vector2Int GetTranslatedPosition(Vector2Int position, Vector2Int d
/// <summary>
/// Spawns an entity on a random node near the given point and returns the entity instance.
/// </summary>
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<BlockmapNode> 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<BlockmapNode> forbiddenNodes = null, string variantName = "", bool randomMirror = false, List<string> forbiddenTags = null)
{
if (standard_deviation == 0f) maxAttempts = 1;
int numAttempts = 0;
Expand All @@ -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;
}

Expand All @@ -231,7 +244,7 @@ public static Entity SpawnEntityAround(World world, EntityDef def, Actor player,
/// <summary>
/// Spawns an entity within a given area in the world and returns the entity instance.
/// </summary>
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<BlockmapNode> 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<BlockmapNode> forbiddenNodes = null, string variantName = "", bool randomMirror = false, List<string> forbiddenTags = null)
{
int numAttempts = 0;
while (numAttempts++ < maxAttempts) // Keep searching until we find a suitable position
Expand All @@ -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;
}

Expand All @@ -251,12 +264,17 @@ public static Entity SpawnEntityWithin(World world, EntityDef def, Actor player,
/// <summary>
/// Tries spawning an entity on a random node on the given coordinates with all the given restrictions.
/// <br/>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<BlockmapNode> forbiddenNodes = null)
private static Entity TrySpawnEntity(World world, EntityDef def, Actor player, Vector2Int worldCoordinates, Direction rotation, bool isMirrored, string variantName = "", int requiredRoamingArea = -1, List<BlockmapNode> forbiddenNodes = null, List<string> 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;
Expand Down
31 changes: 25 additions & 6 deletions Assets/Scripts/BlockmapFramework/MapGeneration/TerrainFunctions.cs
Original file line number Diff line number Diff line change
@@ -1,21 +1,29 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

namespace BlockmapFramework.WorldGeneration
{
public static class TerrainFunctions
{

public static void SmoothOutside(BlockmapNode node, int smoothStep, List<SurfaceDef> ignoredSurfaces = null) => SmoothOutside(node.World, new Parcel(node.World, node.WorldCoordinates, Vector2Int.one), smoothStep, ignoredSurfaces);
public static void SmoothOutside(GroundNode node, int smoothStep, List<SurfaceDef> ignoredSurfaces = null, List<string> 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);
}

/// <summary>
/// Smooths the ground area outside the given parcel so there are no hard edges around the parcel.
/// <br/>Starts with the ring outside the parcel and then goes more and more outside until there are no gaps left.
/// <br/>Smooth step defines the targeted steepness along the smoothed area.
/// <br/>Returns the modified area as a new parcel.
/// </summary>
public static Parcel SmoothOutside(World world, Parcel parcel, int smoothStep = 1, List<SurfaceDef> ignoredSurfaces = null)
public static Parcel SmoothOutside(World world, Parcel parcel, int smoothStep = 1, List<SurfaceDef> ignoredSurfaces = null, List<string> ignoredTags = null)
{
bool anotherLayerRequired;

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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<SurfaceDef> ignoredSurfaces, List<string> 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;
}

}
}
Loading

0 comments on commit de60561

Please # to comment.