use tier
Browse files- src/lib/components/MonsterGenerator/MonsterGenerator.svelte +39 -25
- src/lib/components/MonsterGenerator/MonsterResult.svelte +32 -3
- src/lib/components/Pages/Encounters.svelte +1 -1
- src/lib/components/Piclets/PicletDetail.svelte +1 -1
- src/lib/db/piclets.ts +2 -2
- src/lib/db/schema.ts +1 -1
- src/lib/types/index.ts +1 -1
src/lib/components/MonsterGenerator/MonsterGenerator.svelte
CHANGED
|
@@ -177,14 +177,14 @@ Focus on: colors, body shape, eyes, limbs, mouth, and key visual features. Omit
|
|
| 177 |
}
|
| 178 |
|
| 179 |
try {
|
| 180 |
-
const output = await joyCaptionClient.predict("/stream_chat",
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
|
| 189 |
const [prompt, caption] = output.data;
|
| 190 |
// The caption now contains the full monster concept with lore, visual description, and rarity
|
|
@@ -297,30 +297,36 @@ Focus on: colors, body shape, eyes, limbs, mouth, and key visual features. Omit
|
|
| 297 |
throw new Error('Text generation service not available or no concept');
|
| 298 |
}
|
| 299 |
|
| 300 |
-
// Extract
|
| 301 |
-
let
|
| 302 |
const rarityMatch = state.monsterConcept.match(/Object Rarity:\s*(common|somewhat rare|very rare|extremely rare|legendary)/i);
|
| 303 |
if (rarityMatch) {
|
| 304 |
-
|
| 305 |
-
|
| 306 |
-
'
|
| 307 |
-
'
|
| 308 |
-
'
|
| 309 |
-
'
|
|
|
|
| 310 |
};
|
| 311 |
-
|
| 312 |
}
|
| 313 |
|
| 314 |
// Extract monster name from the concept
|
| 315 |
const nameMatch = state.monsterConcept.match(/^#\s+(.+)$/m);
|
| 316 |
const monsterName = nameMatch ? nameMatch[1] : 'Unknown Monster';
|
| 317 |
|
| 318 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
| 319 |
const statsPrompt = `Convert the following monster concept into a JSON object with stats:
|
| 320 |
"${state.monsterConcept}"
|
| 321 |
|
| 322 |
-
The monster's rarity has been determined as: ${rarityScore}/100
|
| 323 |
The monster's name is: ${monsterName}
|
|
|
|
|
|
|
| 324 |
|
| 325 |
The output should be formatted as a JSON instance that conforms to the JSON schema below.
|
| 326 |
|
|
@@ -328,8 +334,8 @@ The output should be formatted as a JSON instance that conforms to the JSON sche
|
|
| 328 |
{
|
| 329 |
"properties": {
|
| 330 |
"name": {"type": "string", "description": "The monster's unique name"},
|
| 331 |
-
"description": {"type": "string", "description": "
|
| 332 |
-
"
|
| 333 |
"HP": {"type": "integer", "minimum": 0, "maximum": 100, "description": "Health/vitality stat (0=fragile, 100=incredibly tanky)"},
|
| 334 |
"defence": {"type": "integer", "minimum": 0, "maximum": 100, "description": "Defensive/armor stat (0=paper thin, 100=impenetrable fortress)"},
|
| 335 |
"attack": {"type": "integer", "minimum": 0, "maximum": 100, "description": "Physical attack power (0=harmless, 100=devastating force)"},
|
|
@@ -344,11 +350,19 @@ The output should be formatted as a JSON instance that conforms to the JSON sche
|
|
| 344 |
"specialActionName": {"type": "string", "description": "Name of the monster's ultimate move (one use per battle)"},
|
| 345 |
"specialActionDescription": {"type": "string", "description": "Describe this powerful finishing move and its dramatic effects in battle"}
|
| 346 |
},
|
| 347 |
-
"required": ["name", "description", "
|
| 348 |
}
|
| 349 |
\`\`\`
|
| 350 |
|
| 351 |
-
Remember to set
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 352 |
|
| 353 |
Write your response within \`\`\`json\`\`\``;
|
| 354 |
|
|
@@ -393,7 +407,7 @@ Write your response within \`\`\`json\`\`\``;
|
|
| 393 |
const parsedStats = JSON.parse(cleanJson.trim());
|
| 394 |
|
| 395 |
// Remove any extra fields not in our schema
|
| 396 |
-
const allowedFields = ['name', 'description', '
|
| 397 |
'specialPassiveTraitDescription', 'attackActionName', 'attackActionDescription',
|
| 398 |
'buffActionName', 'buffActionDescription', 'debuffActionName', 'debuffActionDescription',
|
| 399 |
'specialActionName', 'specialActionDescription', 'boostActionName', 'boostActionDescription',
|
|
@@ -406,7 +420,7 @@ Write your response within \`\`\`json\`\`\``;
|
|
| 406 |
}
|
| 407 |
|
| 408 |
// Ensure numeric fields are actually numbers
|
| 409 |
-
const numericFields = ['
|
| 410 |
|
| 411 |
for (const field of numericFields) {
|
| 412 |
if (parsedStats[field] !== undefined) {
|
|
|
|
| 177 |
}
|
| 178 |
|
| 179 |
try {
|
| 180 |
+
const output = await joyCaptionClient.predict("/stream_chat", [
|
| 181 |
+
state.userImage, // input_image
|
| 182 |
+
"Descriptive", // caption_type
|
| 183 |
+
"very long", // caption_length
|
| 184 |
+
[], // extra_options
|
| 185 |
+
"", // name_input
|
| 186 |
+
MONSTER_GENERATION_PROMPT // custom_prompt
|
| 187 |
+
]);
|
| 188 |
|
| 189 |
const [prompt, caption] = output.data;
|
| 190 |
// The caption now contains the full monster concept with lore, visual description, and rarity
|
|
|
|
| 297 |
throw new Error('Text generation service not available or no concept');
|
| 298 |
}
|
| 299 |
|
| 300 |
+
// Extract tier from the joy-caption output
|
| 301 |
+
let tier: 'low' | 'medium' | 'high' | 'legendary' = 'medium'; // default
|
| 302 |
const rarityMatch = state.monsterConcept.match(/Object Rarity:\s*(common|somewhat rare|very rare|extremely rare|legendary)/i);
|
| 303 |
if (rarityMatch) {
|
| 304 |
+
// Map rarity to tier
|
| 305 |
+
const tierMap: { [key: string]: 'low' | 'medium' | 'high' | 'legendary' } = {
|
| 306 |
+
'common': 'low',
|
| 307 |
+
'somewhat rare': 'medium',
|
| 308 |
+
'very rare': 'high',
|
| 309 |
+
'extremely rare': 'high',
|
| 310 |
+
'legendary': 'legendary'
|
| 311 |
};
|
| 312 |
+
tier = tierMap[rarityMatch[1].toLowerCase()] || 'medium';
|
| 313 |
}
|
| 314 |
|
| 315 |
// Extract monster name from the concept
|
| 316 |
const nameMatch = state.monsterConcept.match(/^#\s+(.+)$/m);
|
| 317 |
const monsterName = nameMatch ? nameMatch[1] : 'Unknown Monster';
|
| 318 |
|
| 319 |
+
// Extract only the Monster Lore section for description
|
| 320 |
+
const loreMatch = state.monsterConcept.match(/## Monster Lore\s*\n([\s\S]*?)(?=##|$)/);
|
| 321 |
+
const monsterLore = loreMatch ? loreMatch[1].trim() : '';
|
| 322 |
+
|
| 323 |
+
// Update stats prompt to include the tier
|
| 324 |
const statsPrompt = `Convert the following monster concept into a JSON object with stats:
|
| 325 |
"${state.monsterConcept}"
|
| 326 |
|
|
|
|
| 327 |
The monster's name is: ${monsterName}
|
| 328 |
+
The monster's tier is: ${tier}
|
| 329 |
+
Use this as the description: "${monsterLore}"
|
| 330 |
|
| 331 |
The output should be formatted as a JSON instance that conforms to the JSON schema below.
|
| 332 |
|
|
|
|
| 334 |
{
|
| 335 |
"properties": {
|
| 336 |
"name": {"type": "string", "description": "The monster's unique name"},
|
| 337 |
+
"description": {"type": "string", "description": "The monster's lore/backstory"},
|
| 338 |
+
"tier": {"type": "string", "enum": ["low", "medium", "high", "legendary"], "description": "The monster's tier based on rarity"},
|
| 339 |
"HP": {"type": "integer", "minimum": 0, "maximum": 100, "description": "Health/vitality stat (0=fragile, 100=incredibly tanky)"},
|
| 340 |
"defence": {"type": "integer", "minimum": 0, "maximum": 100, "description": "Defensive/armor stat (0=paper thin, 100=impenetrable fortress)"},
|
| 341 |
"attack": {"type": "integer", "minimum": 0, "maximum": 100, "description": "Physical attack power (0=harmless, 100=devastating force)"},
|
|
|
|
| 350 |
"specialActionName": {"type": "string", "description": "Name of the monster's ultimate move (one use per battle)"},
|
| 351 |
"specialActionDescription": {"type": "string", "description": "Describe this powerful finishing move and its dramatic effects in battle"}
|
| 352 |
},
|
| 353 |
+
"required": ["name", "description", "tier", "HP", "defence", "attack", "speed", "specialPassiveTraitDescription", "attackActionName", "attackActionDescription", "buffActionName", "buffActionDescription", "debuffActionName", "debuffActionDescription", "specialActionName", "specialActionDescription"]
|
| 354 |
}
|
| 355 |
\`\`\`
|
| 356 |
|
| 357 |
+
Remember to set:
|
| 358 |
+
- name to "${monsterName}"
|
| 359 |
+
- description to "${monsterLore}"
|
| 360 |
+
- tier to "${tier}"
|
| 361 |
+
Base the HP, defence, attack, and speed stats on the tier level:
|
| 362 |
+
- low: stats should be 10-40
|
| 363 |
+
- medium: stats should be 30-60
|
| 364 |
+
- high: stats should be 50-80
|
| 365 |
+
- legendary: stats should be 70-100
|
| 366 |
|
| 367 |
Write your response within \`\`\`json\`\`\``;
|
| 368 |
|
|
|
|
| 407 |
const parsedStats = JSON.parse(cleanJson.trim());
|
| 408 |
|
| 409 |
// Remove any extra fields not in our schema
|
| 410 |
+
const allowedFields = ['name', 'description', 'tier', 'HP', 'defence', 'attack', 'speed',
|
| 411 |
'specialPassiveTraitDescription', 'attackActionName', 'attackActionDescription',
|
| 412 |
'buffActionName', 'buffActionDescription', 'debuffActionName', 'debuffActionDescription',
|
| 413 |
'specialActionName', 'specialActionDescription', 'boostActionName', 'boostActionDescription',
|
|
|
|
| 420 |
}
|
| 421 |
|
| 422 |
// Ensure numeric fields are actually numbers
|
| 423 |
+
const numericFields = ['HP', 'defence', 'attack', 'speed'];
|
| 424 |
|
| 425 |
for (const field of numericFields) {
|
| 426 |
if (parsedStats[field] !== undefined) {
|
src/lib/components/MonsterGenerator/MonsterResult.svelte
CHANGED
|
@@ -115,9 +115,8 @@
|
|
| 115 |
<h4>Battle Stats</h4>
|
| 116 |
<div class="stats-grid">
|
| 117 |
<div class="stat-item">
|
| 118 |
-
<div class="
|
| 119 |
-
<span class="stat-label">
|
| 120 |
-
<span class="stat-value" style="color: {getStatColor(workflowState.monsterStats.rarity)}">{workflowState.monsterStats.rarity}</span>
|
| 121 |
</div>
|
| 122 |
<div class="stat-item">
|
| 123 |
<div class="stat-bar" style="width: {workflowState.monsterStats.HP}%; background-color: {getStatColor(workflowState.monsterStats.HP)}20"></div>
|
|
@@ -323,6 +322,36 @@
|
|
| 323 |
z-index: 1;
|
| 324 |
}
|
| 325 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 326 |
.abilities-section {
|
| 327 |
display: grid;
|
| 328 |
gap: 1rem;
|
|
|
|
| 115 |
<h4>Battle Stats</h4>
|
| 116 |
<div class="stats-grid">
|
| 117 |
<div class="stat-item">
|
| 118 |
+
<div class="tier-badge {workflowState.monsterStats.tier}">{workflowState.monsterStats.tier.toUpperCase()}</div>
|
| 119 |
+
<span class="stat-label">Tier</span>
|
|
|
|
| 120 |
</div>
|
| 121 |
<div class="stat-item">
|
| 122 |
<div class="stat-bar" style="width: {workflowState.monsterStats.HP}%; background-color: {getStatColor(workflowState.monsterStats.HP)}20"></div>
|
|
|
|
| 322 |
z-index: 1;
|
| 323 |
}
|
| 324 |
|
| 325 |
+
.tier-badge {
|
| 326 |
+
display: inline-block;
|
| 327 |
+
padding: 0.5rem 1rem;
|
| 328 |
+
border-radius: 4px;
|
| 329 |
+
font-weight: 600;
|
| 330 |
+
font-size: 1rem;
|
| 331 |
+
margin-bottom: 0.5rem;
|
| 332 |
+
}
|
| 333 |
+
|
| 334 |
+
.tier-badge.low {
|
| 335 |
+
background: #e0e0e0;
|
| 336 |
+
color: #666;
|
| 337 |
+
}
|
| 338 |
+
|
| 339 |
+
.tier-badge.medium {
|
| 340 |
+
background: #81c784;
|
| 341 |
+
color: white;
|
| 342 |
+
}
|
| 343 |
+
|
| 344 |
+
.tier-badge.high {
|
| 345 |
+
background: #64b5f6;
|
| 346 |
+
color: white;
|
| 347 |
+
}
|
| 348 |
+
|
| 349 |
+
.tier-badge.legendary {
|
| 350 |
+
background: linear-gradient(135deg, #ab47bc, #7b1fa2);
|
| 351 |
+
color: white;
|
| 352 |
+
box-shadow: 0 2px 4px rgba(123, 31, 162, 0.3);
|
| 353 |
+
}
|
| 354 |
+
|
| 355 |
.abilities-section {
|
| 356 |
display: grid;
|
| 357 |
gap: 1rem;
|
src/lib/components/Pages/Encounters.svelte
CHANGED
|
@@ -339,7 +339,7 @@
|
|
| 339 |
isInRoster: false,
|
| 340 |
caughtAt: new Date(),
|
| 341 |
bst: baseHp + baseAttack + baseDefense + baseFieldAttack + baseFieldDefense + baseSpeed,
|
| 342 |
-
tier: stats
|
| 343 |
role: 'balanced',
|
| 344 |
variance: 0,
|
| 345 |
|
|
|
|
| 339 |
isInRoster: false,
|
| 340 |
caughtAt: new Date(),
|
| 341 |
bst: baseHp + baseAttack + baseDefense + baseFieldAttack + baseFieldDefense + baseSpeed,
|
| 342 |
+
tier: (stats as any).tier || 'medium',
|
| 343 |
role: 'balanced',
|
| 344 |
variance: 0,
|
| 345 |
|
src/lib/components/Piclets/PicletDetail.svelte
CHANGED
|
@@ -112,7 +112,7 @@
|
|
| 112 |
|
| 113 |
<!-- Compact stats for caught piclet -->
|
| 114 |
<div class="compact-info">
|
| 115 |
-
<div class="level-badge">
|
| 116 |
|
| 117 |
<div class="compact-stat-bar">
|
| 118 |
<div class="stat-bar-label">
|
|
|
|
| 112 |
|
| 113 |
<!-- Compact stats for caught piclet -->
|
| 114 |
<div class="compact-info">
|
| 115 |
+
<div class="level-badge">{instance.tier.toUpperCase()}</div>
|
| 116 |
|
| 117 |
<div class="compact-stat-bar">
|
| 118 |
<div class="stat-bar-label">
|
src/lib/db/piclets.ts
CHANGED
|
@@ -104,7 +104,7 @@ export async function monsterToPicletInstance(monster: Monster, level: number =
|
|
| 104 |
// Metadata
|
| 105 |
caughtAt: new Date(),
|
| 106 |
bst,
|
| 107 |
-
tier: stats
|
| 108 |
role: 'balanced', // Could be enhanced based on stat distribution
|
| 109 |
variance: 0,
|
| 110 |
|
|
@@ -112,7 +112,7 @@ export async function monsterToPicletInstance(monster: Monster, level: number =
|
|
| 112 |
imageUrl: monster.imageUrl,
|
| 113 |
imageData: monster.imageData,
|
| 114 |
imageCaption: monster.imageCaption,
|
| 115 |
-
concept: monster.concept,
|
| 116 |
imagePrompt: monster.imagePrompt
|
| 117 |
};
|
| 118 |
}
|
|
|
|
| 104 |
// Metadata
|
| 105 |
caughtAt: new Date(),
|
| 106 |
bst,
|
| 107 |
+
tier: (stats as any).tier || 'medium', // Use tier from stats, default to medium
|
| 108 |
role: 'balanced', // Could be enhanced based on stat distribution
|
| 109 |
variance: 0,
|
| 110 |
|
|
|
|
| 112 |
imageUrl: monster.imageUrl,
|
| 113 |
imageData: monster.imageData,
|
| 114 |
imageCaption: monster.imageCaption,
|
| 115 |
+
concept: stats.description || monster.concept, // Use the Monster Lore description
|
| 116 |
imagePrompt: monster.imagePrompt
|
| 117 |
};
|
| 118 |
}
|
src/lib/db/schema.ts
CHANGED
|
@@ -143,7 +143,7 @@ export interface Monster {
|
|
| 143 |
stats?: {
|
| 144 |
name: string;
|
| 145 |
description: string;
|
| 146 |
-
|
| 147 |
HP: number;
|
| 148 |
defence: number;
|
| 149 |
attack: number;
|
|
|
|
| 143 |
stats?: {
|
| 144 |
name: string;
|
| 145 |
description: string;
|
| 146 |
+
tier?: 'low' | 'medium' | 'high' | 'legendary';
|
| 147 |
HP: number;
|
| 148 |
defence: number;
|
| 149 |
attack: number;
|
src/lib/types/index.ts
CHANGED
|
@@ -106,7 +106,7 @@ export interface MonsterGeneratorProps {
|
|
| 106 |
export interface MonsterStats {
|
| 107 |
name: string;
|
| 108 |
description: string;
|
| 109 |
-
|
| 110 |
HP: number; // 0-100
|
| 111 |
defence: number; // 0-100
|
| 112 |
attack: number; // 0-100
|
|
|
|
| 106 |
export interface MonsterStats {
|
| 107 |
name: string;
|
| 108 |
description: string;
|
| 109 |
+
tier: 'low' | 'medium' | 'high' | 'legendary';
|
| 110 |
HP: number; // 0-100
|
| 111 |
defence: number; // 0-100
|
| 112 |
attack: number; // 0-100
|