Continue game states

This commit is contained in:
Miloslav Číž 2020-02-16 13:20:22 +01:00
parent b4f7efacfa
commit ad60208c3f
4 changed files with 447 additions and 330 deletions

34
main.c
View File

@ -37,8 +37,9 @@
#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_COUNT 14 ///< Number of keys.
#define SFG_KEY_COUNT 15 ///< Number of keys.
/* ============================= PORTING =================================== */
@ -223,6 +224,7 @@ typedef struct
#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_MENU_ITEM_CONTINUE 0
#define SFG_MENU_ITEM_MAP 1
@ -2283,6 +2285,14 @@ void SFG_updateLevel()
*/
void SFG_gameStepPlaying()
{
if (
(SFG_keyIsDown(SFG_KEY_C) && SFG_keyIsDown(SFG_KEY_DOWN)) ||
SFG_keyIsDown(SFG_KEY_MENU))
{
SFG_setGameState(SFG_GAME_STATE_MENU);
return;
}
int8_t recomputeDirection = 0;
RCL_Vector2D moveOffset;
@ -2825,8 +2835,10 @@ void SFG_gameStepPlaying()
uint8_t SFG_getMenuItem(uint8_t index)
{
if (index <= SFG_MENU_ITEM_EXIT)
return index;
uint8_t start = (SFG_currentLevel.levelPointer == 0) ? 2 : 0;
if (index <= (SFG_MENU_ITEM_EXIT - start))
return start + index;
return SFG_MENU_ITEM_NONE;
}
@ -2860,6 +2872,14 @@ void SFG_gameStepMenu()
SFG_setGameState(SFG_GAME_STATE_PLAYING);
break;
case SFG_MENU_ITEM_CONTINUE:
SFG_setGameState(SFG_GAME_STATE_PLAYING);
break;
case SFG_MENU_ITEM_MAP:
SFG_setGameState(SFG_GAME_STATE_MAP);
break;
default:
break;
}
@ -2931,6 +2951,12 @@ void SFG_gameStep()
break;
}
case SFG_GAME_STATE_MAP:
if (SFG_keyIsDown(SFG_KEY_B))
SFG_setGameState(SFG_GAME_STATE_MENU);
break;
default:
break;
}
@ -3292,7 +3318,7 @@ void SFG_draw()
return;
}
if (SFG_keyPressed(SFG_KEY_MAP))
if (SFG_keyPressed(SFG_KEY_MAP) || (SFG_game.state == SFG_GAME_STATE_MAP))
{
SFG_drawMap();
}

View File

@ -145,6 +145,10 @@ int8_t SFG_keyPressed(uint8_t key)
return sdlKeyboardState[SDL_SCANCODE_N];
break;
case SFG_KEY_MENU:
return sdlKeyboardState[SDL_SCANCODE_X];
break;
default: return 0; break;
}
}

View File

