Added survival mechanic that consumes inventory items if needed.
This commit is contained in:
+32
-1
@@ -18,6 +18,7 @@
|
||||
[ext_resource type="Script" uid="uid://drscsrkfphpy7" path="res://Scripts/UI/Research/ResearchList.cs" id="12_4q8tf"]
|
||||
[ext_resource type="Texture2D" uid="uid://dt84awx33mulb" path="res://Assets/Images/ResearchSymbol.png" id="13_alh3a"]
|
||||
[ext_resource type="Texture2D" uid="uid://bmcpkt6mae2qi" path="res://Assets/Images/AlarmSign.png" id="13_x3xnh"]
|
||||
[ext_resource type="Texture2D" path="res://Assets/Images/Resources/MushroomSymbol.png" id="14_food"]
|
||||
|
||||
[sub_resource type="CompressedTexture2D" id="CompressedTexture2D_u44n3"]
|
||||
|
||||
@@ -67,7 +68,7 @@ environment = SubResource("Environment_sb48q")
|
||||
[node name="CanvasLayer" type="CanvasLayer" parent="." unique_id=1558432386]
|
||||
follow_viewport_enabled = true
|
||||
|
||||
[node name="UIHandler" type="Control" parent="CanvasLayer" unique_id=1713248285 node_paths=PackedStringArray("codingWindow", "robotList", "mainCam", "map", "FPS", "RAM", "options", "uiContent", "menu", "inventory", "researchList", "robotAlarm")]
|
||||
[node name="UIHandler" type="Control" parent="CanvasLayer" unique_id=1713248285 node_paths=PackedStringArray("codingWindow", "robotList", "mainCam", "map", "FPS", "RAM", "options", "uiContent", "menu", "inventory", "researchList", "robotAlarm", "energyLabel", "waterLabel", "hungerLabel", "survivalStatus")]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
@@ -91,6 +92,10 @@ menu = NodePath("MainUI/Content/Menu")
|
||||
inventory = NodePath("MainUI/Content/Inventory")
|
||||
researchList = NodePath("MainUI/Content/Research")
|
||||
robotAlarm = NodePath("MainUI/FooterContainer/HBoxContainer/RobotAlarm")
|
||||
energyLabel = NodePath("MainUI/HeaderContainer/VBoxContainer/RowEnergy/RichTextLabel")
|
||||
waterLabel = NodePath("MainUI/HeaderContainer/VBoxContainer/RowWater/RichTextLabel")
|
||||
hungerLabel = NodePath("MainUI/HeaderContainer/VBoxContainer/RowHunger/RichTextLabel")
|
||||
survivalStatus = NodePath("MainUI/HeaderContainer/VBoxContainer/SurvivalStatus")
|
||||
|
||||
[node name="MainUI" type="VBoxContainer" parent="CanvasLayer/UIHandler" unique_id=1437975209]
|
||||
layout_mode = 1
|
||||
@@ -141,6 +146,32 @@ autowrap_mode = 0
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="RowHunger" type="HBoxContainer" parent="CanvasLayer/UIHandler/MainUI/HeaderContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="TextureRect" type="TextureRect" parent="CanvasLayer/UIHandler/MainUI/HeaderContainer/VBoxContainer/RowHunger"]
|
||||
layout_mode = 2
|
||||
texture = ExtResource("14_food")
|
||||
expand_mode = 2
|
||||
|
||||
[node name="RichTextLabel" type="RichTextLabel" parent="CanvasLayer/UIHandler/MainUI/HeaderContainer/VBoxContainer/RowHunger"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "Food: 100/100"
|
||||
fit_content = true
|
||||
autowrap_mode = 0
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="SurvivalStatus" type="RichTextLabel" parent="CanvasLayer/UIHandler/MainUI/HeaderContainer/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "Survival stable"
|
||||
fit_content = true
|
||||
autowrap_mode = 0
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="Content" type="Control" parent="CanvasLayer/UIHandler/MainUI" unique_id=45665557]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
@@ -20,6 +20,7 @@ public partial class GameData
|
||||
public static float tileHeight = 4;
|
||||
public static SortedDictionary<string, ItemData> availableItems = ResourceLoader.LoadItems();
|
||||
public static Dictionary<string, Research> availableResearch = ResourceLoader.LoadResearch();
|
||||
public static SurvivalState survival = new SurvivalState();
|
||||
|
||||
public static Color primaryColor = new Color("#276ac2");
|
||||
public static Color lightColor = new Color("#7efff5");
|
||||
|
||||
@@ -42,7 +42,9 @@ public class ExploreNode : ProgramNode
|
||||
Vector3 target = pathPoints[0] - startPosition;
|
||||
float distance = target.Length();
|
||||
|
||||
if (distance < 0.1f * Mathf.Sqrt(GameData.robotSpeed))
|
||||
float movementSpeed = robot.GetMovementSpeed();
|
||||
|
||||
if (distance < 0.1f * Mathf.Sqrt(movementSpeed))
|
||||
{
|
||||
robot.Position = pathPoints[0];
|
||||
Vector3I mapIndex = Pathfinding.GetClosestStartPoint(robot.Position);
|
||||
@@ -70,7 +72,7 @@ public class ExploreNode : ProgramNode
|
||||
{
|
||||
robot.LookAt(robot.GlobalPosition + lookDirection, Vector3.Up);
|
||||
}
|
||||
robot.GlobalPosition += direction * (float)delta * GameData.robotSpeed;
|
||||
robot.GlobalPosition += direction * (float)delta * movementSpeed;
|
||||
|
||||
return NodeResult.RUNNING;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,9 @@ public class MoveNode : ProgramNode
|
||||
Vector3 target = pathPoints[0] - startPosition;
|
||||
float distance = target.Length();
|
||||
|
||||
if (distance < 0.1f)
|
||||
float movementSpeed = robot.GetMovementSpeed();
|
||||
|
||||
if (distance < 0.1f * Mathf.Sqrt(movementSpeed))
|
||||
{
|
||||
robot.Position = pathPoints[0];
|
||||
Vector3I mapIndex = Pathfinding.GetClosestStartPoint(robot.Position);
|
||||
@@ -51,7 +53,7 @@ public class MoveNode : ProgramNode
|
||||
{
|
||||
robot.LookAt(robot.GlobalPosition + lookDirection, Vector3.Up);
|
||||
}
|
||||
robot.GlobalPosition += direction * (float)delta * GameData.robotSpeed;
|
||||
robot.GlobalPosition += direction * (float)delta * movementSpeed;
|
||||
|
||||
return NodeResult.RUNNING;
|
||||
}
|
||||
|
||||
@@ -45,12 +45,48 @@ public class Inventory
|
||||
|
||||
public void RemoveItem(string id, int amount)
|
||||
{
|
||||
Item item = items.Find(x => x.data.Id == id && x.currentAmount >= amount);
|
||||
if (item != null)
|
||||
{
|
||||
item.currentAmount -= amount;
|
||||
NotifyInventoryChanged();
|
||||
TryRemoveItem(id, amount);
|
||||
}
|
||||
|
||||
public bool TryRemoveItem(string id, int amount)
|
||||
{
|
||||
if (GetItemAmount(id) < amount)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int remainingAmount = amount;
|
||||
for (int i = items.Count - 1; i >= 0 && remainingAmount > 0; i--)
|
||||
{
|
||||
Item item = items[i];
|
||||
if (item.data.Id != id) continue;
|
||||
|
||||
int removedAmount = Math.Min(item.currentAmount, remainingAmount);
|
||||
item.currentAmount -= removedAmount;
|
||||
remainingAmount -= removedAmount;
|
||||
|
||||
if (item.currentAmount <= 0)
|
||||
{
|
||||
items.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
NotifyInventoryChanged();
|
||||
return true;
|
||||
}
|
||||
|
||||
public int GetItemAmount(string id)
|
||||
{
|
||||
int amount = 0;
|
||||
foreach (Item item in items)
|
||||
{
|
||||
if (item.data.Id == id)
|
||||
{
|
||||
amount += item.currentAmount;
|
||||
}
|
||||
}
|
||||
|
||||
return amount;
|
||||
}
|
||||
|
||||
private void NotifyInventoryChanged()
|
||||
|
||||
@@ -4,16 +4,29 @@ using Godot;
|
||||
|
||||
public partial class Robot : Node3D
|
||||
{
|
||||
private const float EnergyUsePerSecond = 0.2f;
|
||||
private const float HeatGainPerSecond = 7.5f;
|
||||
private const float ActiveHeatLossPerSecond = 18f;
|
||||
private const float IdleHeatLossPerSecond = 9f;
|
||||
private const float CooldownTarget = 35f;
|
||||
private const float MaintenanceLossPerSecond = 0.04f;
|
||||
|
||||
private List<ProgramNode> nodes = new List<ProgramNode>();
|
||||
private bool isExecuting = false;
|
||||
private 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 override void _Process(double delta)
|
||||
{
|
||||
if (isExecuting)
|
||||
{
|
||||
if (CanExecute(delta))
|
||||
{
|
||||
switch (currentNode.Execute(this, delta))
|
||||
{
|
||||
@@ -33,8 +46,10 @@ public partial class Robot : Node3D
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (currentMessage.Length <= 0)
|
||||
{
|
||||
CoolDown(delta, IdleHeatLossPerSecond);
|
||||
currentMessage = "No script executing";
|
||||
}
|
||||
|
||||
@@ -50,4 +65,80 @@ public partial class Robot : Node3D
|
||||
isExecuting = true;
|
||||
currentNode = nodes[0];
|
||||
}
|
||||
|
||||
public float GetMovementSpeed()
|
||||
{
|
||||
return GameData.robotSpeed * GetWorkEfficiency();
|
||||
}
|
||||
|
||||
public float GetWorkEfficiency()
|
||||
{
|
||||
if (isBroken) return 0f;
|
||||
if (maintenance >= 50f) return 1f;
|
||||
|
||||
return Math.Clamp(0.35f + maintenance / 100f, 0.35f, 1f);
|
||||
}
|
||||
|
||||
public void Maintain()
|
||||
{
|
||||
maintenance = 100f;
|
||||
isBroken = false;
|
||||
currentMessage = "";
|
||||
}
|
||||
|
||||
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, ActiveHeatLossPerSecond);
|
||||
currentMessage = $"Cooling down ({heat:0}%)";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!GameData.survival.TryConsumeEnergy(EnergyUsePerSecond * (float)delta))
|
||||
{
|
||||
currentMessage = "Not enough energy";
|
||||
return false;
|
||||
}
|
||||
|
||||
heat = Math.Clamp(heat + HeatGainPerSecond * (float)delta, 0f, 100f);
|
||||
maintenance = Math.Clamp(maintenance - MaintenanceLossPerSecond * (float)delta, 0f, 100f);
|
||||
|
||||
if (heat >= 100f)
|
||||
{
|
||||
isCoolingDown = true;
|
||||
currentMessage = "Overheated";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (maintenance <= 0f)
|
||||
{
|
||||
isBroken = true;
|
||||
currentMessage = "Maintenance required";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void CoolDown(double delta, float heatLossPerSecond)
|
||||
{
|
||||
heat = Math.Clamp(heat - heatLossPerSecond * (float)delta, 0f, 100f);
|
||||
if (heat <= CooldownTarget)
|
||||
{
|
||||
isCoolingDown = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
using System;
|
||||
|
||||
public class SurvivalState
|
||||
{
|
||||
private const float HungerDrainPerSecond = 0.035f;
|
||||
private const float ThirstDrainPerSecond = 0.055f;
|
||||
private const float PassiveEnergyDrainPerSecond = 0.025f;
|
||||
private const float AutoConsumeThreshold = 35f;
|
||||
|
||||
public float hunger = 100f;
|
||||
public float thirst = 100f;
|
||||
public float energy = 100f;
|
||||
|
||||
public float maxHunger = 100f;
|
||||
public float maxThirst = 100f;
|
||||
public float maxEnergy = 100f;
|
||||
|
||||
public bool isDead = false;
|
||||
public string deathReason = "";
|
||||
public string currentStatus = "";
|
||||
|
||||
public void Update(double delta)
|
||||
{
|
||||
if (isDead) return;
|
||||
|
||||
hunger = Math.Clamp(hunger - HungerDrainPerSecond * (float)delta, 0f, maxHunger);
|
||||
thirst = Math.Clamp(thirst - ThirstDrainPerSecond * (float)delta, 0f, maxThirst);
|
||||
energy = Math.Clamp(energy - PassiveEnergyDrainPerSecond * (float)delta, 0f, maxEnergy);
|
||||
|
||||
TryAutoConsumeFood();
|
||||
TryAutoConsumeWater();
|
||||
TryAutoConsumeEnergy();
|
||||
UpdateStatus();
|
||||
CheckDeath();
|
||||
}
|
||||
|
||||
public bool TryConsumeEnergy(float amount)
|
||||
{
|
||||
if (amount <= 0f) return true;
|
||||
if (energy < amount) return false;
|
||||
|
||||
energy -= amount;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void TryAutoConsumeFood()
|
||||
{
|
||||
if (hunger > AutoConsumeThreshold) return;
|
||||
if (!GameData.inventory.TryRemoveItem("mushroom", 1)) return;
|
||||
|
||||
hunger = Math.Clamp(hunger + 35f, 0f, maxHunger);
|
||||
}
|
||||
|
||||
private void TryAutoConsumeWater()
|
||||
{
|
||||
if (thirst > AutoConsumeThreshold) return;
|
||||
if (!GameData.inventory.TryRemoveItem("water", 1)) return;
|
||||
|
||||
thirst = Math.Clamp(thirst + 40f, 0f, maxThirst);
|
||||
}
|
||||
|
||||
private void TryAutoConsumeEnergy()
|
||||
{
|
||||
if (energy > AutoConsumeThreshold) return;
|
||||
|
||||
if (GameData.inventory.TryRemoveItem("battery_v2", 1))
|
||||
{
|
||||
energy = Math.Clamp(energy + 70f, 0f, maxEnergy);
|
||||
return;
|
||||
}
|
||||
|
||||
if (GameData.inventory.TryRemoveItem("battery_v1", 1))
|
||||
{
|
||||
energy = Math.Clamp(energy + 45f, 0f, maxEnergy);
|
||||
return;
|
||||
}
|
||||
|
||||
if (GameData.inventory.TryRemoveItem("steam", 1))
|
||||
{
|
||||
energy = Math.Clamp(energy + 25f, 0f, maxEnergy);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateStatus()
|
||||
{
|
||||
if (hunger <= AutoConsumeThreshold)
|
||||
{
|
||||
currentStatus = "Food supply critical";
|
||||
return;
|
||||
}
|
||||
|
||||
if (thirst <= AutoConsumeThreshold)
|
||||
{
|
||||
currentStatus = "Water supply critical";
|
||||
return;
|
||||
}
|
||||
|
||||
if (energy <= AutoConsumeThreshold)
|
||||
{
|
||||
currentStatus = "Energy reserves critical";
|
||||
return;
|
||||
}
|
||||
|
||||
currentStatus = "Survival stable";
|
||||
}
|
||||
|
||||
private void CheckDeath()
|
||||
{
|
||||
if (hunger <= 0f)
|
||||
{
|
||||
KillPlayer("Starved");
|
||||
return;
|
||||
}
|
||||
|
||||
if (thirst <= 0f)
|
||||
{
|
||||
KillPlayer("Died of thirst");
|
||||
return;
|
||||
}
|
||||
|
||||
if (energy <= 0f)
|
||||
{
|
||||
KillPlayer("Life support lost power");
|
||||
}
|
||||
}
|
||||
|
||||
private void KillPlayer(string reason)
|
||||
{
|
||||
isDead = true;
|
||||
deathReason = reason;
|
||||
currentStatus = "Survival failed: " + reason;
|
||||
GameData.canMove = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://fpqk4b1v48xy
|
||||
@@ -16,6 +16,10 @@ public partial class UIHandler : Control
|
||||
[Export] PanelContainer inventory;
|
||||
[Export] ResearchList researchList;
|
||||
[Export] TextureRect robotAlarm;
|
||||
[Export] RichTextLabel energyLabel;
|
||||
[Export] RichTextLabel waterLabel;
|
||||
[Export] RichTextLabel hungerLabel;
|
||||
[Export] RichTextLabel survivalStatus;
|
||||
|
||||
private bool receivedRobotJumpSignal = false;
|
||||
public override void _Ready()
|
||||
@@ -89,6 +93,7 @@ public partial class UIHandler : Control
|
||||
double memory = Process.GetCurrentProcess().WorkingSet64 / (1024 * 1024);
|
||||
string memoryDisplay = memory > 1024 ? Math.Round(memory / 1024, 2).ToString() + " GB" : memory.ToString() + " MB";
|
||||
RAM.Text = memoryDisplay;
|
||||
DisplaySurvivalStats();
|
||||
}
|
||||
|
||||
public void ExitGame()
|
||||
@@ -121,6 +126,11 @@ public partial class UIHandler : Control
|
||||
private void DisplayRobotAlarm()
|
||||
{
|
||||
string messages = "";
|
||||
if (GameData.survival.isDead)
|
||||
{
|
||||
messages += GameData.survival.currentStatus + "\r";
|
||||
}
|
||||
|
||||
foreach (Robot robot in GameData.robots)
|
||||
{
|
||||
if (robot.currentMessage.Length > 0)
|
||||
@@ -141,4 +151,12 @@ public partial class UIHandler : Control
|
||||
codingWindow.SetRobot(robot);
|
||||
OpenUIElement(codingWindow);
|
||||
}
|
||||
|
||||
private void DisplaySurvivalStats()
|
||||
{
|
||||
energyLabel.Text = $"Energy: {GameData.survival.energy:0}/{GameData.survival.maxEnergy:0}";
|
||||
waterLabel.Text = $"Water: {GameData.survival.thirst:0}/{GameData.survival.maxThirst:0}";
|
||||
hungerLabel.Text = $"Food: {GameData.survival.hunger:0}/{GameData.survival.maxHunger:0}";
|
||||
survivalStatus.Text = GameData.survival.currentStatus;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,9 +11,10 @@ public partial class RobotDisplay : PanelContainer
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
string programName = robot.currentProgram ?? "";
|
||||
if (programName != currentScript.Text)
|
||||
string status = $"{programName} | Heat {robot.heat:0}% | Maintenance {robot.maintenance:0}%";
|
||||
if (status != currentScript.Text)
|
||||
{
|
||||
currentScript.Text = programName;
|
||||
currentScript.Text = status;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ public partial class World : Node3D
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
ResetRunState();
|
||||
WFC.FillAdjacencies();
|
||||
|
||||
tileMeshes = ResourceLoader.LoadTiles();
|
||||
@@ -78,6 +79,7 @@ public partial class World : Node3D
|
||||
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
GameData.survival.Update(delta);
|
||||
if (!canMove) return;
|
||||
|
||||
if (Input.IsActionJustPressed("layer_up") && currentLayer > 0) currentLayer--;
|
||||
@@ -89,6 +91,18 @@ public partial class World : Node3D
|
||||
}
|
||||
}
|
||||
|
||||
private void ResetRunState()
|
||||
{
|
||||
survival = new SurvivalState();
|
||||
inventory = new Inventory();
|
||||
availableResearch = ResourceLoader.LoadResearch();
|
||||
robots.Clear();
|
||||
currentLayer = 0;
|
||||
visibleLayer = 0;
|
||||
lowestLayer = 0;
|
||||
canMove = true;
|
||||
}
|
||||
|
||||
private void GenerateWorld()
|
||||
{
|
||||
for (int layer = 0; layer < ruinSize; layer++)
|
||||
|
||||
Reference in New Issue
Block a user