using NUnit.Framework.Internal; using System; using System.Collections; using System.Collections.Generic; using System.Linq; using UnityEngine; public class ScriptVM : MonoBehaviour { private Script _currentScript; private ExecutionContext _mainContext; private Dictionary _activeSubroutines = new Dictionary(); private Dictionary _modelTempRegistry = new Dictionary(); private Dictionary _objectRegistry = new Dictionary(); private int selectionVariable; // Jump Table обработчиков команд private Dictionary> _opCodeHandlers; public bool IsRunning => _mainContext != null && !_mainContext.IsFinished; //SCRIPT LOGGING void OnGUI() { GUILayout.BeginArea(new Rect(10, 10, 400, 600)); if (_mainContext != null) { GUILayout.Label($"MAIN: PC={_mainContext.ProgramCounter:X4} CMD=0x{GetCurrentCommand(_mainContext)?.OpCode:X4}"); GUILayout.Label($"Status: {(IsRunning ? "Running" : "Stopped")}"); GUILayout.Label($"Paused: {(_mainContext.IsPaused ? "+" : "-")} RemSec: -{_mainContext.PauseTimeRemaining}"); // Текущие параметры команды var currentCmd = GetCurrentCommand(_mainContext); if (currentCmd != null) { GUILayout.Label($"Params: {string.Join(", ", currentCmd.Params)}"); } } // Подпрограммы GUILayout.Label($"Subroutines: {_activeSubroutines.Count}"); foreach (var kvp in _activeSubroutines) { var context = kvp.Value; GUILayout.Label($"SUB[{kvp.Key:X2}] {context.ContextName}: PC={context.ProgramCounter:X4} CMD=0x{GetCurrentCommand(context)?.OpCode:X4}"); GUILayout.Label($" Paused: {(context.IsPaused ? "+" : "-")} RemSec: -{context.PauseTimeRemaining}"); } GUILayout.EndArea(); } void Awake() { InitializeJumpTable(); } void Update() { if (_mainContext == null) return; if (_mainContext.IsPaused && _mainContext.PauseTimeRemaining > 0) { _mainContext.PauseTimeRemaining -= Time.deltaTime; if (_mainContext.PauseTimeRemaining <= 0) { _mainContext.PauseTimeRemaining = 0; _mainContext.IsPaused = false; _mainContext.ProgramCounter++; MarkCurrentCommandCompleted(_mainContext); // Помечаем текущую команду как выполненную } } // Выполняем основной контекст if (!_mainContext.IsWaiting && !_mainContext.IsPaused) { ExecuteStep(_mainContext); } // Обрабатываем паузу в подпрограммах foreach (var context in _activeSubroutines.Values) { if (context.IsPaused && context.PauseTimeRemaining > 0) { context.PauseTimeRemaining -= Time.deltaTime; if (context.PauseTimeRemaining <= 0) { context.PauseTimeRemaining = 0; context.IsPaused = false; context.ProgramCounter++; MarkCurrentCommandCompleted(context); } } } // Выполняем все активные подпрограммы foreach (var context in _activeSubroutines.Values) { if (!context.IsWaiting && !context.IsPaused) { ExecuteStep(context); } } // Удаляем завершенные подпрограммы _activeSubroutines = _activeSubroutines.Where(kvp => !kvp.Value.IsFinished).ToDictionary(kvp => kvp.Key, kvp => kvp.Value); } public void LoadAndRunScript(Script script) { _currentScript = script; _mainContext = new ExecutionContext(0, "MAIN", null); _activeSubroutines.Clear(); _objectRegistry.Clear(); Debug.Log($"Script loaded with {script.Commands.Count} commands"); } public void StartSubroutine(ushort subId) { if (_currentScript != null && _currentScript.SubroutineTable.TryGetValue(subId, out Subroutine sub)) { var newContext = new ExecutionContext(sub.Address, sub.Name, new ScriptCommand {OpCode = 999}); _activeSubroutines[subId] = newContext; Debug.Log($"Started independent subroutine {subId} at address {sub.Address:X4}"); } else { Debug.LogWarning($"Cannot start subroutine {subId}: script not loaded or subroutine not found"); } } private bool IsCommandCompleted(uint commandId) { if (commandId < _currentScript.Commands.Count) { return _currentScript.Commands[(int)commandId].executedStatus == 3; } return false; } // WaitTo - stops thread and check command status at address private void Handle_WaitTo(ExecutionContext ctx, ScriptCommand cmd) { uint targetCommandId = (uint)GetParam(cmd, 0); if (IsCommandCompleted(targetCommandId)) { ctx.ProgramCounter++; cmd.executedStatus = 3; } } private void ExecuteStep(ExecutionContext context) { if (context.ProgramCounter >= _currentScript.Commands.Count) { context.IsFinished = true; if (context == _mainContext) Debug.Log("Main script finished"); return; } ScriptCommand cmd = _currentScript.Commands[(int)context.ProgramCounter]; cmd.executedStatus = 0; //cleaning status for command restart and proper wait if (_opCodeHandlers.TryGetValue(cmd.OpCode, out var handler)) { try { handler(context, cmd); } catch (Exception ex) { Debug.LogError($"Error executing command 0x{cmd.OpCode:X8}: {ex.Message}"); // При ошибке все равно помечаем как выполненную, чтобы не зависнуть cmd.executedStatus = 3; context.ProgramCounter++; } } else { Debug.LogWarning($"Unknown OpCode: 0x{cmd.OpCode:X8} at PC={context.ProgramCounter:X4}"); // Неизвестные команды просто пропускаем cmd.executedStatus = 3; context.ProgramCounter++; } } private void InitializeJumpTable() { _opCodeHandlers = new Dictionary> { // Управление потоком { 0x00000007, Handle_JumpTo }, { 0x00000001, Handle_If1Go }, { 0x00000002, Handle_If0Go }, { 0x00000004, Handle_IfNotEqualGo }, { 0x0000000D, Handle_GoSub }, { 0x0000000E, Handle_Return }, { 0x00000012, Handle_WaitTo }, { 0x0000001A, Handle_GetInput }, { 0x0000002C, Handle_SetCamXY }, // Set Camera //Characters commands { 0x00000030, Handle_CharLoad }, // CharLoad { 0x00000031, Handle_CharInit }, // CharInit { 0x000000D0, Handle_CharInit }, // Another CharInit??? { 0x00000034, Handle_CharMoveTo }, // CanWait { 0x00000037, Handle_CharAnimate }, // CanWait { 0x00000038, Handle_CharAnimate2 }, // CanWait { 0x00000041, Handle_PlayerInit }, // Create player char at camera set { 0x000000D2, Handle_CharGlowShow }, // CharInitWithGlow { 0x0000003E, Handle_WaitCharAnimEnd }, // Wait by char id { 0x00000046, Handle_CharSetSubroutine }, // Set blockcharacter subroutine //Pause { 0x0000005C, Handle_PausInit }, // PausInit { 0x0000005D, Handle_PauseFr }, // PauseFr_ // Работа с переменными { 0x00000008, Handle_SetTemp }, { 0x00000009, Handle_CopyVar }, { 0x00000060, Handle_GetVar }, { 0x0000005E, Handle_VarTrue }, { 0x0000005F, Handle_VarFlse }, { 0x00000073, Handle_VarDec }, { 0x00000074, Handle_VarSet }, // Управление { 0x00000043, Handle_CtrlLock }, { 0x00000044, Handle_CtrUnlck }, // Загрузка модулей { 0x00000051, Handle_DungLoad }, { 0x00000052, Handle_EventLoad }, { 0x00000053, Handle_CityLoad }, { 0x000000ED, Handle_BattleLoad }, // Видео { 0x00000059, Handle_VideoPly }, // Эффекты { 0x00000092, Handle_ScrnFade }, // Текст { 0x00000024, Handle_WindOpen }, { 0x00000013, Handle_TextShow }, { 0x00000025, Handle_WindClose } }; } // ========== ОБРАБОТЧИКИ КОМАНД ========== private void Handle_JumpTo(ExecutionContext ctx, ScriptCommand cmd) { uint jumpAddress = (uint)GetParam(cmd, 0); ctx.ProgramCounter = jumpAddress; cmd.executedStatus = 3; // Немедленное выполнение } private void Handle_If1Go(ExecutionContext ctx, ScriptCommand cmd) { int varAddress = GetParam(cmd, 0); uint jumpAddress = (uint)GetParam(cmd, 1); if (GetLocalVariable(ctx, varAddress) == 1) ctx.ProgramCounter = jumpAddress; else ctx.ProgramCounter++; cmd.executedStatus = 3; } private void Handle_If0Go(ExecutionContext ctx, ScriptCommand cmd) { int varAddress = GetParam(cmd, 0); uint jumpAddress = (uint)GetParam(cmd, 1); if (GetLocalVariable(ctx, varAddress) == 0) ctx.ProgramCounter = jumpAddress; else ctx.ProgramCounter++; cmd.executedStatus = 3; } private void Handle_IfNotEqualGo(ExecutionContext ctx, ScriptCommand cmd) { int varAddress = GetParam(cmd, 0); int compareValue = GetParam(cmd, 1); uint jumpAddress = (uint)GetParam(cmd, 2); if (GetLocalVariable(ctx, varAddress) != compareValue) ctx.ProgramCounter = jumpAddress; else ctx.ProgramCounter++; cmd.executedStatus = 3; } private void Handle_GoSub(ExecutionContext ctx, ScriptCommand cmd) { ushort subId = (ushort)GetParam(cmd, 0); if (_currentScript.SubroutineTable.TryGetValue(subId, out Subroutine sub)) { var newContext = new ExecutionContext(sub.Address, sub.Name, cmd); _activeSubroutines[subId] = newContext; } ctx.ProgramCounter++; } private void Handle_Return(ExecutionContext ctx, ScriptCommand cmd) { if (ctx.ExecutedByCmd != null) //main thread has ExecutedByCmd = null { // Для подпрограмм, вызванных из основного потока ctx.ExecutedByCmd.executedStatus = 3; ctx.IsFinished = true; } } private void Handle_GetInput(ExecutionContext ctx, ScriptCommand cmd) { int varAddress = GetParam(cmd, 0); SetLocalVariable(ctx, varAddress, selectionVariable); ctx.ProgramCounter++; cmd.executedStatus = 3; } // Обработчики команд паузы: private void Handle_PausInit(ExecutionContext ctx, ScriptCommand cmd) { // Просто сбрасываем счётчик паузы в контексте ctx.PauseTimeRemaining = 0; ctx.ProgramCounter++; cmd.executedStatus = 3; } private void Handle_PauseFr(ExecutionContext ctx, ScriptCommand cmd) { int framesToPause = GetParam(cmd, 0); ctx.PauseTimeRemaining = ctx.PauseTimeRemaining = framesToPause * (1f / App.Ps1FPS); //Ps1 FPS Compability ctx.IsPaused = true; // Ставим контекст на паузу } private void Handle_SetTemp(ExecutionContext ctx, ScriptCommand cmd) { int varAddress = GetParam(cmd, 0); int value = GetParam(cmd, 1); SetLocalVariable(ctx, varAddress, value); ctx.ProgramCounter++; cmd.executedStatus = 3; } private void Handle_CopyVar(ExecutionContext ctx, ScriptCommand cmd) { int srcAddress = GetParam(cmd, 0); int dstAddress = GetParam(cmd, 1); int value = GetLocalVariable(ctx, srcAddress); SetLocalVariable(ctx, dstAddress, value); ctx.ProgramCounter++; cmd.executedStatus = 3; } private void Handle_GetVar(ExecutionContext ctx, ScriptCommand cmd) { int localVarAddress = GetParam(cmd, 0); int globalVarAddress = GetParam(cmd, 1); int value = App.GameState.GetGlobalVariable(globalVarAddress); SetLocalVariable(ctx, localVarAddress, value); ctx.ProgramCounter++; cmd.executedStatus = 3; } private void Handle_VarTrue(ExecutionContext ctx, ScriptCommand cmd) { int globalVarAddress = GetParam(cmd, 0); App.GameState.SetGlobalVariable(globalVarAddress, 1); ctx.ProgramCounter++; cmd.executedStatus = 3; } private void Handle_VarFlse(ExecutionContext ctx, ScriptCommand cmd) { int globalVarAddress = GetParam(cmd, 0); App.GameState.SetGlobalVariable(globalVarAddress, 0); ctx.ProgramCounter++; cmd.executedStatus = 3; } private void Handle_VarSet(ExecutionContext ctx, ScriptCommand cmd) { int globalVarAddress = GetParam(cmd, 0); int value = GetParam(cmd, 1); App.GameState.SetGlobalVariable(globalVarAddress, value); ctx.ProgramCounter++; cmd.executedStatus = 3; } private void Handle_VarDec(ExecutionContext ctx, ScriptCommand cmd) { int globalVarAddress = GetParam(cmd, 0); int currentValue = App.GameState.GetGlobalVariable(globalVarAddress); App.GameState.SetGlobalVariable(globalVarAddress, currentValue - 1); ctx.ProgramCounter++; cmd.executedStatus = 3; } private void Handle_CtrlLock(ExecutionContext ctx, ScriptCommand cmd) { Debug.Log("Controls Locked"); App.GameState.IsPlayerControlLocked = true; ctx.ProgramCounter++; cmd.executedStatus = 3; } private void Handle_CtrUnlck(ExecutionContext ctx, ScriptCommand cmd) { Debug.Log("Controls Unlocked"); App.GameState.IsPlayerControlLocked = false; if (_objectRegistry.TryGetValue(1, out GameObject playerObj)) { ControlsController.Instance?.SetControlledObject(playerObj); } ctx.ProgramCounter++; cmd.executedStatus = 3; } private void Handle_SetCamXY(ExecutionContext ctx, ScriptCommand cmd) { uint rot = (uint)GetParam(cmd, 0); uint x = (uint)GetParam(cmd, 1); uint y = (uint)GetParam(cmd, 2); uint z = (uint)GetParam(cmd, 3); App.CameraController.SetCamTargetXY(rot, x, y, z); Debug.Log($"Camera position set {x}.{y}.{z}. Rot - {rot}"); ctx.ProgramCounter++; cmd.executedStatus = 3; } private void Handle_TextShow(ExecutionContext ctx, ScriptCommand cmd) { int textOffset = GetParam(cmd, 0); var textTokens = TextUtils.ParseText(_currentScript.TextData, textOffset); // Передаем команду в UI менеджер App.TextManager.ShowText(textTokens, cmd); ctx.ProgramCounter++; } private void Handle_WindOpen(ExecutionContext ctx, ScriptCommand cmd) { App.UI.ShowMessageWindow(); ctx.ProgramCounter++; cmd.executedStatus = 3; } private void Handle_WindClose(ExecutionContext ctx, ScriptCommand cmd) { App.UI.HideMessageWindow(ctx); ctx.ProgramCounter++; cmd.executedStatus = 3; } //CHARACTERS SECTION private void Handle_CharLoad(ExecutionContext ctx, ScriptCommand cmd) { //Old PS1 method for loading sprite sheet in VRAM //Now we simply creating disabled char model on scene int modelId = GetParam(cmd, 0); if (!_modelTempRegistry.ContainsKey(modelId)) { GameObject model = App.ResourceManager.CharLoad(modelId); _modelTempRegistry[modelId] = model; } ctx.ProgramCounter++; cmd.executedStatus = 3; } private void Handle_CharInit(ExecutionContext ctx, ScriptCommand cmd) { // Параметры: id, model_id, x, y, z, turn, dummy int objectId = GetParam(cmd, 0); int modelId = GetParam(cmd, 1); uint posX = (uint)GetParam(cmd, 2); uint posY = (uint)GetParam(cmd, 3); uint posZ = (uint)GetParam(cmd, 4); uint rotation = (uint)GetParam(cmd, 5); int dummy = GetParam(cmd, 6); // Неиспользуемый параметр if (!_modelTempRegistry.TryGetValue(modelId, out GameObject templateModel)) { Debug.LogWarning($"Model {modelId} not loaded, creating..."); templateModel = App.ResourceManager.CharLoad(modelId); _modelTempRegistry[modelId] = templateModel; } if (_objectRegistry.TryGetValue(objectId, out GameObject existingModel)) { Destroy(existingModel); _objectRegistry.Remove(objectId); } GameObject characterInstance = App.ResourceManager.CharInit(templateModel, objectId, posX, posY, posZ, rotation); _objectRegistry[objectId] = characterInstance; ctx.ProgramCounter++; cmd.executedStatus = 3; } // Activate player object at camera spot position and link controls private void Handle_PlayerInit(ExecutionContext ctx, ScriptCommand cmd) { int rotation = GetParam(cmd, 0); if (!_modelTempRegistry.TryGetValue(1, out GameObject templateModel)) { Debug.LogWarning($"Player Model not loaded, creating..."); templateModel = App.ResourceManager.CharLoad(1); _modelTempRegistry[1] = templateModel; } if (_objectRegistry.TryGetValue(1, out GameObject existingModel)) { Destroy(existingModel); _objectRegistry.Remove(1); } CameraController sceneCamera = App.CameraController; GameObject character = App.ResourceManager.CharInit(templateModel, 1, sceneCamera.sourceX, sceneCamera.sourceY, sceneCamera.sourceZ, (uint)rotation); _objectRegistry[1] = character; ctx.ProgramCounter++; cmd.executedStatus = 3; } private void Handle_CharGlowShow(ExecutionContext ctx, ScriptCommand cmd) { // Параметры: id, x, y, z, glow, dummy int charId = GetParam(cmd, 0); uint posX = (uint)GetParam(cmd, 1); uint posY = (uint)GetParam(cmd, 2); uint posZ = (uint)GetParam(cmd, 3); uint glow = (uint)GetParam(cmd, 4); int dummy = GetParam(cmd, 5); if (_objectRegistry.TryGetValue(charId, out GameObject characterObj)) { App.ResourceManager.CharInit(characterObj, charId, posX, posY, posZ, glow); //TODO: Async GLOW } ctx.ProgramCounter++; cmd.executedStatus = 3; } private void Handle_CharMoveTo(ExecutionContext ctx, ScriptCommand cmd) { int charId = GetParam(cmd, 0); uint destX = (uint)GetParam(cmd, 1); uint destZ = (uint)GetParam(cmd, 2); uint endAction = (uint)GetParam(cmd, 3); uint speed = (uint)GetParam(cmd, 4); if (_objectRegistry.TryGetValue(charId, out GameObject characterObj) && characterObj is GameObject character) { var controller = character.GetComponent(); if (controller != null) { //async move controller.MoveToGameCoordinates(destX, destZ, endAction, speed); } } ctx.ProgramCounter++; cmd.executedStatus = 3; } private void Handle_CharAnimate(ExecutionContext ctx, ScriptCommand cmd) { int charId = GetParam(cmd, 0); uint animationId = (uint)GetParam(cmd, 1); uint repeatNum = (uint)GetParam(cmd, 2); if (_objectRegistry.TryGetValue(charId, out GameObject characterObj) && characterObj is GameObject character) { var controller = character.GetComponent(); if (controller != null) { controller.PlayAnimation((int)animationId, (int)Mathf.Max(1, repeatNum)); } } ctx.ProgramCounter++; cmd.executedStatus = 3; } private void Handle_CharAnimate2(ExecutionContext ctx, ScriptCommand cmd) { Handle_CharAnimate(ctx, cmd); } private void Handle_WaitCharAnimEnd(ExecutionContext ctx, ScriptCommand cmd) { int charId = GetParam(cmd, 0); if (_objectRegistry.TryGetValue(charId, out GameObject characterObj) && characterObj is GameObject character) { var controller = character.GetComponent(); if (controller != null && !controller.IsMoving) { ctx.ProgramCounter++; cmd.executedStatus = 3; } } else { ctx.ProgramCounter++; cmd.executedStatus = 3; } } private void Handle_CharSetSubroutine(ExecutionContext ctx, ScriptCommand cmd) { int charId = GetParam(cmd, 0); int subId = GetParam(cmd, 1); _objectRegistry.TryGetValue(charId, out GameObject characterObj); characterObj.GetComponent().blockSubroutine = subId; Debug.Log($"Sub {subId} set to char {charId}"); ctx.ProgramCounter++; cmd.executedStatus = 3; } // SCENERY LOADERS private void Handle_CityLoad(ExecutionContext ctx, ScriptCommand cmd) { int districtId = GetParam(cmd, 0); int y = GetParam(cmd, 1); int x = GetParam(cmd, 2); App.SceneLoader.LoadCity(districtId, y, x); _mainContext = null; // Завершаем текущий скрипт } private void Handle_BattleLoad(ExecutionContext ctx, ScriptCommand cmd) { int battleId = GetParam(cmd, 0); App.SceneLoader.LoadBattle(battleId); _mainContext = null; // Завершаем текущий скрипт } private void Handle_DungLoad(ExecutionContext ctx, ScriptCommand cmd) { int dungeonId = GetParam(cmd, 0); App.SceneLoader.LoadDungeon(dungeonId); _mainContext = null; // Завершаем текущий скрипт } private void Handle_EventLoad(ExecutionContext ctx, ScriptCommand cmd) { int hzId = GetParam(cmd, 0); int eventId = GetParam(cmd, 1); App.SceneLoader.LoadEvent(eventId, hzId); _mainContext = null; // Завершаем текущий скрипт } private void Handle_VideoPly(ExecutionContext ctx, ScriptCommand cmd) { int videoId = GetParam(cmd, 0); App.VideoPlayer.PlayVideo(videoId, cmd); PauseContext(cmd); } private void Handle_ScrnFade(ExecutionContext ctx, ScriptCommand cmd) { int fadeType = GetParam(cmd, 0); int duration = GetParam(cmd, 1); //App.UI.ScreenFade(fadeType, duration, () => { // ctx.ProgramCounter++; //}); ctx.ProgramCounter++; } public void PauseContext(ScriptCommand cmd) { // Находим контекст, который выполняет эту команду if (_mainContext != null && GetCurrentCommand(_mainContext) == cmd) { _mainContext.IsPaused = true; return; } foreach (var context in _activeSubroutines.Values) { if (GetCurrentCommand(context) == cmd) { context.IsPaused = true; return; } } } public void ResumeContext(ScriptCommand cmd) { // Находим контекст и снимаем паузу if (_mainContext != null && GetCurrentCommand(_mainContext) == cmd) { _mainContext.IsPaused = false; _mainContext.ProgramCounter++; // Переходим к следующей команде return; } foreach (var context in _activeSubroutines.Values) { if (GetCurrentCommand(context) == cmd) { context.IsPaused = false; context.ProgramCounter++; // Переходим к следующей команде return; } } } // ========== ВСПОМОГАТЕЛЬНЫЕ МЕТОДЫ ========== private ScriptCommand GetCurrentCommand(ExecutionContext ctx) { if (ctx.ProgramCounter < _currentScript.Commands.Count) return _currentScript.Commands[(int)ctx.ProgramCounter]; return null; } private int GetParam(ScriptCommand cmd, int index) { return index < cmd.Params.Length ? cmd.Params[index] : 0; } private int GetLocalVariable(ExecutionContext ctx, int address) { return ctx.LocalVariables.TryGetValue(address, out int value) ? value : 0; } private void SetLocalVariable(ExecutionContext ctx, int address, int value) { ctx.LocalVariables[address] = value; } public void SetSelectionVariable(int value) //external after selection variable set { selectionVariable = value; } public void MarkCommandCompleted(ScriptCommand cmd) { cmd.executedStatus = 3; ResumeContext(cmd); } private void MarkCurrentCommandCompleted(ExecutionContext ctx) { if (ctx.ProgramCounter > 0 && ctx.ProgramCounter - 1 < _currentScript.Commands.Count) { ScriptCommand cmd = _currentScript.Commands[(int)(ctx.ProgramCounter - 1)]; cmd.executedStatus = 3; } } } public class ExecutionContext { public ScriptCommand ExecutedByCmd { get; set; } public string ContextName { get; set; } public uint ProgramCounter { get; set; } public Dictionary LocalVariables { get; set; } public bool IsWaiting { get; set; } public bool IsPaused { get; set; } public bool IsFinished { get; set; } public float PauseTimeRemaining { get; set; } public ExecutionContext(uint startAddress, string name, ScriptCommand cmd) { ContextName = name; ExecutedByCmd = cmd; ProgramCounter = startAddress; LocalVariables = new Dictionary(); PauseTimeRemaining = 0; } }