Fixed an issue where the return path of the room template set was invalid, the initial room placement was extracted separately from the new method and added to the seed of map generation.

修复房间模板集返回路径无效的问题,初始房间的放置被单独提取出新的方法,加入地图生成时的种子。
This commit is contained in:
Cold-Mint 2024-05-21 22:50:33 +08:00
parent 529d5e95b5
commit 544c5303f3
Signed by: Cold-Mint
GPG Key ID: C5A9BF8A98E0CE99
13 changed files with 184 additions and 153 deletions

View File

@ -5,30 +5,6 @@
"ToId": "4ae948ea-82b7-4b2d-bec2-19ed8a9d4c03", "ToId": "4ae948ea-82b7-4b2d-bec2-19ed8a9d4c03",
"FromPort": 0, "FromPort": 0,
"ToPort": 0 "ToPort": 0
},
{
"FromId": "4ae948ea-82b7-4b2d-bec2-19ed8a9d4c03",
"ToId": "c604e1ef-f3e7-4189-a6f7-ab2bd23d5130",
"FromPort": 0,
"ToPort": 0
},
{
"FromId": "4ae948ea-82b7-4b2d-bec2-19ed8a9d4c03",
"ToId": "bb9da3f9-911f-465f-af2e-be1325f17d15",
"FromPort": 0,
"ToPort": 0
},
{
"FromId": "c604e1ef-f3e7-4189-a6f7-ab2bd23d5130",
"ToId": "c9e860b9-e001-4b9b-9e6d-adf132beb64f",
"FromPort": 0,
"ToPort": 0
},
{
"FromId": "bb9da3f9-911f-465f-af2e-be1325f17d15",
"ToId": "c9e860b9-e001-4b9b-9e6d-adf132beb64f",
"FromPort": 0,
"ToPort": 0
} }
], ],
"RoomNodeDataList": [ "RoomNodeDataList": [
@ -37,7 +13,7 @@
"Title": "起点房间", "Title": "起点房间",
"Description": "测试的起点房间。", "Description": "测试的起点房间。",
"RoomTemplateSet": [ "RoomTemplateSet": [
"res://prefab/roomTemplates/dungeon/initialRoom.tscn" "res://prefab/roomTemplates/dungeon"
], ],
"Tags": [ "Tags": [
"StartingRoom" "StartingRoom"
@ -51,33 +27,6 @@
"res://prefab/roomTemplates/dungeon/utilityRoom.tscn" "res://prefab/roomTemplates/dungeon/utilityRoom.tscn"
], ],
"Tags": null "Tags": null
},
{
"Id": "c604e1ef-f3e7-4189-a6f7-ab2bd23d5130",
"Title": "普通房间1",
"Description": "测试使用。",
"RoomTemplateSet": [
"res://prefab/roomTemplates/dungeon"
],
"Tags": null
},
{
"Id": "bb9da3f9-911f-465f-af2e-be1325f17d15",
"Title": "普通房间2",
"Description": "测试使用。",
"RoomTemplateSet": [
"res://prefab/roomTemplates/dungeon"
],
"Tags": null
},
{
"Id": "c9e860b9-e001-4b9b-9e6d-adf132beb64f",
"Title": "Boos房间",
"Description": "关卡结束的地方。",
"RoomTemplateSet": [
"res://prefab/roomTemplates/dungeon"
],
"Tags": null
} }
] ]
} }

View File

