riversong code showcase

This commit is contained in:
Daniele Marotta
2026-05-21 15:52:18 +02:00
commit 4c9eea1c02
462 changed files with 23406 additions and 0 deletions

View File

@@ -0,0 +1,9 @@
namespace DanieleMarotta.RiversongCodeShowcase
{
public interface IUnlocksService
{
void Unlock(UnlockDefinition unlock);
void UnlockAll();
}
}

View File

@@ -0,0 +1,22 @@
using System;
using Sirenix.OdinInspector;
namespace DanieleMarotta.RiversongCodeShowcase
{
[Serializable]
public class UnlockCondition
{
public UnlockConditionType Type;
[ShowIf("Type", UnlockConditionType.BuildingPlaced)]
public BuildingDefinition Building;
[ShowIf("Type", UnlockConditionType.HouseCountAtTierOrAbove)]
[LabelText("Count")]
public int HouseCount;
[ShowIf("Type", UnlockConditionType.HouseCountAtTierOrAbove)]
[LabelText("Tier")]
public int MinHouseTierIndex;
}
}

View File

@@ -0,0 +1,11 @@
namespace DanieleMarotta.RiversongCodeShowcase
{
public enum UnlockConditionType
{
None,
BuildingPlaced,
HouseCountAtTierOrAbove
}
}

View File

@@ -0,0 +1,22 @@
using System.Collections.Generic;
using Sirenix.OdinInspector;
using UnityEngine;
namespace DanieleMarotta.RiversongCodeShowcase
{
[CreateAssetMenu(fileName = "UnlockDefinition", menuName = "Riversong Code Showcase/Unlock Definition")]
public class UnlockDefinition : GameDataAsset
{
public UnlockType Type;
[ShowIf("Type", UnlockType.UnlockBuilding)]
public BuildingDefinition Building;
[ShowIf("Type", UnlockType.UnlockBuilding)]
public List<BuildingDefinition> TeasedBuildings;
[TitleGroup("Conditions")]
[ShowIf("@this.Type != UnlockType.None")]
public List<UnlockCondition> Conditions;
}
}

View File

@@ -0,0 +1,9 @@
namespace DanieleMarotta.RiversongCodeShowcase
{
public enum UnlockType
{
None,
UnlockBuilding
}
}

View File

@@ -0,0 +1,12 @@
namespace DanieleMarotta.RiversongCodeShowcase
{
public struct UnlockUnlockedSignal
{
public UnlockDefinition Unlock;
public UnlockUnlockedSignal(UnlockDefinition unlock)
{
Unlock = unlock;
}
}
}

View File

