Bug - Fixed VampOut is broken due to circular dependency

Obeliks

Member
When I try to vamp out (using plastic fangs), I recently get the following error:

Code:
Putting on plastic vampire fangs...
Equipment changed.
Encounter: Interview With You
ChoiceAdventure # 546 has no Options configured
Missing ChoiceAdventure Options: (546)
Preference _interviewVlad changed from false to true
Encounter: Interview With You - Goal your own black heart
Action: Visit The Masquerade
Unexpected error, debug log printed.

As you can see from the error message, this error message in ChoiceAdventures.java is triggered, which means the ChoiceAdventure was created with an empty list of options. However, the only place where this ChoiceAdventure is created, is further up in the file, and there the list of VampOutManager.VampOutGoals is passed as options.

The ChoiceManager refactoring seems to have introduced a circular dependency: in order to create the ChoiceAdventures class object with its static members, the VampOutManager class object with its static members must be created. But that needs the Options constructor from the ChoiceAdventures class unit.

Eventually, this leads to the list of Options being empty at the time the ChoiceAdventure instance is created.

I have not tested this, but a proposed fix is to move the Option class out of the unit where all the ChoiceAdventures are created. I tested it and it works. I can prepare a PR, but I don't really know how you would want to name the class, and whether the Spoilers class should also be moved.
 
Last edited:

Veracity

Developer
Staff member
Huh.

When I open the GUI and go to "Choice Advs" and then "Item", I see the following:

Screen Shot 2022-11-07 at 4.55.22 PM.png

Those are the Options from VampOutManager.
They were extracted from ChoiceAdventures.CHOICE_ADVS, which was populated during static initialization of ChoiceAdventures.java

What exactly did you do?
 

Veracity

Developer
Staff member
By the way - I had a copy of Bale's VampOut script.
I stripped it down and updated it to work with current KoL
("town.php?action=vampout" -> "place.php?whichplace=town&action=town_vampout")
and got this:

Code:
> MyVampOut 13

Internal checkpoint created.
Encounter: Interview With You
Encounter: Interview With You - Goal your own black heart
Action: Visit The Masquerade
Encounter: Interview With You
Action: "An abandoned, graffiti-covered warehouse."
Encounter: Interview With You
Action: "An angry growl promising impending violence."
Encounter: Interview With You
Action: "The Clash."
Encounter: Interview With You
Action: "I tuned my motorcycle for maximum noise."
Encounter: Interview With You
Action: "Lager."
Encounter: Interview With You
Action: Speak to the Ventrilo representative.
Encounter: Interview With You
Action: Speak to the Brouhaha representative.
Encounter: Interview With You
Action: Speak to the Torremolinos representative.
Encounter: Interview With You
Action: Speak to the Malkovich representative.
Encounter: Interview With You
Action: "Yes."
Encounter: Interview With You
Action: And the result is...
You acquire an item: your own black heart

Note that choice 13 is "your own black heart" as configured by VampoutManager.
 

Obeliks

Member
Huh.

When I open the GUI and go to "Choice Advs" and then "Item", I see the following:

View attachment 10862

Those are the Options from VampOutManager.
They were extracted from ChoiceAdventures.CHOICE_ADVS, which was populated during static initialization of ChoiceAdventures.java

What exactly did you do?

That's a very good question :D This is quite puzzling... Perhaps it's related to the specific JVM used?

Without the change where I move the Choice class to a top-level class, I cannot use the VampOut options due to the error I mentioned above.

I don't think I have any changes in my fork that can cause this, but I will try to reproduce the problem with Vanilla KoLmafia and get back to you on this.
 

Obeliks

Member
I believe that the problem I've outlined is legit, though. Compare this StackOverflow answer regarding a similar (more simplified) setup: https://stackoverflow.com/a/6416481/1479482

Since this seems to be clearly specified in the CLS, I don't think the concrete JVM implementation can be at fault here. I will investigate more soon.
 

Obeliks

Member
I finally managed to try and reproduce this with vanilla KoLmafia (KoLmafia-27068)

It can be triggered by running this ASH commands in the CLI:
Code:
set_property("choiceAdventure546", "13")
visit_url("/place.php?whichplace=town&action=town_vampout")
run_choice(-1)

Code:
 > ash set_property("choiceAdventure546", "13"); visit_url("/place.php?whichplace=town&action=town_vampout"); run_choice(-1);
Encounter: Interview With You
ChoiceAdventure # 546 has no Options configured
Missing ChoiceAdventure Options: (546)
Encounter: Interview With You - Goal your own black heart
Action: Visit The Masquerade
()
Script execution aborted (java.lang.NullPointerException: Cannot read the array length because "this.options" is null): ()
Returned: void

The above ash inline script will definitely trigger the problem for me.

I tried to reproduce it by using "Interview With You (A Vampire)" as well, but I think I did something wrong there. Will try again tomorrow :)

