Skip to content

Commit

Permalink
Merge pull request #306 from xiv-gear-planner/materia-fill-settings
Browse files Browse the repository at this point in the history
Expand materia auto-fill settings
  • Loading branch information
xpdota authored Sep 4, 2024
2 parents a82acf0 + 8823e04 commit a5a0bb4
Show file tree
Hide file tree
Showing 9 changed files with 334 additions and 51 deletions.
2 changes: 1 addition & 1 deletion packages/common-ui/styles/common.less
Original file line number Diff line number Diff line change
Expand Up @@ -2940,7 +2940,7 @@ body.modern {
.gear-sheet-midbar-area {
background-color: var(--button-color);

button {
button, select {
--button-color: var(--bg-color);
}
}
Expand Down
130 changes: 118 additions & 12 deletions packages/core/src/gear.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,18 @@ import {
GearSetResult,
Materia,
MateriaAutoFillController,
MateriaAutoFillPrio,
MateriaAutoFillPrio, MateriaMemoryExport,
NO_SYNC_STATS,
RawStatKey,
RawStats,
RawStats, RelicStatMemoryExport,
RelicStats,
SetDisplaySettingsExport,
SetDisplaySettingsExport, SlotMateriaMemoryExport,
XivCombatItem
} from "@xivgear/xivmath/geartypes";
import {Inactivitytimer} from "./util/inactivitytimer";
import {addStats, finalizeStats} from "@xivgear/xivmath/xivstats";
import {GearPlanSheet} from "./sheet";
import {isMateriaAllowed} from "./materia/materia_utils";


export function nonEmptyRelicStats(stats: RelicStats | undefined): boolean {
Expand Down Expand Up @@ -71,9 +72,70 @@ export class RelicStatMemory {
}
}

export type RelicStatMemoryExport = {
[p: number]: RelicStats;
};

// TODO: this is not yet part of exports
export class MateriaMemory {
// Map from equipment slot to item ID to list of materia IDs.
// The extra layer is needed here because you might have the same (non-unique) ring equipped in both slots.
private readonly memory: Map<EquipSlotKey, Map<number, number[]>> = new Map();

/**
* Get the remembered materia for an item
*
* If there is no mapping, returns an empty list
*
* @param equipSlot The equipment slot (needed to differentiate left/right ring)
* @param item The item in question
*/
get(equipSlot: EquipSlotKey, item: GearItem): number[] {
const bySlot = this.memory.get(equipSlot);
if (!bySlot) {
return []
}
const byItem = bySlot.get(item.id);
return byItem ?? [];
}

/**
* Provide a new list of materia by providing an equipped item
*
* @param equipSlot The slot
* @param item The item which is about to be unequipped from that slot.
*/
set(equipSlot: EquipSlotKey, item: EquippedItem) {
let bySlot: Map<number, number[]>;
if (this.memory.has(equipSlot)) {
bySlot = this.memory.get(equipSlot);
}
else {
bySlot = new Map();
this.memory.set(equipSlot, bySlot);
}
bySlot.set(item.gearItem.id, item.melds.map(meld => meld.equippedMateria?.id ?? -1));
}

export(): MateriaMemoryExport {
const out: MateriaMemoryExport = {};
this.memory.forEach((slotValue, slotKey) => {
const items: SlotMateriaMemoryExport[] = [];
slotValue.forEach((materiaIds, itemId) => {
items.push([itemId, materiaIds]);
});
out[slotKey] = items;
});
return out;
}

import(memory: MateriaMemoryExport) {
Object.entries(memory).forEach(([slotKey, itemMemory]) => {
const slotMap: Map<number, number[]> = new Map();
for (const itemMemoryElement of itemMemory) {
slotMap.set(itemMemoryElement[0], itemMemoryElement[1]);
}
this.memory.set(slotKey as EquipSlotKey, slotMap);
});
}
}


export class SetDisplaySettings {
Expand Down Expand Up @@ -163,6 +225,7 @@ export class CharacterGearSet {
this._notifyListeners();
});
readonly relicStatMemory: RelicStatMemory = new RelicStatMemory();
readonly materiaMemory: MateriaMemory = new MateriaMemory();
readonly displaySettings: SetDisplaySettings = new SetDisplaySettings();
isSeparator: boolean = false;

Expand Down Expand Up @@ -216,20 +279,63 @@ export class CharacterGearSet {
this.notifyListeners();
}

setEquip(slot: EquipSlotKey, item: GearItem, materiaAutoFill?: MateriaAutoFillController) {
// TODO: this is also a good place to implement temporary persistent materia entry
setEquip(slot: EquipSlotKey, item: GearItem, materiaAutoFillController?: MateriaAutoFillController) {
if (this.equipment[slot]?.gearItem === item) {
return;
}
const old = this.equipment[slot];
if (old && old.relicStats) {
this.relicStatMemory.set(old.gearItem, old.relicStats);
if (old) {
if (old.relicStats) {
this.relicStatMemory.set(old.gearItem, old.relicStats);
}
if (old.melds.length > 0) {
this.materiaMemory.set(slot, old);
}
}
this.invalidate();
this.equipment[slot] = this.toEquippedItem(item);
console.log(`Set ${this.name}: slot ${slot} => ${item?.name}`);
if (materiaAutoFill && materiaAutoFill.autoFillNewItem) {
this.fillMateria(materiaAutoFill.prio, false, [slot]);
if (materiaAutoFillController) {
const mode = materiaAutoFillController.autoFillMode;
if (mode === 'leave_empty') {
// Do nothing
}
else {
// This var tracks what we would like to re-equip
let reEquip: Materia[] = [];
if (mode === 'retain_slot' || mode === 'retain_slot_else_prio') {
if (old && old.melds.find(meld => meld.equippedMateria)) {
reEquip = old.melds.map(meld => meld.equippedMateria).filter(value => value);
}
}
else if (mode === 'retain_item' || mode === 'retain_item_else_prio') {
const materiaIds = this.materiaMemory.get(slot, item);
materiaIds.forEach((materiaId, index) => {
const meld = this.equipment[slot].melds[index];
if (meld) {
reEquip.push(this._sheet.getMateriaById(materiaId));
}
});
}
if (mode === 'autofill'
|| (reEquip.length === 0
&& (mode === 'retain_item_else_prio' || mode === 'retain_slot_else_prio'))) {
this.fillMateria(materiaAutoFillController.prio, false, [slot]);
}
else {
const eq = this.equipment[slot];
for (let i = 0; i < reEquip.length; i++) {
if (i in eq.melds) {
const meld = eq.melds[i];
const materia = reEquip[i];
if (isMateriaAllowed(materia, meld.materiaSlot)) {
meld.equippedMateria = reEquip[i];
}
}
}

}
}
}
this.notifyListeners()
}
Expand Down
11 changes: 11 additions & 0 deletions packages/core/src/materia/materia_utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import {Materia, MateriaSlot} from "@xivgear/xivmath/geartypes";


export function isMateriaAllowed(materia: Materia, materiaSlot: MateriaSlot) {
const highGradeAllowed = materiaSlot.allowsHighGrade;
if (materia.isHighGrade && !highGradeAllowed) {
return false;
}
const maxGradeAllowed = materiaSlot.maxGrade;
return materia.materiaGrade <= maxGradeAllowed;
}
41 changes: 26 additions & 15 deletions packages/core/src/sheet.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ // TODO: get back to fixing this at some point
/* eslint-disable @typescript-eslint/no-explicit-any */ // TODO: get back to fixing this at some point
import {
CURRENT_MAX_LEVEL,
defaultItemDisplaySettings,
Expand All @@ -24,6 +24,7 @@ import {
Materia,
MateriaAutoFillController,
MateriaAutoFillPrio,
MateriaFillMode,
MeldableMateriaSlot,
OccGearSlotKey,
PartyBonusAmount,
Expand All @@ -47,6 +48,7 @@ import {CustomItem} from "./customgear/custom_item";
import {CustomFood} from "./customgear/custom_food";
import {IlvlSyncInfo} from "./datamanager_xivapi";
import {toSerializableForm} from "@xivgear/xivmath/xivstats";
import {isMateriaAllowed} from "./materia/materia_utils";

export type SheetCtorArgs = ConstructorParameters<typeof GearPlanSheet>
export type SheetContstructor<SheetType extends GearPlanSheet> = (...values: SheetCtorArgs) => SheetType;
Expand Down Expand Up @@ -166,7 +168,8 @@ export class GearPlanSheet {

// Materia autofill
protected materiaAutoFillPrio: MateriaAutoFillPrio;
protected materiaAutoFillSelectedItems: boolean;
// protected materiaAutoFillSelectedItems: boolean;
protected materiaFillMode: MateriaFillMode;

// Display settings
private _showAdvancedStats: boolean;
Expand Down Expand Up @@ -215,7 +218,7 @@ export class GearPlanSheet {
// Just picking a bogus value so the user understands what it is
minGcd: importedData.mfMinGcd ?? 2.05
};
this.materiaAutoFillSelectedItems = importedData.mfni ?? false;
this.materiaFillMode = importedData.mfm ?? 'retain_item';

if (importedData.customItems) {
importedData.customItems.forEach(ci => {
Expand Down Expand Up @@ -256,11 +259,11 @@ export class GearPlanSheet {
get materiaAutoFillController(): MateriaAutoFillController {
const outer = this;
return {
get autoFillNewItem() {
return outer.materiaAutoFillSelectedItems;
get autoFillMode() {
return outer.materiaFillMode;
},
set autoFillNewItem(enabled: boolean) {
outer.materiaAutoFillSelectedItems = enabled;
set autoFillMode(mode: MateriaFillMode) {
outer.materiaFillMode = mode;
outer.requestSave();
},
get prio() {
Expand All @@ -274,6 +277,9 @@ export class GearPlanSheet {
},
fillEmpty(): void {
console.log('fillEmpty not implemented');
},
refreshOnly(): void {
console.log('nothing to refresh');
}

}
Expand Down Expand Up @@ -427,7 +433,7 @@ export class GearPlanSheet {
race: this._race,
sims: simsExport,
itemDisplaySettings: this._itemDisplaySettings,
mfni: this.materiaAutoFillSelectedItems,
mfm: this.materiaFillMode,
mfp: this.materiaAutoFillPrio.statPrio,
mfMinGcd: this.materiaAutoFillPrio.minGcd,
ilvlSync: this.ilvlSync,
Expand Down Expand Up @@ -521,6 +527,9 @@ export class GearPlanSheet {
if (set.relicStatMemory) {
out.relicStatMemory = set.relicStatMemory.export();
}
if (set.materiaMemory) {
out.materiaMemory = set.materiaMemory.export();
}
}
return out;
}
Expand Down Expand Up @@ -646,6 +655,9 @@ export class GearPlanSheet {
if (importedSet.relicStatMemory) {
set.relicStatMemory.import(importedSet.relicStatMemory);
}
if (importedSet.materiaMemory) {
set.materiaMemory.import(importedSet.materiaMemory);
}
}
return set;
}
Expand Down Expand Up @@ -791,19 +803,18 @@ export class GearPlanSheet {
return [...this._dmRelevantFood.filter(item => item.ilvl >= this._itemDisplaySettings.minILvlFood && item.ilvl <= this._itemDisplaySettings.maxILvlFood), ...this._customFoods];
}

getBestMateria(stat: MateriaSubstat, meldSlot: MeldableMateriaSlot) {
const highGradeAllowed = meldSlot.materiaSlot.allowsHighGrade;
const maxGradeAllowed = meldSlot.materiaSlot.maxGrade;
getBestMateria(stat: MateriaSubstat, meldSlot: MeldableMateriaSlot): Materia | undefined {
const materiaFilter = (materia: Materia) => {
if (materia.isHighGrade && !highGradeAllowed) {
return false;
}
return materia.materiaGrade <= maxGradeAllowed && materia.primaryStat == stat;
return isMateriaAllowed(materia, meldSlot.materiaSlot) && materia.primaryStat == stat;
};
const sortedOptions = this.relevantMateria.filter(materiaFilter).sort((first, second) => second.primaryStatValue - first.primaryStatValue);
return sortedOptions.length >= 1 ? sortedOptions[0] : undefined;
}

getMateriaById(materiaId: number): Materia | undefined {
return this.dataManager.materiaById(materiaId);
}

addDefaultSims() {
const sims = getDefaultSims(this.classJobName, this.level);
for (const simSpec of sims) {
Expand Down
Loading

0 comments on commit a5a0bb4

Please sign in to comment.