Fraser commited on
Commit
a46ce65
·
1 Parent(s): 96377d4

better gen

Browse files
classes/aquatic.png ADDED

Git LFS Details

  • SHA256: 71072081acfa176234ba4de6d48b7b5d93835342db7aaaeef13e60d21d0c926f
  • Pointer size: 131 Bytes
  • Size of remote file: 968 kB
classes/beast.png ADDED

Git LFS Details

  • SHA256: ad5a2d36f38255aa64fa66b342ccbd2815d5d57c44878fb689604ffbfbf1fd75
  • Pointer size: 132 Bytes
  • Size of remote file: 1.23 MB
classes/bug.png ADDED

Git LFS Details

  • SHA256: 99e714469031dc13dca4c425a9c1b32ad11ed325361349759720e28dc9e54123
  • Pointer size: 132 Bytes
  • Size of remote file: 1.85 MB
classes/cuisine.png ADDED

Git LFS Details

  • SHA256: 583b2f39481ab5afe47daa1f81058f5cd64b1a32ca4447f16fc447d4eb8fc470
  • Pointer size: 132 Bytes
  • Size of remote file: 1.3 MB
classes/culture.png ADDED

Git LFS Details

  • SHA256: 900674e46d033b3b4d9180560ef33a80d94f14ce123e0b3f5f09abf307535455
  • Pointer size: 132 Bytes
  • Size of remote file: 1.01 MB
classes/flora.png ADDED

Git LFS Details

  • SHA256: bb912ccad5f1b766283c5a9afcd5a9c1f228bc4160e950f3f028f41ec8dec41f
  • Pointer size: 132 Bytes
  • Size of remote file: 1.42 MB
classes/machina.png ADDED

Git LFS Details

  • SHA256: 0e8496ff9ba615ea897331a92a301dc546004246c41a7bda4a38b1c42aea73fd
  • Pointer size: 132 Bytes
  • Size of remote file: 1.09 MB
classes/mineral.png ADDED

Git LFS Details

  • SHA256: 6e9e07969659d8e1297bb9dad762bf3f9c55eac8ab6decd8097c985db6b72505
  • Pointer size: 132 Bytes
  • Size of remote file: 1.34 MB
classes/space.png ADDED

Git LFS Details

  • SHA256: 4e3c0c48a0bfa2e66eb9f71c86729a09ff3ea7d9424830d5e7cd477af9e1b668
  • Pointer size: 132 Bytes
  • Size of remote file: 1.13 MB
classes/structure.png ADDED

Git LFS Details

  • SHA256: d01417e095943855711b635d0a4efd046d94fd3302f9d8f5979d1eb5ddb96e8b
  • Pointer size: 132 Bytes
  • Size of remote file: 1.42 MB
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, or horns 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
- - 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: any) {
64
  battlePhase = 'main';
65
  processingTurn = true;
66
  currentMessage = `${playerPiclet.nickname} used ${move.name}!`;
67
 
68
- // Mock damage
69
  setTimeout(() => {
70
- enemyHpPercentage = Math.max(0, enemyHpPercentage - 0.2);
 
 
 
 
 
 
 
71
 
72
- if (enemyHpPercentage <= 0) {
73
- currentMessage = `${enemyPiclet.nickname} fainted!`;
74
- battleEnded = true;
75
- setTimeout(() => onBattleEnd(true), 2000);
76
- } else {
77
- // Enemy turn
 
 
78
  setTimeout(() => {
79
- const enemyMove = enemyPiclet.moves[0];
80
- currentMessage = `${enemyPiclet.nickname} used ${enemyMove.name}!`;
81
-
82
- setTimeout(() => {
83
- playerHpPercentage = Math.max(0, playerHpPercentage - 0.15);
84
-
85
- if (playerHpPercentage <= 0) {
86
- currentMessage = `${playerPiclet.nickname} fainted!`;
87
- battleEnded = true;
88
- setTimeout(() => onBattleEnd(false), 2000);
89
- } else {
90
- currentMessage = `What will ${playerPiclet.nickname} do?`;
91
- processingTurn = false;
92
- }
93
- }, 1500);
 
 
 
 
 
 
 
 
 
 
 
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
- primaryTypeString: 'normal', // Default type
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: 'status',
312
  power: 0,
313
  accuracy: 100,
314
  pp: 15,
@@ -317,7 +317,7 @@
317
  },
318
  {
319
  name: stats.debuffActionName,
320
- type: 'status',
321
  power: 0,
322
  accuracy: 85,
323
  pp: 15,
@@ -326,7 +326,7 @@
326
  },
327
  {
328
  name: stats.specialActionName,
329
- type: 'special',
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
- // Default type color - we'll enhance this later with a proper type system
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 (simplified formula)
23
  static calculateDamage(
24
  attacker: PicletInstance,
25
  defender: PicletInstance,
26
- move: any
27
- ): number {
28
  const baseDamage = move.power || 50;
29
- const attackStat = move.type === 'physical' ? attacker.attack : attacker.fieldAttack;
30
- const defenseStat = move.type === 'physical' ? defender.defense : defender.fieldDefense;
 
 
 
 
 
 
 
 
 
 
31
 
32
  // Simple damage formula
33
- const damage = Math.floor((baseDamage * (attackStat / defenseStat) * 0.5) + 10);
 
 
 
34
 
35
  // Add some randomness (85-100% of calculated damage)
36
  const randomFactor = 0.85 + Math.random() * 0.15;
 
 
 
 
 
 
37
 
38
- return Math.floor(damage * randomFactor);
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: 'normal',
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: 'status',
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: 'status',
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: 'special',
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
- primaryTypeString: 'normal', // Default type, could be enhanced based on concept
75
- secondaryTypeString: undefined,
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: string;
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
- primaryTypeString: string;
28
- secondaryTypeString?: string;
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
- primaryTypeString: piclet.primaryTypeString,
73
- secondaryTypeString: piclet.secondaryTypeString,
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
- primaryTypeString: 'normal',
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,