Basic gameplay loop, need to balance values

This commit is contained in:
2026-08-25 23:09:11 -05:00
parent 29cce0de6d
commit e289af808a
10 changed files with 382 additions and 50 deletions

View File

@@ -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() {