Basic gameplay loop, need to balance values
This commit is contained in:
276
src/Game.cpp
276
src/Game.cpp
@@ -1,6 +1,8 @@
|
||||
#include "Game.h"
|
||||
#include "Creature.h"
|
||||
#include <RmlUi/Core/DataModelHandle.h>
|
||||
#include <RmlUi/Core/Types.h>
|
||||
#include <cmath>
|
||||
#include <raylib.h>
|
||||
|
||||
namespace {
|
||||
@@ -25,6 +27,20 @@ bool IsMenuState(GameState state) {
|
||||
return state == OPTIONS_MENU || state == STATS_MENU;
|
||||
}
|
||||
|
||||
Color ColorForTier(AppearanceTier tier) {
|
||||
switch (tier) {
|
||||
case AppearanceTier::Standard:
|
||||
return GRAY;
|
||||
case AppearanceTier::Subtle1:
|
||||
return YELLOW;
|
||||
case AppearanceTier::Subtle2:
|
||||
return ORANGE;
|
||||
case AppearanceTier::Obvious:
|
||||
return RED;
|
||||
}
|
||||
return WHITE;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool Game::Init(Rml::Context *context) {
|
||||
@@ -34,18 +50,29 @@ bool Game::Init(Rml::Context *context) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Game::SetupRmlDataBinding(Rml::DataModelHandle &model_handle) {
|
||||
bool Game::SetupRmlDataBinding() {
|
||||
Rml::DataModelConstructor dmc = ctx->CreateDataModel("game_data");
|
||||
if (!dmc) {
|
||||
TraceLog(LOG_ERROR, "Failed to construct data model.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// ---------- Member bindings ----------
|
||||
|
||||
dmc.Bind("game_state", &game_data.game_state);
|
||||
|
||||
// ---------- Event callback bindings ----------
|
||||
dmc.Bind("marshmallows_remaining", &game_data.marshmallows_remaining);
|
||||
dmc.Bind("creature_number", &game_data.creature_number);
|
||||
dmc.Bind("creature_count", &game_data.creature_count);
|
||||
dmc.Bind("creature_species", &game_data.creature_species);
|
||||
dmc.Bind("debug_appearance_tier", &game_data.debug_appearance_tier);
|
||||
dmc.Bind("debug_is_cryptid", &game_data.debug_is_cryptid);
|
||||
dmc.Bind("debug_behaves_normally", &game_data.debug_behaves_normally);
|
||||
dmc.Bind("has_fed_current", &game_data.has_fed_current);
|
||||
dmc.Bind("feed_reaction_text", &game_data.feed_reaction_text);
|
||||
dmc.Bind("score", &game_data.score);
|
||||
dmc.Bind("round_number", &game_data.round_number);
|
||||
dmc.Bind("round_over", &game_data.round_over);
|
||||
dmc.Bind("round_result_text", &game_data.round_result_text);
|
||||
dmc.Bind("time_remaining_seconds", &game_data.time_remaining_seconds);
|
||||
dmc.Bind("time_urgent", &game_data.time_urgent);
|
||||
|
||||
dmc.BindEventCallback("start_game", [this](Rml::DataModelHandle, Rml::Event &,
|
||||
const Rml::VariantList &) {
|
||||
@@ -79,7 +106,30 @@ bool Game::SetupRmlDataBinding(Rml::DataModelHandle &model_handle) {
|
||||
[this](Rml::DataModelHandle, Rml::Event &,
|
||||
const Rml::VariantList &) { GoBack(); });
|
||||
|
||||
model_handle = dmc.GetModelHandle();
|
||||
// --- gameplay actions ---
|
||||
dmc.BindEventCallback("feed_creature",
|
||||
[this](Rml::DataModelHandle, Rml::Event &,
|
||||
const Rml::VariantList &) { FeedCreature(); });
|
||||
|
||||
dmc.BindEventCallback(
|
||||
"capture_creature",
|
||||
[this](Rml::DataModelHandle, Rml::Event &, const Rml::VariantList &) {
|
||||
CaptureCreature();
|
||||
});
|
||||
|
||||
dmc.BindEventCallback(
|
||||
"release_creature",
|
||||
[this](Rml::DataModelHandle, Rml::Event &, const Rml::VariantList &) {
|
||||
ReleaseCreature();
|
||||
});
|
||||
|
||||
dmc.BindEventCallback(
|
||||
"continue_next_round",
|
||||
[this](Rml::DataModelHandle, Rml::Event &, const Rml::VariantList &) {
|
||||
ContinueToNextRound();
|
||||
});
|
||||
|
||||
modelHandle = dmc.GetModelHandle();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -144,14 +194,192 @@ void Game::GoBack() {
|
||||
}
|
||||
|
||||
void Game::StartNewGame() {
|
||||
// TODO
|
||||
roundIndex = 0;
|
||||
game_data.score = 0;
|
||||
|
||||
// --- placeholder testing stuff ---
|
||||
playerPos = {GetScreenWidth() / 2.0f, GetScreenHeight() / 2.0f};
|
||||
BeginRound();
|
||||
}
|
||||
|
||||
void Game::ResetGame() {
|
||||
// TODO
|
||||
roundIndex = 0;
|
||||
game_data.score = 0;
|
||||
}
|
||||
|
||||
void Game::BeginRound() {
|
||||
RoundConfig cfg = MakeRoundConfig(roundIndex);
|
||||
currentRound = GenerateRound(cfg);
|
||||
|
||||
game_data.round_number = roundIndex + 1;
|
||||
game_data.round_over = false;
|
||||
game_data.round_result_text = "";
|
||||
|
||||
creatureTimeLimit = TimeLimitForRound(roundIndex);
|
||||
RefreshCreatureBindings();
|
||||
}
|
||||
|
||||
float Game::TimeLimitForRound(int roundIndex) const {
|
||||
return std::max(15.0f, 40.0f - roundIndex * 1.0f);
|
||||
}
|
||||
|
||||
void Game::ResetCreatureTimer() {
|
||||
creatureTimeRemaining = creatureTimeLimit;
|
||||
game_data.time_remaining_seconds = (int)std::ceil(creatureTimeRemaining);
|
||||
game_data.time_urgent = false;
|
||||
}
|
||||
|
||||
void Game::UpdateCreatureTimer(float dt) {
|
||||
if (game_data.round_over)
|
||||
return;
|
||||
|
||||
creatureTimeRemaining -= dt;
|
||||
|
||||
int secondsLeft = (int)std::ceil(std::max(creatureTimeRemaining, 0.0f));
|
||||
bool urgent = creatureTimeRemaining <= 2.0f;
|
||||
|
||||
if (secondsLeft != game_data.time_remaining_seconds ||
|
||||
urgent != game_data.time_urgent) {
|
||||
game_data.time_remaining_seconds = secondsLeft;
|
||||
game_data.time_urgent = urgent;
|
||||
if (modelHandle) {
|
||||
modelHandle.DirtyVariable("time_remaining_seconds");
|
||||
modelHandle.DirtyVariable("time_urgent");
|
||||
}
|
||||
}
|
||||
|
||||
if (creatureTimeRemaining <= 0.0f) {
|
||||
ReleaseCreature();
|
||||
}
|
||||
}
|
||||
|
||||
void Game::RefreshCreatureBindings() {
|
||||
Creature *c = currentRound.CurrentCreature();
|
||||
|
||||
game_data.marshmallows_remaining = currentRound.marshmallowsRemaining;
|
||||
game_data.creature_count = currentRound.conf.creature_count;
|
||||
game_data.creature_number = currentRound.currentIndex + 1;
|
||||
|
||||
if (c) {
|
||||
game_data.creature_species = SpeciesName(c->species);
|
||||
game_data.debug_appearance_tier = AppearanceTierName(c->appearance);
|
||||
game_data.debug_is_cryptid = c->is_cryptid;
|
||||
game_data.debug_behaves_normally = c->behaves_normally;
|
||||
game_data.has_fed_current = c->has_been_fed;
|
||||
game_data.feed_reaction_text = "";
|
||||
}
|
||||
|
||||
ResetCreatureTimer();
|
||||
MarkDirty();
|
||||
}
|
||||
|
||||
void Game::MarkDirty() {
|
||||
if (!modelHandle)
|
||||
return;
|
||||
modelHandle.DirtyVariable("marshmallows_remaining");
|
||||
modelHandle.DirtyVariable("creature_number");
|
||||
modelHandle.DirtyVariable("creature_count");
|
||||
modelHandle.DirtyVariable("creature_species");
|
||||
modelHandle.DirtyVariable("debug_appearance_tier");
|
||||
modelHandle.DirtyVariable("debug_is_cryptid");
|
||||
modelHandle.DirtyVariable("debug_behaves_normally");
|
||||
modelHandle.DirtyVariable("has_fed_current");
|
||||
modelHandle.DirtyVariable("feed_reaction_text");
|
||||
modelHandle.DirtyVariable("score");
|
||||
modelHandle.DirtyVariable("round_number");
|
||||
modelHandle.DirtyVariable("round_over");
|
||||
modelHandle.DirtyVariable("round_result_text");
|
||||
}
|
||||
|
||||
void Game::FeedCreature() {
|
||||
if (game_data.round_over)
|
||||
return;
|
||||
|
||||
if (!FeedCurrentCreature(currentRound))
|
||||
return;
|
||||
|
||||
Creature *c = currentRound.CurrentCreature();
|
||||
if (c) {
|
||||
bool ate = WouldEatMarshmallow(*c);
|
||||
game_data.feed_reaction_text =
|
||||
std::string(SpeciesName(c->species)) +
|
||||
(ate ? " eats the marshmallow!" : " ignores it.");
|
||||
game_data.has_fed_current = true;
|
||||
}
|
||||
|
||||
game_data.marshmallows_remaining = currentRound.marshmallowsRemaining;
|
||||
MarkDirty();
|
||||
}
|
||||
|
||||
void Game::CaptureCreature() {
|
||||
if (game_data.round_over)
|
||||
return;
|
||||
|
||||
bool died = ResolveCurrentCreature(currentRound, true);
|
||||
if (currentRound.CurrentCreature() &&
|
||||
!currentRound.CurrentCreature()->is_cryptid) {
|
||||
}
|
||||
|
||||
Creature &resolved = currentRound.creatures[currentRound.currentIndex - 1];
|
||||
game_data.score += resolved.is_cryptid ? 1 : -2;
|
||||
|
||||
if (died) {
|
||||
EndRound(true, false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentRound.IsComplete()) {
|
||||
EndRound(false, currentRound.Passed());
|
||||
return;
|
||||
}
|
||||
|
||||
RefreshCreatureBindings();
|
||||
}
|
||||
|
||||
void Game::ReleaseCreature() {
|
||||
if (game_data.round_over)
|
||||
return;
|
||||
|
||||
bool died = ResolveCurrentCreature(currentRound, false);
|
||||
|
||||
if (died) {
|
||||
EndRound(true, false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentRound.IsComplete()) {
|
||||
EndRound(false, currentRound.Passed());
|
||||
return;
|
||||
}
|
||||
|
||||
RefreshCreatureBindings();
|
||||
}
|
||||
|
||||
void Game::EndRound(bool died, bool passed) {
|
||||
game_data.round_over = true;
|
||||
|
||||
if (died) {
|
||||
game_data.round_result_text = "A cryptid got away. You did not survive.";
|
||||
} else if (passed) {
|
||||
game_data.round_result_text =
|
||||
"Round complete. Success rate: " +
|
||||
std::to_string((int)(currentRound.SuccessRate() * 100.0f)) + "%";
|
||||
} else {
|
||||
game_data.round_result_text =
|
||||
"Too many mistakes. Round failed. Success rate: " +
|
||||
std::to_string((int)(currentRound.SuccessRate() * 100.0f)) + "%";
|
||||
}
|
||||
|
||||
MarkDirty();
|
||||
}
|
||||
|
||||
void Game::ContinueToNextRound() {
|
||||
if (!game_data.round_over)
|
||||
return;
|
||||
|
||||
if (currentRound.Passed()) {
|
||||
roundIndex++;
|
||||
}
|
||||
|
||||
BeginRound();
|
||||
}
|
||||
|
||||
void Game::Update() {
|
||||
@@ -159,32 +387,24 @@ void Game::Update() {
|
||||
|
||||
if (CurrentState() != IN_GAME)
|
||||
return;
|
||||
// TODO
|
||||
|
||||
// --- placeholder testing stuff ---
|
||||
float dt = GetFrameTime();
|
||||
|
||||
if (IsKeyDown(KEY_W) || IsKeyDown(KEY_UP))
|
||||
playerPos.y -= playerSpeed * dt;
|
||||
if (IsKeyDown(KEY_S) || IsKeyDown(KEY_DOWN))
|
||||
playerPos.y += playerSpeed * dt;
|
||||
if (IsKeyDown(KEY_A) || IsKeyDown(KEY_LEFT))
|
||||
playerPos.x -= playerSpeed * dt;
|
||||
if (IsKeyDown(KEY_D) || IsKeyDown(KEY_RIGHT))
|
||||
playerPos.x += playerSpeed * dt;
|
||||
UpdateCreatureTimer(dt);
|
||||
}
|
||||
|
||||
void Game::Draw2D() {
|
||||
if (CurrentState() != IN_GAME)
|
||||
return;
|
||||
// TODO
|
||||
|
||||
// --- placeholder testing stuff ---
|
||||
// DrawText("Hello from raylib!", 50, 50, 20, RAYWHITE);
|
||||
if (CurrentState() != IN_GAME)
|
||||
return;
|
||||
DrawCircleV(playerPos, 24.0f, BLUE);
|
||||
DrawText("WASD/Arrows to move, ESC to pause", 20, 20, 20, RAYWHITE);
|
||||
Creature *c = currentRound.CurrentCreature();
|
||||
if (c && !game_data.round_over) {
|
||||
Color tierColor = ColorForTier(c->appearance);
|
||||
int size = 120;
|
||||
int x = GetScreenWidth() / 2 - size / 2;
|
||||
int y = GetScreenHeight() / 2 - size / 2;
|
||||
DrawRectangle(x, y, size, size, tierColor);
|
||||
DrawRectangleLines(x, y, size, size, BLACK);
|
||||
}
|
||||
}
|
||||
|
||||
void Game::HandleInput() {
|
||||
|
||||
Reference in New Issue
Block a user