forked from TecProg-grupo4-2018-2/panel-attack
-
Notifications
You must be signed in to change notification settings - Fork 0
/
graphics.lua
1148 lines (1026 loc) · 48 KB
/
graphics.lua
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
require("input")
require("util")
local graphicsUtil = require("graphics_util")
local TouchDataEncoding = require("engine.TouchDataEncoding")
local floor = math.floor
local ceil = math.ceil
local shake_arr = {}
-- Setup the shake_arr data used for rendering the stack shake animation
local shake_idx = -6
for i = 14, 6, -1 do
local x = -math.pi
local step = math.pi * 2 / i
for j = 1, i do
shake_arr[shake_idx] = (1 + math.cos(x)) / 2
x = x + step
shake_idx = shake_idx + 1
end
end
-- 1 -> 1
-- #shake -> 0
local shake_step = 1 / (#shake_arr - 1)
local shake_mult = 1
for i = 1, #shake_arr do
shake_arr[i] = shake_arr[i] * shake_mult
-- print(shake_arr[i])
shake_mult = shake_mult - shake_step
end
-- Provides the X origin to draw an element of the stack
-- cameFromLegacyScoreOffset - set to true if this used to use the "score" position in legacy themes
function Stack:elementOriginX(cameFromLegacyScoreOffset, legacyOffsetIsAlreadyScaled)
assert(cameFromLegacyScoreOffset ~= nil)
assert(legacyOffsetIsAlreadyScaled ~= nil)
local x = 546
if self.which == 2 then
x = 642
end
if cameFromLegacyScoreOffset == false or self.theme:offsetsAreFixed() then
x = self.origin_x
if legacyOffsetIsAlreadyScaled == false or self.theme:offsetsAreFixed() then
x = x * GFX_SCALE
end
end
return x
end
-- Provides the Y origin to draw an element of the stack
-- cameFromLegacyScoreOffset - set to true if this used to use the "score" position in legacy themes
function Stack:elementOriginY(cameFromLegacyScoreOffset, legacyOffsetIsAlreadyScaled)
assert(cameFromLegacyScoreOffset ~= nil)
assert(legacyOffsetIsAlreadyScaled ~= nil)
local y = 208
if cameFromLegacyScoreOffset == false or self.theme:offsetsAreFixed() then
y = self.panelOriginY
if legacyOffsetIsAlreadyScaled == false or self.theme:offsetsAreFixed() then
y = y * GFX_SCALE
end
end
return y
end
-- Provides the X position to draw an element of the stack, shifted by the given offset and mirroring
-- themePositionOffset - the theme offset array
-- cameFromLegacyScoreOffset - set to true if this used to use the "score" position in legacy themes
-- legacyOffsetIsAlreadyScaled - set to true if the offset used to be already scaled in legacy themes
function Stack:elementOriginXWithOffset(themePositionOffset, cameFromLegacyScoreOffset, legacyOffsetIsAlreadyScaled)
if legacyOffsetIsAlreadyScaled == nil then
legacyOffsetIsAlreadyScaled = false
end
local xOffset = themePositionOffset[1]
if cameFromLegacyScoreOffset == false or self.theme:offsetsAreFixed() then
xOffset = xOffset * self.mirror_x
end
if cameFromLegacyScoreOffset == false and self.theme:offsetsAreFixed() == false and legacyOffsetIsAlreadyScaled == false then
xOffset = xOffset * GFX_SCALE
end
local x = self:elementOriginX(cameFromLegacyScoreOffset, legacyOffsetIsAlreadyScaled) + xOffset
return x
end
-- Provides the Y position to draw an element of the stack, shifted by the given offset and mirroring
-- themePositionOffset - the theme offset array
-- cameFromLegacyScoreOffset - set to true if this used to use the "score" position in legacy themes
function Stack:elementOriginYWithOffset(themePositionOffset, cameFromLegacyScoreOffset, legacyOffsetIsAlreadyScaled)
if legacyOffsetIsAlreadyScaled == nil then
legacyOffsetIsAlreadyScaled = false
end
local yOffset = themePositionOffset[2]
if cameFromLegacyScoreOffset == false and self.theme:offsetsAreFixed() == false and legacyOffsetIsAlreadyScaled == false then
yOffset = yOffset * GFX_SCALE
end
local y = self:elementOriginY(cameFromLegacyScoreOffset, legacyOffsetIsAlreadyScaled) + yOffset
return y
end
-- Provides the X position to draw a label of the stack, shifted by the given offset, mirroring and label width
-- themePositionOffset - the theme offset array
-- cameFromLegacyScoreOffset - set to true if this used to use the "score" position in legacy themes
-- width - width of the drawable
-- percentWidthShift - the percent of the width you want shifted left
function Stack:labelOriginXWithOffset(themePositionOffset, scale, cameFromLegacyScoreOffset, width, percentWidthShift, legacyOffsetIsAlreadyScaled)
local x = self:elementOriginXWithOffset(themePositionOffset, cameFromLegacyScoreOffset, legacyOffsetIsAlreadyScaled)
if percentWidthShift > 0 then
x = x - math.floor((percentWidthShift * width * scale))
end
return x
end
function Stack:drawLabel(drawable, themePositionOffset, scale, cameFromLegacyScoreOffset, legacyOffsetIsAlreadyScaled)
if cameFromLegacyScoreOffset == nil then
cameFromLegacyScoreOffset = false
end
local percentWidthShift = 0
-- If we are mirroring from the right, move the full width left
if cameFromLegacyScoreOffset == false or self.theme:offsetsAreFixed() then
if self.multiplication > 0 then
percentWidthShift = 1
end
end
local x = self:labelOriginXWithOffset(themePositionOffset, scale, cameFromLegacyScoreOffset, drawable:getWidth(), percentWidthShift, legacyOffsetIsAlreadyScaled)
local y = self:elementOriginYWithOffset(themePositionOffset, cameFromLegacyScoreOffset, legacyOffsetIsAlreadyScaled)
menu_drawf(drawable, x, y, "left", "left", 0, scale, scale)
end
function Stack:drawBar(image, quad, themePositionOffset, height, yOffset, rotate, scale)
local imageWidth, imageHeight = image:getDimensions()
local barYScale = height / imageHeight
local quadY = 0
if barYScale < 1 then
barYScale = 1
quadY = imageHeight - height
end
local x = self:elementOriginXWithOffset(themePositionOffset, false)
local y = self:elementOriginYWithOffset(themePositionOffset, false)
quad:setViewport(0, quadY, imageWidth, imageHeight - quadY)
qdraw(image, quad, x / GFX_SCALE, (y - height - yOffset) / GFX_SCALE, rotate, scale / GFX_SCALE, scale * barYScale / GFX_SCALE, 0, 0, self.mirror_x)
end
function Stack:drawNumber(number, quads, themePositionOffset, scale, cameFromLegacyScoreOffset)
if cameFromLegacyScoreOffset == nil then
cameFromLegacyScoreOffset = false
end
local x = self:elementOriginXWithOffset(themePositionOffset, cameFromLegacyScoreOffset)
local y = self:elementOriginYWithOffset(themePositionOffset, cameFromLegacyScoreOffset)
GraphicsUtil.draw_number(number, self.theme.images["IMG_number_atlas" .. self.id], quads, x, y, scale, "center")
end
function Stack:drawString(string, themePositionOffset, cameFromLegacyScoreOffset, fontSize)
if cameFromLegacyScoreOffset == nil then
cameFromLegacyScoreOffset = false
end
local x = self:elementOriginXWithOffset(themePositionOffset, cameFromLegacyScoreOffset)
local y = self:elementOriginYWithOffset(themePositionOffset, cameFromLegacyScoreOffset)
local limit = canvas_width - x
local alignment = "left"
if self.theme:offsetsAreFixed() then
if self.which == 1 then
limit = x
x = 0
alignment = "right"
end
end
if fontSize == nil then
fontSize = GraphicsUtil.fontSize
end
local fontDelta = fontSize - GraphicsUtil.fontSize
gprintf(string, x, y, limit, alignment, nil, nil, fontDelta)
end
-- Update all the card frames used for doing the card animation
function Stack.update_cards(self)
if self.canvas == nil then
return
end
for i = self.card_q.first, self.card_q.last do
local card = self.card_q[i]
if card_animation[card.frame] then
card.frame = card.frame + 1
if (card_animation[card.frame] == nil) then
if config.popfx == true then
GraphicsUtil:releaseQuad(card.burstParticle)
end
self.card_q:pop()
end
else
card.frame = card.frame + 1
end
end
end
-- Render the card animations used to show "bursts" when a combo or chain happens
function Stack.draw_cards(self)
for i = self.card_q.first, self.card_q.last do
local card = self.card_q[i]
if card_animation[card.frame] then
local draw_x = (self.panelOriginX) + (card.x - 1) * 16
local draw_y = (self.panelOriginY) + (11 - card.y) * 16 + self.displacement - card_animation[card.frame]
-- Draw burst around card
if card.burstAtlas and card.frame then
local burstFrameDimension = card.burstAtlas:getWidth() / 9
local radius = -37.6 * math.log(card.frame) + 132.81
local maxRadius = 8
if radius < maxRadius then
radius = maxRadius
end
for i = 1, 6, 1 do
local cardfx_x = draw_x + math.cos(math.rad((i * 60) + (card.frame * 5))) * radius
local cardfx_y = draw_y + math.sin(math.rad((i * 60) + (card.frame * 5))) * radius
set_color(1, 1, 1, self:opacityForFrame(card.frame, 1, 22))
qdraw(card.burstAtlas, card.burstParticle, cardfx_x, cardfx_y, 0, 16 / burstFrameDimension, 16 / burstFrameDimension)
set_color(1, 1, 1, 1)
end
end
-- draw card
local iconSize = 48 / GFX_SCALE
local cardImage = nil
if card.chain then
cardImage = self.theme:chainImage(card.n)
else
cardImage = self.theme:comboImage(card.n)
end
if cardImage then
local icon_width, icon_height = cardImage:getDimensions()
set_color(1, 1, 1, self:opacityForFrame(card.frame, 1, 22))
draw(cardImage, draw_x, draw_y, 0, iconSize / icon_width, iconSize / icon_height)
set_color(1, 1, 1, 1)
end
end
end
end
function Stack:opacityForFrame(frame, startFadeFrame, maxFadeFrame)
local opacity = 1
if frame >= startFadeFrame then
local currentFrame = frame - startFadeFrame
local maxFrame = maxFadeFrame - startFadeFrame
local minOpacity = 0.5
local maxOpacitySubtract = 1 - minOpacity
opacity = 1 - math.min(maxOpacitySubtract * (currentFrame / maxFrame), maxOpacitySubtract)
end
return opacity
end
-- Update all the pop animations
function Stack.update_popfxs(self)
if self.canvas == nil then
return
end
for i = self.pop_q.first, self.pop_q.last do
local popfx = self.pop_q[i]
if characters[self.character].popfx_style == "burst" or characters[self.character].popfx_style == "fadeburst" then
popfx_animation = popfx_burst_animation
end
if characters[self.character].popfx_style == "fade" then
popfx_animation = popfx_fade_animation
end
if popfx_burst_animation[popfx.frame] then
popfx.frame = popfx.frame + 1
if (popfx_burst_animation[popfx.frame] == nil) then
if characters[self.character].images["burst"] then
GraphicsUtil:releaseQuad(popfx.burstParticle)
end
if characters[self.character].images["fade"] then
GraphicsUtil:releaseQuad(popfx.fadeParticle)
end
if characters[self.character].images["burst"] then
GraphicsUtil:releaseQuad(popfx.bigParticle)
end
self.pop_q:pop()
end
else
popfx.frame = popfx.frame + 1
end
end
end
-- Draw the pop animations that happen when matches are made
function Stack.draw_popfxs(self)
for i = self.pop_q.first, self.pop_q.last do
local popfx = self.pop_q[i]
local draw_x = (self.panelOriginX) + (popfx.x - 1) * 16
local draw_y = (self.panelOriginY) + (11 - popfx.y) * 16 + self.displacement
local burstScale = characters[self.character].popfx_burstScale
local fadeScale = characters[self.character].popfx_fadeScale
local burstParticle_atlas = popfx.burstAtlas
local burstParticle = popfx.burstParticle
local burstFrameDimension = popfx.burstFrameDimension
local fadeParticle_atlas = popfx.fadeAtlas
local fadeParticle = popfx.fadeParticle
local fadeFrameDimension = popfx.fadeFrameDimension
set_color(1, 1, 1, self:opacityForFrame(popfx.frame, 1, 8))
if characters[self.character].popfx_style == "burst" or characters[self.character].popfx_style == "fadeburst" then
if characters[self.character].images["burst"] then
burstFrame = popfx_burst_animation[popfx.frame]
if popfx_burst_animation[popfx.frame] then
burstParticle:setViewport(burstFrame[2] * burstFrameDimension, 0, burstFrameDimension, burstFrameDimension, burstParticle_atlas:getDimensions())
positions = {
-- four corner
{x = draw_x - burstFrame[1], y = draw_y - burstFrame[1]},
{x = draw_x + 15 + burstFrame[1], y = draw_y - burstFrame[1]},
{x = draw_x - burstFrame[1], y = draw_y + 15 + burstFrame[1]},
{x = draw_x + 15 + burstFrame[1], y = draw_y + 15 + burstFrame[1]},
-- top and bottom
{x = draw_x, y = draw_y - (burstFrame[1] * 2)},
{x = draw_x, y = draw_y + 10 + (burstFrame[1] * 2)},
-- left and right
{x = draw_x + 5 - (burstFrame[1] * 2), y = draw_y},
{x = draw_x + 10 + (burstFrame[1] * 2), y = draw_y}
}
if characters[self.character].popfx_burstRotate == true then
topRot = {math.rad(45), (16 / burstFrameDimension) * burstScale, (16 / burstFrameDimension) * burstScale}
bottomRot = {math.rad(-135), (16 / burstFrameDimension) * burstScale, (16 / burstFrameDimension) * burstScale}
leftRot = {math.rad(-45), (16 / burstFrameDimension) * burstScale, (16 / burstFrameDimension) * burstScale}
rightRot = {math.rad(135), (16 / burstFrameDimension) * burstScale, (16 / burstFrameDimension) * burstScale}
else
topRot = {0, (16 / burstFrameDimension) * burstScale, (16 / burstFrameDimension) * burstScale}
bottomRot = {0, (16 / burstFrameDimension) * burstScale, -(16 / burstFrameDimension) * burstScale}
leftRot = {0, (16 / burstFrameDimension) * burstScale, (16 / burstFrameDimension) * burstScale}
rightRot = {0, -(16 / burstFrameDimension) * burstScale, (16 / burstFrameDimension) * burstScale}
end
-- four corner
qdraw(burstParticle_atlas, burstParticle, positions[1].x, positions[1].y, 0, (16 / burstFrameDimension) * burstScale, (16 / burstFrameDimension) * burstScale, (burstFrameDimension * burstScale) / 2, (burstFrameDimension * burstScale) / 2)
qdraw(burstParticle_atlas, burstParticle, positions[2].x, positions[2].y, 0, -(16 / burstFrameDimension) * burstScale, (16 / burstFrameDimension) * burstScale, (burstFrameDimension * burstScale) / 2, (burstFrameDimension * burstScale) / 2)
qdraw(burstParticle_atlas, burstParticle, positions[3].x, positions[3].y, 0, (16 / burstFrameDimension) * burstScale, -(16 / burstFrameDimension) * burstScale, (burstFrameDimension * burstScale) / 2, (burstFrameDimension * burstScale) / 2)
qdraw(burstParticle_atlas, burstParticle, positions[4].x, positions[4].y, 0, -(16 / burstFrameDimension) * burstScale, -16 / burstFrameDimension * burstScale, (burstFrameDimension * burstScale) / 2, (burstFrameDimension * burstScale) / 2)
-- top and bottom
if popfx.popsize == "big" or popfx.popsize == "giant" then
qdraw(burstParticle_atlas, burstParticle, positions[5].x + 8, positions[5].y, topRot[1], topRot[2], topRot[3], (burstFrameDimension * burstScale) / 2, (burstFrameDimension * burstScale) / 2)
qdraw(burstParticle_atlas, burstParticle, positions[6].x + 8, positions[6].y, bottomRot[1], bottomRot[2], bottomRot[3], (burstFrameDimension * burstScale) / 2, (burstFrameDimension * burstScale) / 2)
end
-- left and right
if popfx.popsize == "giant" then
qdraw(burstParticle_atlas, burstParticle, positions[7].x, positions[7].y + 8, leftRot[1], leftRot[2], leftRot[3], (burstFrameDimension * burstScale) / 2, (burstFrameDimension * burstScale) / 2)
qdraw(burstParticle_atlas, burstParticle, positions[8].x, positions[8].y + 8, rightRot[1], rightRot[2], rightRot[3], (burstFrameDimension * burstScale) / 2, (burstFrameDimension * burstScale) / 2)
end
end
end
end
if characters[self.character].popfx_style == "fade" or characters[self.character].popfx_style == "fadeburst" then
if characters[self.character].images["fade"] then
fadeFrame = popfx_fade_animation[popfx.frame]
if (fadeFrame ~= nil) then
fadeParticle:setViewport(fadeFrame * fadeFrameDimension, 0, fadeFrameDimension, fadeFrameDimension, fadeParticle_atlas:getDimensions())
qdraw(fadeParticle_atlas, fadeParticle, draw_x + 8, draw_y + 8, 0, (32 / fadeFrameDimension) * fadeScale, (32 / fadeFrameDimension) * fadeScale, fadeFrameDimension / 2, fadeFrameDimension / 2)
end
end
end
set_color(1, 1, 1, 1)
end
end
local mask_shader = love.graphics.newShader [[
vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 screen_coords) {
if (Texel(texture, texture_coords).rgb == vec3(0.0)) {
// a discarded pixel wont be applied as the stencil.
discard;
}
return vec4(1.0);
}
]]
function Stack:drawDebug()
local x = self.origin_x + 480
local y = self.frameOriginY + 160
if config.debug_mode and self.danger then
gprint("danger", x, y + 135)
end
if config.debug_mode and self.danger_music then
gprint("danger music", x, y + 150)
end
if config.debug_mode then
gprint(loc("pl_cleared", (self.panels_cleared or 0)), x, y + 165)
end
if config.debug_mode then
gprint(loc("pl_metal", (self.metal_panels_queued or 0)), x, y + 180)
end
if config.debug_mode and (self.input_state or self.taunt_up or self.taunt_down) then
local iraise, iswap, iup, idown, ileft, iright
if self.inputMethod == "touch" then
iraise, _, _ = TouchDataEncoding.latinStringToTouchData(self.input_state, self.width)
else
iraise, iswap, iup, idown, ileft, iright = unpack(base64decode[self.input_state])
end
local inputs_to_print = "inputs:"
if iraise then
inputs_to_print = inputs_to_print .. "\nraise"
end --◄▲▼►
if iswap then
inputs_to_print = inputs_to_print .. "\nswap"
end
if iup then
inputs_to_print = inputs_to_print .. "\nup"
end
if idown then
inputs_to_print = inputs_to_print .. "\ndown"
end
if ileft then
inputs_to_print = inputs_to_print .. "\nleft"
end
if iright then
inputs_to_print = inputs_to_print .. "\nright"
end
if self.taunt_down then
inputs_to_print = inputs_to_print .. "\ntaunt_down"
end
if self.taunt_up then
inputs_to_print = inputs_to_print .. "\ntaunt_up"
end
if self.inputMethod == "touch" then
inputs_to_print = inputs_to_print .. self.touchInputController:debugString()
end
gprint(inputs_to_print, x, y + 195)
end
end
-- Renders the player's stack on screen
function Stack.render(self)
if self.canvas == nil then
return
end
local function frame_mask(x_pos, y_pos)
love.graphics.setShader(mask_shader)
love.graphics.setBackgroundColor(1, 1, 1)
local canvas_w, canvas_h = self.canvas:getDimensions()
love.graphics.rectangle("fill", 0, 0, canvas_w, canvas_h)
love.graphics.setBackgroundColor(unpack(global_background_color))
love.graphics.setShader()
end
love.graphics.setCanvas({self.canvas, stencil = true})
love.graphics.clear()
love.graphics.stencil(frame_mask, "replace", 1)
love.graphics.setStencilTest("greater", 0)
local characterObject = characters[self.character]
-- Update portrait fade if needed
if self.do_countdown then
-- self.portraitFade starts at 0 (no fade)
if self.countdown_clock then
local desiredFade = config.portrait_darkness / 100
local startFrame = 50
local fadeDuration = 30
if self.countdown_clock <= 50 then
self.portraitFade = 0
elseif self.countdown_clock > 50 and self.countdown_clock <= startFrame + fadeDuration then
local percent = (self.countdown_clock - startFrame) / fadeDuration
self.portraitFade = desiredFade * percent
end
end
end
characterObject:drawPortrait(self.which, 4, 4, self.portraitFade)
local metals
if self.opponentStack then
metals = panels[self.opponentStack.panels_dir].images.metals
else
metals = panels[self.panels_dir].images.metals
end
local metal_w, metal_h = metals.mid:getDimensions()
local metall_w, metall_h = metals.left:getDimensions()
local metalr_w, metalr_h = metals.right:getDimensions()
local shake_idx = #shake_arr - self.shake_time
local shake = ceil((shake_arr[shake_idx] or 0) * 13)
-- Draw all the panels
for row = 0, self.height do
for col = 1, self.width do
local panel = self.panels[row][col]
local draw_x = 4 + (col - 1) * 16
local draw_y = 4 + (11 - (row)) * 16 + self.displacement - shake
if panel.color ~= 0 and panel.state ~= "popped" then
local draw_frame = 1
if panel.isGarbage then
local imgs = {flash = metals.flash}
if not panel.metal then
if not self.garbageTarget then
imgs = characterObject.images
else
imgs = characters[self.garbageTarget.character].images
end
end
if panel.x_offset == 0 and panel.y_offset == 0 then
-- draw the entire block!
if panel.metal then
draw(metals.left, draw_x, draw_y, 0, 8 / metall_w, 16 / metall_h)
draw(metals.right, draw_x + 16 * (panel.width - 1) + 8, draw_y, 0, 8 / metalr_w, 16 / metalr_h)
for i = 1, 2 * (panel.width - 1) do
draw(metals.mid, draw_x + 8 * i, draw_y, 0, 8 / metal_w, 16 / metal_h)
end
else
local height, width = panel.height, panel.width
local top_y = draw_y - (height - 1) * 16
local use_1 = ((height - (height % 2)) / 2) % 2 == 0
local filler_w, filler_h = imgs.filler1:getDimensions()
for i = 0, height - 1 do
for j = 1, width - 1 do
draw((use_1 or height < 3) and imgs.filler1 or imgs.filler2, draw_x + 16 * j - 8, top_y + 16 * i, 0, 16 / filler_w, 16 / filler_h)
use_1 = not use_1
end
end
if height % 2 == 1 then
local face
if imgs.face2 and width % 2 == 1 then
face = imgs.face2
else
face = imgs.face
end
local face_w, face_h = face:getDimensions()
draw(face, draw_x + 8 * (width - 1), top_y + 16 * ((height - 1) / 2), 0, 16 / face_w, 16 / face_h)
else
local face_w, face_h = imgs.doubleface:getDimensions()
draw(imgs.doubleface, draw_x + 8 * (width - 1), top_y + 16 * ((height - 2) / 2), 0, 16 / face_w, 32 / face_h)
end
local corner_w, corner_h = imgs.topleft:getDimensions()
local lr_w, lr_h = imgs.left:getDimensions()
local topbottom_w, topbottom_h = imgs.top:getDimensions()
draw(imgs.left, draw_x, top_y, 0, 8 / lr_w, (1 / lr_h) * height * 16)
draw(imgs.right, draw_x + 16 * (width - 1) + 8, top_y, 0, 8 / lr_w, (1 / lr_h) * height * 16)
draw(imgs.top, draw_x, top_y, 0, (1 / topbottom_w) * width * 16, 2 / topbottom_h)
draw(imgs.bot, draw_x, draw_y + 14, 0, (1 / topbottom_w) * width * 16, 2 / topbottom_h)
draw(imgs.topleft, draw_x, top_y, 0, 8 / corner_w, 3 / corner_h)
draw(imgs.topright, draw_x + 16 * width - 8, top_y, 0, 8 / corner_w, 3 / corner_h)
draw(imgs.botleft, draw_x, draw_y + 13, 0, 8 / corner_w, 3 / corner_h)
draw(imgs.botright, draw_x + 16 * width - 8, draw_y + 13, 0, 8 / corner_w, 3 / corner_h)
end
end
if panel.state == "matched" then
local flash_time = panel.initial_time - panel.timer
if flash_time >= self.FRAMECOUNTS.FLASH then
if panel.timer > panel.pop_time then
if panel.metal then
draw(metals.left, draw_x, draw_y, 0, 8 / metall_w, 16 / metall_h)
draw(metals.right, draw_x + 8, draw_y, 0, 8 / metalr_w, 16 / metalr_h)
else
local popped_w, popped_h = imgs.pop:getDimensions()
draw(imgs.pop, draw_x, draw_y, 0, 16 / popped_w, 16 / popped_h)
end
elseif panel.y_offset == -1 then
local p_w, p_h = panels[self.panels_dir].images.classic[panel.color][1]:getDimensions()
draw(panels[self.panels_dir].images.classic[panel.color][1], draw_x, draw_y, 0, 16 / p_w, 16 / p_h)
end
elseif flash_time % 2 == 1 then
if panel.metal then
draw(metals.left, draw_x, draw_y, 0, 8 / metall_w, 16 / metall_h)
draw(metals.right, draw_x + 8, draw_y, 0, 8 / metalr_w, 16 / metalr_h)
else
local popped_w, popped_h = imgs.pop:getDimensions()
draw(imgs.pop, draw_x, draw_y, 0, 16 / popped_w, 16 / popped_h)
end
else
local flashed_w, flashed_h = imgs.flash:getDimensions()
draw(imgs.flash, draw_x, draw_y, 0, 16 / flashed_w, 16 / flashed_h)
end
end
else
if panel.state == "matched" then
local flash_time = self.FRAMECOUNTS.MATCH - panel.timer
if flash_time >= self.FRAMECOUNTS.FLASH then
draw_frame = 6
elseif flash_time % 2 == 1 then
draw_frame = 1
else
draw_frame = 5
end
elseif panel.state == "popping" then
draw_frame = 6
elseif panel.state == "landing" then
draw_frame = bounce_table[panel.timer + 1]
elseif panel.state == "swapping" then
if panel.isSwappingFromLeft then
draw_x = draw_x - panel.timer * 4
else
draw_x = draw_x + panel.timer * 4
end
elseif panel.state == "dead" then
draw_frame = 6
elseif panel.state == "dimmed" then
draw_frame = 7
elseif panel.fell_from_garbage then
draw_frame = garbage_bounce_table[panel.fell_from_garbage] or 1
elseif self.danger_col[col] then
draw_frame = danger_bounce_table[wrap(1, self.danger_timer + 1 + floor((col - 1) / 2), #danger_bounce_table)]
else
draw_frame = 1
end
local panel_w, panel_h = panels[self.panels_dir].images.classic[panel.color][draw_frame]:getDimensions()
draw(panels[self.panels_dir].images.classic[panel.color][draw_frame], draw_x, draw_y, 0, 16 / panel_w, 16 / panel_h)
end
end
end
end
-- Draw the frames and wall at the bottom
local frameImage = nil
local wallImage = nil
if self.which == 1 then
frameImage = self.theme.images.IMG_frame1P
wallImage = self.theme.images.IMG_wall1P
else
frameImage = self.theme.images.IMG_frame2P
wallImage = self.theme.images.IMG_wall2P
end
if frameImage then
graphicsUtil.drawScaledImage(frameImage, 0, 0, 312, 612)
end
if wallImage then
graphicsUtil.drawScaledWidthImage(wallImage, 12, (4 - shake + self.height * 16)*GFX_SCALE, 288)
end
-- Draw the cursor
if self:game_ended() == false then
self:render_cursor()
end
-- Draw the countdown timer
if self.do_countdown then
self:render_countdown()
end
-- ends here
love.graphics.setStencilTest()
love.graphics.setCanvas(GAME.globalCanvas)
love.graphics.setBlendMode("alpha", "premultiplied")
love.graphics.draw(self.canvas, self.frameOriginX * GFX_SCALE, self.frameOriginY * GFX_SCALE)
love.graphics.setBlendMode("alpha", "alphamultiply")
-- Draw debug graphics if set
if config.debug_mode then
local mouseX, mouseY = GAME:transform_coordinates(love.mouse.getPosition())
for row = 0, math.min(self.height + 1, #self.panels) do
for col = 1, self.width do
local panel = self.panels[row][col]
local draw_x = (self.panelOriginX + (col - 1) * 16) * GFX_SCALE
local draw_y = (self.panelOriginY + (11 - (row)) * 16 + self.displacement - shake) * GFX_SCALE
-- Require hovering over a stack to show details
if mouseX >= self.panelOriginX * GFX_SCALE and mouseX <= (self.panelOriginX + self.width * 16) * GFX_SCALE then
if not (panel.color == 0 and panel.state == "normal") then
gprint(panel.state, draw_x, draw_y)
if panel.matchAnyway then
gprint(tostring(panel.matchAnyway), draw_x, draw_y + 10)
if panel.debug_tag then
gprint(tostring(panel.debug_tag), draw_x, draw_y + 20)
end
end
if panel.chaining then
gprint("chaining", draw_x, draw_y + 30)
end
end
end
if mouseX >= draw_x and mouseX < draw_x + 16 * GFX_SCALE and mouseY >= draw_y and mouseY < draw_y + 16 * GFX_SCALE then
local str = loc("pl_panel_info", row, col)
for k, v in pairsSortedByKeys(panel) do
str = str .. "\n" .. k .. ": " .. tostring(v)
end
local drawX = 30
local drawY = 10
grectangle_color("fill", (drawX - 5) / GFX_SCALE, (drawY - 5) / GFX_SCALE, 100/GFX_SCALE, 100/GFX_SCALE, 0, 0, 0, 0.5)
gprintf(str, drawX, drawY)
end
end
end
end
local function drawMoveCount()
-- draw outside of stack's frame canvas
if self.match.mode == "puzzle" then
self:drawLabel(self.theme.images.IMG_moves, self.theme.moveLabel_Pos, self.theme.moveLabel_Scale, false, true)
local moveNumber = math.abs(self.puzzle.remaining_moves)
if self.puzzle.puzzleType == "moves" then
moveNumber = self.puzzle.remaining_moves
end
self:drawNumber(moveNumber, self.move_quads, self.theme.move_Pos, self.theme.move_Scale, true)
end
end
local function drawScore()
self:drawLabel(self.theme.images["IMG_score" .. self.id], self.theme.scoreLabel_Pos, self.theme.scoreLabel_Scale)
self:drawNumber(self.score, self.score_quads, self.theme.score_Pos, self.theme.score_Scale)
end
local function drawSpeed()
self:drawLabel(self.theme.images["IMG_speed" .. self.id], self.theme.speedLabel_Pos, self.theme.speedLabel_Scale)
self:drawNumber(self.speed, self.speed_quads, self.theme.speed_Pos, self.theme.speed_Scale)
end
local function drawLevel()
if self.level then
self:drawLabel(self.theme.images["IMG_level" .. self.id], self.theme.levelLabel_Pos, self.theme.levelLabel_Scale)
local x = self:elementOriginXWithOffset(self.theme.level_Pos, false) / GFX_SCALE
local y = self:elementOriginYWithOffset(self.theme.level_Pos, false) / GFX_SCALE
local level_atlas = self.theme.images["IMG_levelNumber_atlas" .. self.id]
self.level_quad:setViewport(tonumber(self.level - 1) * (level_atlas:getWidth() / 11), 0, level_atlas:getWidth() / 11, level_atlas:getHeight(), level_atlas:getDimensions())
qdraw(level_atlas, self.level_quad, x, y, 0, (28 / self.theme.images["levelNumberWidth" .. self.id] * self.theme.level_Scale) / GFX_SCALE, (26 / self.theme.images["levelNumberHeight" .. self.id] * self.theme.level_Scale / GFX_SCALE), 0, 0, self.multiplication)
end
end
local function drawAnalyticData()
if not config.enable_analytics or not self.drawsAnalytics then
return
end
local analytic = self.analytic
local backgroundPadding = 18
local paddingToAnalytics = 16
local width = 160
local height = 600
local x = paddingToAnalytics + backgroundPadding
if self.which == 2 then
x = canvas_width - paddingToAnalytics - width + backgroundPadding
end
local y = self.frameOriginY * GFX_SCALE + backgroundPadding
local iconToTextSpacing = 30
local nextIconIncrement = 30
local column2Distance = 70
local fontIncrement = 8
local iconSize = 8
local icon_width
local icon_height
-- Background
grectangle_color("fill", (x - backgroundPadding) / GFX_SCALE , (y - backgroundPadding) / GFX_SCALE, width/GFX_SCALE, height/GFX_SCALE, 0, 0, 0, 0.5)
-- Panels cleared
icon_width, icon_height = panels[self.panels_dir].images.classic[1][6]:getDimensions()
draw(panels[self.panels_dir].images.classic[1][6], x / GFX_SCALE, y / GFX_SCALE, 0, iconSize / icon_width, iconSize / icon_height)
gprintf(analytic.data.destroyed_panels, x + iconToTextSpacing, y + 0, canvas_width, "left", nil, 1, fontIncrement)
y = y + nextIconIncrement
-- Garbage sent
icon_width, icon_height = characters[self.character].images.face:getDimensions()
draw(characters[self.character].images.face, x / GFX_SCALE, y / GFX_SCALE, 0, iconSize / icon_width, iconSize / icon_height)
gprintf(analytic.data.sent_garbage_lines, x + iconToTextSpacing, y + 0, canvas_width, "left", nil, 1, fontIncrement)
y = y + nextIconIncrement
-- GPM
if analytic.lastGPM == 0 or math.fmod(self.clock, 60) < self.max_runs_per_frame then
if self.clock > 0 and (analytic.data.sent_garbage_lines > 0) then
analytic.lastGPM = analytic:getRoundedGPM(self.clock)
end
end
icon_width, icon_height = self.theme.images.IMG_gpm:getDimensions()
draw(self.theme.images.IMG_gpm, x / GFX_SCALE, y / GFX_SCALE, 0, iconSize / icon_width, iconSize / icon_height)
gprintf(analytic.lastGPM .. "/m", x + iconToTextSpacing, y + 0, canvas_width, "left", nil, 1, fontIncrement)
y = y + nextIconIncrement
-- Moves
icon_width, icon_height = self.theme.images.IMG_cursorCount:getDimensions()
draw(self.theme.images.IMG_cursorCount, x / GFX_SCALE, y / GFX_SCALE, 0, iconSize / icon_width, iconSize / icon_height)
gprintf(analytic.data.move_count, x + iconToTextSpacing, y + 0, canvas_width, "left", nil, 1, fontIncrement)
y = y + nextIconIncrement
-- Swaps
if self.theme.images.IMG_swap then
icon_width, icon_height = self.theme.images.IMG_swap:getDimensions()
draw(self.theme.images.IMG_swap, x / GFX_SCALE, y / GFX_SCALE, 0, iconSize / icon_width, iconSize / icon_height)
end
gprintf(analytic.data.swap_count, x + iconToTextSpacing, y + 0, canvas_width, "left", nil, 1, fontIncrement)
y = y + nextIconIncrement
-- APM
if analytic.lastAPM == 0 or math.fmod(self.clock, 60) < self.max_runs_per_frame then
if self.clock > 0 and (analytic.data.swap_count + analytic.data.move_count > 0) then
local actionsPerMinute = (analytic.data.swap_count + analytic.data.move_count) / (self.clock / 60 / 60)
analytic.lastAPM = string.format("%0.0f", round(actionsPerMinute, 0))
end
end
if self.theme.images.IMG_apm then
icon_width, icon_height = self.theme.images.IMG_apm:getDimensions()
draw(self.theme.images.IMG_apm, x / GFX_SCALE, y / GFX_SCALE, 0, iconSize / icon_width, iconSize / icon_height)
end
gprintf(analytic.lastAPM .. "/m", x + iconToTextSpacing, y + 0, canvas_width, "left", nil, 1, fontIncrement)
y = y + nextIconIncrement
local yCombo = y
-- Clean up the chain data so we only show chains up to the highest chain the user has done
local chainData = {}
local chain_above_limit = analytic:compute_above_chain_card_limit()
for i = 2, self.theme.chainCardLimit, 1 do
if not analytic.data.reached_chains[i] then
chainData[i] = 0
else
chainData[i] = analytic.data.reached_chains[i]
end
end
table.insert(chainData, chain_above_limit)
for i = #chainData, 0, -1 do
if chainData[i] and chainData[i] == 0 then
chainData[i] = nil
else
break
end
end
-- Draw the chain images
for i = 2, self.theme.chainCardLimit + 1 do
local chain_amount = chainData[i]
if chain_amount and chain_amount > 0 then
local cardImage = self.theme:chainImage(i)
if cardImage then
icon_width, icon_height = cardImage:getDimensions()
draw(cardImage, x / GFX_SCALE, y / GFX_SCALE, 0, iconSize / icon_width, iconSize / icon_height)
gprintf(chain_amount, x + iconToTextSpacing, y + 0, canvas_width, "left", nil, 1, fontIncrement)
y = y + nextIconIncrement
end
end
end
-- Clean up the combo data so we only show combos up to the highest combo the user has done
local comboData = shallowcpy(analytic.data.used_combos)
for i = 4, 15, 1 do
if not comboData[i] then
comboData[i] = 0
end
end
local maxCombo = maxComboReached(analytic.data)
for i = maxCombo, 0, -1 do
if comboData[i] and comboData[i] == 0 then
comboData[i] = nil
else
break
end
end
-- Draw the combo images
local xCombo = x + column2Distance
for i, combo_amount in pairs(comboData) do
if combo_amount and combo_amount > 0 then
local cardImage = self.theme:comboImage(i)
if cardImage then
icon_width, icon_height = cardImage:getDimensions()
draw(cardImage, xCombo / GFX_SCALE, yCombo / GFX_SCALE, 0, iconSize / icon_width, iconSize / icon_height)
gprintf(combo_amount, xCombo + iconToTextSpacing, yCombo + 0, canvas_width, "left", nil, 1, fontIncrement)
yCombo = yCombo + nextIconIncrement
end
end
end
end
drawMoveCount()
-- Draw the "extra" game info
if config.show_ingame_infos then
if self.match.mode ~= "puzzle" then
drawScore()
drawSpeed()
end
self:drawMultibar()
end
-- Draw VS HUD
if self.match.battleRoom then
self:drawPlayerName()
self:drawWinCount()
self:drawRating()
end
drawLevel()
drawAnalyticData()
self:drawDebug()
end
function Stack:drawPlayerName()
local username = (self.match.battleRoom.playerNames[self.which] or "")
self:drawString(username, self.theme.name_Pos, true, self.theme.name_Font_Size)
end
function Stack:drawWinCount()
if self.match.P2 == nil then
return -- need multiple players for showing wins to make sense
end
self:drawLabel(self.theme.images.IMG_wins, self.theme.winLabel_Pos, self.theme.winLabel_Scale, true)
self:drawNumber(self.match.battleRoom:getPlayerWinCount(self.player_number), self.wins_quads, self.theme.win_Pos, self.theme.win_Scale, true)
end
function Stack:drawRating()
local match = self.match
local roomRatings = match.room_ratings
if config.debug_mode and roomRatings == nil then
roomRatings = {{new = 1337}, {new = 2042}}
match.my_player_number = 1
match.op_player_number = 2
end
if roomRatings ~= nil and (match_type == "Ranked" or config.debug_mode) then
local playerNumber = match.my_player_number
if self.which == 2 then
playerNumber = match.op_player_number
end
if roomRatings[playerNumber] and roomRatings[playerNumber].new then
local rating_to_print = roomRatings[playerNumber].new
if type(rating_to_print) == "number" and rating_to_print > 0 then
self:drawLabel(self.theme.images["IMG_rating" .. self.id], self.theme.ratingLabel_Pos, self.theme.ratingLabel_Scale, true)
self:drawNumber(rating_to_print, self.rating_quads, self.theme.rating_Pos, self.theme.rating_Scale, true)
end
end
end
end
-- Draw the stacks cursor
function Stack.render_cursor(self)
if self.inputMethod == "touch" then
if self.cur_row == 0 and self.cur_col == 0 then
--no panel is touched, let's not draw the cursor
return
end
end
local cursorImage = self.theme.images.IMG_cursor[(floor(self.clock / 16) % 2) + 1]
local shake_idx = #shake_arr - self.shake_time
local shake = ceil((shake_arr[shake_idx] or 0) * 13)
local desiredCursorWidth = 40
local panelWidth = 16
local scale_x = desiredCursorWidth / cursorImage:getWidth()
local scale_y = 24 / cursorImage:getHeight()
local renderCursor = true
if self.countdown_timer then
if self.clock % 2 ~= 0 then
renderCursor = false
end
end
if renderCursor then
local xPosition = (self.cur_col - 1) * panelWidth
qdraw(cursorImage, self.cursorQuads[1], xPosition, (11 - (self.cur_row)) * panelWidth + self.displacement - shake, 0, scale_x, scale_y)
if self.inputMethod == "touch" then
qdraw(cursorImage, self.cursorQuads[2], xPosition + 12, (11 - (self.cur_row)) * panelWidth + self.displacement - shake, 0, scale_x, scale_y)
end
end
end
-- Draw the stacks countdown timer
function Stack.render_countdown(self)
if self.do_countdown and self.countdown_clock then
local ready_x = 16
local initial_ready_y = 4
local ready_y_drop_speed = 6
local ready_y = initial_ready_y + (math.min(8, self.countdown_clock) - 1) * ready_y_drop_speed
local countdown_x = 44
local countdown_y = 68
if self.countdown_clock <= 8 then
draw(self.theme.images.IMG_ready, ready_x, ready_y)
elseif self.countdown_clock >= 9 and self.countdown_timer and self.countdown_timer > 0 then
if self.countdown_timer >= 100 then
draw(self.theme.images.IMG_ready, ready_x, ready_y)
end
local IMG_number_to_draw = self.theme.images.IMG_numbers[math.ceil(self.countdown_timer / 60)]
if IMG_number_to_draw then
draw(IMG_number_to_draw, countdown_x, countdown_y)
end
end
end
end
-- Draw the stop time and healthbars
function Stack:drawMultibar()