using System; using System.Collections.Generic; using System.Linq; namespace Ashfall.Core { public sealed partial class GameSimulation { private ActionResult Build(PlayerState p, GameCommand cmd) { if (p.location.spaceId != "base" || p.stationId != "") return ActionResult.Fail("原型建造区域位于前哨基地内部"); if (!Enum.IsDefined(typeof(PieceKind), cmd.piece) || cmd.floor < 0 || cmd.floor > 3 || cmd.floor > p.location.floor + 1) return ActionResult.Fail("最多四层;只能建造当前层或上一层"); V2 at = new V2((float)Math.Round(cmd.position.x), (float)Math.Round(cmd.position.y)); if (V2.Distance(p.location.position, at) > 10 || !World.Space("base").floors[0].bounds.Contains(at, .8f)) return ActionResult.Fail("超出建造范围"); var l = WorldFactory.At("base", cmd.floor, at); if (World.pieces.Any(b => b.location.SameFloor(l) && V2.Distance(b.location.position, at) < .1f && b.kind == cmd.piece)) return ActionResult.Fail("这里已有同类构件"); if (cmd.piece != PieceKind.Floor && !World.pieces.Any(b => b.kind == PieceKind.Floor && b.location.SameFloor(l) && V2.Distance(b.location.position, at) < .1f)) return ActionResult.Fail("需要先铺设楼板"); if (cmd.piece == PieceKind.Floor && cmd.floor > 0 && !World.pieces.Any(b => b.kind == PieceKind.Floor && b.location.floor == cmd.floor - 1 && b.location.spaceId == "base" && V2.Distance(b.location.position, at) < .1f)) return ActionResult.Fail("缺少下层结构支持"); if (cmd.piece != PieceKind.Floor) { if (World.facilities.Any(f => f.location.SameFloor(l) && V2.Distance(f.location.position, at) < 1.3f) || World.portals.Any(portal => portal.from.SameFloor(l) && V2.Distance(portal.from.position, at) < 1.4f) || World.players.Any(other => other.connected && other.location.SameFloor(l) && V2.Distance(other.location.position, at) < .75f)) return ActionResult.Fail("入口、设备或人物占用了此处"); } int cost = cmd.piece == PieceKind.Floor ? 2 : cmd.piece == PieceKind.Stairs ? 24 : cmd.piece == PieceKind.Wall || cmd.piece == PieceKind.Door ? 4 : 20; string material = p.species == Species.Human ? "metal" : "biomass"; if (!World.warehouse.Has(material, cost)) return ActionResult.Fail("共享仓库材料不足,需要 " + cost); if (cmd.piece == PieceKind.Stairs) { if (cmd.floor == 3) return ActionResult.Fail("已达到四层上限"); V2 landing = at + new V2(0, 4); if (!World.Space("base").floors[0].bounds.Contains(landing, 1.6f)) return ActionResult.Fail("楼梯需要 2×4 米及安全平台"); var landingLoc = WorldFactory.At("base", cmd.floor + 1, landing); if (World.pieces.Any(b => b.kind != PieceKind.Floor && b.location.SameFloor(landingLoc) && V2.Distance(b.location.position, landing) < 2)) return ActionResult.Fail("上层平台受阻"); } var piece = new BuildPiece { id = World.NewId("build"), ownerId = "base", location = l, kind = cmd.piece, rotation = ((cmd.rotation % 4) + 4) % 4, family = p.species == Species.Human ? Family.Mechanical : Family.Biological }; EnsureBaseFloor(cmd.floor); World.pieces.Add(piece); if (cmd.piece == PieceKind.Wall && !AllRoutesOpen(l.floor)) { World.pieces.Remove(piece); return ActionResult.Fail("不能封死玩家或设施的唯一出口"); } World.warehouse.Take(material, cost); if (cmd.piece == PieceKind.Stairs) { EnsureBaseFloor(cmd.floor + 1); V2 landing = at + new V2(0, 4); for (int x = -1; x <= 1; x++) for (int y = -1; y <= 1; y++) { var tile = WorldFactory.At("base", cmd.floor + 1, landing + new V2(x, y)); if (!World.pieces.Any(b => b.kind == PieceKind.Floor && b.location.SameFloor(tile) && V2.Distance(b.location.position, tile.position) < .1f)) World.pieces.Add(new BuildPiece { id = World.NewId("landing"), ownerId = piece.id, kind = PieceKind.Floor, location = tile, family = piece.family }); } int start = World.portals.Count; WorldFactory.AddStairs(World, "base", cmd.floor, at, cmd.floor + 1, landing); // Stair ownership is distinct from vehicle ownership; keep a mapping in the ID. for (int i = start; i < World.portals.Count; i++) World.portals[i].id = piece.id + "." + i; } if (cmd.piece == PieceKind.Research || cmd.piece == PieceKind.Generator || cmd.piece == PieceKind.Storage) { FacilityKind kind = cmd.piece == PieceKind.Research ? FacilityKind.Research : cmd.piece == PieceKind.Generator ? FacilityKind.Generator : FacilityKind.Storage; WorldFactory.AddFacility(World, piece.id + ".facility", cmd.piece.ToString(), kind, "base", cmd.floor, at, true, piece.family); } return ActionResult.Ok("建造完成:" + cmd.piece); } private void EnsureBaseFloor(int floor) { if (World.Space("base").floors.Any(f => f.index == floor)) return; World.Space("base").floors.Add(new FloorState { index = floor, name = (floor + 1) + "F", playerBuilt = true, bounds = new Box2(new V2(), new V2(18, 18)) }); } private bool AllRoutesOpen(int floor) { var exits = World.portals.Where(p => p.from.spaceId == "base" && p.from.floor == floor).Select(p => p.from.position).ToArray(); if (exits.Length == 0) return false; var points = World.players.Where(p => p.connected && p.location.spaceId == "base" && p.location.floor == floor).Select(p => p.location.position) .Concat(World.facilities.Where(f => f.location.spaceId == "base" && f.location.floor == floor).Select(f => f.location.position + new V2(0, -1))); foreach (var point in points) if (!Reachable(point, exits, floor)) return false; return true; } private bool Reachable(V2 start, V2[] exits, int floor) { var pending = new Queue(); var seen = new HashSet(); pending.Enqueue(new V2((float)Math.Round(start.x), (float)Math.Round(start.y))); var dirs = new[] { new V2(1, 0), new V2(-1, 0), new V2(0, 1), new V2(0, -1) }; while (pending.Count > 0) { var at = pending.Dequeue(); string key = at.x + ":" + at.y; if (!seen.Add(key) || !CanStand(WorldFactory.At("base", floor, at), .18f)) continue; if (exits.Any(e => V2.Distance(e, at) <= 1.2f)) return true; foreach (var d in dirs) pending.Enqueue(at + d); } return false; } private ActionResult Dismantle(PlayerState p, string id) { var b = World.pieces.Find(x => x.id == id); if (b == null || p.location.spaceId != "base" || !p.location.SameFloor(b.location) || V2.Distance(p.location.position, b.location.position) > 8) return ActionResult.Fail("构件不在可拆除范围内"); if (b.id.StartsWith("tile-") || b.ownerId.StartsWith("build-")) return ActionResult.Fail("基础地基或楼梯平台不能单独拆除"); if (b.kind == PieceKind.Stairs) return ActionResult.Fail("楼梯承载上层通路;请先清空上层,本原型保留楼梯结构"); if (World.players.Any(x => x.connected && x.location.SameFloor(b.location) && V2.Distance(x.location.position, b.location.position) < 1.2f)) return ActionResult.Fail("人物占用了此处"); if (World.portals.Any(x => x.from.SameFloor(b.location) && V2.Distance(x.from.position, b.location.position) < 1.6f)) return ActionResult.Fail("构件用于连接通道"); if (World.facilities.Any(x => x.id == b.id + ".facility" && x.jobs.Count > 0)) return ActionResult.Fail("先完成生产队列"); if (b.kind == PieceKind.Floor && World.pieces.Any(x => x.id != id && x.location.spaceId == "base" && x.location.floor >= b.location.floor && V2.Distance(x.location.position, b.location.position) < .2f)) return ActionResult.Fail("构件仍在承载设施或上层楼板"); World.pieces.Remove(b); if (!AllRoutesOpen(b.location.floor)) { World.pieces.Add(b); return ActionResult.Fail("不能切断唯一通路"); } World.facilities.RemoveAll(x => x.id == id + ".facility"); return ActionResult.Ok("已拆除构件"); } } }