supervisor-simulator/scripts/Lab.cs

319 lines
11 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
}
}