parallel animations, a different test dialogue, utf-8

This commit is contained in:
maksw committed 2025-11-12 13:11:17 +01:00
1 parent 1b31faa7ee
commit 475e57d85c
3 files changed
+780 -638

No files matched your search

+201 -159
View File
@@ -1,9 +1,10 @@
#include <cstdio> #include <cstdio>
#include <cstdint> #include <cstdint>
#include <cstring> #include <cstring>
#include <unordered_map> #include <unordered_map>
#include <string> #include <string>
#include <chrono> // <-- NEW: For delta-time calculation #include <chrono>
#include <vector> // <-- NEW: For tracking multiple animations
#include <window.h> #include <window.h>
#include <text.h> #include <text.h>
#include <compositor.h> #include <compositor.h>
@@ -14,52 +15,41 @@
// ============================================================================ // ============================================================================
enum CommandType { enum CommandType {
CMD_SHOW, // Show character with specific sprite CMD_SHOW,
CMD_HIDE, // Hide character (all sprites) CMD_HIDE,
CMD_MOVE, // Move character to position CMD_MOVE,
CMD_CHANGE_SPRITE, // Change character's sprite/expression CMD_CHANGE_SPRITE,
CMD_SET_Z, // Change z-order CMD_SET_Z,
CMD_SET_SCALE, // Change scale CMD_SET_SCALE,
CMD_SPEAK, // Display dialogue CMD_SPEAK,
CMD_WAIT, // Wait for click CMD_WAIT,
CMD_BACKGROUND, // Change background CMD_BACKGROUND,
}; };
enum TransitionType { enum TransitionType {
TRANS_NONE, // Instant snap TRANS_NONE,
TRANS_FADE, // Fade in / Fade out TRANS_FADE,
TRANS_SLIDE_LEFT, // Slide in from left TRANS_SLIDE_LEFT,
TRANS_SLIDE_RIGHT, // Slide in from right TRANS_SLIDE_RIGHT,
}; };
// Easing enums (must match compositor.h)
// We provide them here for the demo's compilation
#ifndef COMPOSITOR_H
enum easing_t {
EASE_LINEAR = 0, EASE_IN_QUAD, EASE_OUT_QUAD, EASE_IN_OUT_QUAD,
EASE_IN_CUBIC, EASE_OUT_CUBIC, EASE_IN_OUT_CUBIC
};
#endif
struct Command { struct Command {
CommandType type; CommandType type;
const char* character_name; // Which character (by name) const char* character_name;
// Position/transform data
int x, y; int x, y;
int z; int z;
float scale; float scale;
// Sprite/image data
const char* sprite_name; const char* sprite_name;
// Dialogue data
const char* speaker; const char* speaker;
const char* text; const char* text;
// Animation/transition data
TransitionType transition; TransitionType transition;
float duration_ms; float duration_ms;
bool blocking; // NEW: If true, wait for this animation to complete before next command
}; };
struct Scene { struct Scene {
@@ -78,24 +68,71 @@ struct Scene {
} }
}; };
// ============================================================================
// ANIMATION TRACKER
// ============================================================================
struct ActiveAnimation {
float remaining_time;
ActiveAnimation(float duration) : remaining_time(duration) {}
};
class AnimationTracker {
private:
std::vector<ActiveAnimation> active_animations;
public:
void add_animation(float duration_sec) {
if (duration_sec > 0.0f) {
active_animations.push_back(ActiveAnimation(duration_sec));
}
}
void update(float delta_time) {
for (size_t i = 0; i < active_animations.size(); ) {
active_animations[i].remaining_time -= delta_time;
if (active_animations[i].remaining_time <= 0.0f) {
// Animation finished, remove it
active_animations.erase(active_animations.begin() + i);
}
else {
++i;
}
}
}
bool has_active_animations() const {
return !active_animations.empty();
}
void clear() {
active_animations.clear();
}
int count() const {
return active_animations.size();
}
};
// ============================================================================ // ============================================================================
// CHARACTER MANAGEMENT // CHARACTER MANAGEMENT
// ============================================================================ // ============================================================================
struct SpriteElement { struct SpriteElement {
uint32_t* image_data; uint32_t* image_data;
uint32_t element_id; // compositor element ID uint32_t element_id;
bool visible; bool visible;
}; };
struct Character { struct Character {
std::string name; std::string name;
std::unordered_map<std::string, SpriteElement> sprites; // emotion -> sprite std::unordered_map<std::string, SpriteElement> sprites;
int width, height; int width, height;
int current_x, current_y; int current_x, current_y;
int current_z; int current_z;
float current_scale; float current_scale;
std::string current_sprite; // Which sprite is currently visible std::string current_sprite;
}; };
class CharacterManager { class CharacterManager {
@@ -109,7 +146,6 @@ public:
: compositor(comp), screen_width(screen_w) { : compositor(comp), screen_width(screen_w) {
} }
// Register a character
void add_character(const char* name, int width, int height) { void add_character(const char* name, int width, int height) {
Character ch; Character ch;
ch.name = name; ch.name = name;
@@ -123,7 +159,6 @@ public:
characters[name] = ch; characters[name] = ch;
} }
// Load a sprite for a character and insert it into compositor
void load_sprite(const char* name, const char* emotion, const char* filename, void load_sprite(const char* name, const char* emotion, const char* filename,
int width, int height, int initial_z = 1) { int width, int height, int initial_z = 1) {
auto it = characters.find(name); auto it = characters.find(name);
@@ -137,21 +172,18 @@ public:
elem.element_id = compositor_insert(compositor, img, width, height, 0, 0, initial_z); elem.element_id = compositor_insert(compositor, img, width, height, 0, 0, initial_z);
elem.visible = false; elem.visible = false;
// NEW: Start fully faded out and invisible
compositor_set_alpha(compositor, elem.element_id, 0.0f); compositor_set_alpha(compositor, elem.element_id, 0.0f);
compositor_set_visible(compositor, elem.element_id, false); compositor_set_visible(compositor, elem.element_id, false);
it->second.sprites[emotion] = elem; it->second.sprites[emotion] = elem;
} }
// Get character by name
Character* get_character(const char* name) { Character* get_character(const char* name) {
auto it = characters.find(name); auto it = characters.find(name);
if (it != characters.end()) return &it->second; if (it != characters.end()) return &it->second;
return nullptr; return nullptr;
} }
// Get the element ID of the character's currently active sprite
uint32_t get_active_element_id(Character* ch) { uint32_t get_active_element_id(Character* ch) {
if (!ch || ch->current_sprite.empty()) return 0; if (!ch || ch->current_sprite.empty()) return 0;
auto it = ch->sprites.find(ch->current_sprite); auto it = ch->sprites.find(ch->current_sprite);
@@ -159,13 +191,11 @@ public:
return it->second.element_id; return it->second.element_id;
} }
// Show character with specific sprite
void show_character(const char* name, int x, int y, int z, const char* sprite_name, void show_character(const char* name, int x, int y, int z, const char* sprite_name,
TransitionType transition, float duration_ms) { TransitionType transition, float duration_ms) {
Character* ch = get_character(name); Character* ch = get_character(name);
if (!ch) return; if (!ch) return;
// Hide all other sprites for this character first
for (auto& pair : ch->sprites) { for (auto& pair : ch->sprites) {
if (pair.first != sprite_name && pair.second.visible) { if (pair.first != sprite_name && pair.second.visible) {
compositor_set_visible(compositor, pair.second.element_id, false); compositor_set_visible(compositor, pair.second.element_id, false);
@@ -174,44 +204,40 @@ public:
} }
} }
// Find the requested sprite
auto it = ch->sprites.find(sprite_name); auto it = ch->sprites.find(sprite_name);
if (it == ch->sprites.end()) return; if (it == ch->sprites.end()) return;
SpriteElement& elem = it->second; SpriteElement& elem = it->second;
float duration_sec = duration_ms / 1000.0f; float duration_sec = duration_ms / 1000.0f;
// Update state
ch->current_x = x; ch->current_x = x;
ch->current_y = y; ch->current_y = y;
ch->current_z = z; ch->current_z = z;
ch->current_sprite = sprite_name; ch->current_sprite = sprite_name;
elem.visible = true; // Mark as logically visible elem.visible = true;
// Set properties that aren't animated
compositor_set_zorder(compositor, elem.element_id, z); compositor_set_zorder(compositor, elem.element_id, z);
// --- NEW: Apply Transition ---
if (transition == TRANS_FADE) { if (transition == TRANS_FADE) {
compositor_set_position(compositor, elem.element_id, x, y); // Snap position compositor_set_position(compositor, elem.element_id, x, y);
compositor_set_visible(compositor, elem.element_id, true); compositor_set_visible(compositor, elem.element_id, true);
compositor_animate_fade(compositor, elem.element_id, 1.0f, duration_sec, EASE_LINEAR); compositor_animate_fade(compositor, elem.element_id, 1.0f, duration_sec, EASE_LINEAR);
} }
else if (transition == TRANS_SLIDE_LEFT) { else if (transition == TRANS_SLIDE_LEFT) {
int start_x = -ch->width; // Start off-screen left int start_x = -ch->width;
compositor_set_position(compositor, elem.element_id, start_x, y); compositor_set_position(compositor, elem.element_id, start_x, y);
compositor_set_alpha(compositor, elem.element_id, 1.0f); // Make opaque compositor_set_alpha(compositor, elem.element_id, 1.0f);
compositor_set_visible(compositor, elem.element_id, true); compositor_set_visible(compositor, elem.element_id, true);
compositor_animate_move(compositor, elem.element_id, x, y, duration_sec, EASE_OUT_CUBIC); compositor_animate_move(compositor, elem.element_id, x, y, duration_sec, EASE_OUT_CUBIC);
} }
else if (transition == TRANS_SLIDE_RIGHT) { else if (transition == TRANS_SLIDE_RIGHT) {
int start_x = screen_width; // Start off-screen right int start_x = screen_width;
compositor_set_position(compositor, elem.element_id, start_x, y); compositor_set_position(compositor, elem.element_id, start_x, y);
compositor_set_alpha(compositor, elem.element_id, 1.0f); // Make opaque compositor_set_alpha(compositor, elem.element_id, 1.0f);
compositor_set_visible(compositor, elem.element_id, true); compositor_set_visible(compositor, elem.element_id, true);
compositor_animate_move(compositor, elem.element_id, x, y, duration_sec, EASE_OUT_CUBIC); compositor_animate_move(compositor, elem.element_id, x, y, duration_sec, EASE_OUT_CUBIC);
} }
else { // TRANS_NONE else {
compositor_set_position(compositor, elem.element_id, x, y); compositor_set_position(compositor, elem.element_id, x, y);
compositor_set_alpha(compositor, elem.element_id, 1.0f); compositor_set_alpha(compositor, elem.element_id, 1.0f);
compositor_set_visible(compositor, elem.element_id, true); compositor_set_visible(compositor, elem.element_id, true);
@@ -224,14 +250,12 @@ public:
float duration_sec = duration_ms / 1000.0f; float duration_sec = duration_ms / 1000.0f;
// Hide all visible sprites for this character
for (auto& pair : ch->sprites) { for (auto& pair : ch->sprites) {
if (pair.second.visible) { if (pair.second.visible) {
if (transition == TRANS_FADE) { if (transition == TRANS_FADE) {
// compositor_tick will set invisible on completion
compositor_animate_fade(compositor, pair.second.element_id, 0.0f, duration_sec, EASE_LINEAR); compositor_animate_fade(compositor, pair.second.element_id, 0.0f, duration_sec, EASE_LINEAR);
} }
else { // TRANS_NONE else {
compositor_set_visible(compositor, pair.second.element_id, false); compositor_set_visible(compositor, pair.second.element_id, false);
compositor_set_alpha(compositor, pair.second.element_id, 0.0f); compositor_set_alpha(compositor, pair.second.element_id, 0.0f);
} }
@@ -259,12 +283,9 @@ public:
} }
void change_sprite(const char* name, const char* new_emotion) { void change_sprite(const char* name, const char* new_emotion) {
// This is an instant snap for cross-fading, which is more complex.
// For now, we just snap.
Character* ch = get_character(name); Character* ch = get_character(name);
if (!ch) return; if (!ch) return;
// Hide current sprite
if (!ch->current_sprite.empty()) { if (!ch->current_sprite.empty()) {
auto old_it = ch->sprites.find(ch->current_sprite); auto old_it = ch->sprites.find(ch->current_sprite);
if (old_it != ch->sprites.end()) { if (old_it != ch->sprites.end()) {
@@ -274,7 +295,6 @@ public:
} }
} }
// Show new sprite at same position/z
auto new_it = ch->sprites.find(new_emotion); auto new_it = ch->sprites.find(new_emotion);
if (new_it != ch->sprites.end()) { if (new_it != ch->sprites.end()) {
compositor_set_position(compositor, new_it->second.element_id, compositor_set_position(compositor, new_it->second.element_id,
@@ -304,7 +324,7 @@ public:
ch->current_scale = scale; ch->current_scale = scale;
float duration_sec = duration_ms / 1000.0f; float duration_sec = duration_ms / 1000.0f;
if (transition == TRANS_FADE) { // Use "fade" as a stand-in for "smooth" if (transition == TRANS_FADE) {
compositor_animate_scale(compositor, id, scale, duration_sec, EASE_IN_OUT_QUAD); compositor_animate_scale(compositor, id, scale, duration_sec, EASE_IN_OUT_QUAD);
} }
else { else {
@@ -312,7 +332,6 @@ public:
} }
} }
// Cleanup
~CharacterManager() { ~CharacterManager() {
for (auto& pair : characters) { for (auto& pair : characters) {
for (auto& sprite : pair.second.sprites) { for (auto& sprite : pair.second.sprites) {
@@ -342,37 +361,36 @@ public:
name_id(nid), dialogue_id(did) { name_id(nid), dialogue_id(did) {
} }
// NEW: Execute a command and return the duration of any triggered animation
float execute(const Command* cmd) { float execute(const Command* cmd) {
switch (cmd->type) { switch (cmd->type) {
case CMD_SHOW: case CMD_SHOW:
char_manager->show_character(cmd->character_name, cmd->x, cmd->y, char_manager->show_character(cmd->character_name, cmd->x, cmd->y,
cmd->z, cmd->sprite_name, cmd->z, cmd->sprite_name,
cmd->transition, cmd->duration_ms); cmd->transition, cmd->duration_ms);
return cmd->duration_ms / 1000.0f; // Return duration return cmd->duration_ms / 1000.0f;
case CMD_HIDE: case CMD_HIDE:
char_manager->hide_character(cmd->character_name, char_manager->hide_character(cmd->character_name,
cmd->transition, cmd->duration_ms); cmd->transition, cmd->duration_ms);
return cmd->duration_ms / 1000.0f; // Return duration return cmd->duration_ms / 1000.0f;
case CMD_MOVE: case CMD_MOVE:
char_manager->move_character(cmd->character_name, cmd->x, cmd->y, char_manager->move_character(cmd->character_name, cmd->x, cmd->y,
cmd->transition, cmd->duration_ms); cmd->transition, cmd->duration_ms);
return cmd->duration_ms / 1000.0f; // Return duration return cmd->duration_ms / 1000.0f;
case CMD_CHANGE_SPRITE: case CMD_CHANGE_SPRITE:
char_manager->change_sprite(cmd->character_name, cmd->sprite_name); char_manager->change_sprite(cmd->character_name, cmd->sprite_name);
break; // Instant break;
case CMD_SET_Z: case CMD_SET_Z:
char_manager->set_z(cmd->character_name, cmd->z); char_manager->set_z(cmd->character_name, cmd->z);
break; // Instant break;
case CMD_SET_SCALE: case CMD_SET_SCALE:
char_manager->set_scale(cmd->character_name, cmd->scale, char_manager->set_scale(cmd->character_name, cmd->scale,
cmd->transition, cmd->duration_ms); cmd->transition, cmd->duration_ms);
return cmd->duration_ms / 1000.0f; // Return duration return cmd->duration_ms / 1000.0f;
case CMD_SPEAK: case CMD_SPEAK:
text_clear(name_text); text_clear(name_text);
@@ -385,13 +403,13 @@ public:
} }
compositor_mark_content_dirty(compositor, name_id); compositor_mark_content_dirty(compositor, name_id);
compositor_mark_content_dirty(compositor, dialogue_id); compositor_mark_content_dirty(compositor, dialogue_id);
break; // Instant break;
case CMD_WAIT: case CMD_WAIT:
case CMD_BACKGROUND: case CMD_BACKGROUND:
break; // No-op break;
} }
return 0.0f; // Default: no duration return 0.0f;
} }
}; };
@@ -399,11 +417,10 @@ public:
// MAIN PROGRAM // MAIN PROGRAM
// ============================================================================ // ============================================================================
// NEW: Main application state
enum AppState { enum AppState {
STATE_IDLE, // Ready to process next command STATE_IDLE,
STATE_ANIMATING, // Waiting for an animation to finish STATE_ANIMATING,
STATE_WAITING_FOR_INPUT // Waiting for user click (after CMD_WAIT) STATE_WAITING_FOR_INPUT
}; };
int main() { int main() {
@@ -423,14 +440,12 @@ int main() {
return 1; return 1;
} }
// --- Background ---
uint32_t* bg = image_load_resized("assets/background.png", WIDTH, HEIGHT); uint32_t* bg = image_load_resized("assets/background.png", WIDTH, HEIGHT);
if (bg) { if (bg) {
uint32_t bg_id = compositor_insert(compositor, bg, WIDTH, HEIGHT, 0, 0, 0); uint32_t bg_id = compositor_insert(compositor, bg, WIDTH, HEIGHT, 0, 0, 0);
compositor_set_alpha(compositor, bg_id, 1.0f); // Make sure bg is visible compositor_set_alpha(compositor, bg_id, 1.0f);
} }
// --- Character Setup ---
const int CHAR_WIDTH = 400; const int CHAR_WIDTH = 400;
const int CHAR_HEIGHT = 600; const int CHAR_HEIGHT = 600;
const int CHAR_Y_OFFSET = 140; const int CHAR_Y_OFFSET = 140;
@@ -438,111 +453,147 @@ int main() {
CharacterManager char_manager(compositor, WIDTH); CharacterManager char_manager(compositor, WIDTH);
// Register characters
char_manager.add_character("maksw", CHAR_WIDTH, CHAR_HEIGHT); char_manager.add_character("maksw", CHAR_WIDTH, CHAR_HEIGHT);
char_manager.add_character("wikor", CHAR_WIDTH, CHAR_HEIGHT); char_manager.add_character("wikor", CHAR_WIDTH, CHAR_HEIGHT);
// Load sprites - these get pre-inserted into compositor (hidden, alpha 0)
char_manager.load_sprite("maksw", "neutral", "assets/maksw.png", CHAR_WIDTH, CHAR_HEIGHT); char_manager.load_sprite("maksw", "neutral", "assets/maksw.png", CHAR_WIDTH, CHAR_HEIGHT);
char_manager.load_sprite("wikor", "neutral", "assets/wikor.png", CHAR_WIDTH, CHAR_HEIGHT); char_manager.load_sprite("wikor", "neutral", "assets/wikor.png", CHAR_WIDTH, CHAR_HEIGHT);
// --- Textbox ---
uint32_t* textbox = image_load_resized("assets/textbox.png", WIDTH, 180); uint32_t* textbox = image_load_resized("assets/textbox.png", WIDTH, 180);
if (textbox) { if (textbox) {
uint32_t box_id = compositor_insert(compositor, textbox, WIDTH, 180, 0, HEIGHT - 180, 3); uint32_t box_id = compositor_insert(compositor, textbox, WIDTH, 180, 0, HEIGHT - 180, 3);
compositor_set_alpha(compositor, box_id, 1.0f); // Make sure it's visible compositor_set_alpha(compositor, box_id, 1.0f);
} }
// --- Text Layers ---
text_t* name_text = text_init(WIDTH, HEIGHT); text_t* name_text = text_init(WIDTH, HEIGHT);
uint32_t name_id = compositor_insert(compositor, text_getfb(name_text), uint32_t name_id = compositor_insert(compositor, text_getfb(name_text),
WIDTH, HEIGHT, 60, HEIGHT - 170, 4); WIDTH, HEIGHT, 60, HEIGHT - 170, 4);
compositor_set_alpha(compositor, name_id, 1.0f); // Text is pre-multiplied compositor_set_alpha(compositor, name_id, 1.0f);
text_t* dialogue_text = text_init_shared(name_text, WIDTH, HEIGHT); text_t* dialogue_text = text_init_shared(name_text, WIDTH - 140, HEIGHT);
uint32_t dialogue_id = compositor_insert(compositor, text_getfb(dialogue_text), uint32_t dialogue_id = compositor_insert(compositor, text_getfb(dialogue_text),
WIDTH, HEIGHT, 60, HEIGHT - 130, 4); WIDTH - 140, HEIGHT, 60, HEIGHT - 130, 4);
compositor_set_alpha(compositor, dialogue_id, 1.0f); compositor_set_alpha(compositor, dialogue_id, 1.0f);
// --- Scene Script (UPDATED with transitions) --- // --- Scene Script ---
Command scene_commands[] = { Command scene_commands[] = {
// Show both characters // Show both characters IN PARALLEL
{CMD_SHOW, "maksw", 100, POS_Y, 2, 1.0f, "neutral", nullptr, nullptr, TRANS_SLIDE_LEFT, 500.0f}, {CMD_SHOW, "maksw", 100, POS_Y, 2, 1.0f, "neutral", nullptr, nullptr, TRANS_SLIDE_LEFT, 500.0f, false},
{CMD_SHOW, "wikor", 800, POS_Y, 1, 1.0f, "neutral", nullptr, nullptr, TRANS_SLIDE_RIGHT, 500.0f}, {CMD_SHOW, "wikor", 800, POS_Y, 1, 1.0f, "neutral", nullptr, nullptr, TRANS_SLIDE_RIGHT, 500.0f, true},
// Maksw speaks (bring to front, scale up, move slightly forward) // Maksw speaks
{CMD_SET_Z, "maksw", 0, 0, 3, 0, nullptr, nullptr, nullptr, TRANS_NONE, 0}, {CMD_SET_Z, "maksw", 0, 0, 3, 0, nullptr, nullptr, nullptr, TRANS_NONE, 0, false},
{CMD_SET_SCALE, "maksw", 0, 0, 0, 1.05f, nullptr, nullptr, nullptr, TRANS_FADE, 200.0f}, {CMD_SET_SCALE, "maksw", 0, 0, 0, 1.05f, nullptr, nullptr, nullptr, TRANS_FADE, 200.0f, false},
{CMD_SPEAK, nullptr, 0, 0, 0, 0, nullptr, "Maksw", {CMD_SPEAK, nullptr, 0, 0, 0, 0, nullptr, "Maksw",
"Hey, Wikor, nice weather today, huh?", TRANS_NONE, 0}, u8"Hey, admirał wiktorov, the sun's actually out today. Did the weather daemon crash overnight?", TRANS_NONE, 0, true},
{CMD_WAIT, nullptr, 0, 0, 0, 0, nullptr, nullptr, nullptr, TRANS_NONE, 0}, {CMD_WAIT, nullptr, 0, 0, 0, 0, nullptr, nullptr, nullptr, TRANS_NONE, 0, true},
// Wikor responds // Wikor responds
{CMD_SET_SCALE, "maksw", 0, 0, 0, 1.0f, nullptr, nullptr, nullptr, TRANS_FADE, 100.0f}, {CMD_SET_SCALE, "maksw", 0, 0, 0, 1.0f, nullptr, nullptr, nullptr, TRANS_FADE, 100.0f, false},
{CMD_SET_Z, "maksw", 0, 0, 1, 0, nullptr, nullptr, nullptr, TRANS_NONE, 0}, {CMD_SET_Z, "maksw", 0, 0, 1, 0, nullptr, nullptr, nullptr, TRANS_NONE, 0, false},
{CMD_SET_Z, "wikor", 0, 0, 3, 0, nullptr, nullptr, nullptr, TRANS_NONE, 0}, {CMD_SET_Z, "wikor", 0, 0, 3, 0, nullptr, nullptr, nullptr, TRANS_NONE, 0, false},
{CMD_SET_SCALE, "wikor", 0, 0, 0, 1.05f, nullptr, nullptr, nullptr, TRANS_FADE, 200.0f}, {CMD_SET_SCALE, "wikor", 0, 0, 0, 1.05f, nullptr, nullptr, nullptr, TRANS_FADE, 200.0f, false},
{CMD_SPEAK, nullptr, 0, 0, 0, 0, nullptr, "Wikor", {CMD_SPEAK, nullptr, 0, 0, 0, 0, nullptr, u8"admirał wiktorov",
"Yeah, it's surprisingly sunny for November.", TRANS_NONE, 0}, "Either that, or someone forgot to reboot the clouds. I didn't authorize this much sunlight.", TRANS_NONE, 0, true},
{CMD_WAIT, nullptr, 0, 0, 0, 0, nullptr, nullptr, nullptr, TRANS_NONE, 0}, {CMD_WAIT, nullptr, 0, 0, 0, 0, nullptr, nullptr, nullptr, TRANS_NONE, 0, true},
// Maksw speaks again // Maksw replies
{CMD_SET_SCALE, "wikor", 0, 0, 0, 1.0f, nullptr, nullptr, nullptr, TRANS_FADE, 100.0f}, {CMD_SET_SCALE, "wikor", 0, 0, 0, 1.0f, nullptr, nullptr, nullptr, TRANS_FADE, 100.0f, false},
{CMD_SET_Z, "wikor", 0, 0, 1, 0, nullptr, nullptr, nullptr, TRANS_NONE, 0}, {CMD_SET_Z, "wikor", 0, 0, 1, 0, nullptr, nullptr, nullptr, TRANS_NONE, 0, false},
{CMD_SET_Z, "maksw", 0, 0, 3, 0, nullptr, nullptr, nullptr, TRANS_NONE, 0}, {CMD_SET_Z, "maksw", 0, 0, 3, 0, nullptr, nullptr, nullptr, TRANS_NONE, 0, false},
{CMD_SET_SCALE, "maksw", 0, 0, 0, 1.05f, nullptr, nullptr, nullptr, TRANS_FADE, 200.0f}, {CMD_SET_SCALE, "maksw", 0, 0, 0, 1.05f, nullptr, nullptr, nullptr, TRANS_FADE, 200.0f, false},
{CMD_SPEAK, nullptr, 0, 0, 0, 0, nullptr, "Maksw", {CMD_SPEAK, nullptr, 0, 0, 0, 0, nullptr, "Maksw",
"We should get back to debugging soon though.", TRANS_NONE, 0}, "You sound disappointed. Don't tell me you actually prefer the cold and gloom.", TRANS_NONE, 0, true},
{CMD_WAIT, nullptr, 0, 0, 0, 0, nullptr, nullptr, nullptr, TRANS_NONE, 0}, {CMD_WAIT, nullptr, 0, 0, 0, 0, nullptr, nullptr, nullptr, TRANS_NONE, 0, true},
// Wikor final line // Wikor gets smug
{CMD_SET_SCALE, "maksw", 0, 0, 0, 1.0f, nullptr, nullptr, nullptr, TRANS_FADE, 100.0f}, {CMD_SET_SCALE, "maksw", 0, 0, 0, 1.0f, nullptr, nullptr, nullptr, TRANS_FADE, 100.0f, false},
{CMD_SET_Z, "maksw", 0, 0, 1, 0, nullptr, nullptr, nullptr, TRANS_NONE, 0}, {CMD_SET_Z, "maksw", 0, 0, 1, 0, nullptr, nullptr, nullptr, TRANS_NONE, 0, false},
{CMD_SET_Z, "wikor", 0, 0, 3, 0, nullptr, nullptr, nullptr, TRANS_NONE, 0}, {CMD_SET_Z, "wikor", 0, 0, 3, 0, nullptr, nullptr, nullptr, TRANS_NONE, 0, false},
{CMD_SET_SCALE, "wikor", 0, 0, 0, 1.05f, nullptr, nullptr, nullptr, TRANS_FADE, 200.0f}, {CMD_SET_SCALE, "wikor", 0, 0, 0, 1.05f, nullptr, nullptr, nullptr, TRANS_FADE, 200.0f, false},
{CMD_SPEAK, nullptr, 0, 0, 0, 0, nullptr, "Wikor", {CMD_SPEAK, nullptr, 0, 0, 0, 0, nullptr, u8"admirał wiktorov",
"You're right, that compositor won't fix itself.", TRANS_NONE, 0}, "Cold means fewer bugs - in the code and in the air. The moment it warms up, the chaos begins.", TRANS_NONE, 0, true},
{CMD_WAIT, nullptr, 0, 0, 0, 0, nullptr, nullptr, nullptr, TRANS_NONE, 0}, {CMD_WAIT, nullptr, 0, 0, 0, 0, nullptr, nullptr, nullptr, TRANS_NONE, 0, true},
// Fade out // Maksw teasingly replies
{CMD_SPEAK, nullptr, 0, 0, 0, 0, nullptr, ""}, {CMD_SET_SCALE, "wikor", 0, 0, 0, 1.0f, nullptr, nullptr, nullptr, TRANS_FADE, 100.0f, false},
{CMD_HIDE, "maksw", 0, 0, 0, 0, nullptr, nullptr, nullptr, TRANS_FADE, 1000.0f}, {CMD_SET_Z, "wikor", 0, 0, 1, 0, nullptr, nullptr, nullptr, TRANS_NONE, 0, false},
{CMD_HIDE, "wikor", 0, 0, 0, 0, nullptr, nullptr, nullptr, TRANS_FADE, 1000.0f}, {CMD_SET_Z, "maksw", 0, 0, 3, 0, nullptr, nullptr, nullptr, TRANS_NONE, 0, false},
{CMD_SET_SCALE, "maksw", 0, 0, 0, 1.05f, nullptr, nullptr, nullptr, TRANS_FADE, 200.0f, false},
{CMD_SPEAK, nullptr, 0, 0, 0, 0, nullptr, "Maksw",
"You just don't like seeing people happy. Admit it.", TRANS_NONE, 0, true},
{CMD_WAIT, nullptr, 0, 0, 0, 0, nullptr, nullptr, nullptr, TRANS_NONE, 0, true},
// Wikor snarks back
{CMD_SET_SCALE, "maksw", 0, 0, 0, 1.0f, nullptr, nullptr, nullptr, TRANS_FADE, 100.0f, false},
{CMD_SET_Z, "maksw", 0, 0, 1, 0, nullptr, nullptr, nullptr, TRANS_NONE, 0, false},
{CMD_SET_Z, "wikor", 0, 0, 3, 0, nullptr, nullptr, nullptr, TRANS_NONE, 0, false},
{CMD_SET_SCALE, "wikor", 0, 0, 0, 1.05f, nullptr, nullptr, nullptr, TRANS_FADE, 200.0f, false},
{CMD_SPEAK, nullptr, 0, 0, 0, 0, nullptr, u8"admirał wiktorov",
"Happiness is fine, as long as it doesn't interfere with system uptime.", TRANS_NONE, 0, true},
{CMD_WAIT, nullptr, 0, 0, 0, 0, nullptr, nullptr, nullptr, TRANS_NONE, 0, true},
// Closing line
{CMD_SET_SCALE, "wikor", 0, 0, 0, 1.0f, nullptr, nullptr, nullptr, TRANS_FADE, 100.0f, false},
{CMD_SET_Z, "wikor", 0, 0, 1, 0, nullptr, nullptr, nullptr, TRANS_NONE, 0, false},
{CMD_SET_Z, "maksw", 0, 0, 3, 0, nullptr, nullptr, nullptr, TRANS_NONE, 0, false},
{CMD_SET_SCALE, "maksw", 0, 0, 0, 1.05f, nullptr, nullptr, nullptr, TRANS_FADE, 200.0f, false},
{CMD_SPEAK, nullptr, 0, 0, 0, 0, nullptr, "Maksw",
"Then let's keep uptime at 100% and go grab some coffee before the next storm update.", TRANS_NONE, 0, true},
{CMD_WAIT, nullptr, 0, 0, 0, 0, nullptr, nullptr, nullptr, TRANS_NONE, 0, true},
// Fade out both
{CMD_SPEAK, nullptr, 0, 0, 0, 0, nullptr, "", nullptr, TRANS_NONE, 0, false},
{CMD_HIDE, "maksw", 0, 0, 0, 0, nullptr, nullptr, nullptr, TRANS_FADE, 500.0f, false},
{CMD_HIDE, "wikor", 0, 0, 0, 0, nullptr, nullptr, nullptr, TRANS_FADE, 500.0f, true},
}; };
Scene scene = { scene_commands, sizeof(scene_commands) / sizeof(Command), 0 }; Scene scene = { scene_commands, sizeof(scene_commands) / sizeof(Command), 0 };
CommandExecutor executor(&char_manager, compositor, name_text, dialogue_text, CommandExecutor executor(&char_manager, compositor, name_text, dialogue_text,
name_id, dialogue_id); name_id, dialogue_id);
// --- Main Loop ---
bool running = true; bool running = true;
// NEW: State machine variables AppState current_state = STATE_IDLE;
AppState current_state = STATE_IDLE; // Start in IDLE to process first commands AnimationTracker anim_tracker; // NEW: Track multiple animations
float animation_timer = 0.0f;
// NEW: Delta-time variables
auto last_frame_time = std::chrono::high_resolution_clock::now(); auto last_frame_time = std::chrono::high_resolution_clock::now();
int frame_count = 0;
float frame_time_sum = 0.0f;
const int FPS_REPORT_FRAME_COUNT = 60;
while (running) { while (running) {
if (window_pollevent(window)) running = false; if (window_pollevent(window)) running = false;
// --- 1. Calculate Delta Time ---
auto current_time = std::chrono::high_resolution_clock::now(); auto current_time = std::chrono::high_resolution_clock::now();
std::chrono::duration<float> delta_duration = current_time - last_frame_time; std::chrono::duration<float> delta_duration = current_time - last_frame_time;
float delta_time_sec = delta_duration.count(); float delta_time_sec = delta_duration.count();
last_frame_time = current_time; last_frame_time = current_time;
// --- 2. Update Animations (ALWAYS) --- frame_count++;
frame_time_sum += delta_time_sec;
if (frame_count >= FPS_REPORT_FRAME_COUNT) {
float avg_delta_time = frame_time_sum / (float)frame_count;
float fps = 1.0f / avg_delta_time;
printf("FPS: %.2f (over %d frames)\n", fps, FPS_REPORT_FRAME_COUNT);
// Reset counters
frame_count = 0;
frame_time_sum = 0.0f;
}
compositor_tick(compositor, delta_time_sec); compositor_tick(compositor, delta_time_sec);
// Update text animation
text_nextframe(name_text); text_nextframe(name_text);
text_nextframe(dialogue_text); text_nextframe(dialogue_text);
compositor_mark_content_dirty(compositor, name_id); compositor_mark_content_dirty(compositor, name_id);
compositor_mark_content_dirty(compositor, dialogue_id); compositor_mark_content_dirty(compositor, dialogue_id);
// --- 3. Handle Input --- // NEW: Update animation tracker
anim_tracker.update(delta_time_sec);
static bool mouse_was_down = false; static bool mouse_was_down = false;
bool mouse_is_down = window_is_mouse_button_down(window, MOUSE_BUTTON_LEFT); bool mouse_is_down = window_is_mouse_button_down(window, MOUSE_BUTTON_LEFT);
bool mouse_clicked = mouse_is_down && !mouse_was_down; bool mouse_clicked = mouse_is_down && !mouse_was_down;
@@ -555,39 +606,28 @@ int main() {
bool advance_input = mouse_clicked || key_pressed; bool advance_input = mouse_clicked || key_pressed;
// --- 4. Main State Machine --- // Check if animations finished
// Check if animation finished
if (current_state == STATE_ANIMATING) { if (current_state == STATE_ANIMATING) {
animation_timer -= delta_time_sec; if (!anim_tracker.has_active_animations()) {
if (animation_timer <= 0.0f) {
current_state = STATE_IDLE; current_state = STATE_IDLE;
} }
} }
// Handle user input
if (advance_input) { if (advance_input) {
if (text_is_animating(dialogue_text)) { if (text_is_animating(dialogue_text)) {
// Fast-forward text
text_show_all_current(dialogue_text); text_show_all_current(dialogue_text);
} }
else if (current_state == STATE_WAITING_FOR_INPUT) { else if (current_state == STATE_WAITING_FOR_INPUT) {
// Advance from a WAIT command
current_state = STATE_IDLE; current_state = STATE_IDLE;
} }
else if (current_state == STATE_ANIMATING) { else if (current_state == STATE_ANIMATING) {
// User wants to skip animation // Skip all remaining animations
animation_timer = 0.0f; anim_tracker.clear();
current_state = STATE_IDLE; current_state = STATE_IDLE;
// Note: The animation won't snap to the end.
// A 'snap' function would be needed in the compositor.
// For now, this just proceeds to the next command.
} }
} }
// Process commands if idle // Process commands if idle
// This while-loop will execute all instant commands in a single frame
// until it hits an animation or a wait.
while (current_state == STATE_IDLE) { while (current_state == STATE_IDLE) {
if (!scene.has_next()) { if (!scene.has_next()) {
running = false; running = false;
@@ -596,31 +636,33 @@ int main() {
const Command* cmd = scene.current(); const Command* cmd = scene.current();
float duration = executor.execute(cmd); float duration = executor.execute(cmd);
// NEW: Add animation to tracker
if (duration > 0.0f) {
anim_tracker.add_animation(duration);
}
scene.next(); scene.next();
if (duration > 0.0f) { // NEW: Check blocking flag
// This was an animated command if (cmd->blocking) {
current_state = STATE_ANIMATING; if (cmd->type == CMD_WAIT) {
animation_timer = duration; current_state = STATE_WAITING_FOR_INPUT;
break;
}
else if (anim_tracker.has_active_animations()) {
current_state = STATE_ANIMATING;
break;
}
// If blocking but no animations, continue to next command
} }
else if (cmd->type == CMD_WAIT) { // If non-blocking, loop continues immediately to next command
// This was a wait command
current_state = STATE_WAITING_FOR_INPUT;
}
// else: Instant command, loop continues to next command
} }
// --- 5. Render Frame ---
const uint32_t* fb = compositor_compose(compositor); const uint32_t* fb = compositor_compose(compositor);
window_blit(window, fb); window_blit(window, fb);
// We no longer manually limit framerate.
// V-Sync or other methods should be used if needed.
// For now, we let it run as fast as possible.
// window_limit_framerate(window, 60); // REMOVED
} }
// --- Cleanup ---
if (bg) image_free(bg); if (bg) image_free(bg);
if (textbox) image_free(textbox); if (textbox) image_free(textbox);
text_destroy(name_text); text_destroy(name_text);
+575 -479
View File
File diff suppressed because it is too large. Load diff
+4
View File
@@ -97,6 +97,7 @@
<LanguageStandard>stdcpp20</LanguageStandard> <LanguageStandard>stdcpp20</LanguageStandard>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary> <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<EnableEnhancedInstructionSet>AdvancedVectorExtensions2</EnableEnhancedInstructionSet> <EnableEnhancedInstructionSet>AdvancedVectorExtensions2</EnableEnhancedInstructionSet>
<AdditionalOptions>/Zc:char8_t- %(AdditionalOptions)</AdditionalOptions>
</ClCompile> </ClCompile>
<Link> <Link>
<SubSystem>Console</SubSystem> <SubSystem>Console</SubSystem>
@@ -118,6 +119,7 @@
<LanguageStandard>stdcpp20</LanguageStandard> <LanguageStandard>stdcpp20</LanguageStandard>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary> <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<EnableEnhancedInstructionSet>AdvancedVectorExtensions2</EnableEnhancedInstructionSet> <EnableEnhancedInstructionSet>AdvancedVectorExtensions2</EnableEnhancedInstructionSet>
<AdditionalOptions>/Zc:char8_t- %(AdditionalOptions)</AdditionalOptions>
</ClCompile> </ClCompile>
<Link> <Link>
<SubSystem>Console</SubSystem> <SubSystem>Console</SubSystem>
@@ -137,6 +139,7 @@
<AdditionalIncludeDirectories>include</AdditionalIncludeDirectories> <AdditionalIncludeDirectories>include</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation> <MultiProcessorCompilation>true</MultiProcessorCompilation>
<EnableEnhancedInstructionSet>AdvancedVectorExtensions2</EnableEnhancedInstructionSet> <EnableEnhancedInstructionSet>AdvancedVectorExtensions2</EnableEnhancedInstructionSet>
<AdditionalOptions>/Zc:char8_t- %(AdditionalOptions)</AdditionalOptions>
</ClCompile> </ClCompile>
<Link> <Link>
<SubSystem>Console</SubSystem> <SubSystem>Console</SubSystem>
@@ -158,6 +161,7 @@
<LanguageStandard>stdcpp20</LanguageStandard> <LanguageStandard>stdcpp20</LanguageStandard>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary> <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<EnableEnhancedInstructionSet>AdvancedVectorExtensions2</EnableEnhancedInstructionSet> <EnableEnhancedInstructionSet>AdvancedVectorExtensions2</EnableEnhancedInstructionSet>
<AdditionalOptions>/Zc:char8_t- %(AdditionalOptions)</AdditionalOptions>
</ClCompile> </ClCompile>
<Link> <Link>
<SubSystem>Console</SubSystem> <SubSystem>Console</SubSystem>