feat: Enforce mutual exclusivity for bond feelings
Reorder feelings so opposing pairs (Admiration/Inferiority, Loyalty/Mistrust, Hatred/Affection) are stacked vertically in the grid. Checking one feeling disables and prevents selecting its opposite. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -82,26 +82,26 @@ weapons:
|
|||||||
accuracy: "【MIG + MIG】"
|
accuracy: "【MIG + MIG】"
|
||||||
damage: "【HR + 10】 physical"
|
damage: "【HR + 10】 physical"
|
||||||
category: "heavy"
|
category: "heavy"
|
||||||
description: One-handed w Melee w No Quality.
|
description: One-handed, Melee, No Quality.
|
||||||
|
|
||||||
- name: Waraxe ✦
|
- name: Waraxe ✦
|
||||||
cost: 250
|
cost: 250
|
||||||
accuracy: "【MIG + MIG】"
|
accuracy: "【MIG + MIG】"
|
||||||
damage: "【HR + 14】 physical"
|
damage: "【HR + 14】 physical"
|
||||||
category: "heavy"
|
category: "heavy"
|
||||||
description: Two-handed w Melee w No Quality.
|
description: Two-handed, Melee, No Quality.
|
||||||
|
|
||||||
- name: Light Spear ✦
|
- name: Light Spear ✦
|
||||||
cost: 200
|
cost: 200
|
||||||
accuracy: "【DEX + MIG】"
|
accuracy: "【DEX + MIG】"
|
||||||
damage: "【HR + 8】 physical"
|
damage: "【HR + 8】 physical"
|
||||||
category: "spear"
|
category: "spear"
|
||||||
description: One-handed w Melee w No Quality.
|
description: One-handed, Melee, No Quality.
|
||||||
|
|
||||||
- name: Heavy Spear ✦
|
- name: Heavy Spear ✦
|
||||||
cost: 200
|
cost: 200
|
||||||
accuracy: "【DEX + MIG】 【HR + 12】 physical"
|
accuracy: "【DEX + MIG】 【HR + 12】 physical"
|
||||||
damage: "Two-handed w Melee w No Quality."
|
damage: "Two-handed, Melee, No Quality."
|
||||||
category: "spear"
|
category: "spear"
|
||||||
description:
|
description:
|
||||||
|
|
||||||
@@ -110,40 +110,40 @@ weapons:
|
|||||||
accuracy: "【DEX + MIG】 +1"
|
accuracy: "【DEX + MIG】 +1"
|
||||||
damage: "【HR + 6】 physical"
|
damage: "【HR + 6】 physical"
|
||||||
category: "sword"
|
category: "sword"
|
||||||
description: One-handed w Melee w No Quality.
|
description: One-handed, Melee, No Quality.
|
||||||
|
|
||||||
- name: Greatsword ✦
|
- name: Greatsword ✦
|
||||||
cost: 200
|
cost: 200
|
||||||
accuracy: "【DEX + MIG】 +1"
|
accuracy: "【DEX + MIG】 +1"
|
||||||
damage: "【HR + 10】 physical"
|
damage: "【HR + 10】 physical"
|
||||||
category: "sword"
|
category: "sword"
|
||||||
description: Two-handed w Melee w No Quality.
|
description: Two-handed, Melee, No Quality.
|
||||||
|
|
||||||
- name: Katana ✦
|
- name: Katana ✦
|
||||||
cost: 200
|
cost: 200
|
||||||
accuracy: "【DEX + INS】 +1"
|
accuracy: "【DEX + INS】 +1"
|
||||||
damage: "【HR + 10】 physical"
|
damage: "【HR + 10】 physical"
|
||||||
category: "sword"
|
category: "sword"
|
||||||
description: Two-handed w Melee w No Quality.
|
description: Two-handed, Melee, No Quality.
|
||||||
|
|
||||||
- name: Rapier ✦
|
- name: Rapier ✦
|
||||||
cost: 200
|
cost: 200
|
||||||
accuracy: "【DEX + INS】 +1"
|
accuracy: "【DEX + INS】 +1"
|
||||||
damage: "【HR + 6】 physical"
|
damage: "【HR + 6】 physical"
|
||||||
category: "sword"
|
category: "sword"
|
||||||
description: One-handed w Melee w No Quality.
|
description: One-handed, Melee, No Quality.
|
||||||
|
|
||||||
- name: Improvised (Ranged)
|
- name: Improvised (Ranged)
|
||||||
cost: 0
|
cost: 0
|
||||||
accuracy: "【DEX + MIG】"
|
accuracy: "【DEX + MIG】"
|
||||||
damage: "【HR + 2】 physical"
|
damage: "【HR + 2】 physical"
|
||||||
category: "thrown"
|
category: "thrown"
|
||||||
description: One-handed w Ranged w Breaks after the attack.
|
description: One-handed, Ranged, Breaks after the attack.
|
||||||
|
|
||||||
- name: Shuriken
|
- name: Shuriken
|
||||||
cost: 150
|
cost: 150
|
||||||
accuracy: "【DEX + INS】"
|
accuracy: "【DEX + INS】"
|
||||||
damage: "【HR + 4】 physical"
|
damage: "【HR + 4】 physical"
|
||||||
category: "thrown"
|
category: "thrown"
|
||||||
description: One-handed w Ranged w No Quality.
|
description: One-handed, Ranged, No Quality.
|
||||||
|
|
||||||
|
|||||||
@@ -6,12 +6,20 @@ import weaponsFile from "../data/weapons.yml";
|
|||||||
const STATUSES = ["Slow", "Enraged", "Dazed", "Weak", "Poisoned", "Shaken"];
|
const STATUSES = ["Slow", "Enraged", "Dazed", "Weak", "Poisoned", "Shaken"];
|
||||||
const FEELINGS = [
|
const FEELINGS = [
|
||||||
"Admiration",
|
"Admiration",
|
||||||
"Inferiority",
|
|
||||||
"Loyalty",
|
"Loyalty",
|
||||||
|
"Hatred",
|
||||||
|
"Inferiority",
|
||||||
"Mistrust",
|
"Mistrust",
|
||||||
"Affection",
|
"Affection",
|
||||||
"Hatred",
|
|
||||||
];
|
];
|
||||||
|
const MUTUAL_EXCLUSIVE: Record<string, string> = {
|
||||||
|
Admiration: "Inferiority",
|
||||||
|
Inferiority: "Admiration",
|
||||||
|
Loyalty: "Mistrust",
|
||||||
|
Mistrust: "Loyalty",
|
||||||
|
Hatred: "Affection",
|
||||||
|
Affection: "Hatred",
|
||||||
|
};
|
||||||
const MARTIAL_ITEMS = [
|
const MARTIAL_ITEMS = [
|
||||||
"Martial Armor",
|
"Martial Armor",
|
||||||
"Martial Shields",
|
"Martial Shields",
|
||||||
@@ -581,11 +589,13 @@ export default function CharacterSheet() {
|
|||||||
prev.map((b, i) => {
|
prev.map((b, i) => {
|
||||||
if (i !== bondIdx) return b;
|
if (i !== bondIdx) return b;
|
||||||
const has = b.feelings.includes(feeling);
|
const has = b.feelings.includes(feeling);
|
||||||
|
const partner = MUTUAL_EXCLUSIVE[feeling];
|
||||||
|
if (!has && partner && b.feelings.includes(partner)) return b;
|
||||||
return {
|
return {
|
||||||
...b,
|
...b,
|
||||||
feelings: has
|
feelings: has
|
||||||
? b.feelings.filter((f) => f !== feeling)
|
? b.feelings.filter((f) => f !== feeling)
|
||||||
: [...b.feelings, feeling],
|
: [...b.feelings.filter((f) => f !== partner), feeling],
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -1039,18 +1049,26 @@ export default function CharacterSheet() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="bond-feelings">
|
<div className="bond-feelings">
|
||||||
{FEELINGS.map((feeling) => (
|
{FEELINGS.map((feeling) => {
|
||||||
<div
|
const isActive = bond.feelings.includes(feeling);
|
||||||
key={feeling}
|
const isDisabled =
|
||||||
className={`bond-feeling${bond.feelings.includes(feeling) ? " active" : ""}`}
|
!isActive &&
|
||||||
onClick={() => toggleFeeling(idx, feeling)}
|
bond.feelings.includes(MUTUAL_EXCLUSIVE[feeling]);
|
||||||
>
|
return (
|
||||||
<div className="bond-feeling-box">
|
<div
|
||||||
{bond.feelings.includes(feeling) ? "✓" : ""}
|
key={feeling}
|
||||||
|
className={`bond-feeling${isActive ? " active" : ""}${isDisabled ? " disabled" : ""}`}
|
||||||
|
onClick={() =>
|
||||||
|
!isDisabled && toggleFeeling(idx, feeling)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className="bond-feeling-box">
|
||||||
|
{isActive ? "✓" : ""}
|
||||||
|
</div>
|
||||||
|
<span>{feeling}</span>
|
||||||
</div>
|
</div>
|
||||||
<span>{feeling}</span>
|
);
|
||||||
</div>
|
})}
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -582,6 +582,11 @@ input[type="number"] {
|
|||||||
color: var(--teal);
|
color: var(--teal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bond-feeling.disabled {
|
||||||
|
opacity: 0.3;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
.bond-feeling-box {
|
.bond-feeling-box {
|
||||||
width: 12px;
|
width: 12px;
|
||||||
height: 12px;
|
height: 12px;
|
||||||
|
|||||||
Reference in New Issue
Block a user