From 87dec96ee8224badce198acfb61cc8fe47fa51aa Mon Sep 17 00:00:00 2001
From: Verdant Purple <239105017+VerdantPurple@users.noreply.github.com>
Date: Wed, 22 Oct 2025 18:09:28 -0700
Subject: [PATCH] implement simulation mode, plus minor tweaks
block BCZ/crepe paper skills, and april shower-dependent effects.
improve description messages. fix meat cost calculation per item.
increase max cast/use limit.
---
Release/scripts/gain.ash | 175 +++++++++++++++++++++++++++++++++++----
1 file changed, 159 insertions(+), 16 deletions(-)
diff --git a/Release/scripts/gain.ash b/Release/scripts/gain.ash
index e0ba415..f8ad8e2 100644
--- a/Release/scripts/gain.ash
+++ b/Release/scripts/gain.ash
@@ -138,8 +138,14 @@ int __maximum_meat_to_spend = 100000;
boolean __setting_silent = false;
boolean __setting_ignore_percentages = false;
boolean __setting_allow_limited_buffs = false;
+
+boolean __setting_simulation_only = false;
+float[string] __simulation_modifier_bonus;
+buffer __simulation_output;
+
int __starting_meat = -1;
int __meat_spent = 0;
+
if (my_class() == $class[turtle tamer])
{
foreach s in $skills[Blessing of the Storm Tortoise,Blessing of She-Who-Was,Blessing of the War Snapper]
@@ -152,9 +158,25 @@ else if (my_class() == $class[pastamancer])
}
foreach sk in $skills[Aug. 6th: Fresh Breath Day!, Aug. 7th: Lighthouse Day!, Cincho: Party Soundtrack] {
- __modify_blocked_skills[sk] = true;
+ __modify_blocked_skills[sk] = true;
}
+
+boolean haveEquipment(item equipment) {
+ return item_amount(equipment) > 0 || have_equipped(equipment);
+}
+
+void initializeRequiredEquipment() {
+ if (!haveEquipment($item[April Shower Thoughts Shield])) {
+ // special effects added to normal skills we may still want to use
+ foreach e in $effects[Slippery as a Seal,Strength of the Tortoise,Tubes of Universal Meat,Lubricating Sauce,Disco over Matter,Mariachi Moisture] {
+ __blocked_effects[e] = true;
+ }
+ }
+}
+initializeRequiredEquipment();
+
+
boolean [effect] __limited_effects;
__limited_effects[to_effect("Blessing of your favorite Bird")] = true;
__limited_effects[to_effect("Blessing of the Bird")] = true;
@@ -265,6 +287,18 @@ void blockLimitedBuffs()
if (s == $skill[none]) continue;
__modify_blocked_skills[s] = true;
}
+
+ // Everything looks beige
+ foreach s in $skills[Pull down your crepe paper phrygian cap,Look through your crepe paper pie clip,Play with your crepe paper puzzle,Embrace polka]
+ {
+ __modify_blocked_skills[s] = true;
+ }
+
+ // How to value scaling stat costs?
+ foreach s in $skills[BCZ: Blood Bath,BCZ: Dial it up to 11,BCZ: Sweat Equity]
+ {
+ __modify_blocked_skills[s] = true;
+ }
}
@@ -295,7 +329,7 @@ Record ModifierUpkeepEntry
int turns_gotten_from_source;
};
-string ModifierUpkeepEntryDescription(ModifierUpkeepEntry entry)
+string ModifierUpkeepEntryDescription(ModifierUpkeepEntry entry, string modifier_name)
{
buffer out;
if (entry.s != $skill[none])
@@ -310,6 +344,12 @@ string ModifierUpkeepEntryDescription(ModifierUpkeepEntry entry)
out.append(entry.turns_gotten_from_source);
out.append(" turns of " );
out.append(entry.e);
+ float modifier_value = entry.e.numeric_modifier_including_percentages_on_base_modifiers(modifier_name);
+ if (modifier_value > 0)
+ out.append(", +");
+ else
+ out.append(", ");
+ out.append(modifier_value + " " + modifier_name);
return out;
}
@@ -343,6 +383,70 @@ float ModifierUpkeepEntryEfficiency(ModifierUpkeepEntry entry, ModifierUpkeepSet
return cost / combined;
}
+string[int] GetAllModifiers(effect e) {
+ string[int] modifier_names;
+ string[int] modifiers = e.string_modifier("modifiers").split_string(", ");
+ foreach _, full_modifier in modifiers {
+ string modifier_name = full_modifier.split_string(": ")[0];
+ modifier_names[modifier_names.count()] = modifier_name.to_lower_case();
+ }
+ return modifier_names;
+}
+
+boolean isBetween(float value, float low, float high) {
+ return (low < value && value < high);
+}
+
+void SimulateBuff(string modifier_name, float value)
+{
+ float value_to_add = value;
+
+ if (modifier_name == "combat rate") {
+ float current_value = numeric_modifier(modifier_name) + __simulation_modifier_bonus[modifier_name];
+ int value_remaining = value;
+ value_to_add = 0;
+
+ int increment = 1;
+ if (value < 0)
+ increment = -1;
+
+ // soft cap
+ while ((current_value + value_to_add).isBetween(-25, 25)) {
+ if (value_remaining == 0) {
+ break;
+ }
+ value_remaining -= increment;
+ value_to_add += increment;
+ }
+ // hard cap
+ while ((current_value + value_to_add).isBetween(-35, 35)) {
+ if (value_remaining == 0) {
+ break;
+ }
+ value_remaining -= increment;
+ value_to_add += (increment.to_float() / 5);
+ }
+ }
+
+ __simulation_modifier_bonus[modifier_name] += value_to_add;
+}
+
+void SimulateModifierUpkeep(ModifierUpkeepEntry entry, ModifierUpkeepSettings settings, int qty)
+{
+
+ if (have_effect(entry.e) == 0) {
+ foreach _, modifier_name in GetAllModifiers(entry.e) {
+ float value = entry.e.numeric_modifier_including_percentages_on_base_modifiers(modifier_name);
+ SimulateBuff(modifier_name, value);
+ }
+ }
+
+ if (entry.type == MODIFIER_UPKEEP_ENTRY_TYPE_ITEM)
+ __simulation_output.append("use " + qty + " " + entry.it.name + "; ");
+ if (entry.type == MODIFIER_UPKEEP_ENTRY_TYPE_SKILL)
+ __simulation_output.append("cast " + qty + " " + entry.s.name + "; ");
+}
+
void ModifierUpkeepEffects(ModifierUpkeepSettings settings)
{
if (settings.minimum_turns_wanted < 0) settings.minimum_turns_wanted = 1;
@@ -480,7 +584,9 @@ void ModifierUpkeepEffects(ModifierUpkeepSettings settings)
relevant_value_for_modifier = my_maxhp();
if (settings.modifier_name ≈ "familiar weight")
relevant_value_for_modifier = numeric_modifier(settings.modifier_name) + my_familiar().familiar_weight(); //FIXME support feasted familiars, because that's a complete pain
-
+
+ if (__setting_simulation_only)
+ relevant_value_for_modifier += __simulation_modifier_bonus[settings.modifier_name];
boolean satisfied = true;
if (settings.minimum_value >= 0.0 && settings.minimum_value > relevant_value_for_modifier)
@@ -495,7 +601,7 @@ void ModifierUpkeepEffects(ModifierUpkeepSettings settings)
{
first = false;
}
- else
+ else if (!__setting_simulation_only)
{
if (last_loop_value == relevant_value_for_modifier && !allow_overriding_modifier_value_safety && !(settings.modifier_name ≈ "any"))
{
@@ -632,11 +738,11 @@ void ModifierUpkeepEffects(ModifierUpkeepSettings settings)
break;
}
if (!__setting_silent)
- print_html(entry.ModifierUpkeepEntryDescription() + ": " + entry_efficiency + " efficiency");
+ print_html(entry.ModifierUpkeepEntryDescription(settings.modifier_name) + ": " + round(entry_efficiency*1000)/1000.0 + " efficiency");
if (__gain_setting_confirm)
{
- boolean ready = user_confirm(entry.ModifierUpkeepEntryDescription() + "\nREADY?");
+ boolean ready = user_confirm(entry.ModifierUpkeepEntryDescription(settings.modifier_name) + "\nREADY?");
if (!ready)
return;
}
@@ -644,8 +750,9 @@ void ModifierUpkeepEffects(ModifierUpkeepSettings settings)
//execute:
int before_effect = entry.e.have_effect();
+ int max_reasonable_uses = (my_adventures() / entry.turns_gotten_from_source);
int amount = MAX(1, ceil(to_float(settings.minimum_turns_wanted - entry.e.have_effect()) / MAX(1.0, to_float(entry.turns_gotten_from_source))));
- amount = MIN(10, amount);
+ amount = MIN(max_reasonable_uses, amount);
if (turn_into_wish)
{
@@ -653,29 +760,51 @@ void ModifierUpkeepEffects(ModifierUpkeepSettings settings)
}
else if (entry.type == MODIFIER_UPKEEP_ENTRY_TYPE_ITEM)
{
- use(amount, entry.it);
+ if (__setting_simulation_only)
+ {
+ SimulateModifierUpkeep(entry, settings, amount);
+ }
+ else
+ {
+ use(amount, entry.it);
+ }
}
else if (entry.type == MODIFIER_UPKEEP_ENTRY_TYPE_SKILL)
{
- int times_can_cast = 10;
+ if (entry.s.mp_cost() > 0)
+ max_reasonable_uses = max(1, my_mp() / entry.s.mp_cost());
if (entry.s.hp_cost() > 0)
- times_can_cast = max(1, (my_hp() - 1) / entry.s.hp_cost());
+ max_reasonable_uses = max(1, (my_hp() - 1) / entry.s.hp_cost());
item [slot] saved_equipment;
- if ($skills[CHEAT CODE: Triple Size,CHEAT CODE: Invisible Avatar] contains entry.s && !$Item[powerful glove].have_equipped())
+ if (!__setting_simulation_only && $skills[CHEAT CODE: Triple Size,CHEAT CODE: Invisible Avatar] contains entry.s && !$Item[powerful glove].have_equipped())
{
saved_equipment[$slot[acc1]] = $slot[acc1].equipped_item();
equip($item[powerful glove], $slot[acc1]);
}
- print_html("casting " + entry.s);
- boolean result = use_skill(min(times_can_cast, amount), entry.s);
+
+ int times_to_cast = min(max_reasonable_uses, amount);
+ if (__setting_simulation_only)
+ {
+ SimulateModifierUpkeep(entry, settings, times_to_cast);
+ }
+ else
+ {
+ use_skill(times_to_cast, entry.s);
+ }
+
foreach s, it in saved_equipment
{
equip(it, s);
}
}
int after_effect = entry.e.have_effect();
- if (after_effect == before_effect)
+
+ if (__setting_simulation_only)
+ {
+ __blocked_effects[entry.e] = true;
+ }
+ else if (after_effect == before_effect)
{
//use 1 future drug: Muscularactum
//You acquire an effect: The Strength... of the Future (0)
@@ -690,7 +819,7 @@ void ModifierUpkeepEffects(ModifierUpkeepSettings settings)
continue;
}
else
- abort("Mafia bug: " + entry.ModifierUpkeepEntryDescription() + " did not gain any turns.");
+ abort("Mafia bug: " + entry.ModifierUpkeepEntryDescription(settings.modifier_name) + " did not gain any turns.");
}
}
else if (before_effect != 0 && after_effect < 1000)
@@ -699,7 +828,7 @@ void ModifierUpkeepEffects(ModifierUpkeepSettings settings)
{
dynamic_blocked_effects[entry.e] = true;
}
- __meat_spent += meat_cost;
+ __meat_spent += meat_cost * amount;
did_execute_one = true;
break;
}
@@ -742,6 +871,7 @@ void ModifierOutputExampleUsage()
if (__setting_silent) return;
print_html("silent: don't output text (useful in libraries)");
print_html("limited: allow limited buffs");
+ print_html("sim: simulate what would happen, and what it might cost");
print_html("");
print_html("Example usage:");
print_html("gain 400 initiative: buff to 400 initiative, as efficiently as possible");
@@ -816,6 +946,11 @@ void main(string arguments)
{
if (argument == "") continue;
boolean ignore_text = false;
+ if (argument == "sim")
+ {
+ __setting_simulation_only = true;
+ continue;
+ }
if (argument == "turns" || argument == "turn")
{
desired_min_turns = MAX(1, modifier_value);
@@ -930,5 +1065,13 @@ void main(string arguments)
modifier_settings.maximum_efficiency = maximum_efficiency;
ModifierUpkeepEffects(modifier_settings);
}
+
+ if (__setting_simulation_only)
+ {
+ print_html("<\br>Execute:");
+ print(__simulation_output);
+ print("===========");
+ print_html("Estimated cost: " + __meat_spent + " meat ( " + __meat_spent/desired_min_turns + "/turn )");
+ }
}
\ No newline at end of file