Advertisement
czeak6464

Random Dungeon

Jan 21st, 2025
41
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 20.85 KB | Source Code | 0 0
  1. using System;
  2. using System.Collections.Generic;
  3. using Yukar.Engine;
  4. using System.IO;
  5. using System.Linq;
  6. using Microsoft.Xna.Framework;
  7.  
  8. namespace Bakin
  9. {
  10.     public static class DungeonConstants
  11.     {
  12.         public const int DEFAULT_FLOOR = 4;
  13.         public const int DEFAULT_WALL = 5;
  14.         public const int DEFAULT_CORRIDOR = 6;
  15.         public const int FLOOR_HEIGHT = 1;
  16.         public const int WALL_HEIGHT = 10;
  17.         public const int CORRIDOR_HEIGHT = 1;
  18.     }
  19.  
  20.     public class RandomDungeon : BakinObject
  21.     {
  22.         private MapScene mapScene;
  23.         private DungeonData currentDungeon;
  24.         private const string SAVE_DIRECTORY = "SavedDungeons";
  25.         private const string FILE_EXTENSION = ".dungeon";
  26.         private List<MapCharacter> usedEvents = new List<MapCharacter>();
  27.  
  28.         public override void Start()
  29.         {
  30.             mapScene = GameMain.instance.mapScene;
  31.  
  32.             if (GameMain.instance.getScenes() == GameMain.Scenes.TITLE ||
  33.                 GameMain.instance.getScenes() == GameMain.Scenes.LOADING ||
  34.                 mapScene == null)
  35.                 return;
  36.  
  37.             Directory.CreateDirectory(SAVE_DIRECTORY);
  38.         }
  39.  
  40.         [BakinFunction(Description = "1) Generate basic dungeon (23x23)")]
  41.         public void Generate()
  42.         {
  43.             GenerateNewDungeon(23, 23, 4, 8, 5,
  44.                 DungeonConstants.DEFAULT_FLOOR,
  45.                 DungeonConstants.DEFAULT_WALL,
  46.                 DungeonConstants.DEFAULT_CORRIDOR);
  47.         }
  48.  
  49.         [BakinFunction(Description = "2) Save current dungeon")]
  50.         public void Save(string dungeonName)
  51.         {
  52.             SaveDungeon(dungeonName);
  53.         }
  54.  
  55.         [BakinFunction(Description = "3) Load saved dungeon")]
  56.         public void Load(string dungeonName)
  57.         {
  58.             LoadDungeon(dungeonName);
  59.         }
  60.  
  61.         [BakinFunction(Description = "4) Generate custom size dungeon (width_height)")]
  62.         public void GenerateCustomSize(string size)
  63.         {
  64.             string[] dimensions = size.Split('_');
  65.             if (dimensions.Length == 2 && int.TryParse(dimensions[0], out int width) && int.TryParse(dimensions[1], out int height))
  66.             {
  67.                 GenerateNewDungeon(width, height, 3, 6, 100,
  68.                     DungeonConstants.DEFAULT_FLOOR,
  69.                     DungeonConstants.DEFAULT_WALL,
  70.                     DungeonConstants.DEFAULT_CORRIDOR);
  71.             }
  72.         }
  73.  
  74.         [BakinFunction(Description = "5) Generate with custom terrain (floor_wall_corridor)")]
  75.         public void GenerateCustomTerrain(string terrainTypes)
  76.         {
  77.             string[] types = terrainTypes.Split('_');
  78.             if (types.Length == 3 && int.TryParse(types[0], out int floor) &&
  79.                 int.TryParse(types[1], out int wall) && int.TryParse(types[2], out int corridor))
  80.             {
  81.                 GenerateNewDungeon(23, 23, 3, 6, 100, floor, wall, corridor);
  82.             }
  83.         }
  84.  
  85.         [BakinFunction(Description = "0) Clear/Reset current dungeon")]
  86.         public void Clear()
  87.         {
  88.             if (mapScene?.mapDrawer == null) return;
  89.  
  90.             int width = currentDungeon != null ? currentDungeon.Width : 23;
  91.             int height = currentDungeon != null ? currentDungeon.Height : 23;
  92.  
  93.             for (int x = 0; x < width; x++)
  94.             {
  95.                 for (int y = 0; y < height; y++)
  96.                 {
  97.                     mapScene.mapDrawer.setTerrain(x, y, 1, 1, true);
  98.                 }
  99.             }
  100.             mapScene.mapDrawer.updateMapHeightAndWalkableState(true);
  101.             currentDungeon = null;
  102.         }
  103.  
  104.         private void GenerateNewDungeon(int width, int height, int minRoomSize, int maxRoomSize, int maxRooms,
  105.             int floorType, int wallType, int corridorType)
  106.         {
  107.             currentDungeon = new DungeonData(width, height)
  108.             {
  109.                 FloorType = floorType,
  110.                 WallType = wallType,
  111.                 CorridorType = corridorType
  112.             };
  113.  
  114.             for (int x = 0; x < width; x++)
  115.             {
  116.                 for (int y = 0; y < height; y++)
  117.                 {
  118.                     currentDungeon.TerrainData[x, y] = wallType;
  119.                 }
  120.             }
  121.  
  122.             GenerateRooms(minRoomSize, maxRoomSize, maxRooms);
  123.         }
  124.  
  125.         private void SaveDungeon(string saveName)
  126.         {
  127.             if (currentDungeon == null) return;
  128.  
  129.             string filePath = Path.Combine(SAVE_DIRECTORY, saveName + FILE_EXTENSION);
  130.             string data = SerializeDungeon(currentDungeon);
  131.             File.WriteAllText(filePath, data);
  132.         }
  133.  
  134.         private void LoadDungeon(string saveName)
  135.         {
  136.             string filePath = Path.Combine(SAVE_DIRECTORY, saveName + FILE_EXTENSION);
  137.             if (!File.Exists(filePath)) return;
  138.  
  139.             string data = File.ReadAllText(filePath);
  140.             currentDungeon = DeserializeDungeon(data);
  141.            
  142.             // Find all rooms in the loaded dungeon
  143.             List<Room> rooms = FindRoomsInLoadedDungeon();
  144.            
  145.             ApplyDungeonToMap();
  146.             PlacePlayerInRandomRoom(rooms); // Place player in a random room after loading
  147.         }
  148.  
  149.         private List<Room> FindRoomsInLoadedDungeon()
  150.         {
  151.             List<Room> rooms = new List<Room>();
  152.             if (currentDungeon == null) return rooms;
  153.  
  154.             // Scan the dungeon for rooms by looking for floor tiles surrounded by walls
  155.             for (int x = 1; x < currentDungeon.Width - 1; x++)
  156.             {
  157.                 for (int y = 1; y < currentDungeon.Height - 1; y++)
  158.                 {
  159.                     if (currentDungeon.TerrainData[x, y] == currentDungeon.FloorType)
  160.                     {
  161.                         Room room = ExpandRoom(x, y);
  162.                         if (room != null)
  163.                         {
  164.                             rooms.Add(room);
  165.                             // Skip the tiles we just processed
  166.                             y += room.Height;
  167.                         }
  168.                     }
  169.                 }
  170.             }
  171.  
  172.             return rooms;
  173.         }
  174.  
  175.         private Room ExpandRoom(int startX, int startY)
  176.         {
  177.             if (currentDungeon.TerrainData[startX, startY] != currentDungeon.FloorType) return null;
  178.  
  179.             int width = 0, height = 0;
  180.  
  181.             // Find width
  182.             for (int x = startX; x < currentDungeon.Width - 1; x++)
  183.             {
  184.                 if (currentDungeon.TerrainData[x, startY] != currentDungeon.FloorType) break;
  185.                 width++;
  186.             }
  187.  
  188.             // Find height
  189.             for (int y = startY; y < currentDungeon.Height - 1; y++)
  190.             {
  191.                 if (currentDungeon.TerrainData[startX, y] != currentDungeon.FloorType) break;
  192.                 height++;
  193.             }
  194.  
  195.             // Verify it's a proper room (has walls around it)
  196.             if (width < 2 || height < 2) return null;
  197.  
  198.             return new Room(startX, startY, width, height);
  199.         }
  200.  
  201.         private string SerializeDungeon(DungeonData dungeon)
  202.         {
  203.             List<string> lines = new List<string>
  204.             {
  205.                 $"Width:{dungeon.Width}",
  206.                 $"Height:{dungeon.Height}",
  207.                 $"Floor:{dungeon.FloorType}",
  208.                 $"Wall:{dungeon.WallType}",
  209.                 $"Corridor:{dungeon.CorridorType}"
  210.             };
  211.  
  212.             // Save terrain data
  213.             for (int y = 0; y < dungeon.Height; y++)
  214.             {
  215.                 string row = "";
  216.                 for (int x = 0; x < dungeon.Width; x++)
  217.                 {
  218.                     row += dungeon.TerrainData[x, y].ToString();
  219.                     if (x < dungeon.Width - 1) row += ",";
  220.                 }
  221.                 lines.Add(row);
  222.             }
  223.  
  224.             // Save event positions
  225.             lines.Add("Events:");
  226.             var moveEvents = mapScene.mapCharList
  227.                 .Where(chr => chr != mapScene.hero && chr.rom?.name?.Contains("Move") == true);
  228.            
  229.             foreach (var evt in moveEvents)
  230.             {
  231.                 Vector3 pos = evt.getPosition();
  232.                 lines.Add($"{evt.rom.name}:{pos.X},{pos.Y},{pos.Z}");
  233.             }
  234.  
  235.             return string.Join("\n", lines);
  236.         }
  237.  
  238.         private DungeonData DeserializeDungeon(string data)
  239.         {
  240.             string[] lines = data.Split('\n');
  241.             int width = int.Parse(lines[0].Split(':')[1]);
  242.             int height = int.Parse(lines[1].Split(':')[1]);
  243.  
  244.             DungeonData dungeon = new DungeonData(width, height);
  245.             dungeon.FloorType = int.Parse(lines[2].Split(':')[1]);
  246.             dungeon.WallType = int.Parse(lines[3].Split(':')[1]);
  247.             dungeon.CorridorType = int.Parse(lines[4].Split(':')[1]);
  248.  
  249.             // Load terrain data
  250.             int currentLine = 5;
  251.             for (int y = 0; y < height; y++)
  252.             {
  253.                 string[] values = lines[currentLine + y].Split(',');
  254.                 for (int x = 0; x < width; x++)
  255.                 {
  256.                     dungeon.TerrainData[x, y] = int.Parse(values[x]);
  257.                 }
  258.             }
  259.  
  260.             // Load event positions
  261.             currentLine += height;
  262.             if (currentLine < lines.Length && lines[currentLine] == "Events:")
  263.             {
  264.                 currentLine++;
  265.                 while (currentLine < lines.Length)
  266.                 {
  267.                     string[] eventData = lines[currentLine].Split(':');
  268.                     string eventName = eventData[0];
  269.                     string[] position = eventData[1].Split(',');
  270.  
  271.                     var evt = mapScene.mapCharList.FirstOrDefault(chr =>
  272.                         chr != mapScene.hero &&
  273.                         chr.rom?.name == eventName);
  274.  
  275.                     if (evt != null)
  276.                     {
  277.                         float x = float.Parse(position[0]);
  278.                         float y = float.Parse(position[1]);
  279.                         float z = float.Parse(position[2]);
  280.                         evt.setPosition(new Vector3(x, y, z));
  281.                     }
  282.  
  283.                     currentLine++;
  284.                 }
  285.             }
  286.  
  287.             return dungeon;
  288.         }
  289.  
  290.         private Dictionary<int, string> GetAvailableTerrains()
  291.         {
  292.             Dictionary<int, string> terrains = new Dictionary<int, string>();
  293.             Dictionary<int, Guid> chipList = mapScene.map.getChipList();
  294.  
  295.             foreach (KeyValuePair<int, Guid> chip in chipList)
  296.             {
  297.                 var mapChip = catalog.getItemFromGuid<Yukar.Common.Resource.MapChip>(chip.Value);
  298.                 terrains.Add(chip.Key, mapChip?.Name ?? "unknown");
  299.             }
  300.  
  301.             return terrains;
  302.         }
  303.  
  304.         private void ApplyDungeonToMap()
  305.         {
  306.             if (mapScene?.mapDrawer == null || currentDungeon == null) return;
  307.  
  308.             Dictionary<int, Guid> availableTerrains = mapScene.map.getChipList();
  309.  
  310.             for (int x = 0; x < currentDungeon.Width; x++)
  311.             {
  312.                 for (int y = 0; y < currentDungeon.Height; y++)
  313.                 {
  314.                     int currentTile = currentDungeon.TerrainData[x, y];
  315.                     int terrainId;
  316.                     int height;
  317.  
  318.                     if (currentTile == currentDungeon.WallType)
  319.                     {
  320.                         terrainId = currentDungeon.WallType;
  321.                         height = DungeonConstants.WALL_HEIGHT;
  322.                     }
  323.                     else if (currentTile == currentDungeon.FloorType)
  324.                     {
  325.                         terrainId = currentDungeon.FloorType;
  326.                         height = DungeonConstants.FLOOR_HEIGHT;
  327.                     }
  328.                     else // Corridor
  329.                     {
  330.                         terrainId = currentDungeon.CorridorType;
  331.                         height = DungeonConstants.CORRIDOR_HEIGHT;
  332.                     }
  333.  
  334.                     mapScene.mapDrawer.setTerrain(x, y, height, terrainId, true);
  335.                     mapScene.mapDrawer.setTerrainAttr(x, y, terrainId, Yukar.Common.Rom.Map.AttribDir.All, true);
  336.                 }
  337.             }
  338.  
  339.             mapScene.mapDrawer.updateMapHeightAndWalkableState(true);
  340.         }
  341.  
  342.         [BakinFunction(Description = "Show available terrain types")]
  343.         public void ShowTerrains()
  344.         {
  345.             Dictionary<int, Guid> terrains = mapScene.map.getChipList();
  346.             System.Text.StringBuilder sb = new System.Text.StringBuilder();
  347.             sb.AppendLine("Available Terrain Types for this map:");
  348.             sb.AppendLine();
  349.  
  350.             foreach (var terrain in terrains)
  351.             {
  352.                 var mapChip = catalog.getItemFromGuid<Yukar.Common.Resource.MapChip>(terrain.Value);
  353.                 sb.AppendLine($"ID: {terrain.Key} - {mapChip?.Name ?? "unknown"}");
  354.             }
  355.  
  356.             mapScene.spManager.ShowText(0, sb.ToString(), 0, 0);
  357.         }
  358.  
  359.         private void GenerateRooms(int minRoomSize, int maxRoomSize, int maxRooms)
  360.         {
  361.             List<Room> rooms = new List<Room>();
  362.             Random random = new Random();
  363.  
  364.             // Get all "Move" events at the start
  365.             var moveEvents = mapScene.mapCharList
  366.                 .Where(chr => chr != mapScene.hero && chr.rom?.name?.Contains("Move") == true)
  367.                 .ToList();
  368.  
  369.             int attempts = 0;
  370.             while (rooms.Count < maxRooms && attempts < 1000)
  371.             {
  372.                 int roomWidth = random.Next(minRoomSize, maxRoomSize + 1);
  373.                 int roomHeight = random.Next(minRoomSize, maxRoomSize + 1);
  374.                 int roomX = random.Next(2, currentDungeon.Width - roomWidth - 2);
  375.                 int roomY = random.Next(2, currentDungeon.Height - roomHeight - 2);
  376.  
  377.                 Room newRoom = new Room(roomX, roomY, roomWidth, roomHeight);
  378.  
  379.                 bool tooClose = rooms.Exists(r =>
  380.                     Math.Abs(r.CenterX - newRoom.CenterX) < roomWidth + 2 ||
  381.                     Math.Abs(r.CenterY - newRoom.CenterY) < roomHeight + 2);
  382.  
  383.                 if (!tooClose)
  384.                 {
  385.                     rooms.Add(newRoom);
  386.                     // Create the room
  387.                     for (int x = roomX - 1; x <= roomX + roomWidth; x++)
  388.                     {
  389.                         for (int y = roomY - 1; y <= roomY + roomHeight; y++)
  390.                         {
  391.                             if (x == roomX - 1 || x == roomX + roomWidth ||
  392.                                 y == roomY - 1 || y == roomY + roomHeight)
  393.                             {
  394.                                 currentDungeon.TerrainData[x, y] = currentDungeon.WallType;
  395.                             }
  396.                             else
  397.                             {
  398.                                 currentDungeon.TerrainData[x, y] = currentDungeon.FloorType;
  399.                             }
  400.                         }
  401.                     }
  402.                 }
  403.                 attempts++;
  404.             }
  405.  
  406.             // Place all "Move" events in random rooms
  407.             foreach (var moveEvent in moveEvents)
  408.             {
  409.                 if (rooms.Count == 0) break;
  410.                
  411.                 // Pick a random room
  412.                 Room randomRoom = rooms[random.Next(rooms.Count)];
  413.                
  414.                 // Calculate random position within the room (avoiding walls)
  415.                 int eventX = random.Next(randomRoom.X + 1, randomRoom.X + randomRoom.Width - 1);
  416.                 int eventY = random.Next(randomRoom.Y + 1, randomRoom.Y + randomRoom.Height - 1);
  417.                
  418.                 // Move the event to the new position, setting Y to 0.5f to match player height
  419.                 Vector3 newPosition = new Vector3(eventX + 0.5f, 0.25f, eventY + 0.5f);
  420.                 moveEvent.setPosition(newPosition);
  421.             }
  422.  
  423.             ConnectRooms(rooms);
  424.             ApplyDungeonToMap();
  425.             PlacePlayerInRandomRoom(rooms);
  426.         }
  427.  
  428.         private void ConnectRooms(List<Room> rooms)
  429.         {
  430.             for (int i = 1; i < rooms.Count; i++)
  431.             {
  432.                 int prevRoomCenterX = Clamp(rooms[i - 1].CenterX, 1, currentDungeon.Width - 2);
  433.                 int prevRoomCenterY = Clamp(rooms[i - 1].CenterY, 1, currentDungeon.Height - 2);
  434.                 int currRoomCenterX = Clamp(rooms[i].CenterX, 1, currentDungeon.Width - 2);
  435.                 int currRoomCenterY = Clamp(rooms[i].CenterY, 1, currentDungeon.Height - 2);
  436.  
  437.                 CreateCorridor(prevRoomCenterX, prevRoomCenterY, currRoomCenterX, currRoomCenterY);
  438.             }
  439.         }
  440.  
  441.         private void CreateCorridor(int fromX, int fromY, int toX, int toY)
  442.         {
  443.             int currentX = fromX;
  444.             int currentY = fromY;
  445.  
  446.             bool horizontalFirst = new Random().Next(2) == 0;
  447.  
  448.             if (horizontalFirst)
  449.             {
  450.                 while (currentX != toX)
  451.                 {
  452.                     currentX += Math.Sign(toX - currentX);
  453.                     CreateCorridorTile(currentX, currentY);
  454.                 }
  455.                 while (currentY != toY)
  456.                 {
  457.                     currentY += Math.Sign(toY - currentY);
  458.                     CreateCorridorTile(currentX, currentY);
  459.                 }
  460.             }
  461.             else
  462.             {
  463.                 while (currentY != toY)
  464.                 {
  465.                     currentY += Math.Sign(toY - currentY);
  466.                     CreateCorridorTile(currentX, currentY);
  467.                 }
  468.                 while (currentX != toX)
  469.                 {
  470.                     currentX += Math.Sign(toX - currentX);
  471.                     CreateCorridorTile(currentX, currentY);
  472.                 }
  473.             }
  474.         }
  475.  
  476.         private void CreateCorridorTile(int x, int y)
  477.         {
  478.             if (x > 0 && x < currentDungeon.Width - 1 && y > 0 && y < currentDungeon.Height - 1)
  479.             {
  480.                 currentDungeon.TerrainData[x, y] = currentDungeon.CorridorType;
  481.  
  482.                 if (currentDungeon.TerrainData[x - 1, y] == currentDungeon.WallType)
  483.                     currentDungeon.TerrainData[x - 1, y] = currentDungeon.WallType;
  484.                 if (currentDungeon.TerrainData[x + 1, y] == currentDungeon.WallType)
  485.                     currentDungeon.TerrainData[x + 1, y] = currentDungeon.WallType;
  486.                 if (currentDungeon.TerrainData[x, y - 1] == currentDungeon.WallType)
  487.                     currentDungeon.TerrainData[x, y - 1] = currentDungeon.WallType;
  488.                 if (currentDungeon.TerrainData[x, y + 1] == currentDungeon.WallType)
  489.                     currentDungeon.TerrainData[x, y + 1] = currentDungeon.WallType;
  490.             }
  491.         }
  492.  
  493.         private int Clamp(int value, int min, int max)
  494.         {
  495.             return (value < min) ? min : (value > max) ? max : value;
  496.         }
  497.  
  498.         private void PlacePlayerInRandomRoom(List<Room> rooms)
  499.         {
  500.             if (rooms.Count == 0 || mapScene?.hero == null) return;
  501.  
  502.             // Pick a random room
  503.             Random random = new Random();
  504.             Room randomRoom = rooms[random.Next(rooms.Count)];
  505.            
  506.             // Calculate random position within the room (avoiding walls)
  507.             int playerX = random.Next(randomRoom.X + 1, randomRoom.X + randomRoom.Width - 1);
  508.             int playerY = random.Next(randomRoom.Y + 1, randomRoom.Y + randomRoom.Height - 1);
  509.            
  510.             // Move the player to the new position
  511.             Vector3 newPosition = new Vector3(playerX + 0.5f, 0, playerY + 0.5f);
  512.             mapScene.hero.setPosition(newPosition);
  513.         }
  514.     }
  515.  
  516.     [Serializable]
  517.     public class DungeonData
  518.     {
  519.         public int Width { get; set; }
  520.         public int Height { get; set; }
  521.         public int[,] TerrainData { get; set; }
  522.         public int FloorType { get; set; }
  523.         public int WallType { get; set; }
  524.         public int CorridorType { get; set; }
  525.  
  526.         public DungeonData(int width, int height)
  527.         {
  528.             Width = width;
  529.             Height = height;
  530.             TerrainData = new int[width, height];
  531.             FloorType = DungeonConstants.DEFAULT_FLOOR;
  532.             WallType = DungeonConstants.DEFAULT_WALL;
  533.             CorridorType = DungeonConstants.DEFAULT_CORRIDOR;
  534.         }
  535.     }
  536.  
  537.     public class Room
  538.     {
  539.         public int X, Y, Width, Height;
  540.  
  541.         public Room(int x, int y, int width, int height)
  542.         {
  543.             X = x;
  544.             Y = y;
  545.             Width = width;
  546.             Height = height;
  547.         }
  548.  
  549.         public int CenterX => X + Width / 2;
  550.         public int CenterY => Y + Height / 2;
  551.  
  552.         public bool Intersects(Room other)
  553.         {
  554.             return X <= other.X + other.Width &&
  555.                    X + Width >= other.X &&
  556.                    Y <= other.Y + other.Height &&
  557.                    Y + Height >= other.Y;
  558.         }
  559.     }
  560. }
  561.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement