using System; using System.Collections.Generic; using System.Linq; using Ashfall.Core; using UnityEngine; using UnityEngine.SceneManagement; namespace Ashfall.Runtime { /// Per-floor physics mirrors. No visual visibility toggle disables simulation. public sealed class FloorPhysics : IDisposable { private sealed class Entry { public Scene scene; public PhysicsScene2D physics; public GameObject root; public int revision = -1; } private readonly Dictionary entries = new Dictionary(); private readonly string scenePrefix = "Physics." + Guid.NewGuid().ToString("N") + "."; private int nextScene; private float accumulator; public void Synchronize(WorldState world, int revision) { var active = new HashSet(world.players.Where(p => p.connected).Select(p => p.location.spaceId + ":" + p.location.floor)); active.Add("world:0"); foreach (var space in world.spaces) foreach (var floor in space.floors) { string key = space.id + ":" + floor.index; if (!active.Contains(key)) continue; if (!entries.TryGetValue(key, out var entry)) { var scene = SceneManager.CreateScene(scenePrefix + nextScene++ + "." + key, new CreateSceneParameters(LocalPhysicsMode.Physics2D)); entry = new Entry { scene = scene, physics = scene.GetPhysicsScene2D() }; entries.Add(key, entry); } if (entry.revision == revision) continue; if (entry.root != null) UnityEngine.Object.Destroy(entry.root); entry.root = new GameObject(key + ".collisions"); SceneManager.MoveGameObjectToScene(entry.root, entry.scene); entry.revision = revision; foreach (var wall in floor.walls.Where(w => !w.open)) AddBox(entry.root, wall.id, wall.bounds); foreach (var piece in world.pieces.Where(b => b.kind == PieceKind.Wall && b.location.spaceId == space.id && b.location.floor == floor.index)) AddBox(entry.root, piece.id, new Box2(piece.location.position, piece.rotation % 2 == 0 ? new V2(1, .18f) : new V2(.18f, 1))); if (space.kind != SpaceKind.Exterior) { var b = floor.bounds; float x = b.size.x / 2, y = b.size.y / 2; AddBox(entry.root, "north", new Box2(new V2(0, y + .25f), new V2(x * 2, .5f))); AddBox(entry.root, "south", new Box2(new V2(0, -y - .25f), new V2(x * 2, .5f))); AddBox(entry.root, "east", new Box2(new V2(x + .25f, 0), new V2(.5f, y * 2))); AddBox(entry.root, "west", new Box2(new V2(-x - .25f, 0), new V2(.5f, y * 2))); } } foreach (var key in entries.Keys.Where(k => !active.Contains(k)).ToArray()) { SceneManager.UnloadSceneAsync(entries[key].scene); entries.Remove(key); } } private static void AddBox(GameObject root, string name, Box2 box) { var go = new GameObject(name); go.transform.SetParent(root.transform); go.transform.localPosition = new Vector3(box.center.x, box.center.y, 0); var collider = go.AddComponent(); collider.size = new Vector2(box.size.x, box.size.y); } public void Simulate(float dt) { accumulator += Mathf.Min(dt, .1f); while (accumulator >= GameSimulation.TickSeconds) { accumulator -= GameSimulation.TickSeconds; foreach (var entry in entries.Values) entry.physics.Simulate(GameSimulation.TickSeconds); } } public bool Blocked(Location l, float radius) { return entries.TryGetValue(l.spaceId + ":" + l.floor, out var entry) && entry.physics.OverlapCircle(new Vector2(l.position.x, l.position.y), radius) != null; } public void Dispose() { foreach (var entry in entries.Values) if (entry.scene.isLoaded) SceneManager.UnloadSceneAsync(entry.scene); entries.Clear(); } } }