Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions assets/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -815,6 +815,15 @@
"freeze_trinket": {
"label": "Freeze trinket slot",
"tooltip": "Freeze one equipped trinket to reduce combination counts"
},
"freeze_weapon": {
"label": "Freeze weapon slot",
"tooltip": "Freeze the equipped main-hand or off-hand weapon to reduce combination counts"
},
"freeze_weapon_types": {
"mainhand_label": "Freeze MH weapon type",
"offhand_label": "Freeze OH weapon type",
"tooltip": "Leave all unchecked to allow any weapon type for this slot."
}
},
"progress": {
Expand Down
9 changes: 9 additions & 0 deletions assets/locales/fr/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -815,6 +815,15 @@
"freeze_trinket": {
"label": "Geler un emplacement de bijou",
"tooltip": "Geler un bijou équipé pour réduire le nombre de combinaisons"
},
"freeze_weapon": {
"label": "Geler un emplacement d'arme",
"tooltip": "Geler l'arme équipée en main principale ou secondaire pour réduire le nombre de combinaisons"
},
"freeze_weapon_types": {
"mainhand_label": "Geler le type d'arme MH",
"offhand_label": "Geler le type d'arme OH",
"tooltip": "Laissez tout décoché pour autoriser n'importe quel type d'arme pour cet emplacement."
}
},
"progress": {
Expand Down
5 changes: 5 additions & 0 deletions proto/api.proto
Original file line number Diff line number Diff line change
Expand Up @@ -595,4 +595,9 @@ message BulkSettings {
int32 default_prismatic_gem = 7;

bool inherit_upgrades = 8;
int32 freeze_ring_slot = 9;
int32 freeze_trinket_slot = 10;
int32 freeze_weapon_slot = 11;
repeated WeaponType freeze_mainhand_weapon_slots = 12;
repeated WeaponType freeze_offhand_weapon_slots = 13;
}
33 changes: 32 additions & 1 deletion schemas/translation.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -2886,6 +2886,35 @@
},
"additionalProperties": false,
"required": ["label", "tooltip"]
},
"freeze_weapon": {
"type": "object",
"properties": {
"label": {
"type": "string"
},
"tooltip": {
"type": "string"
}
},
"additionalProperties": false,
"required": ["label", "tooltip"]
},
"freeze_weapon_types": {
"type": "object",
"properties": {
"mainhand_label": {
"type": "string"
},
"offhand_label": {
"type": "string"
},
"tooltip": {
"type": "string"
}
},
"additionalProperties": false,
"required": ["mainhand_label", "offhand_label", "tooltip"]
}
},
"additionalProperties": false,
Expand All @@ -2896,7 +2925,9 @@
"fallback_gems",
"inherit_upgrades",
"freeze_ring",
"freeze_trinket"
"freeze_trinket",
"freeze_weapon",
"freeze_weapon_types"
]
},
"progress": {
Expand Down
80 changes: 66 additions & 14 deletions ui/core/components/individual_sim_ui/bulk/bulk_item_picker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ import { ItemRenderer } from '../../gear_picker/gear_picker';
import { GearData } from '../../gear_picker/item_list';
import { SelectorModalTabs } from '../../gear_picker/selector_modal';
import { BulkTab } from '../bulk_tab';
import { BulkSimItemSlot } from './utils';
import { BulkSimItemSlot, bulkSimItemSlotToItemSlotPairs } from './utils';

export default class BulkItemPicker extends Component {
private readonly itemElem: ItemRenderer;
private removeBtn: HTMLButtonElement | null = null;
readonly simUI: IndividualSimUI<any>;
readonly bulkUI: BulkTab;
readonly bulkSlot: BulkSimItemSlot;
Expand All @@ -40,7 +41,7 @@ export default class BulkItemPicker extends Component {
this.abortController = new AbortController();
this.signal = this.abortController.signal;

if (!this.isEditable()) {
if (!this.indexIsEditable()) {
this.rootElem.classList.add('bulk-item-picker-equipped');
parent.insertAdjacentElement('afterbegin', this.rootElem);
}
Expand All @@ -51,18 +52,18 @@ export default class BulkItemPicker extends Component {

this.addOnDisposeCallback(() => this.rootElem.remove());

const updateBorder = () => {
if (this.bulkUI.frozenItems.get(this.bulkSlot)?.equals(this.item)) {
this.rootElem.classList.remove('bulk-item-picker-equipped');
this.rootElem.classList.add('bulk-item-picker-frozen');
} else {
this.rootElem.classList.remove('bulk-item-picker-frozen');
this.rootElem.classList.add('bulk-item-picker-equipped');
}
const updatePickerState = () => {
const isFrozen = this.isFrozen();
const isEditable = this.isEditable();

this.rootElem.classList.toggle('bulk-item-picker-frozen', isFrozen);
this.rootElem.classList.toggle('bulk-item-picker-equipped', !isFrozen && !isEditable);
this.removeBtn?.classList.toggle('hide', !isEditable);
};

updateBorder();
TypedEvent.onAny([this.bulkUI.settingsChangedEmitter, this.bulkUI.itemsChangedEmitter]).on(() => updateBorder());
updatePickerState();
const events = TypedEvent.onAny([this.bulkUI.settingsChangedEmitter, this.bulkUI.itemsChangedEmitter]).on(() => updatePickerState());
this.addOnDisposeCallback(() => events.dispose());
}

setItem(newItem: EquippedItem) {
Expand All @@ -72,10 +73,59 @@ export default class BulkItemPicker extends Component {
this.setupHandlers();
}

private isEditable(): boolean {
private indexIsEditable(): boolean {
return this.index >= 0;
}

private isCurrentlyEquipped(): boolean {
if (this.bulkSlot === BulkSimItemSlot.ItemSlotHandWeapon) {
return false;
}

return this.simUI.player.getEquippedItems().some(equippedItem => equippedItem?.id === this.item.id);
}

private isEditable(): boolean {
return this.indexIsEditable() && !this.isCurrentlyEquipped();
}

private getEquippedSlot(): ItemSlot | null {
if (this.indexIsEditable()) {
return null;
}

const slots = bulkSimItemSlotToItemSlotPairs.get(this.bulkSlot);
if (!slots) {
return null;
}

return this.index === -1 ? slots[0] : slots[1];
}

private getFrozenBulkItemSlot(): ItemSlot | null {
const frozenItem = this.bulkUI.frozenItems.get(this.bulkSlot);
const slots = bulkSimItemSlotToItemSlotPairs.get(this.bulkSlot);
if (!frozenItem || !slots) {
return null;
}

const gear = this.simUI.player.getGear();
return slots.find(slot => gear.getEquippedItem(slot) === frozenItem) ?? slots.find(slot => gear.getEquippedItem(slot)?.equals(frozenItem)) ?? null;
}

private isFrozen(): boolean {
const equippedSlot = this.getEquippedSlot();
if (!equippedSlot) {
return false;
}

if (equippedSlot === this.bulkUI.frozenWeaponSlot) {
return this.simUI.player.getGear().getEquippedItem(equippedSlot)?.equals(this.item) ?? false;
}

return equippedSlot === this.getFrozenBulkItemSlot();
}

private setupHandlers() {
const slot = getEligibleItemSlots(this.item.item)[0];
const hasEligibleEnchants = !!this.simUI.sim.db.getEnchants(slot).length;
Expand Down Expand Up @@ -143,7 +193,7 @@ export default class BulkItemPicker extends Component {

this.itemElem.rootElem.appendChild(
<div className="item-picker-actions-container">
{this.isEditable() && (
{this.indexIsEditable() && (
<button className="btn btn-link link-danger item-picker-actions-btn" ref={removeBtnRef}>
<i className="fas fa-times" />
</button>
Expand All @@ -153,6 +203,8 @@ export default class BulkItemPicker extends Component {

if (removeBtnRef.value) {
const removeBtn = removeBtnRef.value;
this.removeBtn = removeBtn;
this.removeBtn.classList.toggle('hide', !this.isEditable());
tippy(removeBtn, { content: i18n.t('bulk_tab.picker.remove_tooltip') });
const removeItem = () => this.bulkUI.removeItemByIndex(this.index);
removeBtn.addEventListener('click', removeItem);
Expand Down
51 changes: 19 additions & 32 deletions ui/core/components/individual_sim_ui/bulk/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { ItemSlot } from '../../../proto/common';
import { getEnumValues } from '../../../utils';

// Combines Fingers 1 and 2 and Trinket 1 and 2 into single groups
export enum BulkSimItemSlot {
Expand All @@ -20,18 +19,6 @@ export enum BulkSimItemSlot {
ItemSlotHandWeapon, // Weapon grouping slot for specs that can dual-wield
}

// Return all eligible bulk item slots.
// If the player can dual-wield, exclude main-hand/off-hand in favor of the grouped weapons slot
// Otherwise include main-hand/off-hand instead of the grouped weapons slot
export const getBulkItemSlots = (canDualWield: boolean) => {
const allSlots = getEnumValues<BulkSimItemSlot>(BulkSimItemSlot);
if (canDualWield) {
return allSlots.filter(bulkSlot => ![BulkSimItemSlot.ItemSlotMainHand, BulkSimItemSlot.ItemSlotOffHand].includes(bulkSlot));
} else {
return allSlots.filter(bulkSlot => bulkSlot !== BulkSimItemSlot.ItemSlotHandWeapon);
}
};

export const itemSlotToBulkSimItemSlot: Map<ItemSlot, BulkSimItemSlot> = new Map([
[ItemSlot.ItemSlotHead, BulkSimItemSlot.ItemSlotHead],
[ItemSlot.ItemSlotNeck, BulkSimItemSlot.ItemSlotNeck],
Expand All @@ -52,7 +39,7 @@ export const itemSlotToBulkSimItemSlot: Map<ItemSlot, BulkSimItemSlot> = new Map
]);

export const bulkSimItemSlotToSingleItemSlot: Map<BulkSimItemSlot, ItemSlot> = new Map([
[BulkSimItemSlot.ItemSlotHead, ItemSlot.ItemSlotHead,],
[BulkSimItemSlot.ItemSlotHead, ItemSlot.ItemSlotHead],
[BulkSimItemSlot.ItemSlotNeck, ItemSlot.ItemSlotNeck],
[BulkSimItemSlot.ItemSlotShoulder, ItemSlot.ItemSlotShoulder],
[BulkSimItemSlot.ItemSlotBack, ItemSlot.ItemSlotBack],
Expand All @@ -67,9 +54,9 @@ export const bulkSimItemSlotToSingleItemSlot: Map<BulkSimItemSlot, ItemSlot> = n
]);

export const bulkSimItemSlotToItemSlotPairs: Map<BulkSimItemSlot, [ItemSlot, ItemSlot]> = new Map([
[BulkSimItemSlot.ItemSlotFinger, [ItemSlot.ItemSlotFinger1, ItemSlot.ItemSlotFinger2]],
[BulkSimItemSlot.ItemSlotTrinket, [ItemSlot.ItemSlotTrinket1, ItemSlot.ItemSlotTrinket2]],
[BulkSimItemSlot.ItemSlotHandWeapon, [ItemSlot.ItemSlotMainHand, ItemSlot.ItemSlotOffHand]],
[BulkSimItemSlot.ItemSlotFinger, [ItemSlot.ItemSlotFinger2, ItemSlot.ItemSlotFinger1]],
[BulkSimItemSlot.ItemSlotTrinket, [ItemSlot.ItemSlotTrinket2, ItemSlot.ItemSlotTrinket1]],
[BulkSimItemSlot.ItemSlotHandWeapon, [ItemSlot.ItemSlotOffHand, ItemSlot.ItemSlotMainHand]],
]);

export const getBulkItemSlotFromSlot = (slot: ItemSlot, canDualWield: boolean): BulkSimItemSlot => {
Expand All @@ -80,22 +67,22 @@ export const getBulkItemSlotFromSlot = (slot: ItemSlot, canDualWield: boolean):
};

export const binomialCoefficient = (n: number, k: number): number => {
if (Number.isNaN(n) || Number.isNaN(k)) return NaN;
if (k < 0 || k > n) return 0;
if (k === 0 || k === n) return 1;
if (k === 1 || k === n - 1) return n;
if (n - k < k) k = n - k;
let res = n;
for (let j = 2; j <= k; j++) res *= (n - j + 1) / j;
return Math.round(res);
if (Number.isNaN(n) || Number.isNaN(k)) return NaN;
if (k < 0 || k > n) return 0;
if (k === 0 || k === n) return 1;
if (k === 1 || k === n - 1) return n;
if (n - k < k) k = n - k;
let res = n;
for (let j = 2; j <= k; j++) res *= (n - j + 1) / j;
return Math.round(res);
};

export function getAllPairs<T>(arr: T[]): [T, T][] {
const pairs: [T, T][] = [];
for (let i = 0; i < arr.length; i++) {
for (let j = i + 1; j < arr.length; j++) {
pairs.push([arr[i], arr[j]]);
}
}
return pairs;
const pairs: [T, T][] = [];
for (let i = 0; i < arr.length; i++) {
for (let j = i + 1; j < arr.length; j++) {
pairs.push([arr[i], arr[j]]);
}
}
return pairs;
}
Loading
Loading