2024-06-05 13:38:45 +00:00
|
|
|
|
using System.Collections.Generic;
|
2024-05-19 12:29:32 +00:00
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
using ColdMint.scripts.debug;
|
2024-05-30 14:49:54 +00:00
|
|
|
|
using ColdMint.scripts.levelGraphEditor;
|
2024-05-21 14:50:33 +00:00
|
|
|
|
using ColdMint.scripts.map.dateBean;
|
2024-05-28 14:14:35 +00:00
|
|
|
|
using ColdMint.scripts.map.events;
|
2024-05-19 12:29:32 +00:00
|
|
|
|
using ColdMint.scripts.map.interfaces;
|
2024-05-19 14:32:34 +00:00
|
|
|
|
using ColdMint.scripts.map.LayoutParsingStrategy;
|
|
|
|
|
using ColdMint.scripts.map.layoutStrategy;
|
2024-05-20 14:38:41 +00:00
|
|
|
|
using ColdMint.scripts.map.room;
|
2024-05-30 14:49:54 +00:00
|
|
|
|
using ColdMint.scripts.serialization;
|
2024-05-21 14:50:33 +00:00
|
|
|
|
using ColdMint.scripts.utils;
|
2024-05-20 14:38:41 +00:00
|
|
|
|
using Godot;
|
2024-04-28 13:55:19 +00:00
|
|
|
|
|
|
|
|
|
namespace ColdMint.scripts.map;
|
|
|
|
|
|
2024-05-18 15:35:12 +00:00
|
|
|
|
/// <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
|
2024-04-28 13:55:19 +00:00
|
|
|
|
{
|
2024-05-08 10:22:04 +00:00
|
|
|
|
/// <summary>
|
2024-05-18 15:35:12 +00:00
|
|
|
|
/// <para>Layout map selection strategy</para>
|
|
|
|
|
/// <para>布局图选择策略</para>
|
2024-05-08 10:22:04 +00:00
|
|
|
|
/// </summary>
|
2024-05-18 15:35:12 +00:00
|
|
|
|
private static ILayoutStrategy? _layoutStrategy;
|
|
|
|
|
|
2024-05-26 03:10:21 +00:00
|
|
|
|
private static bool _running;
|
|
|
|
|
|
2024-05-20 14:38:41 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// <para>Map root node</para>
|
|
|
|
|
/// <para>地图根节点</para>
|
|
|
|
|
/// </summary>
|
|
|
|
|
private static Node? _mapRoot;
|
|
|
|
|
|
2024-05-19 12:29:32 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// <para>Room placement strategy</para>
|
|
|
|
|
/// <para>房间的放置策略</para>
|
|
|
|
|
/// </summary>
|
|
|
|
|
private static IRoomPlacementStrategy? _roomPlacementStrategy;
|
|
|
|
|
|
2024-05-21 14:50:33 +00:00
|
|
|
|
private static ulong _seed;
|
|
|
|
|
|
2024-05-28 14:14:35 +00:00
|
|
|
|
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);
|
|
|
|
|
}
|
2024-05-30 14:49:54 +00:00
|
|
|
|
|
2024-05-28 14:14:35 +00:00
|
|
|
|
/// <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;
|
|
|
|
|
}
|
2024-05-30 14:49:54 +00:00
|
|
|
|
|
2024-05-28 14:14:35 +00:00
|
|
|
|
return _roomInjectionProcessorsDictionary.Remove(id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2024-05-21 14:50:33 +00:00
|
|
|
|
/// <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);
|
|
|
|
|
}
|
2024-05-19 12:29:32 +00:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// <para>Layout diagram parsing policy</para>
|
|
|
|
|
/// <para>布局图解析策略</para>
|
|
|
|
|
/// </summary>
|
|
|
|
|
private static ILayoutParsingStrategy? _layoutParsingStrategy;
|
|
|
|
|
|
2024-05-20 14:38:41 +00:00
|
|
|
|
public static Node? MapRoot
|
|
|
|
|
{
|
|
|
|
|
get => _mapRoot;
|
|
|
|
|
set => _mapRoot = value;
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-19 12:29:32 +00:00
|
|
|
|
public static ILayoutParsingStrategy? LayoutParsingStrategy
|
|
|
|
|
{
|
|
|
|
|
get => _layoutParsingStrategy;
|
|
|
|
|
set => _layoutParsingStrategy = value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static IRoomPlacementStrategy? RoomPlacementStrategy
|
|
|
|
|
{
|
|
|
|
|
get => _roomPlacementStrategy;
|
|
|
|
|
set => _roomPlacementStrategy = value;
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-18 15:35:12 +00:00
|
|
|
|
public static ILayoutStrategy? LayoutStrategy
|
2024-05-08 10:22:04 +00:00
|
|
|
|
{
|
2024-05-18 15:35:12 +00:00
|
|
|
|
get => _layoutStrategy;
|
|
|
|
|
set => _layoutStrategy = value;
|
2024-05-08 10:22:04 +00:00
|
|
|
|
}
|
2024-04-28 13:55:19 +00:00
|
|
|
|
|
2024-05-08 10:22:04 +00:00
|
|
|
|
/// <summary>
|
2024-05-18 15:35:12 +00:00
|
|
|
|
/// <para>Generating a map</para>
|
|
|
|
|
/// <para>生成地图</para>
|
2024-05-08 10:22:04 +00:00
|
|
|
|
/// </summary>
|
2024-05-18 15:35:12 +00:00
|
|
|
|
public static async Task GenerateMap()
|
2024-05-08 10:22:04 +00:00
|
|
|
|
{
|
2024-05-26 03:10:21 +00:00
|
|
|
|
if (_running)
|
|
|
|
|
{
|
|
|
|
|
LogCat.LogWarning("map_generator_is_running");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_running = true;
|
2024-06-03 14:58:59 +00:00
|
|
|
|
EventManager.MapGenerationStartEvent?.Invoke(new MapGenerationStartEvent());
|
2024-05-20 14:38:41 +00:00
|
|
|
|
if (_layoutStrategy == null || _roomPlacementStrategy == null || _layoutParsingStrategy == null ||
|
|
|
|
|
_mapRoot == null)
|
2024-05-08 10:22:04 +00:00
|
|
|
|
{
|
2024-05-19 12:29:32 +00:00
|
|
|
|
LogCat.LogError("map_generator_missing_parameters");
|
2024-05-26 03:10:21 +00:00
|
|
|
|
_running = false;
|
2024-05-18 15:35:12 +00:00
|
|
|
|
return;
|
2024-05-08 10:22:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
2024-06-05 13:38:45 +00:00
|
|
|
|
if (GameSceneNodeHolder.AiCharacterContainer != null)
|
2024-06-03 14:58:59 +00:00
|
|
|
|
{
|
2024-06-05 13:38:45 +00:00
|
|
|
|
NodeUtils.DeleteAllChild(GameSceneNodeHolder.AiCharacterContainer);
|
2024-06-03 14:58:59 +00:00
|
|
|
|
}
|
|
|
|
|
|
2024-05-23 13:58:49 +00:00
|
|
|
|
NodeUtils.DeleteAllChild(_mapRoot);
|
2024-05-25 16:02:00 +00:00
|
|
|
|
if (!await _roomPlacementStrategy.StartGeneration(_mapRoot))
|
|
|
|
|
{
|
2024-05-26 03:10:21 +00:00
|
|
|
|
LogCat.LogError("room_placement_strategy_terminates_map_generation");
|
|
|
|
|
_running = false;
|
2024-05-25 16:02:00 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-18 15:35:12 +00:00
|
|
|
|
//Get the layout data
|
|
|
|
|
//拿到布局图数据
|
|
|
|
|
var levelGraphEditorSaveData = await _layoutStrategy.GetLayout();
|
2024-05-19 14:32:34 +00:00
|
|
|
|
if (levelGraphEditorSaveData == null || levelGraphEditorSaveData.RoomNodeDataList == null ||
|
|
|
|
|
levelGraphEditorSaveData.RoomNodeDataList.Count == 0)
|
2024-05-08 10:22:04 +00:00
|
|
|
|
{
|
2024-05-19 12:29:32 +00:00
|
|
|
|
LogCat.LogError("map_generator_attempts_to_parse_empty_layout_diagrams");
|
2024-05-26 03:10:21 +00:00
|
|
|
|
_running = false;
|
2024-05-18 15:35:12 +00:00
|
|
|
|
return;
|
2024-05-08 10:22:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
2024-05-19 12:29:32 +00:00
|
|
|
|
_layoutParsingStrategy.SetLevelGraph(levelGraphEditorSaveData);
|
|
|
|
|
//Save the dictionary, put the ID in the room data, corresponding to the successful placement of the room.
|
|
|
|
|
//保存字典,将房间数据内的ID,对应放置成功的房间。
|
2024-05-20 14:38:41 +00:00
|
|
|
|
var roomDictionary = new Dictionary<string, Room>();
|
2024-05-21 14:50:33 +00:00
|
|
|
|
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");
|
2024-05-26 03:10:21 +00:00
|
|
|
|
_running = false;
|
2024-05-21 14:50:33 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var startingRoomPlacementData =
|
|
|
|
|
await _roomPlacementStrategy.CalculatePlacementDataForStartingRoom(randomNumberGenerator,
|
|
|
|
|
startRoomNodeData);
|
|
|
|
|
if (startingRoomPlacementData == null)
|
|
|
|
|
{
|
2024-05-26 03:10:21 +00:00
|
|
|
|
LogCat.LogError("start_room_placement_information_returns_empty");
|
|
|
|
|
_running = false;
|
2024-05-21 14:50:33 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var placeSuccess = await PlaceRoomAndAddRecord(startRoomNodeData.Id, startingRoomPlacementData, roomDictionary);
|
|
|
|
|
if (!placeSuccess)
|
|
|
|
|
{
|
2024-05-26 03:10:21 +00:00
|
|
|
|
LogCat.LogError("start_room_placement_failed");
|
|
|
|
|
_running = false;
|
2024-05-21 14:50:33 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-19 12:29:32 +00:00
|
|
|
|
while (await _layoutParsingStrategy.HasNext())
|
2024-05-18 15:35:12 +00:00
|
|
|
|
{
|
2024-05-19 12:29:32 +00:00
|
|
|
|
//When a new room needs to be placed
|
|
|
|
|
//当有新的房间需要放置时
|
|
|
|
|
var roomNodeData = await _layoutParsingStrategy.Next();
|
2024-05-21 14:50:33 +00:00
|
|
|
|
if (roomNodeData == null || string.IsNullOrEmpty(roomNodeData.Id))
|
2024-05-19 12:29:32 +00:00
|
|
|
|
{
|
2024-05-26 03:10:21 +00:00
|
|
|
|
LogCat.LogWarning("room_data_missing");
|
2024-05-19 12:29:32 +00:00
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-30 14:49:54 +00:00
|
|
|
|
var roomInjectionProcessorData = roomNodeData.RoomInjectionProcessorData;
|
|
|
|
|
//Whether room can be placed
|
|
|
|
|
//是否可放置房间
|
|
|
|
|
var canBePlaced = true;
|
|
|
|
|
if (_roomInjectionProcessorsDictionary != null && !string.IsNullOrEmpty(roomInjectionProcessorData))
|
|
|
|
|
{
|
|
|
|
|
var roomInjectionProcessorDataArray =
|
2024-06-16 14:44:50 +00:00
|
|
|
|
YamlSerialization.Deserialize<RoomInjectionProcessorData[]>(roomInjectionProcessorData);
|
|
|
|
|
if (roomInjectionProcessorDataArray.Length > 0)
|
2024-05-30 14:49:54 +00:00
|
|
|
|
{
|
|
|
|
|
foreach (var injectionProcessorData in roomInjectionProcessorDataArray)
|
|
|
|
|
{
|
2024-06-03 14:58:59 +00:00
|
|
|
|
if (string.IsNullOrEmpty(injectionProcessorData.Id) ||
|
|
|
|
|
string.IsNullOrEmpty(injectionProcessorData.Config))
|
2024-05-30 14:49:54 +00:00
|
|
|
|
{
|
|
|
|
|
//The data is incomplete, and the injectionProcessorData is ignored.
|
|
|
|
|
//数据不全,忽略injectionProcessorData。
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2024-06-03 14:58:59 +00:00
|
|
|
|
|
2024-05-30 14:49:54 +00:00
|
|
|
|
if (!_roomInjectionProcessorsDictionary.TryGetValue(injectionProcessorData.Id,
|
|
|
|
|
out var roomInjectionProcessor))
|
|
|
|
|
{
|
|
|
|
|
//If the room injection processor cannot be found, a print error occurs.
|
|
|
|
|
//如果找不到房间注入处理器,那么打印错误。
|
2024-06-03 14:58:59 +00:00
|
|
|
|
LogCat.LogErrorWithFormat("room_injection_processor_does_not_exist",
|
2024-07-19 14:25:55 +00:00
|
|
|
|
LogCat.LogLabel.Default, LogCat.UploadFormat, injectionProcessorData.Id);
|
2024-05-30 14:49:54 +00:00
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (await roomInjectionProcessor.CanBePlaced(randomNumberGenerator,
|
|
|
|
|
injectionProcessorData.Config)) continue;
|
|
|
|
|
//If the room cannot be placed, then out of the loop.
|
|
|
|
|
//如果此房间不能被放置,那么跳出循环。
|
|
|
|
|
canBePlaced = false;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-06-03 14:58:59 +00:00
|
|
|
|
|
2024-05-30 14:49:54 +00:00
|
|
|
|
if (!canBePlaced)
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2024-06-03 14:58:59 +00:00
|
|
|
|
|
2024-05-19 12:29:32 +00:00
|
|
|
|
var nextParentNodeId = await _layoutParsingStrategy.GetNextParentNodeId();
|
2024-05-20 14:38:41 +00:00
|
|
|
|
Room? parentRoomNode = null;
|
2024-05-19 12:29:32 +00:00
|
|
|
|
if (nextParentNodeId != null)
|
|
|
|
|
{
|
|
|
|
|
//If the new room has the parent's ID, then we pass the parent's room into the compute function.
|
|
|
|
|
//如果新房间有父节点的ID,那么我们将父节点的房间传入到计算函数内。
|
2024-05-20 14:38:41 +00:00
|
|
|
|
if (roomDictionary.TryGetValue(nextParentNodeId, out var value))
|
|
|
|
|
{
|
|
|
|
|
parentRoomNode = value;
|
|
|
|
|
}
|
2024-05-19 12:29:32 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var roomPlacementData =
|
2024-05-21 14:50:33 +00:00
|
|
|
|
await _roomPlacementStrategy.CalculateNewRoomPlacementData(randomNumberGenerator, parentRoomNode,
|
|
|
|
|
roomNodeData);
|
2024-05-19 12:29:32 +00:00
|
|
|
|
if (roomPlacementData == null)
|
|
|
|
|
{
|
2024-07-02 15:16:04 +00:00
|
|
|
|
LogCat.LogWithFormat("failed_to_calculate_the_room_location", LogCat.LogLabel.Default,
|
2024-07-19 14:25:55 +00:00
|
|
|
|
LogCat.UploadFormat, roomNodeData.Id);
|
2024-05-19 12:29:32 +00:00
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-21 14:50:33 +00:00
|
|
|
|
await PlaceRoomAndAddRecord(roomNodeData.Id, roomPlacementData, roomDictionary);
|
2024-05-18 15:35:12 +00:00
|
|
|
|
}
|
2024-05-26 03:10:21 +00:00
|
|
|
|
|
2024-05-19 12:29:32 +00:00
|
|
|
|
//All rooms have been placed.
|
|
|
|
|
//所有房间已放置完毕。
|
2024-05-25 16:02:00 +00:00
|
|
|
|
await _roomPlacementStrategy.GeneratedComplete(_mapRoot);
|
2024-05-26 03:10:21 +00:00
|
|
|
|
_running = false;
|
2024-05-28 14:14:35 +00:00
|
|
|
|
//Invoke the map generation completion event
|
|
|
|
|
//调用地图生成完成事件
|
|
|
|
|
var eventObj = new MapGenerationCompleteEvent
|
|
|
|
|
{
|
|
|
|
|
RandomNumberGenerator = randomNumberGenerator
|
|
|
|
|
};
|
2024-06-03 14:58:59 +00:00
|
|
|
|
EventManager.MapGenerationCompleteEvent?.Invoke(eventObj);
|
|
|
|
|
var aiCharacterGenerateEvent = new AiCharacterGenerateEvent
|
|
|
|
|
{
|
|
|
|
|
Tag = AiCharacterGenerateEvent.TagMapGenerationComplete
|
|
|
|
|
};
|
|
|
|
|
EventManager.AiCharacterGenerateEvent?.Invoke(aiCharacterGenerateEvent);
|
2024-05-08 10:22:04 +00:00
|
|
|
|
}
|
2024-05-21 14:50:33 +00:00
|
|
|
|
|
|
|
|
|
/// <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) ||
|
2024-07-19 14:25:55 +00:00
|
|
|
|
roomPlacementData.NewRoom == null)
|
2024-05-21 14:50:33 +00:00
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (dictionary.ContainsKey(roomNodeDataId))
|
|
|
|
|
{
|
2024-07-19 14:25:55 +00:00
|
|
|
|
LogCat.LogWithFormat("place_existing_rooms", LogCat.LogLabel.Default, LogCat.UploadFormat, roomNodeDataId);
|
2024-05-21 14:50:33 +00:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!await _roomPlacementStrategy.PlaceRoom(_mapRoot, roomPlacementData))
|
|
|
|
|
{
|
2024-07-19 14:25:55 +00:00
|
|
|
|
LogCat.LogWarningWithFormat("room_placement_failed", LogCat.UploadFormat, LogCat.LogLabel.Default,
|
|
|
|
|
roomNodeDataId);
|
2024-05-21 14:50:33 +00:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-19 14:25:55 +00:00
|
|
|
|
dictionary.Add(roomNodeDataId, roomPlacementData.NewRoom);
|
|
|
|
|
LogCat.LogWithFormat("room_placement_information", LogCat.LogLabel.Default, LogCat.UploadFormat, roomNodeDataId,
|
2024-07-02 15:16:04 +00:00
|
|
|
|
roomPlacementData.Position.ToString());
|
2024-05-21 14:50:33 +00:00
|
|
|
|
return true;
|
|
|
|
|
}
|
2024-05-08 10:22:04 +00:00
|
|
|
|
}
|