anarch/main.c

883 lines
23 KiB
C
Raw Normal View History

2019-10-03 18:04:14 -04:00
/**
@file main.c
Main source file of the game that puts together all the pieces. main game
logic is implemented here.
by Miloslav Ciz (drummyfish), 2019
Released under CC0 1.0 (https://creativecommons.org/publicdomain/zero/1.0/)
plus a waiver of all other intellectual property. The goal of this work is
be and remain completely in the public domain forever, available for any use
whatsoever.
*/
2019-09-25 09:51:19 -04:00
#include <stdint.h>
2019-10-02 08:42:30 -04:00
/*
The following keys are mandatory to be implemented on any platform in order
for the game to be playable.
*/
2019-09-25 09:51:19 -04:00
#define SFG_KEY_UP 0
#define SFG_KEY_RIGHT 1
#define SFG_KEY_DOWN 2
#define SFG_KEY_LEFT 3
#define SFG_KEY_A 4
#define SFG_KEY_B 5
#define SFG_KEY_C 6
2019-10-02 08:42:30 -04:00
/*
The following keys are optional for a platform to implement. They just make
the controls more comfortable.
*/
#define SFG_KEY_JUMP 7
#define SFG_KEY_STRAFE_LEFT 8
#define SFG_KEY_STRAFE_RIGHT 9
2019-09-25 09:51:19 -04:00
/* ============================= PORTING =================================== */
2019-09-27 06:46:44 -04:00
/* When porting, do the following:
- implement the following functions in your platform_*.h.
- Call SFG_init() from your platform initialization code.
- Call SFG_mainLoopBody() from within your platform's main loop.
- include "settings.h" in your platform_*.h and optionally hard-override
(redefine) some settings in platform_*.h, according to the platform's
needs.
*/
2019-09-25 09:51:19 -04:00
2019-09-29 07:50:40 -04:00
#define SFG_LOG(str) ; ///< Can be redefined to log messages for better debug.
2019-10-02 14:31:27 -04:00
#define SFG_PROGRAM_MEMORY static const /**< Can be redefined to platform's
specifier of program meory. */
2019-10-02 08:42:30 -04:00
/** Return 1 (0) if given key is pressed (not pressed). At least the mandatory
keys have to be implemented, the optional keys don't have to ever return 1.
See the key contant definitions to see which ones are mandatory. */
2019-09-25 09:51:19 -04:00
int8_t SFG_keyPressed(uint8_t key);
/** Return time in ms sice program start. */
uint32_t SFG_getTimeMs();
/** Sleep (yield CPU) for specified amount of ms. This is used to relieve CPU
usage. If your platform doesn't need this or handles it in other way, this
function can do nothing. */
void SFG_sleepMs(uint16_t timeMs);
/** Set specified screen pixel. The function doesn't have to check whether
the coordinates are within screen. */
static inline void SFG_setPixel(uint16_t x, uint16_t y, uint8_t colorIndex);
/* ========================================================================= */
/**
Game main loop body, call this inside the platform's specific main loop.
*/
void SFG_mainLoopBody();
/**
Initializes the whole program, call this in the platform initialization.
*/
void SFG_init();
2019-09-27 10:38:55 -04:00
#ifdef SFG_PLATFORM_POKITTO
#include "platform_pokitto.h"
#else
#include "platform_sdl.h"
#endif
2019-09-25 09:51:19 -04:00
2019-10-02 14:31:27 -04:00
#include "constants.h"
#include "levels.h"
#include "assets.h"
#include "palette.h"
2019-10-04 15:09:10 -04:00
#include "settings.h" // will include if not included by platform
#define RCL_PIXEL_FUNCTION SFG_pixelFunc
#define RCL_TEXTURE_VERTICAL_STRETCH 0
#define RCL_CAMERA_COLL_HEIGHT_BELOW 800
#define RCL_CAMERA_COLL_HEIGHT_ABOVE 100
#include "raycastlib.h"
2019-10-02 14:31:27 -04:00
2019-10-04 10:32:24 -04:00
#define SFG_GAME_RESOLUTION_X \
(SFG_SCREEN_RESOLUTION_X / SFG_RESOLUTION_SCALEDOWN)
#define SFG_GAME_RESOLUTION_Y \
(SFG_SCREEN_RESOLUTION_Y / SFG_RESOLUTION_SCALEDOWN)
2019-09-25 09:51:19 -04:00
#define SFG_MS_PER_FRAME (1000 / SFG_FPS) // ms per frame with target FPS
2019-10-04 15:09:10 -04:00
#if SFG_MS_PER_FRAME == 0
#define SFG_MS_PER_FRAME 1
#endif
#define SFG_PLAYER_TURN_UNITS_PER_FRAME \
2019-09-27 10:38:55 -04:00
((SFG_PLAYER_TURN_SPEED * RCL_UNITS_PER_SQUARE) / (360 * SFG_FPS))
2019-10-04 15:09:10 -04:00
#if SFG_PLAYER_TURN_UNITS_PER_FRAME == 0
#define SFG_PLAYER_TURN_UNITS_PER_FRAME 1
#endif
2019-09-27 10:38:55 -04:00
2019-10-04 15:09:10 -04:00
#define SFG_PLAYER_MOVE_UNITS_PER_FRAME \
((SFG_PLAYER_MOVE_SPEED * RCL_UNITS_PER_SQUARE) / SFG_FPS)
2019-10-01 14:25:21 -04:00
2019-10-04 15:09:10 -04:00
#if SFG_PLAYER_MOVE_UNITS_PER_FRAME == 0
#define SFG_PLAYER_MOVE_UNITS_PER_FRAME 1
#endif
2019-09-25 09:51:19 -04:00
2019-10-04 15:09:10 -04:00
#define SFG_GRAVITY_SPEED_INCREASE_PER_FRAME \
((SFG_GRAVITY_ACCELERATION * RCL_UNITS_PER_SQUARE) / (SFG_FPS * SFG_FPS))
2019-10-01 14:25:21 -04:00
2019-10-04 15:09:10 -04:00
#if SFG_GRAVITY_SPEED_INCREASE_PER_FRAME == 0
#define SFG_GRAVITY_SPEED_INCREASE_PER_FRAME 1
#endif
2019-09-25 09:51:19 -04:00
2019-09-26 21:34:49 -04:00
/**
Step in which walls get higher, in raycastlib units.
*/
#define SFG_WALL_HEIGHT_STEP (RCL_UNITS_PER_SQUARE / 4)
2019-09-30 13:39:21 -04:00
#define SFG_CEILING_MAX_HEIGHT\
(16 * RCL_UNITS_PER_SQUARE - RCL_UNITS_PER_SQUARE / 2 )
2019-10-04 10:32:24 -04:00
int8_t SFG_backgroundScaleMap[SFG_GAME_RESOLUTION_Y];
2019-09-25 19:34:57 -04:00
uint16_t SFG_backgroundScroll;
2019-10-01 14:25:21 -04:00
struct
{
RCL_Camera camera;
2019-10-04 11:42:54 -04:00
int8_t squarePosition[2];
2019-10-01 14:25:21 -04:00
RCL_Vector2D direction;
RCL_Unit verticalSpeed;
2019-10-03 14:09:00 -04:00
RCL_Unit previousVerticalSpeed; /**< Vertical speed in previous frame, needed
for determining whether player is in the
air. */
2019-10-01 14:25:21 -04:00
} SFG_player;
2019-10-04 10:32:24 -04:00
#if SFG_RESOLUTION_SCALEDOWN == 1
#define SFG_setGamePixel SFG_setPixel
#else
/**
Sets the game pixel (a pixel that can potentially be bigger than the screen
pixel).
*/
static inline void SFG_setGamePixel(uint16_t x, uint16_t y, uint8_t colorIndex)
{
uint16_t screenY = y * SFG_RESOLUTION_SCALEDOWN;
uint16_t screenX = x * SFG_RESOLUTION_SCALEDOWN;
for (uint16_t j = screenY; j < screenY + SFG_RESOLUTION_SCALEDOWN; ++j)
for (uint16_t i = screenX; i < screenX + SFG_RESOLUTION_SCALEDOWN; ++i)
SFG_setPixel(i,j,colorIndex);
}
#endif
2019-10-01 14:25:21 -04:00
void SFG_recompurePLayerDirection()
{
SFG_player.camera.direction = RCL_wrap(SFG_player.camera.direction,RCL_UNITS_PER_SQUARE);
SFG_player.direction = RCL_angleToDirection(SFG_player.camera.direction);
SFG_player.direction.x = (SFG_player.direction.x * SFG_PLAYER_MOVE_UNITS_PER_FRAME) / RCL_UNITS_PER_SQUARE;
SFG_player.direction.y = (SFG_player.direction.y * SFG_PLAYER_MOVE_UNITS_PER_FRAME) / RCL_UNITS_PER_SQUARE;
SFG_backgroundScroll =
2019-10-04 10:32:24 -04:00
((SFG_player.camera.direction * 8) * SFG_GAME_RESOLUTION_Y) / RCL_UNITS_PER_SQUARE;
2019-10-01 14:25:21 -04:00
}
void SFG_initPlayer()
{
RCL_initCamera(&SFG_player.camera);
2019-10-04 10:32:24 -04:00
SFG_player.camera.resolution.x = SFG_GAME_RESOLUTION_X / SFG_RAYCASTING_SUBSAMPLE;
SFG_player.camera.resolution.y = SFG_GAME_RESOLUTION_Y;
2019-10-01 14:25:21 -04:00
SFG_player.camera.height = RCL_UNITS_PER_SQUARE * 12;
SFG_player.camera.position.x = RCL_UNITS_PER_SQUARE * 15;
SFG_player.camera.position.y = RCL_UNITS_PER_SQUARE * 8;
SFG_recompurePLayerDirection();
SFG_player.verticalSpeed = 0;
2019-10-03 14:09:00 -04:00
SFG_player.previousVerticalSpeed = 0;
2019-10-01 14:25:21 -04:00
}
RCL_RayConstraints SFG_rayConstraints;
2019-10-02 14:31:27 -04:00
typedef struct
{
uint8_t coords[2];
uint8_t state; /**< door state in format:
2019-10-04 11:18:46 -04:00
MSB ccbaaaaa LSB
2019-10-02 14:31:27 -04:00
aaaaa: current door height (how much they're open)
b: whether currently going up (0) or down (1)
cc: by which keys the door is unlocked
*/
2019-10-04 11:18:46 -04:00
} SFG_DoorRecord;
2019-10-02 14:31:27 -04:00
2019-10-04 11:42:54 -04:00
#define SFG_DOOR_DEFAULT_STATE 0x1f
2019-10-04 15:09:10 -04:00
#define SFG_DOOR_UP_DOWN_MASK 0x20
#define SFG_DOOR_VERTICAL_POSITION_MASK 0x1f
#define SFG_DOOR_HEIGHT_STEP (RCL_UNITS_PER_SQUARE / 0x1f)
#define SFG_DOOR_INCREMENT_PER_FRAME \
(SFG_DOOR_OPEN_SPEED / (SFG_DOOR_HEIGHT_STEP * SFG_FPS))
#if SFG_DOOR_INCREMENT_PER_FRAME == 0
#define SFG_DOOR_INCREMENT_PER_FRAME 1
#endif
2019-10-04 11:42:54 -04:00
2019-10-02 14:31:27 -04:00
#define SFG_MAX_DOORS 32
2019-10-06 10:16:59 -04:00
/**
Holds information about one instance of a level item (a type of level element,
e.g. pickable items, decorations etc.). The format is following:
MSB abbbbbbb LSB
a: active flag, 1 means the item is nearby to player and is active
bbbbbbb: index to elements array of the current level, pointing to element
representing this item
*/
typedef uint8_t SFG_ItemRecord;
2019-10-06 10:47:47 -04:00
#define SFG_ITEM_RECORD_ACTIVE_MASK 0x80
2019-10-06 10:16:59 -04:00
#define SFG_MAX_LEVEL_ITEMS SFG_MAX_LEVEL_ELEMENTS
2019-09-26 15:15:07 -04:00
/**
Stores the current level and helper precomputed vaues for better performance.
*/
struct
{
const SFG_Level *levelPointer;
const uint8_t* textures[7];
2019-10-06 10:16:59 -04:00
2019-09-26 21:34:49 -04:00
uint32_t timeStart;
2019-10-04 11:42:54 -04:00
uint32_t frameStart;
2019-10-06 10:16:59 -04:00
2019-09-29 14:26:53 -04:00
uint8_t floorColor;
uint8_t ceilingColor;
2019-10-06 10:16:59 -04:00
SFG_DoorRecord doorRecords[SFG_MAX_DOORS];
2019-10-02 14:31:27 -04:00
uint8_t doorRecordCount;
2019-10-04 11:42:54 -04:00
uint8_t checkedDoorIndex; ///< Says which door are currently being checked.
2019-10-06 10:16:59 -04:00
SFG_ItemRecord itemRecords[SFG_MAX_LEVEL_ITEMS]; ///< Holds level items
uint8_t itemRecordCount;
2019-10-06 10:47:47 -04:00
uint8_t checkedItemIndex; ///< Same as checkedDoorIndex, but for items.
2019-09-26 15:15:07 -04:00
} SFG_currentLevel;
2019-10-03 17:37:52 -04:00
#if SFG_DITHERED_SHADOW
SFG_PROGRAM_MEMORY uint8_t SFG_ditheringPatterns[] =
{
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,1,0,0,
0,0,0,0,
0,1,0,1,
1,0,1,0,
0,1,0,0,
1,0,1,0,
0,1,0,1,
1,0,1,0,
0,1,1,1,
1,1,1,1,
0,1,0,1,
1,1,1,1,
0,1,1,1,
1,1,1,1,
1,1,1,1
};
#endif
2019-09-25 09:51:19 -04:00
void SFG_pixelFunc(RCL_PixelInfo *pixel)
{
uint8_t color;
uint8_t shadow = 0;
2019-09-29 10:08:38 -04:00
if (pixel->isHorizon && pixel->depth > RCL_UNITS_PER_SQUARE * 16)
{
color = SFG_TRANSPARENT_COLOR;
}
else if (pixel->isWall)
2019-09-25 09:51:19 -04:00
{
2019-09-26 15:15:07 -04:00
uint8_t textureIndex =
pixel->isFloor ?
2019-09-27 13:04:49 -04:00
(
((pixel->hit.type & SFG_TILE_PROPERTY_MASK) != SFG_TILE_PROPERTY_DOOR) ?
(pixel->hit.type & 0x7)
:
(
2019-09-28 15:12:16 -04:00
(pixel->texCoords.y > RCL_UNITS_PER_SQUARE) ?
2019-09-29 10:08:38 -04:00
(pixel->hit.type & 0x7) : 255
2019-09-27 13:04:49 -04:00
)
):
2019-09-26 15:15:07 -04:00
((pixel->hit.type & 0x38) >> 3);
2019-09-29 14:17:31 -04:00
2019-09-27 20:08:28 -04:00
RCL_Unit textureV = pixel->texCoords.y;
2019-09-26 22:41:45 -04:00
2019-09-27 13:04:49 -04:00
if ((pixel->hit.type & SFG_TILE_PROPERTY_MASK) ==
SFG_TILE_PROPERTY_SQUEEZER)
2019-09-27 09:24:36 -04:00
textureV += pixel->wallHeight;
2019-09-26 22:41:45 -04:00
2019-09-25 19:34:57 -04:00
color =
2019-09-26 15:15:07 -04:00
textureIndex != SFG_TILE_TEXTURE_TRANSPARENT ?
2019-09-26 22:12:12 -04:00
(SFG_getTexel(
2019-09-28 15:12:16 -04:00
textureIndex != 255 ?
SFG_currentLevel.textures[textureIndex]:
2019-09-29 03:50:58 -04:00
SFG_texturesWall[SFG_currentLevel.levelPointer->doorTextureIndex],
2019-09-26 22:12:12 -04:00
pixel->texCoords.x / 32,
2019-09-26 22:41:45 -04:00
textureV / 32)
2019-09-26 22:12:12 -04:00
) :
2019-09-25 19:34:57 -04:00
SFG_TRANSPARENT_COLOR;
2019-09-25 09:51:19 -04:00
shadow = pixel->hit.direction >> 1;
2019-09-28 15:12:16 -04:00
2019-09-25 09:51:19 -04:00
}
else
{
2019-09-29 14:26:53 -04:00
color = pixel->isFloor ?
2019-09-30 13:39:21 -04:00
(SFG_currentLevel.floorColor) :
(pixel->height < SFG_CEILING_MAX_HEIGHT ?
SFG_currentLevel.ceilingColor : SFG_TRANSPARENT_COLOR);
2019-09-25 09:51:19 -04:00
}
2019-09-25 19:34:57 -04:00
if (color != SFG_TRANSPARENT_COLOR)
{
2019-09-26 19:25:22 -04:00
#if SFG_DITHERED_SHADOW
2019-10-03 17:37:52 -04:00
uint8_t fogShadow = (pixel->depth * 4) / (RCL_UNITS_PER_SQUARE);
uint8_t fogShadowPart = fogShadow & 0x07;
2019-09-26 19:25:22 -04:00
2019-10-03 17:37:52 -04:00
fogShadow /= 8;
2019-09-26 19:25:22 -04:00
2019-10-03 17:37:52 -04:00
uint8_t xMod4 = pixel->position.x & 0x03;
uint8_t yMod2 = pixel->position.y & 0x01;
2019-09-26 19:25:22 -04:00
2019-10-03 17:37:52 -04:00
shadow +=
fogShadow + SFG_ditheringPatterns[fogShadowPart * 8 + yMod2 * 4 + xMod4];
2019-09-26 19:25:22 -04:00
#else
2019-09-25 19:34:57 -04:00
shadow += pixel->depth / (RCL_UNITS_PER_SQUARE * 2);
2019-09-26 19:25:22 -04:00
#endif
2019-10-04 16:40:41 -04:00
#if SFG_ENABLE_FOG
2019-09-25 19:34:57 -04:00
color = palette_minusValue(color,shadow);
2019-10-04 16:40:41 -04:00
#endif
2019-09-25 19:34:57 -04:00
}
else
{
color = SFG_getTexel(SFG_backgrounds[0],
2019-10-04 10:32:24 -04:00
SFG_backgroundScaleMap[(pixel->position.x * SFG_RAYCASTING_SUBSAMPLE + SFG_backgroundScroll) % SFG_GAME_RESOLUTION_Y],
2019-09-28 15:42:02 -04:00
// ^ TODO: get rid of mod?
2019-09-25 19:34:57 -04:00
SFG_backgroundScaleMap[pixel->position.y]);
}
2019-09-25 09:51:19 -04:00
2019-09-28 15:42:02 -04:00
RCL_Unit screenX = pixel->position.x * SFG_RAYCASTING_SUBSAMPLE;
for (uint8_t i = 0; i < SFG_RAYCASTING_SUBSAMPLE; ++i)
{
2019-10-04 10:32:24 -04:00
SFG_setGamePixel(screenX,pixel->position.y,color);
2019-09-28 15:42:02 -04:00
screenX++;
}
2019-09-25 09:51:19 -04:00
}
2019-10-04 10:32:24 -04:00
#define SFG_MAX_SPRITE_SIZE SFG_GAME_RESOLUTION_X
2019-10-01 19:31:59 -04:00
uint8_t SFG_spriteSamplingPoints[SFG_MAX_SPRITE_SIZE];
void SFG_drawScaledImage(
const uint8_t *image,
int16_t centerX,
int16_t centerY,
int16_t size)
{
if ((size > SFG_MAX_SPRITE_SIZE) || (size == 0))
return;
uint16_t halfSize = size / 2;
int16_t topLeftX = centerX - halfSize;
int16_t topLeftY = centerY - halfSize;
int16_t x0, u0;
if (topLeftX < 0)
{
u0 = -1 * topLeftX;
x0 = 0;
}
else
{
u0 = 0;
x0 = topLeftX;
}
int16_t x1 = topLeftX + size - 1;
2019-10-04 10:32:24 -04:00
if (x1 >= SFG_GAME_RESOLUTION_X)
x1 = SFG_GAME_RESOLUTION_X - 1;
2019-10-01 19:31:59 -04:00
int16_t y0, v0;
if (topLeftY < 0)
{
v0 = -1 * topLeftY;
y0 = 0;
}
else
{
v0 = 0;
y0 = topLeftY;
}
int16_t y1 = topLeftY + size - 1;
2019-10-04 10:32:24 -04:00
if (y1 >= SFG_GAME_RESOLUTION_Y)
y1 = SFG_GAME_RESOLUTION_Y - 1;
2019-10-01 19:31:59 -04:00
if ((x0 > x1) || (y0 > y1)) // completely outside screen?
return;
int16_t u1 = u0 + (x1 - x0);
int16_t v1 = v0 + (y1 - y0);
// precompute sampling positions:
int16_t uMin = RCL_min(u0,u1);
int16_t vMin = RCL_min(v0,v1);
int16_t uMax = RCL_max(u0,u1);
int16_t vMax = RCL_max(v0,v1);
int16_t precompFrom = RCL_min(uMin,vMin);
int16_t precompTo = RCL_max(uMax,vMax);
#define PRECOMP_SCALE 2048
int16_t precompStepScaled = (SFG_TEXTURE_SIZE * PRECOMP_SCALE) / size;
int16_t precompPosScaled = precompFrom * precompStepScaled;
for (int16_t i = precompFrom; i <= precompTo; ++i)
{
SFG_spriteSamplingPoints[i] = precompPosScaled / PRECOMP_SCALE;
precompPosScaled += precompStepScaled;
}
#undef PRECOMP_SCALE
for (int16_t x = x0, u = u0; x <= x1; ++x, ++u)
for (int16_t y = y0, v = v0; y <= y1; ++y, ++v)
2019-10-04 10:32:24 -04:00
SFG_setGamePixel(x,y,SFG_getTexel(image,
2019-10-01 19:31:59 -04:00
SFG_spriteSamplingPoints[u],
SFG_spriteSamplingPoints[v]));
}
2019-09-26 15:15:07 -04:00
RCL_Unit SFG_texturesAt(int16_t x, int16_t y)
2019-09-25 09:51:19 -04:00
{
2019-09-26 22:41:45 -04:00
uint8_t p;
2019-09-26 21:34:49 -04:00
2019-09-30 11:15:15 -04:00
SFG_TileDefinition tile = SFG_getMapTile(SFG_currentLevel.levelPointer,x,y,&p);
2019-09-26 22:41:45 -04:00
return
SFG_TILE_FLOOR_TEXTURE(tile) | (SFG_TILE_CEILING_TEXTURE(tile) << 3) | p;
// ^ store both textures (floor and ceiling) and properties in one number
2019-09-25 09:51:19 -04:00
}
2019-09-26 21:34:49 -04:00
RCL_Unit SFG_movingWallHeight
(
RCL_Unit low,
RCL_Unit high,
uint32_t time
)
{
RCL_Unit height = high - low;
RCL_Unit halfHeight = height / 2;
RCL_Unit sinArg =
(time * ((SFG_MOVING_WALL_SPEED * RCL_UNITS_PER_SQUARE) / 1000)) / height;
return
low + halfHeight + (RCL_sinInt(sinArg) * halfHeight) / RCL_UNITS_PER_SQUARE;
}
2019-09-25 09:51:19 -04:00
RCL_Unit SFG_floorHeightAt(int16_t x, int16_t y)
{
2019-09-26 21:34:49 -04:00
uint8_t properties;
2019-10-04 15:09:10 -04:00
SFG_TileDefinition tile =
SFG_getMapTile(SFG_currentLevel.levelPointer,x,y,&properties);
2019-09-25 09:51:19 -04:00
2019-10-04 15:09:10 -04:00
uint8_t doorHeight = 0;
if (properties == SFG_TILE_PROPERTY_DOOR)
{
for (uint8_t i = 0; i < SFG_currentLevel.doorRecordCount; ++i)
{
2019-10-06 10:16:59 -04:00
SFG_DoorRecord *door = &(SFG_currentLevel.doorRecords[i]);
2019-10-04 15:09:10 -04:00
if ((door->coords[0] == x) && (door->coords[1] == y))
{
doorHeight = door->state & SFG_DOOR_VERTICAL_POSITION_MASK;
break;
}
}
}
else if (properties == SFG_TILE_PROPERTY_ELEVATOR)
{
return SFG_movingWallHeight(
2019-09-26 21:34:49 -04:00
SFG_TILE_FLOOR_HEIGHT(tile) * SFG_WALL_HEIGHT_STEP,
SFG_TILE_CEILING_HEIGHT(tile) * SFG_WALL_HEIGHT_STEP,
SFG_getTimeMs() - SFG_currentLevel.timeStart);
2019-10-04 15:09:10 -04:00
}
return SFG_TILE_FLOOR_HEIGHT(tile) * SFG_WALL_HEIGHT_STEP -
doorHeight * SFG_DOOR_HEIGHT_STEP;
2019-09-25 09:51:19 -04:00
}
RCL_Unit SFG_ceilingHeightAt(int16_t x, int16_t y)
{
2019-09-26 21:34:49 -04:00
uint8_t properties;
2019-09-30 11:15:15 -04:00
SFG_TileDefinition tile = SFG_getMapTile(SFG_currentLevel.levelPointer,x,y,&properties);
2019-09-26 21:34:49 -04:00
if (properties == SFG_TILE_PROPERTY_ELEVATOR)
return SFG_CEILING_MAX_HEIGHT;
2019-09-25 20:40:35 -04:00
uint8_t height = SFG_TILE_CEILING_HEIGHT(tile);
2019-09-27 09:24:36 -04:00
return properties != SFG_TILE_PROPERTY_SQUEEZER ?
(
height != SFG_TILE_CEILING_MAX_HEIGHT ?
((SFG_TILE_FLOOR_HEIGHT(tile) + height) * SFG_WALL_HEIGHT_STEP) :
SFG_CEILING_MAX_HEIGHT
) :
SFG_movingWallHeight(
SFG_TILE_FLOOR_HEIGHT(tile) * SFG_WALL_HEIGHT_STEP,
2019-09-30 15:35:49 -04:00
(SFG_TILE_CEILING_HEIGHT(tile) + SFG_TILE_FLOOR_HEIGHT(tile))
* SFG_WALL_HEIGHT_STEP,
2019-09-27 09:24:36 -04:00
SFG_getTimeMs() - SFG_currentLevel.timeStart);
2019-09-25 09:51:19 -04:00
}
2019-10-03 13:31:25 -04:00
uint32_t SFG_gameFrame;
2019-09-25 09:51:19 -04:00
uint32_t SFG_lastFrameTimeMs;
2019-10-02 08:42:30 -04:00
void SFG_setAndInitLevel(const SFG_Level *level)
2019-09-26 21:34:49 -04:00
{
2019-09-29 07:50:40 -04:00
SFG_LOG("setting and initializing level");
2019-09-26 21:34:49 -04:00
SFG_currentLevel.levelPointer = level;
2019-09-29 14:26:53 -04:00
SFG_currentLevel.floorColor = level->floorColor;
SFG_currentLevel.ceilingColor = level->ceilingColor;
2019-09-26 21:34:49 -04:00
for (uint8_t i = 0; i < 7; ++i)
SFG_currentLevel.textures[i] =
2019-09-29 03:50:58 -04:00
SFG_texturesWall[level->textureIndices[i]];
2019-09-26 21:34:49 -04:00
2019-10-04 11:18:46 -04:00
SFG_LOG("initializing doors");
2019-10-06 10:47:47 -04:00
SFG_currentLevel.checkedDoorIndex = 0;
2019-10-04 11:18:46 -04:00
SFG_currentLevel.doorRecordCount = 0;
for (uint8_t j = 0; j < SFG_MAP_SIZE; ++j)
{
for (uint8_t i = 0; i < SFG_MAP_SIZE; ++i)
{
uint8_t properties;
SFG_getMapTile(level,i,j,&properties);
if ((properties & SFG_TILE_PROPERTY_MASK) == SFG_TILE_PROPERTY_DOOR)
{
SFG_DoorRecord *d =
2019-10-06 10:16:59 -04:00
&(SFG_currentLevel.doorRecords[SFG_currentLevel.doorRecordCount]);
2019-10-04 11:18:46 -04:00
d->coords[0] = i;
d->coords[1] = j;
2019-10-04 11:42:54 -04:00
d->state = SFG_DOOR_DEFAULT_STATE;
2019-10-04 11:18:46 -04:00
SFG_currentLevel.doorRecordCount++;
}
if (SFG_currentLevel.doorRecordCount >= SFG_MAX_DOORS)
break;
}
if (SFG_currentLevel.doorRecordCount >= SFG_MAX_DOORS)
break;
}
2019-10-06 10:16:59 -04:00
SFG_LOG("initializing level elements");
2019-10-06 10:47:47 -04:00
SFG_currentLevel.checkedItemIndex = 0;
2019-10-06 10:16:59 -04:00
SFG_currentLevel.itemRecordCount = 0;
for (uint8_t i = 0; i < SFG_MAX_LEVEL_ELEMENTS; ++i)
{
SFG_LevelElement *e = &(SFG_currentLevel.levelPointer->elements[i]);
if (e->elementType == SFG_LEVEL_ELEMENT_BARREL)
{
2019-10-06 10:47:47 -04:00
SFG_currentLevel.itemRecords[SFG_currentLevel.itemRecordCount] = i;
2019-10-06 10:16:59 -04:00
SFG_currentLevel.itemRecordCount++;
}
}
2019-09-27 10:38:55 -04:00
SFG_currentLevel.timeStart = SFG_getTimeMs();
2019-10-04 11:42:54 -04:00
SFG_currentLevel.frameStart = SFG_gameFrame;
2019-09-26 21:34:49 -04:00
2019-10-01 14:25:21 -04:00
SFG_initPlayer();
2019-09-26 21:34:49 -04:00
}
2019-09-25 09:51:19 -04:00
2019-09-27 10:38:55 -04:00
void SFG_init()
{
2019-09-29 07:50:40 -04:00
SFG_LOG("initializing game")
2019-10-03 13:31:25 -04:00
SFG_gameFrame = 0;
2019-09-27 10:38:55 -04:00
RCL_initRayConstraints(&SFG_rayConstraints);
SFG_rayConstraints.maxHits = SFG_RAYCASTING_MAX_HITS;
SFG_rayConstraints.maxSteps = SFG_RAYCASTING_MAX_STEPS;
2019-10-04 10:32:24 -04:00
for (uint16_t i = 0; i < SFG_GAME_RESOLUTION_Y; ++i)
SFG_backgroundScaleMap[i] =
(i * SFG_TEXTURE_SIZE) / SFG_GAME_RESOLUTION_Y;
2019-09-27 10:38:55 -04:00
SFG_backgroundScroll = 0;
2019-10-02 08:42:30 -04:00
SFG_setAndInitLevel(&SFG_level0);
2019-10-06 10:16:59 -04:00
SFG_lastFrameTimeMs = SFG_getTimeMs();
2019-09-27 10:38:55 -04:00
}
2019-09-25 09:51:19 -04:00
/**
Performs one game step (logic, physics), happening SFG_MS_PER_FRAME after
previous frame.
*/
void SFG_gameStep()
{
int8_t recomputeDirection = 0;
2019-10-01 14:25:21 -04:00
RCL_Vector2D moveOffset;
moveOffset.x = 0;
moveOffset.y = 0;
2019-10-02 08:42:30 -04:00
int8_t strafe = 0;
2019-10-01 14:25:21 -04:00
if (SFG_keyPressed(SFG_KEY_A))
2019-09-25 09:51:19 -04:00
{
2019-10-01 14:25:21 -04:00
if (SFG_keyPressed(SFG_KEY_LEFT))
2019-10-02 08:42:30 -04:00
strafe = -1;
2019-10-01 14:25:21 -04:00
else if (SFG_keyPressed(SFG_KEY_RIGHT))
2019-10-02 08:42:30 -04:00
strafe = 1;
2019-09-25 09:51:19 -04:00
}
2019-10-01 14:25:21 -04:00
else
2019-09-25 09:51:19 -04:00
{
2019-10-01 14:25:21 -04:00
if (SFG_keyPressed(SFG_KEY_LEFT))
{
SFG_player.camera.direction -= SFG_PLAYER_TURN_UNITS_PER_FRAME;
recomputeDirection = 1;
}
else if (SFG_keyPressed(SFG_KEY_RIGHT))
{
SFG_player.camera.direction += SFG_PLAYER_TURN_UNITS_PER_FRAME;
recomputeDirection = 1;
}
if (recomputeDirection)
SFG_recompurePLayerDirection();
2019-09-25 09:51:19 -04:00
}
2019-10-02 08:42:30 -04:00
if (SFG_keyPressed(SFG_KEY_STRAFE_LEFT))
strafe = -1;
else if (SFG_keyPressed(SFG_KEY_STRAFE_RIGHT))
strafe = 1;
if (strafe != 0)
{
moveOffset.x = strafe * SFG_player.direction.y;
moveOffset.y = -1 * strafe * SFG_player.direction.x;
}
2019-10-02 06:02:52 -04:00
#if SFG_PREVIEW_MODE
if (SFG_keyPressed(SFG_KEY_B))
SFG_player.verticalSpeed = SFG_PLAYER_MOVE_UNITS_PER_FRAME;
else if (SFG_keyPressed(SFG_KEY_C))
SFG_player.verticalSpeed = -1 * SFG_PLAYER_MOVE_UNITS_PER_FRAME;
else
SFG_player.verticalSpeed = 0;
#else
2019-10-03 14:09:00 -04:00
RCL_Unit verticalOffset =
2019-10-03 14:24:34 -04:00
(
(
SFG_keyPressed(SFG_KEY_JUMP) ||
(SFG_keyPressed(SFG_KEY_UP) && SFG_keyPressed(SFG_KEY_C))
) &&
(SFG_player.verticalSpeed == 0) &&
2019-10-03 14:09:00 -04:00
(SFG_player.previousVerticalSpeed == 0)) ?
SFG_PLAYER_JUMP_SPEED :
(SFG_player.verticalSpeed - SFG_GRAVITY_SPEED_INCREASE_PER_FRAME);
2019-10-02 06:02:52 -04:00
#endif
2019-09-25 09:51:19 -04:00
if (SFG_keyPressed(SFG_KEY_UP))
{
2019-10-01 14:25:21 -04:00
moveOffset.x += SFG_player.direction.x;
moveOffset.y += SFG_player.direction.y;
2019-09-25 09:51:19 -04:00
}
else if (SFG_keyPressed(SFG_KEY_DOWN))
{
2019-10-01 14:25:21 -04:00
moveOffset.x -= SFG_player.direction.x;
moveOffset.y -= SFG_player.direction.y;
2019-09-25 09:51:19 -04:00
}
2019-10-01 14:25:21 -04:00
RCL_Unit previousHeight = SFG_player.camera.height;
2019-10-02 06:02:52 -04:00
#if SFG_PREVIEW_MODE
SFG_player.camera.position.x +=
SFG_PREVIEW_MODE_SPEED_MULTIPLIER * moveOffset.x;
SFG_player.camera.position.y +=
SFG_PREVIEW_MODE_SPEED_MULTIPLIER * moveOffset.y;
SFG_player.camera.height +=
SFG_PREVIEW_MODE_SPEED_MULTIPLIER * SFG_player.verticalSpeed;
#else
2019-10-01 14:25:21 -04:00
RCL_moveCameraWithCollision(&(SFG_player.camera),moveOffset,
2019-10-03 14:09:00 -04:00
verticalOffset,SFG_floorHeightAt,SFG_ceilingHeightAt,1,1);
2019-10-01 14:25:21 -04:00
2019-10-03 14:09:00 -04:00
SFG_player.previousVerticalSpeed = SFG_player.verticalSpeed;
RCL_Unit limit = RCL_max(RCL_max(0,verticalOffset),SFG_player.verticalSpeed);
2019-10-01 14:25:21 -04:00
SFG_player.verticalSpeed =
2019-10-03 14:09:00 -04:00
RCL_min(limit,SFG_player.camera.height - previousHeight);
/* ^ By "limit" we assure height increase caused by climbing a step doesn't
add vertical velocity. */
2019-10-02 06:02:52 -04:00
#endif
2019-10-04 11:42:54 -04:00
SFG_player.squarePosition[0] =
SFG_player.camera.position.x / RCL_UNITS_PER_SQUARE;
SFG_player.squarePosition[1] =
SFG_player.camera.position.y / RCL_UNITS_PER_SQUARE;
// handle door:
2019-10-06 10:47:47 -04:00
if (SFG_currentLevel.doorRecordCount > 0) // has to be here
{
/* Check one door on whether a player is standing nearby. For performance
reasons we only check one door and move to another in the next frame. */
SFG_DoorRecord *door =
&(SFG_currentLevel.doorRecords[SFG_currentLevel.checkedDoorIndex]);
2019-10-04 11:42:54 -04:00
2019-10-06 10:47:47 -04:00
door->state = (door->state & ~SFG_DOOR_UP_DOWN_MASK) |
(
((door->coords[0] >= (SFG_player.squarePosition[0] - 1)) &&
(door->coords[0] <= (SFG_player.squarePosition[0] + 1)) &&
(door->coords[1] >= (SFG_player.squarePosition[1] - 1)) &&
(door->coords[1] <= (SFG_player.squarePosition[1] + 1))) ?
SFG_DOOR_UP_DOWN_MASK : 0x00
);
2019-10-04 11:42:54 -04:00
2019-10-06 10:47:47 -04:00
SFG_currentLevel.checkedDoorIndex++;
2019-10-04 11:42:54 -04:00
2019-10-06 10:47:47 -04:00
if (SFG_currentLevel.checkedDoorIndex >= SFG_currentLevel.doorRecordCount)
SFG_currentLevel.checkedDoorIndex = 0;
2019-10-04 11:42:54 -04:00
2019-10-06 10:47:47 -04:00
for (uint32_t i = 0; i < SFG_currentLevel.doorRecordCount; ++i)
{
SFG_DoorRecord *door = &(SFG_currentLevel.doorRecords[i]);
2019-10-04 15:09:10 -04:00
2019-10-06 10:47:47 -04:00
int8_t height = door->state & SFG_DOOR_VERTICAL_POSITION_MASK;
height = (door->state & SFG_DOOR_UP_DOWN_MASK) ?
RCL_min(0x1f,height + SFG_DOOR_INCREMENT_PER_FRAME) :
RCL_max(0x00,height - SFG_DOOR_INCREMENT_PER_FRAME);
door->state = (door->state & ~SFG_DOOR_VERTICAL_POSITION_MASK) | height;
}
}
// handle items, in a similar manner to door:
if (SFG_currentLevel.itemRecordCount > 0) // has to be here
2019-10-04 15:09:10 -04:00
{
2019-10-06 10:47:47 -04:00
SFG_ItemRecord item =
SFG_currentLevel.itemRecords[SFG_currentLevel.checkedItemIndex];
item &= ~SFG_ITEM_RECORD_ACTIVE_MASK;
SFG_LevelElement e =
SFG_currentLevel.levelPointer->elements[item];
if (
(RCL_absVal(SFG_player.squarePosition[0] - e.coords[0])
<= SFG_LEVEL_ELEMENT_ACTIVE_DISTANCE)
&&
(RCL_absVal(SFG_player.squarePosition[1] - e.coords[1])
<= SFG_LEVEL_ELEMENT_ACTIVE_DISTANCE)
)
item |= SFG_ITEM_RECORD_ACTIVE_MASK;
2019-10-04 15:09:10 -04:00
2019-10-06 10:47:47 -04:00
SFG_currentLevel.itemRecords[SFG_currentLevel.checkedItemIndex] = item;
2019-10-04 15:09:10 -04:00
2019-10-06 10:47:47 -04:00
SFG_currentLevel.checkedItemIndex++;
2019-10-04 15:09:10 -04:00
2019-10-06 10:47:47 -04:00
if (SFG_currentLevel.checkedItemIndex >= SFG_currentLevel.itemRecordCount)
SFG_currentLevel.checkedItemIndex = 0;
2019-10-04 15:09:10 -04:00
}
2019-09-25 09:51:19 -04:00
}
void SFG_mainLoopBody()
{
/* standard deterministic game loop, independed on actuall achieved FPS,
each game logic (physics) frame is performed with the SFG_MS_PER_FRAME
delta time. */
uint32_t timeNow = SFG_getTimeMs();
2019-10-03 13:31:25 -04:00
uint32_t timeNextFrame = SFG_lastFrameTimeMs + SFG_MS_PER_FRAME;
2019-09-25 09:51:19 -04:00
2019-10-03 13:31:25 -04:00
if (timeNow >= timeNextFrame)
2019-09-25 09:51:19 -04:00
{
2019-10-03 13:31:25 -04:00
uint32_t timeSinceLastFrame = timeNow - SFG_lastFrameTimeMs;
uint8_t steps = 0;
2019-09-25 09:51:19 -04:00
// perform game logic (physics), for each frame
while (timeSinceLastFrame >= SFG_MS_PER_FRAME)
{
SFG_gameStep();
timeSinceLastFrame -= SFG_MS_PER_FRAME;
2019-10-03 13:31:25 -04:00
SFG_gameFrame++;
steps++;
2019-09-25 09:51:19 -04:00
}
2019-10-03 13:31:25 -04:00
if (steps > 1)
SFG_LOG("Failed to reach target FPS! Consider setting a lower value.")
2019-09-25 09:51:19 -04:00
// render noly once
2019-10-01 14:25:21 -04:00
RCL_renderComplex(SFG_player.camera,SFG_floorHeightAt,SFG_ceilingHeightAt,SFG_texturesAt,SFG_rayConstraints);
2019-09-25 09:51:19 -04:00
SFG_lastFrameTimeMs = timeNow;
}
2019-09-29 07:50:40 -04:00
else
2019-10-03 13:31:25 -04:00
{
SFG_sleepMs((timeNextFrame - timeNow) / 2); // wait, relieve CPU
}
2019-09-25 09:51:19 -04:00
}