complex piclet
Browse files- src/lib/components/PicletGenerator/PicletGenerator.svelte +205 -96
- src/lib/db/piclets.ts +27 -64
- src/lib/types/index.ts +49 -14
src/lib/components/PicletGenerator/PicletGenerator.svelte
CHANGED
@@ -455,8 +455,8 @@ Create a concise visual description (1-3 sentences, max 100 words). Focus only o
|
|
455 |
const rarityMatch = state.picletConcept.match(/# Object Rarity\s*\n([\s\S]*?)(?=^#)/m);
|
456 |
const objectRarity = rarityMatch ? rarityMatch[1].trim().toLowerCase() : 'common';
|
457 |
|
458 |
-
// Create
|
459 |
-
const statsPrompt = `Based on this detailed object description and monster concept,
|
460 |
|
461 |
ORIGINAL OBJECT DESCRIPTION:
|
462 |
"${state.imageCaption}"
|
@@ -466,54 +466,194 @@ MONSTER CONCEPT:
|
|
466 |
|
467 |
The object rarity has been assessed as: ${objectRarity}
|
468 |
|
469 |
-
|
|
|
|
|
|
|
|
|
470 |
|
471 |
-
|
472 |
-
|
473 |
-
β’
|
474 |
-
β’
|
475 |
-
β’
|
476 |
-
β’
|
477 |
-
β’
|
478 |
-
β’
|
479 |
-
β’
|
480 |
-
β’
|
481 |
-
β’
|
|
|
482 |
|
483 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
484 |
|
485 |
\`\`\`json
|
486 |
{
|
|
|
487 |
"properties": {
|
488 |
"name": {"type": "string", "description": "Creative name for the monster that hints at the original object"},
|
489 |
-
"
|
490 |
-
"
|
491 |
-
"
|
492 |
-
"
|
493 |
-
"
|
494 |
-
|
495 |
-
|
496 |
-
|
497 |
-
|
498 |
-
|
499 |
-
|
500 |
-
|
501 |
-
|
502 |
-
|
503 |
-
|
504 |
-
"
|
505 |
-
"
|
506 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
507 |
},
|
508 |
-
"required": ["name", "
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
509 |
}
|
510 |
\`\`\`
|
511 |
|
512 |
-
|
513 |
-
|
514 |
-
-
|
515 |
-
-
|
516 |
-
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
517 |
|
518 |
Write your response within \`\`\`json\`\`\``;
|
519 |
|
@@ -605,73 +745,42 @@ Write your response within \`\`\`json\`\`\``;
|
|
605 |
|
606 |
const parsedStats = JSON.parse(cleanJson.trim());
|
607 |
|
608 |
-
//
|
609 |
-
|
610 |
-
'monsterLore', 'specialPassiveTraitDescription', 'attackActionName', 'attackActionDescription',
|
611 |
-
'buffActionName', 'buffActionDescription', 'debuffActionName', 'debuffActionDescription',
|
612 |
-
'specialActionName', 'specialActionDescription', 'boostActionName', 'boostActionDescription',
|
613 |
-
'disparageActionName', 'disparageActionDescription'];
|
614 |
|
615 |
-
|
616 |
-
|
617 |
-
|
618 |
-
}
|
619 |
}
|
620 |
|
621 |
-
//
|
622 |
-
if (parsedStats.
|
623 |
-
|
624 |
-
'common': 'low',
|
625 |
-
'uncommon': 'medium',
|
626 |
-
'rare': 'high',
|
627 |
-
'legendary': 'legendary'
|
628 |
-
};
|
629 |
-
tier = tierMap[parsedStats.rarity.toLowerCase()] || 'medium';
|
630 |
-
}
|
631 |
-
|
632 |
-
// Add the description and tier that we extracted/mapped
|
633 |
-
// Use the name from the structured concept, or fall back to JSON response
|
634 |
-
if (!parsedStats.name) {
|
635 |
-
parsedStats.name = monsterName;
|
636 |
}
|
637 |
-
parsedStats.description = parsedStats.monsterLore || 'A mysterious creature with unknown origins.';
|
638 |
-
parsedStats.tier = tier;
|
639 |
|
640 |
-
// Ensure
|
641 |
-
const
|
642 |
-
|
643 |
-
|
644 |
-
if (parsedStats[field] !== undefined) {
|
645 |
-
// Convert string numbers to actual numbers
|
646 |
-
parsedStats[field] = parseInt(parsedStats[field]);
|
647 |
-
|
648 |
-
// Clamp to 0-100 range
|
649 |
-
parsedStats[field] = Math.max(0, Math.min(100, parsedStats[field]));
|
650 |
}
|
651 |
}
|
652 |
|
653 |
-
//
|
654 |
-
|
655 |
-
parsedStats.specialPassiveTrait = parsedStats.specialPassiveTraitDescription;
|
656 |
-
delete parsedStats.specialPassiveTraitDescription;
|
657 |
-
}
|
658 |
|
659 |
-
//
|
660 |
-
if (
|
661 |
-
parsedStats.
|
662 |
-
delete parsedStats.boostActionName;
|
663 |
-
}
|
664 |
-
if (parsedStats.boostActionDescription) {
|
665 |
-
parsedStats.buffActionDescription = parsedStats.boostActionDescription;
|
666 |
-
delete parsedStats.boostActionDescription;
|
667 |
-
}
|
668 |
-
if (parsedStats.disparageActionName) {
|
669 |
-
parsedStats.debuffActionName = parsedStats.disparageActionName;
|
670 |
-
delete parsedStats.disparageActionName;
|
671 |
}
|
672 |
-
|
673 |
-
|
674 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
675 |
}
|
676 |
|
677 |
const stats: PicletStats = parsedStats;
|
|
|
455 |
const rarityMatch = state.picletConcept.match(/# Object Rarity\s*\n([\s\S]*?)(?=^#)/m);
|
456 |
const objectRarity = rarityMatch ? rarityMatch[1].trim().toLowerCase() : 'common';
|
457 |
|
458 |
+
// Create comprehensive battle-ready monster prompt
|
459 |
+
const statsPrompt = `Based on this detailed object description and monster concept, create a complete battle-ready monster for the Pictuary Battle System:
|
460 |
|
461 |
ORIGINAL OBJECT DESCRIPTION:
|
462 |
"${state.imageCaption}"
|
|
|
466 |
|
467 |
The object rarity has been assessed as: ${objectRarity}
|
468 |
|
469 |
+
## BATTLE SYSTEM OVERVIEW
|
470 |
+
This monster will be used in a turn-based battle system with composable effects. You must create:
|
471 |
+
1. **Base Stats**: Core combat statistics
|
472 |
+
2. **Special Ability**: Passive trait with triggers and effects
|
473 |
+
3. **Movepool**: 4 battle moves with complex effect combinations
|
474 |
|
475 |
+
## TYPE SYSTEM
|
476 |
+
Choose the primary type (and optional secondary type) based on the object:
|
477 |
+
β’ **beast**: Vertebrate wildlife β mammals, birds, reptiles. Raw physicality, instincts
|
478 |
+
β’ **bug**: Arthropods β butterflies, beetles, mantises. Agile swarms, precision strikes
|
479 |
+
β’ **aquatic**: Life that swims, dives, sloshes β fish, octopus, sentient puddles. Tides, pressure
|
480 |
+
β’ **flora**: Plants and fungi β blooming or decaying. Growth, spores, vines, seasonal shifts
|
481 |
+
β’ **mineral**: Stones, crystals, metals β earth's depths. Durability, reflective armor, seismic shocks
|
482 |
+
β’ **space**: Stars, moon, cosmic objects β not of this world. Celestial energy, gravitational effects
|
483 |
+
β’ **machina**: Engineered devices β gadgets to machinery. Gears, circuits, drones, power surges
|
484 |
+
β’ **structure**: Buildings, bridges, monuments β architectural titans. Fortification, terrain shaping
|
485 |
+
β’ **culture**: Art, fashion, toys, symbols β creative expressions. Buffs, debuffs, illusion, stories
|
486 |
+
β’ **cuisine**: Dishes, drinks, culinary art β flavors and aromas. Temperature, restorative effects
|
487 |
|
488 |
+
## EFFECT SYSTEM
|
489 |
+
All abilities and moves use these **atomic building blocks**:
|
490 |
+
|
491 |
+
### **Effect Types:**
|
492 |
+
1. **damage**: Deal damage (weak/normal/strong/extreme) with formulas (standard/recoil/drain/fixed/percentage)
|
493 |
+
2. **modifyStats**: Change stats (increase/decrease/greatly_increase/greatly_decrease) for hp/attack/defense/speed/accuracy
|
494 |
+
3. **applyStatus**: Apply status effects (burn/freeze/paralyze/poison/sleep/confuse) with chance percentage
|
495 |
+
4. **heal**: Restore HP (small/medium/large/full) or by percentage/fixed amounts
|
496 |
+
5. **manipulatePP**: Drain, restore, or disable PP from moves
|
497 |
+
6. **fieldEffect**: Battlefield modifications (reflect, lightScreen, spikes, etc.)
|
498 |
+
7. **counter**: Reflect damage based on attack type (physical/special/any)
|
499 |
+
8. **priority**: Modify move priority (-5 to +5)
|
500 |
+
9. **removeStatus**: Cure specific status conditions
|
501 |
+
10. **mechanicOverride**: Modify core game mechanics (immunity, type changes, etc.)
|
502 |
+
|
503 |
+
### **Targets:**
|
504 |
+
- **self**: The move user
|
505 |
+
- **opponent**: The target opponent
|
506 |
+
- **all**: All combatants
|
507 |
+
- **allies**: Allied creatures (team battles)
|
508 |
+
- **field**: Entire battlefield
|
509 |
+
|
510 |
+
### **Conditions:**
|
511 |
+
- **always**: Effect always applies
|
512 |
+
- **onHit**: Only if move hits successfully
|
513 |
+
- **afterUse**: After move execution regardless of hit/miss
|
514 |
+
- **onCritical**: Only on critical hits
|
515 |
+
- **ifLowHp**: If user's HP < 25%
|
516 |
+
- **ifHighHp**: If user's HP > 75%
|
517 |
+
|
518 |
+
### **Move Flags:**
|
519 |
+
Moves can have flags affecting interactions:
|
520 |
+
- **contact**: Makes physical contact (triggers contact abilities)
|
521 |
+
- **explosive**: Explosive move (affected by explosion-related abilities)
|
522 |
+
- **draining**: Drains HP from target
|
523 |
+
- **priority**: Natural priority move (+1 to +5)
|
524 |
+
- **sacrifice**: Involves self-sacrifice or major cost
|
525 |
+
- **reckless**: High power with drawbacks
|
526 |
+
|
527 |
+
## SPECIAL ABILITY TRIGGERS
|
528 |
+
Special abilities activate on specific events:
|
529 |
+
- **onSwitchIn**: When entering battle
|
530 |
+
- **onDamageTaken**: When this monster takes damage
|
531 |
+
- **onContactDamage**: When hit by a contact move
|
532 |
+
- **endOfTurn**: At the end of each turn
|
533 |
+
- **onLowHP**: When HP drops below 25%
|
534 |
+
- **onStatusInflicted**: When a status is applied
|
535 |
+
|
536 |
+
## BALANCING GUIDELINES
|
537 |
+
**Stat Ranges by Rarity:**
|
538 |
+
- **common**: 45-80 total stats, individual stats 10-25
|
539 |
+
- **uncommon**: 80-120 total stats, individual stats 15-35
|
540 |
+
- **rare**: 120-160 total stats, individual stats 25-45
|
541 |
+
- **legendary**: 160-200+ total stats, individual stats 35-50
|
542 |
+
|
543 |
+
**Design Philosophy:**
|
544 |
+
- **Risk-Reward**: Powerful moves must have meaningful drawbacks
|
545 |
+
- **Type Synergy**: Moves should match the monster's type and concept
|
546 |
+
- **Strategic Depth**: Abilities should create interesting decision points
|
547 |
+
- **No Strictly Better**: Every powerful effect has a cost or condition
|
548 |
+
|
549 |
+
The output should be formatted as a JSON instance that conforms to the schema below.
|
550 |
|
551 |
\`\`\`json
|
552 |
{
|
553 |
+
"type": "object",
|
554 |
"properties": {
|
555 |
"name": {"type": "string", "description": "Creative name for the monster that hints at the original object"},
|
556 |
+
"description": {"type": "string", "description": "Flavor text describing the monster (2-3 sentences)"},
|
557 |
+
"tier": {"type": "string", "enum": ["low", "medium", "high", "legendary"], "description": "Power tier based on rarity: common=low, uncommon=medium, rare=high, legendary=legendary"},
|
558 |
+
"primaryType": {"type": "string", "enum": ["beast", "bug", "aquatic", "flora", "mineral", "space", "machina", "structure", "culture", "cuisine"], "description": "Primary type based on object characteristics"},
|
559 |
+
"secondaryType": {"type": ["string", "null"], "enum": ["beast", "bug", "aquatic", "flora", "mineral", "space", "machina", "structure", "culture", "cuisine", null], "description": "Optional secondary type for dual-type monsters"},
|
560 |
+
"baseStats": {
|
561 |
+
"type": "object",
|
562 |
+
"properties": {
|
563 |
+
"hp": {"type": "integer", "minimum": 10, "maximum": 50, "description": "Hit points"},
|
564 |
+
"attack": {"type": "integer", "minimum": 10, "maximum": 50, "description": "Attack power"},
|
565 |
+
"defense": {"type": "integer", "minimum": 10, "maximum": 50, "description": "Defensive capability"},
|
566 |
+
"speed": {"type": "integer", "minimum": 10, "maximum": 50, "description": "Speed and agility"}
|
567 |
+
},
|
568 |
+
"required": ["hp", "attack", "defense", "speed"],
|
569 |
+
"additionalProperties": false
|
570 |
+
},
|
571 |
+
"nature": {"type": "string", "description": "Personality trait affecting behavior (e.g., 'bold', 'timid', 'hasty')"},
|
572 |
+
"specialAbility": {
|
573 |
+
"type": "object",
|
574 |
+
"properties": {
|
575 |
+
"name": {"type": "string", "description": "Name of the special ability"},
|
576 |
+
"description": {"type": "string", "description": "Description of what the ability does"},
|
577 |
+
"effects": {
|
578 |
+
"type": "array",
|
579 |
+
"items": {"$ref": "#/definitions/Effect"},
|
580 |
+
"description": "Passive effects that are always active"
|
581 |
+
},
|
582 |
+
"triggers": {
|
583 |
+
"type": "array",
|
584 |
+
"items": {"$ref": "#/definitions/Trigger"},
|
585 |
+
"description": "Effects that trigger on specific events"
|
586 |
+
}
|
587 |
+
},
|
588 |
+
"required": ["name", "description"],
|
589 |
+
"additionalProperties": false
|
590 |
+
},
|
591 |
+
"movepool": {
|
592 |
+
"type": "array",
|
593 |
+
"items": {"$ref": "#/definitions/Move"},
|
594 |
+
"minItems": 4,
|
595 |
+
"maxItems": 4,
|
596 |
+
"description": "Exactly 4 battle moves"
|
597 |
+
}
|
598 |
},
|
599 |
+
"required": ["name", "description", "tier", "primaryType", "baseStats", "nature", "specialAbility", "movepool"],
|
600 |
+
"additionalProperties": false,
|
601 |
+
"definitions": {
|
602 |
+
"Effect": {
|
603 |
+
"type": "object",
|
604 |
+
"properties": {
|
605 |
+
"type": {"type": "string", "enum": ["damage", "modifyStats", "applyStatus", "heal", "manipulatePP", "fieldEffect", "counter", "priority", "removeStatus", "mechanicOverride"]},
|
606 |
+
"target": {"type": "string", "enum": ["self", "opponent", "allies", "all", "attacker", "field", "playerSide", "opponentSide"]},
|
607 |
+
"condition": {"type": "string", "enum": ["always", "onHit", "afterUse", "onCritical", "ifLowHp", "ifHighHp", "thisTurn", "nextTurn", "restOfBattle"]}
|
608 |
+
},
|
609 |
+
"required": ["type"],
|
610 |
+
"allOf": [
|
611 |
+
{"if": {"properties": {"type": {"const": "damage"}}}, "then": {"properties": {"amount": {"enum": ["weak", "normal", "strong", "extreme"]}, "formula": {"enum": ["standard", "recoil", "drain", "fixed", "percentage"]}, "value": {"type": "number"}}}},
|
612 |
+
{"if": {"properties": {"type": {"const": "modifyStats"}}}, "then": {"properties": {"stats": {"type": "object", "properties": {"hp": {"enum": ["increase", "decrease", "greatly_increase", "greatly_decrease"]}, "attack": {"enum": ["increase", "decrease", "greatly_increase", "greatly_decrease"]}, "defense": {"enum": ["increase", "decrease", "greatly_increase", "greatly_decrease"]}, "speed": {"enum": ["increase", "decrease", "greatly_increase", "greatly_decrease"]}, "accuracy": {"enum": ["increase", "decrease", "greatly_increase", "greatly_decrease"]}}}}}},
|
613 |
+
{"if": {"properties": {"type": {"const": "applyStatus"}}}, "then": {"properties": {"status": {"enum": ["burn", "freeze", "paralyze", "poison", "sleep", "confuse"]}, "chance": {"type": "number", "minimum": 1, "maximum": 100}}}},
|
614 |
+
{"if": {"properties": {"type": {"const": "heal"}}}, "then": {"properties": {"amount": {"enum": ["small", "medium", "large", "full"]}, "formula": {"enum": ["percentage", "fixed"]}, "value": {"type": "number"}}}}
|
615 |
+
]
|
616 |
+
},
|
617 |
+
"Trigger": {
|
618 |
+
"type": "object",
|
619 |
+
"properties": {
|
620 |
+
"event": {"type": "string", "enum": ["onSwitchIn", "onDamageTaken", "onContactDamage", "endOfTurn", "onLowHP", "onStatusInflicted", "beforeMoveUse", "afterMoveUse"]},
|
621 |
+
"condition": {"type": "string", "enum": ["always", "ifLowHp", "ifHighHp", "ifStatus:burn", "ifStatus:freeze", "ifStatus:paralyze", "ifStatus:poison", "ifStatus:sleep", "ifStatus:confuse"]},
|
622 |
+
"effects": {"type": "array", "items": {"$ref": "#/definitions/Effect"}, "minItems": 1}
|
623 |
+
},
|
624 |
+
"required": ["event", "effects"]
|
625 |
+
},
|
626 |
+
"Move": {
|
627 |
+
"type": "object",
|
628 |
+
"properties": {
|
629 |
+
"name": {"type": "string", "description": "Name of the move"},
|
630 |
+
"type": {"type": "string", "enum": ["beast", "bug", "aquatic", "flora", "mineral", "space", "machina", "structure", "culture", "cuisine", "normal"], "description": "Move type for STAB and effectiveness"},
|
631 |
+
"power": {"type": "integer", "minimum": 0, "maximum": 250, "description": "Base power (0 for status moves)"},
|
632 |
+
"accuracy": {"type": "integer", "minimum": 30, "maximum": 100, "description": "Hit chance percentage"},
|
633 |
+
"pp": {"type": "integer", "minimum": 1, "maximum": 40, "description": "Power points (uses per battle)"},
|
634 |
+
"priority": {"type": "integer", "minimum": -5, "maximum": 5, "description": "Priority bracket"},
|
635 |
+
"flags": {"type": "array", "items": {"enum": ["contact", "explosive", "draining", "priority", "sacrifice", "reckless", "bite", "punch", "sound", "ground"]}, "description": "Move characteristics"},
|
636 |
+
"effects": {"type": "array", "items": {"$ref": "#/definitions/Effect"}, "minItems": 1, "description": "What the move does"}
|
637 |
+
},
|
638 |
+
"required": ["name", "type", "power", "accuracy", "pp", "priority", "flags", "effects"],
|
639 |
+
"additionalProperties": false
|
640 |
+
}
|
641 |
+
}
|
642 |
}
|
643 |
\`\`\`
|
644 |
|
645 |
+
**STAT GUIDELINES:**
|
646 |
+
Base the tier and stats on the object rarity:
|
647 |
+
- **common β low tier**: hp/attack/defense/speed should be 10-25 (total ~40-80)
|
648 |
+
- **uncommon β medium tier**: hp/attack/defense/speed should be 15-35 (total ~80-120)
|
649 |
+
- **rare β high tier**: hp/attack/defense/speed should be 25-45 (total ~120-160)
|
650 |
+
- **legendary β legendary tier**: hp/attack/defense/speed should be 35-50 (total ~160-200)
|
651 |
+
|
652 |
+
**MOVE DESIGN EXAMPLES:**
|
653 |
+
- **Basic Attack**: {"type": "damage", "target": "opponent", "amount": "normal"}
|
654 |
+
- **Status Move**: {"type": "applyStatus", "target": "opponent", "status": "burn", "chance": 30}
|
655 |
+
- **Self-Buff**: {"type": "modifyStats", "target": "self", "stats": {"attack": "increase"}}
|
656 |
+
- **Risk-Reward**: High power + {"type": "damage", "target": "self", "formula": "recoil", "value": 0.25}
|
657 |
|
658 |
Write your response within \`\`\`json\`\`\``;
|
659 |
|
|
|
745 |
|
746 |
const parsedStats = JSON.parse(cleanJson.trim());
|
747 |
|
748 |
+
// Validate the battle-ready monster structure
|
749 |
+
console.log('Parsed battle monster:', parsedStats);
|
|
|
|
|
|
|
|
|
750 |
|
751 |
+
// Ensure required fields exist
|
752 |
+
if (!parsedStats.name || !parsedStats.baseStats || !parsedStats.specialAbility || !parsedStats.movepool) {
|
753 |
+
throw new Error('Generated monster is missing required battle system fields');
|
|
|
754 |
}
|
755 |
|
756 |
+
// Validate movepool has exactly 4 moves
|
757 |
+
if (!Array.isArray(parsedStats.movepool) || parsedStats.movepool.length !== 4) {
|
758 |
+
throw new Error('Monster movepool must contain exactly 4 moves');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
759 |
}
|
|
|
|
|
760 |
|
761 |
+
// Ensure all moves have required effect arrays
|
762 |
+
for (const move of parsedStats.movepool) {
|
763 |
+
if (!move.effects || !Array.isArray(move.effects) || move.effects.length === 0) {
|
764 |
+
throw new Error(`Move "${move.name}" is missing effects array`);
|
|
|
|
|
|
|
|
|
|
|
|
|
765 |
}
|
766 |
}
|
767 |
|
768 |
+
// Use tier from the JSON response
|
769 |
+
tier = parsedStats.tier || 'medium';
|
|
|
|
|
|
|
770 |
|
771 |
+
// Ensure the name from structured concept is used if available
|
772 |
+
if (monsterName && monsterName !== 'Unknown Monster') {
|
773 |
+
parsedStats.name = monsterName;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
774 |
}
|
775 |
+
|
776 |
+
// Ensure baseStats are numbers within reasonable ranges
|
777 |
+
if (parsedStats.baseStats) {
|
778 |
+
const statFields = ['hp', 'attack', 'defense', 'speed'];
|
779 |
+
for (const field of statFields) {
|
780 |
+
if (parsedStats.baseStats[field] !== undefined) {
|
781 |
+
parsedStats.baseStats[field] = Math.max(10, Math.min(50, parseInt(parsedStats.baseStats[field])));
|
782 |
+
}
|
783 |
+
}
|
784 |
}
|
785 |
|
786 |
const stats: PicletStats = parsedStats;
|
src/lib/db/piclets.ts
CHANGED
@@ -1,6 +1,7 @@
|
|
1 |
import { db } from './index';
|
2 |
import type { PicletInstance, Monster, BattleMove } from './schema';
|
3 |
import { PicletType, AttackType, getTypeFromConcept } from '../types/picletTypes';
|
|
|
4 |
|
5 |
// Convert a generated Monster to a PicletInstance
|
6 |
export async function monsterToPicletInstance(monster: Monster, level: number = 5): Promise<Omit<PicletInstance, 'id'>> {
|
@@ -8,13 +9,13 @@ export async function monsterToPicletInstance(monster: Monster, level: number =
|
|
8 |
throw new Error('Monster must have stats to create PicletInstance');
|
9 |
}
|
10 |
|
11 |
-
const stats = monster.stats;
|
12 |
|
13 |
-
// Calculate base stats from
|
14 |
-
const baseHp = Math.floor(stats.
|
15 |
-
const baseAttack = Math.floor(stats.attack * 1.5 + 30);
|
16 |
-
const baseDefense = Math.floor(stats.
|
17 |
-
const baseSpeed = Math.floor(stats.speed * 1.5 + 30);
|
18 |
|
19 |
// Field stats are variations of regular stats
|
20 |
const baseFieldAttack = Math.floor(baseAttack * 0.8);
|
@@ -26,65 +27,27 @@ export async function monsterToPicletInstance(monster: Monster, level: number =
|
|
26 |
|
27 |
const maxHp = calculateHp(baseHp, level);
|
28 |
|
29 |
-
// Determine primary type
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
if (validType) {
|
39 |
-
primaryType = validType;
|
40 |
-
} else {
|
41 |
-
console.warn(`Invalid picletType "${statsType}" from stats, falling back to concept detection`);
|
42 |
-
primaryType = getTypeFromConcept(monster.concept, monster.imageCaption);
|
43 |
-
}
|
44 |
-
} else {
|
45 |
-
// Fallback to concept-based detection
|
46 |
-
primaryType = getTypeFromConcept(monster.concept, monster.imageCaption);
|
47 |
}
|
48 |
|
49 |
-
// Create moves
|
50 |
-
const moves: BattleMove[] =
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
},
|
60 |
-
{
|
61 |
-
name: stats.buffActionName,
|
62 |
-
type: AttackType.NORMAL,
|
63 |
-
power: 0,
|
64 |
-
accuracy: 100,
|
65 |
-
pp: 15,
|
66 |
-
currentPp: 15,
|
67 |
-
description: stats.buffActionDescription
|
68 |
-
},
|
69 |
-
{
|
70 |
-
name: stats.debuffActionName,
|
71 |
-
type: AttackType.NORMAL,
|
72 |
-
power: 0,
|
73 |
-
accuracy: 85,
|
74 |
-
pp: 15,
|
75 |
-
currentPp: 15,
|
76 |
-
description: stats.debuffActionDescription
|
77 |
-
},
|
78 |
-
{
|
79 |
-
name: stats.specialActionName,
|
80 |
-
type: primaryType as unknown as AttackType,
|
81 |
-
power: 80,
|
82 |
-
accuracy: 90,
|
83 |
-
pp: 5,
|
84 |
-
currentPp: 5,
|
85 |
-
description: stats.specialActionDescription
|
86 |
-
}
|
87 |
-
];
|
88 |
|
89 |
const bst = baseHp + baseAttack + baseDefense + baseFieldAttack + baseFieldDefense + baseSpeed;
|
90 |
|
@@ -125,7 +88,7 @@ export async function monsterToPicletInstance(monster: Monster, level: number =
|
|
125 |
// Metadata
|
126 |
caughtAt: new Date(),
|
127 |
bst,
|
128 |
-
tier:
|
129 |
role: 'balanced', // Could be enhanced based on stat distribution
|
130 |
variance: 0,
|
131 |
|
|
|
1 |
import { db } from './index';
|
2 |
import type { PicletInstance, Monster, BattleMove } from './schema';
|
3 |
import { PicletType, AttackType, getTypeFromConcept } from '../types/picletTypes';
|
4 |
+
import type { PicletStats } from '../types';
|
5 |
|
6 |
// Convert a generated Monster to a PicletInstance
|
7 |
export async function monsterToPicletInstance(monster: Monster, level: number = 5): Promise<Omit<PicletInstance, 'id'>> {
|
|
|
9 |
throw new Error('Monster must have stats to create PicletInstance');
|
10 |
}
|
11 |
|
12 |
+
const stats = monster.stats as PicletStats;
|
13 |
|
14 |
+
// Calculate base stats from battle-ready format
|
15 |
+
const baseHp = Math.floor(stats.baseStats.hp * 2 + 50);
|
16 |
+
const baseAttack = Math.floor(stats.baseStats.attack * 1.5 + 30);
|
17 |
+
const baseDefense = Math.floor(stats.baseStats.defense * 1.5 + 30);
|
18 |
+
const baseSpeed = Math.floor(stats.baseStats.speed * 1.5 + 30);
|
19 |
|
20 |
// Field stats are variations of regular stats
|
21 |
const baseFieldAttack = Math.floor(baseAttack * 0.8);
|
|
|
27 |
|
28 |
const maxHp = calculateHp(baseHp, level);
|
29 |
|
30 |
+
// Determine primary type from battle stats
|
31 |
+
const normalizedType = stats.primaryType.toLowerCase();
|
32 |
+
|
33 |
+
// Validate that the type exists in our enum
|
34 |
+
const validType = Object.values(PicletType).find(type => type === normalizedType);
|
35 |
+
const primaryType = validType || getTypeFromConcept(monster.concept, monster.imageCaption);
|
36 |
+
|
37 |
+
if (!validType) {
|
38 |
+
console.warn(`Invalid primaryType "${stats.primaryType}" from stats, falling back to concept detection`);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
39 |
}
|
40 |
|
41 |
+
// Create moves from battle-ready format
|
42 |
+
const moves: BattleMove[] = stats.movepool.map(move => ({
|
43 |
+
name: move.name,
|
44 |
+
type: move.type as unknown as AttackType,
|
45 |
+
power: move.power,
|
46 |
+
accuracy: move.accuracy,
|
47 |
+
pp: move.pp,
|
48 |
+
currentPp: move.pp,
|
49 |
+
description: `${move.effects.length} effects` // Simplified description for now
|
50 |
+
}));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
51 |
|
52 |
const bst = baseHp + baseAttack + baseDefense + baseFieldAttack + baseFieldDefense + baseSpeed;
|
53 |
|
|
|
88 |
// Metadata
|
89 |
caughtAt: new Date(),
|
90 |
bst,
|
91 |
+
tier: stats.tier, // Use tier from battle-ready stats
|
92 |
role: 'balanced', // Could be enhanced based on stat distribution
|
93 |
variance: 0,
|
94 |
|
src/lib/types/index.ts
CHANGED
@@ -103,22 +103,57 @@ export interface PicletGeneratorProps {
|
|
103 |
qwenClient: GradioClient | null;
|
104 |
}
|
105 |
|
106 |
-
// Piclet Stats Types
|
107 |
export interface PicletStats {
|
108 |
name: string;
|
109 |
description: string;
|
110 |
tier: 'low' | 'medium' | 'high' | 'legendary';
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
124 |
}
|
|
|
103 |
qwenClient: GradioClient | null;
|
104 |
}
|
105 |
|
106 |
+
// Piclet Stats Types - now compatible with battle engine
|
107 |
export interface PicletStats {
|
108 |
name: string;
|
109 |
description: string;
|
110 |
tier: 'low' | 'medium' | 'high' | 'legendary';
|
111 |
+
primaryType: 'beast' | 'bug' | 'aquatic' | 'flora' | 'mineral' | 'space' | 'machina' | 'structure' | 'culture' | 'cuisine';
|
112 |
+
secondaryType?: 'beast' | 'bug' | 'aquatic' | 'flora' | 'mineral' | 'space' | 'machina' | 'structure' | 'culture' | 'cuisine' | null;
|
113 |
+
baseStats: {
|
114 |
+
hp: number;
|
115 |
+
attack: number;
|
116 |
+
defense: number;
|
117 |
+
speed: number;
|
118 |
+
};
|
119 |
+
nature: string;
|
120 |
+
specialAbility: {
|
121 |
+
name: string;
|
122 |
+
description: string;
|
123 |
+
effects?: BattleEffect[];
|
124 |
+
triggers?: AbilityTrigger[];
|
125 |
+
};
|
126 |
+
movepool: BattleMove[];
|
127 |
+
}
|
128 |
+
|
129 |
+
// Battle system effect types for PicletStats
|
130 |
+
export interface BattleEffect {
|
131 |
+
type: 'damage' | 'modifyStats' | 'applyStatus' | 'heal' | 'manipulatePP' | 'fieldEffect' | 'counter' | 'priority' | 'removeStatus' | 'mechanicOverride';
|
132 |
+
target: 'self' | 'opponent' | 'allies' | 'all' | 'attacker' | 'field' | 'playerSide' | 'opponentSide';
|
133 |
+
condition?: string;
|
134 |
+
// Additional properties based on effect type
|
135 |
+
amount?: string;
|
136 |
+
formula?: string;
|
137 |
+
value?: number;
|
138 |
+
stats?: { [key: string]: string };
|
139 |
+
status?: string;
|
140 |
+
chance?: number;
|
141 |
+
[key: string]: any; // Allow additional properties for different effect types
|
142 |
+
}
|
143 |
+
|
144 |
+
export interface AbilityTrigger {
|
145 |
+
event: string;
|
146 |
+
condition?: string;
|
147 |
+
effects: BattleEffect[];
|
148 |
+
}
|
149 |
+
|
150 |
+
export interface BattleMove {
|
151 |
+
name: string;
|
152 |
+
type: 'beast' | 'bug' | 'aquatic' | 'flora' | 'mineral' | 'space' | 'machina' | 'structure' | 'culture' | 'cuisine' | 'normal';
|
153 |
+
power: number;
|
154 |
+
accuracy: number;
|
155 |
+
pp: number;
|
156 |
+
priority: number;
|
157 |
+
flags: string[];
|
158 |
+
effects: BattleEffect[];
|
159 |
}
|