using System;
using System.Collections.Generic;
using System.Linq;
using Ashfall.Core;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace Ashfall.Runtime
{
/// <summary>Per-floor physics mirrors. No visual visibility toggle disables simulation.</summary>
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<string, Entry> entries = new Dictionary<string, Entry>();
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<string>(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<BoxCollider2D>(); 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(); }
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using Ashfall.Core;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace Ashfall.Runtime
{
/// <summary>Per-floor physics mirrors. No visual visibility toggle disables simulation.</summary>
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<string, Entry> entries = new Dictionary<string, Entry>();
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<string>(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<BoxCollider2D>(); 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(); }
}
}