Perhaps using the GUI will load the Choice Adventures from a different code path (for the GUI), which does not trigger this problem.
 

Veracity

Developer
Staff member
Code:
> equip acc3 plastic vampire fangs

Putting on plastic vampire fangs...
Equipment changed.

> ashq set_property("choiceAdventure546", "13"); visit_url("/place.php?whichplace=town&action=town_vampout"); run_choice(-1);

Encounter: Interview With You
Encounter: Interview With You - Goal your own black heart
Action: Visit The Masquerade
Encounter: Interview With You
Action: "An abandoned, graffiti-covered warehouse."
Encounter: Interview With You
Action: "An angry growl promising impending violence."
Encounter: Interview With You
Action: "The Clash."
Encounter: Interview With You
Action: "I tuned my motorcycle for maximum noise."
Encounter: Interview With You
Action: "Lager."
Encounter: Interview With You
Action: Speak to the Ventrilo representative.
Encounter: Interview With You
Action: Speak to the Brouhaha representative.
Encounter: Interview With You
Action: Speak to the Torremolinos representative.
Encounter: Interview With You
Action: Speak to the Malkovich representative.
Encounter: Interview With You
Action: "Yes."
Encounter: Interview With You
Action: And the result is...
You acquire an item: your own black heart
 

Obeliks

Member
Ok, strange. This is the first thing you ran after starting KoLmafia --CLI, right?

I'm running this on Ubuntu 22.04 with openjdk 17.0.5.

Code:
$ java --version
openjdk 17.0.5 2022-10-18
OpenJDK Runtime Environment (build 17.0.5+8-Ubuntu-2ubuntu122.04)
OpenJDK 64-Bit Server VM (build 17.0.5+8-Ubuntu-2ubuntu122.04, mixed mode, sharing)
$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 22.04.1 LTS
Release:        22.04
Codename:       jammy
 

Veracity

Developer
Staff member
I did not start with --CLI. Have actually never done that.
I'll try that after rollover.

Perhaps I can reproduce it on another character via Interview with You (a Vampire).
 

Veracity

Developer
Staff member
I had a character who does not have the IOTM try using the book - and, unlike the teeth, which can be used overdrunk, "you are too drunk to read either of the two books".
I'll try after rollover.
 

Veracity

Developer
Staff member
I see. Thanks for trying this, I think this might be the relevant difference.
Could be.

When you start the GUI, it loads the choice options from ChoiceAdventure.java:CHOICE_ADVS.
That is initialized in a static block which streams the choiceToChoiceAdventure map, which should have been fully initialized earlier.
If not, why not?

I can experiment with --CLI mode without needing to actually automate this.
 

Veracity

Developer
Staff member
I munged the "test choicespoilers" command.

From the GUI cli:

Code:
> test choicespoilers 546

Option 1: Mistified
Option 2: Bat Attitude
Option 3: There Wolf
Option 4: Muscle
Option 5: Mysticality
Option 6: Moxie
Option 7: 111 Meat, lose 1-2 hp
Option 8: Prince of Seaside Town and Sword of the Brouhaha Prince
Option 9: Prince of Seaside Town and Sceptre of the Torremolinos Prince
Option 10: Prince of Seaside Town and Medallion of the Ventrilo Prince
Option 11: Prince of Seaside Town and Chalice of the Malkovich Prince
Option 12: Pride of the Vampire and Interview With You (a Vampire)
Option 13: your own black heart

From --CLI:

Code:
> test choicespoilers 546
Option 1: Mistified
Option 2: Bat Attitude
Option 3: There Wolf
Option 4: Muscle
Option 5: Mysticality
Option 6: Moxie
Option 7: 111 Meat, lose 1-2 hp
Option 8: Prince of Seaside Town and Sword of the Brouhaha Prince
Option 9: Prince of Seaside Town and Sceptre of the Torremolinos Prince
Option 10: Prince of Seaside Town and Medallion of the Ventrilo Prince
Option 11: Prince of Seaside Town and Chalice of the Malkovich Prince
Option 12: Pride of the Vampire and Interview With You (a Vampire)
Option 13: your own black heart

I'm not seeing that "not having a GUI" is interfering with looking up the ChoiceAdventure by name and having the expected options.

If you want to try this yourself, this is the change to the test command:

Code:
diff --git a/src/net/sourceforge/kolmafia/textui/command/TestCommand.java b/src/net/sourceforge/kolmafia/textui/command/TestCommand.java
index 270614ca9d..c044f55416 100644
--- a/src/net/sourceforge/kolmafia/textui/command/TestCommand.java
+++ b/src/net/sourceforge/kolmafia/textui/command/TestCommand.java
@@ -63,6 +63,8 @@ import net.sourceforge.kolmafia.request.SpaaaceRequest;
 import net.sourceforge.kolmafia.session.BastilleBattalionManager;
 import net.sourceforge.kolmafia.session.BeachManager;
 import net.sourceforge.kolmafia.session.ChoiceAdventures;
