Skip to content

Commit

Permalink
Add tests and fix various bugs. Add LifebloodCountVariable.
Browse files Browse the repository at this point in the history
  • Loading branch information
homothetyhk committed Dec 31, 2024
1 parent 7f64073 commit 96a1164
Show file tree
Hide file tree
Showing 18 changed files with 471 additions and 46 deletions.
2 changes: 2 additions & 0 deletions RandomizerMod/RC/RandoVariableResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ public override bool TryMatch(LogicManager lm, string term, out LogicVariable va
if (CastSpellVariable.TryMatch(lm, term, out variable)) return true;
if (SlopeballVariable.TryMatch(lm, term, out variable)) return true;
if (ShriekPogoVariable.TryMatch(lm, term, out variable)) return true;
if (SoulStateManager.TryMatch(lm, term, out variable)) return true;
if (SpendSoulVariable.TryMatch(lm, term, out variable)) return true;
if (RegainSoulVariable.TryMatch(lm, term, out variable)) return true;
if (EquipCharmVariable.TryMatch(lm, term, out variable)) return true;
if (HotSpringResetVariable.TryMatch(lm, term, out variable)) return true;
if (ShadeStateVariable.TryMatch(lm, term, out variable)) return true;
if (TakeDamageVariable.TryMatch(lm, term, out variable)) return true;
if (HPStateManager.TryMatch(lm, term, out variable)) return true;
if (LifebloodCountVariable.TryMatch(lm, term, out variable)) return true;
if (StagStateVariable.TryMatch(lm, term, out variable)) return true;
if (FlowerProviderVariable.TryMatch(lm, term, out variable)) return true;
if (SaveQuitResetVariable.TryMatch(lm, term, out variable)) return true;
Expand Down
6 changes: 3 additions & 3 deletions RandomizerMod/RC/StateVariables/BenchResetVariable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public class BenchResetVariable : StateModifier
protected readonly List<StateBool> AnticharmBools;
protected readonly StateBool NoPassedCharmEquip;
protected readonly StateBool UsedShade;
protected readonly StateInt MaxRequiredSoul;
protected readonly StateInt RequiredMaxSoul;
// technically, MaxRequiredSoul should be reset at the start of the path leading to where a soul limit is applied. Since currently the only consumer is ShadeStateVariable, paths start at benches.
// we don't reset SoulLimiter, since the only current consumer is ShadeStateVariable, which sets and resets it already (and there is no natural reason to think benches should reset this)
protected readonly ISoulStateManager SSM;
Expand All @@ -34,7 +34,7 @@ public BenchResetVariable(string name, LogicManager lm)
AnticharmBools = lm.StateManager.Bools.Where(sb => sb.Name.StartsWith("noCHARM")).ToList();
NoPassedCharmEquip = lm.StateManager.GetBoolStrict("NOPASSEDCHARMEQUIP");
UsedShade = lm.StateManager.GetBoolStrict("USEDSHADE");
MaxRequiredSoul = lm.StateManager.GetIntStrict("MAXREQUIREDSOUL");
RequiredMaxSoul = lm.StateManager.GetIntStrict("REQUIREDMAXSOUL");
SSM = (ISoulStateManager)lm.GetVariableStrict(SoulStateManager.Prefix);
HPSM = (IHPStateManager)lm.GetVariableStrict(HPStateManager.Prefix);
SalubrasBlessing = lm.GetTermStrict("Salubra's_Blessing");
Expand Down Expand Up @@ -79,7 +79,7 @@ public override IEnumerable<LazyStateBuilder> ModifyState(object? sender, Progre
state.SetBool(sb, false);
}
if (pm.Has(SalubrasBlessing)) SSM.TryRestoreAllSoul(pm, ref state, true);
state.SetInt(MaxRequiredSoul, 0);
state.SetInt(RequiredMaxSoul, 0);
state.SetBool(NoPassedCharmEquip, false);
state.SetBool(UsedShade, false);
return HPSM.RestoreAllHealth(pm, state, true);
Expand Down
43 changes: 28 additions & 15 deletions RandomizerMod/RC/StateVariables/EquipCharmVariable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@ public class EquipCharmVariable : StateModifier
public override string Name { get; }
protected int CharmID;
protected Term CharmTerm;
protected StateBool AnticharmBool;
protected StateBool OvercharmBool;
protected readonly StateBool Overcharmed;
protected readonly StateBool CannotOvercharm;

protected readonly StateBool NoPassedCharmEquip;
protected readonly Term NotchesTerm;
protected readonly StateBool CharmBool;
protected readonly StateBool AnticharmBool;
protected readonly StateInt UsedNotchesInt;
protected readonly StateInt MaxNotchCost;

Expand All @@ -32,7 +33,8 @@ protected EquipCharmVariable(string name, LogicManager lm)
{
NotchesTerm = lm.GetTermStrict("NOTCHES");
NoPassedCharmEquip = lm.StateManager.GetBoolStrict("NOPASSEDCHARMEQUIP");
OvercharmBool = lm.StateManager.GetBoolStrict("OVERCHARMED");
Overcharmed = lm.StateManager.GetBoolStrict("OVERCHARMED");
CannotOvercharm = lm.StateManager.GetBoolStrict("CANNOTOVERCHARM");
UsedNotchesInt = lm.StateManager.GetIntStrict("USEDNOTCHES");
MaxNotchCost = lm.StateManager.GetIntStrict("MAXNOTCHCOST");
}
Expand Down Expand Up @@ -141,14 +143,14 @@ public EquipResult HasNotchRequirements<T>(ProgressionManager pm, T state) where
{
if (IsEquipped(state))
{
return state.GetBool(OvercharmBool) ? EquipResult.Overcharm : EquipResult.Nonovercharm; // Already equipped
return state.GetBool(Overcharmed) ? EquipResult.Overcharm : EquipResult.Nonovercharm; // Already equipped
}

int notchCost = GetNotchCost(pm, state);

if (notchCost <= 0)
{
return state.GetBool(OvercharmBool) ? EquipResult.Overcharm : EquipResult.Nonovercharm; // free to equip
return state.GetBool(Overcharmed) ? EquipResult.Overcharm : EquipResult.Nonovercharm; // free to equip
}

int netNotches = pm.Get(NotchesTerm) - state.GetInt(UsedNotchesInt) - notchCost;
Expand All @@ -160,7 +162,7 @@ public EquipResult HasNotchRequirements<T>(ProgressionManager pm, T state) where

int overcharmSave = Math.Max(state.GetInt(MaxNotchCost), notchCost);

if (netNotches + overcharmSave > 0)
if (netNotches + overcharmSave > 0 && !state.GetBool(CannotOvercharm))
{
return EquipResult.Overcharm; // charm is not 0 notches, so it requires an open notch to overcharm
}
Expand Down Expand Up @@ -246,7 +248,7 @@ protected virtual void DoEquipCharm(ProgressionManager pm, int notchCost, ref La
state.Increment(UsedNotchesInt, notchCost);
state.SetBool(CharmBool, true);
state.SetInt(MaxNotchCost, Math.Max(state.GetInt(MaxNotchCost), notchCost));
if (state.GetInt(UsedNotchesInt) > pm.Get(NotchesTerm)) state.SetBool(OvercharmBool, true);
if (state.GetInt(UsedNotchesInt) > pm.Get(NotchesTerm)) state.SetBool(Overcharmed, true);
}

public bool IsEquipped(LazyStateBuilder state) => state.GetBool(CharmBool);
Expand Down Expand Up @@ -279,17 +281,28 @@ public IEnumerable<LazyStateBuilder> DecideCharm(ProgressionManager pm, LazyStat

/// <summary>
/// Enumerates states for all equippable subsets of the provided set of charms.
/// <br/>Guarantees that <see cref="IsDetermined{T}(T)"/> returns true for all provided ECVs on all output states.
/// </summary>
/// <exception cref="ArgumentOutOfRangeException">Thrown if length of charm list is greater than 30.</exception>
public static IEnumerable<LazyStateBuilder> GenerateCharmCombinations(ProgressionManager pm, LazyStateBuilder state, IEnumerable<EquipCharmVariable> charmList)
{
EquipCharmVariable[] charms = charmList.Where(c =>
!c.IsDetermined(state)
&& c.HasCharmProgression(pm)
&& c.HasStateRequirements(pm, state)
&& c.HasNotchRequirements(pm, state) != EquipResult.None)
.ToArray();
int len = charms.Length;
List<EquipCharmVariable> charms = [];
foreach (EquipCharmVariable c in charmList)
{
if (!c.IsDetermined(state))
{
if (!c.HasCharmProgression(pm) || !c.HasStateRequirements(pm, state) || c.HasNotchRequirements(pm, state) == EquipResult.None)
{
c.SetUnequippable(ref state);
}
else
{
charms.Add(c);
}
}
}

int len = charms.Count;
if (len == 0)
{
yield return state;
Expand Down Expand Up @@ -320,7 +333,7 @@ public static IEnumerable<LazyStateBuilder> GenerateCharmCombinations(Progressio
}
}
yield return next;
end_of_outer_loop: continue;
end_of_outer_loop: continue; // failed to equip requested charms, so we discard next.
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion RandomizerMod/RC/StateVariables/FragileCharmVariable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public void BreakCharm(ProgressionManager pm, ref LazyStateBuilder state)
{
state.SetBool(CharmBool, false);
state.Increment(UsedNotchesInt, -((RandoModContext)pm.ctx).notchCosts[CharmID - 1]);
if (state.GetBool(OvercharmBool)) state.SetBool(OvercharmBool, false);
if (state.GetBool(Overcharmed)) state.SetBool(Overcharmed, false);
}
state.SetBool(AnticharmBool, true);
state.SetBool(BreakBool, true);
Expand Down
11 changes: 5 additions & 6 deletions RandomizerMod/RC/StateVariables/HPStateManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,8 @@ public override IEnumerable<Term> GetTerms()
{
yield return MaskShards;
yield return Focus;
foreach (ILogicVariable variable in Subvariables)
foreach (ILogicVariable variable in
(ILogicVariable[])[Hiveblood, LifebloodHeart, LifebloodCore, FragileHeart, JonisBlessing, DeepFocus, SSM])
{
foreach (Term t in variable.GetTerms()) yield return t;
}
Expand All @@ -120,20 +121,20 @@ public override IEnumerable<Term> GetTerms()
protected readonly EquipCharmVariable Hiveblood;
protected readonly EquipCharmVariable DeepFocus;
// not supported: grubsong
protected readonly SpendSoulVariable SpendSoul33;
protected EquipCharmVariable[] DetermineHPCharms;
protected EquipCharmVariable[] BeforeDeathCharms;
protected EquipCharmVariable[] FocusCharms;
protected readonly ILogicVariable[] Subvariables;
protected readonly LogicManager lm;
protected ISoulStateManager SSM { get => field ?? (ISoulStateManager)lm.GetVariableStrict(name: SoulStateManager.Prefix); set; } = null!;

public HPStateManager(string name, LogicManager lm)
{
Name = name;
this.lm = lm;
try
{
Overcharmed = lm.StateManager.GetBoolStrict("OVERCHARMED");
CannotOvercharm = lm.StateManager.GetBoolStrict("CANNOTOVERCHARM");
NoFlower = lm.StateManager.GetBoolStrict("NOFLOWER");
SpentHP = lm.StateManager.GetIntStrict("SPENTHP");
SpentBlueHP = lm.StateManager.GetIntStrict("SPENTBLUEHP");
Expand All @@ -146,12 +147,10 @@ public HPStateManager(string name, LogicManager lm)
FragileHeart = (EquipCharmVariable)lm.GetVariableStrict(EquipCharmVariable.GetName("Fragile_Heart"));
Hiveblood = (EquipCharmVariable)lm.GetVariableStrict(EquipCharmVariable.GetName("Hiveblood"));
DeepFocus = (EquipCharmVariable)lm.GetVariableStrict(EquipCharmVariable.GetName("Deep_Focus"));
SpendSoul33 = (SpendSoulVariable)lm.GetVariableStrict(SpendSoulVariable.Prefix + "[33]");

DetermineHPCharms = [Hiveblood, LifebloodHeart, LifebloodCore, FragileHeart, JonisBlessing];
FocusCharms = [DeepFocus, JonisBlessing];
BeforeDeathCharms = [Hiveblood, LifebloodHeart, LifebloodCore, FragileHeart, JonisBlessing, DeepFocus];
Subvariables = [Hiveblood, LifebloodHeart, LifebloodCore, FragileHeart, JonisBlessing, DeepFocus, SpendSoul33];
}
catch (Exception e)
{
Expand Down Expand Up @@ -281,7 +280,7 @@ public IEnumerable<LazyStateBuilder> DetermineHP(ProgressionManager pm, LazyStat

for (int i = 0; i < lazySpentHP; i++)
{
lsbs = lsbs.SelectMany(l => TakeDamage(pm, state, 1));
lsbs = lsbs.SelectMany(l => TakeDamage(pm, l, 1));
}
return lsbs;
}
Expand Down
61 changes: 61 additions & 0 deletions RandomizerMod/RC/StateVariables/LifebloodCountVariable.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using RandomizerCore.Logic;
using RandomizerCore.Logic.StateLogic;

namespace RandomizerMod.RC.StateVariables
{
/*
* Prefix: $LIFEBLOOD
* Required Parameters:
- If any parameters are provided, the first parameter must parse to int to give the required number of blue masks (including Joni masks).
If absent, defaults to 1.
* Optional Parameters: none
* Filters to states with determined hp which have at least a certain number of blue masks, including Joni masks.
*/
public class LifebloodCountVariable : StateModifier
{
public override string Name { get; }
public const string Prefix = "$LIFEBLOOD";

protected readonly int RequiredBlueMasks;
protected readonly IHPStateManager HPSM;
protected readonly EquipCharmVariable JonisBlessing;

public LifebloodCountVariable(string name, LogicManager lm, int requiredBlueMasks)
{
this.Name = name;
RequiredBlueMasks = requiredBlueMasks;
try
{
HPSM = (IHPStateManager)lm.GetVariableStrict(HPStateManager.Prefix);
JonisBlessing = (EquipCharmVariable)lm.GetVariableStrict(EquipCharmVariable.GetName("Joni's_Blessing"));
}
catch (Exception e)
{
throw new InvalidOperationException("Error constructing LifebloodCountVariable", e);
}
}

public override IEnumerable<Term> GetTerms()
{
return HPSM.GetTerms();
}

public static bool TryMatch(LogicManager lm, string term, out LogicVariable variable)
{
if (VariableResolver.TryMatchPrefix(term, Prefix, out string[] parameters))
{
int amount = parameters.Length == 0 ? 1 : int.Parse(parameters[0]);
variable = new LifebloodCountVariable(term, lm, amount);
return true;
}
variable = default;
return false;
}

public override IEnumerable<LazyStateBuilder> ModifyState(object? sender, ProgressionManager pm, LazyStateBuilder state)
{
return HPSM.DetermineHP(pm, state).Where(s => HPSM.GetHPInfo(pm, s) is IHPStateManager.StrictHPInfo hp
&& hp.CurrentBlueHP + (JonisBlessing.IsEquipped(s) ? hp.CurrentWhiteHP : 0) >= RequiredBlueMasks);
}
}
}
6 changes: 3 additions & 3 deletions RandomizerMod/RC/StateVariables/SaveQuitResetVariable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public class SaveQuitResetVariable : StateModifier

protected readonly StateBool NoFlower;
protected readonly StateBool UsedShade;
protected readonly StateInt MaxRequiredSoul;
protected readonly StateInt RequiredMaxSoul;
protected readonly ISoulStateManager SSM;
protected readonly IHPStateManager HPSM;

Expand All @@ -27,7 +27,7 @@ public SaveQuitResetVariable(string term, LogicManager lm)
{
NoFlower = lm.StateManager.GetBoolStrict("NOFLOWER");
UsedShade = lm.StateManager.GetBoolStrict("USEDSHADE");
MaxRequiredSoul = lm.StateManager.GetIntStrict("MAXREQUIREDSOUL");
RequiredMaxSoul = lm.StateManager.GetIntStrict("REQUIREDMAXSOUL");

SSM = (ISoulStateManager)lm.GetVariableStrict(SoulStateManager.Prefix);
HPSM = (IHPStateManager)lm.GetVariableStrict(HPStateManager.Prefix);
Expand Down Expand Up @@ -65,7 +65,7 @@ public override IEnumerable<LazyStateBuilder> ModifyState(object? sender, Progre
state.SetBool(NoFlower, true); // not game accurate, but we do this to prevent warps from being required for flower quest.
SSM.TrySpendAllSoul(pm, ref state); // zero out soul. A subsequent modifier will handle bench / start respawn soul effects.
state.SetBool(UsedShade, false); // not necessary to reset shade variables for typical use, but in the case of warping to a non-start hard respawn, it would be correct to reset them here.
state.SetInt(MaxRequiredSoul, 0);
state.SetInt(RequiredMaxSoul, 0);
return HPSM.RestoreAllHealth(pm, state, restoreBlueHealth: false);
}
}
Expand Down
15 changes: 12 additions & 3 deletions RandomizerMod/RC/StateVariables/SoulStateManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ public interface ISoulStateManager : ILogicVariable
public readonly record struct SoulInfo(int Soul, int MaxSoul, int ReserveSoul, int MaxReserveSoul);
}

internal class SoulStateManager : LogicVariable, ISoulStateManager
public class SoulStateManager : LogicVariable, ISoulStateManager
{
public override string Name { get; }
public const string Prefix = "$SSM";
Expand Down Expand Up @@ -199,6 +199,7 @@ public bool TrySpendAllSoul(ProgressionManager pm, ref LazyStateBuilder state)
{
SoulInfo info = GetSoulInfo(pm, state);
state.SetInt(SpentSoul, info.MaxSoul);
state.SetInt(MaxRequiredSoul, info.MaxSoul);
state.SetInt(SpentReserveSoul, info.MaxReserveSoul);
return true;
}
Expand Down Expand Up @@ -250,10 +251,18 @@ public bool TrySetSoulLimit(ProgressionManager pm, ref LazyStateBuilder state, i
{
if (appliesToPriorPath && state.GetInt(MaxRequiredSoul) > limiter) return false;

if (limiter > state.GetInt(SoulLimiter))
int current = state.GetInt(SoulLimiter);

if (limiter > current)
{
state.SetInt(SoulLimiter, limiter);
}
else if (limiter < current)
{
state.SetInt(SoulLimiter, limiter);
TrySpendSoul(pm, ref state, current - limiter);
}

return true;
}

Expand All @@ -269,7 +278,7 @@ public IEnumerable<LazyStateBuilder> LimitSoul(ProgressionManager pm, LazyStateB
protected void SpendWithoutRebalance(int amount, ref SoulInfo soul, ref LazyStateBuilder state)
{
state.Increment(SpentSoul, amount);
if (amount > state.GetInt(MaxRequiredSoul)) state.SetInt(MaxRequiredSoul, amount);
state.TrySetIntToValue(MaxRequiredSoul, state.GetInt(SpentSoul));
soul = soul with { Soul = soul.Soul - amount };
}

Expand Down
1 change: 1 addition & 0 deletions RandomizerMod/Resources/Logic/macros.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
"KINGSOUL": "$EQUIPPEDCHARM[Kingsoul]",
"VOIDHEART": "$EQUIPPEDCHARM[Void_Heart]",
"DASHSPRINT": "$EQUIPPEDCHARM[Dashmaster] + $EQUIPPEDCHARM[Sprintmaster]",
// deprecated, see LifebloodCountVariable for definition of $LIFEBLOOD
"LIFEBLOOD": "$EQUIPPEDCHARM[Lifeblood_Heart] | $EQUIPPEDCHARM[Lifeblood_Core] | $EQUIPPEDCHARM[Joni's_Blessing]",

// macros for simple comparisons
Expand Down
1 change: 1 addition & 0 deletions RandomizerMod/Resources/Logic/state.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"Bool": [
"USEDSHADE",
"OVERCHARMED",
"CANNOTOVERCHARM",
"CANNOTREGAINSOUL",
"CANNOTSHADESKIP",
"BROKEHEART",
Expand Down
2 changes: 1 addition & 1 deletion RandomizerMod/Resources/Logic/transitions.json
Original file line number Diff line number Diff line change
Expand Up @@ -3516,7 +3516,7 @@
{
"sceneName": "Abyss_06_Core",
"gateName": "left1",
"logic": "Abyss_06_Core[left1] | Warp-Lifeblood_Core_to_Abyss | (Abyss_06_Core[top1] + BRAND | Abyss_06_Core + (LEFTCLAW + (PRECISEMOVEMENT | RIGHTCLAW) + FULLDASH | WINGS)) + LIFEBLOOD",
"logic": "Abyss_06_Core[left1] | Warp-Lifeblood_Core_to_Abyss | (Abyss_06_Core[top1] + BRAND | Abyss_06_Core + (LEFTCLAW + (PRECISEMOVEMENT | RIGHTCLAW) + FULLDASH | WINGS)) + $LIFEBLOOD",
"oneWayType": "TwoWay",
"Name": "Abyss_06_Core[left1]"
},
Expand Down
Loading

0 comments on commit 96a1164

Please sign in to comment.