From 9b66dc451d1273bb0539e1cf5036d6857247753f Mon Sep 17 00:00:00 2001 From: Kevin Ferm <95472394+ToxicKevinFerm@users.noreply.github.com> Date: Tue, 30 Jan 2024 23:33:10 +0100 Subject: [PATCH] WIP: Sim talents in bulk sim (#4205) * WIP: Sim talents in bulk sim * Dont double sim and sort properly * Fix styling * Use proto.Equal for comparison --- proto/api.proto | 10 ++ sim/core/bulksim.go | 36 +++++- .../components/individual_sim_ui/bulk_tab.ts | 119 +++++++++++++++++- ui/scss/core/components/_bulk.scss | 40 ++++-- 4 files changed, 187 insertions(+), 18 deletions(-) diff --git a/proto/api.proto b/proto/api.proto index 21e5536bfc..076c8773ab 100644 --- a/proto/api.proto +++ b/proto/api.proto @@ -431,6 +431,12 @@ message BulkSimRequest { BulkSettings bulk_settings = 2; } +message TalentLoadout { + string talents_string = 1; + Glyphs glyphs = 2; + string name = 3; +} + message BulkSettings { repeated ItemSpec items = 1; bool combinations = 2; @@ -451,6 +457,9 @@ message BulkSettings { // Number of iterations per combo. // If set to 0 the sim core decides the optimal iterations. int32 iterations_per_combo = 11; + // Should sim talents as well + bool sim_talents = 12; + repeated TalentLoadout talents_to_sim = 13; } message BulkSimResult { @@ -462,6 +471,7 @@ message BulkSimResult { message BulkComboResult { repeated ItemSpecWithSlot items_added = 1; UnitMetrics unit_metrics = 2; + TalentLoadout talent_loadout = 3; } message ItemSpecWithSlot { diff --git a/sim/core/bulksim.go b/sim/core/bulksim.go index 85de99c47c..ad3c39f794 100644 --- a/sim/core/bulksim.go +++ b/sim/core/bulksim.go @@ -98,6 +98,7 @@ func (b *bulkSimRunner) Run(pctx context.Context, progress chan *proto.ProgressM // Gemming for now can happen before slots are decided. // We might have to add logic after slot decisions if we want to enforce keeping meta gem active. + if b.Request.BulkSettings.AutoGem { for _, replaceItem := range b.Request.BulkSettings.Items { itemData := ItemsByID[replaceItem.Id] @@ -184,7 +185,29 @@ func (b *bulkSimRunner) Run(pctx context.Context, progress chan *proto.ProgressM } substitutedRequest, changeLog := createNewRequestWithSubstitution(b.Request.BaseSettings, sub, b.Request.BulkSettings.AutoEnchant) if isValidEquipment(substitutedRequest.Raid.Parties[0].Players[0].Equipment) { + // Need to sim base dps of gear loudout validCombos = append(validCombos, singleBulkSim{req: substitutedRequest, cl: changeLog, eq: sub}) + // Todo(Netzone-GehennasEU): Make this its own step? + if !b.Request.BulkSettings.SimTalents { + + } else { + var talentsToSim = b.Request.BulkSettings.GetTalentsToSim() + + if len(talentsToSim) > 0 { + for _, talent := range talentsToSim { + sr := goproto.Clone(substitutedRequest).(*proto.RaidSimRequest) + cl := *changeLog + if sr.Raid.Parties[0].Players[0].TalentsString == talent.TalentsString && goproto.Equal(talent.Glyphs, sr.Raid.Parties[0].Players[0].Glyphs) { + continue + } + + sr.Raid.Parties[0].Players[0].TalentsString = talent.TalentsString + sr.Raid.Parties[0].Players[0].Glyphs = talent.Glyphs + cl.TalentLoadout = talent + validCombos = append(validCombos, singleBulkSim{req: sr, cl: &cl, eq: sub}) + } + } + } } } @@ -275,10 +298,10 @@ func (b *bulkSimRunner) Run(pctx context.Context, progress chan *proto.ProgressM um.Auras = nil um.Resources = nil um.Pets = nil - result.Results = append(result.Results, &proto.BulkComboResult{ - ItemsAdded: r.ChangeLog.AddedItems, - UnitMetrics: um, + ItemsAdded: r.ChangeLog.AddedItems, + UnitMetrics: um, + TalentLoadout: r.ChangeLog.TalentLoadout, }) } @@ -337,6 +360,7 @@ func (b *bulkSimRunner) getRankedResults(pctx context.Context, validCombos []sin for _, singleCombo := range validCombos { <-tickets singleSimProgress := make(chan *proto.ProgressMetrics) + // watches this progress and pushes up to main reporter. go func(prog chan *proto.ProgressMetrics) { var prevDone int32 @@ -352,6 +376,7 @@ func (b *bulkSimRunner) getRankedResults(pctx context.Context, validCombos []sin // actually run the sim in here. go func(sub singleBulkSim) { // overwrite the requests iterations with the input for this function. + sub.req.SimOptions.Iterations = int32(iterations) results <- &itemSubstitutionSimResult{ Request: sub.req, @@ -374,7 +399,7 @@ func (b *bulkSimRunner) getRankedResults(pctx context.Context, validCombos []sin cancel() // cancel reporter return nil, nil, errors.New("simulation failed: " + result.Result.ErrorResult) } - if !result.Substitution.HasItemReplacements() { + if !result.Substitution.HasItemReplacements() && result.ChangeLog.TalentLoadout == nil { baseResult = result } rankedResults[i] = result @@ -620,7 +645,8 @@ type itemWithSlot struct { // raidSimRequestChangeLog stores a change log of which items were added and removed from the base // equipment set. type raidSimRequestChangeLog struct { - AddedItems []*proto.ItemSpecWithSlot + AddedItems []*proto.ItemSpecWithSlot + TalentLoadout *proto.TalentLoadout } // createNewRequestWithSubstitution creates a copy of the input RaidSimRequest and applis the given diff --git a/ui/core/components/individual_sim_ui/bulk_tab.ts b/ui/core/components/individual_sim_ui/bulk_tab.ts index 5c9965bf3c..f8600261bf 100644 --- a/ui/core/components/individual_sim_ui/bulk_tab.ts +++ b/ui/core/components/individual_sim_ui/bulk_tab.ts @@ -7,13 +7,13 @@ import { TypedEvent } from "../../typed_event"; import { EventID } from '../../typed_event.js'; -import { BulkComboResult, BulkSettings, ItemSpecWithSlot, ProgressMetrics } from "../../proto/api"; -import { EquipmentSpec, Faction, GemColor, ItemSlot, ItemSpec, SimDatabase, SimEnchant, SimGem, SimItem, Spec } from "../../proto/common"; +import { BulkComboResult, BulkSettings, ItemSpecWithSlot, ProgressMetrics, TalentLoadout } from "../../proto/api"; +import { EquipmentSpec, Faction, GemColor, Glyphs, ItemSlot, ItemSpec, SimDatabase, SimEnchant, SimGem, SimItem, Spec } from "../../proto/common"; import { ItemData, ItemList, ItemRenderer, SelectorModal, SelectorModalTabs } from "../gear_picker"; import { SimTab } from "../sim_tab"; -import { UIEnchant, UIGem, UIItem } from "../../proto/ui"; +import { SavedTalents, UIEnchant, UIGem, UIItem } from "../../proto/ui"; import { EquippedItem } from "../../proto_utils/equipped_item"; import { Component } from "../component"; import { ResultsViewer } from "../results_viewer"; @@ -50,6 +50,7 @@ export class BulkGearJsonImporter extends Importer { } this.close(); } catch (e: any) { + console.warn(e); alert(e.toString()); } } @@ -81,6 +82,17 @@ class BulkSimResultRenderer { parent.bodyElement.appendChild(itemsContainer); parent.bodyElement.appendChild(dpsDivParent); + const talentText = document.createElement('p'); + talentText.classList.add('talent-loadout-text') + if (result.talentLoadout && typeof result.talentLoadout === 'object') { + if (typeof result.talentLoadout.name === 'string') { + talentText.textContent = 'Talent loadout used: ' + result.talentLoadout.name; + } + } else { + talentText.textContent = 'Current talents' + } + + dpsDiv.appendChild(talentText); if (result.itemsAdded && result.itemsAdded.length > 0) { const equipBtn = document.createElement('button'); equipBtn.textContent = 'Equip'; @@ -105,7 +117,7 @@ class BulkSimResultRenderer { p.textContent = this.itemSlotName(is); renderer.nameElem.appendChild(p); } - } else { + } else if (!result.talentLoadout || typeof result.talentLoadout !== 'object') { const p = document.createElement('p'); p.textContent = 'No changes - this is your currently equipped gear!'; parent.bodyElement.appendChild(p); @@ -228,8 +240,10 @@ export class BulkTab extends SimTab { private doCombos: boolean; private fastMode: boolean; private autoGem: boolean; + private simTalents: boolean; private autoEnchant: boolean; private defaultGems: SimGem[]; + private savedTalents: TalentLoadout[]; private gemIconElements: HTMLImageElement[]; constructor(parentElem: HTMLElement, simUI: IndividualSimUI) { @@ -256,6 +270,8 @@ export class BulkTab extends SimTab { this.fastMode = true; this.autoGem = true; this.autoEnchant = true; + this.savedTalents = []; + this.simTalents = false; this.defaultGems = [UIGem.create(), UIGem.create(), UIGem.create(), UIGem.create()]; this.gemIconElements = []; this.buildTabContent(); @@ -277,7 +293,9 @@ export class BulkTab extends SimTab { this.doCombos = settings.combinations; this.fastMode = settings.fastMode; this.autoEnchant = settings.autoEnchant; + this.savedTalents = settings.talentsToSim; this.autoGem = settings.autoGem; + this.simTalents = settings.simTalents; this.defaultGems = new Array( SimGem.create({ id: settings.defaultRedGem }), SimGem.create({ id: settings.defaultYellowGem }), @@ -311,6 +329,8 @@ export class BulkTab extends SimTab { fastMode: this.fastMode, autoEnchant: this.autoEnchant, autoGem: this.autoGem, + simTalents: this.simTalents, + talentsToSim: this.savedTalents, defaultRedGem: this.defaultGems[0].id, defaultYellowGem: this.defaultGems[1].id, defaultBlueGem: this.defaultGems[2].id, @@ -641,6 +661,81 @@ export class BulkTab extends SimTab { }); settingsBlock.bodyElement.appendChild(clearButton); + // Talents to sim + const talentsToSimDiv = document.createElement("div") + if (this.simTalents) { + talentsToSimDiv.style.display = "flex"; + } else { + talentsToSimDiv.style.display = "none"; + } + talentsToSimDiv.classList.add("talents-picker-container") + const talentsLabel = document.createElement("label") + talentsLabel.innerText = "Pick talents to sim (will increase time to sim)"; + talentsToSimDiv.appendChild(talentsLabel); + const talentsContainerDiv = document.createElement("div"); + talentsContainerDiv.classList.add("talents-container"); + + const dataStr = window.localStorage.getItem(this.simUI.getSavedTalentsStorageKey()); + + let jsonData; + try { + if (dataStr !== null) { + jsonData = JSON.parse(dataStr); + } + } catch (e) { + console.warn('Invalid json for local storage value: ' + dataStr); + } + const handleToggle = (frag: HTMLElement, load: TalentLoadout) => { + let chipDiv = frag.querySelector('.saved-data-set-chip'); + let exists = this.savedTalents.some(talent => talent.name === load.name); // Replace 'id' with your unique identifier + + console.log('Exists:', exists); + console.log('Load Object:', load); + console.log('Saved Talents Before Update:', this.savedTalents); + + if (exists) { + // If the object exists, find its index and remove it + let indexToRemove = this.savedTalents.findIndex(talent => talent.name === load.name); + this.savedTalents.splice(indexToRemove, 1); + chipDiv?.classList.remove('active'); + } else { + // If the object does not exist, add it + this.savedTalents.push(load); + chipDiv?.classList.add('active'); + } + + console.log('Updated savedTalents:', this.savedTalents); + } + for (let name in jsonData) { + try { + console.log(name, jsonData[name]); + let savedTalentLoadout = SavedTalents.fromJson(jsonData[name]); + var loadout = { talentsString: savedTalentLoadout.talentsString, glyphs: savedTalentLoadout.glyphs, name: name }; + + let index = this.savedTalents.findIndex(talent => JSON.stringify(talent) === JSON.stringify(loadout)); + const talentFragment = document.createElement('fragment'); + talentFragment.innerHTML = ` +
+ ${name} +
`; + + console.log("Adding event for loadout", loadout); + // Wrap the event listener addition in an IIFE + (function (talentFragment, loadout) { + talentFragment.addEventListener("click", () => handleToggle(talentFragment, loadout)); + })(talentFragment, loadout); + + talentsContainerDiv.appendChild(talentFragment); + } catch (e) { + console.log(e); + console.warn('Failed parsing saved data: ' + jsonData[name]); + } + } + + talentsToSimDiv.append(talentsContainerDiv); + ////////////////////// + //////////////////////////////////// + // Default Gem Options const defaultGemDiv = document.createElement("div"); if (this.autoGem) { @@ -740,7 +835,23 @@ export class BulkTab extends SimTab { } }); + new BooleanPicker(settingsBlock.bodyElement, this, { + label: "Sim Talents", + labelTooltip: "When checked bulk simulator will sim chosen talent setups. Warning, it might cause the bulk sim to run for a lot longer", + changedEvent: (obj: BulkTab) => this.itemsChangedEmitter, + getValue: (obj) => this.simTalents, + setValue: (id: EventID, obj: BulkTab, value: boolean) => { + obj.simTalents = value + if (value) { + talentsToSimDiv.style.display = "flex"; + } else { + talentsToSimDiv.style.display = "none"; + } + } + }); + settingsBlock.bodyElement.appendChild(defaultGemDiv); + settingsBlock.bodyElement.appendChild(talentsToSimDiv); } private setSimProgress(progress: ProgressMetrics, iterPerSecond: number, currentRound: number, rounds: number, combinations: number) { diff --git a/ui/scss/core/components/_bulk.scss b/ui/scss/core/components/_bulk.scss index 3e7a8902ec..6e0b83363c 100644 --- a/ui/scss/core/components/_bulk.scss +++ b/ui/scss/core/components/_bulk.scss @@ -6,17 +6,19 @@ .bulk-result { margin-bottom: 0.5rem; + .bulk-equipit { height: 40px; flex: 1; } + .bulk-results-body { flex-direction: row; } } .bulk-result-item-slot { - color:aliceblue; + color: aliceblue; margin-left: 0.5rem; font-size: 0.5 * map.get($font-sizes, 6); } @@ -25,7 +27,8 @@ border-bottom: none; } -.bulk-result-header-positive, .bulk-result-header-negative { +.bulk-result-header-positive, +.bulk-result-header-negative { font-weight: bold; margin-left: 0.5rem; } @@ -60,6 +63,7 @@ grid-template-columns: repeat(3, 32%); grid-template-rows: auto; flex: 5; + .item-picker-root { padding: 2px; } @@ -90,9 +94,9 @@ align-items: center; justify-content: center; z-index: 1; - background: rgba(0,0,0,0.5); + background: rgba(0, 0, 0, 0.5); max-height: 100vh; - + .results-content { .results-sim { div { @@ -120,6 +124,7 @@ margin-top: 5px; border-bottom: 1px solid white; padding: 3px; + label { flex: 1; } @@ -133,25 +138,42 @@ display: flex; } } - + .batch-search-results { list-style: none; - background: rgba(0,0,0,0.3); + background: rgba(0, 0, 0, 0.3); + li { cursor: pointer; background: transparent; + &:hover { - background: rgba(255,255,255, 0.3); + background: rgba(255, 255, 255, 0.3); } } } + .talent-loadout-text { + font-size: smaller; + } + + .talents-picker-container { + display: flex; + flex-direction: column; + border-bottom: 1px solid white; + padding-bottom: 5px; + + .talents-container { + display: flex; + } + } + .default-gem-container { display: flex; flex-direction: column; border-bottom: 1px solid white; padding-bottom: 5px; - + .gem-socket-container { height: 40px; width: 40px; @@ -172,4 +194,4 @@ .results-sim { margin-right: map-get($spacers, 3); } -} +} \ No newline at end of file