Add EventManager and event handling system; implement event triggering and popup display

This commit is contained in:
2025-08-23 23:38:45 +02:00
parent c0d18507e3
commit cdd944b9b5
16 changed files with 379 additions and 26 deletions

View File

@@ -0,0 +1,74 @@
using System.Collections.Generic;
using Godot;
using Limbo.Console.Sharp;
using ParasiticGod.Scripts.Core;
using ParasiticGod.Scripts.Singletons;
using ParasiticGod.Scripts.UI;
namespace ParasiticGod.Scripts.Components;
[GlobalClass]
public partial class EventManager : Node
{
[Export] private double _checkInterval = 5.0;
[Export] private PackedScene _eventPopupScene;
[Export] private Container _eventPopupContainer;
private List<EventDefinition> _allEvents;
private Timer _timer;
private RandomNumberGenerator _rng = new();
public override void _Ready()
{
RegisterConsoleCommands();
_allEvents = GameBus.Instance.AllEvents;
_timer = new Timer { WaitTime = _checkInterval, Autostart = true };
AddChild(_timer);
_timer.Timeout += OnCheckEvents;
}
private void OnCheckEvents()
{
if (GetTree().Paused) return;
var state = GameBus.Instance.CurrentState;
foreach (var ev in _allEvents)
{
if (state.Get(Stat.Followers) < ev.Trigger.MinFollowers) continue;
if (state.Get(Stat.Corruption) > ev.Trigger.MaxCorruption) continue;
var probability = _checkInterval / ev.MeanTimeToHappen;
if (_rng.Randf() < probability)
{
FireEvent(ev);
break;
}
}
}
private void FireEvent(EventDefinition eventDef)
{
GD.Print($"Firing event: {eventDef.Title}");
GetTree().Paused = true;
var popup = _eventPopupScene.Instantiate<EventPopup>();
_eventPopupContainer.AddChild(popup);
popup.DisplayEvent(eventDef);
}
[ConsoleCommand("trigger_event", "Triggers an event by its ID for testing purposes.")]
private void TriggerEventCommand(string eventId)
{
var eventDef = _allEvents.Find(e => e.Id == eventId);
if (eventDef != null)
{
FireEvent(eventDef);
}
else
{
GD.PushError($"No event found with ID: {eventId}");
}
}
}

View File

@@ -0,0 +1 @@
uid://2ipbgwlx1ld1

View File

@@ -0,0 +1,22 @@
using System.Collections.Generic;
using Godot.Collections;
using ParasiticGod.Scripts.Core.Effects;
namespace ParasiticGod.Scripts.Core;
public class EventDefinition
{
public string Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public int MeanTimeToHappen { get; set; }
public EventTriggerDto Trigger { get; set; }
public List<EventOptionDefinition> Options { get; set; } = [];
}
public class EventOptionDefinition
{
public string Text { get; set; }
public string Tooltip { get; set; }
public Array<Effect> Effects { get; set; }
}

View File

@@ -0,0 +1 @@
uid://ilyr01u70ciw

46
Scripts/Core/EventDto.cs Normal file
View File

@@ -0,0 +1,46 @@
using System.Collections.Generic;
using Newtonsoft.Json;
namespace ParasiticGod.Scripts.Core;
public class EventTriggerDto
{
[JsonProperty("minFollowers")]
public long MinFollowers { get; set; } = 0;
[JsonProperty("maxCorruption")]
public double MaxCorruption { get; set; } = 100;
}
public class EventOptionDto
{
[JsonProperty("text")]
public string Text { get; set; }
[JsonProperty("tooltip")]
public string Tooltip { get; set; }
[JsonProperty("effects")]
public List<EffectDto> Effects { get; set; }
}
public class EventDto
{
[JsonProperty("id")]
public string Id { get; set; }
[JsonProperty("title")]
public string Title { get; set; }
[JsonProperty("description")]
public string Description { get; set; }
[JsonProperty("meanTimeToHappen")]
public int MeanTimeToHappen { get; set; } = 100; // in game days
[JsonProperty("trigger")]
public EventTriggerDto Trigger { get; set; }
[JsonProperty("options")]
public List<EventOptionDto> Options { get; set; }
}

View File

@@ -0,0 +1 @@
uid://nwwqn028soa5

View File

@@ -0,0 +1,71 @@
using System.Collections.Generic;
using Godot;
using Newtonsoft.Json;
namespace ParasiticGod.Scripts.Core;
public static class EventLoader
{
public static List<EventDefinition> LoadAllEvents()
{
var loadedEvents = new Dictionary<string, EventDefinition>();
LoadEventsFromPath("res://Mods/Events", loadedEvents);
LoadEventsFromPath("user://Mods/Events", loadedEvents);
GD.Print($"Finished loading. Total unique events: {loadedEvents.Count}");
return new List<EventDefinition>(loadedEvents.Values);
}
private static void LoadEventsFromPath(string path, Dictionary<string, EventDefinition> events)
{
if (!DirAccess.DirExistsAbsolute(path)) return;
using var dir = DirAccess.Open(path);
dir.ListDirBegin();
var fileName = dir.GetNext();
while (!string.IsNullOrEmpty(fileName))
{
if (!dir.CurrentIsDir() && fileName.EndsWith(".json"))
{
var filePath = path.PathJoin(fileName);
var ev = LoadEventFromFile(filePath);
if (ev != null)
{
events[ev.Id] = ev; // Add or overwrite
}
}
fileName = dir.GetNext();
}
}
private static EventDefinition LoadEventFromFile(string filePath)
{
var fileContent = FileAccess.GetFileAsString(filePath);
if (string.IsNullOrEmpty(fileContent)) return null;
var dto = JsonConvert.DeserializeObject<EventDto>(fileContent);
if (dto == null) return null;
var eventDef = new EventDefinition
{
Id = dto.Id,
Title = dto.Title,
Description = dto.Description,
MeanTimeToHappen = dto.MeanTimeToHappen,
Trigger = dto.Trigger
};
foreach (var optionDto in dto.Options)
{
var optionDef = new EventOptionDefinition
{
Text = optionDto.Text,
Tooltip = optionDto.Tooltip,
Effects = MiracleLoader.ConvertEffectDtos(optionDto.Effects)
};
eventDef.Options.Add(optionDef);
}
return eventDef;
}
}

