XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

AshfallFrontier

【Unity】灰烬边境,一款生存策略游戏

公开
关注 0 Fork 0 Star 0
UTF-8
using System;
using System.IO;
using System.Linq;
using Ashfall.Core;
using UnityEngine;

namespace Ashfall.Runtime
{
    // Explicit opt-in test runner. It never uses the normal world save.
    public sealed class SmokeHarness : MonoBehaviour
    {
        public static bool Enabled => Environment.GetCommandLineArgs().Contains("--smoke");
        private GameSession session;
        private float start, scenarioStart = -1;
        private bool completed, captured;
        private V2 initialVehicle;
        private int errors;
        private static string Output
        {
            get { var a = Environment.GetCommandLineArgs(); int i = Array.IndexOf(a, "--smoke-output"); return i >= 0 && i + 1 < a.Length ? a[i + 1] : Application.persistentDataPath; }
        }
        private void Start()
        {
            session = GetComponent<GameSession>(); start = Time.realtimeSinceStartup;
            Application.logMessageReceived += Log;
        }
        private void Log(string message, string stack, LogType kind)
        { if (kind == LogType.Error || kind == LogType.Exception || kind == LogType.Assert) errors++; }
        public static PlayerInput InputFor(GameSession s)
        {
            var result = new PlayerInput(); var p = s.LocalPlayer;
            if (p == null || s.World == null) return result;
            var v = s.World.Vehicle("crawler");
            result.aim = v.position + new V2(30, 10);
            if (p.stationId != "")
            {
                result.move = s.World.time < 18 ? new V2(0, 1) : new V2();
                result.brake = s.World.time >= 18; result.fire = result.secondary = true;
            }
            else if (p.location.spaceId == v.spaceId)
            {
                int index = s.World.players.IndexOf(p);
                V2 target = index == 3 && p.location.floor == 0 ? new V2(6, -2) : new V2(0, index == 2 ? -1 : 0);
                V2 diff = target - p.location.position; result.move = diff.Length > .12f ? diff.Normalized : new V2();
            }
            return result;
        }
        private void Update()
        {
            if (completed) return;
            if (Time.realtimeSinceStartup - start > 100) { Complete(false, "connection/scenario timeout"); return; }
            if (!session.Running || session.LocalPlayer == null) return;
            if (!session.IsAuthority)
            {
                if (session.World.time >= 29) Complete(session.World.players.Count(p => p.connected) == 4 && session.LocalPlayer.acknowledgedInput > 0 && errors == 0, "client received world and acknowledged inputs");
                return;
            }
            var w = session.World; var sim = session.Simulation; var v = w.Vehicle("crawler");
            if (scenarioStart < 0 && w.players.Count(p => p.connected) == 4)
            {
                scenarioStart = Time.realtimeSinceStartup; initialVehicle = v.position;
                // Controlled fixtures isolate network and rendering behavior from traversal already covered by unit tests.
                for (int i = 0; i < 4; i++)
                {
                    var p = w.players[i]; p.location = WorldFactory.At(v.spaceId, i < 2 ? 1 : 0, new V2(i < 2 ? 0 : -1, 0));
                    if (i < 2)
                    {
                        var station = v.stations[i]; p.location = station.location.Copy();
                        var r = sim.Execute(p.id, new GameCommand { sequence = p.lastCommand + 1, kind = CommandKind.Interact, targetId = station.id });
                        if (!r.success) { Complete(false, r.message); return; }
                    }
                }
                sim.CriticalChange?.Invoke();
            }
            if (scenarioStart < 0) return;
            if (w.time > 20 && !captured)
            {
                captured = true; Directory.CreateDirectory(Output);
                if (SystemInfo.graphicsDeviceType != UnityEngine.Rendering.GraphicsDeviceType.Null)
                    CaptureCamera(Path.Combine(Output, "prototype-exterior.png"));
            }
            if (w.time < 32) return;
            bool success = w.players.Count(p => p.connected) == 4 && V2.Distance(initialVehicle, v.position) > 4
                && v.cargo.Count("ammo") < 600 && w.players[2].location.floor == 0 && w.players[3].location.floor == 1
                && w.players.Skip(1).All(p => p.IsWorm && p.acknowledgedInput > 0) && errors == 0;
            session.Save();
            var restored = new SaveStore(session.SavePath, new UnityWorldCodec()).Load();
            success &= restored.players.Count == 4 && restored.Vehicle("crawler").position.x == v.position.x
                && restored.players[3].location.floor == 1;
            Complete(success, $"players={w.players.Count}, distance={V2.Distance(initialVehicle, v.position):0.0}, ammo={v.cargo.Count("ammo")}, floors={string.Join(",", w.players.Select(p => p.location.floor))}, errors={errors}");
        }
        private void Complete(bool success, string reason)
        {
            completed = true; Directory.CreateDirectory(Output);
            string role = session != null && session.IsAuthority ? "host" : session?.PlayerId ?? "unknown";
            File.WriteAllText(Path.Combine(Output, "smoke-" + role + ".txt"), (success ? "PASS " : "FAIL ") + reason);
            Debug.Log("ASHFALL_SMOKE " + (success ? "PASS " : "FAIL ") + reason);
            exitCode = success ? 0 : 1;
            if (session != null && !session.IsAuthority && success) Invoke(nameof(BeginExit), 8);
            else BeginExit();
        }
        private int exitCode;
        private void BeginExit()
        {
            session?.Stop(); GetComponent<WorldView>()?.ReleasePhysics();
            // Let asynchronous scene unloads and transport shutdown drain before quitting.
            Invoke(nameof(Exit), .5f);
        }
        private void Exit() => Application.Quit(exitCode);
        private void CaptureCamera(string path)
        {
            var camera = GetComponent<WorldView>().Camera;
            var target = RenderTexture.GetTemporary(1280, 800, 24);
            var oldTarget = camera.targetTexture; var oldActive = RenderTexture.active;
            Texture2D pixels = null;
            try
            {
                camera.targetTexture = target; camera.Render(); RenderTexture.active = target;
                pixels = new Texture2D(1280, 800, TextureFormat.RGB24, false);
                pixels.ReadPixels(new Rect(0, 0, 1280, 800), 0, 0); pixels.Apply();
                File.WriteAllBytes(path, pixels.EncodeToPNG());
            }
            finally
            {
                camera.targetTexture = oldTarget; RenderTexture.active = oldActive;
                RenderTexture.ReleaseTemporary(target); if (pixels != null) Destroy(pixels);
            }
        }
        private void OnDestroy() => Application.logMessageReceived -= Log;
    }
}