diff --git a/Source/relay/TourGuide/Items of the Month/2011/Plastic Vampire Fangs.ash b/Source/relay/TourGuide/Items of the Month/2011/Plastic Vampire Fangs.ash
index b377da83..3ce389d3 100644
--- a/Source/relay/TourGuide/Items of the Month/2011/Plastic Vampire Fangs.ash
+++ b/Source/relay/TourGuide/Items of the Month/2011/Plastic Vampire Fangs.ash
@@ -20,18 +20,16 @@ void IOTMPlasticVampireFangsGenerateResource(ChecklistEntry [int] resource_entri
url = "inventory.php?ftext=plastic+vampire+fangs";
}
}
-
- else if ($item[Interview With You (a Vampire)].available_amount() > 0) {
- fang_source = $item[Interview With You (a Vampire)];
- url = "inventory.php?ftext=interview+with+you";
- }
-
- else {
+ else if ($item[plastic vampire fangs].available_amount() > 0) {
url = "place.php?whichplace=town";
if ($item[plastic vampire fangs].equipped_amount() == 0) {
url = "inventory.php?ftext=plastic+vampire+fangs";
}
}
+ else {
+ fang_source = $item[Interview With You (a Vampire)];
+ url = "inventory.php?ftext=interview+with+you";
+ }
// Show the Isabella interview option if it is valid for the user.
diff --git a/Source/relay/TourGuide/Items of the Month/2016/Intergnat.ash b/Source/relay/TourGuide/Items of the Month/2016/Intergnat.ash
index 7b30d505..b14884b3 100644
--- a/Source/relay/TourGuide/Items of the Month/2016/Intergnat.ash
+++ b/Source/relay/TourGuide/Items of the Month/2016/Intergnat.ash
@@ -1,43 +1,45 @@
-RegisterTaskGenerationFunction("IOTMIntergnatGenerateTasks");
-void IOTMIntergnatGenerateTasks(ChecklistEntry [int] task_entries, ChecklistEntry [int] optional_task_entries, ChecklistEntry [int] future_task_entries)
-{
+// Commenting out demon-finding intergnat task.
+// TODO: refactor into new task that only generates if you happen to have the items needed for demon summoning in-run
+// RegisterTaskGenerationFunction("IOTMIntergnatGenerateTasks");
+// void IOTMIntergnatGenerateTasks(ChecklistEntry [int] task_entries, ChecklistEntry [int] optional_task_entries, ChecklistEntry [int] future_task_entries)
+// {
- if (!get_property_boolean("demonSummoned") && __misc_state["in run"] && !__quest_state["Level 13"].state_boolean["king waiting to be freed"] && $familiar[intergnat].familiar_is_usable() && my_path().id != PATH_AVATAR_OF_SNEAKY_PETE)
- {
- //Should we show this if they aren't using the intergnat? ... Yes?
- //demonName12, thin black candle, scroll of ancient forbidden unspeakable evil
- string [int] reasons;
- if (!get_property("demonName12").contains_text("Neil ") && my_level() < 13) //13 is +50% init... it's kind of useful? But not worth mentioning if they don't have it by this point?
- {
- reasons.listAppend("learn demon name");
- }
- if ($item[thin black candle].available_amount() < 3)
- {
- if ($item[thin black candle].available_amount() < 2)
- reasons.listAppend("collect " + int_to_wordy(3 - $item[thin black candle].available_amount()) + " more thin black candles");
- else
- reasons.listAppend("collect One More Thin black candle");
- }
- if ($item[scroll of ancient forbidden unspeakable evil].available_amount() == 0)
- {
- reasons.listAppend("collect a scroll of ancient forbidden unspeakable evil");
- }
- if (reasons.count() > 0)
- {
- string [int] description;
- description.listAppend(reasons.listJoinComponents(", ", "and").capitaliseFirstLetter() + ".");
- string url = "";
- string title = "Continue running Intergnat for demon summon";
- if (my_familiar() != $familiar[intergnat])
- {
- url = "familiar.php";
- title = "Possibly run Intergnat for demon summon";
- }
- //Don't use the intergnat icon, because animation is distracting:
- optional_task_entries.listAppend(ChecklistEntryMake("__item thin black candle", url, ChecklistSubentryMake(title, "", description)).ChecklistEntrySetIDTag("Intergnat summon Neil"));
- }
- }
-}
+// if (!get_property_boolean("demonSummoned") && __misc_state["in run"] && !__quest_state["Level 13"].state_boolean["king waiting to be freed"] && $familiar[intergnat].familiar_is_usable() && my_path().id != PATH_AVATAR_OF_SNEAKY_PETE)
+// {
+// //Should we show this if they aren't using the intergnat? ... Yes?
+// //demonName12, thin black candle, scroll of ancient forbidden unspeakable evil
+// string [int] reasons;
+// if (!get_property("demonName12").contains_text("Neil ") && my_level() < 13) //13 is +50% init... it's kind of useful? But not worth mentioning if they don't have it by this point?
+// {
+// reasons.listAppend("learn demon name");
+// }
+// if ($item[thin black candle].available_amount() < 3)
+// {
+// if ($item[thin black candle].available_amount() < 2)
+// reasons.listAppend("collect " + int_to_wordy(3 - $item[thin black candle].available_amount()) + " more thin black candles");
+// else
+// reasons.listAppend("collect One More Thin black candle");
+// }
+// if ($item[scroll of ancient forbidden unspeakable evil].available_amount() == 0)
+// {
+// reasons.listAppend("collect a scroll of ancient forbidden unspeakable evil");
+// }
+// if (reasons.count() > 0)
+// {
+// string [int] description;
+// description.listAppend(reasons.listJoinComponents(", ", "and").capitaliseFirstLetter() + ".");
+// string url = "";
+// string title = "Continue running Intergnat for demon summon";
+// if (my_familiar() != $familiar[intergnat])
+// {
+// url = "familiar.php";
+// title = "Possibly run Intergnat for demon summon";
+// }
+// //Don't use the intergnat icon, because animation is distracting:
+// optional_task_entries.listAppend(ChecklistEntryMake("__item thin black candle", url, ChecklistSubentryMake(title, "", description)).ChecklistEntrySetIDTag("Intergnat summon Neil"));
+// }
+// }
+// }
RegisterResourceGenerationFunction("IOTMIntergnatGenerateResource");
void IOTMIntergnatGenerateResource(ChecklistEntry [int] resource_entries)
diff --git a/Source/relay/TourGuide/Items of the Month/2017/Tunnel of Love.ash b/Source/relay/TourGuide/Items of the Month/2017/Tunnel of Love.ash
index 850c45fe..62eb0545 100644
--- a/Source/relay/TourGuide/Items of the Month/2017/Tunnel of Love.ash
+++ b/Source/relay/TourGuide/Items of the Month/2017/Tunnel of Love.ash
@@ -49,6 +49,11 @@ void IOTMTunnelOfLoveGenerateTasks(ChecklistEntry [int] task_entries, ChecklistE
RegisterResourceGenerationFunction("IOTMTunnelOfLoveGenerateResource");
void IOTMTunnelOfLoveGenerateResource(ChecklistEntry [int] resource_entries)
{
+
+ if (!get_property_boolean("_loveTunnelUsed")) {
+ resource_entries.listAppend(ChecklistEntryMake("__item pink candy heart", "place.php?whichplace=town_wrong", ChecklistSubentryMake("3 free L.O.V. dudes", "", "Free fights and useful items/buffs."), 5).ChecklistEntrySetCombinationTag("daily free fight").ChecklistEntrySetIDTag("Love tunnel free fights"));
+ }
+
//mostly the boomerang
//what does sokka throw? a boomeraang!
diff --git a/Source/relay/TourGuide/Items of the Month/2018/BoomBox.ash b/Source/relay/TourGuide/Items of the Month/2018/BoomBox.ash
index b8759259..04cd5258 100644
--- a/Source/relay/TourGuide/Items of the Month/2018/BoomBox.ash
+++ b/Source/relay/TourGuide/Items of the Month/2018/BoomBox.ash
@@ -3,7 +3,9 @@ RegisterTaskGenerationFunction("IOTMBoomBoxGenerateTasks");
void IOTMBoomBoxGenerateTasks(ChecklistEntry [int] task_entries, ChecklistEntry [int] optional_task_entries, ChecklistEntry [int] future_task_entries)
{
if (lookupItem("SongBoom™ BoomBox").available_amount() == 0) return;
-
+
+ // leaving this for aftercore because TES likes this tile, but it is not needed in-run
+ if (__misc_state["in run"]) return;
string song = get_property("boomBoxSong");
int changes_left = get_property_int("_boomBoxSongsLeft"); //the boys are back in town, eleven times. everyone will love it
diff --git a/Source/relay/TourGuide/Items of the Month/2020/Box O Ghosts.ash b/Source/relay/TourGuide/Items of the Month/2020/Box O Ghosts.ash
index c3540cf8..2671b085 100644
--- a/Source/relay/TourGuide/Items of the Month/2020/Box O Ghosts.ash
+++ b/Source/relay/TourGuide/Items of the Month/2020/Box O Ghosts.ash
@@ -7,7 +7,14 @@ void IOTMCommerceGhostGenerateTasks(ChecklistEntry [int] task_entries, Checklist
int commerce_statgain2 = (my_level() * 25) * (1.0 + numeric_modifier(my_primestat().to_string() + " Experience Percent") / 100.0);
boolean in_grey_you = my_class() == $class[grey goo]; // Grey You gains zero stats from your commerce ghost.
- if (__misc_state["in run"] && $familiar[Ghost of Crimbo Commerce].familiar_is_usable() && !in_grey_you)
+ // reduce clutter by not showing this tile if user does not need it
+ if (!$familiar[Ghost of Crimbo Commerce].familiar_is_usable()) return;
+ if (my_level() > 12) return;
+ if (__iotms_usable[lookupItem("Neverending Party invitation envelope")]) return;
+ if ($item[Sept-Ember Censer].available_amount() > 0) return;
+
+ // only show if in-run
+ if (__misc_state["in run"] && !in_grey_you)
{
// Title
string url = "familiar.php";
diff --git a/Source/relay/TourGuide/Items of the Month/2021/Daylight Shavings Helmet.ash b/Source/relay/TourGuide/Items of the Month/2021/Daylight Shavings Helmet.ash
index 206bbd82..168a550e 100644
--- a/Source/relay/TourGuide/Items of the Month/2021/Daylight Shavings Helmet.ash
+++ b/Source/relay/TourGuide/Items of the Month/2021/Daylight Shavings Helmet.ash
@@ -78,7 +78,9 @@ void IOTMDaylightShavingsHelmetGenerateTasks(ChecklistEntry [int] task_entries,
nextBeardBuffDescription = nextBeardBuff[nextBeardBuffName];
nextBeardBuffEffect = boldIfValuable(nextBeardBuffName) + ", " + to_buffer(nextBeardBuffDescription);
}
- description.listAppend("Next shavings effect:
" + nextBeardBuffEffect);
+
+ // Commenting out because you can always use the tooltip.
+ // description.listAppend("Next shavings effect:
" + nextBeardBuffEffect);
string [int][int] tooltip_table;
for i from lastBeardIndex() + 1 to lastBeardIndex() + 11 {
diff --git a/Source/relay/TourGuide/Items of the Month/2025/Allied Radio Backpack.ash b/Source/relay/TourGuide/Items of the Month/2025/Allied Radio Backpack.ash
index 383b0c1b..3a62635d 100644
--- a/Source/relay/TourGuide/Items of the Month/2025/Allied Radio Backpack.ash
+++ b/Source/relay/TourGuide/Items of the Month/2025/Allied Radio Backpack.ash
@@ -2,7 +2,7 @@
RegisterResourceGenerationFunction("IOTTAlliedRadioBackpackGenerateResource");
void IOTTAlliedRadioBackpackGenerateResource(ChecklistEntry [int] resource_entries)
{
- if ($item[allied radio backpack].available_amount() == 0) return;
+ if (__iotms_usable[lookupItem("allied radio backpack")]) return;
string url = "inventory.php?action=requestdrop&pwd=" + my_hash();
int radioDropsLeft = clampi(3 - get_property_int("_alliedRadioDropsUsed"), 0, 3);
diff --git a/Source/relay/TourGuide/Items of the Month/2025/Blood Cubic Zirconia.ash b/Source/relay/TourGuide/Items of the Month/2025/Blood Cubic Zirconia.ash
new file mode 100644
index 00000000..9d43eab0
--- /dev/null
+++ b/Source/relay/TourGuide/Items of the Month/2025/Blood Cubic Zirconia.ash
@@ -0,0 +1,112 @@
+//blood cubic zirconia
+RegisterTaskGenerationFunction("IOTMBloodCubicZirconiaGenerateTasks");
+void IOTMBloodCubicZirconiaGenerateTasks(ChecklistEntry [int] task_entries, ChecklistEntry [int] optional_task_entries, ChecklistEntry [int] future_task_entries)
+{
+ // TODO: reorganize/update tile; obvious changes include:
+ // - maybe update the cost matrix structure, seems a lil silly
+ // - add refract recommendation system
+ // - shorten resource tile by switching the less-useful uses into a hoverover
+ // - small wording tweaks
+ // - match pheromone styling to other banishes
+
+
+ if (__iotms_usable[lookupItem("blood cubic zirconia")]) return;
+ string url = "inventory.php?ftext=blood+cubic+zirconia";
+ string [int] description;
+ int bczRefracts = get_property_int("_bczRefractedGazeCasts");
+ int bczBullets = get_property_int("_bczSweatBulletsCasts");
+ int bczEquitys = get_property_int("_bczSweatEquityCasts");
+
+ int [int] bloodCast = {
+ 0:11, 1:23, 2:37, 3:110, 4:230,
+ 5:370, 6:1100, 7:2300, 8:3700, 9:11000,
+ 10:23000, 11:37000, 12:420000, 13:1100000, 14:2300000,
+ 15:3700000};
+ int refractCost = bloodCast[min(bczRefracts, 15)];
+ int bulletCost = bloodCast[min(bczBullets, 15)];
+ int equityCost = bloodCast[min(bczEquitys, 15)];
+
+ if (lookupItem("blood cubic zirconia").equipped_amount() > 0)
+ {
+ if (bczRefracts < 13) {
+ description.listAppend("Next Refract costs " + HTMLGenerateSpanFont(refractCost + "", "red") + " mys");
+ }
+ else if (bczRefracts >= 13) {
+ description.listAppend(HTMLGenerateSpanFont("Next Refract costs " + refractCost + " mys. EXPENSIVE!", "red") + "");
+ }
+ if (lookupItem("monodent of the sea").equipped_amount() == 0)
+ {
+ description.listAppend(HTMLGenerateSpanFont("Seadent not equipped", "red"));
+ }
+ else if (lookupItem("monodent of the sea").equipped_amount() > 0)
+ {
+ description.listAppend(HTMLGenerateSpanFont("Seadent FLEESH ok!", "blue"));
+ }
+ if (bczBullets < 13) {
+ description.listAppend("Next Bullet costs " + HTMLGenerateSpanFont(bulletCost + "", "red") + " mox");
+ }
+ else if (bczBullets >= 13) {
+ description.listAppend(HTMLGenerateSpanFont("Next Bullet costs " + bulletCost + " mox. EXPENSIVE!", "red") + "");
+ }
+ if (bczEquitys < 13) {
+ description.listAppend("Next Equity costs " + HTMLGenerateSpanFont(equityCost + "", "red") + " mox");
+ }
+ else if (bczEquitys >= 13) {
+ description.listAppend(HTMLGenerateSpanFont("Next Equity costs " + equityCost + " mox. EXPENSIVE!", "red") + "");
+ }
+ task_entries.listAppend(ChecklistEntryMake("__item blood cubic zirconia", url, ChecklistSubentryMake(HTMLGenerateSpanFont("BCZ: Blood Cubic Zirconia skills", "brown"), description), -11).ChecklistEntrySetIDTag("bcz important skills"));
+ }
+}
+
+RegisterResourceGenerationFunction("IOTMBloodCubicZirconiaGenerateResource");
+void IOTMBloodCubicZirconiaGenerateResource(ChecklistEntry [int] resource_entries)
+{
+ if ($item[blood cubic zirconia].available_amount() == 0) return;
+ string url = "inventory.php?ftext=blood+cubic+zirconia";
+ string [int] description;
+ int bczBaths = get_property_int("_bczBloodBathCasts");
+ int bczThinners = get_property_int("_bczBloodThinnerCasts");
+ int bczDials = get_property_int("_bczDialitupCasts");
+ int bczPheromones = get_property_int("_bczPheromoneCocktailCasts");
+ int bczSpinalTapas = get_property_int("_bczSpinalTapasCasts");
+ int bczGeysers = get_property_int("_bczBloodGeyserCasts");
+ int bczRefracts = get_property_int("_bczRefractedGazeCasts");
+ int bczBullets = get_property_int("_bczSweatBulletsCasts");
+ int bczEquitys = get_property_int("_bczSweatEquityCasts");
+
+ int [int] bloodCast = {
+ 0:11, 1:23, 2:37, 3:110, 4:230,
+ 5:370, 6:1100, 7:2300, 8:3700, 9:11000,
+ 10:23000, 11:37000, 12:420000, 13:1100000, 14:2300000,
+ 15:3700000};
+ int bathCost = bloodCast[min(bczBaths, 15)];
+ int thinnerCost = bloodCast[min(bczThinners, 15)];
+ int dialCost = bloodCast[min(bczDials, 15)];
+ int pheromoneCost = bloodCast[min(bczPheromones, 15)];
+ int tapasCost = bloodCast[min(bczSpinalTapas, 15)];
+ int geyserCost = bloodCast[min(bczGeysers, 15)];
+ int refractCost = bloodCast[min(bczRefracts, 15)];
+ int bulletCost = bloodCast[min(bczBullets, 15)];
+ int equityCost = bloodCast[min(bczEquitys, 15)];
+
+ description.listAppend("Next Refract costs " + HTMLGenerateSpanFont(refractCost + "", "red") + " mys");
+ description.listAppend("Next Bullet costs " + HTMLGenerateSpanFont(bulletCost + "", "red") + " mox");
+ description.listAppend("Next Equity costs " + HTMLGenerateSpanFont(equityCost + "", "red") + " mox");
+
+ description.listAppend("Next Geyser costs " + HTMLGenerateSpanFont(geyserCost + "", "brown") + " mus");
+ description.listAppend("Next Bath costs " + HTMLGenerateSpanFont(bathCost + "", "brown") + " mus");
+ description.listAppend("Next Dial costs " + HTMLGenerateSpanFont(dialCost + "", "brown") + " mys");
+ description.listAppend("Next Thinner costs " + HTMLGenerateSpanFont(thinnerCost + "", "brown") + " mus");
+ description.listAppend("Next Tapas costs " + HTMLGenerateSpanFont(tapasCost + "", "brown") + " mys");
+ description.listAppend("Next Pheromone costs " + HTMLGenerateSpanFont(pheromoneCost + "", "brown") + " mox");
+
+ resource_entries.listAppend(ChecklistEntryMake("__item blood cubic zirconia", url, ChecklistSubentryMake(HTMLGenerateSpanFont("BCZ: Blood Cubic Zirconia skills", "brown"), description), 11).ChecklistEntrySetIDTag("bcz important skills"));
+
+ int pheromoneBlasts = get_property_int("markYourTerritoryCharges");
+ if (pheromoneBlasts > 0)
+ {
+ string [int] description2;
+ description2.listAppend("Instakill no items/meat");
+ resource_entries.listAppend(ChecklistEntryMake("__skill mark your territory", "", ChecklistSubentryMake(pluralise(pheromoneBlasts, "BCZ pheromone", "BCZ pheromones"), "", description2), 0).ChecklistEntrySetCombinationTag("banish").ChecklistEntrySetIDTag("BCZ pheromone banish"));
+ }
+}
\ No newline at end of file
diff --git a/Source/relay/TourGuide/Items of the Month/2025/Peridot.ash b/Source/relay/TourGuide/Items of the Month/2025/Peridot.ash
index c2da9ce6..31f6a5c9 100644
--- a/Source/relay/TourGuide/Items of the Month/2025/Peridot.ash
+++ b/Source/relay/TourGuide/Items of the Month/2025/Peridot.ash
@@ -2,7 +2,11 @@
RegisterTaskGenerationFunction("IOTMPeridotGenerateTasks");
void IOTMPeridotGenerateTasks(ChecklistEntry [int] task_entries, ChecklistEntry [int] optional_task_entries, ChecklistEntry [int] future_task_entries)
{
- if ($item[peridot of peril].available_amount() == 0) return;
+ // TODO: tile additions
+ // - add a resource tile outlining useful monsters currently accessible w/ peridot
+ // - not a tile addition, but add a peridot icon to location bar if you can use peridot in the zone
+
+ if (!__iotms_usable[lookupItem("Peridot of Peril")]) return;
string url = "inventory.php?ftext=peridot+of+peril";
string [int] description;
@@ -17,4 +21,104 @@ void IOTMPeridotGenerateTasks(ChecklistEntry [int] task_entries, ChecklistEntry
description.listAppend(HTMLGenerateSpanFont("Equip the peridot to map monsters", "red"));
optional_task_entries.listAppend(ChecklistEntryMake("__item peridot of peril", "", ChecklistSubentryMake("Peridot picking power", description), 10).ChecklistEntrySetIDTag("peridot task"));
}
+}
+
+Record PeriFight
+{
+ location zone;
+ monster target;
+ boolean useful;
+ boolean canAdv;
+ boolean periUsed;
+};
+
+PeriFight makePeriFight(string z, string mon, boolean useful) {
+ PeriFight final;
+
+ final.zone = lookupLocation(z);
+ final.target = lookupMonster(mon);
+ final.useful = useful;
+ final.canAdv = can_adventure(final.zone);
+ final.periUsed = false;
+
+ foreach key,place in get_property("_perilLocations").split_string(",") {
+ if (place.to_int() == final.zone.to_int()) final.periUsed = true;
+ }
+
+ return final;
+}
+
+void listAppend(PeriFight [int] list, PeriFight entry)
+{
+ int position = list.count();
+ while (list contains position)
+ position += 1;
+ list[position] = entry;
+}
+
+string stringPeriFight (PeriFight entry) {
+ return entry.target.to_string()+" "+HTMLGenerateSpanFont("("+entry.zone.to_string()+")", "0.5em");
+}
+
+RegisterResourceGenerationFunction("IOTMPeridotGenerateResource");
+void IOTMPeridotGenerateResource(ChecklistEntry [int] resource_entries) {
+ if (!__iotms_usable[lookupItem("Peridot of Peril")]) return;
+
+ boolean peridotEquipped = lookupItem("peridot of peril").equipped_amount() > 0;
+ string [int] perilLocations = get_property("_perilLocations").split_string(",");
+
+ string title = "Use your Peridot of Peril";
+ string subtitle = "once a day in every zone";
+ string url = peridotEquipped ? "main.php" : "inventory.php?ftext=peridot+of+peril";
+ string [int] description;
+ PeriFight [int] periFolks;
+ string [int] peridotPicks;
+ string pp = HTMLGenerateSpanFont("❖ ","green");
+
+ // Populate periFolks, level by level...
+ // Level 5 (none for 2, 3, 4...)
+ periFolks.listAppend(makePeriFight("Cobb's Knob Harem","Knob Goblin Harem Girl",!have_outfit_components("Knob Goblin Harem Girl Disguise")));
+ // Level 7 (none for 6...)
+ periFolks.listAppend(makePeriFight("The Defiled Niche","dirty old lihc",!__quest_state["Cyrpt"].state_boolean["niche finished"]));
+ periFolks.listAppend(makePeriFight("The Defiled Nook","toothy sklelton",!__quest_state["Cyrpt"].state_boolean["nook finished"]));
+ // Level 9 (none for 8...)
+ periFolks.listAppend(makePeriFight("Twin Peak","bearpig topiary animal",get_property_int("twinPeakProgress") < 14));
+ // Level 10
+ periFolks.listAppend(makePeriFight("The Beanbat Chamber","beanbat",!__quest_state["Level 10"].state_boolean["beanstalk grown"] && $item[enchanted bean].available_amount() == 0));
+ periFolks.listAppend(makePeriFight("The Penultimate Fantasy Airship","quiet healer",__quest_state["Castle"].mafia_internal_step < 7));
+ // Level 11 (unlocks)
+ periFolks.listAppend(makePeriFight("The Hidden Temple","baa-relief sheep",!get_property_ascension("lastTempleUnlock")));
+ // Level 11 (spookyraven)
+ periFolks.listAppend(makePeriFight("The Haunted Library","writing desk",!get_property_ascension("lastSecondFloorUnlock")));
+ periFolks.listAppend(makePeriFight("The Haunted Bedroom","animated ornate nightstand",$location[the haunted ballroom].turnsAttemptedInLocation() == 0 && $item[Lady Spookyraven's finest gown].available_amount() == 0));
+ periFolks.listAppend(makePeriFight("The Haunted Wine Cellar","possessed wine rack",__quest_state["Level 11 Manor"].mafia_internal_step < 4 && $items[wine bomb, unstable fulminate, bottle of Chateau de Vinegar].available_amount() == 0));
+ periFolks.listAppend(makePeriFight("The Haunted Laundry Room","cabinet of Dr. Limpieza",__quest_state["Level 11 Manor"].mafia_internal_step < 4 && $items[wine bomb, unstable fulminate, blasting soda].available_amount() == 0));
+ periFolks.listAppend(makePeriFight("The Haunted Boiler Room","monstrous boiler",__quest_state["Level 11 Manor"].mafia_internal_step < 4 && $item[wine bomb].available_amount() != 0));
+ // Level 11 (hidden city)
+ periFolks.listAppend(makePeriFight("The Hidden Bowling Alley","pygmy bowler",get_property_int("hiddenBowlingAlleyProgress") < 6));
+ periFolks.listAppend(makePeriFight("The Hidden Hospital","pygmy witch surgeon",get_property_int("hiddenHospitalProgress") < 6));
+ periFolks.listAppend(makePeriFight("The Hidden Office Building","pygmy witch accountant",get_property_int("hiddenOfficeProgress") < 6));
+ // Level 11 (zeppelin)
+ // todo: red butler
+ // Level 11 (palindome)
+ periFolks.listAppend(makePeriFight("Inside the Palindome","racecar bob",!__quest_state["Level 11 Palindome"].finished));
+ // Level 12
+ // todo: gremlins
+
+ foreach key,entry in periFolks {
+ if (entry.useful && entry.canadv) {
+ string color = entry.periUsed ? "gray" : "black";
+ peridotPicks.listAppend(HTMLGenerateSpanFont(stringPeriFight(entry),color, "0.9em"));
+ }
+ }
+
+ // If no Peridot Picks, no resource tile
+ if (peridotPicks.count() == 0) return;
+
+ description.listAppend("Select relevant, available monsters!");
+ if (!peridotEquipped) description.listAppend(HTMLGenerateSpanFont("Equip your Peridot of Peril","red"));
+ description.listAppend("
|*"+pp+peridotPicks.listJoinComponents("
|*"+pp));
+
+ resource_entries.listAppend(ChecklistEntryMake("__item Peridot of Peril", url, ChecklistSubentryMake(title, subtitle, description), 14).ChecklistEntrySetIDTag("peridot picking helper"));
+
}
\ No newline at end of file
diff --git a/Source/relay/TourGuide/Items of the Month/2025/Prismatic Beret.ash b/Source/relay/TourGuide/Items of the Month/2025/Prismatic Beret.ash
index 822f85e2..af628ddd 100644
--- a/Source/relay/TourGuide/Items of the Month/2025/Prismatic Beret.ash
+++ b/Source/relay/TourGuide/Items of the Month/2025/Prismatic Beret.ash
@@ -2,7 +2,11 @@
RegisterResourceGenerationFunction("IOTMPrismaticBeretGenerateResource");
void IOTMPrismaticBeretGenerateResource(ChecklistEntry [int] resource_entries)
{
- if ($item[prismatic beret].available_amount() == 0) return;
+ // TODO: tile additions
+ // - add beret busk you will get here
+ // - maybe add easily accessible hats/pants too
+
+ if (!__iotms_usable[lookupItem("prismatic beret")]) return;
string url = "inventory.php?ftext=prismatic+beret";
int busksLeft = clampi(5 - get_property_int("_beretBuskingUses"), 0, 5);
diff --git a/Source/relay/TourGuide/Items of the Month/2025/Seadent.ash b/Source/relay/TourGuide/Items of the Month/2025/Seadent.ash
new file mode 100644
index 00000000..f9b4fc50
--- /dev/null
+++ b/Source/relay/TourGuide/Items of the Month/2025/Seadent.ash
@@ -0,0 +1,46 @@
+//monodent of the sea
+RegisterResourceGenerationFunction("IOTMMonodentGenerateResource");
+void IOTMMonodentGenerateResource(ChecklistEntry [int] resource_entries)
+{
+ // TODO: tile updates:
+ // - remove indigo/blue, no need for color on this
+
+
+ if (!__iotms_usable[lookupItem("monodent of the sea")]) return;
+
+ string url = "inventory.php?ftext=dent+of+the+sea";
+ int monodentLightningsLeft = clampi(11 - get_property_int("_seadentLightningUsed"), 0, 11);
+ boolean monodentWaveUsed = get_property_boolean("_seadentWaveUsed");
+ string monodentWaveZone = get_property("_seadentWaveZone");
+ string [int] description;
+ string title = "Monodent/Seadent powers";
+
+ if (!monodentWaveUsed) {
+ description.listAppend(HTMLGenerateSpanFont("Flood a zone for +30% item/meat", "blue"));
+ }
+ else if (monodentWaveUsed) {
+ description.listAppend(HTMLGenerateSpanFont(monodentWaveZone + " flooded, +30 item/meat", "indigo"));
+ if ($effect[fishy].have_effect() < 1 && lookupItem("monodent of the sea").equipped_amount() == 0) {
+ description.listAppend(HTMLGenerateSpanFont("Equip Monodent for Fishy?", "red"));
+ }
+ }
+ if (monodentLightningsLeft > 0) {
+ description.listAppend("Killbanish, preserves drops. " + monodentLightningsLeft + " left.");
+ }
+
+ int monodentUpgrades = get_property_int("seadentConstructKills");
+ if (monodentUpgrades < 100) {
+ int constructsNeededForNextPerk = floor(sqrt(monodentUpgrades) + 1) ** 2 - monodentUpgrades;
+ description.listAppend("Current upgrades: " + monodentUpgrades + "/100");
+ description.listAppend((HTMLGenerateSpanFont(constructsNeededForNextPerk, "blue")) + " constructs needed for upgrade");
+ }
+ if (lookupItem("monodent of the sea").equipped_amount() == 0)
+ {
+ description.listAppend(HTMLGenerateSpanFont("Equip the Seadent first", "red"));
+ }
+ if (lookupItem("monodent of the sea").equipped_amount() > 0)
+ {
+ description.listAppend(HTMLGenerateSpanFont("Seadent lightning ready!", "blue"));
+ }
+ resource_entries.listAppend(ChecklistEntryMake("__item monodent of the sea", url, ChecklistSubentryMake(title, "", description)));
+}
\ No newline at end of file
diff --git a/Source/relay/TourGuide/Items of the Month/2025/Shrunken Head.ash b/Source/relay/TourGuide/Items of the Month/2025/Shrunken Head.ash
new file mode 100644
index 00000000..88ee540e
--- /dev/null
+++ b/Source/relay/TourGuide/Items of the Month/2025/Shrunken Head.ash
@@ -0,0 +1,171 @@
+//shrunken head
+RegisterTaskGenerationFunction("IOTMShrunkenHeadGenerateTasks");
+void IOTMShrunkenHeadGenerateTasks(ChecklistEntry [int] task_entries, ChecklistEntry [int] optional_task_entries, ChecklistEntry [int] future_task_entries)
+{
+ // TODO: last optional thing i don't really want to do rn but could do later:
+ // - convert current targets to hoverover recommendations w/ filtering
+
+ if (!__iotms_usable[lookupItem("Shrunken Head")]) return;
+
+ monster headZombie = get_property("shrunkenHeadZombieMonster").to_monster();
+ int headZombieHP = get_property_int("shrunkenHeadZombieHP");
+
+ string url = "inventory.php?ftext=shrunken+head";
+ string [int] supernagDescription;
+
+ // Generate a supernag task if head is equipped that inspires the user to toss it at a monster
+ if (lookupItem("shrunken head").equipped_amount() > 0)
+ {
+ supernagDescription.listAppend("Throw it at a monster for a zombified friend!");
+ task_entries.listAppend(ChecklistEntryMake("__item shrunken head", url, ChecklistSubentryMake(HTMLGenerateSpanFont("Shrunken Head equipped", "blue"), supernagDescription), -11).ChecklistEntrySetIDTag("shrunken head"));
+ }
+
+ string [int] description;
+ string [int] itemList;
+ string title;
+ string subtitle;
+
+ // Generate an optional task for killing your zombie
+ if (headZombieHP > 0) {
+ title = "Current Zombie: "+headZombie.name;
+ subtitle = headZombieHP+" HP remaining";
+
+ string headEffects = shrunken_Head_Zombie(headZombie).listJoinComponents(", ");
+ string [string] abilityCompression = {
+ "Item Drop Bonus":"item%",
+ "Meat Drop Bonus":"meat%",
+ "Physical Attack":"atk",
+ "Hot Attack": HTMLGenerateSpanFont("atk","r_element_hot_desaturated"),
+ "Cold Attack": HTMLGenerateSpanFont("atk","r_element_cold_desaturated"),
+ "Sleaze Attack": HTMLGenerateSpanFont("atk","r_element_sleaze_desaturated"),
+ "Stench Attack": HTMLGenerateSpanFont("atk","r_element_stench_desaturated"),
+ "Spooky Attack": HTMLGenerateSpanFont("atk","r_element_spooky_desaturated"),
+ "MP Regen": "mp",
+ "HP Regen": "hp",
+ };
+
+ foreach key,value in abilityCompression
+ {
+ headEffects = headEffects.replace_string(key, value);
+ }
+
+ // parse through item drops array, only grabbing things that might drop
+ foreach key, r in headZombie.item_drops_array() {
+ item it = r.drop;
+ int base_drop_rate = r.rate;
+
+ // ignore items that are:
+ // n = not pickpocketable
+ // p = pickpocket-only
+ // b = bounty item
+ // a = accordion
+ if (!r.type.contains_text("n") && !r.type.contains_text("p") && !r.type.contains_text("a") && !r.type.contains_text("b")) {
+ string [int] item_drop_modifiers_to_display;
+ if (base_drop_rate > 0 && base_drop_rate < 100) {
+ float effective_drop_rate = base_drop_rate;
+ float item_modifier = item_drop_modifier();
+ if (it.fullness > 0 || it.cookable)
+ {
+ item_modifier += numeric_modifier("Food Drop");
+ item_drop_modifiers_to_display.listAppend("+food");
+ }
+ if (it.inebriety > 0 || it.mixable)
+ {
+ item_modifier += numeric_modifier("Booze Drop");
+ item_drop_modifiers_to_display.listAppend("+booze");
+ }
+ if (it.to_slot() == $slot[hat])
+ {
+ item_modifier += numeric_modifier("Hat Drop");
+ item_drop_modifiers_to_display.listAppend("+hat");
+ }
+ if (it.to_slot() == $slot[weapon])
+ {
+ item_modifier += numeric_modifier("Weapon Drop");
+ item_drop_modifiers_to_display.listAppend("+weapon");
+ }
+ if (it.to_slot() == $slot[off-hand])
+ {
+ item_modifier += numeric_modifier("Offhand Drop");
+ item_drop_modifiers_to_display.listAppend("+offhand");
+ }
+ if (it.to_slot() == $slot[shirt])
+ {
+ item_modifier += numeric_modifier("Shirt Drop");
+ item_drop_modifiers_to_display.listAppend("+shirt");
+ }
+ if (it.to_slot() == $slot[pants])
+ {
+ item_modifier += numeric_modifier("Pants Drop");
+ item_drop_modifiers_to_display.listAppend("+pants");
+ }
+ if ($slots[acc1,acc2,acc3] contains it.to_slot())
+ {
+ item_modifier += numeric_modifier("Accessory Drop");
+ item_drop_modifiers_to_display.listAppend("+accessory");
+ }
+ if (it.candy)
+ {
+ item_modifier += numeric_modifier("Candy Drop");
+ item_drop_modifiers_to_display.listAppend("+candy");
+ }
+ if ($slots[hat,weapon,off-hand,back,shirt,pants,acc1,acc2,acc3] contains it.to_slot()) //assuming familiar equipment isn't "gear"
+ {
+ item_modifier += numeric_modifier("Gear Drop");
+ item_drop_modifiers_to_display.listAppend("+gear");
+ }
+ if (it == $item[black picnic basket] && $skill[Bear Essence].have_skill())
+ {
+ item_modifier += 20.0 * MAX(1, get_property_int("skillLevel134"));
+ }
+
+ // subtract familiar item drop modifier because of reasons
+ int effective_familiar_weight = my_familiar().familiar_weight() + numeric_modifier("familiar weight");
+ int familiar_weight_from_familiar_equipment = $slot[familiar].equipped_item().numeric_modifier("familiar weight"); //need to cancel it out
+ float familiar_item_drop = my_familiar().numeric_modifier("item drop", effective_familiar_weight - familiar_weight_from_familiar_equipment, $slot[familiar].equipped_item());
+
+ effective_drop_rate *= 1.0 + (item_modifier-familiar_item_drop) / 100.0;
+ effective_drop_rate = clampf(floor(effective_drop_rate), 0.0, 100.0);
+
+ itemList.listAppend(to_string(effective_drop_rate)+"% "+it.name+HTMLGenerateSpanFont(" ( "+to_string(to_int(base_drop_rate))+"% "+item_drop_modifiers_to_display.listJoinComponents(", ")+")","gray","0.8em"));
+ }
+ }
+ }
+
+ description.listAppend("Active Effects: "+headEffects);
+
+ if (itemList.count() > 0) {
+ description.listAppend("If your zombie dies next turn, here's the % chance you get each item in the drop table!");
+ description.listAppend("
|*"+itemList.listJoinComponents("
|*"));
+ }
+
+ optional_task_entries.listAppend(ChecklistEntryMake("__item shrunken head", url, ChecklistSubentryMake(title, subtitle, description), 9).ChecklistEntrySetIDTag("Shrunken Head zombie"));
+
+ }
+}
+
+
+RegisterResourceGenerationFunction("IOTMShrunkenHeadGenerateResource");
+void IOTMShrunkenHeadGenerateResource(ChecklistEntry [int] resource_entries)
+{
+ if (!__iotms_usable[lookupItem("Shrunken Head")]) return;
+
+ monster headZombie = get_property("shrunkenHeadZombieMonster").to_monster();
+ int headZombieHP = get_property_int("shrunkenHeadZombieHP");
+
+ string title = "Shrunken Head targets";
+ string url = "inventory.php?ftext=shrunken+head";
+ string [int] description;
+
+ description.listAppend("Consider reanimating these monsters for their items:");
+ description.listAppend("|*dairy goat"+HTMLGenerateSpanFont(" (40%)", "gray", "0.8em"));
+ description.listAppend("|*pygmy bowler"+HTMLGenerateSpanFont(" (40%)", "gray", "0.8em"));
+ description.listAppend("|*baa-relief sheep"+HTMLGenerateSpanFont(" (25%)", "gray", "0.8em"));
+ description.listAppend("|*pygmy janitor"+HTMLGenerateSpanFont(" (20%)", "gray", "0.8em"));
+ description.listAppend("|*spiny or toothy skeletons"+HTMLGenerateSpanFont(" (20%)", "gray", "0.8em"));
+ description.listAppend("|*banshee librarian"+HTMLGenerateSpanFont(" (10%)", "gray", "0.8em"));
+ description.listAppend("|*mountain man"+HTMLGenerateSpanFont(" (10%)", "gray", "0.8em"));
+ description.listAppend("|*Any ol' Smut Orc"+HTMLGenerateSpanFont(" (10%)", "gray", "0.8em"));
+
+ resource_entries.listAppend(ChecklistEntryMake("__item shrunken head", url, ChecklistSubentryMake(title, "", description), 9).ChecklistEntrySetIDTag("Shrunken Head Targets"));
+}
\ No newline at end of file
diff --git a/Source/relay/TourGuide/Items of the Month/2025/Skeleton of Crimbo Past.ash b/Source/relay/TourGuide/Items of the Month/2025/Skeleton of Crimbo Past.ash
new file mode 100644
index 00000000..268662f4
--- /dev/null
+++ b/Source/relay/TourGuide/Items of the Month/2025/Skeleton of Crimbo Past.ash
@@ -0,0 +1,54 @@
+//crimbo skeleton
+RegisterTaskGenerationFunction("IOTMSkeletonOfCrimboPastGenerateTasks");
+// TODO: minor clean-up
+// - add conditional URL to send to familiar if not equipped w/in resource
+// - keep the % phylum table in a hoverover but if they have skellies left just point to skellies
+
+void IOTMSkeletonOfCrimboPastGenerateTasks(ChecklistEntry [int] task_entries, ChecklistEntry [int] optional_task_entries, ChecklistEntry [int] future_task_entries)
+{
+ if (!lookupFamiliar("skeleton of crimbo past").familiar_is_usable()) return;
+ string url = "main.php?talktosocp=1";
+ string [int] description;
+
+ int fightKnucklebones = get_property_int("_knuckleboneDrops");
+ int restKnucklebones = get_property_int("_knuckleboneRests");
+ int totalKnucklebonesLeft = clampi(100 - (fightKnucklebones), 0, 100);
+ description.listAppend(5 - restKnucklebones + " rest knucklebones available.");
+
+ if (totalKnucklebonesLeft == 0 && my_familiar() == lookupFamiliar("skeleton of crimbo past")) {
+ task_entries.listAppend(ChecklistEntryMake("__familiar skeleton of crimbo past", url, ChecklistSubentryMake(HTMLGenerateSpanFont("No more knucklebone drops", "red"), description), -11).ChecklistEntrySetIDTag("crimbo skeleton knucklebones"));
+ }
+ else if (totalKnucklebonesLeft > 0 && my_familiar() == lookupFamiliar("skeleton of crimbo past")) {
+ task_entries.listAppend(ChecklistEntryMake("__familiar skeleton of crimbo past", url, ChecklistSubentryMake(HTMLGenerateSpanFont(totalKnucklebonesLeft + " knucklebone drops left", "green"), description), -11).ChecklistEntrySetIDTag("crimbo skeleton knucklebones"));
+ }
+}
+
+RegisterResourceGenerationFunction("IOTMSkeletonOfCrimboPastGenerateResource");
+void IOTMSkeletonOfCrimboPastGenerateResource(ChecklistEntry [int] resource_entries)
+{
+ if (!lookupFamiliar("skeleton of crimbo past").familiar_is_usable()) return;
+ string url = "main.php?talktosocp=1";
+ string [int] description;
+ string title;
+
+ int fightKnucklebones = get_property_int("_knuckleboneDrops");
+ int restKnucklebones = get_property_int("_knuckleboneRests");
+ int totalKnucklebonesLeft = clampi(100 - (fightKnucklebones), 0, 100);
+
+ description.listAppend(5 - restKnucklebones + " rest knucklebones available.");
+
+ if (totalKnucklebonesLeft == 0) {
+ title = (HTMLGenerateSpanFont("No more knucklebone drops", "red"));
+ }
+ else if (totalKnucklebonesLeft > 0) {
+ title = (HTMLGenerateSpanFont(totalKnucklebonesLeft + " knucklebone drops left", "green"));
+ description.listAppend("|*90% - skeleton");
+ description.listAppend("|*70% - orc / pirate");
+ description.listAppend("|*50% - dude / elf / hobo");
+ description.listAppend("|*30% - beast / demon / goblin / humanoid / undead");
+ description.listAppend("|*20% - fish / penguin / weird");
+ description.listAppend("|*10% - construct / bug");
+ description.listAppend("|*0% - constellation / elemental / hippy / horror / mer-kin / plant / slime");
+ }
+ resource_entries.listAppend(ChecklistEntryMake("__familiar skeleton of crimbo past", url, ChecklistSubentryMake(title, description), 11));
+}
\ No newline at end of file
diff --git a/Source/relay/TourGuide/Items of the Month/2026/Archaeologist's Spade.ash b/Source/relay/TourGuide/Items of the Month/2026/Archaeologist's Spade.ash
new file mode 100644
index 00000000..790be11a
--- /dev/null
+++ b/Source/relay/TourGuide/Items of the Month/2026/Archaeologist's Spade.ash
@@ -0,0 +1,45 @@
+// archaeologist's spade
+RegisterResourceGenerationFunction("IOTMArchaeologistSpadeGenerateResource");
+void IOTMArchaeologistSpadeGenerateResource(ChecklistEntry [int] resource_entries) {
+
+ if (!__iotms_usable[lookupItem("Archaeologist's Spade")]) return;
+
+ // end if no digs left
+ int digsLeft = clampi(11 - get_property_int("_archSpadeDigs"), 0, 11);
+ if (digsLeft == 0) return;
+
+ string title;
+ string url;
+ string [int] description;
+
+ url = "inv_use.php?pwd=" + my_hash() + "&whichitem=12184";
+ title = pluralise(digsLeft, "Archaeologist's Spade dig","Archaeologist's Spade digs");
+
+ // freekill combination tag for skelly digs
+ resource_entries.listAppend(ChecklistEntryMake("__item Archaeologist's Spade", url, ChecklistSubentryMake(title, "", "Free kill a skeleton"), 0).ChecklistEntrySetCombinationTag("free instakill").ChecklistEntrySetIDTag("Archaeologist's Spade free kill"));
+
+ // general resource tile, near end of the line probably
+ description.listAppend("Excavate free skeletons!");
+
+ // how many useful skeletons do you have left?
+ int approxKitchenTurnsLeft = ceil(MAX(0, 21 - get_property_int("manorDrawerCount")) / 4) + to_int(lookupItem("Spookyraven billiards room key").available_amount() < 1);
+ if (approxKitchenTurnsLeft > 0) description.listAppend("|*"+pluralise(approxKitchenTurnsLeft,"skelly","skellies")+" in the Haunted Kitchen");
+
+ int approxBallroomTurnsLeft = delayRemainingInLocation($location[the haunted ballroom]);
+ if (approxBallroomTurnsLeft > 0) description.listAppend("|*"+pluralise(approxBallroomTurnsLeft,"skelly","skellies")+" in the Haunted Ballroom");
+
+ int approxNookTurnsLeft = ceil(max(0,get_property_int("cyrptNookEvilness")-13-$item[evil eye].available_amount() * 3) / 3);
+ if (approxNookTurnsLeft > 0) description.listAppend("|*"+pluralise(approxNookTurnsLeft,"skelly","skellies")+" in the Defiled Nook");
+
+ // only generate if you actually have a possibility of doing the garves snake
+ if (__shen_items_to_locations[get_property_item("shenQuestItem")] == $location[The VERY Unquiet Garves] || get_property_int("shenInitiationDay") == 0 || get_property("questL11Shen") != "finished") {
+ int approxGarvesTurnsLeft = ceil(max(0,5-$location[the unquiet garves].turns_spent));
+ if (approxGarvesTurnsLeft > 0) {
+ description.listAppend("|*"+pluralise(approxGarvesTurnsLeft,"skelly","skellies")+" in the Unquiet Garves");
+ if (get_property_int("shenInitiationDay") != 1) description.listAppend("|*(Start Shen on D2 for Garve skellies)");
+ }
+ }
+
+ resource_entries.listAppend(ChecklistEntryMake("__item Archaeologist's Spade", url, ChecklistSubentryMake(title, "", description)).ChecklistEntrySetIDTag("Archaeologist Spade skellies"));
+
+}
\ No newline at end of file
diff --git a/Source/relay/TourGuide/Items of the Month/2026/Baseball Diamond.ash b/Source/relay/TourGuide/Items of the Month/2026/Baseball Diamond.ash
new file mode 100644
index 00000000..cf4c889f
--- /dev/null
+++ b/Source/relay/TourGuide/Items of the Month/2026/Baseball Diamond.ash
@@ -0,0 +1,129 @@
+// baseball diamond
+// helper function to generate monster lineup
+monster [int] baseballBuddies() {
+ monster [int] finalTeam;
+ string [int] rawTeam = get_property("baseballTeam").split_string(",");
+
+ foreach i, playerID in rawTeam {
+ finalTeam[i] = to_monster(to_int(playerID));
+ }
+
+ return finalTeam;
+}
+
+// Currently, most of what I wanted to do in tasks is already handled in the resource tiles. I may someday add some task coverage but for right now this is just a TODO.
+// RegisterTaskGenerationFunction("IOTMBaseballDiamondGenerateTasks");
+// void IOTMBaseballDiamondGenerateTasks(ChecklistEntry [int] task_entries, ChecklistEntry [int] optional_task_entries, ChecklistEntry [int] future_task_entries)
+// {
+// // tasks should include
+// // - recruit monsters for your baseball lineup if < 9
+// // - current lineup hoverover
+// // - maybe a supernag if you have a useful freekill + YR in the last 3 monsters?
+// // - supernag up top w/ what the pitch sequences do
+
+
+// if (!__iotms_usable[lookupItem("Baseball Diamond")]) return;
+// monster [int] myTeam = baseballBuddies();
+// int inningsPlayed = get_property_int("_baseballInnings");
+// monster curveballMonster = to_monster(get_property("_curveballMonster"));
+// int curveballFightsLeft = get_property_int("_curveballFightsLeft");
+// boolean baseballEquipped = lookupItem("Baseball Diamond").equipped_amount() > 0;
+// boolean currentlyPlayingBaseball = get_property("lastEncounter") == "Play Ball!" && get_property("baseballTeam") != "";
+// }
+
+RegisterResourceGenerationFunction("IOTMBaseballDiamondGenerateResource");
+void IOTMBaseballDiamondGenerateResource(ChecklistEntry [int] resource_entries)
+{
+ // resource should include
+ // - number of ball games remaining
+ // - monsters to consider
+ // => use shrunken head for YR
+ // => repeated nonfree monsters for freekill
+ // => feesh for ML
+
+ if (!__iotms_usable[lookupItem("Baseball Diamond")]) return;
+
+ ChecklistSubentry [int] subentries;
+
+ boolean baseballEquipped = lookupItem("Baseball Diamond").equipped_amount() > 0;
+ string url = baseballEquipped ? "inventory.php?which=2" : "inventory.php?ftext=baseball+diamond";
+ int inningsPlayed = get_property_int("_baseballInnings");
+ monster [int] myTeam = baseballBuddies();
+ int monstersNeededToPlayBall = clampi(9-myTeam.count(),0,9);
+
+ // For this tile, we'll start with an overall "what should you use your remaining ballgames on" and transition into "what -have- you used these things on, currently"
+
+ // Generate recommendations with your upcoming innings
+ if (inningsPlayed < 3) {
+ string title = "Play "+pluralise(clampi(3-inningsPlayed, 0, 3),"more inning","more innings")+" of Baseball";
+ string [int] description;
+ if (myTeam.count() < 9) {
+ if (baseballEquipped) description.listAppend("Find "+pluralise(monstersNeededToPlayBall,"more monster","more monsters")+" to play ball!");
+ if (!baseballEquipped) description.listAppend(HTMLGenerateSpanOfClass("Equip your Baseball Diamond","r_element_hot")+" to find "+pluralise(monstersNeededToPlayBall,"more monster","more monsters")+" to play ball!");
+ }
+
+ if (myTeam.count() == 9) {
+ description.listAppend("You've collected enough monsters -- you can play your next inning whenever you want. Remember, try to have your curveball and scorcher targets in the last 3-4 monster slots!");
+ }
+ buffer tooltip;
+ tooltip.append(HTMLGenerateTagWrap("div","Baseball Diamond Lineup",mapMake("class","r_bold r_centre", "style", "padding-bottom:0.25em;")));
+
+ foreach key, mon in baseballBuddies() {
+ tooltip.append("#"+(key+1)+": "+mon.name+"
");
+ }
+
+ string fullTooltip = HTMLGenerateSpanOfClass(HTMLGenerateSpanOfClass(tooltip,"r_tooltip_inner_class r_tooltip_inner_class_margin")+"View your current lineup.","r_tooltip_outer_class");
+ description.listAppend(fullTooltip);
+
+
+ subentries.listAppend(ChecklistSubentryMake(title, description));
+ }
+
+ // Non-Euclidian Curveball sub-tile
+ monster curveballMonster = to_monster(get_property("_curveballMonster"));
+ int curveballFightsLeft = get_property_int("_curveballFightsLeft");
+
+ if (curveballMonster != $monster[none]) {
+ subentries.listAppend(ChecklistSubentryMake(pluralise(curveballFightsLeft, "free copy of", "free copies of") + " "+HTMLGenerateSpanOfClass(curveballMonster.name, "r_element_epic")+" remaining", "naturally free fights!",""));
+ }
+
+ // Some Cheddar sub-tile; annoying because I've never abstracted tracked monsters...
+ // TODO: make a tracked monster olfaction management tile lol
+ monster cheddarMonster = $monster[none];
+ string [int] trackedMonstersSplit = get_property("trackedMonsters").split_string(":");
+ foreach key, str in trackedMonstersSplit {
+ if (str == "Baseball Diamond") cheddarMonster = to_monster(trackedMonstersSplit[key-1]);
+ }
+ if (cheddarMonster != $monster[none]) {
+ subentries.listAppend(ChecklistSubentryMake(HTMLGenerateSpanOfClass(cheddarMonster.name,"r_element_stench")+" tracked by a Cheddarball", "an olfaction-esque tracker!",""));
+ }
+
+ // Minor monster tracking sub-tile
+ // Currently commented out because we don't really need it
+ // monster screwballMonster = to_monster(get_property("_screwballMonster"));
+ // monster beanballMonster = to_monster(get_property("_beanballMonster"));
+ // monster skullballMonster = to_monster(get_property("_skullballMonster"));
+
+ // string [int] minorTracking;
+ // string minorSize = "0.9em";
+
+ // if (screwballMonster != $monster[none]) {
+ // minorTracking.listAppend(HTMLGenerateSpanFont("+ML: "+screwballMonster+")", "r_element_sleaze_desaturated", minorSize));
+ // }
+
+ // if (beanballMonster != $monster[none]) {
+ // minorTracking.listAppend(HTMLGenerateSpanFont("Passive Stench: "+beanballMonster, "r_element_stench_desaturated", minorSize));
+ // }
+
+ // if (skullballMonster != $monster[none]) {
+ // minorTracking.listAppend(HTMLGenerateSpanFont("Deleveled: "+skullballMonster, "r_element_spooky_desaturated", minorSize));
+
+ // }
+
+ // if (minorTracking.count() > 0) subentries.listAppend(ChecklistSubentryMake("Baseball Modified Monsters","",minorTracking));
+
+ // The iceball banish is tracked in the banish combo tile.
+
+ if (subentries.count() > 0)
+ resource_entries.listAppend(ChecklistEntryMake("__item baseball diamond", url, subentries, 12).ChecklistEntrySetIDTag("Baseball Diamond resource"));
+}
\ No newline at end of file
diff --git a/Source/relay/TourGuide/Items of the Month/2026/Eternity Codpiece.ash b/Source/relay/TourGuide/Items of the Month/2026/Eternity Codpiece.ash
new file mode 100644
index 00000000..a4076310
--- /dev/null
+++ b/Source/relay/TourGuide/Items of the Month/2026/Eternity Codpiece.ash
@@ -0,0 +1,20 @@
+// the eternity codpiece
+
+RegisterResourceGenerationFunction("IOTMEternityCodpieceGenerateResource");
+void IOTMEternityCodpieceGenerateResource(ChecklistEntry [int] resource_entries) {
+ // TODO: include following info:
+ // - current enchantment
+ // - alternate available gems + their enchantments
+ // - remind user about BCZ/heartstone in codpiece, maybe note that peridot/baseball can be there too but might not be recommended?
+ // - use Pantogram slot structure maybe
+
+ // Detect current gems via slot
+ item cod1 = equipped_item($slot[codpiece1]);
+ item cod2 = equipped_item($slot[codpiece2]);
+ item cod3 = equipped_item($slot[codpiece3]);
+ item cod4 = equipped_item($slot[codpiece4]);
+ item cod5 = equipped_item($slot[codpiece5]);
+
+ if (!__iotms_usable[lookupItem("The Eternity Codpiece")]) return;
+
+}
\ No newline at end of file
diff --git a/Source/relay/TourGuide/Items of the Month/2026/Heartstone.ash b/Source/relay/TourGuide/Items of the Month/2026/Heartstone.ash
new file mode 100644
index 00000000..2706aeb0
--- /dev/null
+++ b/Source/relay/TourGuide/Items of the Month/2026/Heartstone.ash
@@ -0,0 +1,174 @@
+// heartstone
+
+RegisterTaskGenerationFunction("IOTMHeartstoneGenerateTasks");
+void IOTMHeartstoneGenerateTasks(ChecklistEntry [int] task_entries, ChecklistEntry [int] optional_task_entries, ChecklistEntry [int] future_task_entries)
+{
+ // This is a TAPE-helper optional task tile.
+ if (!__iotms_usable[lookupItem("Heartstone")]) return;
+
+ // What are the heartstone's letters?
+ string heartLetters = get_property("heartstoneLetters");
+ if (heartLetters.length() > 3) heartLetters = "";
+ string hackerToday = "";
+
+ // Is the heartstone equipped?
+ boolean heartstoneEquipped = lookupItem("Heartstone").equipped_amount() > 0;
+
+ // Generate boolean flags for accessibility of each monster
+ // --- T MONSTERS ----------------
+ boolean nightstandsDone = $item[Lady Spookyraven's finest gown].available_amount() > 0 || get_property("questM21Dance") == "finished";
+ string eightBitColor = get_property("8BitColor"); // and A/E
+ boolean fratGearDone = have_outfit_components("Frat Warrior Fatigues");
+ int shenDay = get_property_int("shenInitiationDay");
+ boolean hospitalDone = get_property("questL11Doctor") == "finished";
+ if ($item[server room key].available_amount() > 0 && get_property_int("_cyberFreeFights") < 10 && lookupSkill("OVERCLOCK(10)").have_skill()) hackerToday = get_property("_cyberZone1Hacker");
+ // --- A MONSTERS ----------------
+ boolean forestDone = get_property_ascension("lastTempleUnlock") && get_property("questL02Larva") == "finished";
+ boolean oilPeakDone = get_property_boolean("oilPeakLit");
+ boolean tombRatsDone = "step3,finished".contains_text(get_property("questL11Pyramid"));
+ boolean oresDone = __quest_state["Level 8"].state_boolean["Past mine"];
+ // --- P MONSTERS ----------------
+ boolean warDone = __quest_state["Level 12"].finished;
+ // --- E MONSTERS ----------------
+ boolean castleDone = __quest_state["Castle"].mafia_internal_step > 8;
+ boolean zeppelinDone = __quest_state["Level 11 Ron"].mafia_internal_step > 4;
+ boolean copperheadDone = get_property("questL11Shen") == "finished";
+ boolean haveMobiusRing = $item[Möbius ring].available_amount() > 0;
+ // --- MISCELLANEOUS -------------
+ boolean chestMimicAccessible = lookupFamiliar("Chest Mimic").familiar_is_usable();
+
+ // Only generate tile if TAPE is achievable in this cycle
+ string nextLetter;
+ string title;
+ string subtitle;
+ string url;
+ string [int] description;
+ string [int] monsterList;
+
+ if (heartLetters == "") {
+ nextLetter = "T";
+ title = "Heartstone: Spell TAPE";
+ subtitle = "give me a T!";
+ monsterList.listAppend(HTMLGreyOutTextUnlessTrue("elegant animated nightstand", !nightstandsDone));
+ monsterList.listAppend(HTMLGreyOutTextUnlessTrue("tektite", eightBitColor == "green"));
+ monsterList.listAppend(HTMLGreyOutTextUnlessTrue("War Frat 151st Infantryman", !fratGearDone));
+ monsterList.listAppend(HTMLGreyOutTextUnlessTrue("The Frattlesnake", shenDay == 2));
+ monsterList.listAppend(HTMLGreyOutTextUnlessTrue("Pygmy Witch Nurse", !hospitalDone));
+ if (hackerToday.contains_text("blue")) monsterList.listAppend("Bluehat Hacker (zone 1!)");
+ if (hackerToday.contains_text("grey")) monsterList.listAppend("Greyhat Hacker (zone 1!)");
+ } else if (heartLetters == "T") {
+ nextLetter = "A";
+ title = "Heartstone: Spell "+HTMLGenerateSpanFont("T","r_element_stench")+"APE";
+ subtitle = "give me an A!";
+ monsterList.listAppend(HTMLGreyOutTextUnlessTrue("bar", !forestDone));
+ monsterList.listAppend(HTMLGreyOutTextUnlessTrue("fleaman", eightBitColor == "black"));
+ monsterList.listAppend(HTMLGreyOutTextUnlessTrue("oil cartel", !oilPeakDone));
+ monsterList.listAppend(HTMLGreyOutTextUnlessTrue("tomb rat king", !tombRatsDone));
+ monsterList.listAppend(HTMLGreyOutTextUnlessTrue("mountain man", !oresDone));
+ monsterList.listAppend(HTMLGreyOutTextUnlessTrue("Black Crayon Fish", chestMimicAccessible));
+
+ } else if (heartLetters == "TA") {
+ nextLetter = "P";
+ title = "Heartstone: Spell "+HTMLGenerateSpanFont("TA","r_element_stench")+"PE";
+ subtitle = "give me a P!";
+ monsterList.listAppend(HTMLGreyOutTextUnlessTrue("War Hippy Baker", !warDone));
+ monsterList.listAppend(HTMLGreyOutTextUnlessTrue("War Hippy Naturopathic Homeopath", !warDone));
+ monsterList.listAppend(HTMLGreyOutTextUnlessTrue("Black Crayon Spiraling Shape", chestMimicAccessible));
+ } else if (heartLetters == "TAP") {
+ nextLetter = "E";
+ title = "Heartstone: Spell "+HTMLGenerateSpanFont("TAP","r_element_stench")+"E";
+ subtitle = "give me an E!";
+ monsterList.listAppend(HTMLGreyOutTextUnlessTrue("met", eightBitColor == "blue"));
+ monsterList.listAppend(HTMLGreyOutTextUnlessTrue("Foodie Giant", !castleDone));
+ monsterList.listAppend(HTMLGreyOutTextUnlessTrue("red skeleton", !zeppelinDone));
+ monsterList.listAppend(HTMLGreyOutTextUnlessTrue("tomb servant", !tombRatsDone));
+ monsterList.listAppend(HTMLGreyOutTextUnlessTrue("Keese", eightBitColor == "green"));
+ monsterList.listAppend(HTMLGreyOutTextUnlessTrue("ninja dressed as a waiter", !copperheadDone));
+ monsterList.listAppend(HTMLGreyOutTextUnlessTrue("Mobile Armored Sweat Lodge", !warDone));
+ monsterList.listAppend(HTMLGreyOutTextUnlessTrue("time cop", haveMobiusRing));
+ } else {
+ return;
+ }
+
+ // If unequipped, go to heartstone in inventory. Else, no URL.
+ if (!heartstoneEquipped) url = invSearch("heartstone");
+
+ // Generate tile description
+ description.listAppend("You need "+nextLetter+"; look for these monsters:|*");
+ description.listAppend("
|*"+monsterList.listJoinComponents("
|*"));
+ if (!heartstoneEquipped) description.listAppend(HTMLGenerateSpanFont("Equip your Heartstone!","red"));
+
+ optional_task_entries.listAppend(ChecklistEntryMake("__item heartstone", url, ChecklistSubentryMake(title, subtitle, description)).ChecklistEntrySetIDTag("Heartstone spell TAPE"));
+
+}
+
+RegisterResourceGenerationFunction("IOTMHeartstoneGenerateResource");
+void IOTMHeartstoneGenerateResource(ChecklistEntry [int] resource_entries)
+{
+ // the heart must go on
+ if (!__iotms_usable[lookupItem("Heartstone")]) return;
+ boolean heartstoneEquipped = lookupItem("Heartstone").equipped_amount() > 0;
+
+ // What are the heartstone's letters?
+ string heartLetters = get_property("heartstoneLetters");
+ if (heartLetters.length() > 3) heartLetters = "";
+ string url = heartstoneEquipped ? "" : invSearch("heartstone");
+ string [int] description;
+ string title = heartLetters != "" ? "Heartstone ("+heartLetters.to_upper_case()+")" : "Heartstone (no letters!)";
+
+ // resource should include
+ // - better VHS tape tile; banishes will be in banish zone, but VHS tapes should get its own tile outside of 2002 now
+ // - skills tile for the useful skills; include banish as resource in banishes combination tag
+
+ // Can the user access the skills?
+ boolean accessBOOM = get_property_boolean("heartstoneKillUnlocked");
+ boolean accessGONE = get_property_boolean("heartstoneBanishUnlocked");
+ boolean accessSTUN = get_property_boolean("heartstoneStunUnlocked");
+ boolean accessBUDS = get_property_boolean("heartstonePalsUnlocked");
+ boolean accessBUFF = get_property_boolean("heartstoneBuffUnlocked");
+ boolean accessLUCK = get_property_boolean("heartstoneLuckUnlocked");
+
+ // How many times has the user used the skills?
+ int usesBOOM = get_property_int("heartstoneKillUsed");
+ int usesGONE = get_property_int("heartstoneBanishUsed");
+ int usesSTUN = get_property_int("heartstoneStunUsed");
+ int usesBUDS = get_property_int("heartstonePalsUsed");
+ int usesBUFF = get_property_int("heartstoneBuffUsed");
+ int usesLUCK = get_property_int("heartstoneLuckUsed");
+
+ // Banish combination tag for GONE.
+ if (accessGONE && usesGONE < 5) {
+ string [int] banishDesc;
+ banishDesc.listAppend("Turn-taking banish, 50-turn duration.");
+ if (!heartstoneEquipped) banishDesc.listAppend("Equip your Heartstone.");
+ resource_entries.listAppend(ChecklistEntryMake("__item Heartstone", "", ChecklistSubentryMake(pluralise(5-usesGONE,"cast","casts")+" of Heartstone: GONE", url, banishDesc), 0).ChecklistEntrySetCombinationTag("banish").ChecklistEntrySetIDTag("Heartstone banish"));
+ }
+
+ description.listAppend("Steal Monster Hearts for useful items.");
+ if (usesLUCK + usesBUDS + usesGONE + usesBOOM < 16) {
+ description.listAppend("Also, use some cool skills:");
+ if (accessBOOM && usesBOOM < 5) description.listAppend("|*"+pluralise(5-usesBOOM,"BOOM","BOOMs")+": Instakill with ~150 substats");
+ if (accessGONE && usesGONE < 5) description.listAppend("|*"+pluralise(5-usesGONE,"GONE","GONEs")+": Turn-taking banish");
+ if (accessBUDS && usesBUDS < 5) description.listAppend("|*"+pluralise(5-usesBUDS,"BUDS","BUDS")+": 25 turns of +5 famwt, +1 famxp");
+ if (accessLUCK && usesLUCK < 1) description.listAppend("|*"+pluralise(1-usesLUCK,"LUCK","LUCK")+": A shot of "+HTMLGenerateSpanFont("Lucky!","green"));
+
+ // Remind user to pick up new heartstone skills if needed
+ if (!accessBOOM || !accessGONE || !accessSTUN || !accessBUDS || !accessBUFF || !accessLUCK) {
+ string [int] skillsNeeded;
+ if (!accessBOOM) skillsNeeded.listAppend("BOOM");
+ if (!accessGONE) skillsNeeded.listAppend("GONE");
+ if (!accessSTUN) skillsNeeded.listAppend("STUN");
+ if (!accessBUDS) skillsNeeded.listAppend("BUDS");
+ if (!accessBUFF) skillsNeeded.listAppend("BUFF");
+ if (!accessLUCK) skillsNeeded.listAppend("LUCK");
+
+ description.listAppend("Consider spelling the following words to unlock new skills: "+listJoinComponents(skillsNeeded, ", "));
+ }
+
+ if (!heartstoneEquipped) description.listAppend(HTMLGenerateSpanFont("Equip your Heartstone!","red"));
+ resource_entries.listAppend(ChecklistEntryMake("__item Heartstone", url, ChecklistSubentryMake(title, "", description)).ChecklistEntrySetIDTag("Heartstone skills"));
+
+
+ }
+
+}
\ No newline at end of file
diff --git a/Source/relay/TourGuide/Items of the Month/2026/Legendary Seal-Clubbing Club.ash b/Source/relay/TourGuide/Items of the Month/2026/Legendary Seal-Clubbing Club.ash
new file mode 100644
index 00000000..73718ed4
--- /dev/null
+++ b/Source/relay/TourGuide/Items of the Month/2026/Legendary Seal-Clubbing Club.ash
@@ -0,0 +1,105 @@
+//legendary seal-clubbing club
+RegisterTaskGenerationFunction("IOTMLegendaryClubGenerateTasks");
+
+// TODO: this is totally fine for now but two small notes i may address in the future:
+// - overall wanderer logic is kind of janky on all tiles and maybe could stand to be managed in one central spot
+// - probably decrease colors ever-so-slightly
+
+void IOTMLegendaryClubGenerateTasks(ChecklistEntry [int] task_entries, ChecklistEntry [int] optional_task_entries, ChecklistEntry [int] future_task_entries)
+{
+ if (!__iotms_usable[lookupItem("legendary seal-clubbing club")]) return;
+ string url = "inventory.php?ftext=legendary+seal-clubbing+club";
+ string [int] description;
+ string title;
+
+ { int nextWeekTurn = get_property_int("clubEmNextWeekMonsterTurn") + 8;
+ int nextWeekTimer = (nextWeekTurn - total_turns_played());
+
+ string image_name = get_property("clubEmNextWeekMonster");
+ string [int] description;
+ string [int] warnings;
+
+ // Adding a few warnings for the sake of it
+ boolean [string] holidayTracker = getHolidaysToday();
+
+ if (holidayTracker["El Dia de Los Muertos Borrachos"] == true || holidayTracker["Feast of Boris"] == true) {
+ warnings[1] = 'Be careful -- Borrachos & Feast of Boris wanderers can show up instead of your Legendary club wanderer!';
+ }
+
+ if (nextWeekTurn <= total_turns_played() && (image_name != ""))
+ {
+ description.listAppend(HTMLGenerateSpanFont("Wandering monster", "orange"));
+
+ // Only show warnings if it's right about to happen
+ foreach i, warning in warnings {
+ description.listAppend(HTMLGenerateSpanFont("|* ➾ "+warning, "red"));
+ }
+ task_entries.listAppend(ChecklistEntryMake("__monster " + image_name, "", ChecklistSubentryMake("Legendary club: " + get_property("clubEmNextWeekMonster") + HTMLGenerateSpanFont(" now", "red"), "", description), -11));
+ }
+ else if (nextWeekTurn -1 == total_turns_played() && (image_name != ""))
+ {
+ description.listAppend(HTMLGenerateSpanFont("Wandering monster", "orange"));
+ task_entries.listAppend(ChecklistEntryMake("__monster " + image_name, "", ChecklistSubentryMake("Legendary club: " + get_property("clubEmNextWeekMonster") + HTMLGenerateSpanFont(" in 1 more adv", "blue"), "", description), -11));
+ }
+ else if (image_name != "")
+ {
+ description.listAppend(nextWeekTimer + " advs until Next Week fight.");
+ optional_task_entries.listAppend(ChecklistEntryMake("__monster " + image_name, "", ChecklistSubentryMake("Legendary club: " + get_property("clubEmNextWeekMonster") + "", "", description), 10));
+ }
+ }
+
+ if (lookupItem("legendary seal-clubbing club").equipped_amount() > 0)
+ {
+ int clubBattlefieldsLeft = clampi(5 - get_property_int("_clubEmBattlefieldUsed"), 0, 5);
+ int clubNextWeeksLeft = clampi(5 - get_property_int("_clubEmNextWeekUsed"), 0, 5);
+ int clubBackwardsLeft = clampi(5 - get_property_int("_clubEmTimeUsed"), 0, 5);
+
+ if (clubBattlefieldsLeft == 0) {
+ description.listAppend(HTMLGenerateSpanFont("No Battlefield Clubs left.", "red"));
+ } else {
+ description.listAppend(clubBattlefieldsLeft + " Battlefield Clubs. Weird Saber Force.");
+ }
+ if (clubNextWeeksLeft == 0) {
+ description.listAppend(HTMLGenerateSpanFont("No Next Week Clubs left.", "red"));
+ } else {
+ description.listAppend(clubNextWeeksLeft + " Next Week Clubs. 7-turn Wanderer.");
+ }
+ if (clubBackwardsLeft == 0) {
+ description.listAppend(HTMLGenerateSpanFont("No Backwards Clubs left.", "red"));
+ } else {
+ description.listAppend(clubBackwardsLeft + " Backwards Clubs. Free kill NO ITEMS.");
+ }
+ task_entries.listAppend(ChecklistEntryMake("__item legendary seal-clubbing club", url, ChecklistSubentryMake(HTMLGenerateSpanFont("Legendary seal-clubbing club skills", "orange"), description), -11).ChecklistEntrySetIDTag("LSSC skills"));
+ }
+}
+
+RegisterResourceGenerationFunction("IOTMLegendaryClubGenerateResource");
+void IOTMLegendaryClubGenerateResource(ChecklistEntry [int] resource_entries)
+{
+ if ($item[legendary seal-clubbing club].available_amount() == 0) return;
+ string url = "inventory.php?ftext=legendary+seal-clubbing+club";
+ string [int] description;
+ string title;
+
+ int clubBattlefieldsLeft = clampi(5 - get_property_int("_clubEmBattlefieldUsed"), 0, 5);
+ int clubNextWeeksLeft = clampi(5 - get_property_int("_clubEmNextWeekUsed"), 0, 5);
+ int clubBackwardsLeft = clampi(5 - get_property_int("_clubEmTimeUsed"), 0, 5);
+
+ if (clubBattlefieldsLeft == 0) {
+ description.listAppend(HTMLGenerateSpanFont("No Battlefield Clubs left.", "red"));
+ } else {
+ description.listAppend(clubBattlefieldsLeft + " Battlefield Clubs. Weird Saber Force.");
+ }
+ if (clubNextWeeksLeft == 0) {
+ description.listAppend(HTMLGenerateSpanFont("No Next Week Clubs left.", "red"));
+ } else {
+ description.listAppend(clubNextWeeksLeft + " Next Week Clubs. 7-turn Wanderer.");
+ }
+ if (clubBackwardsLeft == 0) {
+ description.listAppend(HTMLGenerateSpanFont("No Backwards Clubs left.", "red"));
+ } else {
+ description.listAppend(clubBackwardsLeft + " Backwards Clubs. Free kill NO ITEMS.");
+ }
+
+ resource_entries.listAppend(ChecklistEntryMake("__item legendary seal-clubbing club", url, ChecklistSubentryMake(HTMLGenerateSpanFont("Legendary seal-clubbing club skills", "orange"), description), 1).ChecklistEntrySetIDTag("LSSC skills"));
+}
\ No newline at end of file
diff --git a/Source/relay/TourGuide/Items of the Month/Items of the Month import.ash b/Source/relay/TourGuide/Items of the Month/Items of the Month import.ash
index 619b1564..ecacba97 100644
--- a/Source/relay/TourGuide/Items of the Month/Items of the Month import.ash
+++ b/Source/relay/TourGuide/Items of the Month/Items of the Month import.ash
@@ -160,7 +160,7 @@ import "relay/TourGuide/Items of the Month/2024/VIP Photobooth.ash";
import "relay/TourGuide/Items of the Month/2024/Peace Turkey.ash";
import "relay/TourGuide/Items of the Month/2024/TakerSpace.ash";
-// 2024
+// 2025
import "relay/TourGuide/Items of the Month/2025/CyberRealm.ash";
import "relay/TourGuide/Items of the Month/2025/McHugeLarge Ski Set.ash";
import "relay/TourGuide/Items of the Month/2025/Leprecondo.ash";
@@ -169,4 +169,15 @@ import "relay/TourGuide/Items of the Month/2025/Peridot.ash";
import "relay/TourGuide/Items of the Month/2025/Prismatic Beret.ash";
import "relay/TourGuide/Items of the Month/2025/Cooler Yeti.ash";
import "relay/TourGuide/Items of the Month/2025/Allied Radio Backpack.ash";
-import "relay/TourGuide/Items of the Month/2025/Mobius Ring.ash";
\ No newline at end of file
+import "relay/TourGuide/Items of the Month/2025/Mobius Ring.ash";
+import "relay/TourGuide/Items of the Month/2025/Seadent.ash";
+import "relay/TourGuide/Items of the Month/2025/Blood Cubic Zirconia.ash";
+import "relay/TourGuide/Items of the Month/2025/Shrunken Head.ash";
+import "relay/TourGuide/Items of the Month/2025/Skeleton of Crimbo Past.ash";
+
+// 2026
+import "relay/TourGuide/Items of the Month/2026/Eternity Codpiece.ash";
+import "relay/TourGuide/Items of the Month/2026/Legendary Seal-Clubbing Club.ash";
+import "relay/TourGuide/Items of the Month/2026/Heartstone.ash";
+import "relay/TourGuide/Items of the Month/2026/Archaeologist's Spade.ash";
+import "relay/TourGuide/Items of the Month/2026/Baseball Diamond.ash";
\ No newline at end of file
diff --git a/Source/relay/TourGuide/Quests/Airport.ash b/Source/relay/TourGuide/Quests/Airport.ash
index 2587cd45..1d498f3d 100644
--- a/Source/relay/TourGuide/Quests/Airport.ash
+++ b/Source/relay/TourGuide/Quests/Airport.ash
@@ -1029,8 +1029,10 @@ void QStenchAirportGiveMeFuelGenerateTasks(ChecklistEntry [int] task_entries)
void QStenchAirportGarbageGenerateTasks(ChecklistEntry [int] task_entries)
{
- if (get_property_boolean("_dinseyGarbageDisposed"))
- return;
+ // Do not need this task in-run
+ if (get_property_boolean("_dinseyGarbageDisposed")) return;
+ if (__misc_state["in run"]) return;
+
ChecklistSubentry subentry;
subentry.header = "Turn in garbage";
subentry.entries.listAppend("Maintenance Tunnels Access" + __html_right_arrow_character + "Waste Disposal.");
diff --git a/Source/relay/TourGuide/Sections/Location Bar Popup.ash b/Source/relay/TourGuide/Sections/Location Bar Popup.ash
index 33ce6898..df216633 100644
--- a/Source/relay/TourGuide/Sections/Location Bar Popup.ash
+++ b/Source/relay/TourGuide/Sections/Location Bar Popup.ash
@@ -405,6 +405,75 @@ buffer generateItemInformationMethod2(location l, monster m, boolean try_for_min
items_presenting.listAppend(info);
}
+ // ----------- NEW BIT FROM SCOTCH ABOUT BOFA -------------
+ if ($skill[just the facts].have_skill())
+ {
+ // Adding BOFA items/effects to the item table this time
+ LBPItemInformation info;
+
+ info.image_url = "images/itemimages/" + $item[book of facts].smallimage;
+
+ string bofaText;
+ string bofaType = m.fact_type().to_lower_case();
+ boolean displayBOFA = true;
+
+ // Setting return conditions for things that we don't need to add to the table
+ if (bofaType == "none") displayBOFA = false;
+ if (bofaType == "stats") displayBOFA = false;
+ if (bofaType == "hp") displayBOFA = false;
+ if (bofaType == "mp") displayBOFA = false;
+ if (bofaType == "meat") displayBOFA = false;
+ if (bofaType == "modifier") displayBOFA = false;
+
+ // TODO: filter out junk results
+ if (bofaType == "item") {
+ info.item_name = m.item_fact().name;
+ info.image_url = "images/itemimages/" + m.item_fact().smallimage;
+ }
+ if (bofaType == "effect") info.item_name = `{m.effect_fact().name} ({m.numeric_fact()})`;
+
+ info.tags.listAppend("Book of Facts ("+bofaType+")");
+
+ // If the user has already gotten their pocket wishes or scraps, don't append it.
+ if (bofaType == "item" && m.item_fact().name == "pocket wish" && get_property_int("_bookOfFactsWishes") > 2) displayBOFA = false;
+ if (bofaType == "item" && m.item_fact().name == "tattered scrap of paper" && get_property_int("_bookOfFactsWishes") > 10) displayBOFA = false;
+
+ if (displayBOFA) items_presenting.listAppend(info);
+
+ }
+
+ // ----------- NEW BIT FROM SCOTCH ABOUT SHRUNKEN HEAD -------------
+ if (lookupItem("shrunken head").equipped_amount() > 0 && get_property_int("shrunkenHeadZombieHP") == 0)
+ {
+ // Adding a table element for shrunken head effects
+ LBPItemInformation info;
+
+ string headEffects = shrunken_Head_Zombie(m).listJoinComponents(", ");
+ string [string] abilityCompression = {
+ "Item Drop Bonus":"item%",
+ "Meat Drop Bonus":"meat%",
+ "Physical Attack":"atk",
+ "Hot Attack": HTMLGenerateSpanFont("atk","r_element_hot_desaturated"),
+ "Cold Attack": HTMLGenerateSpanFont("atk","r_element_cold_desaturated"),
+ "Sleaze Attack": HTMLGenerateSpanFont("atk","r_element_sleaze_desaturated"),
+ "Stench Attack": HTMLGenerateSpanFont("atk","r_element_stench_desaturated"),
+ "Spooky Attack": HTMLGenerateSpanFont("atk","r_element_spooky_desaturated"),
+ "MP Regen": "mp",
+ "HP Regen": "hp",
+ };
+
+ foreach key,value in abilityCompression
+ {
+ headEffects = headEffects.replace_string(key, value);
+ }
+ info.image_url = "images/itemimages/" + $item[shrunken head].smallimage;
+ info.item_name = "Zombie Powers";
+
+ info.tags.listAppend(headEffects);
+ items_presenting.listAppend(info);
+
+ }
+
int columns = 3;
if (items_presenting.count() % 2 == 0 && items_presenting.count() % 3 != 0 && items_presenting.count() < 8)
@@ -1002,35 +1071,7 @@ buffer generateLocationPopup(float bottom_coordinates, boolean location_bar_loca
fl_entry_styles[fl_entries.count() - 1] = style;
fl_entry_width_weight[fl_entries.count() - 1] = width_weight;
}
-
- // ----------- NEW BIT FROM SCOTCH ABOUT BOFA -------------
- if (true)
- {
- if ($skill[just the facts].have_skill()) {
- string bofaText;
- string bofaEffect = m.fact_type();
- string factAppend;
-
- // TODO: Bunch of stuff, this is a first implementation. Goals:
- // - Try to figure out a nicer way to format this.
- // - Filter out the "junk" BOFA results.
- if (bofaEffect == "item") factAppend = HTMLGenerateSpanFont(m.item_fact().name,"red");
- if (bofaEffect == "effect") factAppend = HTMLGenerateSpanFont(`{m.effect_fact().name} ({m.numeric_fact()})`,"blue");
- bofaText = `{factAppend}`;
- // I tried to put this a few places. First I tried in the stats
- // sidebar, then I tried under the name, then I tried as an
- // appended item. I ended up preferring having it generate in
- // visibly in large zones with cutoff entries. I am pretty sure
- // the "best" solution is a more elegant way to add it to the
- // item list, but this initial implementation is good enough for
- // now, I think.
-
- fl_entries.listAppend(bofaText);
- fl_entry_styles[fl_entries.count() - 1] = "text-align:left;font-size:0.8em";
- }
- }
-
//FIXME handle canceling NC
buffer rate_buffer;
if (m.attributes.contains_text("ULTRARARE"))
@@ -1120,9 +1161,6 @@ buffer generateLocationPopup(float bottom_coordinates, boolean location_bar_loca
-
-
-
int item_count_displaying = m.item_drops_array().count();
if (item_count_displaying > 0 && try_for_minimal_display && !monster_cannot_be_encountered)
{
@@ -1132,6 +1170,7 @@ buffer generateLocationPopup(float bottom_coordinates, boolean location_bar_loca
if (!monster_cannot_be_encountered)
{
+
string [int] stats_l1;
string [int] stats_l2;
//if (m.base_hp > 0)
@@ -1164,10 +1203,17 @@ buffer generateLocationPopup(float bottom_coordinates, boolean location_bar_loca
stats_l1.listAppend(average_meat.round() + " meat");
}
}
+
+ // Add heart to the phyla row; coloring pink for TES
+ if (__iotms_usable[lookupItem("Heartstone")] && heartstone_middle_letter(m) != "")
+ stats_l1.listAppend(HTMLGenerateSpanOfClass("♡ = "+heartstone_middle_letter(m), "#ce538c"));
+
if (m.base_attack > 0)
stats_l2.listAppend(m.base_attack + " ML");
+
if (spelunking)
stats_l2.listAppend(m.base_hp + " HP");
+
if ($skill[Extract Oil].have_skill())
{
string oil_type = lookupAWOLOilForMonster(m);
diff --git a/Source/relay/TourGuide/Sections/Location Bar.ash b/Source/relay/TourGuide/Sections/Location Bar.ash
index f3dfd389..7eb0d74b 100644
--- a/Source/relay/TourGuide/Sections/Location Bar.ash
+++ b/Source/relay/TourGuide/Sections/Location Bar.ash
@@ -578,7 +578,18 @@ buffer generateLocationBar(boolean displaying_navbar)
//style.append("white-space:nowrap;");
//style.append("text-overflow:clip;");
- string l_name = l.to_string();
+ // If user has peridot and peridot has not been used yet, add a diamond
+ string pp = "❖ ";
+ boolean peridotUsed = false;
+
+ foreach key,place in get_property("_perilLocations").split_string(",") {
+ if (l.to_int() == place.to_int()) peridotUsed = true;
+ }
+
+ // Do not display if user does not have peridot
+ if (!__iotms_usable[lookupItem("Peridot of Peril")]) peridotUsed = true;
+
+ string l_name = peridotUsed ? l.to_string() : pp+l.to_string();
if (__setting_location_bar_fixed_layout)
l_name = HTMLGenerateDivOfClass(l_name, "r_location_bar_ellipsis_entry");
diff --git a/Source/relay/TourGuide/Sets/Daily Dungeon.ash b/Source/relay/TourGuide/Sets/Daily Dungeon.ash
index 5005965f..1a351bd8 100644
--- a/Source/relay/TourGuide/Sets/Daily Dungeon.ash
+++ b/Source/relay/TourGuide/Sets/Daily Dungeon.ash
@@ -236,7 +236,13 @@ void SDailyDungeonGenerateTasks(ChecklistEntry [int] task_entries, ChecklistEntr
if (avoid_using_skeleton_key && $item[skeleton key].available_amount() > 0)
description.listAppend(HTMLGenerateSpanOfClass("Avoid using your skeleton key, you don't have many left.", "r_bold"));
- optional_task_entries.listAppend(ChecklistEntryMake("daily dungeon", url, ChecklistSubentryMake("Daily Dungeon", "", description), $locations[the daily dungeon]).ChecklistEntrySetIDTag("Daily dungeon today"));
+ if (rooms_left == 14) {
+ description.listAppend(pluraliseWordy(rooms_left, "room", "rooms").capitaliseFirstLetter() + " left.");
+ description.listAppend(HTMLGenerateSpanOfClass("Last room; claim your loot token!","r_element_hot"));
+ task_entries.listAppend(ChecklistEntryMake("daily dungeon", url, ChecklistSubentryMake("Daily Dungeon", "", description), -11).ChecklistEntrySetIDTag("Daily dungeon last-room supernag"));
+ } else {
+ optional_task_entries.listAppend(ChecklistEntryMake("daily dungeon", url, ChecklistSubentryMake("Daily Dungeon", "", description), $locations[the daily dungeon]).ChecklistEntrySetIDTag("Daily dungeon today"));
+ }
}
}
diff --git a/Source/relay/TourGuide/Sets/Familiars.ash b/Source/relay/TourGuide/Sets/Familiars.ash
index cbed9d17..1875e4e8 100644
--- a/Source/relay/TourGuide/Sets/Familiars.ash
+++ b/Source/relay/TourGuide/Sets/Familiars.ash
@@ -454,10 +454,10 @@ void SFamiliarsGenerateResource(ChecklistEntry [int] resource_entries)
void SFamiliarsGenerateTasks(ChecklistEntry [int] task_entries, ChecklistEntry [int] optional_task_entries, ChecklistEntry [int] future_task_entries)
{
- if (my_familiar() == $familiar[none] && !__misc_state["single familiar run"] && !__misc_state["familiars temporarily blocked"] && !($locations[The Bandit Crossroads,The Towering Mountains,The Mystic Wood,The Putrid Swamp,The Cursed Village,The Sprawling Cemetery,The Old Rubee Mine,The Foreboding Cave,The Faerie Cyrkle,The Druidic Campsite,Near the Witch's House,The Evil Cathedral,The Barrow Mounds,The Cursed Village Thieves' Guild,The Troll Fortress,The Labyrinthine Crypt,The Lair of the Phoenix,The Dragon's Moor,Duke Vampire's Chateau,The Master Thief's Chalet,The Spider Queen's Lair,The Archwizard's Tower,The Ley Nexus,The Ghoul King's Catacomb,The Ogre Chieftain's Keep] contains __last_adventure_location))
+ if (my_familiar() == $familiar[none] && !__misc_state["single familiar run"] && lookupItem("FantasyRealm G. E. M.").equipped_amount() > 0 && !__misc_state["familiars temporarily blocked"] && !($locations[The Bandit Crossroads,The Towering Mountains,The Mystic Wood,The Putrid Swamp,The Cursed Village,The Sprawling Cemetery,The Old Rubee Mine,The Foreboding Cave,The Faerie Cyrkle,The Druidic Campsite,Near the Witch's House,The Evil Cathedral,The Barrow Mounds,The Cursed Village Thieves' Guild,The Troll Fortress,The Labyrinthine Crypt,The Lair of the Phoenix,The Dragon's Moor,Duke Vampire's Chateau,The Master Thief's Chalet,The Spider Queen's Lair,The Archwizard's Tower,The Ley Nexus,The Ghoul King's Catacomb,The Ogre Chieftain's Keep] contains __last_adventure_location))
{
string image_name = "black cat";
- optional_task_entries.listAppend(ChecklistEntryMake(image_name, "familiar.php", ChecklistSubentryMake("Bring along a familiar", "", "")).ChecklistEntrySetIDTag("Bring familiar reminder"));
+ task_entries.listAppend(ChecklistEntryMake(image_name, "familiar.php", ChecklistSubentryMake("Bring along a familiar", "", "It's dangerous to go alone!"), -11).ChecklistEntrySetIDTag("Bring familiar reminder"));
}
if ($familiar[Crimbo Shrub].familiar_is_usable() && my_path().id != PATH_G_LOVER)
diff --git a/Source/relay/TourGuide/Sets/Lucky.ash b/Source/relay/TourGuide/Sets/Lucky.ash
index 41005c12..19138596 100644
--- a/Source/relay/TourGuide/Sets/Lucky.ash
+++ b/Source/relay/TourGuide/Sets/Lucky.ash
@@ -55,6 +55,8 @@ string [int] luckyOptions(int cloversAvailable) {
if (protestorsRemaining > 10 && protestorsPerClover > 15)
allTheLuckyStuff.listAppend("Zeppelin Mob (x"+projectedZeppClovers+")");
cloversAdjusted = MAX(cloversAdjusted - projectedZeppClovers, 3);
+ if (my_path().id == 56) //adventurer meats world
+ allTheLuckyStuff.listAppend("Knob Goblin Embezzler");
if (__misc_state["wand of nagamar needed"] && lettersStillNeeded > 0)
allTheLuckyStuff.listAppend("Wand of Nagamar");
if (roughUHTurnsNeeded > 5)
@@ -73,46 +75,225 @@ string [int] luckyOptions(int cloversAvailable) {
return selectedOptions;
}
-
+
//Clovers and Lucky
RegisterResourceGenerationFunction("LuckyGenerateResource");
void LuckyGenerateResource(ChecklistEntry [int] resource_entries)
{
- if (!__misc_state["in run"]) return;
- {
- string [int] description;
+
+ // SYNTAX FOR NEW LUCKY SOURCES
+ // In order to centralize, we are using the SneakSource concept from the sneaks megatile.
+
+ record LuckySource {
+ string sourceName;
string url;
- description.listAppend(HTMLGenerateSpanFont("Have a Lucky adventure!", "green"));
+ string imageLookupName;
+ boolean luckyCondition;
+ int luckyCount;
+ string tileDescription;
+ };
- // Figure out how many clovers you have available/possible and join the needed components
+ // Refactoring lucky to reflect/use TES megatile suggestion
+ // if (!__misc_state["in run"]) return;
+
+ // useful state variables
+ int spleenRemaining = spleen_limit() - my_spleen_use();
+ int stomachLeft = availableFullness();
+
+ // Build out all the lucky sources.
+
+ // EVERGREEN: 11-leaf clovers
+ LuckySource getClovers() {
+ LuckySource final;
+ final.sourceName = "clover";
+ final.url = invSearch("11-leaf clover");
+ final.imageLookupName = "__item 11-leaf clover";
+
+ // # of clovers available
int cloversAvailable = clampi(3 - get_property_int("_cloversPurchased"), 0, 3);
+ if (my_path().id == PATH_ZOMBIE_SLAYER) cloversAvailable = 0;
+ if (my_path().id == PATH_NUCLEAR_AUTUMN) cloversAvailable = 0;
int cloversPossible = $item[11-leaf clover].available_amount() + cloversAvailable;
- description.listAppend(luckyOptions(cloversPossible).listJoinComponents(", "));
+
+ // usable if we have any clovers available/possible
+ final.luckyCondition = cloversPossible > 0;
+ final.luckyCount = cloversPossible;
+ final.tileDescription = `{cloversPossible}x clovers left`;
+ final.tileDescription = cloversAvailable > 0 ? final.tileDescription + `, with {cloversAvailable}x at the Hermit` : final.tileDescription;
+
+ return final;
+ }
+
+ // EVERGREEN: Astral Energy Drinks
+ LuckySource getAEDs() {
+ LuckySource final;
+ final.sourceName = "astral energy drink";
+ final.url = invSearch("astral energy");
+ final.imageLookupName = "__item astral energy drink";
-
- if ($item[11-leaf clover].available_amount() > 0)
- {
- url = invSearch("11-leaf clover");
- resource_entries.listAppend(ChecklistEntryMake("__item 11-leaf clover", url, ChecklistSubentryMake(pluralise($item[11-leaf clover]), "Inhale leaves for good luck", description), 2).ChecklistEntrySetCombinationTag("fortune"));
- }
- if ($item[[10883]astral energy drink].available_amount() > 0 && $item[11-leaf clover].available_amount() == 0)
- {
- url = invSearch("astral energy drink");
- resource_entries.listAppend(ChecklistEntryMake("__item [10883]astral energy drink", url, ChecklistSubentryMake(pluralise(available_amount($item[[10883]astral energy drink]),"astral energy drink", "astral energy drinks"), "Costs 5 spleen each", description), 2).ChecklistEntrySetCombinationTag("fortune"));
- }
- else if ($item[[10883]astral energy drink].available_amount() > 0 && $item[11-leaf clover].available_amount() > 0)
- {
- url = invSearch("astral energy drink");
- resource_entries.listAppend(ChecklistEntryMake("__item [10883]astral energy drink", url, ChecklistSubentryMake(pluralise(available_amount($item[[10883]astral energy drink]),"astral energy drink", "astral energy drinks"), "Costs 5 spleen each", ""), 2).ChecklistEntrySetCombinationTag("fortune"));
- }
+ // # of AEDs available or possible
+ int availableAEDs = available_amount($item[[10883]astral energy drink]) + available_amount($item[[10882]carton of astral energy drinks])*6;
+ int spleenFitsThisManyAEDs = min(floor(spleenRemaining / 5),availableAEDs);
+
+ // usable if we have any clovers available/possible
+ final.luckyCondition = availableAEDs > 0 && spleenFitsThisManyAEDs > 0;
+
+ final.luckyCount = spleenFitsThisManyAEDs;
+ final.tileDescription = `{spleenFitsThisManyAEDs}x AEDs to consume`;
+ final.tileDescription = availableAEDs - spleenFitsThisManyAEDs > 0 ? final.tileDescription + `, with {availableAEDs - spleenFitsThisManyAEDs}x ready for tomorrow` : final.tileDescription;
+
+ return final;
+ }
+
+ // 2026: Cast 1x "Heartstone: LUCK"
+ LuckySource getHeartstone() {
+ LuckySource final;
+
+ final.sourceName = `august scepter`;
+ final.url = 'skillz.php';
+ final.imageLookupName = "__item heartstone";
+
+ final.luckyCondition = __iotms_usable[$item[August Scepter]];
+ final.luckyCount = get_property_boolean("_aug2Cast") ? 0 : 1;
+ final.tileDescription = `{final.luckyCount}x August Scepter cast left (Aug. 2)`;
+
+ return final;
+ }
+
+ // 2024: 3x plays of the apriling band saxophone
+ LuckySource getSaxophones() {
+ LuckySource final;
+
+ final.sourceName = 'apriling tuba';
+ final.url = "inventory.php?ftext=apriling+band+tuba";
+ final.imageLookupName = "__item Apriling band tuba";
+
+ int aprilingBandSaxUsesLeft = clampi(3 - get_property_int("_aprilBandSaxUses"), 0, 3);
+
+ final.luckyCondition = (aprilingBandSaxUsesLeft > 0 && available_amount($item[apriling band tuba]) > 0);
+ final.luckyCount = aprilingBandSaxUsesLeft;
+ final.tileDescription = `{aprilingBandSaxUsesLeft}x apriling sax solos left`;
+ return final;
+
+ }
+ // 2024: moai statues (cool); no clovermint tho, no thank you
+ LuckySource getMoaiStatues() {
+ LuckySource final;
+
+ final.sourceName = `moai statuette`;
+ final.url = invSearch("lucky moai statuette");
+ final.imageLookupName = "__item lucky moai statuette";
+ final.luckyCondition = available_amount($item[lucky moai statuette]) > 0;
+ final.luckyCount = available_amount($item[lucky moai statuette])*3;
+ final.tileDescription = `{final.luckyCount}x clovers via lucky moai`;
+
+ return final;
+ }
+
+ // 2023: Cast 1x "Aug. 2nd: Find an Eleven-Leaf Clover Day"
+ LuckySource getScepter() {
+ LuckySource final;
+
+ final.sourceName = `august scepter`;
+ final.url = 'skillz.php';
+ final.imageLookupName = "__item august scepter";
+
+ final.luckyCondition = __iotms_usable[$item[August Scepter]];
+ final.luckyCount = get_property_boolean("_aug2Cast") ? 0 : 1;
+ final.tileDescription = `{final.luckyCount}x August Scepter cast left (Aug. 2)`;
+
+ return final;
+ }
+
+ // 2019: Select "Surprise Me" from the Eight Days a Week Pill Keeper
+ LuckySource getPillkeeper() {
+ LuckySource final;
+
+ final.sourceName = "pillkeeper";
+ final.url = "main.php?eowkeeper=1";
+ final.imageLookupName = "__item Eight Days a Week Pill Keeper";
+
+ // see # of free pillkeeepers remaining
+ int freeLuckLeft = get_property_boolean("_freePillKeeperUsed") ? 0 : 1;
+
+ // calculate possible spleen-based lucky
+ int spleenLucks = floor(spleenRemaining / 3);
+
+ // usable if we have pill keeper plus free lucky or spleen lucky available
+ final.luckyCondition = __iotms_usable[lookupItem("Eight Days a Week Pill Keeper")] && (freeLuckLeft + spleenLucks > 0);
+
+ // never noticed I didn't explicitly say this was pillkeeper in the tile lol
+ final.luckyCount = freeLuckLeft + spleenLucks;
+ final.tileDescription = get_property_boolean("_freePillKeeperUsed") ? "" : `1x PillKeeper free lucky, `;
+ final.tileDescription = final.tileDescription + `and {spleenLucks}x more for 3 spleen each`;
+ return final;
+ }
+
+ // 2014: 1x Lucky Lindy -- not including to start, too old
+ // 2013: 1x optimal dog -- not including to start, too old
+
+ // Populate a list of lucky sources with our cool functions
+ LuckySource [string] luckySources;
+
+ luckySources["clovo"] = getClovers();
+ luckySources["astro"] = getAEDs();
+ luckySources["stono"] = getHeartstone();
+ luckySources["saxxo"] = getSaxophones();
+ luckySources["mohio"] = getMoaiStatues();
+ luckySources["scepo"] = getScepter();
+ luckySources["pillo"] = getPillkeeper();
+
+ // For order, I am starting from things that are daily refresh -> can be saved
+ string [int] luckyOrder = listMake("stono","scepo","saxxo","pillo","clovo","astro","mohio");
+
+ ChecklistEntry entry;
+
+ entry.url = "";
+ entry.image_lookup_name = "__item 11-leaf clover";
+ entry.tags.id = "Lucky sources available";
+ entry.importance_level = -1;
+
+ string [int] description;
+ int totalLuckyCharges = 0;
+ string line = HTMLGenerateSpanOfClass("Get a Lucky! adventure", "r_bold r_element_stench_desaturated");
+ string ll = HTMLGenerateSpanOfClass("✾", "r_element_stench");
+ string luckyText = HTMLGenerateSpanOfClass("Lucky!", "r_element_stench_desaturated");
+
+ foreach it, luckyType in luckyOrder
+ {
+ LuckySource lucko = luckySources[luckyType];
+ if (lucko.luckyCount > 0 && lucko.luckyCondition) {
+ if (totalLuckyCharges == 0) entry.url = lucko.url;
+ totalLuckyCharges += lucko.luckyCount;
+
+ line += "|*"+ll+lucko.tileDescription;
+ }
+
+ }
+
+ if (totalLuckyCharges == 0) return;
+
+ // Append all the lines to a description
+ description.listAppend(line);
+
+ // Add a description that falls away when you hoverover
+ entry.subentries.listAppend(ChecklistSubentryMake(pluralise(totalLuckyCharges, luckyText+" charge available", luckyText+" charges available"), "", description));
+
+ if (entry.subentries.count() > 0) resource_entries.listAppend(entry);
+
+ // do not run old tile
+ if (false)
+ {
+
// Add a reminder to buy clovers if you haven't yet
string [int] hermitDescription;
+ int cloversAvailable = clampi(3 - get_property_int("_cloversPurchased"), 0, 3);
if (cloversAvailable > 0)
{
- url = "hermit.php";
+ string url = "hermit.php";
string title = HTMLGenerateSpanFont("Hey! You! GRAB YOUR CLOVERS!", "green");
hermitDescription.listAppend(cloversAvailable + " in stock at the Hermit");
resource_entries.listAppend(ChecklistEntryMake("__item 11-leaf clover", url, ChecklistSubentryMake(title, hermitDescription), -11).ChecklistEntrySetIDTag("Clover resource"));
diff --git a/Source/relay/TourGuide/Sets/Pulverise.ash b/Source/relay/TourGuide/Sets/Pulverise.ash
index 93e17559..39586729 100644
--- a/Source/relay/TourGuide/Sets/Pulverise.ash
+++ b/Source/relay/TourGuide/Sets/Pulverise.ash
@@ -195,6 +195,7 @@ void SPulveriseGenerateResource(ChecklistEntry [int] resource_entries)
pulveriseAppendOutputListForProducts(details, "handful of smithereens", $items[handful of smithereens], blacklist);
//Elemental powder, for +1 resistances?
+ pulveriseAppendOutputListForProducts(details, "cold powder", $items[cold powder], blacklist);
//Elemental nuggets, for +3 tower test? (very marginal)
if (details.count() > 0)
diff --git a/Source/relay/TourGuide/Sets/Skills.ash b/Source/relay/TourGuide/Sets/Skills.ash
index 74093b19..3cb30530 100644
--- a/Source/relay/TourGuide/Sets/Skills.ash
+++ b/Source/relay/TourGuide/Sets/Skills.ash
@@ -41,41 +41,41 @@ void SSkillsGenerateResource(ChecklistEntry [int] resource_entries)
}
if (lookupSkill("Expert Corner-Cutter").skill_is_usable()) {
free_crafts_left += clampi(5 - get_property_int("_expertCornerCutterUsed"), 0, 5);
- }
+ }
if (get_property_int("homebodylCharges") > 0) {
free_crafts_left += (get_property_int("homebodylCharges"));
}
+ if (get_property_int("craftingPlansCharges") >0) {
+ free_crafts_left += (get_property_int("craftingPlansCharges"));
+ }
+
// adding cookbookbat free crafts into crafting tile
if (lookupFamiliar("Cookbookbat").familiar_is_usable()) {
- string [int] description;
free_cooks_left += clampi(5 - get_property_int("_cookbookbatCrafting"), 0, 5);
- string title = "free cooking";
- if (free_cooks_left > 0) {
- craft_entry.subentries.listAppend(ChecklistSubentryMake(pluralise(free_cooks_left, title, title + "s") + " remaining", free_crafts_left > 0 ? "COOKING only" : "", description));
- }
}
// holiday multitasking
if (lookupSkill("Holiday Multitasking").skill_is_usable()) {
free_crafts_left += clampi(3 - get_property_int("_holidayMultitaskingUsed"), 0, 3);
- }
+ }
// elf guard cooking
if (lookupSkill("Elf Guard Cooking").skill_is_usable()) {
- string [int] description;
free_cooks_left += clampi(3 - get_property_int("_elfGuardCookingUsed"), 0, 3);
- string title = "free cooking";
- if (free_cooks_left > 0) {
- craft_entry.subentries.listAppend(ChecklistSubentryMake(pluralise(free_cooks_left, title, title + "s") + " remaining", free_crafts_left > 0 ? "COOKING only" : "", description));
- }
}
// cocktails of the age of sail
if (lookupSkill("Old-School Cocktailcrafting").skill_is_usable()) {
- string [int] description;
free_mixes_left += clampi(3 - get_property_int("_oldSchoolCocktailCraftingUsed"), 0, 3);
+ }
+
+ if (free_cooks_left > 0) {
+ string [int] description;
+ string title = "free cooking";
+ craft_entry.subentries.listAppend(ChecklistSubentryMake(pluralise(free_cooks_left, title, title + "s") + " remaining", free_crafts_left > 0 ? "COOKING only" : "", description));
+ }
+ if (free_mixes_left > 0) {
+ string [int] description;
string title = "free mixing";
- if (free_mixes_left > 0) {
craft_entry.subentries.listAppend(ChecklistSubentryMake(pluralise(free_mixes_left, title, title + "s") + " remaining", free_crafts_left > 0 ? "MIXING only" : "", description));
- }
- }
+ }
int free_smiths_left = 0;
if (__campground[$item[warbear auto-anvil]] > 0) {
@@ -105,8 +105,11 @@ void SSkillsGenerateResource(ChecklistEntry [int] resource_entries)
craft_entry.subentries.listAppend(ChecklistSubentryMake(pluralise(free_smiths_left, title, title + "s") + " remaining", free_crafts_left > 0 ? "SMITHING only" : "", description));
}
- if (free_crafts_left > 0) {
- string description = SSkillsPotentialCraftingOptions().listJoinComponents(", ").capitaliseFirstLetter();
+ int crafting_plans = $item[crafting plans].available_amount();
+ if (free_crafts_left > 0 || crafting_plans > 0) {
+ string [int] description;
+ description.listAppend(SSkillsPotentialCraftingOptions().listJoinComponents(", ").capitaliseFirstLetter());
+ if (crafting_plans > 0) description.listAppend("Also, "+pluralise($item[crafting plans])+" for more free crafts.");
craft_entry.subentries.listAppend(ChecklistSubentryMake(pluralise(free_crafts_left, "free craft", "free crafts") + " remaining", free_smiths_left > 0 || jackhammer_crafts_later > 0 ? "Any crafting mode, including smithing" : "", description));
}
diff --git a/Source/relay/TourGuide/Sets/Sneaks.ash b/Source/relay/TourGuide/Sets/Sneaks.ash
index 66e928c4..43c38b02 100644
--- a/Source/relay/TourGuide/Sets/Sneaks.ash
+++ b/Source/relay/TourGuide/Sets/Sneaks.ash
@@ -225,7 +225,7 @@ void SocialDistanceGenerator(ChecklistEntry [int] resource_entries)
string [int] description;
int totalSneaks = 0;
- string line = HTMLGenerateSpanOfClass("Force an NC with sneaky tricks!", "r_bold r_element_stench_desaturated");
+ string line = HTMLGenerateSpanOfClass("Force an NC with sneaky tricks!", "r_bold r_element_cold_desaturated");
foreach it, sneakType in sneakOrder
{
diff --git a/Source/relay/TourGuide/Settings.ash b/Source/relay/TourGuide/Settings.ash
index 1b8083c3..45357a22 100644
--- a/Source/relay/TourGuide/Settings.ash
+++ b/Source/relay/TourGuide/Settings.ash
@@ -1,5 +1,8 @@
//These settings are for development. Don't worry about editing them.
-string __version = "2.3.1"; // pushed to 2.2.1 on jill/leaves tiles, 2.3.1 on sea path upgrades
+string __version = "2.3.2";
+// pushed to 2.2.1 on jill/leaves tiles
+// 2.3.1 on sea path upgrades
+// 2.3.2 on the lucky refactor ++ in spring 2026
//Path and name of the .js file. In case you change either.
string __javascript = "TourGuide/TourGuide.js";
diff --git a/Source/relay/TourGuide/Support/Banishers.ash b/Source/relay/TourGuide/Support/Banishers.ash
index 3d3d470e..199f8030 100644
--- a/Source/relay/TourGuide/Support/Banishers.ash
+++ b/Source/relay/TourGuide/Support/Banishers.ash
@@ -107,6 +107,15 @@ static
__banish_source_length["roar like a lion"] = 30; // not sure it is needed; it should generally be not more than 30
__banish_source_length["monkey slap"] = 1234567; // this, for some reason, was not properly respecting the reset condition. so imma just do this to hopefully solve it.
__banish_source_length["spring kick"] = -1;
+ __banish_source_length["stuffed yam stinkbomb"] = 15;
+ __banish_source_length["throwin' ember"] = 30;
+ __banish_source_length["anchor bomb"] = 30;
+ __banish_source_length["handful of split pea soup"] = 30;
+ __banish_source_length["punch out your foe"] = 20;
+ __banish_source_length["sea *dent"] = -1;
+ __banish_source_length["mark your territory"] = -1;
+ __banish_source_length["heartstone: gone"] = 50;
+ __banish_source_length["baseball diamond"] = -1;
int [string] __banish_simultaneous_limit;
__banish_simultaneous_limit["beancannon"] = 5;
diff --git a/Source/relay/TourGuide/Support/IOTMs.ash b/Source/relay/TourGuide/Support/IOTMs.ash
index 4e8ca3a3..ba48824a 100644
--- a/Source/relay/TourGuide/Support/IOTMs.ash
+++ b/Source/relay/TourGuide/Support/IOTMs.ash
@@ -193,22 +193,49 @@ void initialiseIOTMsUsable()
if (lookupItem("Leprecondo").available_amount() > 0) //Mar 2025
__iotms_usable[lookupItem("Leprecondo")] = true;
- if (lookupItem("April Shower Thoughts shield").available_amount() > 0) //Apr 2024
+ if (lookupItem("April Shower Thoughts shield").available_amount() > 0) //Apr 2025
__iotms_usable[lookupItem("April Shower Thoughts shield")] = true;
- if (lookupItem("Peridot of Peril").available_amount() > 0) //May 2024
+ if (lookupItem("Peridot of Peril").available_amount() > 0) //May 2025
__iotms_usable[lookupItem("Peridot of Peril")] = true;
- if (lookupItem("prismatic beret").available_amount() > 0) //Jun 2024
+ if (lookupItem("prismatic beret").available_amount() > 0) //Jun 2025
__iotms_usable[lookupItem("prismatic beret")] = true;
// july 2025 -- yeti, familiar, unneeded
- if (lookupItem("Möbius ring").available_amount() > 0) //Aug 2024
+ if (lookupItem("Möbius ring").available_amount() > 0) //Aug 2025
__iotms_usable[lookupItem("Möbius ring")] = true;
- if (lookupItem("allied radio backpack").available_amount() > 0) //Aug 2024
+ if (lookupItem("allied radio backpack").available_amount() > 0) //Aug 2025
__iotms_usable[lookupItem("allied radio backpack")] = true;
+
+ if (lookupItem("monodent of the sea").available_amount() > 0) //Sep 2025
+ __iotms_usable[lookupItem("monodent of the sea")] = true;
+
+ if (lookupItem("blood cubic zirconia").available_amount() > 0) //Oct 2025
+ __iotms_usable[lookupItem("blood cubic zirconia")] = true;
+
+ if (get_property_boolean("hasShrunkenHead")) //nov 2025
+ __iotms_usable[lookupItem("shrunken head")] = true;
+
+ // Dec 2025 -- crimbo skeleton, unneeded
+
+ if (lookupItem("the eternity codpiece").available_amount() > 0) //Jan 2026
+ __iotms_usable[lookupItem("the eternity codpiece")] = true;
+
+ if (lookupItem("legendary seal-clubbing club").available_amount() > 0) //Jan 2026
+ __iotms_usable[lookupItem("legendary seal-clubbing club")] = true;
+
+ if (lookupItem("Heartstone").available_amount() > 0) //Feb 2026
+ __iotms_usable[lookupItem("Heartstone")] = true;
+
+ if (lookupItem("archaeologist's spade").available_amount() > 0) //Mar 2026
+ __iotms_usable[lookupItem("archaeologist's spade")] = true;
+
+ if (lookupItem("Baseball Diamond").available_amount() > 0) //Apr 2026
+ __iotms_usable[lookupItem("Baseball Diamond")] = true;
+
//Can't use many things in G-Lover
if (my_path().id == PATH_G_LOVER) //Path 33