-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmanapool.lua
More file actions
1585 lines (1311 loc) · 63.5 KB
/
manapool.lua
File metadata and controls
1585 lines (1311 loc) · 63.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
-- ManaPool class
-- Represents the shared pool of mana tokens in the center
-- Import modules at module level so they're available to all methods
local AssetCache = require("core.AssetCache")
local Constants = require("core.Constants")
local Pool = require("core.Pool")
local ManaPool = {}
ManaPool.__index = ManaPool
function ManaPool.new(x, y)
local self = setmetatable({}, ManaPool)
self.x = x
self.y = y
self.tokens = {} -- List of mana tokens
-- Make elliptical shape even flatter and wider
self.radiusX = 280 -- Wider horizontal radius
self.radiusY = 60 -- Flatter vertical radius
-- Define orbital rings (valences) for tokens to follow
self.valences = {
{radiusX = 180, radiusY = 25, baseSpeed = 0.35}, -- Inner valence
{radiusX = 230, radiusY = 40, baseSpeed = 0.25}, -- Middle valence
{radiusX = 280, radiusY = 55, baseSpeed = 0.18} -- Outer valence
}
-- Chance for a token to switch valences
self.valenceJumpChance = 0.002 -- Per frame chance of switching
-- Initialize token trails system
self.tokenTrails = {}
self.trailLength = 30 -- Max number of positions to store per token
-- Initialize the token pool if not already done
if not Pool.pools["token"] then
Pool.create("token", 50, function()
return {} -- Simple factory function that creates an empty table
end, ManaPool.resetToken) -- Use our custom token reset function
end
return self
end
-- Token methods for state machine
local TokenMethods = {}
-- Set the token's state with validation
function TokenMethods:setState(newStatus)
local oldStatus = self.status
-- Validate state transitions
if self.status == Constants.TokenStatus.POOLED then
print("[TOKEN LIFECYCLE] WARNING: Cannot transition from POOLED state!")
return false
end
-- Finalize scale if leaving an animation state that modifies scale
if (oldStatus == Constants.TokenStatus.APPEARING or oldStatus == Constants.TokenStatus.ORBITING) and
(newStatus == Constants.TokenStatus.CHANNELED or
newStatus == Constants.TokenStatus.SHIELDING or
newStatus == Constants.TokenStatus.FREE) then
if self.targetScale then
self.scale = self.targetScale
else
self.scale = 0.85 + math.random() * 0.3
end
end
-- Update the token's status
self.status = newStatus
-- For backwards compatibility, keep the legacy state in sync with the new status
if newStatus == Constants.TokenStatus.FREE or
newStatus == Constants.TokenStatus.CHANNELED or
newStatus == Constants.TokenStatus.SHIELDING then
self.state = newStatus
elseif newStatus == Constants.TokenStatus.RETURNING or
newStatus == Constants.TokenStatus.APPEARING or
newStatus == Constants.TokenStatus.ORBITING then
self.state = self.originalStatus or Constants.TokenState.FREE -- Keep original state during animation
elseif newStatus == Constants.TokenStatus.DISSOLVING then
self.state = Constants.TokenState.DESTROYED
elseif newStatus == Constants.TokenStatus.POOLED then
self.state = Constants.TokenState.DESTROYED
end
return true
end
-- Request token return animation
function TokenMethods:requestReturnAnimation()
-- Validate current state
if self.status ~= Constants.TokenStatus.CHANNELED and self.status ~= Constants.TokenStatus.SHIELDING then
print("[TOKEN LIFECYCLE] WARNING: Can only return tokens from CHANNELED or SHIELDING state, not " .. (self.status or "nil"))
return false
end
-- Store the original status for later reference
self.originalStatus = self.status
-- Set animation flags
self.isAnimating = true
self.returning = true -- For backward compatibility
-- Set animation parameters
self.startX = self.x
self.startY = self.y
self.animTime = 0
self.animDuration = 0.5 -- Half second return animation
-- Store callback to be called when animation completes
self.animationCallback = function() self:finalizeReturn() end
-- Change state to RETURNING
self:setState(Constants.TokenStatus.RETURNING)
return true
end
-- Request token destruction animation
function TokenMethods:requestDestructionAnimation()
-- Validate current state
if self.status == Constants.TokenStatus.DISSOLVING or self.status == Constants.TokenStatus.POOLED then
print("[TOKEN LIFECYCLE] Token is already dissolving or pooled")
return false
end
-- Set animation flags
self.isAnimating = true
self.dissolving = true -- For backward compatibility
-- Set animation parameters
self.dissolveTime = 0
self.dissolveMaxTime = 0.8 -- Dissolution animation duration
self.dissolveScale = self.scale or 1.0
self.initialX = self.x
self.initialY = self.y
-- Store callback to be called when animation completes
self.animationCallback = function() self:finalizeDestruction() end
-- Change state to DISSOLVING
self:setState(Constants.TokenStatus.DISSOLVING)
-- Create visual particle effects at the token's position using events
if not self.exploding and self.gameState then
self.exploding = true
-- Get token color based on its type
local colorTable = Constants.getColorForTokenType(self.type)
-- Create an EFFECT event instead of calling VFX directly
if self.gameState.eventRunner then
local event = {
type = "EFFECT",
source = "token",
target = Constants.TargetType.SELF, -- Not targeting a wizard
effectType = Constants.VFXType.IMPACT,
duration = 0.7,
vfxParams = {
x = self.x, -- Pass coordinates directly in vfxParams
y = self.y,
color = colorTable,
particleCount = 15,
radius = 30,
tokenType = self.type
}
}
-- Process the event immediately
self.gameState.eventRunner.processEvents({event}, self, nil)
else
print("[TOKEN LIFECYCLE] Warning: No eventRunner in gameState for token VFX")
end
end
return true
end
-- Finalize return to pool after animation (first phase)
function TokenMethods:finalizeReturn()
-- Validate current state
if self.status ~= Constants.TokenStatus.RETURNING then
print("[TOKEN LIFECYCLE] WARNING: Can only finalize return from RETURNING state, not " .. (self.status or "nil"))
return false
end
-- Reset some animation flags but keep isAnimating true for orbit animation
self.returning = false -- For backward compatibility
-- Clear wizard/spell references
self.wizardOwner = nil
self.spellSlot = nil
self.tokenIndex = nil
-- Get the ManaPool instance from the token's game state or another reference
local manaPool = self.manaPool
if not manaPool then
print("[TOKEN LIFECYCLE] ERROR: Cannot find manaPool reference to finalize token return!")
return false
end
-- Choose a random valence for the token's destination
local valenceIndex = math.random(1, #manaPool.valences)
local valence = manaPool.valences[valenceIndex]
self.valenceIndex = valenceIndex
-- Calculate a random angle for the token's destination
-- We'll use a random angle rather than the current angle to ensure
-- tokens don't all follow the same path
local angle = math.random() * math.pi * 2
self.orbitAngle = angle
-- Calculate target position based on valence
local targetX = manaPool.x + math.cos(angle) * valence.radiusX
local targetY = manaPool.y + math.sin(angle) * valence.radiusY
-- Add slight position variation to the target
local variationX = math.random(-2, 2)
local variationY = math.random(-1, 1)
-- Store current position (center of pool) as start for orbit animation
self.startOrbitX = self.x
self.startOrbitY = self.y
-- Store target position for orbit animation
self.targetOrbitX = targetX + variationX
self.targetOrbitY = targetY + variationY
-- Set up orbit animation parameters
self.orbitAnimTime = 0
self.orbitAnimDuration = 0.8 -- Slightly faster than return animation
-- Set an animation callback for when the orbit animation completes
self.animationCallback = function() self:finalizeOrbit() end
-- Set transitioning to orbit state
self:setState(Constants.TokenStatus.ORBITING)
-- Randomize orbit direction and speed
local direction = math.random(0, 1) * 2 - 1 -- -1 or 1
self.orbitSpeed = valence.baseSpeed * (0.8 + math.random() * 0.4) * direction
self.originalSpeed = self.orbitSpeed
-- Initialize valence properties
self.valenceJumpTimer = 2 + math.random() * 8
self.inValenceTransition = false
self.valenceTransitionTime = 0
self.valenceTransitionDuration = 0.8
self.sourceValenceIndex = valenceIndex
self.targetValenceIndex = valenceIndex
self.sourceRadiusX = valence.radiusX
self.sourceRadiusY = valence.radiusY
self.targetRadiusX = valence.radiusX
self.targetRadiusY = valence.radiusY
self.currentRadiusX = valence.radiusX
self.currentRadiusY = valence.radiusY
-- Visual variance
self.scale = 0.85 + math.random() * 0.3
self.zOrder = math.random()
return true
end
-- Finalize orbit transition (second phase)
function TokenMethods:finalizeOrbit()
-- Validate current state
if self.status ~= Constants.TokenStatus.ORBITING then
print("[TOKEN LIFECYCLE] WARNING: Can only finalize orbit from ORBITING state, not " .. (self.status or "nil"))
return false
end
-- Reset animation flags
self.isAnimating = false
-- Get the ManaPool instance from the token's game state or another reference
local manaPool = self.manaPool
if not manaPool then
print("[TOKEN LIFECYCLE] ERROR: Cannot find manaPool reference to finalize token orbit!")
return false
end
-- Update position to make sure it's at the target
self.x = self.targetOrbitX
self.y = self.targetOrbitY
-- Ensure scale is finalized
if self.targetScale then
self.scale = self.targetScale
else
self.scale = 0.85 + math.random() * 0.3
end
-- Clean up orbit animation properties
self.startOrbitX = nil
self.startOrbitY = nil
self.targetOrbitX = nil
self.targetOrbitY = nil
self.orbitAnimTime = nil
self.orbitAnimDuration = nil
-- Set state to FREE
self:setState(Constants.TokenStatus.FREE)
-- Set transition flags for smooth animation of orbital motion
self.transitionTime = 0
self.transitionDuration = 0.4 -- Shorter orbital transition (was 1.0)
self.inTransition = true
return true
end
-- Initialize conjured token appearance animation
function TokenMethods:requestAppearAnimation(fromWizard)
-- Set up animation parameters
self.isAnimating = true
-- Store the source position (wizard position)
self.startX = fromWizard.x
self.startY = fromWizard.y - 40 -- Start a little above the wizard
-- Initialize animation timing
self.appearAnimTime = 0
self.appearAnimDuration = 0.7 -- Slightly longer than return animation
-- Set the target scale
self.targetScale = 0.85 + math.random() * 0.3
-- Start very small
self.scale = 0.1
-- Initial position is at the wizard
self.x = self.startX
self.y = self.startY
-- Prepare callback for when animation completes
self.animationCallback = function() self:finalizeAppear() end
-- Set status to APPEARING
self:setState(Constants.TokenStatus.APPEARING)
return true
end
-- Finalize appear animation and transition to orbiting
function TokenMethods:finalizeAppear()
-- Validate current state
if self.status ~= Constants.TokenStatus.APPEARING then
print("[TOKEN LIFECYCLE] WARNING: Can only finalize appear from APPEARING state, not " .. (self.status or "nil"))
return false
end
-- Maintain animation flag
self.isAnimating = true
-- Get the ManaPool instance from the token's game state
local manaPool = self.manaPool
if not manaPool then
print("[TOKEN LIFECYCLE] ERROR: Cannot find manaPool reference to finalize token appearance!")
return false
end
-- Choose a random valence for the token's destination
local valenceIndex = math.random(1, #manaPool.valences)
local valence = manaPool.valences[valenceIndex]
self.valenceIndex = valenceIndex
-- Calculate a random angle for the token's destination
local angle = math.random() * math.pi * 2
self.orbitAngle = angle
-- Calculate target position based on valence
local targetX = manaPool.x + math.cos(angle) * valence.radiusX
local targetY = manaPool.y + math.sin(angle) * valence.radiusY
-- Add slight position variation to the target
local variationX = math.random(-2, 2)
local variationY = math.random(-1, 1)
-- Store current position (center of pool) as start for orbit animation
self.startOrbitX = self.x
self.startOrbitY = self.y
-- Store target position for orbit animation
self.targetOrbitX = targetX + variationX
self.targetOrbitY = targetY + variationY
-- Set up orbit animation parameters
self.orbitAnimTime = 0
self.orbitAnimDuration = 0.8 -- Slightly faster than return animation
-- Set an animation callback for when the orbit animation completes
self.animationCallback = function() self:finalizeOrbit() end
-- Set transitioning to orbit state
self:setState(Constants.TokenStatus.ORBITING)
-- Randomize orbit direction and speed
local direction = math.random(0, 1) * 2 - 1 -- -1 or 1
self.orbitSpeed = valence.baseSpeed * (0.8 + math.random() * 0.4) * direction
self.originalSpeed = self.orbitSpeed
-- Initialize valence properties
self.valenceJumpTimer = 2 + math.random() * 8
self.inValenceTransition = false
self.valenceTransitionTime = 0
self.valenceTransitionDuration = 0.8
self.sourceValenceIndex = valenceIndex
self.targetValenceIndex = valenceIndex
self.sourceRadiusX = valence.radiusX
self.sourceRadiusY = valence.radiusY
self.targetRadiusX = valence.radiusX
self.targetRadiusY = valence.radiusY
self.currentRadiusX = valence.radiusX
self.currentRadiusY = valence.radiusY
-- Visual variance set during appearing animation
self.scale = self.targetScale
self.zOrder = math.random()
return true
end
-- Finalize token destruction and release to pool
function TokenMethods:finalizeDestruction()
-- Validate current state
if self.status ~= Constants.TokenStatus.DISSOLVING then
print("[TOKEN LIFECYCLE] WARNING: Can only finalize destruction from DISSOLVING state, not " .. (self.status or "nil"))
return false
end
-- Reset animation flags
self.isAnimating = false
self.dissolving = false -- For backward compatibility
-- Set new state
self:setState(Constants.TokenStatus.POOLED)
-- Get the token's index in the mana pool
local found = false
local manaPool = self.manaPool
local index = nil
if not manaPool then
print("[TOKEN LIFECYCLE] ERROR: Cannot find manaPool reference to finalize token destruction!")
return false
end
for i, t in ipairs(manaPool.tokens) do
if t == self then
index = i
found = true
break
end
end
if found and index then
-- Remove the token from the mana pool's token list
table.remove(manaPool.tokens, index)
else
print("[TOKEN LIFECYCLE] WARNING: Token not found in manaPool.tokens during finalization!")
end
-- Release the token back to the object pool
Pool.release("token", self)
return true
end
-- Token reset function for the pool
function ManaPool.resetToken(token)
-- Remove all methods first
for name, _ in pairs(TokenMethods) do
token[name] = nil
end
-- Clear token trails if they exist
if token.manaPool and token.manaPool.tokenTrails and token.manaPool.tokenTrails[token] then
token.manaPool.tokenTrails[token] = nil
end
-- Clear all references and fields
token.type = nil
token.image = nil
token.x = nil
token.y = nil
token.state = nil
token.status = nil -- New field for the state machine
token.isAnimating = nil -- New field to track animation state
token.animationCallback = nil -- New field for animation completion callback
token.originalStatus = nil -- To store the state before transitions
token.valenceIndex = nil
token.orbitAngle = nil
token.orbitSpeed = nil
token.pulsePhase = nil
token.pulseSpeed = nil
token.rotAngle = nil
token.rotSpeed = nil
token.valenceJumpTimer = nil
token.inValenceTransition = nil
token.valenceTransitionTime = nil
token.valenceTransitionDuration = nil
token.sourceValenceIndex = nil
token.targetValenceIndex = nil
token.sourceRadiusX = nil
token.sourceRadiusY = nil
token.targetRadiusX = nil
token.targetRadiusY = nil
token.currentRadiusX = nil
token.currentRadiusY = nil
token.scale = nil
token.targetScale = nil
token.zOrder = nil
-- Clear animation-specific fields
token.startOrbitX = nil
token.startOrbitY = nil
token.targetOrbitX = nil
token.targetOrbitY = nil
token.orbitAnimTime = nil
token.orbitAnimDuration = nil
token.appearAnimTime = nil
token.appearAnimDuration = nil
token.startX = nil
token.startY = nil
token.originalSpeed = nil
token.wizardOwner = nil
token.spellSlot = nil
token.dissolving = nil
token.gameState = nil
token.manaPool = nil -- New field to reference the mana pool
token.id = nil -- New field for tracking tokens
-- Clear animation-related fields
token.returning = nil
token.animTime = nil
token.animDuration = nil
token.startX = nil
token.startY = nil
token.targetX = nil
token.targetY = nil
token.tokenIndex = nil
token.inTransition = nil
token.transitionTime = nil
token.transitionDuration = nil
token.originalState = nil
token.dissolveTime = nil
token.dissolveMaxTime = nil
token.dissolveScale = nil
token.initialX = nil
token.initialY = nil
token.exploding = nil
return token
end
-- Clear all tokens from the mana pool
function ManaPool:clear()
-- Release all tokens back to the pool
for _, token in ipairs(self.tokens) do
Pool.release("token", token)
end
self.tokens = {}
self.reservedTokens = {}
self.tokenTrails = {} -- Clear token trails when clearing the pool
end
-- Standard token addition - creates token directly at its final position
function ManaPool:addToken(tokenType, imagePath)
-- Pick a random valence for the token
local valenceIndex = math.random(1, #self.valences)
local valence = self.valences[valenceIndex]
-- Calculate a random angle along the valence
local angle = math.random() * math.pi * 2
-- Calculate position based on elliptical path
local x = self.x + math.cos(angle) * valence.radiusX
local y = self.y + math.sin(angle) * valence.radiusY
-- Generate slight positional variation to avoid tokens stacking perfectly
local variationX = math.random(-5, 5)
local variationY = math.random(-3, 3)
-- Randomize orbit direction (clockwise or counter-clockwise)
local direction = math.random(0, 1) * 2 - 1 -- -1 or 1
-- Get image from cache, with fallback
local tokenImage = AssetCache.getImage(imagePath)
if not tokenImage then
print("WARNING: Failed to load token image: " .. imagePath .. " - using placeholder")
-- Create a placeholder image using LÖVE's built-in canvas
tokenImage = love.graphics.newCanvas(32, 32)
love.graphics.setCanvas(tokenImage)
love.graphics.clear(0.8, 0.2, 0.8, 1) -- Bright color to make missing textures obvious
love.graphics.rectangle("fill", 0, 0, 32, 32)
love.graphics.setCanvas()
end
-- Create a new token from the pool
local token = Pool.acquire("token")
-- Add methods from TokenMethods table
for name, method in pairs(TokenMethods) do
token[name] = method
end
-- Initialize basic properties
token.type = tokenType
token.image = tokenImage
token.x = x + variationX
token.y = y + variationY
-- Initialize state machine properties
token.status = Constants.TokenStatus.FREE
token.state = Constants.TokenState.FREE -- For backwards compatibility
token.isAnimating = false
token.manaPool = self -- Reference to this mana pool instance
token.id = #self.tokens + 1
token.valenceIndex = valenceIndex
token.orbitAngle = angle
token.orbitSpeed = valence.baseSpeed * (0.8 + math.random() * 0.4) * direction
-- Visual effects
token.pulsePhase = math.random() * math.pi * 2
token.pulseSpeed = 2 + math.random() * 3
token.rotAngle = math.random() * math.pi * 2
token.rotSpeed = math.random(-2, 2) * 0.5
-- Valence jump timer
token.valenceJumpTimer = 2 + math.random() * 8
-- Valence transition properties
token.inValenceTransition = false
token.valenceTransitionTime = 0
token.valenceTransitionDuration = 0.8
token.sourceValenceIndex = valenceIndex
token.targetValenceIndex = valenceIndex
token.sourceRadiusX = valence.radiusX
token.sourceRadiusY = valence.radiusY
token.targetRadiusX = valence.radiusX
token.targetRadiusY = valence.radiusY
token.currentRadiusX = valence.radiusX
token.currentRadiusY = valence.radiusY
-- Size variation for visual interest
token.scale = 0.85 + math.random() * 0.3
-- Depth/z-order variation
token.zOrder = math.random()
token.originalSpeed = token.orbitSpeed
-- If game state is available, store it for VFX access
if self.gameState then
token.gameState = self.gameState
end
-- Add to the pool's token list
table.insert(self.tokens, token)
return token
end
-- Add token with appearance animation from wizard
function ManaPool:addTokenWithAnimation(tokenType, imagePath, sourceWizard)
if not sourceWizard then
-- Fall back to regular addition if no wizard provided
return self:addToken(tokenType, imagePath)
end
-- Get image from cache, with fallback
local tokenImage = AssetCache.getImage(imagePath)
if not tokenImage then
print("WARNING: Failed to load token image: " .. imagePath .. " - using placeholder")
-- Create a placeholder image using LÖVE's built-in canvas
tokenImage = love.graphics.newCanvas(32, 32)
love.graphics.setCanvas(tokenImage)
love.graphics.clear(0.8, 0.2, 0.8, 1) -- Bright color to make missing textures obvious
love.graphics.rectangle("fill", 0, 0, 32, 32)
love.graphics.setCanvas()
end
-- Create a new token with animation-ready properties
local token = Pool.acquire("token")
-- Add methods from TokenMethods table
for name, method in pairs(TokenMethods) do
token[name] = method
end
-- Initialize basic properties
token.type = tokenType
token.image = tokenImage
-- Position will be set by animation
token.x = sourceWizard.x
token.y = sourceWizard.y - 40 -- Start slightly above wizard
-- Initialize state machine properties
token.status = nil -- Will be set to APPEARING by requestAppearAnimation
token.state = Constants.TokenState.FREE -- For backwards compatibility
token.isAnimating = true
token.manaPool = self -- Reference to this mana pool instance
token.id = #self.tokens + 1
-- Visual effects
token.pulsePhase = math.random() * math.pi * 2
token.pulseSpeed = 2 + math.random() * 3
token.rotAngle = math.random() * math.pi * 2
token.rotSpeed = math.random(-2, 2) * 0.5
-- Scale starts small and grows during animation
token.scale = 0.1
-- If game state is available, store it for VFX access
if self.gameState then
token.gameState = self.gameState
end
-- Add to the pool's token list
table.insert(self.tokens, token)
-- Start appearance animation
token:requestAppearAnimation(sourceWizard)
return token
end
-- Removed token repulsion system, reverting to pure orbital motion
function ManaPool:update(dt)
-- Update token positions and states
for i = #self.tokens, 1, -1 do
local token = self.tokens[i]
-- Skip updating POOLED tokens, they've been reset and their properties are nil
if token.status == Constants.TokenStatus.POOLED then
goto continue_token
end
-- Update token trail position history
if not self.tokenTrails[token] then
self.tokenTrails[token] = {}
end
-- Only add a new trail point if the token has moved significantly
local lastPosition = self.tokenTrails[token][1]
local shouldAddTrail = true
if lastPosition then
-- Calculate distance moved since last trail point
local dx = token.x - lastPosition.x
local dy = token.y - lastPosition.y
local distSquared = dx*dx + dy*dy
-- Only add trail points if moved more than a minimum distance
shouldAddTrail = distSquared > 4
end
if shouldAddTrail then
-- Store new position at the beginning of history array
table.insert(self.tokenTrails[token], 1, {
x = token.x,
y = token.y,
time = love.timer.getTime()
})
-- Limit trail length
if #self.tokenTrails[token] > self.trailLength then
table.remove(self.tokenTrails[token])
end
end
-- Update token based on its status in the state machine
if token.status == Constants.TokenStatus.FREE then
-- Handle the transition period for newly returned tokens
if token.inTransition then
token.transitionTime = token.transitionTime + dt
local transProgress = math.min(1, token.transitionTime / token.transitionDuration)
-- Ease transition using a smooth curve
transProgress = transProgress < 0.5 and 4 * transProgress * transProgress * transProgress
or 1 - math.pow(-2 * transProgress + 2, 3) / 2
-- During transition, gradually start orbital motion
token.orbitAngle = token.orbitAngle + token.orbitSpeed * dt * transProgress
-- Check if transition is complete
if token.transitionTime >= token.transitionDuration then
token.inTransition = false
end
else
-- Normal FREE token behavior after transition
-- Update orbit angle with variable speed
token.orbitAngle = token.orbitAngle + token.orbitSpeed * dt
-- Update valence jump timer
token.valenceJumpTimer = token.valenceJumpTimer - dt
-- Chance to change valence when timer expires
if token.valenceJumpTimer <= 0 then
token.valenceJumpTimer = 2 + math.random() * 8 -- Reset timer
-- Random chance to jump to a different valence
if math.random() < self.valenceJumpChance * 100 then
-- Store current valence for interpolation
local oldValenceIndex = token.valenceIndex
local oldValence = self.valences[oldValenceIndex]
local newValenceIndex = oldValenceIndex
-- Ensure we pick a different valence if more than one exists
if #self.valences > 1 then
while newValenceIndex == oldValenceIndex do
newValenceIndex = math.random(1, #self.valences)
end
end
-- Start valence transition
local newValence = self.valences[newValenceIndex]
local direction = token.orbitSpeed > 0 and 1 or -1
-- Set up transition parameters
token.inValenceTransition = true
token.valenceTransitionTime = 0
token.valenceTransitionDuration = 0.8 -- Time to transition between valences
token.sourceValenceIndex = oldValenceIndex
token.targetValenceIndex = newValenceIndex
token.sourceRadiusX = oldValence.radiusX
token.sourceRadiusY = oldValence.radiusY
token.targetRadiusX = newValence.radiusX
token.targetRadiusY = newValence.radiusY
-- Update speed for new valence but maintain direction
token.orbitSpeed = newValence.baseSpeed * (0.8 + math.random() * 0.4) * direction
token.originalSpeed = token.orbitSpeed
end
end
-- Handle valence transition if active
if token.inValenceTransition then
token.valenceTransitionTime = token.valenceTransitionTime + dt
local progress = math.min(1, token.valenceTransitionTime / token.valenceTransitionDuration)
-- Use easing function for smooth transition
progress = progress < 0.5 and 4 * progress * progress * progress
or 1 - math.pow(-2 * progress + 2, 3) / 2
-- Interpolate between source and target radiuses
token.currentRadiusX = token.sourceRadiusX + (token.targetRadiusX - token.sourceRadiusX) * progress
token.currentRadiusY = token.sourceRadiusY + (token.targetRadiusY - token.sourceRadiusY) * progress
-- Check if transition is complete
if token.valenceTransitionTime >= token.valenceTransitionDuration then
token.inValenceTransition = false
token.valenceIndex = token.targetValenceIndex
end
end
-- Occasionally vary the speed slightly
if math.random() < 0.01 then
local direction = token.orbitSpeed > 0 and 1 or -1
local valence = self.valences[token.valenceIndex]
local variation = 0.9 + math.random() * 0.2 -- Subtle variation
token.orbitSpeed = valence.baseSpeed * variation * direction
end
end
-- Common behavior for all FREE tokens
-- Update pulse phase
if token.pulsePhase and token.pulseSpeed then
token.pulsePhase = token.pulsePhase + token.pulseSpeed * dt
end
-- Calculate new position based on elliptical orbit - maintain perfect elliptical path
if token.inValenceTransition then
-- Use interpolated radii during transition
token.x = self.x + math.cos(token.orbitAngle) * token.currentRadiusX
token.y = self.y + math.sin(token.orbitAngle) * token.currentRadiusY
else
-- Use valence radii when not transitioning
local valence = self.valences[token.valenceIndex]
token.x = self.x + math.cos(token.orbitAngle) * valence.radiusX
token.y = self.y + math.sin(token.orbitAngle) * valence.radiusY
end
-- Minimal wobble to maintain clean orbits but add slight visual interest
local wobbleX = math.sin(token.pulsePhase * 0.7) * 2
local wobbleY = math.cos(token.pulsePhase * 0.5) * 1
token.x = token.x + wobbleX
token.y = token.y + wobbleY
-- Rotate token itself for visual interest, occasionally reversing direction
if token.rotAngle and token.rotSpeed then
token.rotAngle = token.rotAngle + token.rotSpeed * dt
if math.random() < 0.002 then -- Small chance to reverse rotation
token.rotSpeed = -token.rotSpeed
end
end
elseif token.status == Constants.TokenStatus.CHANNELED or token.status == Constants.TokenStatus.SHIELDING then
-- For channeled or shielding tokens, animate movement to/from their spell slot
if token.animTime < token.animDuration then
-- Token is still being animated to the spell slot
token.animTime = token.animTime + dt
local progress = math.min(1, token.animTime / token.animDuration)
-- Ease in-out function for smoother animation
progress = progress < 0.5 and 4 * progress * progress * progress
or 1 - math.pow(-2 * progress + 2, 3) / 2
-- Calculate current position based on bezier curve for arcing motion
-- Start point
local x0 = token.startX
local y0 = token.startY
-- End point (in the spell slot)
local wizard = token.wizardOwner
if wizard then
-- Calculate position in the 3D elliptical spell slot orbit
-- These values must match those in wizard.lua drawSpellSlots
local slotYOffsets = {30, 0, -30} -- legs, midsection, head
local horizontalRadii = {80, 70, 60} -- From bottom to top
local verticalRadii = {20, 25, 30} -- From bottom to top
local slotY = wizard.y + slotYOffsets[token.slotIndex]
local radiusX = horizontalRadii[token.slotIndex]
local radiusY = verticalRadii[token.slotIndex]
local tokenCount = #wizard.spellSlots[token.slotIndex].tokens
local anglePerToken = math.pi * 2 / tokenCount
local tokenAngle = wizard.spellSlots[token.slotIndex].progress /
wizard.spellSlots[token.slotIndex].castTime * math.pi * 2 +
anglePerToken * (token.tokenIndex - 1)
-- Calculate position using elliptical projection
-- Apply the NEAR/FAR offset to the target position
local xOffset = 0
local isNear = wizard.gameState and wizard.gameState.rangeState == Constants.RangeState.NEAR
-- Apply the same NEAR/FAR offset logic as in the wizard's draw function
local isLeft = true
if wizard.gameState and wizard.gameState.wizards then
for _, other in ipairs(wizard.gameState.wizards) do
if other ~= wizard then
isLeft = wizard.x <= other.x
break
end
end
end
if isLeft then
xOffset = isNear and 60 or 0
else
xOffset = isNear and -60 or 0
end
local x3 = wizard.x + xOffset + math.cos(tokenAngle) * radiusX
local y3 = slotY + math.sin(tokenAngle) * radiusY
-- Control points for bezier (creating an arc)
local midX = (x0 + x3) / 2
local midY = (y0 + y3) / 2 - 80 -- Arc height
-- Quadratic bezier calculation
local t = progress
local u = 1 - t
token.x = u*u*x0 + 2*u*t*midX + t*t*x3
token.y = u*u*y0 + 2*u*t*midY + t*t*y3
-- Update token rotation during flight
token.rotAngle = token.rotAngle + dt * 5 -- Spin faster during flight
-- Store target position for the drawing function
token.targetX = x3
token.targetY = y3
end
else
-- Animation complete - token is now in the spell orbit
-- Token position will be updated by the wizard's drawSpellSlots function
token.rotAngle = token.rotAngle + dt * 2 -- Continue spinning in orbit
end
-- Update common pulse
if token.pulsePhase and token.pulseSpeed then
token.pulsePhase = token.pulsePhase + token.pulseSpeed * dt
end
elseif token.status == Constants.TokenStatus.RETURNING then
-- Token is being animated back to the mana pool
token.animTime = token.animTime + dt
local progress = math.min(1, token.animTime / token.animDuration)
-- Ease in-out function for smoother animation