fruit-popper/DRAW.PAS

490 lines
13 KiB
Plaintext
Raw Normal View History

{$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.