+import net.sourceforge.kolmafia.session.ChoiceAdventures.ChoiceAdventure;
+import net.sourceforge.kolmafia.session.ChoiceAdventures.Option;
 import net.sourceforge.kolmafia.session.ChoiceManager;
 import net.sourceforge.kolmafia.session.DadManager;
 import net.sourceforge.kolmafia.session.DvorakManager;
@@ -219,10 +221,11 @@ public class TestCommand extends AbstractCommand {
         return;
       }
       int choice = StringUtilities.parseInt(split[1]);
-      Object[] spoilers = ChoiceAdventures.dynamicChoiceOptions(choice);
-      if (spoilers != null) {
-        for (int i = 0; i < spoilers.length; ++i) {
-          RequestLogger.printLine("Option " + (i + 1) + ": " + spoilers[i]);
+      ChoiceAdventure adventure = ChoiceAdventures.choiceToChoiceAdventure.get(choice);
+      if (adventure != null) {
+        Option[] options = adventure.getOptions();
+        for (int i = 0; i < options.length; i++) {
+          RequestLogger.printLine("Option " + (i + 1) + ": " + options[i]);
         }
       }
       return;
 

Veracity

Developer
Staff member
OK. After rollover, my multi is logged in via "--CLI" and is not overdrunk.

Code:
> ashq set_property("choiceAdventure546", "13"); visit_url("inv_use.php?whichitem=5300"); run_choice(-1)
choiceAdventure546 => 13
Encounter: Interview With You
Encounter: Interview With You - Goal your own black heart
Action: Visit The Masquerade
Encounter: Interview With You
Action: "An abandoned, graffiti-covered warehouse."
Encounter: Interview With You
Action: "An angry growl promising impending violence."
Encounter: Interview With You
Action: "The Clash."
Encounter: Interview With You
Action: "I tuned my motorcycle for maximum noise."
Encounter: Interview With You
Action: "Lager."
Encounter: Interview With You
Action: Speak to the Ventrilo representative.
Encounter: Interview With You
Action: Speak to the Brouhaha representative.
Encounter: Interview With You
Action: Speak to the Torremolinos representative.
Encounter: Interview With You
Action: Speak to the Malkovich representative.
Encounter: Interview With You
Action: "Yes."
Encounter: Interview With You
Action: And the result is...
You acquire an item: your own black heart
I see no bug.

Code:
bash-3.2$ java --version
openjdk 17.0.5 2022-10-18
OpenJDK Runtime Environment Temurin-17.0.5+8 (build 17.0.5+8)
OpenJDK 64-Bit Server VM Temurin-17.0.5+8 (build 17.0.5+8, mixed mode)
 

Obeliks

Member
Thanks for your detailed tests. I tried your test code, and indeed that doesn't trigger it for me as well.

The problem happens if ChoiceManager gets loaded before ChoiceAdventures. If you modify the test to call the following:

Java:
      String result = ChoiceManager.specialChoiceDecision1(546, "13", 0, "");

... I hope that that will allow you to reproduce the problem.

Code:
 > test choicespoilers 546
ChoiceAdventure # 546 has no Options configured
Missing ChoiceAdventure Options: (546)
0

EDIT: When running the same jar as GUI on Windows, this does not seem to happen, either because the GUI already initialized the ChoiceAdventures static block, or ¯\_(ツ)_/¯

EDIT2: Here is the full session of my attempt in Ubuntu on WSL2:

Code:
$ java -jar dist/KoLmafia-27071-M.jar --CLI

KoLmafia r27071-M
Build HEAD-b0e192e-M 17.0.5 (Private Build 17.0.5+8-Ubuntu-2ubuntu122.04) Linux amd64 5.15.79.1-microsoft-standard-WSL2

Currently Running on Linux
Local Directory is /home/obeliks/.kolmafia
Using Java 17.0.5


username:
Invalid login.
 > test choicespoilers 546
ChoiceAdventure # 546 has no Options configured
Missing ChoiceAdventure Options: (546)
0

 > quit
 
Last edited:

Veracity

Developer
Staff member
OK. That reproduces the error.
Unclear to me why automating the choice in --CLI mode worked for me but not you.

I suppose splitting out ChoiceOption is reasonable. Let me try it out.
 

Veracity

Developer
Staff member
a proposed fix is to move the Option class out of the unit where all the ChoiceAdventures are created. I tested it and it works. I can prepare a PR, but I don't really know how you would want to name the class, and whether the Spoilers class should also be moved.
I named it ChoiceOption, since Option would be a lot too general.
I looked at Spoilers but it appears that those are all synthesized out of ChoiceOptions, which are now safe.
I didn't see a way to get a failing test case.
 
Top