View File

@@ -0,0 +1 @@
uid://ceimix3ejnadj

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using Godot;
using Godot.Collections;
using Newtonsoft.Json;
@@ -86,7 +87,17 @@ public static class MiracleLoader
Effects = []
};
foreach (var effectDto in miracleDto.Effects)
var effects = ConvertEffectDtos(miracleDto.Effects);
miracleDef.Effects = effects;
return miracleDef;
}
public static Array<Effect> ConvertEffectDtos(List<EffectDto> dtos)
{
var effects = new Array<Effect>();
if (dtos == null) return effects;
foreach (var effectDto in dtos)
{
if (EffectRegistry.TryGetValue(effectDto.Type, out var effectType))
{
@@ -118,11 +129,9 @@ public static class MiracleLoader
unlockMiracleEffect.MiraclesToUnlock = new Array<string>(effectDto.MiraclesToUnlock);
break;
}
miracleDef.Effects.Add(effectInstance);
effects.Add(effectInstance);
}
}
return miracleDef;
return effects;
}
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using Godot;
using Godot.Collections;
using Limbo.Console.Sharp;
using ParasiticGod.Scripts.Core;
using ParasiticGod.Scripts.Core.Effects;
@@ -10,10 +11,11 @@ namespace ParasiticGod.Scripts.Singletons;
public partial class GameBus : Node
{
public static GameBus Instance { get; private set; }
public Dictionary<string, MiracleDefinition> AllMiracles { get; private set; }
public System.Collections.Generic.Dictionary<string, MiracleDefinition> AllMiracles { get; private set; }
public List<TierDefinition> FollowerTiers { get; private set; }
public List<TierDefinition> HutTiers { get; private set; }
public List<TierDefinition> TempleTiers { get; private set; }
public List<EventDefinition> AllEvents { get; private set; }
private PackedScene _gameOverScene = GD.Load<PackedScene>("res://Scenes/game_over.tscn");
private PackedScene _winScene = GD.Load<PackedScene>("res://Scenes/win_screen.tscn");
@@ -35,6 +37,7 @@ public partial class GameBus : Node
{
Instance = this;
AllMiracles = MiracleLoader.LoadAllMiracles();
AllEvents = EventLoader.LoadAllEvents();
FollowerTiers = TierLoader.LoadTiers("res://Mods/Tiers/follower_tiers.json", "user://Mods/Tiers/follower_tiers.json");
HutTiers = TierLoader.LoadTiers("res://Mods/Tiers/hut_tiers.json","user://Mods/Tiers/hut_tiers.json");
TempleTiers = TierLoader.LoadTiers("res://Mods/Tiers/temple_tiers.json","user://Mods/Tiers/temple_tiers.json");
@@ -124,6 +127,15 @@ public partial class GameBus : Node
GameWon?.Invoke();
}
public void ExecuteEffects(Array<Effect> effects)
{
foreach (var effect in effects)
{
effect.Execute(_gameState);
}
GetTree().Paused = false;
}
public void SubscribeToStat(Stat stat, Action<double> listener) => _gameState.Subscribe(stat, listener);
public void UnsubscribeFromStat(Stat stat, Action<double> listener) => _gameState.Unsubscribe(stat, listener);

40
Scripts/UI/EventPopup.cs Normal file
View File

@@ -0,0 +1,40 @@
using Godot;
using ParasiticGod.Scripts.Core;
using ParasiticGod.Scripts.Singletons;
namespace ParasiticGod.Scripts.UI;
[GlobalClass]
public partial class EventPopup : PanelContainer
{
[Export] private Label _titleLabel;
[Export] private RichTextLabel _descriptionLabel;
[Export] private VBoxContainer _optionsContainer;
[Export] private PackedScene _optionButtonScene; // A scene for a single button
public void DisplayEvent(EventDefinition eventDef)
{
_titleLabel.Text = eventDef.Title;
_descriptionLabel.Text = eventDef.Description;
foreach (var child in _optionsContainer.GetChildren())
{
child.QueueFree();
}
foreach (var option in eventDef.Options)
{
var button = _optionButtonScene.Instantiate<Button>();
button.Text = option.Text;
button.TooltipText = option.Tooltip;
button.Pressed += () =>
{
GameBus.Instance.ExecuteEffects(option.Effects);
QueueFree();
};
_optionsContainer.AddChild(button);
}
}
}

View File

@@ -0,0 +1 @@
uid://h1x5eqt0lc5m