mirror of
https://github.com/IvarK/AntimatterDimensionsSourceCode.git
synced 2024-11-10 06:02:13 +00:00
Improve glyph preset loading (fixes #3312)
This commit is contained in:
parent
2a433149d6
commit
0e96cda876
@ -60,65 +60,98 @@ export default {
|
||||
const name = this.names[id] === "" ? "" : `: ${this.names[id]}`;
|
||||
return `Glyph Preset #${id + 1}${name}`;
|
||||
},
|
||||
// Let the player load if the currently equipped glyphs are a subset of the preset
|
||||
canLoadSet(set) {
|
||||
let toLoad = [...set];
|
||||
let currActive = [...Glyphs.active.filter(g => g)];
|
||||
for (const targetGlyph of currActive) {
|
||||
const matchingGlyph = Glyphs.findByValues(targetGlyph, toLoad, {
|
||||
level: this.level ? -1 : 0,
|
||||
strength: this.rarity ? -1 : 0,
|
||||
effects: this.effects ? -1 : 0
|
||||
});
|
||||
if (!matchingGlyph) return false;
|
||||
toLoad = toLoad.filter(g => g !== matchingGlyph);
|
||||
currActive = currActive.filter(g => g !== targetGlyph);
|
||||
}
|
||||
return toLoad.length > 0;
|
||||
},
|
||||
saveGlyphSet(id) {
|
||||
if (!this.hasEquipped || player.reality.glyphs.sets[id].glyphs.length) return;
|
||||
player.reality.glyphs.sets[id].glyphs = Glyphs.active.compact();
|
||||
this.refreshGlyphSets();
|
||||
EventHub.dispatch(GAME_EVENT.GLYPH_SET_SAVE_CHANGE);
|
||||
},
|
||||
loadGlyphSet(set) {
|
||||
if (!this.canLoadSet(set) || !this.setLengthValid(set)) return;
|
||||
// A proper full solution to this turns out to contain an NP-hard problem as a subproblem, so instead we do
|
||||
// somwthing which should work in most cases - we match greedily when it won't obviously lead to an incomplete
|
||||
// preset match, and leniently when matching greedily may lead to an incomplete set being loaded
|
||||
loadGlyphSet(set, id) {
|
||||
if (!this.setLengthValid(set)) return;
|
||||
let glyphsToLoad = [...set];
|
||||
const activeGlyphs = [...Glyphs.active.filter(g => g)];
|
||||
|
||||
// If we already have a subset of the preset loaded, don't try to load glyphs from that subset again
|
||||
let toLoad = [...set];
|
||||
let currActive = [...Glyphs.active.filter(g => g)];
|
||||
for (const targetGlyph of currActive) {
|
||||
const matchingGlyph = Glyphs.findByValues(targetGlyph, toLoad, {
|
||||
// Create an array where each entry contains a single active glyph and all its matches in the preset which it
|
||||
// could fill in for, based on the preset loading settings
|
||||
const activeOptions = [];
|
||||
for (const glyph of activeGlyphs) {
|
||||
const options = Glyphs.findByValues(glyph, glyphsToLoad, {
|
||||
level: this.level ? -1 : 0,
|
||||
strength: this.rarity ? -1 : 0,
|
||||
effects: this.effects ? -1 : 0
|
||||
});
|
||||
if (!matchingGlyph) continue;
|
||||
toLoad = toLoad.filter(g => g !== matchingGlyph);
|
||||
currActive = currActive.filter(g => g !== targetGlyph);
|
||||
activeOptions.push({ glyph, options });
|
||||
}
|
||||
|
||||
// Try to load the rest from the inventory
|
||||
let missingGlyphs = 0;
|
||||
for (const targetGlyph of toLoad) {
|
||||
const matchingGlyph = Glyphs.findByValues(targetGlyph, Glyphs.sortedInventoryList, {
|
||||
level: this.level ? 1 : 0,
|
||||
strength: this.rarity ? 1 : 0,
|
||||
effects: this.effects ? 1 : 0
|
||||
// Using the active glyphs one by one, select matching to-be-loaded preset glyphs to be removed from the list.
|
||||
// This makes sure the inventory doesn't attempt to match a glyph which is already satisfied by an equipped one
|
||||
const selectedFromActive = this.findSelectedGlyphs(activeOptions, 5);
|
||||
for (const glyph of selectedFromActive) glyphsToLoad = glyphsToLoad.filter(g => g !== glyph);
|
||||
|
||||
// For the remaining glyphs to load from the preset, find all their appropriate matches within the inventory.
|
||||
// This is largely the same as earlier with the equipped glyphs
|
||||
const remainingOptions = [];
|
||||
for (let index = 0; index < glyphsToLoad.length; index++) {
|
||||
const glyph = glyphsToLoad[index];
|
||||
const options = Glyphs.findByValues(glyph, Glyphs.sortedInventoryList, {
|
||||
level: this.level ? -1 : 0,
|
||||
strength: this.rarity ? -1 : 0,
|
||||
effects: this.effects ? -1 : 0
|
||||
});
|
||||
if (!matchingGlyph) {
|
||||
missingGlyphs++;
|
||||
continue;
|
||||
}
|
||||
remainingOptions[index] = { glyph, options };
|
||||
}
|
||||
|
||||
// This is scanned through similarly to the active slot glyphs, except we need to make sure we don't try to
|
||||
// match more glyphs than we have room for
|
||||
const selectedFromInventory = this.findSelectedGlyphs(remainingOptions,
|
||||
Glyphs.active.countWhere(g => g === null));
|
||||
for (const glyph of selectedFromInventory) glyphsToLoad = glyphsToLoad.filter(g => g !== glyph);
|
||||
|
||||
// Actually equip the glyphs and then notify how successful (or not) the loading was
|
||||
let missingGlyphs = glyphsToLoad.length;
|
||||
for (const glyph of selectedFromInventory) {
|
||||
const idx = Glyphs.active.indexOf(null);
|
||||
if (idx !== -1) Glyphs.equip(matchingGlyph, idx);
|
||||
if (idx !== -1) {
|
||||
Glyphs.equip(glyph, idx);
|
||||
missingGlyphs--;
|
||||
}
|
||||
}
|
||||
if (missingGlyphs) {
|
||||
GameUI.notify.error(`Could not find ${missingGlyphs} ${pluralize("Glyph", missingGlyphs)} to load from
|
||||
Glyph preset.`);
|
||||
if (missingGlyphs > 0) {
|
||||
GameUI.notify.error(`Could not find or equip ${missingGlyphs} ${pluralize("Glyph", missingGlyphs)} from
|
||||
${this.setName(id)}.`);
|
||||
} else {
|
||||
GameUI.notify.success(`Successfully loaded ${this.setName(id)}.`);
|
||||
}
|
||||
EventHub.dispatch(GAME_EVENT.GLYPH_SET_SAVE_CHANGE);
|
||||
},
|
||||
// Given a list of options for suitable matches to those glyphs and a maximum glyph count to match, returns the
|
||||
// set of glyphs which should be loaded. This is a tricky matching process to do since on one hand we don't want
|
||||
// early matches to prevent later ones, but on the other hand matching too leniently can cause any passed-on later
|
||||
// requirements to be too strict (eg. preset 1234 and equipped 234 could match 123, leaving an unmatchable 4).
|
||||
// The compromise solution here is to check how many choices the next-strictest option list has - if it only has
|
||||
// one choice then we pick conservatively (the weakest glyph) - otherwise we pick greedily (the strongest glyph).
|
||||
findSelectedGlyphs(optionList, maxGlyphs) {
|
||||
// We do a weird composite sorting function here in order to make sure that glyphs get treated by type first, and
|
||||
// with in type are generally ordered in strictest to most lenient in terms of matches. Note that the options
|
||||
// are sorted internally starting with the strictest match first
|
||||
optionList.sort((a, b) => 100 * (a.glyph.type.charCodeAt(0) - b.glyph.type.charCodeAt(0)) +
|
||||
(a.options.length - b.options.length));
|
||||
const toLoad = [];
|
||||
let slotsLeft = maxGlyphs;
|
||||
for (let index = 0; index < optionList.length; index++) {
|
||||
if (slotsLeft === 0) break;
|
||||
const entry = optionList[index];
|
||||
const greedyPick = index === optionList.length - 1 || optionList[index + 1].options.length > 1;
|
||||
|
||||
const filteredOptions = entry.options.filter(g => !toLoad.includes(g));
|
||||
if (filteredOptions.length === 0) continue;
|
||||
const selectedGlyph = filteredOptions[greedyPick ? 0 : (filteredOptions.length - 1)];
|
||||
toLoad.push(selectedGlyph);
|
||||
slotsLeft--;
|
||||
}
|
||||
return toLoad;
|
||||
},
|
||||
deleteGlyphSet(id) {
|
||||
if (!player.reality.glyphs.sets[id].glyphs.length) return;
|
||||
@ -137,6 +170,11 @@ export default {
|
||||
setLengthValid(set) {
|
||||
return set.length && set.length <= Glyphs.activeSlotCount;
|
||||
},
|
||||
loadingTooltip(set) {
|
||||
return this.setLengthValid(set) && this.hasEquipped
|
||||
? "This set may not load properly because you already have some Glyphs equipped"
|
||||
: null;
|
||||
},
|
||||
glyphSetKey(set, index) {
|
||||
return `${index} ${Glyphs.hash(set)}`;
|
||||
}
|
||||
@ -219,9 +257,10 @@ export default {
|
||||
Save
|
||||
</button>
|
||||
<button
|
||||
v-tooltip="loadingTooltip(set)"
|
||||
class="c-glyph-set-save-button"
|
||||
:class="{'c-glyph-set-save-button--unavailable': !canLoadSet(set) || !setLengthValid(set)}"
|
||||
@click="loadGlyphSet(set)"
|
||||
:class="{'c-glyph-set-save-button--unavailable': !setLengthValid(set)}"
|
||||
@click="loadGlyphSet(set, id)"
|
||||
>
|
||||
Load
|
||||
</button>
|
||||
@ -254,20 +293,4 @@ export default {
|
||||
.c-glyph-set-preview-area {
|
||||
width: 18rem;
|
||||
}
|
||||
|
||||
.l-glyph-sacrifice-options__help {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: calc(100% - 1.8rem);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.c-glyph-sacrifice-options__help {
|
||||
font-size: 1.2rem;
|
||||
color: var(--color-reality-dark);
|
||||
}
|
||||
|
||||
.s-base--dark .c-glyph-sacrifice-options__help {
|
||||
color: var(--color-reality-light);
|
||||
}
|
||||
</style>
|
||||
|
@ -214,12 +214,6 @@ export function getGlyphIDsFromBitmask(bitmask) {
|
||||
return getGlyphEffectsFromBitmask(bitmask).map(x => x.id);
|
||||
}
|
||||
|
||||
export function hasAtLeastGlyphEffects(needleBitmask, haystackBitmask) {
|
||||
const needle = getGlyphIDsFromBitmask(needleBitmask);
|
||||
const haystack = getGlyphIDsFromBitmask(haystackBitmask);
|
||||
return haystack.every(x => needle.includes(x));
|
||||
}
|
||||
|
||||
class FunctionalGlyphType {
|
||||
/**
|
||||
* @param {Object} setup
|
||||
|
@ -192,40 +192,66 @@ export const Glyphs = {
|
||||
this.validate();
|
||||
EventHub.dispatch(GAME_EVENT.GLYPHS_CHANGED);
|
||||
},
|
||||
// This compares targetGlyph to all the glyphs in searchList, returning a subset of them which fulfills the comparison
|
||||
// direction specified by the parameters in fuzzyMatch:
|
||||
// -1: Will find glyphs which are equal to or worse than targetGlyph
|
||||
// 0: Will only return glyphs which have identical values
|
||||
// +1: Will find glyphs which are equal to or better than targetGlyph
|
||||
findByValues(targetGlyph, searchList, fuzzyMatch = { level, strength, effects }) {
|
||||
// We need comparison to go both ways for normal matching and subset matching for partially-equipped sets
|
||||
const compFn = (op, comp1, comp2) => {
|
||||
switch (op) {
|
||||
case -1:
|
||||
return comp1 <= comp2;
|
||||
return comp2 - comp1;
|
||||
case 0:
|
||||
return comp1 === comp2;
|
||||
return comp1 === comp2 ? 0 : -1;
|
||||
case 1:
|
||||
return comp1 >= comp2;
|
||||
return comp1 - comp2;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// Returns a number based on how much the small mask is found inside of the large mask. Returns a non-negative
|
||||
// number if small contains all of large, with a value equal to the number of extra bits. Otherwise, returns a
|
||||
// negative number equal to the negative of the number of bits that large has which small doesn't.
|
||||
const matchedEffects = (large, small) => {
|
||||
if ((large & small) === large) return countValuesFromBitmask(small - large);
|
||||
return -countValuesFromBitmask(large - (large & small));
|
||||
};
|
||||
|
||||
// Make an array containing all glyphs which match the given criteria, with an additional "quality" prop in order
|
||||
// to determine roughly how good the glyph itself is relative to other matches
|
||||
const allMatches = [];
|
||||
for (const glyph of searchList) {
|
||||
const type = glyph.type === targetGlyph.type;
|
||||
let eff = false;
|
||||
let eff;
|
||||
switch (fuzzyMatch.effects) {
|
||||
case -1:
|
||||
eff = hasAtLeastGlyphEffects(targetGlyph.effects, glyph.effects);
|
||||
eff = matchedEffects(targetGlyph.effects, glyph.effects);
|
||||
break;
|
||||
case 0:
|
||||
eff = glyph.effects === targetGlyph.effects;
|
||||
eff = glyph.effects === targetGlyph.effects ? 0 : -1;
|
||||
break;
|
||||
case 1:
|
||||
eff = hasAtLeastGlyphEffects(glyph.effects, targetGlyph.effects);
|
||||
eff = matchedEffects(glyph.effects, targetGlyph.effects);
|
||||
break;
|
||||
}
|
||||
const str = compFn(fuzzyMatch.strength, glyph.strength, targetGlyph.strength);
|
||||
const lvl = compFn(fuzzyMatch.level, glyph.level, targetGlyph.level);
|
||||
const str = compFn(fuzzyMatch.strength, glyph.strength, targetGlyph.strength) / 2.5;
|
||||
const lvl = compFn(fuzzyMatch.level, glyph.level, targetGlyph.level) / 5000;
|
||||
const sym = glyph.symbol === targetGlyph.symbol;
|
||||
if (type && eff && str && lvl && sym) return glyph;
|
||||
if (type && eff >= 0 && str >= 0 && lvl >= 0 && sym) {
|
||||
allMatches.push({
|
||||
glyph,
|
||||
// Flatten glyph qualities, with 10% rarity, 500 levels, and an extra effect all being equal value. This
|
||||
// is used to sort the options by some rough measure of distance from the target glyph
|
||||
gap: str + lvl + eff / 10
|
||||
});
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
|
||||
// Sort by increasing gap, then discard the value as it's not directly used anywhere else
|
||||
allMatches.sort((a, b) => a.gap - b.gap);
|
||||
return allMatches.map(m => m.glyph);
|
||||
},
|
||||
findById(id) {
|
||||
return player.reality.glyphs.inventory.find(glyph => glyph.id === id);
|
||||
|
Loading…
Reference in New Issue
Block a user