Traveller/scripts/map/MapGenerator.cs
2024-10-11 17:59:47 +08:00

387 lines
14 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Collections.Generic;
using System.Threading.Tasks;
using ColdMint.scripts.debug;
using ColdMint.scripts.levelGraphEditor;
using ColdMint.scripts.map.dateBean;
using ColdMint.scripts.map.events;
using ColdMint.scripts.map.interfaces;
using ColdMint.scripts.map.LayoutParsingStrategy;
using ColdMint.scripts.map.layoutStrategy;
using ColdMint.scripts.map.room;
using ColdMint.scripts.serialization;
using ColdMint.scripts.utils;
using Godot;
namespace ColdMint.scripts.map;
/// <summary>
/// <para>Map generator</para>
/// <para>地图生成器</para>
/// </summary>
/// <remarks>
///<para>Responsible for the overall map generation process control</para>
///<para>负责地图的整体生成流程控制</para>
/// </remarks>
public static class MapGenerator
{
/// <summary>
/// <para>Layout map selection strategy</para>
/// <para>布局图选择策略</para>
/// </summary>
private static ILayoutStrategy? _layoutStrategy;
private static bool _running;
/// <summary>
/// <para>Map root node</para>
/// <para>地图根节点</para>
/// </summary>
private static Node? _mapRoot;
/// <summary>
/// <para>Room placement strategy</para>
/// <para>房间的放置策略</para>
/// </summary>
private static IRoomPlacementStrategy? _roomPlacementStrategy;
private static ulong _seed;
private static Dictionary<string, IRoomInjectionProcessor>? _roomInjectionProcessorsDictionary;
/// <summary>
/// <para>Register the room injection processor</para>
/// <para>注册房间注入处理器</para>
/// </summary>
/// <param name="roomInjectionProcessor"></param>
/// <returns></returns>
public static bool RegisterRoomInjectionProcessor(IRoomInjectionProcessor roomInjectionProcessor)
{
var key = roomInjectionProcessor.GetId();
if (_roomInjectionProcessorsDictionary == null)
{
_roomInjectionProcessorsDictionary = new Dictionary<string, IRoomInjectionProcessor>
{
{
key, roomInjectionProcessor
}
};
return true;
}
return _roomInjectionProcessorsDictionary.TryAdd(key, roomInjectionProcessor);
}
/// <summary>
/// <para>Log out of the room injection processor</para>
/// <para>注销房间注入处理器</para>
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public static bool UnRegisterRoomInjectionProcessor(string id)
{
if (_roomInjectionProcessorsDictionary == null)
{
return false;
}
return _roomInjectionProcessorsDictionary.Remove(id);
}
/// <summary>
/// <para>Set seed</para>
/// <para>设置种子</para>
/// </summary>
public static string Seed
{
get => _seed.ToString();
//If the player inputs integers, we seed them directly with the input values. If it is not an integer, the hash value is taken.
//如果玩家输入的是整数,那么我们直接用输入值作为种子。如果不是整数,则取哈希值。
set => _seed = ulong.TryParse(value, out var result) ? result : HashCodeUtils.GetFixedHashCode(value);
}
/// <summary>
/// <para>Layout diagram parsing policy</para>
/// <para>布局图解析策略</para>
/// </summary>
private static ILayoutParsingStrategy? _layoutParsingStrategy;
public static Node? MapRoot
{
get => _mapRoot;
set => _mapRoot = value;
}
public static ILayoutParsingStrategy? LayoutParsingStrategy
{
get => _layoutParsingStrategy;
set => _layoutParsingStrategy = value;
}
public static IRoomPlacementStrategy? RoomPlacementStrategy
{
get => _roomPlacementStrategy;
set => _roomPlacementStrategy = value;
}
public static ILayoutStrategy? LayoutStrategy
{
get => _layoutStrategy;
set => _layoutStrategy = value;
}
/// <summary>
/// <para>Generating a map</para>
/// <para>生成地图</para>
/// </summary>
public static async Task GenerateMap()
{
if (_running)
{
LogCat.LogWarning("map_generator_is_running");
return;
}
_running = true;
EventBus.MapGenerationStartEvent?.Invoke(new MapGenerationStartEvent());
if (_layoutStrategy == null || _roomPlacementStrategy == null || _layoutParsingStrategy == null ||
_mapRoot == null)
{
LogCat.LogError("map_generator_missing_parameters");
_running = false;
return;
}
if (GameSceneDepend.AiCharacterContainer != null)
{
NodeUtils.DeleteAllChild(GameSceneDepend.AiCharacterContainer);
}
NodeUtils.DeleteAllChild(_mapRoot);
if (!await _roomPlacementStrategy.StartGeneration(_mapRoot))
{
LogCat.LogError("room_placement_strategy_terminates_map_generation");
_running = false;
return;
}
//Get the layout data
//拿到布局图数据
var levelGraphEditorSaveData = await _layoutStrategy.GetLayout();
if (levelGraphEditorSaveData == null || levelGraphEditorSaveData.RoomNodeDataList == null ||
levelGraphEditorSaveData.RoomNodeDataList.Count == 0)
{
LogCat.LogError("map_generator_attempts_to_parse_empty_layout_diagrams");
_running = false;
return;
}
_layoutParsingStrategy.SetLevelGraph(levelGraphEditorSaveData);
//Save the dictionary, put the ID in the room data, corresponding to the successful placement of the room.
//保存字典将房间数据内的ID对应放置成功的房间。
var roomDictionary = new Dictionary<string, Room>();
var randomNumberGenerator = new RandomNumberGenerator();
randomNumberGenerator.Seed = _seed;
var startRoomNodeData = await _layoutParsingStrategy.GetStartRoomNodeData();
if (startRoomNodeData == null || string.IsNullOrEmpty(startRoomNodeData.Id))
{
LogCat.LogError("map_generator_has_no_starting_room_data");
_running = false;
return;
}
var startingRoomPlacementData =
await _roomPlacementStrategy.CalculatePlacementDataForStartingRoom(randomNumberGenerator,
startRoomNodeData);
if (startingRoomPlacementData == null)
{
LogCat.LogError("start_room_placement_information_returns_empty");
_running = false;
return;
}
var placeSuccess = await PlaceRoomAndAddRecord(startRoomNodeData.Id, startingRoomPlacementData, roomDictionary);
if (!placeSuccess)
{
LogCat.LogError("start_room_placement_failed");
_running = false;
return;
}
while (await _layoutParsingStrategy.HasNext())
{
//When a new room needs to be placed
//当有新的房间需要放置时
var roomNodeData = await _layoutParsingStrategy.Next();
if (roomNodeData == null || string.IsNullOrEmpty(roomNodeData.Id))
{
LogCat.LogWarning("room_data_missing");
continue;
}
var roomInjectionProcessorData = roomNodeData.RoomInjectionProcessorData;
//Whether room can be placed
//是否可放置房间
var canBePlaced = true;
if (_roomInjectionProcessorsDictionary != null && !string.IsNullOrEmpty(roomInjectionProcessorData))
{
var roomInjectionProcessorDataArray =
YamlSerialization.Deserialize<RoomInjectionProcessorData[]>(roomInjectionProcessorData);
if (roomInjectionProcessorDataArray is { Length: > 0 })
{
foreach (var injectionProcessorData in roomInjectionProcessorDataArray)
{
if (string.IsNullOrEmpty(injectionProcessorData.Id) ||
string.IsNullOrEmpty(injectionProcessorData.Config))
{
//The data is incomplete, and the injectionProcessorData is ignored.
//数据不全忽略injectionProcessorData。
continue;
}
if (!_roomInjectionProcessorsDictionary.TryGetValue(injectionProcessorData.Id,
out var roomInjectionProcessor))
{
//If the room injection processor cannot be found, a print error occurs.
//如果找不到房间注入处理器,那么打印错误。
LogCat.LogErrorWithFormat("room_injection_processor_does_not_exist",
LogCat.LogLabel.Default, injectionProcessorData.Id);
continue;
}
if (await roomInjectionProcessor.CanBePlaced(randomNumberGenerator,
injectionProcessorData.Config)) continue;
//If the room cannot be placed, then out of the loop.
//如果此房间不能被放置,那么跳出循环。
canBePlaced = false;
break;
}
}
}
if (!canBePlaced)
{
continue;
}
var nextParentNodeId = await _layoutParsingStrategy.GetNextParentNodeId();
Room? parentRoomNode = null;
if (nextParentNodeId != null)
{
//If the new room has the parent's ID, then we pass the parent's room into the compute function.
//如果新房间有父节点的ID那么我们将父节点的房间传入到计算函数内。
if (roomDictionary.TryGetValue(nextParentNodeId, out var value))
{
parentRoomNode = value;
}
}
var roomPlacementData =
await _roomPlacementStrategy.CalculateNewRoomPlacementData(randomNumberGenerator, parentRoomNode,
roomNodeData);
if (roomPlacementData == null)
{
LogCat.LogWithFormat("failed_to_calculate_the_room_location", LogCat.LogLabel.Default,
roomNodeData.Id);
continue;
}
if (await PlaceRoomAndAddRecord(roomNodeData.Id, roomPlacementData, roomDictionary))
{
MarkRoomSlot(roomPlacementData);
}
}
//Place barriers
//放置屏障
foreach (var roomDictionaryValue in roomDictionary.Values)
{
PlaceBarrier(roomDictionaryValue);
}
//All rooms have been placed.
//所有房间已放置完毕。
await _roomPlacementStrategy.GeneratedComplete(_mapRoot);
_running = false;
//Invoke the map generation completion event
//调用地图生成完成事件
var eventObj = new MapGenerationCompleteEvent
{
RandomNumberGenerator = randomNumberGenerator,
RoomDictionary = roomDictionary
};
EventBus.MapGenerationCompleteEvent?.Invoke(eventObj);
}
/// <summary>
/// <para>Place barriers</para>
/// <para>放置屏障</para>
/// </summary>
/// <param name="room"></param>
/// <returns></returns>
private static void PlaceBarrier(Room? room)
{
if (room == null)
{
return;
}
room.PlaceBarrierInUnmatchedSlots();
}
/// <summary>
/// <para>Mark the room slot as matched</para>
/// <para>将房间槽标记为已匹配</para>
/// </summary>
/// <param name="roomPlacementData"></param>
private static void MarkRoomSlot(RoomPlacementData roomPlacementData)
{
if (roomPlacementData.ParentRoomSlot != null)
{
roomPlacementData.ParentRoomSlot.Matched = true;
}
if (roomPlacementData.NewRoomSlot != null)
{
roomPlacementData.NewRoomSlot.Matched = true;
}
}
/// <summary>
/// <para>Place rooms and add mappings</para>
/// <para>放置房间,并增加映射</para>
/// </summary>
/// <param name="roomNodeDataId"></param>
/// <param name="roomPlacementData"></param>
/// <param name="dictionary"></param>
/// <returns></returns>
private static async Task<bool> PlaceRoomAndAddRecord(string roomNodeDataId,
RoomPlacementData roomPlacementData, Dictionary<string, Room> dictionary)
{
//The input parameters are incomplete.
//输入参数不全。
if (_roomPlacementStrategy == null || _mapRoot == null || string.IsNullOrEmpty(roomNodeDataId) ||
roomPlacementData.NewRoom == null)
{
return false;
}
if (dictionary.ContainsKey(roomNodeDataId))
{
LogCat.LogWithFormat("place_existing_rooms", LogCat.LogLabel.Default, roomNodeDataId);
return false;
}
if (!await _roomPlacementStrategy.PlaceRoom(_mapRoot, roomPlacementData))
{
LogCat.LogWarningWithFormat("room_placement_failed", LogCat.LogLabel.Default,
roomNodeDataId);
return false;
}
dictionary.Add(roomNodeDataId, roomPlacementData.NewRoom);
LogCat.LogWithFormat("room_placement_information", LogCat.LogLabel.Default, roomNodeDataId,
roomPlacementData.Position.ToString());
return true;
}
}