Added final features for this release. Now only polishing (if needed) remains.

Features: Sacrifice-Node, Maintain-Node, Options for screen type, lightcolor and soundvolume, tied in sound effects, game pause when menu is open, visibly open up gate when opening it.
This commit is contained in:
2026-05-10 14:09:14 +02:00
parent 228e81ab4e
commit 8170b700b2
28 changed files with 797 additions and 14 deletions
+19
View File
@@ -62,6 +62,25 @@ public class FileHandler
return file.GetAsText();
}
public static bool DeleteProgram(string name)
{
CreateScriptDirectory();
string path = GetProgramPath(name);
if (!FileAccess.FileExists(path))
{
return false;
}
DirAccess dir = DirAccess.Open(ScriptDirectory);
if (dir == null)
{
return false;
}
return dir.Remove($"{name}{ScriptExtension}") == Error.Ok;
}
private static string GetProgramPath(string filename)
{
return $"{ScriptDirectory}/{filename}{ScriptExtension}";
+23
View File
@@ -25,9 +25,12 @@ public partial class GameData
public static Dictionary<int, List<Ingredient>> gateUnlocks;
public static bool loadSaveOnStart = false;
public static bool showTutorial = true;
public static bool isPaused = false;
public static Color primaryColor = new Color("#276ac2");
public static Color lightColor = new Color("#7efff5");
public static int screenMode = 2;
public static float soundVolume = 0.8f;
public static int ruinSize = 10;
public static int layerSize = 20;
@@ -44,12 +47,14 @@ public partial class GameData
robotStats = new RobotStats();
inventory = new Inventory();
availableResearch = ResourceLoader.LoadResearch();
map = null;
robots.Clear();
currentLayer = 0;
visibleLayer = 0;
lowestLayer = 0;
maxRobotCount = 10;
canMove = true;
isPaused = false;
}
public static void RebuildRobotStatsFromResearch()
@@ -65,4 +70,22 @@ public partial class GameData
}
}
}
public static bool HasSpawnableRobotInInventory()
{
foreach (Item item in inventory.items)
{
if (robotStats.RobotTypes.ContainsKey(item.data.Id) && item.currentAmount > 0)
{
return true;
}
}
return false;
}
public static bool HasNoRobotRecovery()
{
return robots.Count <= 0 && !HasSpawnableRobotInInventory();
}
}
+3 -1
View File
@@ -74,7 +74,9 @@ public partial class ResourceLoader
{ new ElseNode(), GD.Load<PackedScene>("res://Prefabs/DSL/ElseNode.tscn") },
{ new UntilNode(), GD.Load<PackedScene>("res://Prefabs/DSL/UntilNode.tscn") },
{ new ForNode(), GD.Load<PackedScene>("res://Prefabs/DSL/ForNode.tscn") },
{ new WhileNode(), GD.Load<PackedScene>("res://Prefabs/DSL/WhileNode.tscn") }
{ new WhileNode(), GD.Load<PackedScene>("res://Prefabs/DSL/WhileNode.tscn") },
{ new MaintainNode(), GD.Load<PackedScene>("res://Prefabs/DSL/MaintainNode.tscn") },
{ new SacrificeNode(), GD.Load<PackedScene>("res://Prefabs/DSL/SacrificeNode.tscn") }
};
return nodes;
}
+11
View File
@@ -8,6 +8,7 @@ public class SaveGameData
public int LowestLayer { get; set; }
public int MaxRobotCount { get; set; }
public bool CanMove { get; set; }
public SettingsSaveData Settings { get; set; }
public SurvivalSaveData Survival { get; set; }
public List<ItemSaveData> Inventory { get; set; }
public List<ResearchSaveData> Research { get; set; }
@@ -15,6 +16,16 @@ public class SaveGameData
public List<RobotSaveData> Robots { get; set; }
}
public class SettingsSaveData
{
public int ScreenMode { get; set; }
public float SoundVolume { get; set; }
public float LightColorR { get; set; }
public float LightColorG { get; set; }
public float LightColorB { get; set; }
public float LightColorA { get; set; }
}
public class SurvivalSaveData
{
public float Hunger { get; set; }
+54 -1
View File
@@ -61,6 +61,7 @@ public static class SaveGameManager
LowestLayer = GameData.lowestLayer,
MaxRobotCount = GameData.maxRobotCount,
CanMove = GameData.canMove,
Settings = CreateSettingsSaveData(),
Survival = CreateSurvivalSaveData(),
Inventory = CreateInventorySaveData(),
Research = CreateResearchSaveData(),
@@ -80,6 +81,7 @@ public static class SaveGameManager
GameData.maxRobotCount = saveGame.MaxRobotCount;
GameData.canMove = saveGame.CanMove;
ApplySettingsData(saveGame.Settings);
ApplySurvivalData(saveGame.Survival);
ApplyInventoryData(saveGame.Inventory);
ApplyResearchData(saveGame.Research);
@@ -100,6 +102,19 @@ public static class SaveGameManager
};
}
private static SettingsSaveData CreateSettingsSaveData()
{
return new SettingsSaveData
{
ScreenMode = GameData.screenMode,
SoundVolume = GameData.soundVolume,
LightColorR = GameData.lightColor.R,
LightColorG = GameData.lightColor.G,
LightColorB = GameData.lightColor.B,
LightColorA = GameData.lightColor.A
};
}
private static List<ItemSaveData> CreateInventorySaveData()
{
List<ItemSaveData> result = new List<ItemSaveData>();
@@ -201,6 +216,7 @@ public static class SaveGameManager
LowestLayer = saveGame.LowestLayer,
MaxRobotCount = saveGame.MaxRobotCount,
CanMove = saveGame.CanMove,
Settings = saveGame.Settings,
Survival = saveGame.Survival,
Inventory = saveGame.Inventory,
Research = new List<ResearchSaveData>(),
@@ -286,6 +302,43 @@ public static class SaveGameManager
GameData.survival.elapsedSeconds = survival.ElapsedSeconds;
}
private static void ApplySettingsData(SettingsSaveData settings)
{
if (settings == null) return;
GameData.screenMode = settings.ScreenMode;
GameData.soundVolume = settings.SoundVolume;
GameData.lightColor = new Color(
settings.LightColorR,
settings.LightColorG,
settings.LightColorB,
settings.LightColorA
);
ApplyScreenMode(settings.ScreenMode);
SoundManager.SetMasterVolume(settings.SoundVolume);
LightHandler.RedrawLights(GameData.lightColor);
}
private static void ApplyScreenMode(int screenMode)
{
switch (screenMode)
{
case 0:
DisplayServer.WindowSetMode(DisplayServer.WindowMode.Fullscreen);
DisplayServer.WindowSetFlag(DisplayServer.WindowFlags.Borderless, false);
break;
case 1:
DisplayServer.WindowSetMode(DisplayServer.WindowMode.Windowed);
DisplayServer.WindowSetFlag(DisplayServer.WindowFlags.Borderless, false);
break;
case 2:
DisplayServer.WindowSetMode(DisplayServer.WindowMode.Fullscreen);
DisplayServer.WindowSetFlag(DisplayServer.WindowFlags.Borderless, true);
break;
}
}
private static void ApplyInventoryData(List<ItemSaveData> savedItems)
{
GameData.inventory = new Inventory();
@@ -353,7 +406,7 @@ public static class SaveGameManager
tile.containsResource = savedTile.ContainsResource;
tile.wasVisited = savedTile.WasVisited;
tile.resource = savedTile.Resource == null ? null : GameResource.FromSaveData(savedTile.Resource);
tile.ContentNode.Visible = savedTile.WasVisited || tile.collapsedMesh == "gate";
tile.ContentNode.Visible = savedTile.WasVisited || (tile.collapsedMesh == "gate" && !layer.isGateOpen);
}
}
}
+44
View File
@@ -0,0 +1,44 @@
using Godot;
public partial class SoundManager : Node
{
private static SoundManager instance;
[Export] private AudioStreamPlayer buttonSound;
[Export] private AudioStreamPlayer miningSound;
public override void _Ready()
{
instance = this;
}
public override void _ExitTree()
{
if (instance == this)
{
instance = null;
}
}
public static void PlayButton()
{
if (instance == null || instance.buttonSound == null) return;
instance.buttonSound.Play();
}
public static void PlayMining()
{
if (instance == null || instance.miningSound == null) return;
instance.miningSound.Play();
}
public static void SetMasterVolume(float percent)
{
percent = Mathf.Clamp(percent, 0f, 1f);
int busIndex = AudioServer.GetBusIndex("Master");
AudioServer.SetBusVolumeDb(busIndex, Mathf.LinearToDb(percent));
AudioServer.SetBusMute(busIndex, percent <= 0f);
}
}
+1
View File
@@ -0,0 +1 @@
uid://gvy12mefkwax
+1
View File
@@ -25,6 +25,7 @@ public class HarvestNode : ProgramNode
if (tile.resource.Extract(delta))
{
SoundManager.PlayMining();
if (!GameData.inventory.AddItem(new Item {data = tile.resource.item}, 1))
{
lastExecutionMessage = "Not enough space";
+51
View File
@@ -0,0 +1,51 @@
using Godot;
public class MaintainNode : ProgramNode
{
private const float MaintenancePerGear = 10f;
public MaintainNode()
{
DisplayText = "Maintain";
}
public override NodeResult Execute(Robot robot, double delta)
{
string gearId = GetGearId(robot);
if (!GameData.inventory.TryRemoveItem(gearId, 1))
{
lastExecutionMessage = $"Missing {ItemData.GetReadableName(gearId)}";
return NodeResult.FAILURE;
}
robot.Maintain(MaintenancePerGear);
lastExecutionMessage = "";
return NodeResult.SUCCESS;
}
public override void ReadParameters(NodeDisplay display)
{
}
public override ProgramNode Duplicate()
{
MaintainNode duplicate = new MaintainNode();
return duplicate;
}
public override void Setup(NodeDisplay display)
{
}
public override string Save()
{
return $"Name: {DisplayText}";
}
public static string GetGearId(Robot robot)
{
string robotType = robot == null ? "stone_robot" : robot.robotType;
return robotType.Replace("_robot", "_gear");
}
}
+1
View File
@@ -0,0 +1 @@
uid://bmibtsh1iq2x
+52
View File
@@ -0,0 +1,52 @@
using Godot;
public class SacrificeNode : ProgramNode
{
public SacrificeNode()
{
DisplayText = "Sacrifice";
}
public override NodeResult Execute(Robot robot, double delta)
{
Vector3I mapIndex = Pathfinding.GetClosestStartPoint(robot.Position);
Tile tile = GameData.map[mapIndex.Y].tiles[mapIndex.X, mapIndex.Z];
if (!tile.containsResource || tile.resource == null)
{
lastExecutionMessage = "No resource on this tile";
return NodeResult.FAILURE;
}
if (tile.resource.IsEndless())
{
lastExecutionMessage = "Resource is already endless";
return NodeResult.FAILURE;
}
tile.resource.MakeEndless();
GameData.robots.Remove(robot);
robot.QueueFree();
lastExecutionMessage = "";
return NodeResult.SUCCESS;
}
public override void ReadParameters(NodeDisplay display)
{
}
public override ProgramNode Duplicate()
{
SacrificeNode duplicate = new SacrificeNode();
return duplicate;
}
public override void Setup(NodeDisplay display)
{
}
public override string Save()
{
return $"Name: {DisplayText}";
}
}
+1
View File
@@ -0,0 +1 @@
uid://dq8e7txyldpew
+31 -6
View File
@@ -1,5 +1,8 @@
public class GameResource
{
private const float NormalExtractionSpeed = 1f;
private const float EndlessExtractionSpeed = 4f;
public string name;
public ItemData item;
@@ -15,17 +18,23 @@ public class GameResource
maxAmount = GameData.rand.Next(1000, 10000);
currentAmount = maxAmount;
isEndless = false;
extractionSpeed = 1f;
extractionSpeed = NormalExtractionSpeed;
item = GameData.availableItems[name];
}
public static GameResource FromSaveData(ResourceSaveData saveData)
{
GameResource resource = new GameResource(saveData.Name);
resource.currentAmount = saveData.CurrentAmount;
resource.maxAmount = saveData.MaxAmount;
resource.isEndless = saveData.IsEndless;
resource.extractionSpeed = saveData.ExtractionSpeed;
GameResource resource = new GameResource(saveData.Name)
{
currentAmount = saveData.CurrentAmount,
maxAmount = saveData.MaxAmount,
isEndless = saveData.IsEndless,
extractionSpeed = saveData.ExtractionSpeed
};
if (resource.isEndless && resource.extractionSpeed <= NormalExtractionSpeed)
{
resource.extractionSpeed = EndlessExtractionSpeed;
}
resource.timeSinceLastExtraction = saveData.TimeSinceLastExtraction;
return resource;
}
@@ -64,4 +73,20 @@ public class GameResource
{
return (isEndless || currentAmount > 0) && GameData.availableResearch[item.Research].state == ResearchState.RESEARCHED;
}
public void MakeEndless()
{
isEndless = true;
extractionSpeed = EndlessExtractionSpeed;
}
public bool IsEndless()
{
return isEndless;
}
public float GetExtractionSpeed()
{
return extractionSpeed;
}
}
+12
View File
@@ -29,6 +29,8 @@ public partial class Robot : Node3D
public override void _Process(double delta)
{
if (GameData.isPaused) return;
if (isExecuting)
{
if (CanExecute(delta))
@@ -155,6 +157,16 @@ public partial class Robot : Node3D
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
+135
View File
@@ -19,8 +19,15 @@ public partial class TestRunner : Node
Run("Research execution pays resources and finishes", TestResearchExecutionPaysResourcesAndFinishes);
Run("Research cannot start without resources", TestResearchCannotStartWithoutResources);
Run("Inventory add failure keeps inventory unchanged", TestInventoryAddFailureKeepsInventoryUnchanged);
Run("Saved scripts can be deleted", TestSavedScriptsCanBeDeleted);
Run("Resource extraction and save data roundtrip", TestResourceSaveRoundtrip);
Run("Endless resources extract slower", TestEndlessResourcesExtractSlower);
Run("Robot save data roundtrip keeps robot state", TestRobotSaveRoundtrip);
Run("Maintain node consumes matching gear", TestMaintainNodeConsumesMatchingGear);
Run("Sacrifice node makes resource endless", TestSacrificeNodeMakesResourceEndless);
Run("No robot recovery detects loss", TestNoRobotRecoveryDetectsLoss);
Run("Robot item prevents no robot loss", TestRobotItemPreventsNoRobotLoss);
Run("Save data captures and restores options", TestSaveDataRestoresOptions);
Run("Save data captures and restores global state", TestSaveDataRestoresGlobalState);
Run("Save data restores survival timer", TestSaveDataRestoresSurvivalTimer);
Run("Split save files store and load data", TestSplitSaveFilesRoundtrip);
@@ -28,6 +35,8 @@ public partial class TestRunner : Node
Run("If node evaluates inventory comparisons", TestIfNodeEvaluatesInventoryComparisons);
Run("While node reports false conditions", TestWhileNodeReportsFalseConditions);
Run("For node stops after configured amount", TestForNodeStopsAfterConfiguredAmount);
Run("Paused world does not drain survival", TestPausedWorldDoesNotDrainSurvival);
Run("Open gate hides gate content", TestOpenGateHidesGateContent);
Run("Item data readable names are stable", TestItemDataReadableNames);
Run("Resource files load core game data", TestResourceFilesLoadCoreData);
@@ -173,6 +182,20 @@ public partial class TestRunner : Node
AssertEqual(saveData.CurrentAmount, loadedResource.CreateSaveData().CurrentAmount, "resource amount");
}
private void TestEndlessResourcesExtractSlower()
{
GameResource resource = new GameResource("stone");
AssertTrue(resource.Extract(1.0), "normal resource should extract after one second");
resource.MakeEndless();
AssertFalse(resource.Extract(1.0), "endless resource should not extract after one second");
AssertTrue(resource.Extract(3.0), "endless resource should extract after four total seconds");
AssertTrue(resource.IsEndless(), "resource should be endless");
AssertTrue(resource.GetExtractionSpeed() > 1f, "endless extraction speed should be slower");
}
private void TestRobotSaveRoundtrip()
{
Robot robot = new Robot
@@ -201,6 +224,59 @@ public partial class TestRunner : Node
AssertTrue(loadedRobot.isCoolingDown, "robot cooling state");
}
private void TestMaintainNodeConsumesMatchingGear()
{
Robot robot = new Robot
{
robotType = "copper_robot",
maintenance = 50f,
isBroken = true
};
GameData.inventory.AddItem(new Item { data = GameData.availableItems["copper_gear"] }, 1);
MaintainNode node = new MaintainNode();
AssertEqual(NodeResult.SUCCESS, node.Execute(robot, 0), "maintain should succeed");
AssertClose(60f, robot.maintenance, 0.001f, "maintenance repaired by 10");
AssertFalse(robot.isBroken, "robot should be usable again");
AssertEqual(0, GameData.inventory.GetItemAmount("copper_gear"), "gear should be consumed");
}
private void TestSacrificeNodeMakesResourceEndless()
{
GameData.ruinSize = 1;
GameData.layerSize = 1;
GameData.map = new Layer[1];
GameData.map[0] = CreateTestLayer(0, "spawn");
GameData.map[0].tiles[0, 0].resource = new GameResource("stone");
GameData.map[0].tiles[0, 0].containsResource = true;
Pathfinding.BuildAStarGraph();
Robot robot = new Robot
{
Position = Vector3.Zero
};
GameData.robots.Add(robot);
SacrificeNode node = new SacrificeNode();
AssertEqual(NodeResult.SUCCESS, node.Execute(robot, 0), "sacrifice should succeed");
AssertTrue(GameData.map[0].tiles[0, 0].resource.IsEndless(), "resource should become endless");
AssertEqual(0, GameData.robots.Count, "robot should be removed");
}
private void TestNoRobotRecoveryDetectsLoss()
{
AssertTrue(GameData.HasNoRobotRecovery(), "no robots and no robot items should be a loss");
}
private void TestRobotItemPreventsNoRobotLoss()
{
GameData.inventory.AddItem(new Item { data = GameData.availableItems["stone_robot"] }, 1);
AssertFalse(GameData.HasNoRobotRecovery(), "robot item should prevent loss");
}
private void TestSaveDataRestoresGlobalState()
{
GameData.inventory.AddItem(new Item { data = GameData.availableItems["stone"] }, 12);
@@ -221,6 +297,27 @@ public partial class TestRunner : Node
AssertEqual(ResearchState.RESEARCHED, GameData.availableResearch["stoneage"].state, "saved research");
}
private void TestSaveDataRestoresOptions()
{
GameData.screenMode = 1;
GameData.soundVolume = 0.35f;
GameData.lightColor = new Color(0.2f, 0.4f, 0.6f, 1f);
SaveGameData saveData = SaveGameManager.CreateSaveData();
GameData.screenMode = 2;
GameData.soundVolume = 0.8f;
GameData.lightColor = Colors.White;
SaveGameManager.ApplyWorldData(saveData);
AssertEqual(1, GameData.screenMode, "saved screen mode");
AssertClose(0.35f, GameData.soundVolume, 0.001f, "saved sound volume");
AssertClose(0.2f, GameData.lightColor.R, 0.001f, "saved light red");
AssertClose(0.4f, GameData.lightColor.G, 0.001f, "saved light green");
AssertClose(0.6f, GameData.lightColor.B, 0.001f, "saved light blue");
}
private void TestSaveDataRestoresSurvivalTimer()
{
GameData.survival.elapsedSeconds = 321.5;
@@ -270,6 +367,17 @@ public partial class TestRunner : Node
AssertEqual(0, GameData.inventory.items.Count, "failed add should not create stacks");
}
private void TestSavedScriptsCanBeDeleted()
{
string scriptName = "delete_test_script";
FileHandler.SaveProgram(scriptName, "Name: Explore;");
AssertTrue(FileHandler.LoadProgramNames().Contains(scriptName), "script should be listed");
AssertTrue(FileHandler.DeleteProgram(scriptName), "delete should succeed");
AssertEqual("", FileHandler.LoadProgram(scriptName), "deleted script should be empty");
AssertFalse(FileHandler.DeleteProgram(scriptName), "second delete should fail");
}
private void TestResearchCannotStartWithoutResources()
{
ResearchData researchData = new ResearchData
@@ -367,6 +475,33 @@ public partial class TestRunner : Node
AssertEqual(NodeResult.CONDITIONFALSE, node.Execute(null, 0), "loop finished");
}
private void TestPausedWorldDoesNotDrainSurvival()
{
GameData.isPaused = true;
GameData.canMove = false;
GameData.survival.energy = 100f;
World world = new World();
world.UpdateGameLoop(100.0);
AssertClose(100f, GameData.survival.energy, 0.001f, "energy should not drain while paused");
}
private void TestOpenGateHidesGateContent()
{
Layer layer = CreateTestLayer(0, "gate");
layer.gateCoordinate = new Vector2I(0, 0);
layer.tiles[0, 0].ContentNode = new Node3D
{
Visible = true
};
layer.OpenGate();
AssertTrue(layer.isGateOpen, "gate should be open");
AssertFalse(layer.tiles[0, 0].ContentNode.Visible, "gate content should be hidden");
}
private void TestItemDataReadableNames()
{
AssertEqual("Iron gear", ItemData.GetReadableName("iron_gear"), "readable name");
+29 -1
View File
@@ -58,6 +58,7 @@ public partial class UIHandler : Control
public void HandleMenuButton()
{
OpenUIElement(menu);
GameData.isPaused = menu.Visible || options.Visible;
}
public void HandleMenu()
@@ -67,7 +68,9 @@ public partial class UIHandler : Control
public void ShowOptions()
{
menu.Hide();
OpenUIElement(options);
GameData.isPaused = options.Visible;
}
public void HandleMapButton()
@@ -101,6 +104,7 @@ public partial class UIHandler : Control
RAM.Text = memoryDisplay;
DisplaySurvivalStats();
DisplayWorldStats();
DisplayLoseCondition();
}
public void ExitGame()
@@ -123,6 +127,7 @@ public partial class UIHandler : Control
public void OpenUIElement(Control element)
{
SoundManager.PlayButton();
if (element.Visible)
{
element.Hide();
@@ -141,6 +146,11 @@ public partial class UIHandler : Control
if (child == element) continue;
child.Visible = false;
}
if (element != menu && element != options)
{
GameData.isPaused = false;
}
}
private void DisplayRobotAlarm()
@@ -201,15 +211,28 @@ public partial class UIHandler : Control
unlockLayer.Disabled = !GameData.inventory.CanCraft(GameData.map[GameData.lowestLayer].gateIngredients, 1);
}
private void DisplayLoseCondition()
{
if (!GameData.HasNoRobotRecovery()) return;
ShowGameOver("No robots remain and no robot can be spawned from inventory.");
}
public void UnlockLayer()
{
if (GameData.inventory.CanCraft(GameData.map[GameData.lowestLayer].gateIngredients, 1))
{
int openedLayer = GameData.lowestLayer;
foreach (Ingredient ingredient in GameData.map[GameData.lowestLayer].gateIngredients)
{
GameData.inventory.RemoveItem(ingredient.Item, ingredient.Amount);
}
GameData.lowestLayer++;
World world = GetNodeOrNull<World>("/root/Main/World");
if (world != null)
{
world.OpenGate(openedLayer);
}
if (GameData.lowestLayer == GameData.ruinSize)
{
gameOver.Show();
@@ -218,9 +241,14 @@ public partial class UIHandler : Control
}
public void ShowGameOver()
{
ShowGameOver($"You died!\rReason: {GameData.survival.deathReason}\rBetter luck next time.");
}
public void ShowGameOver(string message)
{
if (gameOver.Visible) return;
gameOver.GetNode<RichTextLabel>("./VBoxContainer/Content").Text = $"[font_size=32]You died!\rReason: {GameData.survival.deathReason}\rBetter luck next time.\r";
gameOver.GetNode<RichTextLabel>("./VBoxContainer/Content").Text = $"[font_size=32]{message}\r";
gameOver.Show();
}
+18
View File
@@ -130,6 +130,8 @@ public partial class CodingWindow : PanelContainer
public void LoadProgram(int index)
{
if (index <= 0) return;
ClearWindow();
string scriptContent = FileHandler.LoadProgram(availableScripts.GetItemText(index));
string[] nodes = scriptContent.Split(";");
@@ -151,6 +153,22 @@ public partial class CodingWindow : PanelContainer
availableScripts.Select(0);
}
public void DeleteProgram()
{
string filename = scriptName.Text;
int selectedIndex = availableScripts.GetSelectedId();
if (selectedIndex > 0)
{
filename = availableScripts.GetItemText(selectedIndex);
}
if (filename.Length <= 0) return;
if (!FileHandler.DeleteProgram(filename)) return;
ClearWindow();
SetupScriptOptions();
}
public void SaveProgram()
{
string result = "";
+12
View File
@@ -89,6 +89,14 @@ public partial class NodeDisplay : PanelContainer
result.node = new ElseNode();
result.LoadElse(nodeSanitized);
break;
case "maintain":
result.node = new MaintainNode();
result.LoadMaintain(nodeSanitized);
break;
case "sacrifice":
result.node = new SacrificeNode();
result.LoadSacrifice(nodeSanitized);
break;
default:
result.QueueFree();
return null;
@@ -99,6 +107,10 @@ public partial class NodeDisplay : PanelContainer
private void LoadElse(string content) { }
private void LoadMaintain(string content) { }
private void LoadSacrifice(string content) { }
private void LoadIf(string content)
{
HBoxContainer valueContainer = GetNode<HBoxContainer>("./EditorDisplay/VBoxContainer/Values");
+9
View File
@@ -2,6 +2,8 @@ using Godot;
public partial class MainMenu : Control
{
[Export] private PanelContainer options;
public override void _Ready()
{
UIStyle.Apply(this);
@@ -26,4 +28,11 @@ public partial class MainMenu : Control
{
GetTree().Quit();
}
public void OnOptionsPressed()
{
if (options == null) return;
options.Show();
}
}
+83
View File
@@ -0,0 +1,83 @@
using Godot;
public partial class OptionsMenu : PanelContainer
{
private readonly Vector2 panelSize = new Vector2(420, 260);
[Export] private OptionButton screenMode;
[Export] private HSlider soundVolume;
[Export] private ColorPickerButton lightColor;
public override void _Ready()
{
CenterPanel();
SetupScreenModes();
screenMode.Select(GameData.screenMode);
soundVolume.Value = GameData.soundVolume * 100f;
lightColor.Color = GameData.lightColor;
ApplyScreenMode(GameData.screenMode);
SoundManager.SetMasterVolume(GameData.soundVolume);
}
private void SetupScreenModes()
{
screenMode.Clear();
screenMode.AddItem("Fullscreen");
screenMode.AddItem("Windowed");
screenMode.AddItem("Windowed Fullscreen");
screenMode.Select(2);
}
public void OnScreenModeSelected(int index)
{
GameData.screenMode = index;
ApplyScreenMode(index);
}
private void ApplyScreenMode(int index)
{
switch (index)
{
case 0:
DisplayServer.WindowSetMode(DisplayServer.WindowMode.Fullscreen);
DisplayServer.WindowSetFlag(DisplayServer.WindowFlags.Borderless, false);
break;
case 1:
DisplayServer.WindowSetMode(DisplayServer.WindowMode.Windowed);
DisplayServer.WindowSetFlag(DisplayServer.WindowFlags.Borderless, false);
break;
case 2:
DisplayServer.WindowSetMode(DisplayServer.WindowMode.Fullscreen);
DisplayServer.WindowSetFlag(DisplayServer.WindowFlags.Borderless, true);
break;
}
}
public void OnSoundVolumeChanged(double value)
{
GameData.soundVolume = (float)value / 100f;
SoundManager.SetMasterVolume(GameData.soundVolume);
}
public void OnLightColorChanged(Color color)
{
GameData.lightColor = color;
LightHandler.RedrawLights(color);
}
public void CloseOptions()
{
Hide();
GameData.isPaused = false;
}
private void CenterPanel()
{
CustomMinimumSize = panelSize;
SetAnchorsPreset(LayoutPreset.Center);
OffsetLeft = -panelSize.X / 2f;
OffsetTop = -panelSize.Y / 2f;
OffsetRight = panelSize.X / 2f;
OffsetBottom = panelSize.Y / 2f;
}
}
+1
View File
@@ -0,0 +1 @@
uid://ca1rfge0y3y4i
+10
View File
@@ -39,6 +39,16 @@ public partial class Layer : Node3D
}
}
public void OpenGate()
{
isGateOpen = true;
Tile gateTile = tiles[gateCoordinate.X, gateCoordinate.Y];
if (gateTile.ContentNode != null)
{
gateTile.ContentNode.Visible = false;
}
}
public void SetupLayer(int layerSize, int level, Dictionary<string, MeshInstance3D> tileMeshes, Vector2I collapseOrigin)
{
this.layerSize = layerSize;
+30
View File
@@ -98,6 +98,13 @@ public partial class World : Node3D
public override void _Process(double delta)
{
UpdateGameLoop(delta);
}
public void UpdateGameLoop(double delta)
{
if (isPaused) return;
survival.Update(delta);
if (!canMove) return;
@@ -110,6 +117,25 @@ public partial class World : Node3D
}
}
public void RefreshVisibleLayer()
{
ShowLayer(visibleLayer);
}
public void OpenGate(int layerIndex)
{
if (layerIndex < 0 || layerIndex >= map.Length) return;
Layer layer = map[layerIndex];
layer.OpenGate();
Pathfinding.UpdateGatePoint(layerIndex, true);
if (layerIndex == visibleLayer)
{
RefreshVisibleLayer();
}
}
private void SpawnDefaultRobot()
{
Robot robot = ResourceLoader.LoadRobotPrefab().Instantiate<Robot>();
@@ -184,6 +210,10 @@ public partial class World : Node3D
for (int y = 0; y < layerSize; y++)
{
Tile tile = layer.tiles[x, y];
if (layer.isGateOpen && tile.collapsedMesh == "gate")
{
continue;
}
result.Add(new TileRenderData
{
Tile = tile,