-
Notifications
You must be signed in to change notification settings - Fork 2
/
DRAW.PAS
489 lines (397 loc) · 13.2 KB
/
DRAW.PAS
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
{$A+,B-,E+,F-,G+,I-,N+,P-,Q-,R-,S-,T-,V-,X+}
unit Draw;
interface
uses GDGfx, Entities, Assets;
procedure DrawMap;
procedure DrawPlayer(const player : Player);
procedure DrawAllFruit;
procedure DrawAllParticles;
procedure DrawGameStatusBackdrop;
procedure DrawPlayerStatuses;
procedure DrawMatchStatus;
procedure DrawBackdrop;
procedure DrawUIFrame(x1, y1, width, height : integer;
const frameBitmaps : UIFrameBitmaps);
procedure FadeOut;
procedure FadeIn;
procedure FadeOutAndIn(delay : word);
procedure BlackOutPalette;
procedure BlitSpriteScaled(x, y: integer; xs, ys: word; const bmp: PBitmap);
implementation
uses GDTimer, FixedP, Toolbox, Maps, Shared;
const
TOMATO_X = 0;
GRAPES_X = 208;
GENERAL_X = 112;
STATUS_Y = 176;
procedure DrawMap;
var
x, y : word;
currentMapTile : ^byte;
oldLayer : integer;
begin
oldLayer := GetBoundLayerIndex;
UseLayer(SCREEN_MAP_LAYER);
currentMapTile := @map.map[0];
for y := 0 to 10 do begin
for x := 0 to 19 do begin
Blitf(x*16, y*16, tiles[currentMapTile^]);
inc(currentMapTile);
end;
end;
UseLayer(oldLayer);
isMapDirty := false;
end;
procedure DrawPlayer(const player : Player);
var
playerIndex, thumbTackIndex : word;
tx, ty : integer;
dir : Direction;
fruit : FruitKind;
skip : boolean;
begin
with player do begin
dir := entity.direction;
fruit := fruitPref;
skip := ((stabbedDebuffTime > 0) or (splashedDebuffTime > 0)) and skipRenderFlag;
{ compute the current final sprite index from the player's
current animation state and facing direction.
note that this value still needs to be added to playerSpriteOffsets
to get the real final sprite index for the correct player sprite set
(based on the player's chosen fruit type). }
playerIndex := GetAnimationFrame(
entity.animation,
playerAnimations[ord(state)],
dir
);
{ if the player is currently stabbing, then also get the thumbtack
sprite index to use }
if state = Stabbing then begin
thumbTackIndex := ord(dir) + thumbTackSpriteOffsets[ord(fruit)];
end;
end;
with player.entity.position do begin
if player.state = Stabbing then begin
{ the player is currently stabbing with their thumb tack, so we
need to render the thumb tack sprite too. the exact position
of the thumb tack sprite varies depending on the player's
facing direction (so as to position it in the player's hand) }
GetThumbTackRenderCoords(player, tx, ty);
if dir = North then begin
{ if we're facing north, the thumb tack sprite should be
rendered first, so that it is layered underneath the player
sprite }
BlitSpritef(tx, ty, sprites[thumbTackIndex]);
if not skip then
BlitSpritef(
FixToInt(x),
FixToInt(y),
sprites[playerIndex + playerSpriteOffsets[ord(fruit)]]
);
end else begin
{ but for every other direction, render the thumb tack sprite
layered on top of the player sprite }
if not skip then
BlitSpritef(
FixToInt(x),
FixToInt(y),
sprites[playerIndex + playerSpriteOffsets[ord(fruit)]]
);
BlitSpritef(tx, ty, sprites[thumbTackIndex]);
end;
end else begin
{ not stabbing, so just render the player sprite itself }
if not skip then
BlitSpritef(
FixToInt(x),
FixToInt(y),
sprites[playerIndex + playerSpriteOffsets[ord(fruit)]]
);
end;
end;
end;
procedure DrawAllFruit;
var
i, index : word;
value, offset : integer;
begin
for i := 0 to numDirtTiles-1 do begin
with dirtTiles[i] do begin
if not hasFruit then continue;
{ compute the final sprite index of the fruit based on its current
animation. note that not all states of the fruit entity actually
use real "animations" ... some are just an animation sequence of
1 frame, and is just being abused to fit into this general
framework ... :-) }
with fruit do begin
index := GetAnimationFrame(
entity.animation,
fruitAnimations[ord(state)],
South
);
end;
case fruit.state of
Plant: begin
{ just a simple plant }
with fruit.entity.position do begin
BlitSpritef(FixToInt(x), FixToInt(y), sprites[index]);
end;
end;
Growing: begin
{ render the fruit sprite, scaled. }
if fruit.isGold then inc(index, GOLD_FRUIT_TILE_OFFSET);
with fruit.entity.position do begin
value := fruit.value; { the pixel width/height of the
fruit sprite }
offset := 8-(value div 2); { the x/y coordinate offset used
to center the fruit sprite
within the map tile it is on }
BlitSpriteScaled(
FixToInt(x) + offset,
FixToInt(y) + offset,
value,
value,
sprites[index + fruitSpriteOffsets[ord(fruit.kind)]]
);
end;
end;
Grown, Popped: begin
{ render the fruit sprite }
if fruit.isGold then inc(index, GOLD_FRUIT_TILE_OFFSET);
with fruit.entity.position do begin
BlitSpritef(
FixToInt(x),
FixToInt(y),
sprites[index + fruitSpriteOffsets[ord(fruit.kind)]]
);
end;
end;
end;
end;
end;
end;
procedure DrawAllParticles;
var
i, index : word;
begin
for i := 0 to MAX_PARTICLES-1 do begin
with particles[i] do begin
if not active then continue;
if animation <> nil then begin
{ particle is a "sprite-animated" particle type. get its current
"final" sprite index based on its animation state }
index := GetAnimationFrame(
entity.animation,
animation^,
entity.direction
);
with entity.position do begin
BlitSpritef(FixToInt(x), FixToInt(y), sprites[index]);
end;
end else begin
{ TODO: "pixel" particle types ... }
end;
end;
end;
end;
procedure DrawGameStatusBackdrop;
var
i, oldLayer : integer;
begin
oldLayer := GetBoundLayerIndex;
UseLayer(SCREEN_MAP_LAYER);
{ tomato player status }
DrawUIFrame(TOMATO_X, STATUS_Y, 112, 24, uiTomatoFrame);
BlitSpritef(TOMATO_X+8, STATUS_Y+4, sprites[PLAYER_TOMATO_TILE_START]);
BlitSpritef(TOMATO_X+28, STATUS_Y+4, sprites[FRUIT_TOMATO_TILE_START]);
{ grapes player status }
DrawUIFrame(GRAPES_X, STATUS_Y, 112, 24, uiGrapesFrame);
BlitSpritef((SCREEN_RIGHT-16)-8, STATUS_Y+4, sprites[PLAYER_GRAPES_TILE_START]);
BlitSpritef((SCREEN_RIGHT-16)-28, STATUS_Y+4, sprites[FRUIT_GRAPES_TILE_START]);
{ general match info }
DrawUIFrame(GENERAL_X, STATUS_Y, 96, 24, uiGeneralFrame);
UseLayer(oldLayer);
isStatusBackdropDirty := false;
end;
procedure DrawPlayerStatuses;
const
TEXT_Y = STATUS_Y+13;
var
x, value : integer;
s : string[3];
begin
UseFont(@chunkyFnt);
with tomatoPlayer^ do begin
{ number of popped tomatoes }
Str(score:3, s);
DrawStringf(TOMATO_X+28, TEXT_Y, TOMATO_TEXT_COLOR, s);
x := TOMATO_X+56;
{ 'stabbed' debuff icon and time left }
if stabbedDebuffTime > 0 then begin
BlitSpritef(x, STATUS_Y+4, sprites[GRAPES_THUMBTACK_TILE]);
value := stabbedDebuffTime div 1000;
Str(value:3, s);
DrawStringf(x, TEXT_Y, DEBUFF_TEXT_COLOR, s);
inc(x, 28);
end;
{ 'splashed' debuff icon and time left }
if splashedDebuffTime > 0 then begin
BlitSpritef(x, STATUS_Y+4, sprites[SPLASH_GRAPES_TILE_START]);
value := splashedDebuffTime div 1000;
Str(value:3, s);
DrawStringf(x, TEXT_Y, DEBUFF_TEXT_COLOR, s);
end;
end;
with grapesPlayer^ do begin
{ number of popped tomatoes }
Str(score:3, s);
DrawStringf(GRAPES_X+67, TEXT_Y, GRAPES_TEXT_COLOR, s);
x := GRAPES_X+67-28;
{ 'stabbed' debuff icon and time left }
if stabbedDebuffTime > 0 then begin
BlitSpritef(x, STATUS_Y+4, sprites[TOMATO_THUMBTACK_TILE]);
value := stabbedDebuffTime div 1000;
Str(value:3, s);
DrawStringf(x, TEXT_Y, DEBUFF_TEXT_COLOR, s);
dec(x, 28);
end;
{ 'splashed' debuff icon and time left }
if splashedDebuffTime > 0 then begin
BlitSpritef(x, STATUS_Y+4, sprites[SPLASH_TOMATO_TILE_START]);
value := splashedDebuffTime div 1000;
Str(value:3, s);
DrawStringf(x, TEXT_Y, DEBUFF_TEXT_COLOR, s);
end;
end;
UseFont(nil);
end;
procedure DrawMatchStatus;
var
totalSeconds, minutes, seconds, seconds10 : word;
s : string[2];
begin
BlitSpritef(GENERAL_X+8, STATUS_Y+4, sprites[TIMER_SPRITE]);
UseFont(nil);
PrintAt(GENERAL_X+8+16+6, STATUS_Y+8);
totalSeconds := matchTime div 1000;
minutes := totalSeconds div 60;
seconds := totalSeconds mod 60;
seconds10 := (matchTime mod 1000) div 100;
Str(minutes:2, s);
PrintString(s, 15);
PrintString(':', 15);
if seconds < 10 then PrintString('0', 15);
PrintWord(seconds, 15);
PrintString('.', 15);
PrintWord(seconds10, 15);
end;
procedure DrawBackdrop;
begin
if isMapDirty then DrawMap;
if isStatusBackdropDirty then DrawGameStatusBackdrop;
CopyLayer(SCREEN_MAP_LAYER);
end;
procedure DrawUIFrame(x1, y1, width, height : integer;
const frameBitmaps : UIFrameBitmaps);
var
i, n, x, y : integer;
middleTilesX, middleTilesY : integer;
begin
{ TODO: this drawing routine will not look so great with dimensions
that are not some multiple of 8 (both width and height) ... }
{ smallest reasonable dimensions that could really work given
the tiles we're currently using for these ... }
if width < 24 then width := 24;
if height < 24 then height := 24;
middleTilesX := (width - (2*8)) div 8;
if middleTilesX < 0 then middleTilesX := 0;
middleTilesY := (height - (2*8)) div 8;
if middleTilesY < 0 then middleTilesY := 0;
{ middle }
for y := 0 to middleTilesY-1 do begin
for x := 0 to middleTilesX-1 do begin
Blitf(x1+8+(x*8), y1+8+(y*8), frameBitmaps[4]);
end;
end;
{ top and bottom border }
for i := 0 to middleTilesX-2 do begin
Blitf(x1+16+(i*8), y1, frameBitmaps[1]);
Blitf(x1+16+(i*8), y1+height-8, frameBitmaps[7]);
end;
{ left and right borders }
for i := 0 to middleTilesY-2 do begin
Blitf(x1, y1+16+(i*8), frameBitmaps[3]);
Blitf(x1+width-8, y1+16+(i*8), frameBitmaps[5]);
end;
{ top-left corner }
BlitSpritef(x1, y1, frameBitmaps[0]);
{ bottom-left corner }
BlitSpritef(x1, y1+height-16, frameBitmaps[6]);
{ top-right corner }
BlitSpritef(x1+width-16, y1, frameBitmaps[2]);
{ bottom-right corner }
BlitSpritef(x1+width-16, y1+height-16, frameBitmaps[8]);
end;
{ ----------------------------------------------------------------------- }
procedure FadeOut;
begin
FadeRangeToColor(0, 255, 0, 0, 0, 4);
end;
procedure FadeIn;
begin
FadeRangeToPalette(0, 255, @pal, 4);
end;
procedure FadeOutAndIn(delay : word);
var
elapsed : word;
begin
FadeRangeToColor(0, 255, 0, 0, 0, 4);
if delay > 0 then begin
elapsed := 0;
MarkTimer;
while elapsed < delay do begin
inc(elapsed, MarkTimer);
end;
end;
FadeRangeToPalette(0, 255, @pal, 4);
end;
procedure BlackOutPalette;
begin
FadeRangeToColor(0, 255, 0, 0, 0, 255);
end;
{ ----------------------------------------------------------------------- }
procedure BlitSpriteScaled(x, y: integer; xs, ys: word; const bmp: PBitmap);
var
width, height : word;
xStep, yStep : fixed;
xIndex, yIndex : fixed;
srcOffset, destOffset : word;
src, dest : PByteArray;
dx, dy : integer;
pixel : byte;
begin
{ TODO: clipping support }
{ TODO: re-write the render loop in assembly }
width := bmp^.Width;
height := bmp^.Height;
xStep := FixDiv(IntToFix(width), IntToFix(xs));
yStep := FixDiv(IntToFix(height), IntToFix(ys));
dest := GetBoundLayerPointerAt(x, y);
destOffset := 0;
src := ptr(Seg(bmp^.Pixels), Ofs(bmp^.Pixels));
srcOffset := 0;
yIndex := 0;
for dy := 0 to (ys-1) do begin
xIndex := 0;
for dx := 0 to (xs-1) do begin
pixel := src^[srcOffset + FixToInt(xIndex)];
if pixel > 0 then
dest^[destOffset + dx] := pixel;
inc(xIndex, xStep);
end;
inc(yIndex, yStep);
inc(destOffset, SCREEN_WIDTH);
srcOffset := width * FixToInt(yIndex);
end;
end;
end.