better gen
Browse files- classes/aquatic.png +3 -0
- classes/beast.png +3 -0
- classes/bug.png +3 -0
- classes/cuisine.png +3 -0
- classes/culture.png +3 -0
- classes/flora.png +3 -0
- classes/machina.png +3 -0
- classes/mineral.png +3 -0
- classes/space.png +3 -0
- classes/structure.png +3 -0
- src/lib/components/MonsterGenerator/MonsterGenerator.svelte +6 -3
- src/lib/components/Pages/Battle.svelte +73 -25
- src/lib/components/Pages/Encounters.svelte +5 -5
- src/lib/components/Piclets/PicletCard.svelte +17 -2
- src/lib/components/UI/TypeBadge.svelte +82 -0
- src/lib/db/battleService.ts +28 -8
- src/lib/db/piclets.ts +10 -6
- src/lib/db/schema.ts +5 -3
- src/lib/services/picletMetadata.ts +2 -2
- src/lib/types/picletTypes.ts +307 -0
- src/tests/encounterService.test.ts +1 -1
classes/aquatic.png
ADDED
![]() |
Git LFS Details
|
classes/beast.png
ADDED
![]() |
Git LFS Details
|
classes/bug.png
ADDED
![]() |
Git LFS Details
|
classes/cuisine.png
ADDED
![]() |
Git LFS Details
|
classes/culture.png
ADDED
![]() |
Git LFS Details
|
classes/flora.png
ADDED
![]() |
Git LFS Details
|
classes/machina.png
ADDED
![]() |
Git LFS Details
|
classes/mineral.png
ADDED
![]() |
Git LFS Details
|
classes/space.png
ADDED
![]() |
Git LFS Details
|
classes/structure.png
ADDED
![]() |
Git LFS Details
|
src/lib/components/MonsterGenerator/MonsterGenerator.svelte
CHANGED
@@ -33,19 +33,22 @@ Guidelines:
|
|
33 |
- Add eyes (can be glowing, mechanical, multiple, etc.) positioned where they make sense
|
34 |
- Include limbs (legs, arms, wings, tentacles) that grow from or replace parts of the object
|
35 |
- Add a mouth, beak, or feeding apparatus if appropriate
|
36 |
-
- Add creature elements like tail, fins, claws,
|
37 |
|
38 |
Include:
|
39 |
- A creative name that hints at the original object
|
40 |
- Physical description showing how the object becomes a creature
|
41 |
-
- Special abilities derived from the object's function
|
42 |
- Personality traits based on the object's purpose
|
43 |
|
44 |
Format your response as:
|
45 |
\`\`\`
|
|
|
|
|
|
|
|
|
46 |
# {monster name}
|
47 |
## Monster Visual Description
|
48 |
-
|
49 |
## Monster Lore
|
50 |
...
|
51 |
\`\`\``;
|
|
|
33 |
- Add eyes (can be glowing, mechanical, multiple, etc.) positioned where they make sense
|
34 |
- Include limbs (legs, arms, wings, tentacles) that grow from or replace parts of the object
|
35 |
- Add a mouth, beak, or feeding apparatus if appropriate
|
36 |
+
- Add creature elements like tail, fins, claws, horns, etc where fitting
|
37 |
|
38 |
Include:
|
39 |
- A creative name that hints at the original object
|
40 |
- Physical description showing how the object becomes a creature
|
|
|
41 |
- Personality traits based on the object's purpose
|
42 |
|
43 |
Format your response as:
|
44 |
\`\`\`
|
45 |
+
# Object Caption
|
46 |
+
{object description, also assess how rare the object is, the rarer the object the stronger the monster}
|
47 |
+
# Transformation Brainstorm
|
48 |
+
{think through how to transform the object into a Pokémon-like creature}
|
49 |
# {monster name}
|
50 |
## Monster Visual Description
|
51 |
+
{ensure the creature uses all the unique attributes of the object}
|
52 |
## Monster Lore
|
53 |
...
|
54 |
\`\`\``;
|
src/lib/components/Pages/Battle.svelte
CHANGED
@@ -1,9 +1,11 @@
|
|
1 |
<script lang="ts">
|
2 |
import { onMount } from 'svelte';
|
3 |
import { fade } from 'svelte/transition';
|
4 |
-
import type { PicletInstance } from '$lib/db/schema';
|
5 |
import BattleField from '../Battle/BattleField.svelte';
|
6 |
import BattleControls from '../Battle/BattleControls.svelte';
|
|
|
|
|
7 |
|
8 |
export let playerPiclet: PicletInstance;
|
9 |
export let enemyPiclet: PicletInstance;
|
@@ -60,39 +62,85 @@
|
|
60 |
}
|
61 |
}
|
62 |
|
63 |
-
function handleMoveSelect(move:
|
64 |
battlePhase = 'main';
|
65 |
processingTurn = true;
|
66 |
currentMessage = `${playerPiclet.nickname} used ${move.name}!`;
|
67 |
|
68 |
-
// Mock damage
|
69 |
setTimeout(() => {
|
70 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
71 |
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
|
|
|
|
78 |
setTimeout(() => {
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
94 |
}, 1500);
|
|
|
95 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
96 |
}, 1500);
|
97 |
}
|
98 |
|
|
|
1 |
<script lang="ts">
|
2 |
import { onMount } from 'svelte';
|
3 |
import { fade } from 'svelte/transition';
|
4 |
+
import type { PicletInstance, BattleMove } from '$lib/db/schema';
|
5 |
import BattleField from '../Battle/BattleField.svelte';
|
6 |
import BattleControls from '../Battle/BattleControls.svelte';
|
7 |
+
import { BattleService } from '$lib/db/battleService';
|
8 |
+
import { getEffectivenessText, getEffectivenessColor } from '$lib/types/picletTypes';
|
9 |
|
10 |
export let playerPiclet: PicletInstance;
|
11 |
export let enemyPiclet: PicletInstance;
|
|
|
62 |
}
|
63 |
}
|
64 |
|
65 |
+
function handleMoveSelect(move: BattleMove) {
|
66 |
battlePhase = 'main';
|
67 |
processingTurn = true;
|
68 |
currentMessage = `${playerPiclet.nickname} used ${move.name}!`;
|
69 |
|
|
|
70 |
setTimeout(() => {
|
71 |
+
if (!BattleService.doesMoveHit(move.accuracy)) {
|
72 |
+
currentMessage = `${playerPiclet.nickname}'s attack missed!`;
|
73 |
+
setTimeout(() => enemyTurn(), 1500);
|
74 |
+
return;
|
75 |
+
}
|
76 |
+
|
77 |
+
// Calculate damage with type effectiveness
|
78 |
+
const { damage, effectiveness } = BattleService.calculateDamage(playerPiclet, enemyPiclet, move);
|
79 |
|
80 |
+
// Update enemy HP
|
81 |
+
const newHp = Math.max(0, enemyPiclet.currentHp - damage);
|
82 |
+
enemyPiclet.currentHp = newHp;
|
83 |
+
enemyHpPercentage = newHp / enemyPiclet.maxHp;
|
84 |
+
|
85 |
+
// Show effectiveness message if applicable
|
86 |
+
const effectivenessMessage = getEffectivenessText(effectiveness);
|
87 |
+
if (effectivenessMessage) {
|
88 |
setTimeout(() => {
|
89 |
+
currentMessage = effectivenessMessage;
|
90 |
+
}, 1000);
|
91 |
+
}
|
92 |
+
|
93 |
+
setTimeout(() => {
|
94 |
+
if (enemyHpPercentage <= 0) {
|
95 |
+
currentMessage = `${enemyPiclet.nickname} fainted!`;
|
96 |
+
battleEnded = true;
|
97 |
+
setTimeout(() => onBattleEnd(true), 2000);
|
98 |
+
} else {
|
99 |
+
enemyTurn();
|
100 |
+
}
|
101 |
+
}, effectivenessMessage ? 2500 : 1500);
|
102 |
+
}, 1500);
|
103 |
+
}
|
104 |
+
|
105 |
+
function enemyTurn() {
|
106 |
+
const enemyMove = enemyPiclet.moves[Math.floor(Math.random() * enemyPiclet.moves.length)];
|
107 |
+
currentMessage = `${enemyPiclet.nickname} used ${enemyMove.name}!`;
|
108 |
+
|
109 |
+
setTimeout(() => {
|
110 |
+
if (!BattleService.doesMoveHit(enemyMove.accuracy)) {
|
111 |
+
currentMessage = `${enemyPiclet.nickname}'s attack missed!`;
|
112 |
+
setTimeout(() => {
|
113 |
+
currentMessage = `What will ${playerPiclet.nickname} do?`;
|
114 |
+
processingTurn = false;
|
115 |
}, 1500);
|
116 |
+
return;
|
117 |
}
|
118 |
+
|
119 |
+
const { damage, effectiveness } = BattleService.calculateDamage(enemyPiclet, playerPiclet, enemyMove);
|
120 |
+
|
121 |
+
// Update player HP
|
122 |
+
const newHp = Math.max(0, playerPiclet.currentHp - damage);
|
123 |
+
playerPiclet.currentHp = newHp;
|
124 |
+
playerHpPercentage = newHp / playerPiclet.maxHp;
|
125 |
+
|
126 |
+
// Show effectiveness message if applicable
|
127 |
+
const effectivenessMessage = getEffectivenessText(effectiveness);
|
128 |
+
if (effectivenessMessage) {
|
129 |
+
setTimeout(() => {
|
130 |
+
currentMessage = effectivenessMessage;
|
131 |
+
}, 1000);
|
132 |
+
}
|
133 |
+
|
134 |
+
setTimeout(() => {
|
135 |
+
if (playerHpPercentage <= 0) {
|
136 |
+
currentMessage = `${playerPiclet.nickname} fainted!`;
|
137 |
+
battleEnded = true;
|
138 |
+
setTimeout(() => onBattleEnd(false), 2000);
|
139 |
+
} else {
|
140 |
+
currentMessage = `What will ${playerPiclet.nickname} do?`;
|
141 |
+
processingTurn = false;
|
142 |
+
}
|
143 |
+
}, effectivenessMessage ? 2500 : 1500);
|
144 |
}, 1500);
|
145 |
}
|
146 |
|
src/lib/components/Pages/Encounters.svelte
CHANGED
@@ -277,7 +277,7 @@
|
|
277 |
id: -1, // Temporary ID for enemy
|
278 |
typeId: encounter.picletTypeId,
|
279 |
nickname: monster.name,
|
280 |
-
|
281 |
|
282 |
level: level,
|
283 |
xp: 0,
|
@@ -299,7 +299,7 @@
|
|
299 |
moves: [
|
300 |
{
|
301 |
name: stats.attackActionName,
|
302 |
-
type: 'normal',
|
303 |
power: 50,
|
304 |
accuracy: 95,
|
305 |
pp: 20,
|
@@ -308,7 +308,7 @@
|
|
308 |
},
|
309 |
{
|
310 |
name: stats.buffActionName,
|
311 |
-
type: '
|
312 |
power: 0,
|
313 |
accuracy: 100,
|
314 |
pp: 15,
|
@@ -317,7 +317,7 @@
|
|
317 |
},
|
318 |
{
|
319 |
name: stats.debuffActionName,
|
320 |
-
type: '
|
321 |
power: 0,
|
322 |
accuracy: 85,
|
323 |
pp: 15,
|
@@ -326,7 +326,7 @@
|
|
326 |
},
|
327 |
{
|
328 |
name: stats.specialActionName,
|
329 |
-
type: '
|
330 |
power: 80,
|
331 |
accuracy: 90,
|
332 |
pp: 5,
|
|
|
277 |
id: -1, // Temporary ID for enemy
|
278 |
typeId: encounter.picletTypeId,
|
279 |
nickname: monster.name,
|
280 |
+
primaryType: 'beast' as any, // Default type
|
281 |
|
282 |
level: level,
|
283 |
xp: 0,
|
|
|
299 |
moves: [
|
300 |
{
|
301 |
name: stats.attackActionName,
|
302 |
+
type: 'normal' as any,
|
303 |
power: 50,
|
304 |
accuracy: 95,
|
305 |
pp: 20,
|
|
|
308 |
},
|
309 |
{
|
310 |
name: stats.buffActionName,
|
311 |
+
type: 'normal' as any,
|
312 |
power: 0,
|
313 |
accuracy: 100,
|
314 |
pp: 15,
|
|
|
317 |
},
|
318 |
{
|
319 |
name: stats.debuffActionName,
|
320 |
+
type: 'normal' as any,
|
321 |
power: 0,
|
322 |
accuracy: 85,
|
323 |
pp: 15,
|
|
|
326 |
},
|
327 |
{
|
328 |
name: stats.specialActionName,
|
329 |
+
type: 'normal' as any,
|
330 |
power: 80,
|
331 |
accuracy: 90,
|
332 |
pp: 5,
|
src/lib/components/Piclets/PicletCard.svelte
CHANGED
@@ -1,5 +1,7 @@
|
|
1 |
<script lang="ts">
|
2 |
import type { PicletInstance } from '$lib/db/schema';
|
|
|
|
|
3 |
|
4 |
interface Props {
|
5 |
instance: PicletInstance;
|
@@ -17,8 +19,7 @@
|
|
17 |
'#ff3b30'
|
18 |
);
|
19 |
|
20 |
-
|
21 |
-
const typeColor = '#007bff';
|
22 |
</script>
|
23 |
|
24 |
<button
|
@@ -37,6 +38,12 @@
|
|
37 |
<div class="level-badge">
|
38 |
Lv.{instance.level}
|
39 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
40 |
</div>
|
41 |
|
42 |
{#if showDetails}
|
@@ -96,6 +103,14 @@
|
|
96 |
font-weight: bold;
|
97 |
}
|
98 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
99 |
.details-section {
|
100 |
height: 40px;
|
101 |
padding: 4px 6px;
|
|
|
1 |
<script lang="ts">
|
2 |
import type { PicletInstance } from '$lib/db/schema';
|
3 |
+
import { TYPE_DATA } from '$lib/types/picletTypes';
|
4 |
+
import TypeBadge from '$lib/components/UI/TypeBadge.svelte';
|
5 |
|
6 |
interface Props {
|
7 |
instance: PicletInstance;
|
|
|
19 |
'#ff3b30'
|
20 |
);
|
21 |
|
22 |
+
const typeColor = $derived(TYPE_DATA[instance.primaryType].color);
|
|
|
23 |
</script>
|
24 |
|
25 |
<button
|
|
|
38 |
<div class="level-badge">
|
39 |
Lv.{instance.level}
|
40 |
</div>
|
41 |
+
<div class="type-badge-container">
|
42 |
+
<TypeBadge type={instance.primaryType} size="small" />
|
43 |
+
{#if instance.secondaryType}
|
44 |
+
<TypeBadge type={instance.secondaryType} size="small" />
|
45 |
+
{/if}
|
46 |
+
</div>
|
47 |
</div>
|
48 |
|
49 |
{#if showDetails}
|
|
|
103 |
font-weight: bold;
|
104 |
}
|
105 |
|
106 |
+
.type-badge-container {
|
107 |
+
position: absolute;
|
108 |
+
bottom: 4px;
|
109 |
+
left: 4px;
|
110 |
+
display: flex;
|
111 |
+
gap: 2px;
|
112 |
+
}
|
113 |
+
|
114 |
.details-section {
|
115 |
height: 40px;
|
116 |
padding: 4px 6px;
|
src/lib/components/UI/TypeBadge.svelte
ADDED
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
import type { PicletType } from '$lib/types/picletTypes';
|
3 |
+
import { TYPE_DATA } from '$lib/types/picletTypes';
|
4 |
+
|
5 |
+
interface Props {
|
6 |
+
type: PicletType;
|
7 |
+
size?: 'small' | 'medium' | 'large';
|
8 |
+
showIcon?: boolean;
|
9 |
+
showLabel?: boolean;
|
10 |
+
}
|
11 |
+
|
12 |
+
let { type, size = 'medium', showIcon = true, showLabel = false }: Props = $props();
|
13 |
+
|
14 |
+
const typeInfo = $derived(TYPE_DATA[type]);
|
15 |
+
const sizeClass = $derived(`size-${size}`);
|
16 |
+
</script>
|
17 |
+
|
18 |
+
<div class="type-badge {sizeClass}" style="--type-color: {typeInfo.color}">
|
19 |
+
{#if showIcon}
|
20 |
+
<span class="type-icon">{typeInfo.icon}</span>
|
21 |
+
{/if}
|
22 |
+
{#if showLabel}
|
23 |
+
<span class="type-label">{typeInfo.name}</span>
|
24 |
+
{/if}
|
25 |
+
</div>
|
26 |
+
|
27 |
+
<style>
|
28 |
+
.type-badge {
|
29 |
+
display: inline-flex;
|
30 |
+
align-items: center;
|
31 |
+
gap: 2px;
|
32 |
+
background-color: var(--type-color);
|
33 |
+
color: white;
|
34 |
+
border-radius: 12px;
|
35 |
+
font-weight: 600;
|
36 |
+
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
|
37 |
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
38 |
+
border: 1px solid color-mix(in srgb, var(--type-color) 80%, black);
|
39 |
+
}
|
40 |
+
|
41 |
+
.size-small {
|
42 |
+
padding: 2px 4px;
|
43 |
+
font-size: 10px;
|
44 |
+
border-radius: 8px;
|
45 |
+
}
|
46 |
+
|
47 |
+
.size-medium {
|
48 |
+
padding: 3px 6px;
|
49 |
+
font-size: 11px;
|
50 |
+
border-radius: 10px;
|
51 |
+
}
|
52 |
+
|
53 |
+
.size-large {
|
54 |
+
padding: 4px 8px;
|
55 |
+
font-size: 12px;
|
56 |
+
border-radius: 12px;
|
57 |
+
}
|
58 |
+
|
59 |
+
.type-icon {
|
60 |
+
line-height: 1;
|
61 |
+
filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.5));
|
62 |
+
}
|
63 |
+
|
64 |
+
.type-label {
|
65 |
+
line-height: 1;
|
66 |
+
font-weight: 700;
|
67 |
+
text-transform: uppercase;
|
68 |
+
letter-spacing: 0.5px;
|
69 |
+
}
|
70 |
+
|
71 |
+
.size-small .type-icon {
|
72 |
+
font-size: 8px;
|
73 |
+
}
|
74 |
+
|
75 |
+
.size-medium .type-icon {
|
76 |
+
font-size: 10px;
|
77 |
+
}
|
78 |
+
|
79 |
+
.size-large .type-icon {
|
80 |
+
font-size: 12px;
|
81 |
+
}
|
82 |
+
</style>
|
src/lib/db/battleService.ts
CHANGED
@@ -1,5 +1,6 @@
|
|
1 |
-
import type { PicletInstance, BattleState, BattlePhase } from './schema';
|
2 |
import { db } from './index';
|
|
|
3 |
|
4 |
export class BattleService {
|
5 |
// Initialize a new battle
|
@@ -19,23 +20,42 @@ export class BattleService {
|
|
19 |
};
|
20 |
}
|
21 |
|
22 |
-
// Calculate damage
|
23 |
static calculateDamage(
|
24 |
attacker: PicletInstance,
|
25 |
defender: PicletInstance,
|
26 |
-
move:
|
27 |
-
): number {
|
28 |
const baseDamage = move.power || 50;
|
29 |
-
const attackStat = move.
|
30 |
-
const defenseStat = move.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
31 |
|
32 |
// Simple damage formula
|
33 |
-
|
|
|
|
|
|
|
34 |
|
35 |
// Add some randomness (85-100% of calculated damage)
|
36 |
const randomFactor = 0.85 + Math.random() * 0.15;
|
|
|
|
|
|
|
|
|
|
|
|
|
37 |
|
38 |
-
return
|
39 |
}
|
40 |
|
41 |
// Check if move hits (based on accuracy)
|
|
|
1 |
+
import type { PicletInstance, BattleState, BattlePhase, BattleMove } from './schema';
|
2 |
import { db } from './index';
|
3 |
+
import { getEffectivenessMultiplier, AttackType } from '../types/picletTypes';
|
4 |
|
5 |
export class BattleService {
|
6 |
// Initialize a new battle
|
|
|
20 |
};
|
21 |
}
|
22 |
|
23 |
+
// Calculate damage with type effectiveness
|
24 |
static calculateDamage(
|
25 |
attacker: PicletInstance,
|
26 |
defender: PicletInstance,
|
27 |
+
move: BattleMove
|
28 |
+
): { damage: number; effectiveness: number } {
|
29 |
const baseDamage = move.power || 50;
|
30 |
+
const attackStat = move.power > 0 ? attacker.attack : attacker.fieldAttack;
|
31 |
+
const defenseStat = move.power > 0 ? defender.defense : defender.fieldDefense;
|
32 |
+
|
33 |
+
// Type effectiveness
|
34 |
+
const effectiveness = getEffectivenessMultiplier(
|
35 |
+
move.type,
|
36 |
+
defender.primaryType,
|
37 |
+
defender.secondaryType
|
38 |
+
);
|
39 |
+
|
40 |
+
// STAB (Same Type Attack Bonus) - 1.5x if move type matches attacker's type
|
41 |
+
const stab = (move.type === attacker.primaryType as unknown as AttackType || move.type === attacker.secondaryType as unknown as AttackType) ? 1.5 : 1;
|
42 |
|
43 |
// Simple damage formula
|
44 |
+
let damage = Math.floor((baseDamage * (attackStat / defenseStat) * 0.5) + 10);
|
45 |
+
|
46 |
+
// Apply type effectiveness and STAB
|
47 |
+
damage = Math.floor(damage * effectiveness * stab);
|
48 |
|
49 |
// Add some randomness (85-100% of calculated damage)
|
50 |
const randomFactor = 0.85 + Math.random() * 0.15;
|
51 |
+
damage = Math.floor(damage * randomFactor);
|
52 |
+
|
53 |
+
// Minimum 1 damage for non-immune moves
|
54 |
+
if (effectiveness > 0 && damage < 1) {
|
55 |
+
damage = 1;
|
56 |
+
}
|
57 |
|
58 |
+
return { damage, effectiveness };
|
59 |
}
|
60 |
|
61 |
// Check if move hits (based on accuracy)
|
src/lib/db/piclets.ts
CHANGED
@@ -1,5 +1,6 @@
|
|
1 |
import { db } from './index';
|
2 |
import type { PicletInstance, Monster, BattleMove } from './schema';
|
|
|
3 |
|
4 |
// Convert a generated Monster to a PicletInstance
|
5 |
export async function monsterToPicletInstance(monster: Monster, level: number = 5): Promise<Omit<PicletInstance, 'id'>> {
|
@@ -25,11 +26,14 @@ export async function monsterToPicletInstance(monster: Monster, level: number =
|
|
25 |
|
26 |
const maxHp = calculateHp(baseHp, level);
|
27 |
|
|
|
|
|
|
|
28 |
// Create moves based on monster's abilities
|
29 |
const moves: BattleMove[] = [
|
30 |
{
|
31 |
name: stats.attackActionName,
|
32 |
-
type:
|
33 |
power: 50,
|
34 |
accuracy: 95,
|
35 |
pp: 20,
|
@@ -38,7 +42,7 @@ export async function monsterToPicletInstance(monster: Monster, level: number =
|
|
38 |
},
|
39 |
{
|
40 |
name: stats.buffActionName,
|
41 |
-
type:
|
42 |
power: 0,
|
43 |
accuracy: 100,
|
44 |
pp: 15,
|
@@ -47,7 +51,7 @@ export async function monsterToPicletInstance(monster: Monster, level: number =
|
|
47 |
},
|
48 |
{
|
49 |
name: stats.debuffActionName,
|
50 |
-
type:
|
51 |
power: 0,
|
52 |
accuracy: 85,
|
53 |
pp: 15,
|
@@ -56,7 +60,7 @@ export async function monsterToPicletInstance(monster: Monster, level: number =
|
|
56 |
},
|
57 |
{
|
58 |
name: stats.specialActionName,
|
59 |
-
type:
|
60 |
power: 80,
|
61 |
accuracy: 90,
|
62 |
pp: 5,
|
@@ -71,8 +75,8 @@ export async function monsterToPicletInstance(monster: Monster, level: number =
|
|
71 |
// Type Info
|
72 |
typeId: monster.name.toLowerCase().replace(/\s+/g, '-'),
|
73 |
nickname: monster.name,
|
74 |
-
|
75 |
-
|
76 |
|
77 |
// Current Stats
|
78 |
currentHp: maxHp,
|
|
|
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'>> {
|
|
|
26 |
|
27 |
const maxHp = calculateHp(baseHp, level);
|
28 |
|
29 |
+
// Determine primary type based on concept and caption
|
30 |
+
const primaryType = getTypeFromConcept(monster.concept, monster.imageCaption);
|
31 |
+
|
32 |
// Create moves based on monster's abilities
|
33 |
const moves: BattleMove[] = [
|
34 |
{
|
35 |
name: stats.attackActionName,
|
36 |
+
type: primaryType as unknown as AttackType,
|
37 |
power: 50,
|
38 |
accuracy: 95,
|
39 |
pp: 20,
|
|
|
42 |
},
|
43 |
{
|
44 |
name: stats.buffActionName,
|
45 |
+
type: AttackType.NORMAL,
|
46 |
power: 0,
|
47 |
accuracy: 100,
|
48 |
pp: 15,
|
|
|
51 |
},
|
52 |
{
|
53 |
name: stats.debuffActionName,
|
54 |
+
type: AttackType.NORMAL,
|
55 |
power: 0,
|
56 |
accuracy: 85,
|
57 |
pp: 15,
|
|
|
60 |
},
|
61 |
{
|
62 |
name: stats.specialActionName,
|
63 |
+
type: primaryType as unknown as AttackType,
|
64 |
power: 80,
|
65 |
accuracy: 90,
|
66 |
pp: 5,
|
|
|
75 |
// Type Info
|
76 |
typeId: monster.name.toLowerCase().replace(/\s+/g, '-'),
|
77 |
nickname: monster.name,
|
78 |
+
primaryType: primaryType,
|
79 |
+
secondaryType: undefined,
|
80 |
|
81 |
// Current Stats
|
82 |
currentHp: maxHp,
|
src/lib/db/schema.ts
CHANGED
@@ -1,3 +1,5 @@
|
|
|
|
|
|
1 |
// Enums
|
2 |
export enum EncounterType {
|
3 |
WILD_PICLET = 'wildPiclet',
|
@@ -9,7 +11,7 @@ export enum EncounterType {
|
|
9 |
// Battle Move embedded object
|
10 |
export interface BattleMove {
|
11 |
name: string;
|
12 |
-
type:
|
13 |
power: number;
|
14 |
accuracy: number;
|
15 |
pp: number;
|
@@ -24,8 +26,8 @@ export interface PicletInstance {
|
|
24 |
// Type Info
|
25 |
typeId: string;
|
26 |
nickname?: string;
|
27 |
-
|
28 |
-
|
29 |
|
30 |
// Current Stats
|
31 |
currentHp: number;
|
|
|
1 |
+
import type { PicletType, AttackType } from '../types/picletTypes';
|
2 |
+
|
3 |
// Enums
|
4 |
export enum EncounterType {
|
5 |
WILD_PICLET = 'wildPiclet',
|
|
|
11 |
// Battle Move embedded object
|
12 |
export interface BattleMove {
|
13 |
name: string;
|
14 |
+
type: AttackType;
|
15 |
power: number;
|
16 |
accuracy: number;
|
17 |
pp: number;
|
|
|
26 |
// Type Info
|
27 |
typeId: string;
|
28 |
nickname?: string;
|
29 |
+
primaryType: PicletType;
|
30 |
+
secondaryType?: PicletType;
|
31 |
|
32 |
// Current Stats
|
33 |
currentHp: number;
|
src/lib/services/picletMetadata.ts
CHANGED
@@ -69,8 +69,8 @@ export async function embedPicletMetadata(imageBlob: Blob, piclet: PicletInstanc
|
|
69 |
data: {
|
70 |
typeId: piclet.typeId,
|
71 |
nickname: piclet.nickname,
|
72 |
-
|
73 |
-
|
74 |
currentHp: piclet.maxHp, // Reset to full HP for sharing
|
75 |
maxHp: piclet.maxHp,
|
76 |
level: piclet.level,
|
|
|
69 |
data: {
|
70 |
typeId: piclet.typeId,
|
71 |
nickname: piclet.nickname,
|
72 |
+
primaryType: piclet.primaryType,
|
73 |
+
secondaryType: piclet.secondaryType,
|
74 |
currentHp: piclet.maxHp, // Reset to full HP for sharing
|
75 |
maxHp: piclet.maxHp,
|
76 |
level: piclet.level,
|
src/lib/types/picletTypes.ts
ADDED
@@ -0,0 +1,307 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
export enum PicletType {
|
2 |
+
BEAST = 'beast',
|
3 |
+
BUG = 'bug',
|
4 |
+
AQUATIC = 'aquatic',
|
5 |
+
FLORA = 'flora',
|
6 |
+
MINERAL = 'mineral',
|
7 |
+
SPACE = 'space',
|
8 |
+
MACHINA = 'machina',
|
9 |
+
STRUCTURE = 'structure',
|
10 |
+
CULTURE = 'culture',
|
11 |
+
CUISINE = 'cuisine'
|
12 |
+
}
|
13 |
+
|
14 |
+
export interface TypeInfo {
|
15 |
+
name: string;
|
16 |
+
icon: string;
|
17 |
+
color: string;
|
18 |
+
description: string;
|
19 |
+
}
|
20 |
+
|
21 |
+
export const TYPE_DATA: Record<PicletType, TypeInfo> = {
|
22 |
+
[PicletType.BEAST]: {
|
23 |
+
name: 'Beast',
|
24 |
+
icon: '🐾',
|
25 |
+
color: '#8B4513',
|
26 |
+
description: 'Vertebrate wildlife — mammals, birds, reptiles. Brings raw physicality, instincts, and region-based variants.'
|
27 |
+
},
|
28 |
+
[PicletType.BUG]: {
|
29 |
+
name: 'Bug',
|
30 |
+
icon: '🐛',
|
31 |
+
color: '#9ACD32',
|
32 |
+
description: 'Arthropods great and small: butterflies, beetles, mantises. Agile swarms, precision strikes, and metamorph-style evolutions.'
|
33 |
+
},
|
34 |
+
[PicletType.AQUATIC]: {
|
35 |
+
name: 'Aquatic',
|
36 |
+
icon: '🌊',
|
37 |
+
color: '#1E90FF',
|
38 |
+
description: 'Life that swims, dives, sloshes, or seeps: fish, octopus, ink-creatures, sentient puddles. Masters of tides, mist, and pressure.'
|
39 |
+
},
|
40 |
+
[PicletType.FLORA]: {
|
41 |
+
name: 'Flora',
|
42 |
+
icon: '🌿',
|
43 |
+
color: '#228B22',
|
44 |
+
description: 'Plants and fungi captured in bloom or decay. Harness growth, spores, vines, and seasonal shifts to entangle foes or heal allies.'
|
45 |
+
},
|
46 |
+
[PicletType.MINERAL]: {
|
47 |
+
name: 'Mineral',
|
48 |
+
icon: '🪨',
|
49 |
+
color: '#696969',
|
50 |
+
description: 'Stones, crystals, and metals shaped by earth\'s depths. High durability, reflective armor, seismic shocks, and gem-beam attacks.'
|
51 |
+
},
|
52 |
+
[PicletType.SPACE]: {
|
53 |
+
name: 'Space',
|
54 |
+
icon: '✨',
|
55 |
+
color: '#4B0082',
|
56 |
+
description: 'Stars, moon, and other cosmic objects not on this world.'
|
57 |
+
},
|
58 |
+
[PicletType.MACHINA]: {
|
59 |
+
name: 'Machina',
|
60 |
+
icon: '⚙️',
|
61 |
+
color: '#C0C0C0',
|
62 |
+
description: 'Engineered devices from pocket gadgets to heavy machinery. Deploy gears, circuits, drones, and overclocked power surges.'
|
63 |
+
},
|
64 |
+
[PicletType.STRUCTURE]: {
|
65 |
+
name: 'Structure',
|
66 |
+
icon: '🏛️',
|
67 |
+
color: '#A0522D',
|
68 |
+
description: 'Buildings, bridges, monuments, and ruins re-imagined as titans. Excel at fortification, terrain shaping, and zone denial.'
|
69 |
+
},
|
70 |
+
[PicletType.CULTURE]: {
|
71 |
+
name: 'Culture',
|
72 |
+
icon: '🎨',
|
73 |
+
color: '#FF69B4',
|
74 |
+
description: 'Art, fashion, toys, and written symbols. Specialises in buffs, debuffs, illusion, and story-driven move interactions.'
|
75 |
+
},
|
76 |
+
[PicletType.CUISINE]: {
|
77 |
+
name: 'Cuisine',
|
78 |
+
icon: '🍣',
|
79 |
+
color: '#FF6347',
|
80 |
+
description: 'Dishes, drinks, and culinary artistry. Uses flavours, aromas, and temperature shifts for restorative support or spicy offense.'
|
81 |
+
}
|
82 |
+
};
|
83 |
+
|
84 |
+
export enum AttackType {
|
85 |
+
NORMAL = 'normal',
|
86 |
+
BEAST = 'beast',
|
87 |
+
BUG = 'bug',
|
88 |
+
AQUATIC = 'aquatic',
|
89 |
+
FLORA = 'flora',
|
90 |
+
MINERAL = 'mineral',
|
91 |
+
SPACE = 'space',
|
92 |
+
MACHINA = 'machina',
|
93 |
+
STRUCTURE = 'structure',
|
94 |
+
CULTURE = 'culture',
|
95 |
+
CUISINE = 'cuisine'
|
96 |
+
}
|
97 |
+
|
98 |
+
export type TypeEffectiveness = 0 | 0.5 | 1 | 2;
|
99 |
+
|
100 |
+
export const TYPE_EFFECTIVENESS: Record<AttackType, Record<PicletType, TypeEffectiveness>> = {
|
101 |
+
[AttackType.NORMAL]: {
|
102 |
+
[PicletType.BEAST]: 1,
|
103 |
+
[PicletType.BUG]: 1,
|
104 |
+
[PicletType.AQUATIC]: 1,
|
105 |
+
[PicletType.FLORA]: 1,
|
106 |
+
[PicletType.MINERAL]: 1,
|
107 |
+
[PicletType.SPACE]: 0,
|
108 |
+
[PicletType.MACHINA]: 1,
|
109 |
+
[PicletType.STRUCTURE]: 1,
|
110 |
+
[PicletType.CULTURE]: 1,
|
111 |
+
[PicletType.CUISINE]: 1
|
112 |
+
},
|
113 |
+
[AttackType.BEAST]: {
|
114 |
+
[PicletType.BEAST]: 1,
|
115 |
+
[PicletType.BUG]: 2,
|
116 |
+
[PicletType.AQUATIC]: 1,
|
117 |
+
[PicletType.FLORA]: 1,
|
118 |
+
[PicletType.MINERAL]: 0.5,
|
119 |
+
[PicletType.SPACE]: 0,
|
120 |
+
[PicletType.MACHINA]: 0.5,
|
121 |
+
[PicletType.STRUCTURE]: 0.5,
|
122 |
+
[PicletType.CULTURE]: 2,
|
123 |
+
[PicletType.CUISINE]: 2
|
124 |
+
},
|
125 |
+
[AttackType.BUG]: {
|
126 |
+
[PicletType.BEAST]: 2,
|
127 |
+
[PicletType.BUG]: 1,
|
128 |
+
[PicletType.AQUATIC]: 1,
|
129 |
+
[PicletType.FLORA]: 2,
|
130 |
+
[PicletType.MINERAL]: 0.5,
|
131 |
+
[PicletType.SPACE]: 0.5,
|
132 |
+
[PicletType.MACHINA]: 1,
|
133 |
+
[PicletType.STRUCTURE]: 0,
|
134 |
+
[PicletType.CULTURE]: 0.5,
|
135 |
+
[PicletType.CUISINE]: 0.5
|
136 |
+
},
|
137 |
+
[AttackType.AQUATIC]: {
|
138 |
+
[PicletType.BEAST]: 1,
|
139 |
+
[PicletType.BUG]: 1,
|
140 |
+
[PicletType.AQUATIC]: 1,
|
141 |
+
[PicletType.FLORA]: 0.5,
|
142 |
+
[PicletType.MINERAL]: 2,
|
143 |
+
[PicletType.SPACE]: 2,
|
144 |
+
[PicletType.MACHINA]: 2,
|
145 |
+
[PicletType.STRUCTURE]: 1,
|
146 |
+
[PicletType.CULTURE]: 0.5,
|
147 |
+
[PicletType.CUISINE]: 0.5
|
148 |
+
},
|
149 |
+
[AttackType.FLORA]: {
|
150 |
+
[PicletType.BEAST]: 1,
|
151 |
+
[PicletType.BUG]: 2,
|
152 |
+
[PicletType.AQUATIC]: 2,
|
153 |
+
[PicletType.FLORA]: 1,
|
154 |
+
[PicletType.MINERAL]: 2,
|
155 |
+
[PicletType.SPACE]: 0.5,
|
156 |
+
[PicletType.MACHINA]: 0,
|
157 |
+
[PicletType.STRUCTURE]: 2,
|
158 |
+
[PicletType.CULTURE]: 1,
|
159 |
+
[PicletType.CUISINE]: 0.5
|
160 |
+
},
|
161 |
+
[AttackType.MINERAL]: {
|
162 |
+
[PicletType.BEAST]: 2,
|
163 |
+
[PicletType.BUG]: 2,
|
164 |
+
[PicletType.AQUATIC]: 0.5,
|
165 |
+
[PicletType.FLORA]: 0.5,
|
166 |
+
[PicletType.MINERAL]: 1,
|
167 |
+
[PicletType.SPACE]: 0.5,
|
168 |
+
[PicletType.MACHINA]: 2,
|
169 |
+
[PicletType.STRUCTURE]: 1,
|
170 |
+
[PicletType.CULTURE]: 1,
|
171 |
+
[PicletType.CUISINE]: 0
|
172 |
+
},
|
173 |
+
[AttackType.SPACE]: {
|
174 |
+
[PicletType.BEAST]: 0,
|
175 |
+
[PicletType.BUG]: 2,
|
176 |
+
[PicletType.AQUATIC]: 0.5,
|
177 |
+
[PicletType.FLORA]: 2,
|
178 |
+
[PicletType.MINERAL]: 2,
|
179 |
+
[PicletType.SPACE]: 1,
|
180 |
+
[PicletType.MACHINA]: 0.5,
|
181 |
+
[PicletType.STRUCTURE]: 2,
|
182 |
+
[PicletType.CULTURE]: 0.5,
|
183 |
+
[PicletType.CUISINE]: 0.5
|
184 |
+
},
|
185 |
+
[AttackType.MACHINA]: {
|
186 |
+
[PicletType.BEAST]: 2,
|
187 |
+
[PicletType.BUG]: 0.5,
|
188 |
+
[PicletType.AQUATIC]: 0.5,
|
189 |
+
[PicletType.FLORA]: 2,
|
190 |
+
[PicletType.MINERAL]: 0.5,
|
191 |
+
[PicletType.SPACE]: 0.5,
|
192 |
+
[PicletType.MACHINA]: 1,
|
193 |
+
[PicletType.STRUCTURE]: 2,
|
194 |
+
[PicletType.CULTURE]: 1,
|
195 |
+
[PicletType.CUISINE]: 1
|
196 |
+
},
|
197 |
+
[AttackType.STRUCTURE]: {
|
198 |
+
[PicletType.BEAST]: 0.5,
|
199 |
+
[PicletType.BUG]: 0.5,
|
200 |
+
[PicletType.AQUATIC]: 1,
|
201 |
+
[PicletType.FLORA]: 1,
|
202 |
+
[PicletType.MINERAL]: 1,
|
203 |
+
[PicletType.SPACE]: 0.5,
|
204 |
+
[PicletType.MACHINA]: 2,
|
205 |
+
[PicletType.STRUCTURE]: 1,
|
206 |
+
[PicletType.CULTURE]: 2,
|
207 |
+
[PicletType.CUISINE]: 2
|
208 |
+
},
|
209 |
+
[AttackType.CULTURE]: {
|
210 |
+
[PicletType.BEAST]: 0.5,
|
211 |
+
[PicletType.BUG]: 0.5,
|
212 |
+
[PicletType.AQUATIC]: 1,
|
213 |
+
[PicletType.FLORA]: 1,
|
214 |
+
[PicletType.MINERAL]: 0,
|
215 |
+
[PicletType.SPACE]: 2,
|
216 |
+
[PicletType.MACHINA]: 2,
|
217 |
+
[PicletType.STRUCTURE]: 2,
|
218 |
+
[PicletType.CULTURE]: 1,
|
219 |
+
[PicletType.CUISINE]: 0.5
|
220 |
+
},
|
221 |
+
[AttackType.CUISINE]: {
|
222 |
+
[PicletType.BEAST]: 2,
|
223 |
+
[PicletType.BUG]: 0.5,
|
224 |
+
[PicletType.AQUATIC]: 0.5,
|
225 |
+
[PicletType.FLORA]: 1,
|
226 |
+
[PicletType.MINERAL]: 0,
|
227 |
+
[PicletType.SPACE]: 2,
|
228 |
+
[PicletType.MACHINA]: 1,
|
229 |
+
[PicletType.STRUCTURE]: 0.5,
|
230 |
+
[PicletType.CULTURE]: 2,
|
231 |
+
[PicletType.CUISINE]: 1
|
232 |
+
}
|
233 |
+
};
|
234 |
+
|
235 |
+
export function getTypeEffectiveness(attackType: AttackType, defenseType: PicletType): TypeEffectiveness {
|
236 |
+
return TYPE_EFFECTIVENESS[attackType][defenseType];
|
237 |
+
}
|
238 |
+
|
239 |
+
export function getEffectivenessMultiplier(attackType: AttackType, defenseType: PicletType, secondaryType?: PicletType): number {
|
240 |
+
let multiplier = getTypeEffectiveness(attackType, defenseType);
|
241 |
+
|
242 |
+
if (secondaryType && secondaryType !== defenseType) {
|
243 |
+
multiplier *= getTypeEffectiveness(attackType, secondaryType);
|
244 |
+
}
|
245 |
+
|
246 |
+
return multiplier;
|
247 |
+
}
|
248 |
+
|
249 |
+
export function getEffectivenessText(effectiveness: number): string {
|
250 |
+
if (effectiveness === 0) return "It had no effect!";
|
251 |
+
if (effectiveness > 1) return "It's super effective!";
|
252 |
+
if (effectiveness < 1) return "It's not very effective...";
|
253 |
+
return "";
|
254 |
+
}
|
255 |
+
|
256 |
+
export function getEffectivenessColor(effectiveness: number): string {
|
257 |
+
if (effectiveness === 0) return "#666666";
|
258 |
+
if (effectiveness > 1) return "#4CAF50";
|
259 |
+
if (effectiveness < 1) return "#FF6B6B";
|
260 |
+
return "#333333";
|
261 |
+
}
|
262 |
+
|
263 |
+
export function getTypeFromConcept(concept: string, imageCaption?: string): PicletType {
|
264 |
+
const text = `${concept} ${imageCaption || ''}`.toLowerCase();
|
265 |
+
|
266 |
+
if (text.includes('beast') || text.includes('animal') || text.includes('mammal') || text.includes('bird') || text.includes('reptile') || text.includes('wolf') || text.includes('cat') || text.includes('dog') || text.includes('tiger') || text.includes('lion') || text.includes('bear') || text.includes('eagle') || text.includes('dragon')) {
|
267 |
+
return PicletType.BEAST;
|
268 |
+
}
|
269 |
+
|
270 |
+
if (text.includes('bug') || text.includes('insect') || text.includes('spider') || text.includes('beetle') || text.includes('butterfly') || text.includes('ant') || text.includes('mantis') || text.includes('bee') || text.includes('wasp')) {
|
271 |
+
return PicletType.BUG;
|
272 |
+
}
|
273 |
+
|
274 |
+
if (text.includes('water') || text.includes('aquatic') || text.includes('fish') || text.includes('ocean') || text.includes('sea') || text.includes('swim') || text.includes('tentacle') || text.includes('octopus') || text.includes('squid') || text.includes('jellyfish') || text.includes('whale') || text.includes('shark')) {
|
275 |
+
return PicletType.AQUATIC;
|
276 |
+
}
|
277 |
+
|
278 |
+
if (text.includes('plant') || text.includes('tree') || text.includes('flower') || text.includes('vine') || text.includes('leaf') || text.includes('root') || text.includes('mushroom') || text.includes('fungus') || text.includes('moss') || text.includes('seed') || text.includes('petal') || text.includes('thorn')) {
|
279 |
+
return PicletType.FLORA;
|
280 |
+
}
|
281 |
+
|
282 |
+
if (text.includes('rock') || text.includes('stone') || text.includes('crystal') || text.includes('metal') || text.includes('iron') || text.includes('steel') || text.includes('gold') || text.includes('silver') || text.includes('gem') || text.includes('diamond') || text.includes('mineral') || text.includes('ore')) {
|
283 |
+
return PicletType.MINERAL;
|
284 |
+
}
|
285 |
+
|
286 |
+
if (text.includes('star') || text.includes('space') || text.includes('cosmic') || text.includes('celestial') || text.includes('moon') || text.includes('sun') || text.includes('planet') || text.includes('galaxy') || text.includes('nebula') || text.includes('comet') || text.includes('asteroid')) {
|
287 |
+
return PicletType.SPACE;
|
288 |
+
}
|
289 |
+
|
290 |
+
if (text.includes('machine') || text.includes('robot') || text.includes('mechanical') || text.includes('gear') || text.includes('engine') || text.includes('circuit') || text.includes('wire') || text.includes('electronic') || text.includes('computer') || text.includes('device') || text.includes('gadget')) {
|
291 |
+
return PicletType.MACHINA;
|
292 |
+
}
|
293 |
+
|
294 |
+
if (text.includes('building') || text.includes('house') || text.includes('castle') || text.includes('tower') || text.includes('bridge') || text.includes('monument') || text.includes('statue') || text.includes('pillar') || text.includes('wall') || text.includes('fortress') || text.includes('temple') || text.includes('ruins')) {
|
295 |
+
return PicletType.STRUCTURE;
|
296 |
+
}
|
297 |
+
|
298 |
+
if (text.includes('art') || text.includes('paint') || text.includes('music') || text.includes('dance') || text.includes('book') || text.includes('story') || text.includes('poem') || text.includes('fashion') || text.includes('toy') || text.includes('game') || text.includes('symbol') || text.includes('letter') || text.includes('word')) {
|
299 |
+
return PicletType.CULTURE;
|
300 |
+
}
|
301 |
+
|
302 |
+
if (text.includes('food') || text.includes('cook') || text.includes('dish') || text.includes('meal') || text.includes('bread') || text.includes('fruit') || text.includes('vegetable') || text.includes('meat') || text.includes('drink') || text.includes('beverage') || text.includes('soup') || text.includes('cake') || text.includes('spice') || text.includes('flavor') || text.includes('taste')) {
|
303 |
+
return PicletType.CUISINE;
|
304 |
+
}
|
305 |
+
|
306 |
+
return PicletType.BEAST;
|
307 |
+
}
|
src/tests/encounterService.test.ts
CHANGED
@@ -60,7 +60,7 @@ describe('EncounterService', () => {
|
|
60 |
const testPiclet: Omit<PicletInstance, 'id'> = {
|
61 |
typeId: 'test-001',
|
62 |
nickname: 'Testy',
|
63 |
-
|
64 |
level: 5,
|
65 |
xp: 0,
|
66 |
currentHp: 20,
|
|
|
60 |
const testPiclet: Omit<PicletInstance, 'id'> = {
|
61 |
typeId: 'test-001',
|
62 |
nickname: 'Testy',
|
63 |
+
primaryType: 'beast' as any,
|
64 |
level: 5,
|
65 |
xp: 0,
|
66 |
currentHp: 20,
|