loathers / TourGuide

Relay script for KoLmafia, for the web game Kingdom of Loathing. Gives advice on how to play.
The Unlicense
14 stars 17 forks source link

[Feature]: Lucky! megatile #223

Open uthuluc opened 2 months ago

uthuluc commented 2 months ago

What's the request?

Same formatting as Sneak megatile

clovers 3/day bankable AED limited optimal dog 1/day lucky lindy 3/day pill keeper X/day scepter clover 1/day april sax 3/day

Account State

All account states would benefit equally!

Your current TourGuide version

No response

uthuluc commented 2 months ago

NOT COMPLETE, DOES NOT DISPLAY BOTTOM SECTION UNCHANGED MISSING: OPTIMAL DOG, LUCKY LINDY, ETC MISSING: CLOVER ADV RECOMMENDATIONS

record LuckySource {
    string sourceName;
    string url;
    string imageLookupName;
    boolean LuckyCondition;
    int LuckyCount;
    string tileDescription;
};

RegisterResourceGenerationFunction("LuckyDayGenerator"); void LuckyDayGenerator(ChecklistEntry [int] resource_entries) { // Saving some useful variables for use in the calculations. int spleenRemaining = spleen_limit() - my_spleen_use(); int stomachLeft = availableFullness();

LuckySource getAstralEnergy() {
    LuckySource final;

    final.sourceName = "Astral Energy Drink";
    final.url = "inventory.php?ftext=astral+energy+drink";
    final.imageLookupName = "__item astral energy drink";

    // calculate possible spleen-based Luckys
    int spleenLuckys = floor(spleenRemaining / 5);

    // usable if we have pill keeper plus free Luckys or spleen Luckys available
    final.LuckyCondition = available_amount($item[[10883]astral energy drink]) > 0 && (spleenLuckys > 0);

    final.LuckyCount = spleenLuckys;
    final.tileDescription = final.tileDescription + `<b>{spleenLuckys}x AED</b> for 5 spleen each`;
    return final;
}

LuckySource getLuckyisol() {
    LuckySource final;

    final.sourceName = "Surprise Element pills";
    final.url = "main.php?eowkeeper=1";
    final.imageLookupName = "__item Eight Days a Week Pill Keeper";

    // see # of free pillkeeepers remaining
    int freeLuckyLeft = get_property_boolean("_freePillKeeperUsed") ? 0 : 1;

    // calculate possible spleen-based Luckys
    int spleenLuckys = floor(spleenRemaining / 3);

    // usable if we have pill keeper plus free Luckys or spleen Luckys available
    final.LuckyCondition = __iotms_usable[lookupItem("Eight Days a Week Pill Keeper")] && (freeLuckyLeft + spleenLuckys > 0);

    final.LuckyCount = freeLuckyLeft + spleenLuckys;
    final.tileDescription = get_property_boolean("_freePillKeeperUsed") ? "" : `<b>1x free Lucky, </b>`;
    final.tileDescription = final.tileDescription + `<b>{spleenLuckys}x Luckys</b> for 3 spleen each`;
    return final;
}

LuckySource getClovers() {
    LuckySource final;

    final.sourceName = "11-Leaf Clover";
    final.url = "inventory.php?ftext=11-leaf+clover";
    final.imageLookupName = "__item 11-leaf clover";

    // usable if we have pill keeper plus free Luckys or spleen Luckys available
    int cloverLuckys = available_amount($item[11-leaf clover]);

    final.LuckyCount = cloverLuckys;
    final.tileDescription = final.tileDescription + `<b>{cloverLuckys}x 11-leaf clover</b>`;
    return final;
}

LuckySource getSaxes() {
    LuckySource final;

    final.sourceName = 'Apriling saxes';
    final.url = "inventory.php?ftext=apriling+band+sax";
    final.imageLookupName = "__item apriling band sax";

    int saxosLeft = clampi(3 - get_property_int("_aprilBandSaxUses"), 0, 3);

    final.LuckyCondition = ($item[apriling band saxophone].available_amount() > 0 && (saxosLeft > 0));
    final.LuckyCount = saxosLeft;
    final.tileDescription = `<b>{saxosLeft}x Apriling sax blows</b> left`;
    return final;

}

LuckySource getScepters() {
    LuckySource final;

    final.sourceName = "August scepter";
    final.url = "inventory.php?ftext=august+scepter";
    final.imageLookupName = "__item august scepter";

    final.LuckyCondition = __iotms_usable[lookupItem("august scepter")] && !get_property_boolean("_aug2Cast");
    final.LuckyCount = get_property_boolean("_aug2Cast") ? 0 : 1; 
    final.tileDescription = "<b>{final.LuckyCount}x August Scepter</b> holiday left";

    return final;
}

// Having generated these, we now get to generate a tile that combines them.

LuckySource [string] LuckySources;

LuckySources["saxo"] = getSaxes();
LuckySources["scepto"] = getScepters();
LuckySources["clovo"] = getClovers(); 
LuckySources["pillo"] = getLuckyisol();
LuckySources["AEDo"] = getAstralEnergy();

LuckySources["doggo"] = getOptimalDogs();

LuckySources["lindo"] = getLuckyLindys();

// Making it use the order we want; almost most recent to oldest, but pills on the bottom.
string [int] LuckyOrder = listMake("saxo","scepto","pillo","AEDo","clovo");

ChecklistEntry entry;

entry.url = "";
entry.image_lookup_name = "__effect Feeling Lucky";
entry.tags.id = "Lucky sources available";
entry.importance_level = -2;

string [int] description;
int totalLuckys = 0;

string line = HTMLGenerateSpanOfClass("Get a Lucky adventure!", "r_bold r_element_stench_desaturated");

foreach it, LuckyType in LuckyOrder
{
    LuckySource Luckyer = LuckySources[LuckyType];
    if (Luckyer.LuckyCount > 0 && Luckyer.LuckyCondition) {
        totalLuckys += Luckyer.LuckyCount;
        entry.url = Luckyer.url;

        line += "|*"+Luckyer.tileDescription; //"
    }

}

if (totalLuckys == 0) return;

// Append all the lines to a description
description.listAppend(line);

// Store the base description within the mouseover subentries
entry.subentries_on_mouse_over.listAppend(ChecklistSubentryMake(pluralise(totalLuckys, "Lucky usable", "Luckys usable"), "", description));

// Add a description that falls away when you hoverover
entry.subentries.listAppend(ChecklistSubentryMake(pluralise(totalLuckys, "Lucky usable", "Luckys usable"), "", description));

// OK, now we're going to make a big table with the NC recommendations. Yeesh. 
//   This tile is complicated, dude! First, start by initializing variables. 
//   Ezan's table creators are formatted as string[int][int], where the first
//   is the row and the second is the column... I think?

int totalNCsRemaining = 0;
string [int][int] table;

// In the synth tile, Ezan populates table_lines and builds out from there. I
//   am doing the same because I don't fully understand the syntax.
string [int] tableLines;

// This is a function that generates the right format for the table. Basically,
//   it ingests the table title + a separated list of all Lucky opportunities in
//   that summarized title. 
string populateLuckyTable(string title, string [int] desc) {
    string finalDesc = "";

    // If nothing got added, just add an "all done!" to make the user feel better
    if (desc.count() == 0) finalDesc = "All done!";

    // Have to add line breaks here.
    foreach k, d in desc {
        finalDesc += d+"<br>";
    }

    // Finally, generate a little sub-tile that looks like this:

    //   NAME OF LuckyS
    //   1 Luckysource
    //   1 Luckysource

    // Where the name is bold and the Luckysources are tiny. In some future world,
    // it might be nice to have coloring that grays those that are not available 
    // yet, but that is too much for this first implementation.
    return HTMLGenerateSpanOfClass(title, "r_bold") + "<br>" + HTMLGenerateSpanOfStyle(finalDesc, "font-size:0.8em");
}

// We aren't going to do a loop here; we're just going to populate table_lines
//   semi-manually, using logic that is roughly correct in each case.

// START = DELAY LuckyS
//   1x hidden apartment
//   1x hidden office

// Initialize your string-int array of the Luckys.
string [int] LuckyDelay;

// You want to use prefs when possible to isolate that the user can use that Lucky, then append to LuckyDelay
if (get_property_int("hiddenApartmentProgress") < 7) LuckyDelay.listAppend("1 hidden apartment");
if (get_property_int("hiddenOfficeProgress") < 7) LuckyDelay.listAppend("1 hidden office");

// Populate the Luckyy table.
tableLines[1] = populateLuckyTable("Delay Zones", LuckyDelay);

// Add the detected NCs to your total NCs remaining.
totalNCsRemaining += LuckyDelay.count();

// NEXT = 95% COMBAT LuckyS
//   12x friars NCs (-1 w/ carto)
//  1x castle basement
//  1x castle top

string [int] Lucky95;

// Ripping some code from the friars tile to count NCs encountered. First, names of the relevant NCs.
boolean [string] necks_known_ncs = $strings[How Do We Do It? Quaint and Curious Volume!,Strike One!,Olive My Love To You\, Oh.,Dodecahedrariffic!];
boolean [string] heart_known_ncs = $strings[Moon Over the Dark Heart,Running the Lode,I\, Martin,Imp Be Nimble\, Imp Be Quick];
boolean [string] elbow_known_ncs = $strings[Deep Imp Act,Imp Art\, Some Wisdom,A Secret\, But Not the Secret You're Looking For,Butter Knife?  I'll Take the Knife];

// Then, a tiny function to count the NCs found by zone for friars.
int countFriarNCs(boolean [string] known_ncs, location place) {
    int ncs_found = 0;

    if (known_ncs.count() > 0) {
        string [int] location_ncs = place.locationSeenNoncombats();

        foreach key, s in location_ncs
        {
            if (known_ncs contains s) ncs_found += 1;
        }
    }

    return ncs_found;

}

// You can remove one from the needed NCs if they have carto in their run.
int cartoAdjustment = lookupSkill("Comprehensive Cartography").have_skill() ? 1 : 0;

// Right now I think the raw logic off; I noted in the discord that we are having some small issues with
//   it showing extra NCs in a few places. Not really sure what's up with that? An easy fix is to
//   just set all of these to 0 in the event that questL06Friar = 'finished' -- I've implemented
//   this fix, but I do think it's worth solving this at some point.

int questPropMin = get_property('questL06Friar') == 'finished' ? 0 : 4;

int necksNCsLeft = min(4 - countFriarNCs(necks_known_ncs, $location[The Dark Neck of the Woods]) - cartoAdjustment, questPropMin);
int heartNCsLeft = min(4 - countFriarNCs(heart_known_ncs, $location[The Dark Heart of the Woods]), questPropMin);
int elbowNCsLeft = min(4 - countFriarNCs(elbow_known_ncs, $location[The Dark Elbow of the Woods]), questPropMin);

// Also correcting total NCs left here; we are using a "count" for this, and thus we only get 1
//   out of however many NCs actually are left in these zones, because it just counts the elements
//   in the list rather than the # prepending the element in the list.
if (necksNCsLeft > 0) {
    Lucky95.listAppend(`{necksNCsLeft} Dark Neck`);
    totalNCsRemaining += necksNCsLeft-1;
}
if (heartNCsLeft > 0) {
    Lucky95.listAppend(`{heartNCsLeft} Dark Heart`);
    totalNCsRemaining += heartNCsLeft-1;
}
if (elbowNCsLeft > 0) {
    Lucky95.listAppend(`{elbowNCsLeft} Dark Elbow`);
    totalNCsRemaining += elbowNCsLeft-1;
}

// If the pref is any of these, you can still Lucky the basement.
boolean [string] canLuckyBasement = $strings[unstarted,started,step1,step2,step3,step4,step5,step6,step7];

if (canLuckyBasement contains get_property("questL10Garbage")) Lucky95.listAppend("1 castle basement");

// If the quest is unfinished, you can Lucky the top floor.
if (get_property("questL10Garbage")!= "finished") Lucky95.listAppend("1 castle top floor");

tableLines[2] = populateLuckyTable("95% Combat", Lucky95);

totalNCsRemaining += Lucky95.count();

// NEXT = 90% COMBAT LuckyS
//  1x spookyraven bedroom
//  1x spookyraven bathroom

string [int] Lucky90;

// I don't really think I need to import this but I'm tired and copy/pasting logic from the spookyraven tile seems fine.
QuestState dance_quest_state;
QuestStateParseMafiaQuestProperty(dance_quest_state, "questM21Dance");
if ($item[Lady Spookyraven's powder puff].available_amount() == 0 && dance_quest_state.mafia_internal_step < 4) Lucky90.listAppend("1 spookyraven bathroom");
if ($item[Lady Spookyraven's dancing shoes].available_amount() == 0 && dance_quest_state.mafia_internal_step < 4) Lucky90.listAppend("1 spookyraven gallery");

tableLines[3] = populateLuckyTable("90% Combat", Lucky90);

totalNCsRemaining += Lucky90.count();

// Here is how Ezan built the lines into a table. It's kind of cute. First, make a placeholder "builder" line.
string [int] building_line;
foreach key in tableLines
{
    // For each key, you append it to the empty building lines.
    building_line.listAppend(tableLines[key]);

    // However, if the key is even, you append to the table. This works, because you are appending a two-element
    //   item into the table, so it creates a string [int] that creates a row in the table.
    if (key % 2 == 1)
    {
        table.listAppend(building_line);

        // Then, you clear out the building line, to reset the next table row
        building_line = listMakeBlankString();
    }
}
// Then, at the end, you append the remainder to the table.
if (building_line.count() > 0)
    table.listAppend(building_line);

// Having done this, you now append the NCs remaining subentry to the end of the core entry, with an on_mouse_over bit as well.

// However, I am going to be lazy, and not append either of these in the event the user is in CS/GG.
if (my_path().id != PATH_COMMUNITY_SERVICE && my_path().id != PATH_GREY_GOO) {
    entry.subentries.listAppend(ChecklistSubentryMake(pluralise(totalNCsRemaining, "NC remaining","NCs remaining"), "", HTMLGenerateSpanOfClass("Mouse over for the best Luckys!", "r_bold r_element_spooky_desaturated")));
    entry.subentries_on_mouse_over.listAppend(ChecklistSubentryMake(pluralise(totalNCsRemaining, "NC remaining","NCs remaining"), "", table.HTMLGenerateSimpleTableLines(false)));
}

if (entry.subentries.count() > 0) resource_entries.listAppend(entry);

}

uthuluc commented 1 month ago

https://github.com/loathers/TourGuide/commit/7d09b6b313c6def1c26bb5b59885b6b5284a9bd6