Skip to content

Commit 3076f9e

Browse files
committed
Add stat effects to menu choices and update label lines schema
1 parent 74b5f0a commit 3076f9e

4 files changed

Lines changed: 171 additions & 2 deletions

File tree

apps/backend/scripts/seed-technical-badges.ts

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,17 +240,34 @@ async function seedTechnicalBadgesData() {
240240
label: "Fight",
241241
targetLabelId: "label_fight",
242242
targetLabelName: "label_fight",
243+
effects: {
244+
stats: {
245+
bravery: 10,
246+
},
247+
},
243248
},
244249
{
245250
label: "Run away",
246251
targetLabelId: "label_run",
247252
targetLabelName: "label_run",
248253
conditionFlags: ["has_stamina"],
254+
effects: {
255+
stats: {
256+
stamina: -5,
257+
cowardice: 3,
258+
},
259+
},
249260
},
250261
{
251262
label: "Talk",
252263
targetLabelId: "label_talk",
253264
targetLabelName: "label_talk",
265+
effects: {
266+
stats: {
267+
charisma: 5,
268+
trust: 8,
269+
},
270+
},
254271
},
255272
],
256273
},
@@ -325,11 +342,23 @@ async function seedTechnicalBadgesData() {
325342
label: "Light path",
326343
targetLabelId: "label_light",
327344
targetLabelName: "label_light",
345+
effects: {
346+
stats: {
347+
light: 5,
348+
darkness: -3,
349+
},
350+
},
328351
},
329352
{
330353
label: "Dark path",
331354
targetLabelId: "label_dark",
332355
targetLabelName: "label_dark",
356+
effects: {
357+
stats: {
358+
darkness: 8,
359+
suspicion: 2,
360+
},
361+
},
333362
},
334363
],
335364
conditions: {
@@ -392,17 +421,35 @@ async function seedTechnicalBadgesData() {
392421
label: "Gold coins",
393422
targetLabelId: "label_gold",
394423
targetLabelName: "label_gold",
424+
effects: {
425+
stats: {
426+
gold: -50,
427+
trust: 3,
428+
},
429+
},
395430
},
396431
{
397432
label: "Magic gem",
398433
targetLabelId: "label_gem",
399434
targetLabelName: "label_gem",
400435
conditionFlags: ["has_gem"],
436+
effects: {
437+
stats: {
438+
magic: 10,
439+
greed: -5,
440+
},
441+
},
401442
},
402443
{
403444
label: "Nothing",
404445
targetLabelId: "label_nothing",
405446
targetLabelName: "label_nothing",
447+
effects: {
448+
stats: {
449+
trust: -5,
450+
anger: 7,
451+
},
452+
},
406453
},
407454
],
408455
},
@@ -426,6 +473,102 @@ async function seedTechnicalBadgesData() {
426473
},
427474
},
428475
},
476+
477+
// Line 11: Menu with multiple stat effects per choice
478+
{
479+
labelId,
480+
sequence: 11,
481+
content: "Who will you ally with?",
482+
contentType: "DIALOGUE",
483+
speakerId: characterId,
484+
visualType: "GENERATED",
485+
menuOptions: [
486+
{
487+
label: "The Kingdom",
488+
targetLabelId: "label_fight",
489+
targetLabelName: "label_fight",
490+
effects: {
491+
stats: {
492+
honor: 15,
493+
kingdom_reputation: 20,
494+
rebel_loyalty: -10,
495+
},
496+
},
497+
},
498+
{
499+
label: "The Rebels",
500+
targetLabelId: "label_run",
501+
targetLabelName: "label_run",
502+
effects: {
503+
stats: {
504+
honor: -10,
505+
kingdom_reputation: -15,
506+
rebel_loyalty: 25,
507+
freedom: 10,
508+
},
509+
},
510+
},
511+
{
512+
label: "Stay Neutral",
513+
targetLabelId: "label_talk",
514+
targetLabelName: "label_talk",
515+
effects: {
516+
stats: {
517+
caution: 8,
518+
wisdom: 5,
519+
},
520+
},
521+
},
522+
],
523+
},
524+
525+
// Line 12: Menu with mixed positive and negative effects
526+
{
527+
labelId,
528+
sequence: 12,
529+
content: "What's your strategy?",
530+
contentType: "DIALOGUE",
531+
speakerId: characterId,
532+
visualType: "GENERATED",
533+
menuOptions: [
534+
{
535+
label: "Aggressive approach",
536+
targetLabelId: "label_gold",
537+
targetLabelName: "label_gold",
538+
effects: {
539+
stats: {
540+
damage: 20,
541+
stealth: -15,
542+
health_risk: 10,
543+
},
544+
},
545+
},
546+
{
547+
label: "Stealthy approach",
548+
targetLabelId: "label_gem",
549+
targetLabelName: "label_gem",
550+
effects: {
551+
stats: {
552+
stealth: 25,
553+
reputation: 5,
554+
damage: -5,
555+
},
556+
},
557+
},
558+
{
559+
label: "Diplomatic approach",
560+
targetLabelId: "label_nothing",
561+
targetLabelName: "label_nothing",
562+
effects: {
563+
stats: {
564+
charisma: 15,
565+
reputation: 10,
566+
intimidation: -10,
567+
},
568+
},
569+
},
570+
],
571+
},
429572
];
430573

