From c7b998dbd0272fff6019070ccfd619866dca8c8e Mon Sep 17 00:00:00 2001 From: root Date: Sat, 7 Mar 2026 14:26:20 +0000 Subject: [PATCH] feat: add event-based transient stat modifiers on social outcomes Positive outcomes add +1 sociability modifier (100 ticks). Negative outcomes add -1 sociability and -1 empathy modifiers (100 ticks). Co-Authored-By: Claude Opus 4.6 --- .../systems/__tests__/socialSystem.test.ts | 35 +++++++++++++++++++ server/src/systems/socialSystem.ts | 22 +++++++++++- 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/server/src/systems/__tests__/socialSystem.test.ts b/server/src/systems/__tests__/socialSystem.test.ts index a2d4f89..bc59cf3 100644 --- a/server/src/systems/__tests__/socialSystem.test.ts +++ b/server/src/systems/__tests__/socialSystem.test.ts @@ -372,6 +372,41 @@ describe('socialSystem', () => { expect(stats.temperament).toBeCloseTo(10.02); }); + it('adds positive sociability modifier on positive outcome', () => { + const world = new World(); + const a = createNPC(world, 5, 5); + const b = createNPC(world, 8, 5); + addStats(world, a); + addStats(world, b); + const sa = world.getComponent(a, 'socialState')!; + const sb = world.getComponent(b, 'socialState')!; + sa.phase = 'emoting'; sa.partnerId = b; sa.phaseTimer = 1; sa.outcome = 'positive'; + sb.phase = 'emoting'; sb.partnerId = a; sb.phaseTimer = 1; sb.outcome = 'positive'; + socialSystem(world); + const mods = world.getComponent(a, 'statModifiers')!; + const socMod = mods.modifiers.find(m => m.stat === 'sociability' && m.remaining === 100); + expect(socMod).toBeDefined(); + expect(socMod!.value).toBe(1); + }); + + it('adds negative sociability and empathy modifiers on negative outcome', () => { + const world = new World(); + const a = createNPC(world, 5, 5); + const b = createNPC(world, 8, 5); + addStats(world, a); + addStats(world, b); + const sa = world.getComponent(a, 'socialState')!; + const sb = world.getComponent(b, 'socialState')!; + sa.phase = 'emoting'; sa.partnerId = b; sa.phaseTimer = 1; sa.outcome = 'negative'; + sb.phase = 'emoting'; sb.partnerId = a; sb.phaseTimer = 1; sb.outcome = 'negative'; + socialSystem(world); + const mods = world.getComponent(a, 'statModifiers')!; + const socMod = mods.modifiers.find(m => m.stat === 'sociability' && m.remaining === 100); + const empMod = mods.modifiers.find(m => m.stat === 'empathy' && m.remaining === 100); + expect(socMod?.value).toBe(-1); + expect(empMod?.value).toBe(-1); + }); + it('high empathy biases positive outcome', () => { const world = new World(); let positiveCount = 0; diff --git a/server/src/systems/socialSystem.ts b/server/src/systems/socialSystem.ts index 0deff58..a714821 100644 --- a/server/src/systems/socialSystem.ts +++ b/server/src/systems/socialSystem.ts @@ -3,7 +3,7 @@ import { SOCIAL_GLOBAL_COOLDOWN, SOCIAL_PAIR_COOLDOWN, HUNGER_THRESHOLD, ENERGY_THRESHOLD, Direction, type SocialState, type Position, type Movement, type NPCBrain, type Needs, type EntityId, - type Stats, + type Stats, type StatModifiers, } from '@dflike/shared'; import type { World } from '../ecs/World.js'; import { getEffectiveStat } from './statHelpers.js'; @@ -128,6 +128,26 @@ export function socialSystem(world: World): void { } } + // Event-based transient modifiers + const modsA = world.getComponent(e, 'statModifiers'); + if (modsA) { + if (social.outcome === 'positive') { + modsA.modifiers.push({ stat: 'sociability', value: 1, remaining: 100 }); + } else { + modsA.modifiers.push({ stat: 'sociability', value: -1, remaining: 100 }); + modsA.modifiers.push({ stat: 'empathy', value: -1, remaining: 100 }); + } + } + const modsB = world.getComponent(partnerId!, 'statModifiers'); + if (modsB) { + if (partnerSocial.outcome === 'positive') { + modsB.modifiers.push({ stat: 'sociability', value: 1, remaining: 100 }); + } else { + modsB.modifiers.push({ stat: 'sociability', value: -1, remaining: 100 }); + modsB.modifiers.push({ stat: 'empathy', value: -1, remaining: 100 }); + } + } + social.phase = 'none'; social.partnerId = null; social.phaseTimer = 0;