You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

5025 lines
138 KiB

/**
@file game.h
Main source file of Anarch the game that puts together all the pieces. main
game logic is implemented here.
2 years ago
physics notes (you can break this when messing around with game constants):
- Lowest ceiling under which player can fit is 4 height steps.
- Widest hole over which player can run without jumping is 1 square.
- Widest hole over which the player can jump is 3 squares.
- Highest step a player can walk onto without jumping is 2 height steps.
- Highest step a player can jump onto is 3 height steps.
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.
*/
#ifndef _SFG_GAME_H
#define _SFG_GAME_H
#include <stdint.h> // Needed for fixed width types, can easily be replaced.
/*
The following keys are mandatory to be implemented on any platform in order
for the game to be playable. Enums are bloat.
*/
#define SFG_KEY_UP 0
#define SFG_KEY_RIGHT 1
#define SFG_KEY_DOWN 2
#define SFG_KEY_LEFT 3
2 years ago
#define SFG_KEY_A 4 ///< fire, confirm
#define SFG_KEY_B 5 ///< cancel, strafe, look up/down
#define SFG_KEY_C 6 ///< menu, jump, switch weapons
/*
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
#define SFG_KEY_MAP 10
#define SFG_KEY_TOGGLE_FREELOOK 11
#define SFG_KEY_NEXT_WEAPON 12
#define SFG_KEY_PREVIOUS_WEAPON 13
#define SFG_KEY_MENU 14
#define SFG_KEY_CYCLE_WEAPON 15
#define SFG_KEY_COUNT 16 ///< Number of keys.
/* ============================= PORTING =================================== */
/* When porting, do the following:
2 years ago
- Include this file (and possibly other optional files, like sounds.h) in
3 years ago
your main_*.c frontend source.
- Implement the following functions in your frontend source.
- Call SFG_init() from your frontend initialization code.
- Call SFG_mainLoopBody() from within your frontend main loop.
If your platform is an AVR CPU (e.g. some Arduinos) and so has Harvard
architecture, define #SFG_AVR 1 before including this file in your frontend
source. */
#ifndef SFG_LOG
3 years ago
#define SFG_LOG(str) {} ///< Can be redefined to log game messages.
#endif
#ifndef SFG_CPU_LOAD
#define SFG_CPU_LOAD(percent) {} ///< Can be redefined to check CPU load in %.
#endif
2 years ago
#ifndef SFG_GAME_STEP_COMMAND
#define SFG_GAME_STEP_COMMAND {} /**< Will be called each simlation step (good
for creating deterministic behavior such as
2 years ago
demos (SFG_mainLoopBody() calls potentially
2 years ago
multiple simulation steps). */
#endif
/**
Returns 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.
3 years ago
See the key constant definitions to see which ones are mandatory.
*/
int8_t SFG_keyPressed(uint8_t key);
/**
2 years ago
Optional function for mouse/joystick/analog controls, gets mouse x and y
offset in pixels from the game screen center (to achieve classic FPS mouse
controls the platform should center the mouse after this call). If the
platform isn't using a mouse, this function can simply return [0,0] offset at
each call, or even do nothing at all (leave the variables as are).
*/
void SFG_getMouseOffset(int16_t *x, int16_t *y);
/**
2 years ago
Returns time in milliseconds 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);
/**
3 years ago
Set specified screen pixel. ColorIndex is the index of the game's palette.
The function doesn't have to (and shouldn't, for the sake of performance)
2 years ago
check whether the coordinates are within screen bounds.
*/
static inline void SFG_setPixel(uint16_t x, uint16_t y, uint8_t colorIndex);
/**
Play given sound effect (SFX). This function may or may not use the sound
3 years ago
samples provided in sounds.h, and it may or may not ignore the (logarithmic)
2 years ago
volume parameter (0 to 255). Depending on the platform, the function can play
completely different samples or even e.g. just beeps. If the platform can't
play sounds, this function implementation can simply be left empty. This
function doesn't have to implement safety measures, the back end takes cares
of them.
*/
void SFG_playSound(uint8_t soundIndex, uint8_t volume);
#define SFG_MUSIC_TURN_OFF 0
#define SFG_MUSIC_TURN_ON 1
#define SFG_MUSIC_NEXT 2
/**
Informs the frontend how music should play, e.g. turn on/off, change track,
2 years ago
... See SFG_MUSIC_* constants. Playing music is optional and the frontend may
ignore this. If a frontend wants to implement music, it can use the bytebeat
provided in sounds.h or use its own.
*/
void SFG_setMusic(uint8_t value);
2 years ago
#define SFG_EVENT_VIBRATE 0 ///< the controller should vibrate (or blink etc.)
3 years ago
#define SFG_EVENT_PLAYER_HURT 1
#define SFG_EVENT_PLAYER_DIES 2
#define SFG_EVENT_LEVEL_STARTS 3
#define SFG_EVENT_LEVEL_WON 4
#define SFG_EVENT_MONSTER_DIES 5
#define SFG_EVENT_PLAYER_TAKES_ITEM 6
#define SFG_EVENT_EXPLOSION 7
#define SFG_EVENT_PLAYER_TELEPORTS 8
#define SFG_EVENT_PLAYER_CHANGES_WEAPON 9
/**
This is an optional function that informs the frontend about special events
3 years ago
which may trigger something special on the platform, such as a controller
2 years ago
vibration, logging etc. The implementation of this function may be left empty.
3 years ago
*/
void SFG_processEvent(uint8_t event, uint8_t data);
2 years ago
#define SFG_SAVE_SIZE 12 ///< size of the save in bytes
/**
3 years ago
Optional function for permanently saving the game state. Platforms that don't
have permanent storage (HDD, EEPROM etc.) may let this function simply do
nothing. If implemented, the function should save the passed data into its
permanent storage, e.g. a file, a cookie etc.
*/
void SFG_save(uint8_t data[SFG_SAVE_SIZE]);
/**
Optional function for retrieving game data that were saved to permanent
storage. Platforms without permanent storage may let this function do nothing.
If implemented, the function should fill the passed array with data from
permanent storage, e.g. a file, a cookie etc.
3 years ago
If this function is called before SFG_save was ever called and no data is
present in permanent memory, this function should do nothing (leave the data
array as is).
2 years ago
This function should return 1 if saving/loading is possible or 0 if not (this
3 years ago
will be used by the game to detect saving/loading capability).
*/
3 years ago
uint8_t SFG_load(uint8_t data[SFG_SAVE_SIZE]);
/* ========================================================================= */
/**
2 years ago
Main game loop body, call this inside your platform's specific main loop.
Returns 1 if the game continues or 0 if the game was exited and program should
3 years ago
halt. This functions handles reaching the target FPS and sleeping for
relieving CPU, so don't do this.
*/
3 years ago
uint8_t SFG_mainLoopBody();
/**
3 years ago
Initializes the game, call this in the platform's initialization code.
*/
void SFG_init();
#include "settings.h"
#if SFG_AVR
#include <avr/pgmspace.h>
3 years ago
#define SFG_PROGRAM_MEMORY const PROGMEM
#define SFG_PROGRAM_MEMORY_U8(addr) pgm_read_byte(addr)
#else
#define SFG_PROGRAM_MEMORY static const
#define SFG_PROGRAM_MEMORY_U8(addr) ((uint8_t) (*(addr)))
#endif
3 years ago
#include "images.h" // don't change the order of these includes
#include "levels.h"
#include "texts.h"
#include "palette.h"
#if SFG_TEXTURE_DISTANCE == 0
#define RCL_COMPUTE_WALL_TEXCOORDS 0
#endif
#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 200
2 years ago
#define RCL_HORIZONTAL_FOV SFG_FOV_HORIZONTAL
#define RCL_VERTICAL_FOV SFG_FOV_VERTICAL
2 years ago
#include "raycastlib.h"
#include "constants.h"
typedef struct
{
uint8_t coords[2];
3 years ago
uint8_t state; /**< door state in format:
3 years ago
MSB ccbaaaaa LSB
3 years ago
aaaaa: current door height (how much they're open)
b: whether currently going up (0) or down (1)
cc: by which card (key) the door is unlocked, 00
means no card (unlocked), 1 means card 0 etc. */
} SFG_DoorRecord;
#define SFG_SPRITE_SIZE(size0to3) \
(((size0to3 + 3) * SFG_BASE_SPRITE_SIZE) / 4)
#define SFG_SPRITE_SIZE_TO_HEIGHT_ABOVE_GROUND(size0to3) \
(SFG_SPRITE_SIZE(size0to3) / 2)
2 years ago
#define SFG_SPRITE_SIZE_PIXELS(size0to3) \
((SFG_SPRITE_SIZE(size0to3) * SFG_SPRITE_MAX_SIZE) / RCL_UNITS_PER_SQUARE)
/**
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;
#define SFG_ITEM_RECORD_ACTIVE_MASK 0x80
#define SFG_ITEM_RECORD_LEVEL_ELEMENT(itemRecord) \
(SFG_currentLevel.levelPointer->elements[itemRecord & \
~SFG_ITEM_RECORD_ACTIVE_MASK])
typedef struct
{
3 years ago
uint8_t stateType; /**< Holds state (lower 4 bits) and type of monster (upper
4 bits). */
uint8_t coords[2]; /**< monster position, in 1/4s of a square */
uint8_t health;
} SFG_MonsterRecord;
#define SFG_MR_STATE(mr) ((mr).stateType & SFG_MONSTER_MASK_STATE)
#define SFG_MR_TYPE(mr) \
(SFG_MONSTER_INDEX_TO_TYPE(((mr).stateType & SFG_MONSTER_MASK_TYPE) >> 4))
#define SFG_MONSTER_COORD_TO_RCL_UNITS(c) ((RCL_UNITS_PER_SQUARE / 8) + c * 256)
#define SFG_MONSTER_COORD_TO_SQUARES(c) (c / 4)
#define SFG_ELEMENT_COORD_TO_RCL_UNITS(c) \
(c * RCL_UNITS_PER_SQUARE + RCL_UNITS_PER_SQUARE / 2)
#define SFG_MONSTER_MASK_STATE 0x0f
#define SFG_MONSTER_MASK_TYPE 0xf0
#define SFG_MONSTER_STATE_INACTIVE 0 ///< Not nearby, not actively updated.
#define SFG_MONSTER_STATE_IDLE 1
#define SFG_MONSTER_STATE_ATTACKING 2
#define SFG_MONSTER_STATE_HURTING 3
#define SFG_MONSTER_STATE_DYING 4
#define SFG_MONSTER_STATE_GOING_N 5
#define SFG_MONSTER_STATE_GOING_NE 6
#define SFG_MONSTER_STATE_GOING_E 7
#define SFG_MONSTER_STATE_GOING_SE 8
#define SFG_MONSTER_STATE_GOING_S 9
#define SFG_MONSTER_STATE_GOING_SW 10
#define SFG_MONSTER_STATE_GOING_W 11
#define SFG_MONSTER_STATE_GOING_NW 12
#define SFG_MONSTER_STATE_DEAD 13
typedef struct
{
uint8_t type;
3 years ago
uint8_t doubleFramesToLive; /**< This number times two (because 255 could be
too little at high FPS) says after how many
frames the projectile is destroyed. */
uint16_t position[3]; /**< Current position, stored as u16 to save space, as
that is exactly enough to store position on 64x64
map. */
int16_t direction[3]; /**< Added to position each game step. */
} SFG_ProjectileRecord;
2 years ago
#define SFG_GAME_STATE_INIT 0 ///< first state, waiting for key releases
#define SFG_GAME_STATE_PLAYING 1
#define SFG_GAME_STATE_WIN 2
#define SFG_GAME_STATE_LOSE 3
#define SFG_GAME_STATE_INTRO 4
#define SFG_GAME_STATE_OUTRO 5
#define SFG_GAME_STATE_MAP 6
#define SFG_GAME_STATE_LEVEL_START 7
2 years ago
#define SFG_GAME_STATE_MENU 8
#define SFG_MENU_ITEM_CONTINUE 0
#define SFG_MENU_ITEM_MAP 1
#define SFG_MENU_ITEM_PLAY 2
#define SFG_MENU_ITEM_LOAD 3
#define SFG_MENU_ITEM_SOUND 4
#define SFG_MENU_ITEM_SHEAR 5
#define SFG_MENU_ITEM_EXIT 6
#define SFG_MENU_ITEM_NONE 255
/*
GLOBAL VARIABLES
===============================================================================
*/
/**
Groups global variables related to the game as such in a single struct. There
are still other global structs for player, level etc.
*/
struct
{
3 years ago
uint8_t state; ///< Current game state.
2 years ago
uint32_t stateTime; ///< Time in ms from last state change.
uint8_t currentRandom; ///< for RNG
uint8_t spriteAnimationFrame;
uint8_t soundsPlayedThisFrame; /**< Each bit says whether given sound was
played this frame, prevents playing too many
sounds at once. */
2 years ago
RCL_RayConstraints rayConstraints; ///< Ray constraints for rendering.
RCL_RayConstraints visibilityRayConstraints; ///< Constraints for visibility.
uint8_t keyStates[SFG_KEY_COUNT]; /**< Pressed states of keys, each value
stores the number of frames for which the
key has been held. */
uint8_t zBuffer[SFG_Z_BUFFER_SIZE];
uint8_t textureAverageColors[SFG_WALL_TEXTURE_COUNT]; /**< Contains average
color for each wall texture. */
int8_t backgroundScaleMap[SFG_GAME_RESOLUTION_Y];
uint16_t backgroundScroll;
uint8_t spriteSamplingPoints[SFG_MAX_SPRITE_SIZE]; /**< Helper for
precomputing sprite
sampling positions for
drawing. */
3 years ago
uint32_t frameTime; ///< time (in ms) of the current frame start
uint32_t frame; ///< frame number
uint8_t selectedMenuItem;
3 years ago
uint8_t selectedLevel; ///< level to play selected in the main menu
uint8_t antiSpam; ///< Prevents log message spamming.
uint8_t settings; /**< dynamic game settings (can be changed at runtime),
bit meaning:
MSB -------- LSB
||||
|||\_ sound (SFX)
||\__ music
|\___ shearing
\____ freelook (shearing not sliding back) */
uint8_t blink; ///< Says whether blinkg is currently on or off.
3 years ago
uint8_t saved; /**< Helper variable to know if game was saved. Can be
0 (not saved), 1 (just saved) or 255 (can't save).*/
2 years ago
uint8_t cheatState; /**< Highest bit say whether cheat is enabled, other bits
represent the state of typing the cheat code. */
3 years ago
uint8_t save[SFG_SAVE_SIZE]; /**< Stores the game save state that's kept in
the persistent memory.
The save format is binary and platform independent.
The save contains game settings, game progress and a
saved position. The format is as follows:
2 years ago
0 4b (less signif.) highest level that has been reached
2 years ago
0 4b (more signif.) level number of the saved position (0: no save)
2 years ago
1 8b game settings (SFG_game.settings)
2 8b health at saved position
3 8b bullet ammo at saved position
4 8b rocket ammo at saved position
5 8b plasma ammo at saved position
6 32b little endian total play time, in 10ths of sec
10 16b little endian total enemies killed from start */
3 years ago
uint8_t continues; ///< Whether the game continues or was exited.
} SFG_game;
3 years ago
#define SFG_SAVE_TOTAL_TIME (SFG_game.save[6] + SFG_game.save[7] * 256 + \
SFG_game.save[8] * 65536 + SFG_game.save[9] * 4294967296)
/**
Stores player state.
*/
struct
{
RCL_Camera camera;
int8_t squarePosition[2];
RCL_Vector2D direction;
RCL_Unit verticalSpeed;
2 years ago
RCL_Unit previousVerticalSpeed; /**< Vertical speed in previous frame, needed
for determining whether player is in the
air. */
uint16_t headBobFrame;
uint8_t weapon; ///< currently selected weapon
uint8_t health;
2 years ago
uint32_t weaponCooldownFrames; ///< frames left for weapon cooldown
uint32_t lastHurtFrame;
uint32_t lastItemTakenFrame;
uint8_t ammo[SFG_AMMO_TOTAL];
3 years ago
uint8_t cards; /**< Lowest 3 bits say which access cards
2 years ago
have been taken, the next 3 bits say
3 years ago
which cards should be blinking in the HUD,
the last 2 bits are a blink reset counter. */
uint8_t justTeleported;
2 years ago
int8_t previousWeaponDirection; ///< Direction (+/0/-) of previous weapon.
} SFG_player;
/**
2 years ago
Stores the current level and helper precomputed values for better performance.
*/
struct
{
const SFG_Level *levelPointer;
uint8_t levelNumber;
3 years ago
const uint8_t* textures[7]; ///< textures the level is using
uint32_t timeStart;
uint32_t frameStart;
2 years ago
uint32_t completionTime10sOfS; ///< completion time in 10ths of second
uint8_t floorColor;
uint8_t ceilingColor;
SFG_DoorRecord doorRecords[SFG_MAX_DOORS];
uint8_t doorRecordCount;
uint8_t checkedDoorIndex; ///< Says which door are currently being checked.
SFG_ItemRecord itemRecords[SFG_MAX_ITEMS]; ///< Holds level items.
uint8_t itemRecordCount;
uint8_t checkedItemIndex; ///< Same as checkedDoorIndex, but for items.
SFG_MonsterRecord monsterRecords[SFG_MAX_MONSTERS];
uint8_t monsterRecordCount;
uint8_t checkedMonsterIndex;
SFG_ProjectileRecord projectileRecords[SFG_MAX_PROJECTILES];
uint8_t projectileRecordCount;
uint8_t bossCount;
uint8_t monstersDead;
uint8_t backgroundImage;
2 years ago
uint8_t teleporterCount;
uint16_t mapRevealMask; /**< Bits say which parts of the map have been
revealed. */
uint8_t itemCollisionMap[(SFG_MAP_SIZE * SFG_MAP_SIZE) / 8];
/**< Bit array, for each map square says whether there
is a colliding item or not. */
} SFG_currentLevel;
2 years ago
#if SFG_AVR
/**
Copy of the current level that is stored in RAM. This is only done on Arduino
3 years ago
because accessing it in program memory (PROGMEM) directly would be a pain.
Because of this Arduino needs more RAM.
*/
SFG_Level SFG_ramLevel;
#endif
/**
Helper function for accessing the itemCollisionMap bits.
*/
void SFG_getItemCollisionMapIndex(
uint8_t x, uint8_t y, uint16_t *byte, uint8_t *bit)
{
uint16_t index = y * SFG_MAP_SIZE + x;
*byte = index / 8;
*bit = index % 8;
}
void SFG_setItemCollisionMapBit(uint8_t x, uint8_t y, uint8_t value)
{
uint16_t byte;
uint8_t bit;
SFG_getItemCollisionMapIndex(x,y,&byte,&bit);
SFG_currentLevel.itemCollisionMap[byte] &= ~(0x01 << bit);
SFG_currentLevel.itemCollisionMap[byte] |= (value & 0x01) << bit;
}
uint8_t SFG_getItemCollisionMapBit(uint8_t x, uint8_t y)
{
uint16_t byte;
uint8_t bit;
SFG_getItemCollisionMapIndex(x,y,&byte,&bit);
return (SFG_currentLevel.itemCollisionMap[byte] >> bit) & 0x01;
}
#if SFG_DITHERED_SHADOW
3 years ago
static const 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
/*
FUNCTIONS
===============================================================================
*/
/**
2 years ago
Returns a pseudorandom byte. This is a very simple congruent generator, its
parameters have been chosen so that each number (0-255) is included in the
output exactly once!
*/
uint8_t SFG_random()
{
SFG_game.currentRandom *= 13;
SFG_game.currentRandom += 7;
return SFG_game.currentRandom;
}
void SFG_playGameSound(uint8_t soundIndex, uint8_t volume)
{
if (!(SFG_game.settings & 0x01))
return;
uint8_t mask = 0x01 << soundIndex;
if (!(SFG_game.soundsPlayedThisFrame & mask))
{
SFG_playSound(soundIndex,volume);
SFG_game.soundsPlayedThisFrame |= mask;
}
}
/**
Returns a damage value for specific attack type (SFG_WEAPON_FIRE_TYPE_...),
with added randomness (so the values will differ). For explosion pass
SFG_WEAPON_FIRE_TYPE_FIREBALL.
*/
uint8_t SFG_getDamageValue(uint8_t attackType)
{
if (attackType >= SFG_WEAPON_FIRE_TYPES_TOTAL)
return 0;
int32_t value = SFG_attackDamageTable[attackType]; // has to be signed
int32_t maxAdd = (value * SFG_DAMAGE_RANDOMNESS) / 256;
value = value + (maxAdd / 2) - (SFG_random() * maxAdd / 256);
if (value < 0)
value = 0;
return value;
}
/**
Saves game data to persistent storage.
*/
void SFG_gameSave()
{
3 years ago
if (SFG_game.saved == SFG_CANT_SAVE)
return;
3 years ago
SFG_LOG("saving game data");
3 years ago
SFG_save(SFG_game.save);
}
/**
Loads game data from persistent storage.
*/
void SFG_gameLoad()
{
3 years ago
if (SFG_game.saved == SFG_CANT_SAVE)
return;
3 years ago
SFG_LOG("loading game data");
3 years ago
uint8_t result = SFG_load(SFG_game.save);
3 years ago
if (result == 0)
SFG_game.saved = SFG_CANT_SAVE;
}
/**
Returns ammo type for given weapon.
*/
uint8_t SFG_weaponAmmo(uint8_t weapon)
{
if (weapon == SFG_WEAPON_KNIFE)
return SFG_AMMO_NONE;
if (weapon == SFG_WEAPON_MACHINE_GUN ||
weapon == SFG_WEAPON_SHOTGUN)
return SFG_AMMO_BULLETS;
else if (weapon == SFG_WEAPON_ROCKET_LAUNCHER)
return SFG_AMMO_ROCKETS;
else
return SFG_AMMO_PLASMA;
}
RCL_Unit SFG_taxicabDistance(
RCL_Unit x0, RCL_Unit y0, RCL_Unit z0, RCL_Unit x1, RCL_Unit y1, RCL_Unit z1)
{
return (RCL_abs(x0 - x1) + RCL_abs(y0 - y1) + RCL_abs(z0 - z1));
}
uint8_t SFG_isInActiveDistanceFromPlayer(RCL_Unit x, RCL_Unit y, RCL_Unit z)
{
return SFG_taxicabDistance(
x,y,z,SFG_player.camera.position.x,SFG_player.camera.position.y,
SFG_player.camera.height) <= SFG_LEVEL_ELEMENT_ACTIVE_DISTANCE;
}
/**
Function called when a level end to compute the stats etc.
*/
void SFG_levelEnds()
{
3 years ago
SFG_currentLevel.completionTime10sOfS = (SFG_MS_PER_FRAME *
(SFG_game.frame - SFG_currentLevel.frameStart)) / 100;
if (
(SFG_player.health != 0) &&
(SFG_currentLevel.levelNumber >= (SFG_game.save[0] & 0x0f)) &&
((SFG_currentLevel.levelNumber + 1) < SFG_NUMBER_OF_LEVELS))
2 years ago
{
SFG_game.save[0] = // save progress
(SFG_game.save[0] & 0xf0) | (SFG_currentLevel.levelNumber + 1);
SFG_gameSave();
}
SFG_currentLevel.monstersDead = 0;
2 years ago
for (uint16_t i = 0; i < SFG_currentLevel.monsterRecordCount; ++i)
if (SFG_currentLevel.monsterRecords[i].health == 0)
SFG_currentLevel.monstersDead++;
3 years ago
uint32_t totalTime = SFG_SAVE_TOTAL_TIME;
if ((SFG_currentLevel.levelNumber == 0) || (totalTime != 0))
{
SFG_LOG("Updating save totals.");
totalTime += SFG_currentLevel.completionTime10sOfS;
for (uint8_t i = 0; i < 4; ++i)
{
SFG_game.save[6 + i] = totalTime % 256;
totalTime /= 256;
}
SFG_game.save[10] += SFG_currentLevel.monstersDead % 256;
SFG_game.save[11] += SFG_currentLevel.monstersDead / 256;
}
SFG_game.save[2] = SFG_player.health;
SFG_game.save[3] = SFG_player.ammo[0];
SFG_game.save[4] = SFG_player.ammo[1];
SFG_game.save[5] = SFG_player.ammo[2];
}
static inline uint8_t SFG_RCLUnitToZBuffer(RCL_Unit x)
{
x /= (RCL_UNITS_PER_SQUARE / 8);
uint8_t okay = x < 256;
return okay * (x + 1) - 1;
}
const uint8_t *SFG_getMonsterSprite(
uint8_t monsterType, uint8_t state, uint8_t frame)
{
uint8_t index =
state == SFG_MONSTER_STATE_DEAD ? 18 : 17;
3 years ago
// ^ makes the compiled binary smaller compared to returning pointers directly
if ((state != SFG_MONSTER_STATE_DYING) && (state != SFG_MONSTER_STATE_DEAD))
switch (monsterType)
{
case SFG_LEVEL_ELEMENT_MONSTER_SPIDER:
switch (state)
{
case SFG_MONSTER_STATE_ATTACKING: index = 1; break;
case SFG_MONSTER_STATE_IDLE: index = 0; break;
default: index = frame ? 0 : 2; break;
}
break;
case SFG_LEVEL_ELEMENT_MONSTER_WARRIOR:
index = state != SFG_MONSTER_STATE_ATTACKING ? 6 : 7;
break;
case SFG_LEVEL_ELEMENT_MONSTER_DESTROYER:
switch (state)
{
case SFG_MONSTER_STATE_ATTACKING: index = 4; break;
case SFG_MONSTER_STATE_IDLE: index = 3; break;
default: index = frame ? 3 : 5; break;
}
break;
case SFG_LEVEL_ELEMENT_MONSTER_PLASMABOT:
index = state != SFG_MONSTER_STATE_ATTACKING ? 8 : 9;
break;
case SFG_LEVEL_ELEMENT_MONSTER_ENDER:
switch (state)
{
case SFG_MONSTER_STATE_ATTACKING: index = 12; break;
case SFG_MONSTER_STATE_IDLE: index = 10; break;
default: index = frame ? 10 : 11; break;
}
break;
case SFG_LEVEL_ELEMENT_MONSTER_TURRET:
switch (state)
{
case SFG_MONSTER_STATE_ATTACKING: index = 15; break;
case SFG_MONSTER_STATE_IDLE: index = 13; break;
default: index = frame ? 13 : 14; break;
}
break;
case SFG_LEVEL_ELEMENT_MONSTER_EXPLODER:
default:
index = 16;
break;
}
return SFG_monsterSprites + index * SFG_TEXTURE_STORE_SIZE;
}
/**
Says whether given key is currently pressed (down). This should be preferred
to SFG_keyPressed().
*/
uint8_t SFG_keyIsDown(uint8_t key)
{
return SFG_game.keyStates[key] != 0;
}
/**
Says whether given key has been pressed in the current frame.
*/
uint8_t SFG_keyJustPressed(uint8_t key)
{
return (SFG_game.keyStates[key]) == 1;
}
/**
Says whether a key is being repeated after being held for certain time.
*/
uint8_t SFG_keyRepeated(uint8_t key)
{
return
((SFG_game.keyStates[key] >= SFG_KEY_REPEAT_DELAY_FRAMES) ||
(SFG_game.keyStates[key] == 255)) &&
(SFG_game.frame % SFG_KEY_REPEAT_PERIOD_FRAMES == 0);
}
uint16_t SFG_keyRegisters(uint8_t key)
{
return SFG_keyJustPressed(key) || SFG_keyRepeated(key);
}
#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
void SFG_recomputePLayerDirection()
{
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_game.backgroundScroll =
((SFG_player.camera.direction * 8) * SFG_GAME_RESOLUTION_Y)
/ RCL_UNITS_PER_SQUARE;
}
#if SFG_BACKGROUND_BLUR != 0
uint8_t SFG_backgroundBlurIndex = 0;
3 years ago
static const int8_t SFG_backgroundBlurOffsets[8] =
{
0 * SFG_BACKGROUND_BLUR,
16 * SFG_BACKGROUND_BLUR,
7 * SFG_BACKGROUND_BLUR,
17 * SFG_BACKGROUND_BLUR,
1 * SFG_BACKGROUND_BLUR,
4 * SFG_BACKGROUND_BLUR,
15 * SFG_BACKGROUND_BLUR,
9 * SFG_BACKGROUND_BLUR,
};
#endif
static inline uint8_t SFG_fogValueDiminish(RCL_Unit depth)
{
return depth / SFG_FOG_DIMINISH_STEP;
}
static inline uint8_t
SFG_getTexelFull(uint8_t textureIndex,RCL_Unit u, RCL_Unit v)
{
return
SFG_getTexel(
textureIndex != 255 ?
SFG_currentLevel.textures[textureIndex] :
(SFG_wallTextures + SFG_currentLevel.levelPointer->doorTextureIndex
2 years ago
* SFG_TEXTURE_STORE_SIZE),
u / (RCL_UNITS_PER_SQUARE / SFG_TEXTURE_SIZE),
v / (RCL_UNITS_PER_SQUARE / SFG_TEXTURE_SIZE));
}
static inline uint8_t SFG_getTexelAverage(uint8_t textureIndex)
{
return
textureIndex != 255 ?
SFG_game.textureAverageColors[
SFG_currentLevel.levelPointer->textureIndices[textureIndex]]
:
(
SFG_game.textureAverageColors[
SFG_currentLevel.levelPointer->doorTextureIndex]
+ 1 // to distinguish from normal walls
);
}
void SFG_pixelFunc(RCL_PixelInfo *pixel)
{
uint8_t color;
uint8_t shadow = 0;
if (pixel->isHorizon && pixel->depth > RCL_UNITS_PER_SQUARE * 16)
{
color = SFG_TRANSPARENT_COLOR;
}
else if (pixel->isWall)