319 lines
11 KiB
C#
319 lines
11 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using Godot;
|
||
|
||
/// <summary>
|
||
/// 实验室场景主控脚本
|
||
/// </summary>
|
||
public partial class Lab : Node2D {
|
||
/// <summary>
|
||
/// 地图节点类型标志位
|
||
/// </summary>
|
||
[Flags]
|
||
public enum MapNodeType {
|
||
Invalid = 0, // 无效
|
||
Walkable = 1, // 可行走
|
||
Wall = 2, // 墙壁
|
||
Blocker = 4, // 阻挡物
|
||
SeatUp = 8, // 上方座位
|
||
SeatDown = 16 // 下方座位
|
||
}
|
||
|
||
public const int MapWidth = 40;
|
||
public const int MapHeight = 21;
|
||
|
||
// 墙壁矩形区域列表
|
||
private static readonly Rect2I[] wallRectangles = {
|
||
new(0, 0, 40, 2),
|
||
new(0, 5, 1, 15),
|
||
new(0, 20, 40, 1),
|
||
new(39, 2, 1, 18)
|
||
};
|
||
|
||
private readonly MapNodeType[,] _blocks = new MapNodeType[MapWidth, MapHeight];
|
||
|
||
private readonly Dictionary<Rect2I, ITileDraggable> _furniturePlacement = new();
|
||
|
||
private bool _isDragging;
|
||
|
||
private Label _moneyLabel;
|
||
|
||
private TileMapLayer _tileMap;
|
||
|
||
/// <summary>
|
||
/// 家具字典,通过ID索引
|
||
/// </summary>
|
||
public Dictionary<Guid, ITileDraggable> Furniture { get; } = new();
|
||
|
||
/// <summary>
|
||
/// 当前正在拖拽的目标
|
||
/// </summary>
|
||
public ITileDraggable DraggingTarget { get; set; }
|
||
|
||
// Called when the node enters the scene tree for the first time.
|
||
/// <summary>
|
||
/// 场景加载完成时调用
|
||
/// </summary>
|
||
public override void _Ready() {
|
||
var ticker = GetNode<Timer>("/root/GameManager/OneSecondTicker");
|
||
Player.Timeline.Attach(ticker);
|
||
Player.Timeline.Subscribe(DateOnly.Parse("2024/11/20"), Guid.NewGuid());
|
||
Player.Timeline.OnEventTriggered += (d, e) => GD.Print($"Timeline event triggered: {d}, {e}");
|
||
|
||
var label = GetNode<Label>("BottomBar/HBoxContainer/Date");
|
||
label.Text = Player.Timeline.InternalDate.ToLongDateString();
|
||
Player.Timeline.OnDayChanged += d => label.Text = d.ToLongDateString();
|
||
var table = GetNode<Cube>("Cube");
|
||
table.Draggable = true;
|
||
_moneyLabel = GetNode<Label>("BottomBar/HBoxContainer/Money");
|
||
|
||
var rect = table.TileRect;
|
||
rect.Position += table.TilePosition;
|
||
_furniturePlacement.Add(rect, table);
|
||
Furniture.Add(table.Id, table);
|
||
|
||
_tileMap = GetNode<TileMapLayer>("OutGround");
|
||
UpdateMap();
|
||
|
||
var student = GetNode<Student>("Student");
|
||
student.CubeId = table.Id;
|
||
|
||
var r = new Rect2I(0, 0, 1, 1);
|
||
GD.Print(H.RectHasPointInclusive(r, new Vector2I(1, 0)));
|
||
}
|
||
|
||
// Called every frame. 'delta' is the elapsed time since the previous frame.
|
||
/// <summary>
|
||
/// 每帧更新
|
||
/// </summary>
|
||
/// <param name="delta">帧间隔</param>
|
||
public override void _Process(double delta) {
|
||
switch (GD.RandRange(0, 2)) {
|
||
case 0:
|
||
Player.Budget.Operational++;
|
||
break;
|
||
case 1:
|
||
Player.Budget.Facility += 10;
|
||
break;
|
||
case 2:
|
||
Player.Budget.Labor += 100;
|
||
break;
|
||
}
|
||
|
||
_moneyLabel.Text = Player.Budget.Total.ToString("N0");
|
||
if (_isDragging && DraggingTarget != null) {
|
||
var mousePos = GetLocalMousePosition();
|
||
var cell = Point2Coord(mousePos) - DraggingTarget.MouseOffset;
|
||
var targetRect = DraggingTarget.TileRect;
|
||
targetRect.Position += cell;
|
||
DraggingTarget.IsCollided = wallRectangles.Any(r => r.Intersects(targetRect));
|
||
DraggingTarget.TilePosition = cell;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 拾起物品
|
||
/// </summary>
|
||
/// <param name="target">目标物品</param>
|
||
public void Pickup(ITileDraggable target) {
|
||
if (target == null) return;
|
||
_isDragging = true;
|
||
DraggingTarget = target;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 放下物品
|
||
/// </summary>
|
||
/// <param name="target">目标物品</param>
|
||
public void PutDown(ITileDraggable target) {
|
||
if (target == null) return;
|
||
_isDragging = false;
|
||
_furniturePlacement.Remove(_furniturePlacement.First(kv => kv.Value == target).Key);
|
||
var rect = target.TileRect;
|
||
rect.Position += target.TilePosition;
|
||
_furniturePlacement.Add(rect, target);
|
||
DraggingTarget = null;
|
||
UpdateMap();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新地图数据
|
||
/// </summary>
|
||
public void UpdateMap() {
|
||
for (var i = 0; i < MapWidth; i++)
|
||
for (var j = 0; j < MapHeight; j++) {
|
||
Vector2I vec = new(i, j);
|
||
if (wallRectangles.Any(w => w.HasPoint(vec))) {
|
||
_blocks[i, j] = MapNodeType.Wall;
|
||
}
|
||
else if (_furniturePlacement.Any(f => f.Key.HasPoint(vec))) {
|
||
MapNodeType t = 0;
|
||
foreach (var kv in _furniturePlacement.Where(f => f.Key.HasPoint(vec)))
|
||
t |= kv.Value.GetTileType(vec - kv.Value.TilePosition);
|
||
if ((t & MapNodeType.Walkable) != 0 && (t & MapNodeType.Blocker) != 0) t ^= MapNodeType.Walkable;
|
||
_blocks[i, j] = t;
|
||
}
|
||
else {
|
||
_blocks[i, j] = MapNodeType.Walkable;
|
||
}
|
||
}
|
||
}
|
||
|
||
private static bool IsValidPosition(Vector2I pos) {
|
||
var x = pos.X;
|
||
var y = pos.Y;
|
||
if (x < 0 || x >= MapWidth || y < 0 || y >= MapHeight) return false;
|
||
return true;
|
||
}
|
||
|
||
private List<Vector2I> GetNeighbors(Vector2I pos) {
|
||
var x = pos.X;
|
||
var y = pos.Y;
|
||
var neighbor = new List<Vector2I>();
|
||
if (!IsValidPosition(pos)) return neighbor;
|
||
|
||
if (IsValidPosition(new Vector2I(x - 1, y)) &&
|
||
(_blocks[x - 1, y] & MapNodeType.Walkable) == MapNodeType.Walkable) neighbor.Add(new Vector2I(x - 1, y));
|
||
if (IsValidPosition(new Vector2I(x + 1, y)) &&
|
||
(_blocks[x + 1, y] & MapNodeType.Walkable) == MapNodeType.Walkable) neighbor.Add(new Vector2I(x + 1, y));
|
||
if (IsValidPosition(new Vector2I(x, y - 1)) &&
|
||
(_blocks[x, y - 1] & MapNodeType.Walkable) == MapNodeType.Walkable) neighbor.Add(new Vector2I(x, y - 1));
|
||
if (IsValidPosition(new Vector2I(x, y + 1)) &&
|
||
(_blocks[x, y + 1] & MapNodeType.Walkable) == MapNodeType.Walkable) neighbor.Add(new Vector2I(x, y + 1));
|
||
return neighbor;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取最短路径
|
||
/// </summary>
|
||
/// <param name="start">起点</param>
|
||
/// <param name="end">终点</param>
|
||
/// <returns>路径点列表</returns>
|
||
public List<Vector2I> GetShortestPath(Vector2I start, Vector2I end) {
|
||
for (var j = 0; j < MapHeight; j++) {
|
||
var t = "";
|
||
for (var i = 0; i < MapWidth; i++)
|
||
if ((_blocks[i, j] & MapNodeType.SeatUp) != 0)
|
||
t += "U";
|
||
else if ((_blocks[i, j] & MapNodeType.SeatDown) != 0)
|
||
t += "D";
|
||
else
|
||
t += $"{_blocks[i, j].ToString()[0]}";
|
||
|
||
GD.Print(t);
|
||
}
|
||
|
||
var path = new List<Vector2I>();
|
||
if (_tileMap.GetCellSourceId(start) == -1 || _tileMap.GetCellSourceId(end) == -1) return path;
|
||
if (start == end) return path;
|
||
GD.Print($"start = {start}, end = {end}");
|
||
|
||
static int Vec2Idx(Vector2I v) {
|
||
return v.Y * MapWidth + v.X;
|
||
}
|
||
|
||
static Vector2I Idx2Vec2(int i) {
|
||
return new Vector2I(i % MapWidth, i / MapWidth);
|
||
}
|
||
|
||
HashSet<int> entriesIdx = new();
|
||
foreach (var e in GetNeighbors(end)) entriesIdx.Add(Vec2Idx(e));
|
||
if (entriesIdx.Count == 0) return path;
|
||
|
||
// Use Dijkstra algorithm to find the shortest path
|
||
var dist = new int[MapWidth * MapHeight];
|
||
var prev = new int[MapWidth * MapHeight];
|
||
var visited = new bool[MapWidth * MapHeight];
|
||
|
||
for (var i = 0; i < dist.Length; i++) {
|
||
dist[i] = int.MaxValue;
|
||
prev[i] = -1;
|
||
visited[i] = false;
|
||
}
|
||
|
||
dist[Vec2Idx(start)] = 0;
|
||
|
||
var answer = -1;
|
||
for (var i = 0; i < dist.Length; i++) {
|
||
var minDistance = int.MaxValue;
|
||
var u = -1;
|
||
for (var j = 0; j < dist.Length; j++)
|
||
if (!visited[j] && dist[j] <= minDistance) {
|
||
minDistance = dist[j];
|
||
u = j;
|
||
}
|
||
|
||
if (u == -1) return path;
|
||
if (entriesIdx.Contains(u)) {
|
||
answer = u;
|
||
break;
|
||
}
|
||
|
||
visited[u] = true;
|
||
var neighbors = GetNeighbors(Idx2Vec2(u));
|
||
var alt = dist[u] + 1;
|
||
foreach (var neighbor in neighbors) {
|
||
var idx = Vec2Idx(neighbor);
|
||
if (visited[idx]) continue;
|
||
if (alt < dist[idx]) {
|
||
dist[idx] = alt;
|
||
prev[idx] = u;
|
||
}
|
||
}
|
||
}
|
||
|
||
while (answer != Vec2Idx(start)) {
|
||
path.Add(Idx2Vec2(answer));
|
||
answer = prev[answer];
|
||
}
|
||
|
||
path.Reverse();
|
||
GD.Print(string.Join(',', path));
|
||
return path;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取指定类型的地图块位置
|
||
/// </summary>
|
||
/// <param name="nType">地图节点类型</param>
|
||
/// <param name="idx">索引</param>
|
||
/// <returns>地图块位置</returns>
|
||
public Vector2I GetTypedBlock(MapNodeType nType, uint idx) {
|
||
switch (nType) {
|
||
default:
|
||
return Vector2I.Zero;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取指定位置的地图节点类型
|
||
/// </summary>
|
||
/// <param name="pos">位置</param>
|
||
/// <returns>节点类型</returns>
|
||
public MapNodeType GetMapNodeTypeOfPosition(Vector2I pos) {
|
||
if (!IsValidPosition(pos)) return MapNodeType.Invalid;
|
||
return _blocks[pos.X, pos.Y];
|
||
}
|
||
|
||
/// <summary>
|
||
/// 世界坐标转网格坐标
|
||
/// </summary>
|
||
/// <param name="pos">世界坐标</param>
|
||
/// <returns>网格坐标</returns>
|
||
public Vector2I Point2Coord(Vector2 pos) {
|
||
return _tileMap.LocalToMap(_tileMap.ToLocal(pos));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取家具的特殊位置(如座位)
|
||
/// </summary>
|
||
/// <param name="fId">家具ID</param>
|
||
/// <param name="idx">索引</param>
|
||
/// <returns>特殊位置坐标</returns>
|
||
public Vector2I GetFurSpecialPosition(Guid fId, int idx) {
|
||
if (!Furniture.ContainsKey(fId)) return Vector2I.Zero;
|
||
if (idx < 0 || idx > Furniture[fId].SpecialTiles.Length) return Vector2I.Zero;
|
||
return Furniture[fId].SpecialTiles[idx].Position + Furniture[fId].TilePosition;
|
||
}
|
||
} |