diff --git a/laevis/CHANGELOG.md b/laevis/CHANGELOG.md index e96d2b6..f011538 100644 --- a/laevis/CHANGELOG.md +++ b/laevis/CHANGELOG.md @@ -7,6 +7,9 @@ - Change: Explosive Death's range now increases more slowly with levels, and is based on the radius of the exploding monster; bigger enemies produce bigger booms. - Change: split the menu code into a separate library, libtooltipmenu.pk3 +- Change: you can now pick between 4 upgrades when you gain a level +- Fix: upgrade generation can no longer take unbounded time if you're unlucky +- Fix: upgrade generation can no longer freeze the game if the pool of upgrade candidates is very small - Fix: Added some missing sprites to the repo (they were still in the pk3 but not versioned) # 0.6.4 diff --git a/laevis/ca.ancilla.laevis/PlayerUpgradeGiver.zs b/laevis/ca.ancilla.laevis/PlayerUpgradeGiver.zs index 4c2fb62..7305cfd 100644 --- a/laevis/ca.ancilla.laevis/PlayerUpgradeGiver.zs +++ b/laevis/ca.ancilla.laevis/PlayerUpgradeGiver.zs @@ -5,17 +5,9 @@ class ::PlayerUpgradeGiver : ::UpgradeGiver { ::PerPlayerStats stats; - // TODO: we might want to force the first upgrade to always be a damage bonus - // or some other simple, generally useful upgrade. override void CreateUpgradeCandidates() { - while (candidates.size() < 3) { - let upgrade = ::Upgrade::Registry.GenerateUpgradeForPlayer(stats); - if (!AlreadyHasUpgrade(upgrade)) { - candidates.push(upgrade); - } else { - upgrade.Destroy(); - } - } + candidates.clear(); + ::Upgrade::Registry.GenerateUpgradesForPlayer(stats, candidates); } void InstallUpgrade(int index) { @@ -23,7 +15,7 @@ class ::PlayerUpgradeGiver : ::UpgradeGiver { console.printf("Level-up rejected!"); } else { console.printf("You gained a level of %s!", candidates[index].GetName()); - stats.upgrades.AddUpgrade(candidates[index]); + stats.upgrades.Add(candidates[index].GetClassName()); } Destroy(); } diff --git a/laevis/ca.ancilla.laevis/WeaponUpgradeGiver.zs b/laevis/ca.ancilla.laevis/WeaponUpgradeGiver.zs index 4925f34..97ef421 100644 --- a/laevis/ca.ancilla.laevis/WeaponUpgradeGiver.zs +++ b/laevis/ca.ancilla.laevis/WeaponUpgradeGiver.zs @@ -6,16 +6,8 @@ class ::WeaponUpgradeGiver : ::UpgradeGiver { TFLV_WeaponInfo wielded; override void CreateUpgradeCandidates() { - // TODO: properly handle the case where the number of valid upgrades is - // less than the number we want to display. - while (candidates.size() < 3) { - let upgrade = ::Upgrade::Registry.GenerateUpgradeForWeapon(wielded); - if (!AlreadyHasUpgrade(upgrade)) { - candidates.push(upgrade); - } else { - upgrade.Destroy(); - } - } + candidates.clear(); + ::Upgrade::Registry.GenerateUpgradesForWeapon(wielded, candidates); } void InstallUpgrade(int index) { @@ -24,7 +16,7 @@ class ::WeaponUpgradeGiver : ::UpgradeGiver { } else { console.printf("Your %s gained a level of %s!", wielded.weapon.GetTag(), candidates[index].GetName()); - wielded.upgrades.AddUpgrade(candidates[index]); + wielded.upgrades.Add(candidates[index].GetClassName()); } Destroy(); } diff --git a/laevis/ca.ancilla.laevis/upgrades/Registry.zs b/laevis/ca.ancilla.laevis/upgrades/Registry.zs index fa4ce9c..f1c84ce 100644 --- a/laevis/ca.ancilla.laevis/upgrades/Registry.zs +++ b/laevis/ca.ancilla.laevis/upgrades/Registry.zs @@ -1,8 +1,11 @@ #namespace TFLV::Upgrade; #debug off +const UPGRADES_PER_LEVEL = 4; + class ::Registry : Object play { - array<string> upgrades; + array<string> upgrade_names; + array<::BaseUpgrade> upgrades; static ::Registry GetRegistry() { let reg = TFLV::EventHandler(StaticEventHandler.Find("TFLV::EventHandler")); @@ -13,13 +16,14 @@ class ::Registry : Object play { static void Register(string upgrade) { DEBUG("Register: %s", upgrade); - if (GetRegistry().upgrades.find(upgrade) != GetRegistry().upgrades.size()) { + if (GetRegistry().upgrade_names.find(upgrade) != GetRegistry().upgrade_names.size()) { // Assume that this is because a mod has tried to double-register an upgrade, // and permit it as a no-op. //ThrowAbortException("Duplicate upgrades named %s", upgrade); return; } - GetRegistry().upgrades.push(upgrade); + GetRegistry().upgrade_names.push(upgrade); + GetRegistry().upgrades.push(::BaseUpgrade(new(upgrade))); } // Can't be static because we need to call it during eventmanager initialization, @@ -63,35 +67,40 @@ class ::Registry : Object play { "::Embrittlement" }; for (uint i = 0; i < UpgradeNames.size(); ++i) { - upgrades.push(UpgradeNames[i]); + upgrade_names.push(UpgradeNames[i]); + upgrades.push(::BaseUpgrade(new(UpgradeNames[i]))); } } - static ::BaseUpgrade GenerateUpgrade() { - let cls = GetRegistry().upgrades[random(0, GetRegistry().upgrades.size()-1)]; - DEBUG("GenerateUpgrade(%s)", cls); - return ::BaseUpgrade(new(cls)); + static void PickN(Array<::BaseUpgrade> dst, Array<::BaseUpgrade> src, uint n) { + uint max = src.size(); + while (max > 0 && dst.size() < n) { + uint i = random(0, max-1); + dst.push(src[i]); + src[i] = src[--max]; + } } - static ::BaseUpgrade GenerateUpgradeForPlayer(TFLV::PerPlayerStats stats) { - ::BaseUpgrade upgrade = null; - while (upgrade == null) { - upgrade = ::Registry.GenerateUpgrade(); - if (upgrade.IsSuitableForPlayer(stats)) return upgrade; - upgrade.Destroy(); - upgrade = null; + static void GenerateUpgradesForPlayer( + TFLV::PerPlayerStats stats, Array<::BaseUpgrade> generated) { + Array<::BaseUpgrade> candidates; + // Array<::BaseUpgrade> all_upgrades = GetRegistry().upgrades; + for (uint i = 0; i < GetRegistry().upgrades.size(); ++i) { + if (GetRegistry().upgrades[i].IsSuitableForPlayer(stats)) + candidates.push(GetRegistry().upgrades[i]); } - return null; // unreachable + + PickN(generated, candidates, UPGRADES_PER_LEVEL); } - static ::BaseUpgrade GenerateUpgradeForWeapon(TFLV::WeaponInfo info) { - ::BaseUpgrade upgrade = null; - while (upgrade == null) { - upgrade = ::Registry.GenerateUpgrade(); - if (upgrade.IsSuitableForWeapon(info)) return upgrade; - upgrade.Destroy(); - upgrade = null; + static void GenerateUpgradesForWeapon( + TFLV::WeaponInfo info, Array<::BaseUpgrade> generated) { + array<::BaseUpgrade> candidates; + for (uint i = 0; i < GetRegistry().upgrades.size(); ++i) { + if (GetRegistry().upgrades[i].IsSuitableForWeapon(info)) + candidates.push(GetRegistry().upgrades[i]); } - return null; // unreachable + + PickN(generated, candidates, UPGRADES_PER_LEVEL); } }