Implement animations, pipeline for duplicates and merging.
This commit is contained in:
@@ -43,8 +43,8 @@ namespace KernelPanic.Combat
|
||||
continue;
|
||||
}
|
||||
|
||||
int minAmount = UpgradeMath.ScaleAmount(_minAmount, context.Card);
|
||||
int maxAmount = UpgradeMath.ScaleAmount(_maxAmount, context.Card);
|
||||
int minAmount = UpgradeMath.ScaleAmount(UpgradeMath.ApplySourceFlatBonus(_minAmount, context), context.Card);
|
||||
int maxAmount = UpgradeMath.ScaleAmount(UpgradeMath.ApplySourceFlatBonus(_maxAmount, context), context.Card);
|
||||
int amount = minAmount == maxAmount
|
||||
? minAmount
|
||||
: RandomRoll.RollRange(minAmount, maxAmount, new RollContext(context.Source));
|
||||
@@ -77,7 +77,7 @@ namespace KernelPanic.Combat
|
||||
|
||||
public void Execute(CombatContext context)
|
||||
{
|
||||
int amount = UpgradeMath.ScaleShield(_amount, context.Card);
|
||||
int amount = UpgradeMath.ScaleShield(UpgradeMath.ApplySourceFlatBonus(_amount, context), context.Card);
|
||||
context.Source.Shield += amount;
|
||||
context.CombatManager.ReportEffectResult($"gained {amount} shield");
|
||||
}
|
||||
@@ -107,7 +107,7 @@ namespace KernelPanic.Combat
|
||||
}
|
||||
|
||||
int roll = RandomRoll.RollRange(1, 100, new RollContext(context.Source));
|
||||
int damageAmount = UpgradeMath.ScaleAmount(_damageAmount, context.Card);
|
||||
int damageAmount = UpgradeMath.ScaleAmount(UpgradeMath.ApplySourceFlatBonus(_damageAmount, context), context.Card);
|
||||
int amount = roll <= _successPercent ? damageAmount : 0;
|
||||
context.DamagePipeline.DealDamage(new DamageRequest(context.Source, target, amount, _language, false, false));
|
||||
context.CombatManager.ReportEffectResult(amount == 0 ? "typeof -> NaN (0)" : $"typeof -> {amount}");
|
||||
@@ -136,13 +136,13 @@ namespace KernelPanic.Combat
|
||||
{
|
||||
if (_targetSelf)
|
||||
{
|
||||
context.StatusEffects.Apply(context.Source, _statusType, UpgradeMath.ScaleAmount(_stacks, context.Card), _duration, context.Source, _skipNextTick);
|
||||
context.StatusEffects.Apply(context.Source, _statusType, UpgradeMath.ScaleAmount(UpgradeMath.ApplySourceFlatBonus(_stacks, context), context.Card), _duration, context.Source, _skipNextTick);
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < context.Targets.Count; i++)
|
||||
{
|
||||
context.StatusEffects.Apply(context.Targets[i], _statusType, UpgradeMath.ScaleAmount(_stacks, context.Card), _duration, context.Source, _skipNextTick);
|
||||
context.StatusEffects.Apply(context.Targets[i], _statusType, UpgradeMath.ScaleAmount(UpgradeMath.ApplySourceFlatBonus(_stacks, context), context.Card), _duration, context.Source, _skipNextTick);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -184,7 +184,15 @@ namespace KernelPanic.Combat
|
||||
}
|
||||
|
||||
// RAM is a hard hand cap. Draw effects only pull cards that can fit; blocked cards stay in the draw pile.
|
||||
int requested = UpgradeMath.ScaleAmount(_count, context.Card);
|
||||
int baseCount = _count;
|
||||
if (context.Card?.Definition?.Id == "ubuntu_ask_ubuntu"
|
||||
&& string.Equals(context.CombatManager.RunConfig?.Distro?.Id, "ubuntu", System.StringComparison.OrdinalIgnoreCase)
|
||||
&& context.CombatManager.RunConfig.DistroVersion >= 3)
|
||||
{
|
||||
baseCount = 3;
|
||||
}
|
||||
|
||||
int requested = UpgradeMath.ScaleAmount(UpgradeMath.ApplySourceFlatBonus(baseCount, context), context.Card);
|
||||
int drawCount = UnityEngine.Mathf.Min(requested, room);
|
||||
IReadOnlyList<CardInstance> drawn = context.DeckController.Draw(drawCount);
|
||||
int added = 0;
|
||||
@@ -214,8 +222,8 @@ namespace KernelPanic.Combat
|
||||
|
||||
public void Execute(CombatContext context)
|
||||
{
|
||||
int minCount = UpgradeMath.ScaleAmount(_minCount, context.Card);
|
||||
int maxCount = UpgradeMath.ScaleAmount(_maxCount, context.Card);
|
||||
int minCount = UpgradeMath.ScaleAmount(UpgradeMath.ApplySourceFlatBonus(_minCount, context), context.Card);
|
||||
int maxCount = UpgradeMath.ScaleAmount(UpgradeMath.ApplySourceFlatBonus(_maxCount, context), context.Card);
|
||||
int targetCount = minCount == maxCount
|
||||
? minCount
|
||||
: RandomRoll.RollRange(minCount, maxCount, new RollContext(context.Source));
|
||||
@@ -347,11 +355,11 @@ namespace KernelPanic.Combat
|
||||
"mint_update_manager" => Todo("TODO: update manager needs delayed repeat system."),
|
||||
"mint_cinnamon" => Todo("TODO: cinnamon needs shield effect support."),
|
||||
"mint_nemo" => Todo("TODO: nemo needs conditional repeat based on shield."),
|
||||
"mint_timeshift" => Todo("TODO: timeshift snapshot needs delayed restore system."),
|
||||
"mint_timeshift" => Todo("TODO: timeshift snapshot V3 needs delayed restore system before threshold/restore can be upgraded."),
|
||||
"fedora_borrow_checker" => Todo("TODO: borrow checker needs shield and first-card tracking."),
|
||||
"fedora_cargo_build" => Todo("TODO: cargo build --release needs overkill-to-shield handling."),
|
||||
"fedora_dnf_update" => Todo("TODO: dnf update needs Java played count and cost mutation."),
|
||||
"fedora_rawhide" => Todo("TODO: rawhide needs next-card cost mutation and crash-risk hook."),
|
||||
"fedora_rawhide" => Todo("TODO: rawhide V3 needs permanent damage-growth doubling after growth effects exist."),
|
||||
"fedora_selinux" => Todo("TODO: SELinux enforcing needs enemy attack mitigation statuses."),
|
||||
_ => Todo("TODO: card effect is not implemented yet.")
|
||||
};
|
||||
@@ -429,6 +437,7 @@ namespace KernelPanic.Combat
|
||||
_ => string.IsNullOrWhiteSpace(card.Definition.Description) ? "TODO: effect not implemented." : $"{card.Definition.Description}{marker}"
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class UpgradeMath
|
||||
@@ -443,5 +452,9 @@ namespace KernelPanic.Combat
|
||||
return baseAmount + UnityEngine.Mathf.Max(0, card?.MagnitudeBonus ?? 0);
|
||||
}
|
||||
|
||||
public static int ApplySourceFlatBonus(int amount, CombatContext context)
|
||||
{
|
||||
return amount + UnityEngine.Mathf.Max(0, context?.Source?.FlatEffectBonus ?? 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using KernelPanic.Core;
|
||||
using KernelPanic.Data;
|
||||
using KernelPanic.Run;
|
||||
using KernelPanic.Meta;
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -33,6 +34,9 @@ namespace KernelPanic.Combat
|
||||
private bool awaitingWaveContinue;
|
||||
private bool runLost;
|
||||
private bool skipNextAllocateDraw;
|
||||
private bool ubuntuEmptyHandRefillUsed;
|
||||
private int fedoraCardsDiscountedThisTurn;
|
||||
private int fedoraCrashChance = 10;
|
||||
|
||||
public TurnPhase CurrentPhase => currentPhase;
|
||||
public RunConfig RunConfig => runConfig;
|
||||
@@ -141,7 +145,13 @@ namespace KernelPanic.Combat
|
||||
return false;
|
||||
}
|
||||
|
||||
bool fedoraBonus = CanApplyFedoraBonus();
|
||||
int cost = GetCardCost(card);
|
||||
if (fedoraBonus)
|
||||
{
|
||||
cost = Mathf.Max(0, cost - 1);
|
||||
}
|
||||
|
||||
if (playerState.Cycles < cost)
|
||||
{
|
||||
Log($"not enough cycles for {GetCardName(card)}");
|
||||
@@ -167,6 +177,29 @@ namespace KernelPanic.Combat
|
||||
LogPlayIntent(card);
|
||||
card.SetTargetSnapshot(CaptureTargets(card));
|
||||
|
||||
if (fedoraBonus)
|
||||
{
|
||||
fedoraCardsDiscountedThisTurn++;
|
||||
if (RandomRoll.RollRange(1, 100, new RollContext(playerState)) <= fedoraCrashChance)
|
||||
{
|
||||
fedoraCrashChance = 10;
|
||||
if (IsDistro("fedora") && runConfig.DistroVersion >= 4)
|
||||
{
|
||||
playerState.Cycles += 1;
|
||||
}
|
||||
|
||||
deckController.Discard(card);
|
||||
pendingTargetCard = null;
|
||||
selectedEnemyIndex = -1;
|
||||
Log($"{GetCardName(card)} crashed under bleeding edge");
|
||||
StateChanged?.Invoke();
|
||||
return true;
|
||||
}
|
||||
|
||||
fedoraCrashChance = Mathf.Min(90, fedoraCrashChance + 10);
|
||||
playerState.DamageMultiplierPercent = runConfig.DistroVersion >= 2 ? 175 : 150;
|
||||
}
|
||||
|
||||
switch (track)
|
||||
{
|
||||
case ResolutionTrack.InterpreterQueue:
|
||||
@@ -184,7 +217,10 @@ namespace KernelPanic.Combat
|
||||
break;
|
||||
}
|
||||
|
||||
playerState.DamageMultiplierPercent = 100;
|
||||
|
||||
pendingTargetCard = null;
|
||||
TryApplyUbuntuEmptyHandRefill();
|
||||
StateChanged?.Invoke();
|
||||
return true;
|
||||
}
|
||||
@@ -355,8 +391,12 @@ namespace KernelPanic.Combat
|
||||
runLost = false;
|
||||
awaitingWaveContinue = false;
|
||||
loggedEffectTodos.Clear();
|
||||
ubuntuEmptyHandRefillUsed = false;
|
||||
fedoraCrashChance = 10;
|
||||
fedoraCardsDiscountedThisTurn = 0;
|
||||
RandomRoll.Seed(runConfig.RunSeed);
|
||||
playerState = new CombatantState(runManager.EffectiveMaxUptime(), runManager.EffectiveRam(), runManager.EffectiveMaxCycles());
|
||||
ApplyVersionState(playerState);
|
||||
StartWave(preservePlayerUptime: false);
|
||||
Log($"booted {runConfig.Distro.DisplayName} with {runManager.RunDeck.Count} cards");
|
||||
StateChanged?.Invoke();
|
||||
@@ -368,6 +408,7 @@ namespace KernelPanic.Combat
|
||||
if (!preservePlayerUptime || playerState == null)
|
||||
{
|
||||
playerState = new CombatantState(runManager.EffectiveMaxUptime(), runManager.EffectiveRam(), runManager.EffectiveMaxCycles());
|
||||
ApplyVersionState(playerState);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -379,6 +420,7 @@ namespace KernelPanic.Combat
|
||||
playerState.Shield = 0;
|
||||
playerState.IsDefeated = false;
|
||||
playerState.MutableStatuses.Clear();
|
||||
ApplyVersionState(playerState);
|
||||
}
|
||||
|
||||
handController = new HandController(playerState.Ram);
|
||||
@@ -413,12 +455,15 @@ namespace KernelPanic.Combat
|
||||
}
|
||||
|
||||
playerState.Cycles = playerState.MaxCycles;
|
||||
fedoraCardsDiscountedThisTurn = 0;
|
||||
statusEffects.Tick(playerState, StatusTickTiming.StartOfTurn, playerState, damagePipeline);
|
||||
if (CheckLoss())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TryApplyUbuntuAptUpdate();
|
||||
|
||||
if (skipNextAllocateDraw)
|
||||
{
|
||||
skipNextAllocateDraw = false;
|
||||
@@ -557,6 +602,71 @@ namespace KernelPanic.Combat
|
||||
}
|
||||
}
|
||||
|
||||
private void TryApplyUbuntuAptUpdate()
|
||||
{
|
||||
if (!IsDistro("ubuntu") || handController == null || playerState == null || handController.Cards.Count >= handController.RamCapacity)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int lookCount = runConfig.DistroVersion >= 2 ? 3 : 2;
|
||||
if (!deckController.TryDrawCheapestFromTop(lookCount, out CardInstance card))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (runConfig.DistroVersion >= 4)
|
||||
{
|
||||
card.TemporaryCostDelta -= 1;
|
||||
}
|
||||
|
||||
if (handController.Add(card))
|
||||
{
|
||||
Log($"apt update: staged {GetCardName(card)} from top {lookCount}");
|
||||
}
|
||||
}
|
||||
|
||||
private void TryApplyUbuntuEmptyHandRefill()
|
||||
{
|
||||
if (!IsDistro("ubuntu") || runConfig.DistroVersion < 5 || ubuntuEmptyHandRefillUsed || handController == null || handController.Cards.Count > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ubuntuEmptyHandRefillUsed = true;
|
||||
DrawCardsToHand(playerState.Ram, "ubuntu 24.04 refill");
|
||||
}
|
||||
|
||||
private bool CanApplyFedoraBonus()
|
||||
{
|
||||
if (!IsDistro("fedora") || playerState == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int limit = runConfig.DistroVersion >= 5 ? 2 : 1;
|
||||
return fedoraCardsDiscountedThisTurn < limit;
|
||||
}
|
||||
|
||||
private void ApplyVersionState(CombatantState state)
|
||||
{
|
||||
if (state == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
state.forceMaxRolls = IsDistro("mint");
|
||||
state.IgnoreDamageMultipliers = IsDistro("mint");
|
||||
state.AllowFlatDamageBuffs = IsDistro("mint") && runConfig.DistroVersion >= 4;
|
||||
state.FlatEffectBonus = IsDistro("mint") && runConfig.DistroVersion >= 2 ? 2 : 0;
|
||||
state.DamageMultiplierPercent = 100;
|
||||
}
|
||||
|
||||
private bool IsDistro(string id)
|
||||
{
|
||||
return string.Equals(runConfig?.Distro?.Id, id, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private CombatContext BuildContext(CardInstance card, IReadOnlyList<CombatantState> targets)
|
||||
{
|
||||
return new CombatContext(card, playerState, targets, this, damagePipeline, statusEffects, deckController, handController, enemies);
|
||||
|
||||
@@ -27,6 +27,10 @@ namespace KernelPanic.Combat
|
||||
public int MaxCycles { get; set; }
|
||||
public bool IsDefeated { get; set; }
|
||||
public bool forceMaxRolls { get; set; }
|
||||
public bool IgnoreDamageMultipliers { get; set; }
|
||||
public bool AllowFlatDamageBuffs { get; set; }
|
||||
public int FlatEffectBonus { get; set; }
|
||||
public int DamageMultiplierPercent { get; set; } = 100;
|
||||
public IReadOnlyList<StatusInstance> Statuses => _statuses;
|
||||
public IReadOnlyDictionary<StatusType, int> ActiveStatuses => BuildStatusSnapshot();
|
||||
|
||||
|
||||
@@ -74,18 +74,32 @@ namespace KernelPanic.Combat
|
||||
|
||||
private static int ApplyMultipliers(int amount, DamageRequest request)
|
||||
{
|
||||
// TODO: Apply buffs/debuffs and Mint's no-multiplier rule here.
|
||||
return amount;
|
||||
if (request.Source != null && request.Source.IgnoreDamageMultipliers)
|
||||
{
|
||||
// Mint V1+ ignores multiplicative damage modifiers. Mint V4 still allows flat
|
||||
// bonuses before this step; no flat buff status exists yet, so the flag is a hook.
|
||||
return amount;
|
||||
}
|
||||
|
||||
int multiplier = request.Source == null ? 100 : Mathf.Max(0, request.Source.DamageMultiplierPercent);
|
||||
return Mathf.RoundToInt(amount * (multiplier / 100f));
|
||||
}
|
||||
|
||||
private static int ApplyCrit(int amount, DamageRequest request)
|
||||
{
|
||||
if (request.Source != null && request.Source.forceMaxRolls)
|
||||
{
|
||||
return amount;
|
||||
}
|
||||
|
||||
// TODO: Add crit calculation and crit modifiers.
|
||||
return amount;
|
||||
}
|
||||
|
||||
private static int ApplyResistancesAndWeaknesses(int amount, DamageRequest request)
|
||||
{
|
||||
// TODO: Enemy resistances/reductions are not authored yet. Mint V5 should re-resolve
|
||||
// a once-per-combat reduced effect here once those reduction sources exist.
|
||||
// TODO: Apply enemy language resistances and weaknesses here.
|
||||
return amount;
|
||||
}
|
||||
|
||||
@@ -55,6 +55,43 @@ namespace KernelPanic.Combat
|
||||
return drawn;
|
||||
}
|
||||
|
||||
public bool TryDrawCheapestFromTop(int lookCount, out CardInstance card)
|
||||
{
|
||||
card = null;
|
||||
if (lookCount <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_drawPile.Count == 0)
|
||||
{
|
||||
ReshuffleDiscardIntoDraw();
|
||||
}
|
||||
|
||||
if (_drawPile.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int inspectCount = System.Math.Min(lookCount, _drawPile.Count);
|
||||
int selectedIndex = _drawPile.Count - 1;
|
||||
int selectedCost = CombatManager.GetCardCost(_drawPile[selectedIndex]);
|
||||
for (int offset = 1; offset < inspectCount; offset++)
|
||||
{
|
||||
int index = _drawPile.Count - 1 - offset;
|
||||
int cost = CombatManager.GetCardCost(_drawPile[index]);
|
||||
if (cost < selectedCost)
|
||||
{
|
||||
selectedCost = cost;
|
||||
selectedIndex = index;
|
||||
}
|
||||
}
|
||||
|
||||
card = _drawPile[selectedIndex];
|
||||
_drawPile.RemoveAt(selectedIndex);
|
||||
return card != null;
|
||||
}
|
||||
|
||||
public void Discard(CardInstance card)
|
||||
{
|
||||
if (card != null)
|
||||
|
||||
Reference in New Issue
Block a user