@ -7,3 +7,4 @@ connected_room_timeout,连接房间超时。,Connecting the room timed out.,接
projectiles_is_empty,未设置抛射体。,The projectile is not set.,射出体は設置されていません。 projectiles_is_empty,未设置抛射体。,The projectile is not set.,射出体は設置されていません。
map_generator_missing_parameters,地图生成器缺少参数。,Map generator missing parameters.,マップジェネレータが不足しています。 map_generator_missing_parameters,地图生成器缺少参数。,Map generator missing parameters.,マップジェネレータが不足しています。
map_generator_attempts_to_parse_empty_layout_diagrams,地图生成器尝试解析空的布局图。,Map generator attempts to parse empty layout diagrams.,マップジェネレータは空のレイアウト図を解析しようとしています。 map_generator_attempts_to_parse_empty_layout_diagrams,地图生成器尝试解析空的布局图。,Map generator attempts to parse empty layout diagrams.,マップジェネレータは空のレイアウト図を解析しようとしています。
map_generator_has_no_starting_room_data,地图生成器没有起点房间数据。,Map generator has no starting room data.,マップ生成器に起点部屋データはありません。
1 id zh en jp
7 projectiles_is_empty 未设置抛射体。 The projectile is not set. 射出体は設置されていません。
8 map_generator_missing_parameters 地图生成器缺少参数。 Map generator missing parameters. マップジェネレータが不足しています。
9 map_generator_attempts_to_parse_empty_layout_diagrams 地图生成器尝试解析空的布局图。 Map generator attempts to parse empty layout diagrams. マップジェネレータは空のレイアウト図を解析しようとしています。
10 map_generator_has_no_starting_room_data 地图生成器没有起点房间数据。 Map generator has no starting room data. マップ生成器に起点部屋データはありません。

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -4,6 +4,7 @@ using ColdMint.scripts.map;
using ColdMint.scripts.map.LayoutParsingStrategy; using ColdMint.scripts.map.LayoutParsingStrategy;
using ColdMint.scripts.map.layoutStrategy; using ColdMint.scripts.map.layoutStrategy;
using ColdMint.scripts.map.RoomPlacer; using ColdMint.scripts.map.RoomPlacer;
using ColdMint.scripts.utils;
using Godot; using Godot;
namespace ColdMint.scripts.loader.sceneLoader; namespace ColdMint.scripts.loader.sceneLoader;
@ -33,6 +34,7 @@ public partial class GameSceneLoader : SceneLoaderTemplate
MapGenerator.LayoutStrategy = new TestLayoutStrategy(); MapGenerator.LayoutStrategy = new TestLayoutStrategy();
MapGenerator.LayoutParsingStrategy = new SequenceLayoutParsingStrategy(); MapGenerator.LayoutParsingStrategy = new SequenceLayoutParsingStrategy();
MapGenerator.RoomPlacementStrategy = new PatchworkRoomPlacementStrategy(); MapGenerator.RoomPlacementStrategy = new PatchworkRoomPlacementStrategy();
MapGenerator.Seed = GuidUtils.GetGuid();
await MapGenerator.GenerateMap(); await MapGenerator.GenerateMap();
} }
} }

View File

@ -16,6 +16,13 @@ public interface ILayoutParsingStrategy
/// <param name="levelGraphEditorSaveData"></param> /// <param name="levelGraphEditorSaveData"></param>
public void SetLevelGraph(LevelGraphEditorSaveData levelGraphEditorSaveData); public void SetLevelGraph(LevelGraphEditorSaveData levelGraphEditorSaveData);
/// <summary>
/// <para>Gets data for the start room node</para>
/// <para>获取起始房间节点的数据</para>
/// </summary>
/// <returns></returns>
public Task<RoomNodeData?> GetStartRoomNodeData();
/// <summary> /// <summary>
/// <para>Gets the next room to place</para> /// <para>Gets the next room to place</para>
/// <para>获取下一个要放置的房间</para> /// <para>获取下一个要放置的房间</para>

View File

