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 |
}
|