Skip to content

Commit

Permalink
#88 battle effect casting workflow refactoring wip
Browse files Browse the repository at this point in the history
  • Loading branch information
Konstantin V. Krupovich committed Jun 3, 2023
1 parent b5a2218 commit 638a7ad
Show file tree
Hide file tree
Showing 19 changed files with 546 additions and 210 deletions.
13 changes: 13 additions & 0 deletions Assets/Scripts/ECS/Data/BattleComp.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Assets.Scripts.Data;
using Leopotam.EcsLite;
using System.Collections.Generic;

namespace Assets.Scripts.ECS.Data
{
Expand Down Expand Up @@ -30,7 +31,19 @@ public struct HeroInstanceRefComp : IPackedWithWorldRef
{
public EcsPackedEntityWithWorld HeroInstancePackedEntity { get; internal set; }
public EcsPackedEntityWithWorld Packed => HeroInstancePackedEntity;
}

public struct HeroInstanceMapping
{
/// <summary>
/// Origin world entity packed used as a key, so when needed we can get a battle world entity by this key
/// </summary>
public Dictionary<EcsPackedEntityWithWorld, EcsPackedEntityWithWorld> OriginToBattleMapping { get; internal set; }

/// <summary>
/// Battle world entity packed used as a key, so when needed we can get an origin world entity by this key
/// </summary>
public Dictionary<EcsPackedEntityWithWorld, EcsPackedEntityWithWorld> BattleToOriginMapping { get; internal set; }
}

/// <summary>
Expand Down
38 changes: 36 additions & 2 deletions Assets/Scripts/ECS/Data/Relations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,29 @@ public struct P2PRelationTag { }
/// Marks a value of current relation score between some parties
/// </summary>
public struct RelationScoreTag { }


/// <summary>
/// For casting probability needs, we don't need any info about current effects, only their count
/// </summary>
public struct RelationEffectsCountTag { }

/// <summary>
/// Marks a turn to process additional round queue manipulation to insert a hero after current turn
/// </summary>
public struct PrepareRevengeComp{
public struct PrepareRevengeComp {
public EcsPackedEntityWithWorld RevengeFor { get; set; }
public EcsPackedEntityWithWorld RevengeBy { get; set; }
}

/// <summary>
/// Marks a turn to affect targeting (aiming) system so all teammates will attack an effect's focus
/// </summary>
public struct PrepareTargetComp
{
public EcsPackedEntityWithWorld TargetFor { get; set; }
public EcsPackedEntityWithWorld TargetBy { get; set; }
}

/// <summary>
/// Contains references to each score entity by hero instance entity,
/// so when event is spawned or we just need to check a score with some other guy
Expand Down Expand Up @@ -52,6 +66,11 @@ public struct EffectInstanceInfo
/// </summary>
public EcsPackedEntityWithWorld EffectSource { get; set; }

/// <summary>
/// Score and effects count in the world of the relation origin
/// </summary>
public EcsPackedEntityWithWorld EffectP2PEntity { get; set; }

/// <summary>
/// Applicable For:
/// - AlgoRevenge
Expand All @@ -76,6 +95,21 @@ public override string ToString()
public RelationEffectInfo EffectInfo { get; set; }
}

public struct RelEffectProbeComp
{
public EcsPackedEntityWithWorld TargetConfigRefPacked { get; internal set; }
public EcsPackedEntityWithWorld SourceOrigPacked { get; internal set; }
public EcsPackedEntityWithWorld TargetOrigPacked { get; internal set; }

/// <summary>
/// Score, current effects count (in the origin world), current effects info (in the battle wolrd
/// </summary>
public EcsPackedEntityWithWorld P2PEntityPacked { get; internal set; }

public EcsPackedEntity TurnEntity { get; internal set; }
public RelationSubjectState SubjectState { get; internal set; }
}

public struct RelationEffectsComp
{
private Dictionary<RelationEffectKey, EffectInstanceInfo> currentEffects;
Expand Down
156 changes: 14 additions & 142 deletions Assets/Scripts/ECS/Systems/Battle/BattleAssignRelationEffectsSystem.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
using Assets.Scripts.Data;
using Assets.Scripts.ECS.Data;
using Assets.Scripts.Services;
using Leopotam.EcsLite;
using Leopotam.EcsLite.Di;
using System;
using UnityEngine;

namespace Assets.Scripts.ECS.Systems
{

public class BattleAssignRelationEffectsSystem<T> : IEcsRunSystem
where T : struct, IPackedWithWorldRef
{
Expand All @@ -17,20 +15,13 @@ public class BattleAssignRelationEffectsSystem<T> : IEcsRunSystem
protected readonly EcsWorldInject ecsWorld;

protected readonly EcsPoolInject<T> pool = default;
protected readonly EcsPoolInject<RelEffectProbeComp> relEffectProbePool = default;
protected readonly EcsPoolInject<PlayerTeamTag> playerTeamTagPool = default;
protected readonly EcsPoolInject<HeroInstanceOriginRefComp> heroInstanceOriginRefPool = default;
protected readonly EcsPoolInject<HeroConfigRefComp> heroConfigRefPool = default;
protected readonly EcsPoolInject<RelationEffectsComp> relEffectsPool = default;
protected readonly EcsPoolInject<UpdateTag<RelationEffectInfo>> updatePool = default;
protected readonly EcsPoolInject<AttackerRef> attackerRefPool = default;
protected readonly EcsPoolInject<PrepareRevengeComp> revengePool = default;

protected readonly EcsFilterInject<Inc<DraftTag, BattleTurnInfo, T>> filter = default;

protected readonly EcsCustomInject<BattleManagementService> battleService = default;

protected readonly EcsCustomInject<HeroLibraryService> heroLibraryService = default;

public void Run(IEcsSystems systems)
{
foreach (var entity in filter.Value)
Expand All @@ -42,8 +33,8 @@ public void Run(IEcsSystems systems)
if (!playerTeamTagPool.Value.Has(effectTargetEntity))
continue;

ref var originRef = ref heroInstanceOriginRefPool.Value.Get(effectTargetEntity);
if (!originRef.Packed.Unpack(out var origWorld, out var effectTargetOrig))
ref var effTargetOriginRef = ref heroInstanceOriginRefPool.Value.Get(effectTargetEntity);
if (!effTargetOriginRef.Packed.Unpack(out var origWorld, out var effectTargetOrig))
continue;

ref var heroConfigRef = ref heroConfigRefPool.Value.Get(effectTargetEntity);
Expand All @@ -57,142 +48,23 @@ public void Run(IEcsSystems systems)
foreach (var item in matrixComp.Matrix)
{
// matching only one side of the key to avoid duplicates
if (!item.Key.Item1.EqualsTo(originRef.Packed))
if (!item.Key.Item1.EqualsTo(effTargetOriginRef.Packed))
continue;

if (TryCastRelationEffect(heroConfigRef.Packed, item.Key.Item2, originRef.Packed, item.Value,
out var effect))
{
// registering effect for the hero affected (in the battle world, to make it handy when needed)
RelationEffectKey ruleKey = effect.Rule.Key;
item.Key.Item2.Unpack(out _, out var effectSourceEntity);

var affectedParty = effectTargetEntity;

if (ruleKey.RelationsEffectType switch {
RelationsEffectType.AlgoRevenge => true,
RelationsEffectType.AlgoTarget => true,
_ => false
})
{
affectedParty = effectSourceEntity;

ref var attackerRef = ref attackerRefPool.Value.Get(entity);
effect.EffectFocus = attackerRef.Packed;

var revengeEntity = ecsWorld.Value.NewEntity();
ref var revengeComp = ref revengePool.Value.Add(revengeEntity);
revengeComp.RevengeBy = ecsWorld.Value.PackEntityWithWorld(effectSourceEntity);
revengeComp.RevengeFor = ecsWorld.Value.PackEntityWithWorld(effectTargetEntity);
}

// add info for UI
var heroIconPool = origWorld.GetPool<NameValueComp<IconTag>>();
ref var heroIcon = ref heroIconPool.Get(effectSourceEntity);
var info = effect.Rule.DraftEffectInfo(effect.Rule.GetHashCode(), heroIcon.Name);
effect.EffectInfo = info;

ref var relEffects = ref relEffectsPool.Value.Get(affectedParty);
relEffects.SetEffect(ruleKey, effect);

if (!updatePool.Value.Has(affectedParty))
updatePool.Value.Add(affectedParty);
}

var relEffectProbeEntity = ecsWorld.Value.NewEntity();
ref var probeComp = ref relEffectProbePool.Value.Add(relEffectProbeEntity);
probeComp.SourceOrigPacked = item.Key.Item1;
probeComp.TargetOrigPacked = item.Key.Item2;
probeComp.TargetConfigRefPacked = heroConfigRef.Packed;
probeComp.P2PEntityPacked = item.Value;
probeComp.SubjectState = SubjectState;
probeComp.TurnEntity = systems.GetWorld().PackEntity(entity);
}
}

}
}

/// <summary>
/// Will add relation effect if rules applied will allow to
/// </summary>
/// <param name="targetEntity">Entity of a hero to wich the effect will be casted (if any)</param>
/// <param name="origWorld">EcsWorld to take relations from</param>
/// <param name="heroConfigPackedEntity">Hero Config Entity of the given hero</param>
/// <param name="effectSource">Packed other guy entity in the origin world</param>
/// <param name="effectTarget">Packed this guy entity in the origin world</param>
/// <param name="scoreEntityPacked">Packed score entity for the given hero and the other guy</param>
/// <returns>true if new effect was casted</returns>
protected bool TryCastRelationEffect(
EcsPackedEntityWithWorld heroConfigPackedEntity,
EcsPackedEntityWithWorld effectSource,
EcsPackedEntityWithWorld effectTarget,
EcsPackedEntityWithWorld scoreEntityPacked,
out EffectInstanceInfo effect)
{
effect = default;
if (!effectSource.Unpack(out var origWorld, out var otherGuyEntity))
throw new Exception("Stale Other Guy Entity (probably dead already)");

if (!scoreEntityPacked.Unpack(out _, out var scoreEntity))
throw new Exception("Stale Score Entity");

var relationsConfig = heroLibraryService.Value.HeroRelationsConfigProcessor();

var score = origWorld.ReadIntValue<RelationScoreTag>(scoreEntity);
var relationsState = relationsConfig.GetRelationState(score);


if (!heroConfigPackedEntity.Unpack(out var libWorld, out var heroConfigEntity))
throw new Exception("No Hero Config for current guy");

var libHeroPool = libWorld.GetPool<Hero>();
ref var heroConfig = ref libHeroPool.Get(heroConfigEntity);

var rulesCaseKey = new RelationEffectLibraryKey(
heroConfig.Id, SubjectState, relationsState);

var effectRules = heroLibraryService.Value.HeroRelationEffectsLibProcessor();

if (!effectRules.SubjectStateEffectsIndex.TryGetValue(rulesCaseKey, out var scope))
return false; // no effect for relation state, it's ok

var origWorldConfigRefPool = origWorld.GetPool<HeroConfigRefComp>();
ref var otherGuyConfigRef = ref origWorldConfigRefPool.Get(otherGuyEntity);
if (!otherGuyConfigRef.Packed.Unpack(out _, out var otherGuyHeroConfigEntity))
throw new Exception("No Hero Config for the other guy");

ref var otherGuyHeroConfig = ref libHeroPool.Get(otherGuyHeroConfigEntity);

var ruleType = scope.EffectRule.EffectType;

if (ruleType.EffectClass() != RelationsEffectClass.Battle)
return false; // this system process only battle effects

var rule = (IBattleEffectRule)scope.EffectRule;
ref var currentRound = ref battleService.Value.CurrentRound;

Debug.Log($"Relations Effect of type {ruleType} was just spawned " +
$"for {heroConfig.Name} in {scope.SelfState} due to {scope.RelationState} " +
$"with {otherGuyHeroConfig.Name}");

// 1. add component to the entity of the current score data;
// 2. keep all spawned effects (EffectRules) in that component;
// 3. count of already spawned effects used as a wheight for spawn rate (key for
// AdditioinalEffectSpawnRate queries);

var currentEffectsPool = origWorld.GetPool<RelationEffectsComp>();
// respect spawn rate from AdditionalEffectSpawnRate:
ref var currentEffects = ref currentEffectsPool.Get(scoreEntity);
if (!effectRules.TrySpawnAdditionalEffect(currentEffects.CurrentEffects.Count))
return false;

Debug.Log($"Spawned with respect of exisiting {currentEffects.CurrentEffects.Count} effects");

effect = new EffectInstanceInfo()
{
StartRound = currentRound.Round,
EndRound = currentRound.Round + rule.TurnsCount,
UsageLeft = rule.TurnsCount,
Rule = rule,
EffectSource = effectSource,
};

currentEffects.SetEffect(rule.Key, effect); // effect focus (if any) is omitted here, but added for battle context.

return true;
}

}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using Assets.Scripts.Data;
using Assets.Scripts.ECS.Data;
using Assets.Scripts.Services;
using Leopotam.EcsLite;
using Leopotam.EcsLite.Di;

Expand All @@ -14,8 +13,6 @@ public class BattleDequeueExpiredRelationEffectsSystem : IEcsRunSystem
private readonly EcsFilterInject<Inc<RelationEffectsComp>> currentEffectFilter = default;
private readonly EcsFilterInject<Inc<BattleRoundInfo, GarbageTag>> filter = default;

private readonly EcsCustomInject<RaidService> raidService = default;

public void Run(IEcsSystems systems)
{
foreach (var entity in filter.Value)
Expand All @@ -25,14 +22,10 @@ public void Run(IEcsSystems systems)
// for both battle and raid worlds and remove expired effects:
DequeueRelationEffects(systems.GetWorld(), roundInfo.Round);

if (raidService.Value.EcsWorld != null)
DequeueRelationEffects(raidService.Value.EcsWorld, roundInfo.Round);

// enqueue view update to reflect changes (if any)
foreach (var effectsEntity in currentEffectFilter.Value)
if (!updatePool.Value.Has(effectsEntity))
updatePool.Value.Add(effectsEntity);

updatePool.Value.Add(effectsEntity);
}
}

Expand All @@ -50,13 +43,18 @@ private void DequeueRelationEffects(EcsWorld world, int round)
buff.Add(item.Key);

foreach (var item in buff)
{
var effect = relEffect.CurrentEffects[item];
if (effect.EffectP2PEntity.Unpack(out var origWorld, out var p2pEntity))
origWorld.IncrementIntValue<RelationEffectsCountTag>(-1, p2pEntity);

relEffect.CurrentEffects.Remove(item);
}

buff.Clear();
}

ListPool<RelationEffectKey>.Add(buff);
}

}
}
Loading

0 comments on commit 638a7ad

Please sign in to comment.