Fraser commited on
Commit
81b3c3b
Β·
1 Parent(s): 22c2bfb
src/lib/components/Battle/BattleEffects.svelte ADDED
@@ -0,0 +1,214 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import { fade } from 'svelte/transition';
3
+
4
+ export let effects: Array<{type: string, emoji: string, duration: number}> = [];
5
+ export let flash: boolean = false;
6
+ </script>
7
+
8
+ <!-- Flash overlay -->
9
+ {#if flash}
10
+ <div class="flash-overlay" transition:fade={{ duration: 200 }}></div>
11
+ {/if}
12
+
13
+ <!-- Particle effects -->
14
+ {#each effects as effect (effect)}
15
+ <div class="effect-particle {effect.type}" style="animation-duration: {effect.duration}ms">
16
+ <span class="effect-emoji">{effect.emoji}</span>
17
+ </div>
18
+ {/each}
19
+
20
+ <style>
21
+ .flash-overlay {
22
+ position: absolute;
23
+ top: 0;
24
+ left: 0;
25
+ right: 0;
26
+ bottom: 0;
27
+ background: rgba(255, 255, 255, 0.8);
28
+ z-index: 10;
29
+ pointer-events: none;
30
+ }
31
+
32
+ .effect-particle {
33
+ position: absolute;
34
+ pointer-events: none;
35
+ z-index: 5;
36
+ animation-fill-mode: forwards;
37
+ }
38
+
39
+ .effect-emoji {
40
+ font-size: 24px;
41
+ display: block;
42
+ filter: drop-shadow(0 0 4px rgba(0, 0, 0, 0.3));
43
+ }
44
+
45
+ /* Damage and status effects */
46
+ .effect-particle.burn,
47
+ .effect-particle.poison,
48
+ .effect-particle.paralyze,
49
+ .effect-particle.sleep,
50
+ .effect-particle.freeze {
51
+ top: 20%;
52
+ left: 50%;
53
+ animation: statusEffect linear;
54
+ }
55
+
56
+ /* Stat changes */
57
+ .effect-particle.attackUp,
58
+ .effect-particle.defenseUp,
59
+ .effect-particle.speedUp,
60
+ .effect-particle.accuracyUp {
61
+ bottom: 30%;
62
+ left: 50%;
63
+ animation: statIncrease ease-out;
64
+ }
65
+
66
+ .effect-particle.attackDown,
67
+ .effect-particle.defenseDown,
68
+ .effect-particle.speedDown,
69
+ .effect-particle.accuracyDown {
70
+ top: 30%;
71
+ left: 50%;
72
+ animation: statDecrease ease-out;
73
+ }
74
+
75
+ /* Special effects */
76
+ .effect-particle.critical,
77
+ .effect-particle.superEffective {
78
+ top: 10%;
79
+ left: 50%;
80
+ animation: criticalEffect ease-out;
81
+ }
82
+
83
+ .effect-particle.notVeryEffective,
84
+ .effect-particle.miss {
85
+ top: 40%;
86
+ left: 50%;
87
+ animation: missEffect ease-out;
88
+ }
89
+
90
+ .effect-particle.heal {
91
+ bottom: 20%;
92
+ left: 50%;
93
+ animation: healEffect ease-out;
94
+ }
95
+
96
+ /* Animations */
97
+ @keyframes statusEffect {
98
+ 0% {
99
+ transform: translate(-50%, 0) scale(0.5);
100
+ opacity: 0;
101
+ }
102
+ 20% {
103
+ transform: translate(-50%, -10px) scale(1.2);
104
+ opacity: 1;
105
+ }
106
+ 40% {
107
+ transform: translate(-50%, -5px) scale(1);
108
+ opacity: 1;
109
+ }
110
+ 100% {
111
+ transform: translate(-50%, -20px) scale(0.8);
112
+ opacity: 0;
113
+ }
114
+ }
115
+
116
+ @keyframes statIncrease {
117
+ 0% {
118
+ transform: translate(-50%, 0) scale(0.5);
119
+ opacity: 0;
120
+ }
121
+ 30% {
122
+ transform: translate(-50%, -30px) scale(1.3);
123
+ opacity: 1;
124
+ }
125
+ 70% {
126
+ transform: translate(-50%, -40px) scale(1.1);
127
+ opacity: 1;
128
+ }
129
+ 100% {
130
+ transform: translate(-50%, -60px) scale(0.7);
131
+ opacity: 0;
132
+ }
133
+ }
134
+
135
+ @keyframes statDecrease {
136
+ 0% {
137
+ transform: translate(-50%, 0) scale(0.5);
138
+ opacity: 0;
139
+ }
140
+ 30% {
141
+ transform: translate(-50%, 20px) scale(1.3);
142
+ opacity: 1;
143
+ }
144
+ 70% {
145
+ transform: translate(-50%, 30px) scale(1.1);
146
+ opacity: 1;
147
+ }
148
+ 100% {
149
+ transform: translate(-50%, 50px) scale(0.7);
150
+ opacity: 0;
151
+ }
152
+ }
153
+
154
+ @keyframes criticalEffect {
155
+ 0% {
156
+ transform: translate(-50%, 0) scale(0.3) rotate(-10deg);
157
+ opacity: 0;
158
+ }
159
+ 20% {
160
+ transform: translate(-50%, -20px) scale(1.5) rotate(5deg);
161
+ opacity: 1;
162
+ }
163
+ 40% {
164
+ transform: translate(-50%, -15px) scale(1.3) rotate(-2deg);
165
+ opacity: 1;
166
+ }
167
+ 60% {
168
+ transform: translate(-50%, -25px) scale(1.4) rotate(1deg);
169
+ opacity: 1;
170
+ }
171
+ 100% {
172
+ transform: translate(-50%, -40px) scale(0.8) rotate(0deg);
173
+ opacity: 0;
174
+ }
175
+ }
176
+
177
+ @keyframes missEffect {
178
+ 0% {
179
+ transform: translate(-50%, 0) scale(1);
180
+ opacity: 0;
181
+ }
182
+ 20% {
183
+ transform: translate(-50%, 0) scale(1.2);
184
+ opacity: 0.7;
185
+ }
186
+ 80% {
187
+ transform: translate(-50%, 0) scale(1.1);
188
+ opacity: 0.3;
189
+ }
190
+ 100% {
191
+ transform: translate(-50%, 0) scale(1);
192
+ opacity: 0;
193
+ }
194
+ }
195
+
196
+ @keyframes healEffect {
197
+ 0% {
198
+ transform: translate(-50%, 20px) scale(0.5);
199
+ opacity: 0;
200
+ }
201
+ 30% {
202
+ transform: translate(-50%, -10px) scale(1.2);
203
+ opacity: 1;
204
+ }
205
+ 70% {
206
+ transform: translate(-50%, -30px) scale(1);
207
+ opacity: 1;
208
+ }
209
+ 100% {
210
+ transform: translate(-50%, -50px) scale(0.6);
211
+ opacity: 0;
212
+ }
213
+ }
214
+ </style>
src/lib/components/Battle/BattleField.svelte CHANGED
@@ -6,6 +6,7 @@
6
  import PicletInfo from './PicletInfo.svelte';
7
  import StatusEffectIndicator from './StatusEffectIndicator.svelte';
8
  import FieldEffectIndicator from './FieldEffectIndicator.svelte';
 
9
 
10
  export let playerPiclet: PicletInstance;
11
  export let enemyPiclet: PicletInstance;
@@ -13,6 +14,10 @@
13
  export let enemyHpPercentage: number;
14
  export let showIntro: boolean = false;
15
  export let battleState: BattleState | undefined = undefined;
 
 
 
 
16
 
17
  // Animation states
18
  let playerVisible = false;
@@ -96,6 +101,9 @@
96
  <StatusEffectIndicator statusEffects={battleState.opponentPiclet.statusEffects.map(effect => ({ type: effect, turnsLeft: 3 }))} />
97
  </div>
98
  {/if}
 
 
 
99
  </div>
100
  {/if}
101
  </div>
@@ -136,6 +144,9 @@
136
  <StatusEffectIndicator statusEffects={battleState.playerPiclet.statusEffects.map(effect => ({ type: effect, turnsLeft: 3 }))} />
137
  </div>
138
  {/if}
 
 
 
139
  </div>
140
  {/if}
141
 
 
6
  import PicletInfo from './PicletInfo.svelte';
7
  import StatusEffectIndicator from './StatusEffectIndicator.svelte';
8
  import FieldEffectIndicator from './FieldEffectIndicator.svelte';
9
+ import BattleEffects from './BattleEffects.svelte';
10
 
11
  export let playerPiclet: PicletInstance;
12
  export let enemyPiclet: PicletInstance;
 
14
  export let enemyHpPercentage: number;
15
  export let showIntro: boolean = false;
16
  export let battleState: BattleState | undefined = undefined;
17
+ export let playerEffects: Array<{type: string, emoji: string, duration: number}> = [];
18
+ export let enemyEffects: Array<{type: string, emoji: string, duration: number}> = [];
19
+ export let playerFlash: boolean = false;
20
+ export let enemyFlash: boolean = false;
21
 
22
  // Animation states
23
  let playerVisible = false;
 
101
  <StatusEffectIndicator statusEffects={battleState.opponentPiclet.statusEffects.map(effect => ({ type: effect, turnsLeft: 3 }))} />
102
  </div>
103
  {/if}
104
+
105
+ <!-- Enemy Battle Effects -->
106
+ <BattleEffects effects={enemyEffects} flash={enemyFlash} />
107
  </div>
108
  {/if}
109
  </div>
 
144
  <StatusEffectIndicator statusEffects={battleState.playerPiclet.statusEffects.map(effect => ({ type: effect, turnsLeft: 3 }))} />
145
  </div>
146
  {/if}
147
+
148
+ <!-- Player Battle Effects -->
149
+ <BattleEffects effects={playerEffects} flash={playerFlash} />
150
  </div>
151
  {/if}
152
 
src/lib/components/Battle/PicletInfo.svelte CHANGED
@@ -7,7 +7,17 @@
7
  export let isPlayer: boolean;
8
 
9
  $: hpColor = hpPercentage > 0.5 ? '#4caf50' : hpPercentage > 0.2 ? '#ffc107' : '#f44336';
10
- $: displayHp = Math.ceil(piclet.currentHp * hpPercentage);
 
 
 
 
 
 
 
 
 
 
11
 
12
  // Get type emoji (simplified - should map from actual types)
13
  const typeEmoji = 'πŸ”₯'; // Default fire type
@@ -33,7 +43,7 @@
33
  <!-- HP Text (Player only) -->
34
  {#if isPlayer}
35
  <div class="hp-text">
36
- <span class="hp-values">{displayHp}/{piclet.maxHp} HP</span>
37
  </div>
38
 
39
  <!-- XP Bar (Player only) -->
@@ -128,6 +138,19 @@
128
 
129
  .hp-values {
130
  font-weight: 600;
 
 
 
 
 
 
 
 
 
 
 
 
 
131
  }
132
 
133
  /* XP Bar */
 
7
  export let isPlayer: boolean;
8
 
9
  $: hpColor = hpPercentage > 0.5 ? '#4caf50' : hpPercentage > 0.2 ? '#ffc107' : '#f44336';
10
+ $: displayHp = Math.ceil(piclet.currentHp);
11
+
12
+ // Track HP changes for animations
13
+ let previousHp = displayHp;
14
+ let hpFlash = false;
15
+
16
+ $: if (displayHp !== previousHp) {
17
+ hpFlash = true;
18
+ setTimeout(() => hpFlash = false, 300);
19
+ previousHp = displayHp;
20
+ }
21
 
22
  // Get type emoji (simplified - should map from actual types)
23
  const typeEmoji = 'πŸ”₯'; // Default fire type
 
43
  <!-- HP Text (Player only) -->
44
  {#if isPlayer}
45
  <div class="hp-text">
46
+ <span class="hp-values" class:hp-flash={hpFlash}>{displayHp}/{piclet.maxHp} HP</span>
47
  </div>
48
 
49
  <!-- XP Bar (Player only) -->
 
138
 
139
  .hp-values {
140
  font-weight: 600;
141
+ transition: all 0.2s ease;
142
+ }
143
+
144
+ .hp-values.hp-flash {
145
+ color: #ff4444;
146
+ text-shadow: 0 0 8px rgba(255, 68, 68, 0.6);
147
+ animation: hpFlash 0.3s ease-in-out;
148
+ }
149
+
150
+ @keyframes hpFlash {
151
+ 0% { transform: scale(1); }
152
+ 50% { transform: scale(1.1); }
153
+ 100% { transform: scale(1); }
154
  }
155
 
156
  /* XP Bar */
src/lib/components/Pages/Battle.svelte CHANGED
@@ -33,6 +33,12 @@
33
  let playerHpPercentage = playerPiclet.currentHp / playerPiclet.maxHp;
34
  let enemyHpPercentage = enemyPiclet.currentHp / enemyPiclet.maxHp;
35
 
 
 
 
 
 
 
36
  onMount(() => {
37
  // Initialize battle engine with converted piclet definitions
38
  const playerDefinition = picletInstanceToBattleDefinition(playerPiclet);
@@ -120,12 +126,17 @@
120
  const newLogEntries = logAfter.slice(logBefore.length);
121
  const result = { log: newLogEntries };
122
 
123
- // Show battle messages with timing
124
  if (result.log && result.log.length > 0) {
125
  let messageIndex = 0;
126
  function showNextBattleMessage() {
127
  if (messageIndex < result.log.length) {
128
- currentMessage = result.log[messageIndex];
 
 
 
 
 
129
  messageIndex++;
130
  setTimeout(showNextBattleMessage, 1500);
131
  } else {
@@ -167,6 +178,105 @@
167
  }
168
 
169
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
170
  function updateUIFromBattleState() {
171
  if (!battleState) return;
172
 
@@ -228,6 +338,10 @@
228
  {enemyHpPercentage}
229
  showIntro={battlePhase === 'intro'}
230
  {battleState}
 
 
 
 
231
  />
232
 
233
  <BattleControls
 
33
  let playerHpPercentage = playerPiclet.currentHp / playerPiclet.maxHp;
34
  let enemyHpPercentage = enemyPiclet.currentHp / enemyPiclet.maxHp;
35
 
36
+ // Visual effects state
37
+ let playerEffects: Array<{type: string, emoji: string, duration: number}> = [];
38
+ let enemyEffects: Array<{type: string, emoji: string, duration: number}> = [];
39
+ let playerFlash = false;
40
+ let enemyFlash = false;
41
+
42
  onMount(() => {
43
  // Initialize battle engine with converted piclet definitions
44
  const playerDefinition = picletInstanceToBattleDefinition(playerPiclet);
 
126
  const newLogEntries = logAfter.slice(logBefore.length);
127
  const result = { log: newLogEntries };
128
 
129
+ // Show battle messages with timing and visual effects
130
  if (result.log && result.log.length > 0) {
131
  let messageIndex = 0;
132
  function showNextBattleMessage() {
133
  if (messageIndex < result.log.length) {
134
+ const message = result.log[messageIndex];
135
+ currentMessage = message;
136
+
137
+ // Trigger visual effects based on message content
138
+ triggerVisualEffectsFromMessage(message);
139
+
140
  messageIndex++;
141
  setTimeout(showNextBattleMessage, 1500);
142
  } else {
 
178
  }
179
 
180
 
181
+ function triggerVisualEffectsFromMessage(message: string) {
182
+ const playerName = currentPlayerPiclet.nickname;
183
+ const enemyName = currentEnemyPiclet.nickname;
184
+
185
+ // Damage effects
186
+ if (message.includes('took') && message.includes('damage')) {
187
+ if (message.includes(playerName)) {
188
+ triggerDamageFlash('player');
189
+ } else if (message.includes(enemyName)) {
190
+ triggerDamageFlash('enemy');
191
+ }
192
+ }
193
+
194
+ // Critical hit effects
195
+ if (message.includes('critical hit')) {
196
+ triggerEffect('both', 'critical', 'πŸ’₯', 1000);
197
+ }
198
+
199
+ // Effectiveness messages
200
+ if (message.includes("It's super effective")) {
201
+ triggerEffect('both', 'superEffective', '⚑', 800);
202
+ } else if (message.includes("not very effective")) {
203
+ triggerEffect('both', 'notVeryEffective', 'πŸ’¨', 800);
204
+ }
205
+
206
+ // Status effects
207
+ if (message.includes('was burned')) {
208
+ const target = message.includes(playerName) ? 'player' : 'enemy';
209
+ triggerEffect(target, 'burn', 'πŸ”₯', 1200);
210
+ } else if (message.includes('was poisoned')) {
211
+ const target = message.includes(playerName) ? 'player' : 'enemy';
212
+ triggerEffect(target, 'poison', '☠️', 1200);
213
+ } else if (message.includes('was paralyzed')) {
214
+ const target = message.includes(playerName) ? 'player' : 'enemy';
215
+ triggerEffect(target, 'paralyze', '⚑', 1200);
216
+ } else if (message.includes('fell asleep')) {
217
+ const target = message.includes(playerName) ? 'player' : 'enemy';
218
+ triggerEffect(target, 'sleep', '😴', 1200);
219
+ } else if (message.includes('was frozen')) {
220
+ const target = message.includes(playerName) ? 'player' : 'enemy';
221
+ triggerEffect(target, 'freeze', '❄️', 1200);
222
+ }
223
+
224
+ // Stat changes
225
+ if (message.includes("'s") && (message.includes('rose') || message.includes('fell'))) {
226
+ const target = message.includes(playerName) ? 'player' : 'enemy';
227
+ const isIncrease = message.includes('rose');
228
+
229
+ if (message.includes('attack')) {
230
+ triggerEffect(target, isIncrease ? 'attackUp' : 'attackDown', isIncrease ? 'βš”οΈ' : 'πŸ”»', 1000);
231
+ } else if (message.includes('defense')) {
232
+ triggerEffect(target, isIncrease ? 'defenseUp' : 'defenseDown', isIncrease ? 'πŸ›‘οΈ' : 'πŸ”»', 1000);
233
+ } else if (message.includes('speed')) {
234
+ triggerEffect(target, isIncrease ? 'speedUp' : 'speedDown', isIncrease ? 'πŸ’¨' : '🐌', 1000);
235
+ } else if (message.includes('accuracy')) {
236
+ triggerEffect(target, isIncrease ? 'accuracyUp' : 'accuracyDown', isIncrease ? '🎯' : 'πŸ‘οΈ', 1000);
237
+ }
238
+ }
239
+
240
+ // Healing effects
241
+ if (message.includes('recovered') && message.includes('HP')) {
242
+ const target = message.includes(playerName) ? 'player' : 'enemy';
243
+ triggerEffect(target, 'heal', 'πŸ’š', 1000);
244
+ }
245
+
246
+ // Miss effects
247
+ if (message.includes('missed')) {
248
+ triggerEffect('both', 'miss', 'πŸ’«', 800);
249
+ }
250
+ }
251
+
252
+ function triggerDamageFlash(target: 'player' | 'enemy') {
253
+ if (target === 'player') {
254
+ playerFlash = true;
255
+ setTimeout(() => playerFlash = false, 200);
256
+ } else {
257
+ enemyFlash = true;
258
+ setTimeout(() => enemyFlash = false, 200);
259
+ }
260
+ }
261
+
262
+ function triggerEffect(target: 'player' | 'enemy' | 'both', type: string, emoji: string, duration: number) {
263
+ const effect = { type, emoji, duration };
264
+
265
+ if (target === 'player' || target === 'both') {
266
+ playerEffects = [...playerEffects, effect];
267
+ setTimeout(() => {
268
+ playerEffects = playerEffects.filter(e => e !== effect);
269
+ }, duration);
270
+ }
271
+
272
+ if (target === 'enemy' || target === 'both') {
273
+ enemyEffects = [...enemyEffects, effect];
274
+ setTimeout(() => {
275
+ enemyEffects = enemyEffects.filter(e => e !== effect);
276
+ }, duration);
277
+ }
278
+ }
279
+
280
  function updateUIFromBattleState() {
281
  if (!battleState) return;
282
 
 
338
  {enemyHpPercentage}
339
  showIntro={battlePhase === 'intro'}
340
  {battleState}
341
+ {playerEffects}
342
+ {enemyEffects}
343
+ {playerFlash}
344
+ {enemyFlash}
345
  />
346
 
347
  <BattleControls