Honor RoM off-hand and ranged equipment slots

This commit is contained in:
phenom committed 2026-09-23 19:59:21 -04:00
1 parent 0206fda9e2
commit 7449abdad2
15 files changed
+150 -29

No files matched your search

+26 -3
View File
@@ -44,6 +44,11 @@ export interface EquipmentChange {
readonly reason?: string;
}
export interface EquipmentClassContext {
readonly secondaryClassId?: ClassId | null;
readonly level?: number;
}
const equipmentSlotSet = new Set<string>(EQUIPMENT_SLOTS);
const ARMOR_RANK: Readonly<Record<string, number>> = Object.freeze({
@@ -62,6 +67,7 @@ const SHIELD_CLASSES = new Set<ClassId>([
const WEAPON_CLASSES: Readonly<Record<string, ReadonlySet<BaseClassId>>> = Object.freeze({
bow: new Set<BaseClassId>(["warrior", "hunter", "rogue"]),
crossbow: new Set<BaseClassId>(["warrior", "hunter", "rogue"]),
dagger: new Set<BaseClassId>(["warrior", "hunter", "rogue", "priest", "shaman", "mage", "warlock", "druid"]),
mace: new Set<BaseClassId>(["warrior", "paladin", "priest", "death-knight", "shaman", "druid"]),
staff: new Set<BaseClassId>(["warrior", "hunter", "priest", "shaman", "mage", "warlock", "druid"]),
@@ -75,7 +81,23 @@ export function equipmentSlotForItem(item: InventoryItem): EquipmentSlot | null
return equipmentSlotSet.has(slot) ? slot as EquipmentSlot : null;
}
export function classCanEquipItem(classId: ClassId, item: InventoryItem): boolean {
export function classCanEquipItem(classId: ClassId, item: InventoryItem, context: EquipmentClassContext = {}): boolean {
const slot = equipmentSlotForItem(item);
if (!slot) return false;
const level = context.level ?? Number.POSITIVE_INFINITY;
if (classId.startsWith("rom-")) {
if (slot === "off-hand" && item.weaponType
&& level < 16) return false;
if (slot === "off-hand" && item.weaponType
&& classId !== "rom-rogue" && context.secondaryClassId !== "rom-rogue") return false;
if (slot === "ranged") {
if (item.weaponType === "bow" || item.weaponType === "crossbow") return classId === "rom-scout"
|| (context.secondaryClassId === "rom-scout" && level >= 10);
if (item.weaponType === "projectile" || item.weaponType === "throwing") return (classId === "rom-rogue"
|| context.secondaryClassId === "rom-rogue") && level >= 12;
return false;
}
}
if (item.armorType === "shield" && !SHIELD_CLASSES.has(classId)) return false;
if (item.armorType && item.armorType !== "shield") {
const requiredRank = ARMOR_RANK[item.armorType] ?? Number.POSITIVE_INFINITY;
@@ -85,7 +107,7 @@ export function classCanEquipItem(classId: ClassId, item: InventoryItem): boolea
const allowedClasses = WEAPON_CLASSES[item.weaponType];
if (!allowedClasses?.has(baseClassFor(classId))) return false;
}
return equipmentSlotForItem(item) !== null;
return true;
}
function mutableAssignments(assignments: EquipmentAssignments): Record<string, EquipmentLoadout> {
@@ -114,12 +136,13 @@ export function equipInventoryItem(
ownerId: string,
ownerClassId: ClassId,
instanceId: string,
classContext: EquipmentClassContext = {},
): EquipmentChange {
const item = inventory.find((candidate) => candidate.instanceId === instanceId);
if (!item) return { ok: false, assignments, reason: "That item is no longer in your inventory." };
const slot = equipmentSlotForItem(item);
if (!slot) return { ok: false, assignments, reason: `${item.name} does not use an equippable slot.` };
if (!classCanEquipItem(ownerClassId, item)) {
if (!classCanEquipItem(ownerClassId, item, classContext)) {
return { ok: false, assignments, reason: `That class cannot equip ${item.name}.` };
}