@ -1,4 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using ColdMint.scripts.levelGraphEditor; using ColdMint.scripts.levelGraphEditor;
@ -23,19 +24,17 @@ public class SequenceLayoutParsingStrategy : ILayoutParsingStrategy
//设置数据时,是否检查合法了 //设置数据时,是否检查合法了
private bool _checkLegality; private bool _checkLegality;
//A special marker for the starting room.
//特殊标记,代表起始房间。
private const int StartingRoomIndex = -1;
//The connection index of the query //The connection index of the query
//查询的连接索引 //查询的连接索引
private int _index = StartingRoomIndex; private int _index;
private int _maxIndex; private int _maxIndex;
private Dictionary<string, RoomNodeData> _roomNodeDataDictionary = new Dictionary<string, RoomNodeData>(); private Dictionary<string, RoomNodeData> _roomNodeDataDictionary = new Dictionary<string, RoomNodeData>();
public void SetLevelGraph(LevelGraphEditorSaveData levelGraphEditorSaveData) public void SetLevelGraph(LevelGraphEditorSaveData levelGraphEditorSaveData)
{ {
_index = StartingRoomIndex; _checkLegality = false;
_index = -1;
_levelGraphEditorSaveData = levelGraphEditorSaveData; _levelGraphEditorSaveData = levelGraphEditorSaveData;
if (_levelGraphEditorSaveData.RoomNodeDataList == null || _levelGraphEditorSaveData.RoomNodeDataList.Count == 0) if (_levelGraphEditorSaveData.RoomNodeDataList == null || _levelGraphEditorSaveData.RoomNodeDataList.Count == 0)
{ {
@ -51,7 +50,7 @@ public class SequenceLayoutParsingStrategy : ILayoutParsingStrategy
} }
else else
{ {
_maxIndex = _levelGraphEditorSaveData.ConnectionDataList.Count - 1; _maxIndex = _levelGraphEditorSaveData.ConnectionDataList.Count;
} }
_roomNodeDataDictionary.Clear(); _roomNodeDataDictionary.Clear();
@ -64,66 +63,34 @@ public class SequenceLayoutParsingStrategy : ILayoutParsingStrategy
_roomNodeDataDictionary.Add(roomNodeData.Id, roomNodeData); _roomNodeDataDictionary.Add(roomNodeData.Id, roomNodeData);
} }
_checkLegality = true;
//Check that the first room is the starting room.
//检查首个房间是否为起始房间。
var firstRoom = GetFirstRoom(_levelGraphEditorSaveData);
if (firstRoom == null)
{
return;
}
_checkLegality = firstRoom.HasTag(Config.RoomDataTag.StartingRoom);
} }
/// <summary> public Task<RoomNodeData?> GetStartRoomNodeData()
/// <para>Get the first room</para>
/// <para>获取第一个房间</para>
/// </summary>
/// <param name="levelGraphEditorSaveData"></param>
/// <returns></returns>
private RoomNodeData? GetFirstRoom(LevelGraphEditorSaveData? levelGraphEditorSaveData)
{ {
if (levelGraphEditorSaveData == null || levelGraphEditorSaveData.RoomNodeDataList == null || if (_levelGraphEditorSaveData == null)
levelGraphEditorSaveData.RoomNodeDataList.Count == 0)
{ {
return null; return Task.FromResult<RoomNodeData?>(null);
} }
RoomNodeData? firstRoom = null; if (_levelGraphEditorSaveData.RoomNodeDataList == null || _levelGraphEditorSaveData.RoomNodeDataList.Count == 0)
if (levelGraphEditorSaveData.ConnectionDataList == null ||
levelGraphEditorSaveData.ConnectionDataList.Count == 0)
{ {
//If there is no connection information, then fetch the first room in the RoomNodeDataList. //If there is no room data in the level map set.
//如果没有连接信息那么在RoomNodeDataList内取出第一个房间。 //如果设置的关卡图内没有房间数据。
firstRoom = levelGraphEditorSaveData.RoomNodeDataList[0]; return Task.FromResult<RoomNodeData?>(null);
}
else
{
//If there is connection information, then fetch the first connected From room in the ConnectionDataList.
//如果有连接信息那么在ConnectionDataList内取出第一个连接的From房间。
var firstConnection = levelGraphEditorSaveData.ConnectionDataList[0];
if (firstConnection.FromId == null)
{
return firstRoom;
}
if (_roomNodeDataDictionary.TryGetValue(firstConnection.FromId, out var value))
{
firstRoom = value;
}
} }
return firstRoom; foreach (var roomNodeData in _levelGraphEditorSaveData.RoomNodeDataList.Where(roomNodeData =>
roomNodeData.HasTag(Config.RoomDataTag.StartingRoom)))
{
return Task.FromResult<RoomNodeData?>(roomNodeData);
}
return Task.FromResult<RoomNodeData?>(null);
} }
public Task<RoomNodeData?> Next() public Task<RoomNodeData?> Next()
{ {
if (_index == StartingRoomIndex)
{
return Task.FromResult(GetFirstRoom(_levelGraphEditorSaveData));
}
var connectionData = GetIndexOfConnectionData(_index); var connectionData = GetIndexOfConnectionData(_index);
if (connectionData == null) if (connectionData == null)
{ {
@ -167,13 +134,6 @@ public class SequenceLayoutParsingStrategy : ILayoutParsingStrategy
public Task<string?> GetNextParentNodeId() public Task<string?> GetNextParentNodeId()
{ {
if (_index == StartingRoomIndex)
{
//The start room will not have a parent node.
//起始房间不会有父节点。
return Task.FromResult<string?>(null);
}
var connectionData = GetIndexOfConnectionData(_index); var connectionData = GetIndexOfConnectionData(_index);
if (connectionData == null) if (connectionData == null)
{ {
@ -202,14 +162,7 @@ public class SequenceLayoutParsingStrategy : ILayoutParsingStrategy
return Task.FromResult(false); return Task.FromResult(false);
} }
if (_index == StartingRoomIndex) _index++;
{
//The start room is always considered to have the next room, in order to handle situations where levelGraphEditorSaveData has only room data and no connection data.
//起始房间始终被认为有下一个房间这是为了处理levelGraphEditorSaveData仅有房间数据没有连接数据的情况。
_index++;
return Task.FromResult(true);
}
return Task.FromResult(_index < _maxIndex); return Task.FromResult(_index < _maxIndex);
} }
} }

View File

@ -1,10 +1,13 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using ColdMint.scripts.debug; using ColdMint.scripts.debug;
using ColdMint.scripts.levelGraphEditor;
using ColdMint.scripts.map.dateBean;
using ColdMint.scripts.map.interfaces; using ColdMint.scripts.map.interfaces;
using ColdMint.scripts.map.LayoutParsingStrategy; using ColdMint.scripts.map.LayoutParsingStrategy;
using ColdMint.scripts.map.layoutStrategy; using ColdMint.scripts.map.layoutStrategy;
using ColdMint.scripts.map.room; using ColdMint.scripts.map.room;
using ColdMint.scripts.utils;
using Godot; using Godot;
namespace ColdMint.scripts.map; namespace ColdMint.scripts.map;
@ -37,6 +40,19 @@ public static class MapGenerator
/// </summary> /// </summary>
private static IRoomPlacementStrategy? _roomPlacementStrategy; private static IRoomPlacementStrategy? _roomPlacementStrategy;
private static ulong _seed;
/// <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> /// <summary>
/// <para>Layout diagram parsing policy</para> /// <para>Layout diagram parsing policy</para>
@ -95,12 +111,36 @@ public static class MapGenerator
//Save the dictionary, put the ID in the room data, corresponding to the successful placement of the room. //Save the dictionary, put the ID in the room data, corresponding to the successful placement of the room.
//保存字典将房间数据内的ID对应放置成功的房间。 //保存字典将房间数据内的ID对应放置成功的房间。
var roomDictionary = new Dictionary<string, Room>(); var roomDictionary = new Dictionary<string, Room>();
var randomNumberGenerator = new RandomNumberGenerator();
randomNumberGenerator.Seed = _seed;
LogCat.Log("Seed:" + _seed);
var startRoomNodeData = await _layoutParsingStrategy.GetStartRoomNodeData();
if (startRoomNodeData == null || string.IsNullOrEmpty(startRoomNodeData.Id))
{
LogCat.LogError("map_generator_has_no_starting_room_data");
return;
}
var startingRoomPlacementData =
await _roomPlacementStrategy.CalculatePlacementDataForStartingRoom(randomNumberGenerator,
startRoomNodeData);
if (startingRoomPlacementData == null)
{
return;
}
var placeSuccess = await PlaceRoomAndAddRecord(startRoomNodeData.Id, startingRoomPlacementData, roomDictionary);
if (!placeSuccess)
{
return;
}
while (await _layoutParsingStrategy.HasNext()) while (await _layoutParsingStrategy.HasNext())
{ {
//When a new room needs to be placed //When a new room needs to be placed
//当有新的房间需要放置时 //当有新的房间需要放置时
var roomNodeData = await _layoutParsingStrategy.Next(); var roomNodeData = await _layoutParsingStrategy.Next();
if (roomNodeData == null) if (roomNodeData == null || string.IsNullOrEmpty(roomNodeData.Id))
{ {
continue; continue;
} }
@ -118,23 +158,49 @@ public static class MapGenerator
} }
var roomPlacementData = var roomPlacementData =
await _roomPlacementStrategy.CalculateNewRoomPlacementData(parentRoomNode, roomNodeData); await _roomPlacementStrategy.CalculateNewRoomPlacementData(randomNumberGenerator, parentRoomNode,
roomNodeData);
if (roomPlacementData == null) if (roomPlacementData == null)
{ {
continue; continue;
} }
if (!await _roomPlacementStrategy.PlaceRoom(_mapRoot, roomPlacementData)) await PlaceRoomAndAddRecord(roomNodeData.Id, roomPlacementData, roomDictionary);
{
continue;
}
if (!string.IsNullOrEmpty(roomNodeData.Id) && roomPlacementData.Room != null)
{
roomDictionary.Add(roomNodeData.Id, roomPlacementData.Room);
}
} }
//All rooms have been placed. //All rooms have been placed.
//所有房间已放置完毕。 //所有房间已放置完毕。
} }
/// <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.Room == null)
{
return false;
}
if (dictionary.ContainsKey(roomNodeDataId))
{
return false;
}
if (!await _roomPlacementStrategy.PlaceRoom(_mapRoot, roomPlacementData))
{
return false;
}
dictionary.Add(roomNodeDataId, roomPlacementData.Room);
return true;
}
} }