431574
await db.insert(labelLines).values(testLines);

apps/backend/src/db/schema/tables/label-lines.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ export const labelLines = pgTable(
4343
targetLabelId: string;
4444
targetLabelName: string;
4545
conditionFlags?: string[];
46+
effects?: {
47+
stats?: Record<string, number>;
48+
};
4649
}>
4750
>(),
4851

apps/backend/src/services/labels.service.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2499,6 +2499,9 @@ export function resolveJumpTargets<
24992499
targetLabelId: string;
25002500
targetLabelName: string;
25012501
conditionFlags?: string[];
2502+
effects?: {
2503+
stats?: Record<string, number>;
2504+
};
25022505
}> | null;
25032506
},
25042507
>(lines: T[], allLabels: Array<{ id: string; labelName: string | null }>): T[] {

apps/frontend/src/components/write-mode/TechnicalPopover.tsx

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { ArrowUpRight, Image, HelpCircle, Split } from "lucide-react";
2-
import { useRef, useEffect } from "react";
2+
import { useRef, useEffect, useState } from "react";
33
import type { ComparisonOperator, StatCondition } from "@branchforge/shared";
44

55
interface ConditionsData {
@@ -50,6 +50,7 @@ export function TechnicalPopover({
5050
onClose,
5151
}: TechnicalPopoverProps) {
5252
const popoverRef = useRef<HTMLDivElement>(null);
53+
const [isFlipped, setIsFlipped] = useState(false);
5354

5455
// Handle click outside to close
5556
useEffect(() => {
@@ -68,6 +69,23 @@ export function TechnicalPopover({
6869
};
6970
}, [onClose]);
7071

72+
// Calculate position - flip upwards if not enough space below
73+
useEffect(() => {
74+
const popover = popoverRef.current;
75+
if (!popover) return;
76+
77+
const rect = popover.getBoundingClientRect();
78+
const viewportHeight = window.innerHeight;
79+
const spaceBelow = viewportHeight - rect.bottom;
80+
81+
// If less than 16px space below (allow some margin), flip upwards
82+
if (spaceBelow < 16) {
83+
setIsFlipped(true);
84+
} else {
85+
setIsFlipped(false);
86+
}
87+
}, []);
88+
7189
const renderContent = () => {
7290
switch (type) {
7391
case "conditions": {
@@ -207,7 +225,9 @@ export function TechnicalPopover({
207225
return (
208226
<div
209227
ref={popoverRef}
210-
className="absolute left-0 top-full mt-1 z-50 bg-popover border border-border rounded-md shadow-lg p-3 min-w-[200px] animate-in fade-in-0 zoom-in-95 duration-200"
228+
className={`absolute left-0 z-50 bg-popover border border-border rounded-md shadow-lg p-3 min-w-[200px] animate-in fade-in-0 zoom-in-95 duration-200 ${
229+
isFlipped ? "bottom-full mb-1" : "top-full mt-1"
230+
}`}
211231
style={{ maxWidth: "280px" }}
212232
>
213233
{renderContent()}

0 commit comments

Comments
 (0)