@@ -0,0 +1,155 @@
using System;
using System.Collections.Generic;
using Cysharp.Threading.Tasks;
namespace DanieleMarotta.RiversongCodeShowcase
{
[Service(typeof(IUnlocksService))]
[InitializeAfter(typeof(HouseTierCountTrackingSystem))]
public class UnlocksManagerSystem : GameSystem, IInitializable, IDisposable, IUnlocksService
{
[InjectService]
private IGameDatabase _gameDatabase;
[InjectService]
private ISignalBus _signalBus;
[InjectService]
private IEntityCollection _entityCollection;
[InjectService]
private World _world;
private List<UnlockDefinition> _unlocks;
public UnlocksManagerSystem(IServiceLocator serviceLocator) : base(serviceLocator)
{
}
public UniTask InitializeAsync()
{
_unlocks = _gameDatabase.OfType<UnlockDefinition>();
foreach (var unlock in _unlocks)
if (unlock.Building)
_world.UnlocksState.AddBuildingUnlock(unlock.Building, unlock.RuntimeId);
_signalBus.Subscribe<WorldReadySignal>(OnWorldReady);
_signalBus.Subscribe<BuildingCreatedSignal>(OnBuildingCreated);
_signalBus.Subscribe<BuildingUpgradedSignal>(OnBuildingUpgraded);
return UniTask.CompletedTask;
}
public void Dispose()
{
_signalBus.Unsubscribe<WorldReadySignal>(OnWorldReady);
_signalBus.Unsubscribe<BuildingCreatedSignal>(OnBuildingCreated);
_signalBus.Unsubscribe<BuildingUpgradedSignal>(OnBuildingUpgraded);
}
private void OnWorldReady(WorldReadySignal signal)
{
_ = GrantFreeUnlocksWithDelayAsync();
}
private async UniTask GrantFreeUnlocksWithDelayAsync()
{
await UniTask.WaitForSeconds(2, true);
foreach (var unlock in _unlocks)
if (unlock.Conditions.Count <= 0)
Unlock(unlock);
}
private void OnBuildingCreated(BuildingCreatedSignal signal)
{
TryUnlockAvailableUnlocks();
}
private void OnBuildingUpgraded(BuildingUpgradedSignal signal)
{
TryUnlockAvailableUnlocks();
}
private void TryUnlockAvailableUnlocks()
{
foreach (var unlock in _unlocks)
if (CanUnlock(unlock))
Unlock(unlock);
}
private bool CanUnlock(UnlockDefinition unlock)
{
if (_world.UnlocksState.Unlocked.Contains(unlock.RuntimeId)) return false;
foreach (var condition in unlock.Conditions)
if (!IsConditionSatisfied(condition))
return false;
return true;
}
public void Unlock(UnlockDefinition unlock)
{
if (!_world.UnlocksState.Unlocked.Add(unlock.RuntimeId)) return;
switch (unlock.Type)
{
case UnlockType.UnlockBuilding:
UnlockBuilding(unlock);
break;
case UnlockType.None:
default:
throw new ArgumentOutOfRangeException();
}
_signalBus.Raise(new UnlockUnlockedSignal(unlock));
}
public void UnlockAll()
{
foreach (var unlock in _unlocks) Unlock(unlock);
}
private void UnlockBuilding(UnlockDefinition unlock)
{
var unlocksState = _world.UnlocksState;
var unlockedId = unlock.Building.RuntimeId;
unlocksState.UnlockedBuildings.Add(unlockedId);
unlocksState.TeasedBuildings.Remove(unlockedId);
foreach (var teased in unlock.TeasedBuildings)
{
if (unlocksState.UnlockedBuildings.Contains(teased.RuntimeId)) continue;
unlocksState.TeasedBuildings.Add(teased.RuntimeId);
}
}
private bool IsConditionSatisfied(UnlockCondition condition)
{
switch (condition.Type)
{
case UnlockConditionType.BuildingPlaced:
foreach (Building building in _entityCollection.GetInternalEntityList(typeof(Building)))
if (building.Definition.RuntimeId == condition.Building.RuntimeId)
return true;
return false;
case UnlockConditionType.HouseCountAtTierOrAbove:
var houseCount = 0;
var houseCountsByTier = _world.PopulationState.HouseCountsByTier;
for (var i = condition.MinHouseTierIndex; i < WorldPopulationState.MaxBuildingTier; i++) houseCount += houseCountsByTier[i];
return houseCount >= condition.HouseCount;
case UnlockConditionType.None:
default:
throw new ArgumentOutOfRangeException();
}
}
}
}

View File

@@ -0,0 +1,25 @@
using System.Collections.Generic;
namespace DanieleMarotta.RiversongCodeShowcase
{
public class UnlocksState
{
private Dictionary<int, int> _buildingUnlockLookup = new();
public HashSet<int> Unlocked { get; } = new();
public HashSet<int> UnlockedBuildings { get; } = new();
public HashSet<int> TeasedBuildings { get; } = new();
public void AddBuildingUnlock(BuildingDefinition building, int unlockId)
{
_buildingUnlockLookup.Add(building.RuntimeId, unlockId);
}
public bool TryGetBuildingUnlock(BuildingDefinition building, out int unlockId)
{
return _buildingUnlockLookup.TryGetValue(building.RuntimeId, out unlockId);
}
}
}