using System; using System.Collections.Generic; using Godot; public partial class Robot : Node3D { private const float EnergyUsePerSecond = 0.3f; private const float HeatGainPerSecond = 2.5f; private const float ActiveHeatLossPerSecond = 22f; private const float IdleHeatLossPerSecond = 12f; private const float CooldownTarget = 35f; private const float MaintenanceLossPerSecond = 0.025f; public bool isExecuting = false; public ProgramNode programStartNode; public ProgramNode currentNode; public string currentProgram; public string currentMessage = ""; public float heat = 0f; public float maintenance = 100f; public bool isCoolingDown = false; public bool isBroken = false; public string robotType = "iron_robot"; public bool showOnMap = true; private RobotTypeStats TypeStats => GameData.robotStats.RobotTypes.TryGetValue(robotType, out RobotTypeStats stats) ? stats : GameData.robotStats.RobotTypes["stone_robot"]; public override void _Process(double delta) { if (GameData.isPaused) return; if (isExecuting) { UpdateExecution(delta); } else { UpdateIdle(delta); } Visible = Math.Round(Math.Abs(Position.Y / GameData.tileHeight), 0) == GameData.visibleLayer; } private void UpdateExecution(double delta) { if (!CanExecute(delta)) return; NodeResult result = currentNode.Execute(this, delta); ApplyNodeResult(result); } private void ApplyNodeResult(NodeResult result) { switch (result) { case NodeResult.SUCCESS: MoveToNextNode(currentNode.nextNode); break; case NodeResult.CONDITIONFALSE: MoveToNextNode(currentNode.NegativeNode); break; case NodeResult.FAILURE: isExecuting = false; currentMessage = "(FAILED)" + currentNode.lastExecutionMessage; break; case NodeResult.RUNNING: currentMessage = ""; break; } } private void MoveToNextNode(ProgramNode nextNode) { currentNode = nextNode; if (currentNode == null) { isExecuting = false; } } private void UpdateIdle(double delta) { CoolDown( delta, GameData.robotStats.GetCoolingRate(IdleHeatLossPerSecond) * TypeStats.CoolingMultiplier ); if (currentMessage.Length <= 0) { currentMessage = "No script executing"; } } public void SetupExecution(List nodes) { if (nodes.Count <= 0) return; isExecuting = true; programStartNode = nodes[0]; currentNode = nodes[0]; currentMessage = ""; } public void StopExecution(string message) { isExecuting = false; programStartNode = null; currentNode = null; currentMessage = message; } public float GetMovementSpeed() { return GameData.robotStats.GetMovementSpeed(GameData.robotSpeed) * TypeStats.SpeedMultiplier * GetWorkEfficiency(); } public float GetWorkEfficiency() { if (isBroken) return 0f; if (maintenance >= 50f) return 1f; float minimumEfficiency = GameData.robotStats.GetMinimumEfficiency() + TypeStats.MinimumEfficiencyBonus; minimumEfficiency = Math.Clamp(minimumEfficiency, 0f, 1f); return Math.Clamp( minimumEfficiency + maintenance / 100f, minimumEfficiency, 1f ); } public void Maintain() { maintenance = 100f; isBroken = false; currentMessage = ""; } public void Maintain(float amount) { maintenance = Math.Clamp(maintenance + amount, 0f, 100f); if (maintenance > 0f) { isBroken = false; } currentMessage = ""; } public RobotSaveData CreateSaveData() { return new RobotSaveData { Name = Name, CurrentProgram = currentProgram, CurrentMessage = currentMessage, RobotType = robotType, X = Position.X, Y = Position.Y, Z = Position.Z, Heat = heat, Maintenance = maintenance, IsCoolingDown = isCoolingDown, IsBroken = isBroken }; } public void LoadSaveData(RobotSaveData saveData) { Name = saveData.Name; currentProgram = saveData.CurrentProgram; currentMessage = saveData.CurrentMessage ?? ""; robotType = saveData.RobotType ?? "stone_robot"; Position = new Vector3(saveData.X, saveData.Y, saveData.Z); heat = saveData.Heat; maintenance = saveData.Maintenance; isCoolingDown = saveData.IsCoolingDown; isBroken = saveData.IsBroken; } private bool CanExecute(double delta) { if (GameData.survival.isDead) { currentMessage = "Survival failed"; return false; } if (isBroken) { currentMessage = "Maintenance required"; return false; } if (isCoolingDown) { CoolDown( delta, GameData.robotStats.GetCoolingRate(ActiveHeatLossPerSecond) * TypeStats.CoolingMultiplier ); currentMessage = $"Cooling down ({heat:0}%)"; return false; } if (!TryConsumeEnergy(delta)) { currentMessage = "Not enough energy"; return false; } ApplyWear(delta); if (heat >= 100f) { isCoolingDown = true; currentMessage = "Overheated"; return false; } if (maintenance <= 0f) { isBroken = true; currentMessage = "Maintenance required"; return false; } return true; } private bool TryConsumeEnergy(double delta) { float energyUse = GameData.robotStats.GetEnergyUse(EnergyUsePerSecond) * TypeStats.EnergyUseMultiplier * (float)delta; return GameData.survival.TryConsumeEnergy(energyUse); } private void ApplyWear(double delta) { heat = Math.Clamp( heat + GameData.robotStats.GetHeatGain(HeatGainPerSecond) * TypeStats.HeatGainMultiplier * (float)delta, 0f, 100f ); maintenance = Math.Clamp( maintenance - GameData.robotStats.GetMaintenanceLoss(MaintenanceLossPerSecond) * TypeStats.MaintenanceLossMultiplier * (float)delta, 0f, 100f ); } private void CoolDown(double delta, float heatLossPerSecond) { heat = Math.Clamp( heat - heatLossPerSecond * (float)delta, 0f, 100f ); if (heat <= CooldownTarget) { isCoolingDown = false; } } }