View File

@ -40,5 +40,18 @@ public interface IRoomPlacementStrategy
///<para>欲放置的新房间数据</para> ///<para>欲放置的新房间数据</para>
/// </param> /// </param>
/// <returns></returns> /// <returns></returns>
public Task<RoomPlacementData?> CalculateNewRoomPlacementData(Room? parentRoomNode, RoomNodeData newRoomNodeData); public Task<RoomPlacementData?> CalculateNewRoomPlacementData(RandomNumberGenerator randomNumberGenerator,
Room? parentRoomNode,
RoomNodeData newRoomNodeData);
/// <summary>
/// <para>Calculates the placement information for the starting room</para>
/// <para>计算起始房间的放置信息</para>
/// </summary>
/// <param name="randomNumberGenerator"></param>
/// <param name="startRoomNodeData"></param>
/// <returns></returns>
public Task<RoomPlacementData?> CalculatePlacementDataForStartingRoom(
RandomNumberGenerator randomNumberGenerator, RoomNodeData startRoomNodeData);
} }

View File

@ -1,5 +1,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using Godot; using Godot;
using FileAccess = Godot.FileAccess;
namespace ColdMint.scripts.map.room; namespace ColdMint.scripts.map.room;
@ -36,7 +38,7 @@ public static class RoomFactory
{ {
if (!dir.CurrentIsDir()) if (!dir.CurrentIsDir())
{ {
resList.Add(fileName); resList.Add(Path.Join(roomTemplate, fileName));
} }
fileName = dir.GetNext(); fileName = dir.GetNext();

View File

@ -32,40 +32,55 @@ public class PatchworkRoomPlacementStrategy : IRoomPlacementStrategy
} }
var rootNode = roomPlacementData.Room.RootNode; var rootNode = roomPlacementData.Room.RootNode;
rootNode.Reparent(mapRoot); mapRoot.AddChild(rootNode);
rootNode.Position = roomPlacementData.Position.Value; rootNode.Position = roomPlacementData.Position.Value;
return Task.FromResult(true); return Task.FromResult(true);
} }
public Task<RoomPlacementData?> CalculateNewRoomPlacementData(Room? parentRoomNode, RoomNodeData newRoomNodeData) public Task<RoomPlacementData?> CalculateNewRoomPlacementData(RandomNumberGenerator randomNumberGenerator,
Room? parentRoomNode,
RoomNodeData newRoomNodeData)
{ {
if (newRoomNodeData.RoomTemplateSet == null || newRoomNodeData.RoomTemplateSet.Length == 0) if (newRoomNodeData.RoomTemplateSet == null || newRoomNodeData.RoomTemplateSet.Length == 0)
{ {
return Task.FromResult<RoomPlacementData?>(null); return Task.FromResult<RoomPlacementData?>(null);
} }
var roomResArray = RoomFactory.RoomTemplateSetToRoomRes(newRoomNodeData.RoomTemplateSet);
if (parentRoomNode == null) if (parentRoomNode == null)
{ {
//No parent node is set, which we think is the starting room.
//没有设置父节点,我们认为是起点房间。
//TODO:在这里兼容世界种子。
var roomPlacementData = new RoomPlacementData
{
Room = RoomFactory.CreateRoom(roomResArray[0]),
Position = Vector2.Zero
};
return Task.FromResult<RoomPlacementData?>(roomPlacementData);
}
else
{
//TODO:在这里实现房间的放置策略。
return Task.FromResult<RoomPlacementData?>(null); return Task.FromResult<RoomPlacementData?>(null);
} }
// var roomResArray = RoomFactory.RoomTemplateSetToRoomRes(newRoomNodeData.RoomTemplateSet);
//TODO:在这里实现房间的放置策略。
return Task.FromResult<RoomPlacementData?>(null);
} }
public Task<RoomPlacementData?> CalculatePlacementDataForStartingRoom(RandomNumberGenerator randomNumberGenerator,
RoomNodeData startRoomNodeData)
{
if (startRoomNodeData.RoomTemplateSet == null || startRoomNodeData.RoomTemplateSet.Length == 0)
{
return Task.FromResult<RoomPlacementData?>(null);
}
var roomResArray = RoomFactory.RoomTemplateSetToRoomRes(startRoomNodeData.RoomTemplateSet);
if (roomResArray.Length == 0)
{
return Task.FromResult<RoomPlacementData?>(null);
}
var index = randomNumberGenerator.Randi() % roomResArray.Length;
var roomPlacementData = new RoomPlacementData
{
Room = RoomFactory.CreateRoom(roomResArray[index]),
Position = Vector2.Zero
};
return Task.FromResult<RoomPlacementData?>(roomPlacementData);
}
private Task<Vector2?> CalculatedPosition(Room mainRoom, Room newRoom, RoomSlot? mainRoomSlot, private Task<Vector2?> CalculatedPosition(Room mainRoom, Room newRoom, RoomSlot? mainRoomSlot,
RoomSlot? newRoomSlot,bool roomSlotOverlap) RoomSlot? newRoomSlot, bool roomSlotOverlap)
{ {
if (mainRoom.RootNode == null || mainRoom.TileMap == null || newRoom.TileMap == null || mainRoomSlot == null || if (mainRoom.RootNode == null || mainRoom.TileMap == null || newRoom.TileMap == null || mainRoomSlot == null ||
newRoomSlot == null) newRoomSlot == null)

View File

@ -0,0 +1,23 @@
using System.Linq;
namespace ColdMint.scripts.utils;
public class HashCodeUtils
{
/// <summary>
/// <para>Gets the hash code for a string</para>
/// <para>获取字符串的哈希码</para>
/// </summary>
/// <param name="str">
///<para>The input string returns a fixed hash code</para>
///<para>输入的字符串,返回固定的哈希码</para>
/// </param>
/// <returns></returns>
public static uint GetFixedHashCode(string str)
{
unchecked
{
return str.Aggregate(2166136261, (current, c) => (current ^ c) * 16777619);
}
}
}