@@ -0,0 +1,277 @@
|
||||
import { NPC_RELATIONS } from './data/npcTables.js';
|
||||
import {
|
||||
RELATION_FORMULAS,
|
||||
AFFINITY_INIMITY_MAP,
|
||||
POWER_INFLUENCE_MAP,
|
||||
AFFINITY_LABELS,
|
||||
INIMITY_LABELS,
|
||||
POWER_LABELS,
|
||||
INFLUENCE_LABELS,
|
||||
SPECIAL_CHARACTERISTICS_TABLE,
|
||||
} from './data/allyEnemyTables.js';
|
||||
|
||||
export function mapRollToValue(roll, mapping) {
|
||||
return mapping[roll] ?? 0;
|
||||
}
|
||||
|
||||
export function getLabel(value, labels) {
|
||||
return labels.find(l => l.value === Math.abs(value)) ?? labels[0];
|
||||
}
|
||||
|
||||
export function clamp(value, min, max) {
|
||||
return Math.max(min, Math.min(max, value));
|
||||
}
|
||||
|
||||
async function rollFormula(formula) {
|
||||
const roll = await new Roll(formula).evaluate();
|
||||
return { formula, total: roll.total };
|
||||
}
|
||||
|
||||
function getD66Entry(entries, total) {
|
||||
return entries.find(e => e.d66 === total) ?? null;
|
||||
}
|
||||
|
||||
async function rollD66(entries) {
|
||||
const tens = await rollFormula('1d6');
|
||||
const ones = await rollFormula('1d6');
|
||||
const total = (tens.total * 10) + ones.total;
|
||||
return {
|
||||
total,
|
||||
tens: tens.total,
|
||||
ones: ones.total,
|
||||
entry: getD66Entry(entries, total),
|
||||
};
|
||||
}
|
||||
|
||||
async function rollAffinityInimity(relationKey) {
|
||||
const formulas = RELATION_FORMULAS[relationKey];
|
||||
let affinityRoll = null;
|
||||
let inimityRoll = null;
|
||||
|
||||
if (formulas.affinity !== '0') {
|
||||
affinityRoll = await rollFormula(formulas.affinity);
|
||||
}
|
||||
if (formulas.inimity !== '0') {
|
||||
inimityRoll = await rollFormula(formulas.inimity);
|
||||
}
|
||||
|
||||
return {
|
||||
affinityValue: affinityRoll ? mapRollToValue(affinityRoll.total, AFFINITY_INIMITY_MAP) : 0,
|
||||
inimityValue: inimityRoll ? mapRollToValue(inimityRoll.total, AFFINITY_INIMITY_MAP) : 0,
|
||||
affinityRoll,
|
||||
inimityRoll,
|
||||
formulas,
|
||||
};
|
||||
}
|
||||
|
||||
async function resolveSpecialCharacteristics(currentRelationKey, depth = 0) {
|
||||
if (depth > 5) return [];
|
||||
|
||||
const d66Result = await rollD66(SPECIAL_CHARACTERISTICS_TABLE);
|
||||
if (!d66Result.entry) return [];
|
||||
|
||||
const entry = d66Result.entry;
|
||||
const result = {
|
||||
d66: d66Result.total,
|
||||
text: entry.text,
|
||||
effects: entry.effects,
|
||||
appliedDeltas: { affinity: 0, inimity: 0, power: 0, influence: 0 },
|
||||
rerollNote: null,
|
||||
swapNote: null,
|
||||
narrativeText: entry.effects.action === 'narrativeOnly' ? entry.text : null,
|
||||
newRelationKey: null,
|
||||
subCharacteristics: [],
|
||||
};
|
||||
|
||||
if (entry.effects.affinityMod) result.appliedDeltas.affinity = entry.effects.affinityMod;
|
||||
if (entry.effects.inimityMod) result.appliedDeltas.inimity = entry.effects.inimityMod;
|
||||
if (entry.effects.powerMod) result.appliedDeltas.power = entry.effects.powerMod;
|
||||
if (entry.effects.influenceMod) result.appliedDeltas.influence = entry.effects.influenceMod;
|
||||
|
||||
if (entry.effects.action === 'extraRolls') {
|
||||
const count = entry.effects.actionValue || 1;
|
||||
for (let i = 0; i < count; i++) {
|
||||
const extra = await resolveSpecialCharacteristics(currentRelationKey, depth + 1);
|
||||
result.subCharacteristics.push(...extra);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function generateAllyEnemy(relationKey = 'contact', options = {}) {
|
||||
const relation = NPC_RELATIONS[relationKey];
|
||||
let currentRelationKey = relationKey;
|
||||
|
||||
const initial = await rollAffinityInimity(relationKey);
|
||||
let affinityValue = initial.affinityValue;
|
||||
let inimityValue = initial.inimityValue;
|
||||
let affinityRoll = initial.affinityRoll;
|
||||
let inimityRoll = initial.inimityRoll;
|
||||
let currentFormulas = initial.formulas;
|
||||
|
||||
const powerRoll = await rollFormula('2d6');
|
||||
const influenceRoll = await rollFormula('2d6');
|
||||
let powerValue = mapRollToValue(powerRoll.total, POWER_INFLUENCE_MAP);
|
||||
let influenceValue = mapRollToValue(influenceRoll.total, POWER_INFLUENCE_MAP);
|
||||
|
||||
let specialRoll = null;
|
||||
let specialCharacteristics = [];
|
||||
|
||||
if (options.includeSpecial !== false) {
|
||||
specialRoll = await rollFormula('2d6');
|
||||
|
||||
if (specialRoll.total >= 8) {
|
||||
let queue = await resolveSpecialCharacteristics(currentRelationKey);
|
||||
|
||||
while (queue.length > 0) {
|
||||
const sc = queue.shift();
|
||||
|
||||
if (sc.effects.action === 'extraRolls') {
|
||||
queue.push(...sc.subCharacteristics);
|
||||
}
|
||||
|
||||
if (sc.effects.action === 'moderateRelation') {
|
||||
if (currentRelationKey === 'enemy') {
|
||||
currentRelationKey = 'rival';
|
||||
const rerolled = await rollAffinityInimity(currentRelationKey);
|
||||
affinityValue = rerolled.affinityValue;
|
||||
inimityValue = rerolled.inimityValue;
|
||||
affinityRoll = rerolled.affinityRoll;
|
||||
inimityRoll = rerolled.inimityRoll;
|
||||
currentFormulas = rerolled.formulas;
|
||||
sc.newRelationKey = currentRelationKey;
|
||||
} else if (currentRelationKey === 'ally') {
|
||||
currentRelationKey = 'contact';
|
||||
const rerolled = await rollAffinityInimity(currentRelationKey);
|
||||
affinityValue = rerolled.affinityValue;
|
||||
inimityValue = rerolled.inimityValue;
|
||||
affinityRoll = rerolled.affinityRoll;
|
||||
inimityRoll = rerolled.inimityRoll;
|
||||
currentFormulas = rerolled.formulas;
|
||||
sc.newRelationKey = currentRelationKey;
|
||||
}
|
||||
}
|
||||
|
||||
if (sc.effects.action === 'intensifyRelation') {
|
||||
if (currentRelationKey === 'rival') {
|
||||
currentRelationKey = 'enemy';
|
||||
const rerolled = await rollAffinityInimity(currentRelationKey);
|
||||
affinityValue = rerolled.affinityValue;
|
||||
inimityValue = rerolled.inimityValue;
|
||||
affinityRoll = rerolled.affinityRoll;
|
||||
inimityRoll = rerolled.inimityRoll;
|
||||
currentFormulas = rerolled.formulas;
|
||||
sc.newRelationKey = currentRelationKey;
|
||||
} else if (currentRelationKey === 'contact') {
|
||||
currentRelationKey = 'ally';
|
||||
const rerolled = await rollAffinityInimity(currentRelationKey);
|
||||
affinityValue = rerolled.affinityValue;
|
||||
inimityValue = rerolled.inimityValue;
|
||||
affinityRoll = rerolled.affinityRoll;
|
||||
inimityRoll = rerolled.inimityRoll;
|
||||
currentFormulas = rerolled.formulas;
|
||||
sc.newRelationKey = currentRelationKey;
|
||||
}
|
||||
}
|
||||
|
||||
if (sc.effects.action === 'reRollAffinity') {
|
||||
const reroll = await rollFormula('2d6');
|
||||
const rerolledValue = mapRollToValue(reroll.total, AFFINITY_INIMITY_MAP);
|
||||
if (rerolledValue > affinityValue) {
|
||||
sc.rerollNote = `Affinité relancée : ${reroll.total} → ${rerolledValue} (était ${affinityValue})`;
|
||||
sc.appliedDeltas.affinity = rerolledValue - affinityValue;
|
||||
}
|
||||
}
|
||||
|
||||
if (sc.effects.action === 'reRollInimity') {
|
||||
const reroll = await rollFormula('2d6');
|
||||
const rerolledValue = mapRollToValue(reroll.total, AFFINITY_INIMITY_MAP);
|
||||
if (rerolledValue > inimityValue) {
|
||||
sc.rerollNote = `Inimitié relancée : ${reroll.total} → ${rerolledValue} (était ${inimityValue})`;
|
||||
sc.appliedDeltas.inimity = rerolledValue - inimityValue;
|
||||
}
|
||||
}
|
||||
|
||||
if (sc.effects.action === 'swapAffinityInimity') {
|
||||
sc.swapNote = 'Affinité et Inimitié échangées';
|
||||
const tmpAff = affinityValue;
|
||||
const tmpInim = inimityValue;
|
||||
sc.appliedDeltas.affinity = tmpInim - affinityValue;
|
||||
sc.appliedDeltas.inimity = tmpAff - inimityValue;
|
||||
}
|
||||
|
||||
if (sc.effects.action === 'setPowerToZero') {
|
||||
sc.appliedDeltas.power = -powerValue;
|
||||
}
|
||||
|
||||
if (sc.effects.action === 'createEnemy') {
|
||||
sc.narrativeText = 'Un nouvel Ennemi commun au Voyageur et à cet individu est créé.';
|
||||
}
|
||||
|
||||
if (sc.effects.action === 'createContactOrRival') {
|
||||
const net = affinityValue - inimityValue;
|
||||
sc.narrativeText = net > 0
|
||||
? 'Un nouveau Contact est créé (Affinité supérieure à l\'Inimitié).'
|
||||
: 'Un nouveau Rival est créé (Inimitié supérieure à l\'Affinité).';
|
||||
}
|
||||
|
||||
let newAffinity = affinityValue + (sc.appliedDeltas.affinity || 0);
|
||||
let newInimity = inimityValue + (sc.appliedDeltas.inimity || 0);
|
||||
let newPower = powerValue + (sc.appliedDeltas.power || 0);
|
||||
let newInfluence = influenceValue + (sc.appliedDeltas.influence || 0);
|
||||
|
||||
affinityValue = clamp(newAffinity, 0, 6);
|
||||
inimityValue = clamp(newInimity, 0, 6);
|
||||
powerValue = clamp(newPower, 0, 6);
|
||||
influenceValue = clamp(newInfluence, 0, 6);
|
||||
|
||||
specialCharacteristics.push(sc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const finalRelation = currentRelationKey !== relationKey
|
||||
? NPC_RELATIONS[currentRelationKey]
|
||||
: relation;
|
||||
|
||||
const netScore = affinityValue - inimityValue;
|
||||
|
||||
return {
|
||||
success: true,
|
||||
type: 'ally-enemy',
|
||||
relation: { key: currentRelationKey, label: finalRelation.label, summary: finalRelation.summary },
|
||||
originalRelationKey: relationKey,
|
||||
relationChanged: currentRelationKey !== relationKey,
|
||||
|
||||
affinity: {
|
||||
formula: currentFormulas.affinity,
|
||||
roll: affinityRoll?.total ?? 0,
|
||||
value: affinityValue,
|
||||
label: getLabel(affinityValue, AFFINITY_LABELS).label,
|
||||
description: getLabel(affinityValue, AFFINITY_LABELS).description,
|
||||
},
|
||||
inimity: {
|
||||
formula: currentFormulas.inimity,
|
||||
roll: inimityRoll?.total ?? 0,
|
||||
value: inimityValue,
|
||||
label: getLabel(inimityValue, INIMITY_LABELS).label,
|
||||
description: getLabel(inimityValue, INIMITY_LABELS).description,
|
||||
},
|
||||
netScore,
|
||||
|
||||
power: {
|
||||
value: powerValue,
|
||||
label: getLabel(powerValue, POWER_LABELS).label,
|
||||
description: getLabel(powerValue, POWER_LABELS).description,
|
||||
},
|
||||
influence: {
|
||||
value: influenceValue,
|
||||
label: getLabel(influenceValue, INFLUENCE_LABELS).label,
|
||||
description: getLabel(influenceValue, INFLUENCE_LABELS).description,
|
||||
},
|
||||
|
||||
specialRoll: specialRoll ? { roll: specialRoll.total, triggered: specialRoll.total >= 8 } : null,
|
||||
specialCharacteristics,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user