@ -37,8 +37,9 @@
#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_COUNT 14 ///< Number of keys.
#define SFG_KEY_COUNT 15 ///< Number of keys.
/* ============================= PORTING =================================== */
@ -223,6 +224,7 @@ typedef struct
#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_MENU_ITEM_CONTINUE 0
#define SFG_MENU_ITEM_MAP 1
@ -621,7 +623,8 @@ void SFG_pixelFunc(RCL_PixelInfo *pixel)
uint8_t zValue = pixel->isWall ? SFG_RCLUnitToZBuffer(pixel->depth) : 255;
for (uint8_t i = 0; i < SFG_RAYCASTING_SUBSAMPLE; ++i)
SFG_game.zBuffer[pixel->position.x * SFG_RAYCASTING_SUBSAMPLE + i] = zValue;
SFG_game.zBuffer[pixel->position.x * SFG_RAYCASTING_SUBSAMPLE + i] =
zValue;
}
if (pixel->isHorizon && pixel->depth > RCL_UNITS_PER_SQUARE * 16)
@ -1205,8 +1208,6 @@ void SFG_init()
{
SFG_LOG("initializing game")
SFG_setGameState(SFG_GAME_STATE_MENU);
SFG_game.frame = 0;
SFG_game.currentRandom = 0;
@ -1227,6 +1228,13 @@ void SFG_init()
SFG_game.selectedMenuItem = 0;
SFG_game.selectedLevel = 0;
SFG_player.freeLook = 0;
#if SFG_START_LEVEL == 0
SFG_setGameState(SFG_GAME_STATE_MENU);
#else
SFG_setAndInitLevel(&SFG_levels[SFG_START_LEVEL - 1]);
SFG_setGameState(SFG_GAME_STATE_PLAYING);
#endif
}
void SFG_getPlayerWeaponInfo(
@ -1952,11 +1960,339 @@ void SFG_getLevelElementSprite(
}
}
/**
Updates a frame of the currently loaded level, i.e. enemies, projectiles,
aimations etc., with the exception of player.
*/
void SFG_updateLevel()
{
// update projectiles:
uint8_t substractFrames =
(SFG_game.frame - SFG_currentLevel.frameStart) & 0x01 ? 1 : 0;
// ^ only substract frames to live every other frame
for (int8_t i = 0; i < SFG_currentLevel.projectileRecordCount; ++i)
{ // ^ has to be signed
SFG_ProjectileRecord *p = &(SFG_currentLevel.projectileRecords[i]);
uint8_t attackType = 255;
if (p->type == SFG_PROJECTILE_BULLET)
attackType = SFG_WEAPON_FIRE_TYPE_BULLET;
else if (p->type == SFG_PROJECTILE_PLASMA)
attackType = SFG_WEAPON_FIRE_TYPE_PLASMA;
RCL_Unit pos[3]; // we have to convert from uint16_t because under/overflows
uint8_t eliminate = 0;
for (uint8_t j = 0; j < 3; ++j)
{
pos[j] = p->position[j];
pos[j] += p->direction[j];
if ( // projectile outside map?
(pos[j] < 0) ||
(pos[j] >= (SFG_MAP_SIZE * RCL_UNITS_PER_SQUARE)))
{
eliminate = 1;
break;
}
}
if (p->doubleFramesToLive == 0) // no more time to live?
{
eliminate = 1;
}
else if (
(p->type != SFG_PROJECTILE_EXPLOSION) &&
(p->type != SFG_PROJECTILE_DUST))
{
if (SFG_projectileCollides( // collides with player?
p,
SFG_player.camera.position.x,
SFG_player.camera.position.y,
SFG_player.camera.height))
{
eliminate = 1;
SFG_playerChangeHealth(-1 * SFG_getDamageValue(attackType));
}
// check collision with the map
if (!eliminate &&
((SFG_floorHeightAt(pos[0] / RCL_UNITS_PER_SQUARE,pos[1] /
RCL_UNITS_PER_SQUARE) >= pos[2])
||
(SFG_ceilingHeightAt(pos[0] / RCL_UNITS_PER_SQUARE,pos[1] /
RCL_UNITS_PER_SQUARE) <= pos[2]))
)
eliminate = 1;
// check collision with active level elements
if (!eliminate) // monsters
for (uint16_t j = 0; j < SFG_currentLevel.monsterRecordCount; ++j)
{
SFG_MonsterRecord *m = &(SFG_currentLevel.monsterRecords[j]);
if (SFG_MR_STATE(*m) != SFG_MONSTER_STATE_INACTIVE)
{
if (SFG_projectileCollides(p,
SFG_MONSTER_COORD_TO_RCL_UNITS(m->coords[0]),
SFG_MONSTER_COORD_TO_RCL_UNITS(m->coords[1]),
SFG_floorHeightAt(
SFG_MONSTER_COORD_TO_SQUARES(m->coords[0]),
SFG_MONSTER_COORD_TO_SQUARES(m->coords[1]))
))
{
eliminate = 1;
SFG_monsterChangeHealth(m,-1 * SFG_getDamageValue(attackType));
break;
}
}
}
if (!eliminate) // items
for (uint16_t j = 0; j < SFG_currentLevel.itemRecordCount; ++j)
{
const SFG_LevelElement *e = SFG_getActiveItemElement(j);
if (e != 0)
{
RCL_Unit x = SFG_ELEMENT_COORD_TO_RCL_UNITS(e->coords[0]);
RCL_Unit y = SFG_ELEMENT_COORD_TO_RCL_UNITS(e->coords[1]);
RCL_Unit z = SFG_floorHeightAt(e->coords[0],e->coords[1]);
if (SFG_projectileCollides(p,x,y,z))
{
if (
(e->type == SFG_LEVEL_ELEMENT_BARREL) &&
(SFG_getDamageValue(attackType) >=
SFG_BARREL_EXPLOSION_DAMAGE_THRESHOLD)
)
{
SFG_explodeBarrel(j,x,y,z);
}
eliminate = 1;
break;
}
}
}
}
if (eliminate)
{
if (p->type == SFG_PROJECTILE_FIREBALL)
SFG_createExplosion(p->position[0],p->position[1],p->position[2]);
else if (p->type == SFG_PROJECTILE_BULLET)
SFG_createDust(p->position[0],p->position[1],p->position[2]);
else if (p->type == SFG_PROJECTILE_PLASMA)
SFG_playSoundSafe(4,SFG_distantSoundVolume(pos[0],pos[1],pos[2]));
// remove the projectile
for (uint8_t j = i; j < SFG_currentLevel.projectileRecordCount - 1; ++j)
SFG_currentLevel.projectileRecords[j] =
SFG_currentLevel.projectileRecords[j + 1];
SFG_currentLevel.projectileRecordCount--;
i--;
}
else
{
p->position[0] = pos[0];
p->position[1] = pos[1];
p->position[2] = pos[2];
}
p->doubleFramesToLive -= substractFrames;
}
// handle door:
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 a few doors and move to others in the next
frame. */
for (uint16_t i = 0;
i < RCL_min(SFG_ELEMENT_DISTANCES_CHECKED_PER_FRAME,
SFG_currentLevel.doorRecordCount);
++i)
{
SFG_DoorRecord *door =
&(SFG_currentLevel.doorRecords[SFG_currentLevel.checkedDoorIndex]);
uint8_t upDownState = door->state & SFG_DOOR_UP_DOWN_MASK;
uint8_t lock = SFG_DOOR_LOCK(door->state);
uint8_t newUpDownState =
(
((lock == 0) || (SFG_player.cards & (1 << (lock - 1)))) &&
(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;
if (upDownState != newUpDownState)
SFG_playSoundSafe(1,255);
door->state = (door->state & ~SFG_DOOR_UP_DOWN_MASK) | newUpDownState;
SFG_currentLevel.checkedDoorIndex++;
if (SFG_currentLevel.checkedDoorIndex >= SFG_currentLevel.doorRecordCount)
SFG_currentLevel.checkedDoorIndex = 0;
}
// move door up/down:
for (uint32_t i = 0; i < SFG_currentLevel.doorRecordCount; ++i)
{
SFG_DoorRecord *door = &(SFG_currentLevel.doorRecords[i]);
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
{
// check item distances:
for (uint16_t i = 0;
i < RCL_min(SFG_ELEMENT_DISTANCES_CHECKED_PER_FRAME,
SFG_currentLevel.itemRecordCount);
++i)
{
SFG_ItemRecord item =
SFG_currentLevel.itemRecords[SFG_currentLevel.checkedItemIndex];
item &= ~SFG_ITEM_RECORD_ACTIVE_MASK;
SFG_LevelElement e =
SFG_currentLevel.levelPointer->elements[item];
if (
SFG_isInActiveDistanceFromPlayer(
e.coords[0] * RCL_UNITS_PER_SQUARE + RCL_UNITS_PER_SQUARE / 2,
e.coords[1] * RCL_UNITS_PER_SQUARE + RCL_UNITS_PER_SQUARE / 2,
SFG_floorHeightAt(e.coords[0],e.coords[1]) + RCL_UNITS_PER_SQUARE / 2)
)
item |= SFG_ITEM_RECORD_ACTIVE_MASK;
SFG_currentLevel.itemRecords[SFG_currentLevel.checkedItemIndex] = item;
SFG_currentLevel.checkedItemIndex++;
if (SFG_currentLevel.checkedItemIndex >= SFG_currentLevel.itemRecordCount)
SFG_currentLevel.checkedItemIndex = 0;
}
}
// similarly handle monsters:
if (SFG_currentLevel.monsterRecordCount > 0) // has to be here
{
// check monster distances:
for (uint16_t i = 0;
i < RCL_min(SFG_ELEMENT_DISTANCES_CHECKED_PER_FRAME,
SFG_currentLevel.monsterRecordCount);
++i)
{
SFG_MonsterRecord *monster =
&(SFG_currentLevel.monsterRecords[SFG_currentLevel.checkedMonsterIndex]);
if ( // far away from the player?
!SFG_isInActiveDistanceFromPlayer(
SFG_MONSTER_COORD_TO_RCL_UNITS(monster->coords[0]),
SFG_MONSTER_COORD_TO_RCL_UNITS(monster->coords[1]),
SFG_floorHeightAt(
SFG_MONSTER_COORD_TO_SQUARES(monster->coords[0]),
SFG_MONSTER_COORD_TO_SQUARES(monster->coords[1]))
+ RCL_UNITS_PER_SQUARE / 2
)
)
{
monster->stateType =
(monster->stateType & SFG_MONSTER_MASK_TYPE) |
SFG_MONSTER_STATE_INACTIVE;
}
else if (SFG_MR_STATE(*monster) == SFG_MONSTER_STATE_INACTIVE)
{
monster->stateType =
(monster->stateType & SFG_MONSTER_MASK_TYPE) |
SFG_MONSTER_STATE_IDLE;
}
SFG_currentLevel.checkedMonsterIndex++;
if (SFG_currentLevel.checkedMonsterIndex >=
SFG_currentLevel.monsterRecordCount)
SFG_currentLevel.checkedMonsterIndex = 0;
}
}
// update AI and handle dead monsters:
if ((SFG_game.frame - SFG_currentLevel.frameStart) %
SFG_AI_UPDATE_FRAME_INTERVAL == 0)
{
for (uint16_t i = 0; i < SFG_currentLevel.monsterRecordCount; ++i)
{
SFG_MonsterRecord *monster = &(SFG_currentLevel.monsterRecords[i]);
uint8_t state = SFG_MR_STATE(*monster);
if (state == SFG_MONSTER_STATE_DYING)
{
// remove dead
for (uint16_t j = i; j < SFG_currentLevel.monsterRecordCount - 1; ++j)
SFG_currentLevel.monsterRecords[j] =
SFG_currentLevel.monsterRecords[j + 1];
SFG_currentLevel.monsterRecordCount -= 1;
i--;
}
else if (monster->health == 0)
{
monster->stateType = SFG_MR_TYPE(*monster) | SFG_MONSTER_STATE_DYING;
SFG_playSoundSafe(2,255);
}
else if (state != SFG_MONSTER_STATE_INACTIVE)
{
#if SFG_PREVIEW_MODE == 0
SFG_monsterPerformAI(monster);
#endif
}
}
}
}
/**
Part of SFG_gameStep() for SFG_GAME_STATE_PLAYING.
*/
void SFG_gameStepPlaying()
{
if (
(SFG_keyIsDown(SFG_KEY_C) && SFG_keyIsDown(SFG_KEY_DOWN)) ||
SFG_keyIsDown(SFG_KEY_MENU))
{
SFG_setGameState(SFG_GAME_STATE_MENU);
return;
}
int8_t recomputeDirection = 0;
RCL_Vector2D moveOffset;
@ -2486,324 +2822,23 @@ void SFG_gameStepPlaying()
SFG_player.squarePosition[1] =
SFG_player.camera.position.y / RCL_UNITS_PER_SQUARE;
// update projectiles:
SFG_updateLevel();
uint8_t substractFrames =
(SFG_game.frame - SFG_currentLevel.frameStart) & 0x01 ? 1 : 0;
// ^ only substract frames to live every other frame
for (int8_t i = 0; i < SFG_currentLevel.projectileRecordCount; ++i)
{ // ^ has to be signed
SFG_ProjectileRecord *p = &(SFG_currentLevel.projectileRecords[i]);
uint8_t attackType = 255;
if (p->type == SFG_PROJECTILE_BULLET)
attackType = SFG_WEAPON_FIRE_TYPE_BULLET;
else if (p->type == SFG_PROJECTILE_PLASMA)
attackType = SFG_WEAPON_FIRE_TYPE_PLASMA;
RCL_Unit pos[3]; // we have to convert from uint16_t because under/overflows
uint8_t eliminate = 0;
for (uint8_t j = 0; j < 3; ++j)
{
pos[j] = p->position[j];
pos[j] += p->direction[j];
if ( // projectile outside map?
(pos[j] < 0) ||
(pos[j] >= (SFG_MAP_SIZE * RCL_UNITS_PER_SQUARE)))
{
eliminate = 1;
break;
}
}
if (p->doubleFramesToLive == 0) // no more time to live?
{
eliminate = 1;
}
else if (
(p->type != SFG_PROJECTILE_EXPLOSION) &&
(p->type != SFG_PROJECTILE_DUST))
{
if (SFG_projectileCollides( // collides with player?
p,
SFG_player.camera.position.x,
SFG_player.camera.position.y,
SFG_player.camera.height))
{
eliminate = 1;
SFG_playerChangeHealth(-1 * SFG_getDamageValue(attackType));
}
// check collision with the map
if (!eliminate &&
((SFG_floorHeightAt(pos[0] / RCL_UNITS_PER_SQUARE,pos[1] /
RCL_UNITS_PER_SQUARE) >= pos[2])
||
(SFG_ceilingHeightAt(pos[0] / RCL_UNITS_PER_SQUARE,pos[1] /
RCL_UNITS_PER_SQUARE) <= pos[2]))
)
eliminate = 1;
// check collision with active level elements
if (!eliminate) // monsters
for (uint16_t j = 0; j < SFG_currentLevel.monsterRecordCount; ++j)
{
SFG_MonsterRecord *m = &(SFG_currentLevel.monsterRecords[j]);
if (SFG_MR_STATE(*m) != SFG_MONSTER_STATE_INACTIVE)
{
if (SFG_projectileCollides(p,
SFG_MONSTER_COORD_TO_RCL_UNITS(m->coords[0]),
SFG_MONSTER_COORD_TO_RCL_UNITS(m->coords[1]),
SFG_floorHeightAt(
SFG_MONSTER_COORD_TO_SQUARES(m->coords[0]),
SFG_MONSTER_COORD_TO_SQUARES(m->coords[1]))
))
{
eliminate = 1;
SFG_monsterChangeHealth(m,-1 * SFG_getDamageValue(attackType));
break;
}
}
}
if (!eliminate) // items
for (uint16_t j = 0; j < SFG_currentLevel.itemRecordCount; ++j)
{
const SFG_LevelElement *e = SFG_getActiveItemElement(j);
if (e != 0)
{
RCL_Unit x = SFG_ELEMENT_COORD_TO_RCL_UNITS(e->coords[0]);
RCL_Unit y = SFG_ELEMENT_COORD_TO_RCL_UNITS(e->coords[1]);
RCL_Unit z = SFG_floorHeightAt(e->coords[0],e->coords[1]);
if (SFG_projectileCollides(p,x,y,z))
{
if (
(e->type == SFG_LEVEL_ELEMENT_BARREL) &&
(SFG_getDamageValue(attackType) >=
SFG_BARREL_EXPLOSION_DAMAGE_THRESHOLD)
)
{
SFG_explodeBarrel(j,x,y,z);
}
eliminate = 1;
break;
}
}
}
}
if (eliminate)
{
if (p->type == SFG_PROJECTILE_FIREBALL)
SFG_createExplosion(p->position[0],p->position[1],p->position[2]);
else if (p->type == SFG_PROJECTILE_BULLET)
SFG_createDust(p->position[0],p->position[1],p->position[2]);
else if (p->type == SFG_PROJECTILE_PLASMA)
SFG_playSoundSafe(4,SFG_distantSoundVolume(pos[0],pos[1],pos[2]));
// remove the projectile
for (uint8_t j = i; j < SFG_currentLevel.projectileRecordCount - 1; ++j)
SFG_currentLevel.projectileRecords[j] =
SFG_currentLevel.projectileRecords[j + 1];
SFG_currentLevel.projectileRecordCount--;
i--;
}
else
{
p->position[0] = pos[0];
p->position[1] = pos[1];
p->position[2] = pos[2];
}
p->doubleFramesToLive -= substractFrames;
}
// handle door:
if (SFG_currentLevel.doorRecordCount > 0) // has to be here
#if SFG_IMMORTAL == 0
if (SFG_player.health == 0)
{
/* Check one door on whether a player is standing nearby. For performance
reasons we only check a few doors and move to others in the next
frame. */
for (uint16_t i = 0;
i < RCL_min(SFG_ELEMENT_DISTANCES_CHECKED_PER_FRAME,
SFG_currentLevel.doorRecordCount);
++i)
{
SFG_DoorRecord *door =
&(SFG_currentLevel.doorRecords[SFG_currentLevel.checkedDoorIndex]);
uint8_t upDownState = door->state & SFG_DOOR_UP_DOWN_MASK;
uint8_t lock = SFG_DOOR_LOCK(door->state);
uint8_t newUpDownState =
(
((lock == 0) || (SFG_player.cards & (1 << (lock - 1)))) &&
(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;
if (upDownState != newUpDownState)
SFG_playSoundSafe(1,255);
door->state = (door->state & ~SFG_DOOR_UP_DOWN_MASK) | newUpDownState;
SFG_currentLevel.checkedDoorIndex++;
if (SFG_currentLevel.checkedDoorIndex >= SFG_currentLevel.doorRecordCount)
SFG_currentLevel.checkedDoorIndex = 0;
}
// move door up/down:
for (uint32_t i = 0; i < SFG_currentLevel.doorRecordCount; ++i)
{
SFG_DoorRecord *door = &(SFG_currentLevel.doorRecords[i]);
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;
}
SFG_LOG("player dies");
SFG_setGameState(SFG_GAME_STATE_LOSE);
}
// handle items, in a similar manner to door:
if (SFG_currentLevel.itemRecordCount > 0) // has to be here
{
// check item distances:
for (uint16_t i = 0;
i < RCL_min(SFG_ELEMENT_DISTANCES_CHECKED_PER_FRAME,
SFG_currentLevel.itemRecordCount);
++i)
{
SFG_ItemRecord item =
SFG_currentLevel.itemRecords[SFG_currentLevel.checkedItemIndex];
item &= ~SFG_ITEM_RECORD_ACTIVE_MASK;
SFG_LevelElement e =
SFG_currentLevel.levelPointer->elements[item];
if (
SFG_isInActiveDistanceFromPlayer(
e.coords[0] * RCL_UNITS_PER_SQUARE + RCL_UNITS_PER_SQUARE / 2,
e.coords[1] * RCL_UNITS_PER_SQUARE + RCL_UNITS_PER_SQUARE / 2,
SFG_floorHeightAt(e.coords[0],e.coords[1]) + RCL_UNITS_PER_SQUARE / 2)
)
item |= SFG_ITEM_RECORD_ACTIVE_MASK;
SFG_currentLevel.itemRecords[SFG_currentLevel.checkedItemIndex] = item;
SFG_currentLevel.checkedItemIndex++;
if (SFG_currentLevel.checkedItemIndex >= SFG_currentLevel.itemRecordCount)
SFG_currentLevel.checkedItemIndex = 0;
}
}
// similarly handle monsters:
if (SFG_currentLevel.monsterRecordCount > 0) // has to be here
{
// check monster distances:
for (uint16_t i = 0;
i < RCL_min(SFG_ELEMENT_DISTANCES_CHECKED_PER_FRAME,
SFG_currentLevel.monsterRecordCount);
++i)
{
SFG_MonsterRecord *monster =
&(SFG_currentLevel.monsterRecords[SFG_currentLevel.checkedMonsterIndex]);
if ( // far away from the player?
!SFG_isInActiveDistanceFromPlayer(
SFG_MONSTER_COORD_TO_RCL_UNITS(monster->coords[0]),
SFG_MONSTER_COORD_TO_RCL_UNITS(monster->coords[1]),
SFG_floorHeightAt(
SFG_MONSTER_COORD_TO_SQUARES(monster->coords[0]),
SFG_MONSTER_COORD_TO_SQUARES(monster->coords[1]))
+ RCL_UNITS_PER_SQUARE / 2
)
)
{
monster->stateType =
(monster->stateType & SFG_MONSTER_MASK_TYPE) |
SFG_MONSTER_STATE_INACTIVE;
}
else if (SFG_MR_STATE(*monster) == SFG_MONSTER_STATE_INACTIVE)
{
monster->stateType =
(monster->stateType & SFG_MONSTER_MASK_TYPE) |
SFG_MONSTER_STATE_IDLE;
}
SFG_currentLevel.checkedMonsterIndex++;
if (SFG_currentLevel.checkedMonsterIndex >=
SFG_currentLevel.monsterRecordCount)
SFG_currentLevel.checkedMonsterIndex = 0;
}
}
// update AI and handle dead monsters:
if ((SFG_game.frame - SFG_currentLevel.frameStart) %
SFG_AI_UPDATE_FRAME_INTERVAL == 0)
{
for (uint16_t i = 0; i < SFG_currentLevel.monsterRecordCount; ++i)
{
SFG_MonsterRecord *monster = &(SFG_currentLevel.monsterRecords[i]);
uint8_t state = SFG_MR_STATE(*monster);
if (state == SFG_MONSTER_STATE_DYING)
{
// remove dead
for (uint16_t j = i; j < SFG_currentLevel.monsterRecordCount - 1; ++j)
SFG_currentLevel.monsterRecords[j] =
SFG_currentLevel.monsterRecords[j + 1];
SFG_currentLevel.monsterRecordCount -= 1;
i--;
}
else if (monster->health == 0)
{
monster->stateType = SFG_MR_TYPE(*monster) | SFG_MONSTER_STATE_DYING;
SFG_playSoundSafe(2,255);
}
else if (state != SFG_MONSTER_STATE_INACTIVE)
{
#if SFG_PREVIEW_MODE == 0
SFG_monsterPerformAI(monster);
#endif
}
}
}
}
uint8_t SFG_getMenuItem(uint8_t index)
{
if (index <= SFG_MENU_ITEM_EXIT)
return index;
uint8_t start = (SFG_currentLevel.levelPointer == 0) ? 2 : 0;
if (index <= (SFG_MENU_ITEM_EXIT - start))
return start + index;
return SFG_MENU_ITEM_NONE;
}
@ -2837,6 +2872,14 @@ void SFG_gameStepMenu()
SFG_setGameState(SFG_GAME_STATE_PLAYING);
break;
case SFG_MENU_ITEM_CONTINUE:
SFG_setGameState(SFG_GAME_STATE_PLAYING);
break;
case SFG_MENU_ITEM_MAP:
SFG_setGameState(SFG_GAME_STATE_MAP);
break;
default:
break;
}
@ -2885,6 +2928,35 @@ void SFG_gameStep()
SFG_gameStepMenu();
break;
case SFG_GAME_STATE_LOSE:
{
// player die animation
SFG_updateLevel();
int32_t t = SFG_game.frameTime - SFG_game.stateChangeTime;
RCL_Unit h = SFG_floorHeightAt(
SFG_player.squarePosition[0],
SFG_player.squarePosition[1]);
SFG_player.camera.height =
RCL_max(h,h + ((SFG_LOSE_ANIMATION_DURATION - t) *
RCL_CAMERA_COLL_HEIGHT_BELOW) / SFG_LOSE_ANIMATION_DURATION);
SFG_player.camera.shear =
RCL_min(SFG_CAMERA_MAX_SHEAR_PIXELS / 4,
(t * (SFG_CAMERA_MAX_SHEAR_PIXELS / 4)) / SFG_LOSE_ANIMATION_DURATION);
break;
}
case SFG_GAME_STATE_MAP:
if (SFG_keyIsDown(SFG_KEY_B))
SFG_setGameState(SFG_GAME_STATE_MENU);
break;
default:
break;
}
@ -3246,7 +3318,7 @@ void SFG_draw()
return;
}
if (SFG_keyPressed(SFG_KEY_MAP))
if (SFG_keyPressed(SFG_KEY_MAP) || (SFG_game.state == SFG_GAME_STATE_MAP))
{
SFG_drawMap();
}
@ -3255,19 +3327,33 @@ void SFG_draw()
for (uint16_t i = 0; i < SFG_Z_BUFFER_SIZE; ++i)
SFG_game.zBuffer[i] = 255;
int16_t weaponBobOffset;
int16_t weaponBobOffset = 0;
#if SFG_HEADBOB_ENABLED
RCL_Unit bobSin = RCL_sinInt(SFG_player.headBobFrame);
RCL_Unit headBobOffset = 0;
RCL_Unit headBobOffset =
(bobSin * SFG_HEADBOB_OFFSET) / RCL_UNITS_PER_SQUARE;
if (SFG_game.state != SFG_GAME_STATE_LOSE)
{
RCL_Unit bobSin = RCL_sinInt(SFG_player.headBobFrame);
weaponBobOffset =
(bobSin * SFG_WEAPONBOB_OFFSET_PIXELS) / (RCL_UNITS_PER_SQUARE) +
SFG_WEAPONBOB_OFFSET_PIXELS;
headBobOffset = (bobSin * SFG_HEADBOB_OFFSET) / RCL_UNITS_PER_SQUARE;
weaponBobOffset =
(bobSin * SFG_WEAPONBOB_OFFSET_PIXELS) / (RCL_UNITS_PER_SQUARE) +
SFG_WEAPONBOB_OFFSET_PIXELS;
}
else
{
// player die animation
int32_t t = SFG_game.frameTime - SFG_game.stateChangeTime;
weaponBobOffset = (SFG_WEAPON_IMAGE_SCALE * SFG_TEXTURE_SIZE * t) /
SFG_LOSE_ANIMATION_DURATION;
}
// add head bob just for the rendering
SFG_player.camera.height += headBobOffset;
#endif
@ -3409,7 +3495,7 @@ void SFG_draw()
}
#if SFG_HEADBOB_ENABLED
// substract head bob after rendering
// after rendering sprites substract back the head bob offset
SFG_player.camera.height -= headBobOffset;
#endif
@ -3460,8 +3546,9 @@ void SFG_draw()
// border indicator
if (SFG_game.frame - SFG_player.lastHurtFrame
<= SFG_HUD_BORDER_INDICATOR_DURATION_FRAMES)
if ((SFG_game.frame - SFG_player.lastHurtFrame
<= SFG_HUD_BORDER_INDICATOR_DURATION_FRAMES) ||
(SFG_game.state == SFG_GAME_STATE_LOSE))
SFG_drawIndicationBorder(SFG_HUD_BORDER_INDICATOR_WIDTH_PIXELS,
SFG_HUD_HURT_INDICATION_COLOR);
else if (SFG_game.frame - SFG_player.lastItemTakenFrame

View File

@ -208,6 +208,6 @@
Skips menu and starts given level immediatelly, for development. 0 means this
options is ignored, 1 means load level 1 etc.
*/
#define SFG_START_LEVEL 1
#define SFG_START_LEVEL 0
#endif // guard