feat:1、创建项目
This commit is contained in:
@@ -0,0 +1,363 @@
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using ChillConnect;
|
||||
|
||||
public class ArrowLevelAutoGenerator : EditorWindow
|
||||
{
|
||||
// ----- 参数 -----
|
||||
private int _levelCount = 10;
|
||||
private bool _autoSizeByDifficulty = true;
|
||||
private int _gridRows = 8;
|
||||
private int _gridCols = 8;
|
||||
private int _pointSpacing = 60;
|
||||
private Difficulty _difficulty = Difficulty.Hard;
|
||||
private GenerationMode _mode = GenerationMode.Random;
|
||||
private PatternType _pattern = PatternType.Heart;
|
||||
private bool _randomPattern = false;
|
||||
private string _outputFileName = "all_levels.json";
|
||||
|
||||
private enum Difficulty { Easy, Medium, Hard }
|
||||
private enum GenerationMode { Random, Pattern }
|
||||
private enum PatternType { Heart, Star, LetterA, Diamond, Circle, XShape, Random }
|
||||
|
||||
[MenuItem("Tools/Arrow Level Auto Generator")]
|
||||
public static void ShowWindow()
|
||||
{
|
||||
GetWindow<ArrowLevelAutoGenerator>("自动生成关卡");
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
GUILayout.Label("自动化关卡生成器", EditorStyles.boldLabel);
|
||||
EditorGUILayout.Space();
|
||||
|
||||
_levelCount = EditorGUILayout.IntField("关卡数量", _levelCount);
|
||||
_autoSizeByDifficulty = EditorGUILayout.Toggle("根据难度自动调整网格尺寸", _autoSizeByDifficulty);
|
||||
if (!_autoSizeByDifficulty)
|
||||
{
|
||||
_gridRows = EditorGUILayout.IntField("网格行数", _gridRows);
|
||||
_gridCols = EditorGUILayout.IntField("网格列数", _gridCols);
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUILayout.HelpBox("网格尺寸:简单6×6,中等8×8,困难34×21", MessageType.Info);
|
||||
}
|
||||
|
||||
_pointSpacing = EditorGUILayout.IntField("点间距", _pointSpacing);
|
||||
_difficulty = (Difficulty)EditorGUILayout.EnumPopup("难度", _difficulty);
|
||||
_mode = (GenerationMode)EditorGUILayout.EnumPopup("生成模式", _mode);
|
||||
|
||||
if (_mode == GenerationMode.Pattern)
|
||||
{
|
||||
_randomPattern = EditorGUILayout.Toggle("随机图案", _randomPattern);
|
||||
if (!_randomPattern)
|
||||
{
|
||||
_pattern = (PatternType)EditorGUILayout.EnumPopup("指定图案", _pattern);
|
||||
}
|
||||
}
|
||||
|
||||
_outputFileName = EditorGUILayout.TextField("输出文件名", _outputFileName);
|
||||
|
||||
EditorGUILayout.Space();
|
||||
if (GUILayout.Button("生成关卡", GUILayout.Height(40)))
|
||||
{
|
||||
GenerateLevels();
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateLevels()
|
||||
{
|
||||
if (_levelCount <= 0)
|
||||
{
|
||||
EditorUtility.DisplayDialog("错误", "关卡数量必须大于0", "确定");
|
||||
return;
|
||||
}
|
||||
|
||||
List<LevelConfig> levels = new List<LevelConfig>();
|
||||
|
||||
for (int i = 0; i < _levelCount; i++)
|
||||
{
|
||||
LevelConfig lv = null;
|
||||
int rows, cols;
|
||||
if (_autoSizeByDifficulty)
|
||||
GetSizeByDifficulty(out rows, out cols);
|
||||
else
|
||||
{
|
||||
rows = _gridRows;
|
||||
cols = _gridCols;
|
||||
}
|
||||
|
||||
if (_mode == GenerationMode.Pattern)
|
||||
{
|
||||
PatternType selectedPattern = _pattern;
|
||||
if (_randomPattern)
|
||||
selectedPattern = (PatternType)Random.Range(0, System.Enum.GetValues(typeof(PatternType)).Length - 1);
|
||||
lv = GeneratePatternLevel(selectedPattern, i + 1, rows, cols);
|
||||
if (lv == null)
|
||||
{
|
||||
Debug.LogWarning($"图案生成失败,回退到随机");
|
||||
lv = GenerateRandomLevel(i + 1, rows, cols);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
lv = GenerateRandomLevel(i + 1, rows, cols);
|
||||
}
|
||||
|
||||
if (lv != null)
|
||||
levels.Add(lv);
|
||||
}
|
||||
|
||||
if (levels.Count == 0)
|
||||
{
|
||||
EditorUtility.DisplayDialog("错误", "没有生成任何有效关卡", "确定");
|
||||
return;
|
||||
}
|
||||
|
||||
AllLevelRoot root = new AllLevelRoot();
|
||||
root.levels = levels;
|
||||
string json = JsonUtility.ToJson(root, true);
|
||||
string path = EditorUtility.SaveFilePanel("保存关卡配置", Application.dataPath, _outputFileName, "json");
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
{
|
||||
File.WriteAllText(path, json);
|
||||
EditorUtility.DisplayDialog("成功", $"已生成 {levels.Count} 个关卡并保存到 {path}", "确定");
|
||||
}
|
||||
}
|
||||
|
||||
private void GetSizeByDifficulty(out int rows, out int cols)
|
||||
{
|
||||
switch (_difficulty)
|
||||
{
|
||||
case Difficulty.Easy: rows = 6; cols = 6; break;
|
||||
case Difficulty.Medium: rows = 8; cols = 8; break;
|
||||
case Difficulty.Hard: rows = 34; cols = 21; break;
|
||||
default: rows = 8; cols = 8; break;
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 改进的随机生成(无交叉) ==========
|
||||
private LevelConfig GenerateRandomLevel(int levelId, int rows, int cols)
|
||||
{
|
||||
LevelConfig lv = new LevelConfig();
|
||||
lv.levelId = levelId;
|
||||
lv.levelName = $"第{levelId}关";
|
||||
lv.gridRows = rows;
|
||||
lv.gridCols = cols;
|
||||
lv.pointSpacing = _pointSpacing;
|
||||
lv.arrows = new List<ArrowConfig>();
|
||||
|
||||
int arrowCount = 0;
|
||||
int minPathLen=0,maxPathLen=0;
|
||||
int maxTurns = 0;
|
||||
switch (_difficulty)
|
||||
{
|
||||
case Difficulty.Easy: arrowCount = Random.Range(3, 6); minPathLen = 1; maxPathLen = 3; maxTurns = 1; break;
|
||||
case Difficulty.Medium: arrowCount = Random.Range(6, 10); minPathLen = 2; maxPathLen = 5; maxTurns = 2; break;
|
||||
case Difficulty.Hard: arrowCount = Random.Range(25, 40); minPathLen = 4; maxPathLen = 10; maxTurns = 5; break;
|
||||
}
|
||||
|
||||
HashSet<int> occupiedPoints = new HashSet<int>();
|
||||
int maxAttemptsPerArrow = 200;
|
||||
int totalAttempts = 0;
|
||||
int maxTotalAttempts = arrowCount * 50;
|
||||
|
||||
for (int i = 0; i < arrowCount; i++)
|
||||
{
|
||||
bool success = false;
|
||||
int attempts = 0;
|
||||
while (!success && attempts < maxAttemptsPerArrow && totalAttempts < maxTotalAttempts)
|
||||
{
|
||||
attempts++;
|
||||
totalAttempts++;
|
||||
|
||||
int start = Random.Range(0, rows * cols);
|
||||
if (occupiedPoints.Contains(start))
|
||||
continue;
|
||||
|
||||
int pathLen = Random.Range(minPathLen, maxPathLen + 1);
|
||||
List<string> dirs = new List<string>();
|
||||
int cur = start;
|
||||
bool valid = true;
|
||||
int turns = 0;
|
||||
string lastDir = "";
|
||||
List<int> pathPoints = new List<int> { start };
|
||||
|
||||
for (int j = 0; j < pathLen; j++)
|
||||
{
|
||||
List<string> validDirs = GetValidDirections(cur, rows, cols);
|
||||
if (validDirs.Count == 0) { valid = false; break; }
|
||||
string chosenDir = "";
|
||||
if (turns < maxTurns && !string.IsNullOrEmpty(lastDir))
|
||||
{
|
||||
var otherDirs = validDirs.Where(d => d != lastDir).ToList();
|
||||
if (otherDirs.Count > 0)
|
||||
chosenDir = otherDirs[Random.Range(0, otherDirs.Count)];
|
||||
else
|
||||
chosenDir = validDirs[Random.Range(0, validDirs.Count)];
|
||||
}
|
||||
else
|
||||
{
|
||||
chosenDir = validDirs[Random.Range(0, validDirs.Count)];
|
||||
}
|
||||
if (chosenDir != lastDir && !string.IsNullOrEmpty(lastDir))
|
||||
turns++;
|
||||
dirs.Add(chosenDir);
|
||||
cur = GetNextPoint(cur, chosenDir, cols, rows);
|
||||
if (cur < 0) { valid = false; break; }
|
||||
pathPoints.Add(cur);
|
||||
lastDir = chosenDir;
|
||||
}
|
||||
|
||||
if (!valid) continue;
|
||||
|
||||
// 检测冲突
|
||||
bool conflict = false;
|
||||
foreach (int p in pathPoints)
|
||||
{
|
||||
if (occupiedPoints.Contains(p))
|
||||
{
|
||||
conflict = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (conflict) continue;
|
||||
|
||||
// 无冲突,添加箭头
|
||||
ArrowConfig arrow = new ArrowConfig();
|
||||
arrow.id = i + 1;
|
||||
arrow.startPoint = start;
|
||||
arrow.path = dirs;
|
||||
lv.arrows.Add(arrow);
|
||||
|
||||
foreach (int p in pathPoints)
|
||||
occupiedPoints.Add(p);
|
||||
|
||||
success = true;
|
||||
}
|
||||
|
||||
if (!success)
|
||||
Debug.LogWarning($"生成第 {levelId} 关时,第 {i+1} 个箭头生成失败,跳过");
|
||||
}
|
||||
|
||||
if (lv.arrows.Count == 0) return null;
|
||||
return lv;
|
||||
}
|
||||
|
||||
private List<string> GetValidDirections(int point, int rows, int cols)
|
||||
{
|
||||
List<string> dirs = new List<string>();
|
||||
int row = point / cols;
|
||||
int col = point % cols;
|
||||
if (row > 0) dirs.Add("up");
|
||||
if (row < rows - 1) dirs.Add("down");
|
||||
if (col > 0) dirs.Add("left");
|
||||
if (col < cols - 1) dirs.Add("right");
|
||||
return dirs;
|
||||
}
|
||||
|
||||
// ========== 图案生成(简单版) ==========
|
||||
private LevelConfig GeneratePatternLevel(PatternType pattern, int levelId, int rows, int cols)
|
||||
{
|
||||
// 这里仅实现菱形图案,您可自行扩展
|
||||
return GenerateDiamondPattern(levelId, rows, cols);
|
||||
}
|
||||
|
||||
private LevelConfig GenerateDiamondPattern(int levelId, int rows, int cols)
|
||||
{
|
||||
if (rows < 5 || cols < 5) return null;
|
||||
LevelConfig lv = new LevelConfig();
|
||||
lv.levelId = levelId;
|
||||
lv.levelName = $"图案Diamond_{levelId}";
|
||||
lv.gridRows = rows;
|
||||
lv.gridCols = cols;
|
||||
lv.pointSpacing = _pointSpacing;
|
||||
lv.arrows = new List<ArrowConfig>();
|
||||
|
||||
int centerRow = rows / 2;
|
||||
int centerCol = cols / 2;
|
||||
int radius = Mathf.Min(rows, cols) / 2 - 1;
|
||||
if (radius < 2) return null;
|
||||
|
||||
List<int> points = new List<int>();
|
||||
for (int i = 0; i <= radius; i++)
|
||||
{
|
||||
int r = centerRow - i;
|
||||
int c = centerCol + i;
|
||||
if (r >= 0 && c < cols) points.Add(r * cols + c);
|
||||
}
|
||||
for (int i = 1; i <= radius; i++)
|
||||
{
|
||||
int r = centerRow + i;
|
||||
int c = centerCol + radius - i;
|
||||
if (r < rows && c >= 0) points.Add(r * cols + c);
|
||||
}
|
||||
for (int i = 1; i <= radius; i++)
|
||||
{
|
||||
int r = centerRow + radius - i;
|
||||
int c = centerCol - i;
|
||||
if (r >= 0 && c >= 0) points.Add(r * cols + c);
|
||||
}
|
||||
for (int i = 1; i < radius; i++)
|
||||
{
|
||||
int r = centerRow - i;
|
||||
int c = centerCol - radius + i;
|
||||
if (r >= 0 && c >= 0) points.Add(r * cols + c);
|
||||
}
|
||||
if (points.Count < 3) return null;
|
||||
|
||||
int maxStep = 4;
|
||||
int arrowId = 1;
|
||||
for (int i = 0; i < points.Count - 1;)
|
||||
{
|
||||
int remaining = points.Count - 1 - i;
|
||||
int step = Mathf.Min(maxStep, remaining);
|
||||
var segment = points.Skip(i).Take(step + 1).ToList();
|
||||
if (segment.Count >= 2)
|
||||
{
|
||||
ArrowConfig arrow = new ArrowConfig();
|
||||
arrow.id = arrowId++;
|
||||
arrow.startPoint = segment[0];
|
||||
arrow.path = new List<string>();
|
||||
for (int j = 0; j < segment.Count - 1; j++)
|
||||
{
|
||||
string dir = GetDirection(segment[j], segment[j + 1], cols);
|
||||
arrow.path.Add(dir);
|
||||
}
|
||||
lv.arrows.Add(arrow);
|
||||
}
|
||||
i += step;
|
||||
}
|
||||
return lv;
|
||||
}
|
||||
|
||||
// ========== 工具方法 ==========
|
||||
private int GetNextPoint(int current, string dir, int cols, int rows)
|
||||
{
|
||||
int row = current / cols;
|
||||
int col = current % cols;
|
||||
switch (dir)
|
||||
{
|
||||
case "up": row--; break;
|
||||
case "down": row++; break;
|
||||
case "left": col--; break;
|
||||
case "right": col++; break;
|
||||
}
|
||||
if (row < 0 || row >= rows || col < 0 || col >= cols) return -1;
|
||||
return row * cols + col;
|
||||
}
|
||||
|
||||
private string GetDirection(int from, int to, int cols)
|
||||
{
|
||||
int fromRow = from / cols, fromCol = from % cols;
|
||||
int toRow = to / cols, toCol = to % cols;
|
||||
if (toRow < fromRow) return "up";
|
||||
if (toRow > fromRow) return "down";
|
||||
if (toCol < fromCol) return "left";
|
||||
if (toCol > fromCol) return "right";
|
||||
return "up";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1d00ed1816894c03a2aa8dcf12d00069
|
||||
timeCreated: 1781774604
|
||||
@@ -0,0 +1,495 @@
|
||||
// 文件名: ArrowLevelEditor.cs
|
||||
// 路径: Assets/Editor/ArrowLevelEditor.cs
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using ChillConnect;
|
||||
|
||||
public class ArrowLevelEditor : EditorWindow
|
||||
{
|
||||
// ========== 多关卡数据 ==========
|
||||
private List<LevelConfig> _levels = new List<LevelConfig>();
|
||||
private int _selectedLevelIndex = -1;
|
||||
private int _nextLevelId = 1;
|
||||
|
||||
// ========== 当前正在编辑的箭头路径 ==========
|
||||
private List<int> _currentPathPoints = new List<int>();
|
||||
private int _arrowIdCounter = 1;
|
||||
|
||||
// ========== 预设颜色 ==========
|
||||
private readonly Color[] _colorPalette = new Color[]
|
||||
{
|
||||
new Color(1f, 0.3f, 0.3f),
|
||||
new Color(1f, 0.6f, 0.2f),
|
||||
new Color(1f, 0.9f, 0.2f),
|
||||
new Color(0.3f, 0.8f, 0.4f),
|
||||
new Color(0.2f, 0.8f, 0.9f),
|
||||
new Color(0.3f, 0.5f, 1f),
|
||||
new Color(0.7f, 0.4f, 1f),
|
||||
new Color(1f, 0.5f, 0.8f)
|
||||
};
|
||||
|
||||
private Vector2 _scrollPos;
|
||||
|
||||
[MenuItem("Tools/Arrow Level Editor")]
|
||||
public static void ShowWindow()
|
||||
{
|
||||
GetWindow<ArrowLevelEditor>("箭头关卡编辑器 (多关卡)");
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
if (_levels.Count == 0)
|
||||
CreateNewLevel("第1关", 8, 8, 60);
|
||||
if (_selectedLevelIndex < 0 || _selectedLevelIndex >= _levels.Count)
|
||||
_selectedLevelIndex = 0;
|
||||
OnLevelChanged();
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
_scrollPos = EditorGUILayout.BeginScrollView(_scrollPos);
|
||||
|
||||
// ========== 顶部工具栏 ==========
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
if (GUILayout.Button("导入 all_levels.json", GUILayout.Width(150))) ImportLevels();
|
||||
if (GUILayout.Button("导出 all_levels.json", GUILayout.Width(150))) ExportAllLevels();
|
||||
GUILayout.FlexibleSpace();
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.Space(10);
|
||||
|
||||
// ========== 关卡管理 ==========
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
GUILayout.Label("关卡列表", EditorStyles.boldLabel);
|
||||
if (GUILayout.Button("新建关卡", GUILayout.Width(80)))
|
||||
CreateNewLevel($"第{_levels.Count + 1}关", 8, 8, 60);
|
||||
if (GUILayout.Button("删除当前", GUILayout.Width(80)))
|
||||
{
|
||||
if (_selectedLevelIndex >= 0 && _selectedLevelIndex < _levels.Count)
|
||||
{
|
||||
if (EditorUtility.DisplayDialog("删除关卡", $"确定删除 '{_levels[_selectedLevelIndex].levelName}'?", "确定", "取消"))
|
||||
{
|
||||
_levels.RemoveAt(_selectedLevelIndex);
|
||||
if (_levels.Count == 0) CreateNewLevel("第1关", 8, 8, 60);
|
||||
_selectedLevelIndex = Mathf.Clamp(_selectedLevelIndex, 0, _levels.Count - 1);
|
||||
OnLevelChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (GUILayout.Button("复制当前", GUILayout.Width(80)))
|
||||
{
|
||||
if (_selectedLevelIndex >= 0)
|
||||
{
|
||||
var src = _levels[_selectedLevelIndex];
|
||||
LevelConfig copy = new LevelConfig();
|
||||
copy.levelId = _nextLevelId++;
|
||||
copy.levelName = src.levelName + "_副本";
|
||||
copy.gridRows = src.gridRows;
|
||||
copy.gridCols = src.gridCols;
|
||||
copy.pointSpacing = src.pointSpacing;
|
||||
copy.arrows = new List<ArrowConfig>();
|
||||
foreach (var a in src.arrows)
|
||||
{
|
||||
ArrowConfig newA = new ArrowConfig();
|
||||
newA.id = a.id;
|
||||
newA.startPoint = a.startPoint;
|
||||
newA.path = new List<string>(a.path);
|
||||
copy.arrows.Add(newA);
|
||||
}
|
||||
_levels.Add(copy);
|
||||
_selectedLevelIndex = _levels.Count - 1;
|
||||
OnLevelChanged();
|
||||
}
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
// 显示关卡列表
|
||||
for (int i = 0; i < _levels.Count; i++)
|
||||
{
|
||||
var lv = _levels[i];
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
if (i == _selectedLevelIndex) GUI.backgroundColor = Color.green;
|
||||
if (GUILayout.Button($"ID:{lv.levelId} {lv.levelName} ({lv.arrows.Count}箭头)", GUILayout.MinWidth(150)))
|
||||
{
|
||||
if (_selectedLevelIndex != i)
|
||||
{
|
||||
_selectedLevelIndex = i;
|
||||
OnLevelChanged();
|
||||
}
|
||||
}
|
||||
GUI.backgroundColor = Color.white;
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
GUILayout.Space(10);
|
||||
|
||||
// ========== 当前关卡编辑区 ==========
|
||||
if (_selectedLevelIndex >= 0 && _selectedLevelIndex < _levels.Count)
|
||||
{
|
||||
LevelConfig currentLevel = _levels[_selectedLevelIndex];
|
||||
|
||||
EditorGUILayout.LabelField($"编辑关卡: {currentLevel.levelName}", EditorStyles.boldLabel);
|
||||
currentLevel.levelId = EditorGUILayout.IntField("关卡ID", currentLevel.levelId);
|
||||
currentLevel.levelName = EditorGUILayout.TextField("关卡名称", currentLevel.levelName);
|
||||
currentLevel.gridRows = EditorGUILayout.IntField("行数", currentLevel.gridRows);
|
||||
currentLevel.gridCols = EditorGUILayout.IntField("列数", currentLevel.gridCols);
|
||||
currentLevel.pointSpacing = EditorGUILayout.IntField("点间距", currentLevel.pointSpacing);
|
||||
|
||||
GUILayout.Space(10);
|
||||
|
||||
EditorGUILayout.LabelField("绘制当前箭头", EditorStyles.boldLabel);
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
if (GUILayout.Button("撤销上一点") && _currentPathPoints.Count > 0)
|
||||
_currentPathPoints.RemoveAt(_currentPathPoints.Count - 1);
|
||||
if (GUILayout.Button("清空当前路径"))
|
||||
_currentPathPoints.Clear();
|
||||
if (GUILayout.Button("清空所有箭头"))
|
||||
{
|
||||
if (EditorUtility.DisplayDialog("确认", "删除当前关卡所有箭头?", "确定", "取消"))
|
||||
{
|
||||
currentLevel.arrows.Clear();
|
||||
_arrowIdCounter = 1;
|
||||
_currentPathPoints.Clear();
|
||||
}
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
// 网格绘制(增强:显示起点/终点标记)
|
||||
DrawGridWithArrows(currentLevel);
|
||||
|
||||
// 确认生成
|
||||
if (GUILayout.Button("确认生成当前箭头 (加入列表)"))
|
||||
{
|
||||
if (_currentPathPoints.Count < 2)
|
||||
{
|
||||
EditorUtility.DisplayDialog("错误", "路径至少需要2个点", "确定");
|
||||
}
|
||||
else
|
||||
{
|
||||
ArrowConfig newArrow = new ArrowConfig();
|
||||
newArrow.id = _arrowIdCounter++;
|
||||
newArrow.startPoint = _currentPathPoints[0];
|
||||
newArrow.path = new List<string>();
|
||||
for (int i = 0; i < _currentPathPoints.Count - 1; i++)
|
||||
{
|
||||
newArrow.path.Add(GetDirection(_currentPathPoints[i], _currentPathPoints[i + 1], currentLevel.gridCols));
|
||||
}
|
||||
int colorIdx = (currentLevel.arrows.Count) % _colorPalette.Length;
|
||||
newArrow.runtimeColor = _colorPalette[colorIdx];
|
||||
currentLevel.arrows.Add(newArrow);
|
||||
_currentPathPoints.Clear();
|
||||
Debug.Log($"箭头已生成,当前箭头总数 {currentLevel.arrows.Count}");
|
||||
}
|
||||
}
|
||||
|
||||
// 箭头列表(显示起点→终点)
|
||||
GUILayout.Space(10);
|
||||
EditorGUILayout.LabelField($"当前箭头 ({currentLevel.arrows.Count})", EditorStyles.boldLabel);
|
||||
for (int i = 0; i < currentLevel.arrows.Count; i++)
|
||||
{
|
||||
var arrow = currentLevel.arrows[i];
|
||||
// 计算终点
|
||||
int endPoint = arrow.startPoint;
|
||||
if (arrow.path.Count > 0)
|
||||
{
|
||||
int cur = arrow.startPoint;
|
||||
foreach (string dir in arrow.path)
|
||||
{
|
||||
int next = GetNextPoint(cur, dir, currentLevel.gridCols, currentLevel.gridRows);
|
||||
if (next >= 0) cur = next;
|
||||
}
|
||||
endPoint = cur;
|
||||
}
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
Color arrowColor = arrow.runtimeColor;
|
||||
if (arrowColor == default)
|
||||
{
|
||||
arrowColor = _colorPalette[i % _colorPalette.Length];
|
||||
arrow.runtimeColor = arrowColor;
|
||||
}
|
||||
GUI.backgroundColor = arrowColor;
|
||||
GUILayout.Label(" ", GUILayout.Width(20), GUILayout.Height(18));
|
||||
GUI.backgroundColor = Color.white;
|
||||
|
||||
if (GUILayout.Button($"ID:{arrow.id} {arrow.startPoint} → {endPoint} 路径:{string.Join(",", arrow.path)}", GUILayout.MinWidth(200)))
|
||||
{
|
||||
// 可扩展高亮
|
||||
}
|
||||
if (GUILayout.Button("✕", GUILayout.Width(20)))
|
||||
{
|
||||
if (EditorUtility.DisplayDialog("删除箭头", $"删除箭头 ID:{arrow.id}?", "确定", "取消"))
|
||||
{
|
||||
currentLevel.arrows.RemoveAt(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUILayout.HelpBox("请先创建或选择一个关卡", MessageType.Info);
|
||||
}
|
||||
|
||||
EditorGUILayout.EndScrollView();
|
||||
}
|
||||
|
||||
// ========== 辅助方法 ==========
|
||||
|
||||
private void CreateNewLevel(string name, int rows, int cols, int spacing)
|
||||
{
|
||||
LevelConfig lv = new LevelConfig();
|
||||
lv.levelId = _nextLevelId++;
|
||||
lv.levelName = name;
|
||||
lv.gridRows = rows;
|
||||
lv.gridCols = cols;
|
||||
lv.pointSpacing = spacing;
|
||||
lv.arrows = new List<ArrowConfig>();
|
||||
_levels.Add(lv);
|
||||
_selectedLevelIndex = _levels.Count - 1;
|
||||
OnLevelChanged();
|
||||
}
|
||||
|
||||
private void OnLevelChanged()
|
||||
{
|
||||
_currentPathPoints.Clear();
|
||||
if (_selectedLevelIndex >= 0 && _selectedLevelIndex < _levels.Count)
|
||||
{
|
||||
var lv = _levels[_selectedLevelIndex];
|
||||
_arrowIdCounter = lv.arrows.Count + 1;
|
||||
for (int i = 0; i < lv.arrows.Count; i++)
|
||||
{
|
||||
if (lv.arrows[i].runtimeColor == default)
|
||||
lv.arrows[i].runtimeColor = _colorPalette[i % _colorPalette.Length];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 增强的网格绘制(标记起点/终点) ==========
|
||||
private void DrawGridWithArrows(LevelConfig level)
|
||||
{
|
||||
int totalPoints = level.gridRows * level.gridCols;
|
||||
int rows = level.gridRows;
|
||||
int cols = level.gridCols;
|
||||
|
||||
// 构建:点ID -> (箭头索引, 是否起点, 是否终点)
|
||||
var pointInfo = new Dictionary<int, List<(int idx, bool isStart, bool isEnd)>>();
|
||||
for (int i = 0; i < level.arrows.Count; i++)
|
||||
{
|
||||
var arrow = level.arrows[i];
|
||||
List<int> allPoints = new List<int> { arrow.startPoint };
|
||||
int cur = arrow.startPoint;
|
||||
foreach (string dir in arrow.path)
|
||||
{
|
||||
int next = GetNextPoint(cur, dir, cols, rows);
|
||||
if (next >= 0) allPoints.Add(next);
|
||||
cur = next;
|
||||
}
|
||||
// 终点是路径最后一个点
|
||||
int end = allPoints.LastOrDefault();
|
||||
for (int j = 0; j < allPoints.Count; j++)
|
||||
{
|
||||
int pid = allPoints[j];
|
||||
bool isStart = (j == 0);
|
||||
bool isEnd = (j == allPoints.Count - 1);
|
||||
if (!pointInfo.ContainsKey(pid))
|
||||
pointInfo[pid] = new List<(int, bool, bool)>();
|
||||
pointInfo[pid].Add((i, isStart, isEnd));
|
||||
}
|
||||
}
|
||||
|
||||
// 构建每个点的显示标签
|
||||
var pointLabels = new Dictionary<int, string>();
|
||||
foreach (var kv in pointInfo)
|
||||
{
|
||||
int pid = kv.Key;
|
||||
var list = kv.Value;
|
||||
bool hasStart = list.Any(x => x.isStart);
|
||||
bool hasEnd = list.Any(x => x.isEnd);
|
||||
bool multi = list.Count > 1;
|
||||
string label = "";
|
||||
if (hasStart && hasEnd)
|
||||
label = "S/E";
|
||||
else if (hasStart)
|
||||
label = "S";
|
||||
else if (hasEnd)
|
||||
label = "E";
|
||||
if (multi && (hasStart || hasEnd))
|
||||
label += "+";
|
||||
else if (multi && !hasStart && !hasEnd)
|
||||
label = "+";
|
||||
pointLabels[pid] = label;
|
||||
}
|
||||
|
||||
// 绘制网格
|
||||
for (int i = 0; i < totalPoints; i++)
|
||||
{
|
||||
int row = i / cols;
|
||||
int col = i % cols;
|
||||
if (col == 0) EditorGUILayout.BeginHorizontal();
|
||||
|
||||
bool isInCurrentPath = _currentPathPoints.Contains(i);
|
||||
bool isInSavedPath = pointInfo.ContainsKey(i);
|
||||
|
||||
Color bgColor = GUI.backgroundColor;
|
||||
string label = i.ToString();
|
||||
if (isInSavedPath)
|
||||
{
|
||||
var owners = pointInfo[i];
|
||||
if (owners.Count == 1)
|
||||
{
|
||||
int idx = owners[0].idx;
|
||||
bgColor = level.arrows[idx].runtimeColor;
|
||||
}
|
||||
else
|
||||
{
|
||||
bgColor = Color.gray;
|
||||
}
|
||||
// 添加起点/终点标记
|
||||
if (pointLabels.ContainsKey(i))
|
||||
label += pointLabels[i];
|
||||
}
|
||||
|
||||
if (isInCurrentPath)
|
||||
{
|
||||
bgColor = Color.green;
|
||||
label = i + "●";
|
||||
}
|
||||
|
||||
// 起点/终点特殊颜色边框(仅当无重叠时)
|
||||
if (isInSavedPath && pointInfo[i].Count == 1)
|
||||
{
|
||||
var info = pointInfo[i][0];
|
||||
if (info.isStart)
|
||||
bgColor = Color.Lerp(bgColor, Color.green, 0.3f);
|
||||
else if (info.isEnd)
|
||||
bgColor = Color.Lerp(bgColor, Color.red, 0.3f);
|
||||
}
|
||||
|
||||
GUI.backgroundColor = bgColor;
|
||||
if (GUILayout.Button(label, GUILayout.Width(50), GUILayout.Height(30))) // 增加宽度容纳标记
|
||||
{
|
||||
if (_currentPathPoints.Count == 0)
|
||||
_currentPathPoints.Add(i);
|
||||
else
|
||||
{
|
||||
int last = _currentPathPoints[_currentPathPoints.Count - 1];
|
||||
if (IsAdjacent(last, i, cols))
|
||||
{
|
||||
if (isInSavedPath)
|
||||
Debug.LogWarning($"点 {i} 已被其他箭头占用,可能重叠");
|
||||
_currentPathPoints.Add(i);
|
||||
}
|
||||
else
|
||||
EditorUtility.DisplayDialog("提示", $"必须与上一个点 ({last}) 相邻", "确定");
|
||||
}
|
||||
}
|
||||
|
||||
GUI.backgroundColor = Color.white;
|
||||
if (col == cols - 1) EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 工具方法 ==========
|
||||
|
||||
private int GetNextPoint(int current, string dir, int cols, int rows)
|
||||
{
|
||||
int row = current / cols;
|
||||
int col = current % cols;
|
||||
switch (dir)
|
||||
{
|
||||
case "up": row--; break;
|
||||
case "down": row++; break;
|
||||
case "left": col--; break;
|
||||
case "right": col++; break;
|
||||
}
|
||||
if (row < 0 || row >= rows || col < 0 || col >= cols) return -1;
|
||||
return row * cols + col;
|
||||
}
|
||||
|
||||
private bool IsAdjacent(int a, int b, int cols)
|
||||
{
|
||||
int aRow = a / cols, aCol = a % cols;
|
||||
int bRow = b / cols, bCol = b % cols;
|
||||
return (Mathf.Abs(aRow - bRow) + Mathf.Abs(aCol - bCol)) == 1;
|
||||
}
|
||||
|
||||
private string GetDirection(int from, int to, int cols)
|
||||
{
|
||||
int fromRow = from / cols, fromCol = from % cols;
|
||||
int toRow = to / cols, toCol = to % cols;
|
||||
if (toRow < fromRow) return "up";
|
||||
if (toRow > fromRow) return "down";
|
||||
if (toCol < fromCol) return "left";
|
||||
if (toCol > fromCol) return "right";
|
||||
return "up";
|
||||
}
|
||||
|
||||
// ========== 导入/导出 ==========
|
||||
|
||||
private void ImportLevels()
|
||||
{
|
||||
string path = EditorUtility.OpenFilePanel("导入 all_levels.json", Application.dataPath, "json");
|
||||
if (string.IsNullOrEmpty(path)) return;
|
||||
try
|
||||
{
|
||||
string json = File.ReadAllText(path);
|
||||
AllLevelRoot root = JsonUtility.FromJson<AllLevelRoot>(json);
|
||||
if (root != null && root.levels != null && root.levels.Count > 0)
|
||||
{
|
||||
_levels = root.levels;
|
||||
foreach (var lv in _levels)
|
||||
for (int i = 0; i < lv.arrows.Count; i++)
|
||||
lv.arrows[i].runtimeColor = _colorPalette[i % _colorPalette.Length];
|
||||
int maxId = _levels.Max(l => l.levelId);
|
||||
_nextLevelId = maxId + 1;
|
||||
_selectedLevelIndex = 0;
|
||||
OnLevelChanged();
|
||||
Debug.Log($"导入成功,共 {_levels.Count} 个关卡");
|
||||
}
|
||||
else
|
||||
EditorUtility.DisplayDialog("导入失败", "文件格式不正确或为空", "确定");
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
EditorUtility.DisplayDialog("导入错误", e.Message, "确定");
|
||||
}
|
||||
}
|
||||
|
||||
private void ExportAllLevels()
|
||||
{
|
||||
if (_levels.Count == 0) { EditorUtility.DisplayDialog("错误", "没有关卡可导出", "确定"); return; }
|
||||
AllLevelRoot root = new AllLevelRoot();
|
||||
root.levels = new List<LevelConfig>();
|
||||
foreach (var lv in _levels)
|
||||
{
|
||||
LevelConfig copy = new LevelConfig();
|
||||
copy.levelId = lv.levelId;
|
||||
copy.levelName = lv.levelName;
|
||||
copy.gridRows = lv.gridRows;
|
||||
copy.gridCols = lv.gridCols;
|
||||
copy.pointSpacing = lv.pointSpacing;
|
||||
copy.arrows = new List<ArrowConfig>();
|
||||
foreach (var a in lv.arrows)
|
||||
{
|
||||
ArrowConfig newA = new ArrowConfig();
|
||||
newA.id = a.id;
|
||||
newA.startPoint = a.startPoint;
|
||||
newA.path = new List<string>(a.path);
|
||||
copy.arrows.Add(newA);
|
||||
}
|
||||
root.levels.Add(copy);
|
||||
}
|
||||
string json = JsonUtility.ToJson(root, true);
|
||||
string path = EditorUtility.SaveFilePanel("保存 all_levels.json", Application.dataPath, "all_levels.json", "json");
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
{
|
||||
File.WriteAllText(path, json);
|
||||
EditorUtility.DisplayDialog("成功", $"已保存 {root.levels.Count} 个关卡", "确定");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a75099f55ed545f9b5c1fe0aedc9c82c
|
||||
timeCreated: 1781769832
|
||||
@@ -0,0 +1,261 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace ChillConnect.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// 第三方关卡配置批量转换工具
|
||||
/// 输入:文件夹内所有第三方单关JSON
|
||||
/// 输出:对应转换后的我方格式单关JSON
|
||||
/// </summary>
|
||||
public class LevelBatchConverter : EditorWindow
|
||||
{
|
||||
#region 数据结构定义
|
||||
// 第三方单关结构(只声明需要的字段,多余自动忽略)
|
||||
[System.Serializable]
|
||||
private class ThirdPartyLevel
|
||||
{
|
||||
public Vector2Int size;
|
||||
public string name;
|
||||
public List<ThirdPartyArrow> arrows;
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
private class ThirdPartyArrow
|
||||
{
|
||||
public List<Vector2Int> nodes;
|
||||
public int color;
|
||||
}
|
||||
|
||||
// 我方单关结构(和运行时对齐)
|
||||
[System.Serializable]
|
||||
public class ArrowConfig
|
||||
{
|
||||
public int id;
|
||||
public int startPoint;
|
||||
public List<string> path = new List<string>();
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public class LevelConfig
|
||||
{
|
||||
public int levelId;
|
||||
public string levelName;
|
||||
public int gridRows;
|
||||
public int gridCols;
|
||||
public int pointSpacing = 60;
|
||||
public List<ArrowConfig> arrows = new List<ArrowConfig>();
|
||||
}
|
||||
#endregion
|
||||
|
||||
private string _inputFolder = "";
|
||||
private string _outputFolder = "";
|
||||
private int _pointSpacing = 60;
|
||||
private Vector2 _scrollPos;
|
||||
private List<string> _logList = new List<string>();
|
||||
|
||||
[MenuItem("Window/Arrow Game/批量关卡转换工具")]
|
||||
public static void ShowWindow()
|
||||
{
|
||||
var window = GetWindow<LevelBatchConverter>("批量关卡转换");
|
||||
window.minSize = new Vector2(500, 400);
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
EditorGUILayout.Space(10);
|
||||
|
||||
// 输入文件夹
|
||||
EditorGUILayout.LabelField("输入文件夹(第三方单关JSON)", EditorStyles.boldLabel);
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
_inputFolder = EditorGUILayout.TextField(_inputFolder);
|
||||
if (GUILayout.Button("选择", GUILayout.Width(60)))
|
||||
{
|
||||
string path = EditorUtility.OpenFolderPanel("选择输入文件夹", Application.dataPath, "");
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
_inputFolder = path;
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
EditorGUILayout.Space(8);
|
||||
|
||||
// 输出文件夹
|
||||
EditorGUILayout.LabelField("输出文件夹(转换后JSON)", EditorStyles.boldLabel);
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
_outputFolder = EditorGUILayout.TextField(_outputFolder);
|
||||
if (GUILayout.Button("选择", GUILayout.Width(60)))
|
||||
{
|
||||
string path = EditorUtility.OpenFolderPanel("选择输出文件夹", Application.dataPath, "");
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
_outputFolder = path;
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
EditorGUILayout.Space(8);
|
||||
|
||||
// 基础配置
|
||||
_pointSpacing = EditorGUILayout.IntField("默认点间距", _pointSpacing);
|
||||
|
||||
EditorGUILayout.Space(10);
|
||||
|
||||
// 转换按钮
|
||||
if (GUILayout.Button("开始批量转换", GUILayout.Height(30)))
|
||||
{
|
||||
BatchConvert();
|
||||
}
|
||||
|
||||
EditorGUILayout.Space(10);
|
||||
|
||||
// 日志区域
|
||||
EditorGUILayout.LabelField("转换日志", EditorStyles.boldLabel);
|
||||
_scrollPos = EditorGUILayout.BeginScrollView(_scrollPos, GUILayout.ExpandHeight(true));
|
||||
foreach (string log in _logList)
|
||||
{
|
||||
EditorGUILayout.LabelField(log);
|
||||
}
|
||||
EditorGUILayout.EndScrollView();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量转换核心逻辑
|
||||
/// </summary>
|
||||
private void BatchConvert()
|
||||
{
|
||||
_logList.Clear();
|
||||
|
||||
if (string.IsNullOrEmpty(_inputFolder) || !Directory.Exists(_inputFolder))
|
||||
{
|
||||
_logList.Add("❌ 输入文件夹不存在");
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(_outputFolder))
|
||||
{
|
||||
_logList.Add("❌ 请选择输出文件夹");
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建输出目录
|
||||
if (!Directory.Exists(_outputFolder))
|
||||
{
|
||||
Directory.CreateDirectory(_outputFolder);
|
||||
}
|
||||
|
||||
// 获取所有json文件
|
||||
string[] files = Directory.GetFiles(_inputFolder, "*.json", SearchOption.TopDirectoryOnly);
|
||||
if (files.Length == 0)
|
||||
{
|
||||
_logList.Add("⚠️ 输入文件夹内未找到JSON文件");
|
||||
return;
|
||||
}
|
||||
|
||||
_logList.Add($"找到 {files.Length} 个JSON文件,开始转换...");
|
||||
|
||||
int successCount = 0;
|
||||
int failCount = 0;
|
||||
|
||||
for (int i = 0; i < files.Length; i++)
|
||||
{
|
||||
string filePath = files[i];
|
||||
string fileName = Path.GetFileNameWithoutExtension(filePath);
|
||||
|
||||
try
|
||||
{
|
||||
// 1. 读取并解析第三方配置
|
||||
string json = File.ReadAllText(filePath);
|
||||
ThirdPartyLevel thirdLevel = JsonUtility.FromJson<ThirdPartyLevel>(json);
|
||||
|
||||
if (thirdLevel == null || thirdLevel.arrows == null)
|
||||
{
|
||||
_logList.Add($"❌ {fileName}:解析失败,格式不匹配");
|
||||
failCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 2. 转换为我方格式
|
||||
LevelConfig level = ConvertLevel(thirdLevel, fileName, i + 1);
|
||||
|
||||
// 3. 导出到输出文件夹
|
||||
string outJson = JsonUtility.ToJson(level, true);
|
||||
string outPath = Path.Combine(_outputFolder, $"{fileName}.json");
|
||||
File.WriteAllText(outPath, outJson);
|
||||
|
||||
_logList.Add($"✅ {fileName}:转换成功 | {level.gridCols}×{level.gridRows} | {level.arrows.Count}个箭头");
|
||||
successCount++;
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
_logList.Add($"❌ {fileName}:转换异常 - {e.Message}");
|
||||
failCount++;
|
||||
}
|
||||
}
|
||||
|
||||
_logList.Add("");
|
||||
_logList.Add($"===== 转换完成 =====");
|
||||
_logList.Add($"成功:{successCount} 个");
|
||||
_logList.Add($"失败:{failCount} 个");
|
||||
_logList.Add($"输出路径:{_outputFolder}");
|
||||
|
||||
EditorUtility.DisplayDialog("批量转换完成",
|
||||
$"成功:{successCount}\n失败:{failCount}\n输出路径:{_outputFolder}",
|
||||
"确定");
|
||||
|
||||
AssetDatabase.Refresh();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 单个关卡转换逻辑(和之前编辑器内转换完全一致)
|
||||
/// </summary>
|
||||
private LevelConfig ConvertLevel(ThirdPartyLevel thirdLevel, string fileName, int index)
|
||||
{
|
||||
LevelConfig level = new LevelConfig
|
||||
{
|
||||
levelId = index,
|
||||
levelName = string.IsNullOrEmpty(thirdLevel.name) ? fileName : thirdLevel.name,
|
||||
gridCols = thirdLevel.size.x,
|
||||
gridRows = thirdLevel.size.y,
|
||||
pointSpacing = _pointSpacing,
|
||||
arrows = new List<ArrowConfig>()
|
||||
};
|
||||
|
||||
for (int i = 0; i < thirdLevel.arrows.Count; i++)
|
||||
{
|
||||
var thirdArrow = thirdLevel.arrows[i];
|
||||
if (thirdArrow.nodes == null || thirdArrow.nodes.Count == 0)
|
||||
continue;
|
||||
|
||||
ArrowConfig arrow = new ArrowConfig
|
||||
{
|
||||
id = i + 1,
|
||||
path = new List<string>()
|
||||
};
|
||||
|
||||
// 起点转换:(x,y) → 点ID = y * 列数 + x
|
||||
Vector2Int startNode = thirdArrow.nodes[0];
|
||||
arrow.startPoint = startNode.y * level.gridCols + startNode.x;
|
||||
|
||||
// 路径转换:相邻节点计算方向
|
||||
for (int j = 1; j < thirdArrow.nodes.Count; j++)
|
||||
{
|
||||
Vector2Int prev = thirdArrow.nodes[j - 1];
|
||||
Vector2Int cur = thirdArrow.nodes[j];
|
||||
|
||||
int dx = cur.x - prev.x;
|
||||
int dy = cur.y - prev.y;
|
||||
|
||||
if (dx == 1) arrow.path.Add("right");
|
||||
else if (dx == -1) arrow.path.Add("left");
|
||||
else if (dy == 1) arrow.path.Add("down");
|
||||
else if (dy == -1) arrow.path.Add("up");
|
||||
}
|
||||
|
||||
level.arrows.Add(arrow);
|
||||
}
|
||||
|
||||
return level;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 027b5eb354fe4e4698f807233ca6dc5d
|
||||
timeCreated: 1782205667
|
||||
Reference in New Issue
Block a user