Files
RuinAdventurer/Scripts/World/World.cs
T

311 lines
8.2 KiB
C#

using Godot;
using System.Collections.Generic;
using System.Linq;
using static GameData;
public partial class World : Node3D
{
public Dictionary<string, MeshInstance3D> tileMeshes;
public Dictionary<string, MeshInstance3D> contentMeshes;
public Dictionary<string, List<Placeholder>> tilePlaceholders;
private PackedScene layerPrefab = ResourceLoader.LoadLayerPrefab();
private Dictionary<string, MultiMeshInstance3D> multiMeshes = new Dictionary<string, MultiMeshInstance3D>();
private Dictionary<string, Mesh> meshLibrary = new Dictionary<string, Mesh>();
private MultiMeshHandler multiMeshHandler;
public override void _Ready()
{
ResetRunState();
WFC.FillAdjacencies();
tileMeshes = ResourceLoader.LoadTiles();
contentMeshes = ResourceLoader.LoadDecorations();
tilePlaceholders = new Dictionary<string, List<Placeholder>>();
foreach (KeyValuePair<string, MeshInstance3D> kvp in tileMeshes)
{
tilePlaceholders[kvp.Key] = new List<Placeholder>();
foreach (Node3D child in kvp.Value.GetChildren())
{
tilePlaceholders[kvp.Key].Add(new Placeholder(child.Name, child.Transform));
}
meshLibrary[kvp.Key] = kvp.Value.Mesh;
kvp.Value.QueueFree();
}
multiMeshes = CreateMultiMeshes(meshLibrary);
multiMeshHandler = new MultiMeshHandler(multiMeshes);
map = new Layer[ruinSize];
GenerateWorld();
Pathfinding.BuildAStarGraph();
HandleRenderData(BuildRenderData(0));
Robot robot = ResourceLoader.LoadRobotPrefab().Instantiate<Robot>();
robot.Name = "Bob";
robot.Position = map[0].tiles[0, 0].Position;
AddChild(robot);
robots.Add(robot);
SetGateRequirements();
}
private Dictionary<string, MultiMeshInstance3D> CreateMultiMeshes(Dictionary<string, Mesh> meshLibrary)
{
Dictionary<string, MultiMeshInstance3D> result = new Dictionary<string, MultiMeshInstance3D>();
foreach (KeyValuePair<string, Mesh> kvp in meshLibrary)
{
MultiMesh multiMesh = new MultiMesh
{
Mesh = kvp.Value,
TransformFormat = MultiMesh.TransformFormatEnum.Transform3D
};
MultiMeshInstance3D instance = new MultiMeshInstance3D
{
Multimesh = multiMesh
};
AddChild(instance);
result[kvp.Key] = instance;
}
return result;
}
public override void _Process(double delta)
{
GameData.survival.Update(delta);
if (!canMove) return;
if (Input.IsActionJustPressed("layer_up") && currentLayer > 0) currentLayer--;
if (Input.IsActionJustPressed("layer_down") && currentLayer < ruinSize - 1 && map[currentLayer].isGateOpen) currentLayer++;
if (currentLayer != visibleLayer)
{
ShowLayer(currentLayer);
}
}
private void ResetRunState()
{
survival = new SurvivalState();
robotStats = new RobotStats();
inventory = new Inventory();
availableResearch = ResourceLoader.LoadResearch();
robots.Clear();
currentLayer = 0;
visibleLayer = 0;
lowestLayer = 0;
canMove = true;
}
private void GenerateWorld()
{
for (int layer = 0; layer < ruinSize; layer++)
{
Layer layerNode = layerPrefab.Instantiate<Layer>();
AddChild(layerNode);
if (layer == 0)
{
layerNode.SetupLayer(layerSize, layer, tileMeshes, new Vector2I());
}
else
{
layerNode.SetupLayer(layerSize, layer, tileMeshes, map[layer - 1].gateCoordinate);
}
map[layer] = layerNode;
}
SetupSpawn();
}
private void ShowLayer(int layer)
{
map[visibleLayer].ClearDecorations();
HandleRenderData(BuildRenderData(layer));
visibleLayer = layer;
}
private void SetupSpawn()
{
map[0].tiles[0, 0].wasVisited = true;
map[0].tiles[0, 0].containsDecoration = true;
map[0].tiles[0, 0].containsLight = true;
map[0].tiles[0, 0].containsResource = false;
map[0].tiles[0, 0].ContentNode.Visible = true;
}
private List<TileRenderData> BuildRenderData(int layerIndex)
{
List<TileRenderData> result = new List<TileRenderData>();
Layer layer = map[layerIndex];
layer.ClearDecorations();
for (int x = 0; x < layerSize; x++)
{
for (int y = 0; y < layerSize; y++)
{
Tile tile = layer.tiles[x, y];
result.Add(new TileRenderData
{
Tile = tile,
MeshKey = tile.collapsedMesh,
Transform = new Transform3D(Basis.Identity, tile.Position),
Placeholders = tilePlaceholders[tile.collapsedMesh]
});
}
}
if (!layer.hasContentGenerated)
{
DistributeTileContent(layer);
layer.hasContentGenerated = true;
}
return result;
}
private void DistributeTileContent(Layer layer)
{
int currentDecoration = 0;
int currentLight = 0;
int currentResource = 0;
int posX, posY;
while (currentLight < layerSize * 3)
{
posX = rand.Next(layerSize);
posY = rand.Next(layerSize);
if (CannotContainLight(layer.tiles[posX, posY])) continue;
if (layer.tiles[posX, posY].containsLight) continue;
layer.tiles[posX, posY].containsLight = true;
currentLight++;
}
while (currentDecoration < layerSize)
{
posX = rand.Next(layerSize);
posY = rand.Next(layerSize);
if (layer.tiles[posX, posY].containsDecoration) continue;
layer.tiles[posX, posY].containsDecoration = true;
currentDecoration++;
}
while (currentResource < layerSize)
{
posX = rand.Next(layerSize);
posY = rand.Next(layerSize);
if (layer.tiles[posX, posY].containsResource) continue;
layer.tiles[posX, posY].containsResource = true;
layer.tiles[posX, posY].resource = new GameResource(ResourceDistributor.GetResource(layer.level));
layer.currentResources.Add(layer.tiles[posX, posY].resource.name);
currentResource++;
}
}
private bool CannotContainLight(Tile tile)
{
return tile.collapsedMesh == "junction" || tile.collapsedMesh == "gate";
}
private void HandleRenderData(List<TileRenderData> renderData)
{
multiMeshHandler.Build(renderData);
foreach (TileRenderData data in renderData)
{
data.Tile.SpawnContent(contentMeshes, data.Transform, data.Placeholders);
}
}
private void SetGateRequirements()
{
List<string> availableResources = new List<string>();
List<ItemData> possibleIngredients;
bool canCraft;
double highestCraftTime;
double lowestCraftTime;
foreach (Layer layer in map)
{
highestCraftTime = 0;
lowestCraftTime = double.MaxValue;
possibleIngredients = new List<ItemData>();
//Step 1: Determine all possible resources for this and all previous layers combined
foreach (string resource in layer.currentResources)
{
if (availableResources.Contains(resource)) continue;
availableResources.Add(resource);
}
//Step 2: Check which items can be crafted with those items, repeat until no further items are added to the list
bool addedNewItem;
do
{
addedNewItem = false;
foreach (ItemData item in availableItems.Values)
{
if (possibleIngredients.Any(existing => existing.Id == item.Id))
continue;
canCraft = item.Inputs.All(input => availableResources.Contains(input.Item));
if (!canCraft)
continue;
possibleIngredients.Add(item);
availableResources.Add(item.Id);
lowestCraftTime = Mathf.Min(lowestCraftTime, item.CraftTime);
highestCraftTime = Mathf.Max(highestCraftTime, item.CraftTime);
addedNewItem = true;
}
} while (addedNewItem);
//Step 3: Choose gate items needed based on crafting time and layer it is for (Lower layers -> More advanced items -> More crafting time)
double goalCraftTime = Mathf.Lerp(lowestCraftTime, highestCraftTime, Mathf.Clamp(layer.level/(float)ruinSize, 0, 1));
int ingredientAmount = rand.Next(1, 1 + ruinSize / 2);
float craftTimeModifier = 0f;
double craftTimeLower, craftTimeUpper;
for (int i = 0; i < ingredientAmount; i++)
{
craftTimeLower = goalCraftTime - goalCraftTime * craftTimeModifier;
craftTimeUpper = goalCraftTime + goalCraftTime * craftTimeModifier;
List<ItemData> validIngredients = possibleIngredients
.Where(item =>
item.CraftTime >= craftTimeLower &&
item.CraftTime <= craftTimeUpper &&
!layer.gateIngredients.Any(ingredient => ingredient.Item == item.Id))
.ToList();
if (validIngredients.Count == 0)
{
i--;
craftTimeModifier += 0.05f;
continue;
}
ItemData item = validIngredients[rand.Next(validIngredients.Count)];
layer.gateIngredients.Add(new Ingredient
{
Item = item.Id,
Amount = rand.Next(5 + layer.level, 20 + layer.level)
});
craftTimeModifier = 0f;
}
}
}
}