338 lines
14 KiB
C
338 lines
14 KiB
C
#include "state_ingame.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <assert.h>
|
|
#include <math.h>
|
|
|
|
#include "raylib.h"
|
|
#include "raygui.h"
|
|
|
|
#include "session/game_session.h"
|
|
|
|
#include "shared/wang_hash.h"
|
|
|
|
#define GRID_CELL_SIZE 8
|
|
#define ENTITY_PRESENTATION_Y_OFFSET -2
|
|
|
|
static void state_enter(Presentation_State *state) {
|
|
Presentation_State_Ingame_Context *ctx = (Presentation_State_Ingame_Context *)state->context;
|
|
(void)ctx;
|
|
|
|
printf("Entered ingame.\n");
|
|
|
|
printf("Setting up context ...\n");
|
|
ctx->main_camera = (Camera2D) {
|
|
.offset = (Vector2) { 0, 0 },
|
|
.target = (Vector2) { 0, 0 },
|
|
.rotation = 0.0f,
|
|
.zoom = 3.0f
|
|
};
|
|
|
|
// TODO: SS - Maybe put the textures in an array and index them using an enum?
|
|
// These should probably be loaded at start-up instead of here.
|
|
ctx->texture_shadow_basic = LoadTexture("assets/sprites/spr_shadow_basic.png");
|
|
assert(IsTextureValid(ctx->texture_shadow_basic));
|
|
ctx->texture_grass = LoadTexture("assets/sprites/spr_floor_grass.png");
|
|
assert(IsTextureValid(ctx->texture_grass));
|
|
ctx->texture_apple = LoadTexture("assets/sprites/spr_food_apple.png");
|
|
assert(IsTextureValid(ctx->texture_apple));
|
|
ctx->texture_snake_head = LoadTexture("assets/sprites/spr_snake_head.png");
|
|
assert(IsTextureValid(ctx->texture_snake_head));
|
|
ctx->texture_snake_body = LoadTexture("assets/sprites/spr_snake_body.png");
|
|
assert(IsTextureValid(ctx->texture_snake_body));
|
|
}
|
|
|
|
static void state_tick(Presentation_State *state) {
|
|
Presentation_State_Ingame_Context *ctx = (Presentation_State_Ingame_Context *)state->context;
|
|
(void)ctx;
|
|
|
|
Game_Instance *instance = ctx->game_instance;
|
|
assert(instance != NULL);
|
|
Game_Session *session = instance->game_session;
|
|
assert(session != NULL);
|
|
|
|
{ // Push local input to queue.
|
|
Simulation_Game_Input input = (Simulation_Game_Input) { // NOTE: SS - This needs to be slightly modified to support N local players.
|
|
.up = IsKeyDown(KEY_UP) || IsKeyDown(KEY_W),
|
|
.down = IsKeyDown(KEY_DOWN) || IsKeyDown(KEY_S),
|
|
.right = IsKeyDown(KEY_RIGHT) || IsKeyDown(KEY_D),
|
|
.left = IsKeyDown(KEY_LEFT) || IsKeyDown(KEY_A)
|
|
};
|
|
|
|
if(!simulation_input_equal(input, ctx->prev_local_input)) {
|
|
game_instance_push_local_input(instance, input);
|
|
ctx->prev_local_input = input;
|
|
}
|
|
}
|
|
|
|
const double delta_time = GetFrameTime();
|
|
ctx->simulation_accumulator += delta_time;
|
|
|
|
const double sim_dt = 1.0f / session->settings.tickrate;
|
|
|
|
while (ctx->simulation_accumulator >= sim_dt) {
|
|
// Pop input from instance's local_input_queue and set the local player's input to it, if we have one.
|
|
Simulation_Game_Input input = {0};
|
|
if(game_instance_pop_local_input(instance, &input)) {
|
|
// TODO: SS - We should probably check if this input is invalid for the snake. (pressing "go right" when going left, for example.)
|
|
// If it is invalid, the next input in the queue should be tried.
|
|
game_session_set_player_input(session, instance->local_player_id, input);
|
|
}
|
|
|
|
game_session_tick(session);
|
|
ctx->simulation_accumulator -= sim_dt;
|
|
}
|
|
|
|
{ // TEMP: SS
|
|
if(IsKeyPressed(KEY_TAB)) {
|
|
ctx->debug_draw_session_details = !ctx->debug_draw_session_details;
|
|
}
|
|
|
|
if(IsKeyPressed(KEY_ESCAPE)) {
|
|
presentation_state_machine_go_to(&presentation_state_main_menu);
|
|
}
|
|
}
|
|
}
|
|
|
|
uint32_t floor_texture_variant(uint32_t x, uint32_t y, uint32_t seed, uint32_t num_variants) {
|
|
return (wang_hash(x * 73856093u ^ y * 19349663u ^ seed)) % num_variants;
|
|
}
|
|
|
|
#define SHADOW_ALPHA 64
|
|
|
|
static void state_render(Presentation_State *state) {
|
|
Presentation_State_Ingame_Context *ctx = (Presentation_State_Ingame_Context *)state->context;
|
|
(void)ctx;
|
|
|
|
ClearBackground((Color) { 240, 236, 226, 255 });
|
|
|
|
Game_Instance *instance = ctx->game_instance;
|
|
Game_Session *session = instance->game_session;
|
|
Simulation_World *sim_world = &session->simulation_world;
|
|
assert(sim_world != NULL);
|
|
|
|
Game_World *world = sim_world->game_world;
|
|
assert(world != NULL);
|
|
Grid *grid = &world->grid;
|
|
|
|
uint32_t grid_total_size = grid->width * grid->height;
|
|
|
|
BeginMode2D(ctx->main_camera); {
|
|
// Render the level.
|
|
for(uint32_t i = 0; i < grid_total_size; i++) {
|
|
uint32_t x = i % grid->width;
|
|
uint32_t y = i / grid->width;
|
|
|
|
uint32_t pres_x = x * GRID_CELL_SIZE;
|
|
uint32_t pres_y = y * GRID_CELL_SIZE;
|
|
|
|
uint32_t random_floor_index = floor_texture_variant(x, y, world->seed, 4);
|
|
|
|
DrawTextureRec(
|
|
ctx->texture_grass,
|
|
(Rectangle) { GRID_CELL_SIZE * random_floor_index, 0, GRID_CELL_SIZE, GRID_CELL_SIZE },
|
|
(Vector2) { pres_x, pres_y },
|
|
WHITE
|
|
);
|
|
DrawRectangleLines(pres_x, pres_y, GRID_CELL_SIZE, GRID_CELL_SIZE, (Color) { 0, 0, 0, 2 }); // TODO: SS - Let the user customize the alpha locally.
|
|
}
|
|
|
|
for(uint32_t i = 0; i < grid_total_size; i++) {
|
|
uint32_t x = i % grid->width;
|
|
uint32_t y = i / grid->width;
|
|
|
|
uint32_t pres_x = x * GRID_CELL_SIZE;
|
|
uint32_t pres_y = y * GRID_CELL_SIZE;
|
|
|
|
Grid_Cell *cell = &grid->cells[i];
|
|
if(cell->entity != NULL) {
|
|
Entity *entity = cell->entity;
|
|
|
|
switch(entity->type) {
|
|
case Entity_Type_Snake_Head: {
|
|
Color tint = (Color) { 255, 135, 102, 255 }; // TODO: SS - Get tint based on player ID.
|
|
Vector2 origin = (Vector2) { GRID_CELL_SIZE/2, GRID_CELL_SIZE/2 };
|
|
|
|
Entity *e = entity;
|
|
uint16_t i = 0;
|
|
while(e != NULL) { // Loop over all the body-parts of the snake, including the head.
|
|
float sin_frequency = 0.0f;
|
|
float sin_amplitude = 0.0f;
|
|
|
|
uint32_t pres_x = e->x * GRID_CELL_SIZE;
|
|
uint32_t pres_y = e->y * GRID_CELL_SIZE;
|
|
|
|
Texture2D *texture = NULL;
|
|
if(e->type == Entity_Type_Snake_Head) {
|
|
texture = &ctx->texture_snake_head;
|
|
}
|
|
else {
|
|
texture = &ctx->texture_snake_body;
|
|
sin_frequency = 4.0f;
|
|
sin_amplitude = 1.0f;
|
|
}
|
|
|
|
// Draw shadow.
|
|
DrawTextureEx(
|
|
ctx->texture_shadow_basic,
|
|
(Vector2) { pres_x, pres_y },
|
|
0.0f,
|
|
1.0f,
|
|
(Color) { 0, 0, 0, SHADOW_ALPHA }
|
|
);
|
|
|
|
float rotation = 0.0f;
|
|
switch(e->move_direction){
|
|
case Entity_Movement_Direction_None: {
|
|
rotation = 0.0f;
|
|
break;
|
|
}
|
|
case Entity_Movement_Direction_Up: {
|
|
rotation = -90.0f;
|
|
break;
|
|
}
|
|
case Entity_Movement_Direction_Down: {
|
|
rotation = 90.0f;
|
|
break;
|
|
}
|
|
case Entity_Movement_Direction_Right: {
|
|
rotation = 0.0f;
|
|
break;
|
|
}
|
|
case Entity_Movement_Direction_Left: {
|
|
rotation = 180.0f;
|
|
break;
|
|
}
|
|
}
|
|
|
|
DrawTexturePro(
|
|
*texture,
|
|
(Rectangle) { // Source.
|
|
0, 0, GRID_CELL_SIZE, GRID_CELL_SIZE
|
|
},
|
|
(Rectangle) { // Destination.
|
|
pres_x + origin.x,
|
|
pres_y + origin.y + ENTITY_PRESENTATION_Y_OFFSET - (sin((GetTime() + (float)(i) / 4.0) * sin_frequency)) * sin_amplitude,
|
|
GRID_CELL_SIZE,
|
|
GRID_CELL_SIZE
|
|
},
|
|
origin, // Origin.
|
|
rotation, // Rotation.
|
|
tint // Tint.
|
|
);
|
|
|
|
e = game_world_try_get_entity_by_id(world, e->child);
|
|
i += 1;
|
|
}
|
|
|
|
// TODO: SS - Don't draw player-name if playing by yourself.
|
|
// TODO: SS - Don't draw your own player-name, only others.
|
|
{ // Draw player-name.
|
|
const char *player_name = "player"; // NOTE: SS - Hardcoded.
|
|
const uint32_t font_size = 8;
|
|
int text_width = MeasureText(player_name, font_size);
|
|
DrawText(player_name, pres_x - (float)text_width/2.0f + 4, pres_y - 16, font_size, (Color) { 255, 255, 255, 128 });
|
|
}
|
|
|
|
break;
|
|
}
|
|
case Entity_Type_Snake_Body: {
|
|
break;
|
|
}
|
|
case Entity_Type_Food: {
|
|
uint32_t flash = ((sin(GetTime() * 8) + 1)/2) * 32;
|
|
Color tint = (Color) { 255-flash, 255-flash, 255-flash, 255 };
|
|
|
|
Vector2 origin = (Vector2) { GRID_CELL_SIZE/2, GRID_CELL_SIZE/2 };
|
|
|
|
// Draw shadow.
|
|
DrawTextureEx(
|
|
ctx->texture_shadow_basic,
|
|
(Vector2) { pres_x, pres_y },
|
|
0.0f,
|
|
1.0f,
|
|
(Color) { 0, 0, 0, SHADOW_ALPHA }
|
|
);
|
|
|
|
DrawTexturePro(
|
|
ctx->texture_apple,
|
|
(Rectangle) { // Source.
|
|
0, 0, GRID_CELL_SIZE, GRID_CELL_SIZE
|
|
},
|
|
(Rectangle) { // Destination.
|
|
pres_x + origin.x,
|
|
pres_y + origin.y + ENTITY_PRESENTATION_Y_OFFSET - ((sin((GetTime() + (x + y)) * 12) + 1)/2) * 1,
|
|
GRID_CELL_SIZE,
|
|
GRID_CELL_SIZE
|
|
},
|
|
origin, // Origin.
|
|
0, // Rotation.
|
|
tint // Tint.
|
|
);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO: SS - Switch on 'sim_world->match_state'.
|
|
|
|
if(ctx->debug_draw_session_details) {
|
|
char buf[1024];
|
|
snprintf(&buf[0], sizeof(buf),
|
|
"Match-state: %i\n"
|
|
"Tick: %lu\n"
|
|
"\n"
|
|
"Seed: %u\n"
|
|
"Level: %ux%u\n"
|
|
"Tickrate: %.2f\n"
|
|
"\n"
|
|
|
|
// TODO: SS - This should only be shown when playing online. Other things should be shown in the cases below;
|
|
// When playing offline/singleplayer you can only be one player.
|
|
// When playing local-multiplayer, you control multiple players from the same computer/client/instance.
|
|
"Local player: %u (host? %s)\n"
|
|
"Player count: %u / %u\n"
|
|
,
|
|
sim_world->match_state, sim_world->tick,
|
|
sim_world->game_world->seed,
|
|
sim_world->game_world->grid.width, sim_world->game_world->grid.height,
|
|
session->settings.tickrate,
|
|
instance->local_player_id, instance->local_player_id == 0 ? "true" : "false", // NOTE: SS - You're the host if you're player 0.
|
|
game_session_get_amount_of_active_players(session), sim_world->max_players
|
|
);
|
|
|
|
DrawText(buf, 17, 17, 8, BLACK);
|
|
DrawText(buf, 16, 16, 8, WHITE);
|
|
}
|
|
}
|
|
EndMode2D();
|
|
}
|
|
|
|
static void state_exit(Presentation_State *state) {
|
|
Presentation_State_Ingame_Context *ctx = (Presentation_State_Ingame_Context *)state->context;
|
|
(void)ctx;
|
|
printf("Exiting ingame\n");
|
|
|
|
UnloadTexture(ctx->texture_shadow_basic);
|
|
UnloadTexture(ctx->texture_grass);
|
|
UnloadTexture(ctx->texture_apple);
|
|
UnloadTexture(ctx->texture_snake_head);
|
|
UnloadTexture(ctx->texture_snake_body);
|
|
|
|
ctx->game_instance = NULL;
|
|
}
|
|
|
|
Presentation_State presentation_state_ingame;
|
|
|
|
void presentation_state_ingame_init(Presentation_State_Ingame_Context *ctx) {
|
|
presentation_state_ingame = (Presentation_State) {
|
|
.name = "Ingame",
|
|
.context = (void *)ctx,
|
|
.enter = state_enter,
|
|
.tick = state_tick,
|
|
.render = state_render,
|
|
.exit = state_exit
|
|
};
|
|
} |