forked from NotAShelf/rogged
Compare commits
No commits in common. "d9c1ee9faa8ee1f61a4644a0cf13cfd22b1f7ad2" and "4a718b968528c94be54ba4b833d25194411964e6" have entirely different histories.
d9c1ee9faa
...
4a718b9685
8 changed files with 47 additions and 297 deletions
2
Justfile
2
Justfile
|
|
@ -13,7 +13,7 @@ clean:
|
||||||
# Format all C source files
|
# Format all C source files
|
||||||
fmt:
|
fmt:
|
||||||
clang-format -i src/*.c src/*.h
|
clang-format -i src/*.c src/*.h
|
||||||
zig fmt libs/combat/*.zig
|
zig fmt **/*.zig
|
||||||
|
|
||||||
# Check formatting
|
# Check formatting
|
||||||
fmt-check:
|
fmt-check:
|
||||||
|
|
|
||||||
122
README.md
122
README.md
|
|
@ -1,121 +1,3 @@
|
||||||
# Rogged
|
# rogged
|
||||||
|
|
||||||
Turn-based roguelike dungeon crawler, built in C99 and with a dash of Zig to
|
I got rogged :/
|
||||||
serve as a learning opportunity. Rogged is basically a classic roguelike where
|
|
||||||
you descend through floors of a procedurally generated dungeon, fighting
|
|
||||||
enemies, managing inventory, and trying to reach the bottom alive.
|
|
||||||
|
|
||||||
A non-exhaustive list of its (current) features:
|
|
||||||
|
|
||||||
- Turn-based combat with damage variance, critical hits, dodge, and block
|
|
||||||
mechanics
|
|
||||||
- Damage classes (Slash, Impact, Pierce, Fire, and Poison)
|
|
||||||
- Status effects (Poison, Bleed, Stun, Weaken, and Burn)
|
|
||||||
- Various enemy classes (Goblin, Skeleton, Orc) with distinct resistance
|
|
||||||
profiles
|
|
||||||
- Procedural dungeon generation with rooms and corridors, seeded per floor
|
|
||||||
- Inventory and equipment system (weapons, armor, potions)
|
|
||||||
- Procedural audio via raylib
|
|
||||||
- ASCII-inspired tile rendering, with HP bars and floating damage text
|
|
||||||
|
|
||||||
**Controls:**
|
|
||||||
|
|
||||||
| Key | Action |
|
|
||||||
| ------------- | ----------------------------------- |
|
|
||||||
| WASD / Arrows | Move or attack |
|
|
||||||
| G | Pick up item |
|
|
||||||
| I | Open inventory |
|
|
||||||
| U | Use a potion |
|
|
||||||
| E | Equip item from inventory |
|
|
||||||
| D | Drop item |
|
|
||||||
| Y / N | Confirm / decline descending stairs |
|
|
||||||
| R | Restart (on game over) |
|
|
||||||
| Q | Quit |
|
|
||||||
|
|
||||||
## Build Instructions
|
|
||||||
|
|
||||||
Rogged is built with C99 and Zig. Besides `raylib` and `pkg-config` you will
|
|
||||||
need a C compiler and basic Zig tooling. For now, we use C99 and Zig 0.15.2.
|
|
||||||
Those might change in the future.
|
|
||||||
|
|
||||||
Additionally you will need `clang-format` and `just` for the developer workflow
|
|
||||||
if you plan to contribute.
|
|
||||||
|
|
||||||
### Using Nix (Recommended)
|
|
||||||
|
|
||||||
The recommended developer tooling is [Nix](https://nixos.org). This provides a
|
|
||||||
pure, reproducible devshell across all machines.
|
|
||||||
|
|
||||||
```sh
|
|
||||||
# Enter the development shell
|
|
||||||
$ nix develop
|
|
||||||
|
|
||||||
# Build and run
|
|
||||||
$ just dev
|
|
||||||
```
|
|
||||||
|
|
||||||
### Manual Build
|
|
||||||
|
|
||||||
```sh
|
|
||||||
# Full build
|
|
||||||
$ zig build
|
|
||||||
|
|
||||||
# Build and run
|
|
||||||
$ zig build run
|
|
||||||
|
|
||||||
# or
|
|
||||||
$ just dev
|
|
||||||
```
|
|
||||||
|
|
||||||
### Task Runner Commands
|
|
||||||
|
|
||||||
There's a `Justfile` designed to make common tasks somewhat easier. For now,
|
|
||||||
they are as follows:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
just build # zig build
|
|
||||||
just dev # zig build run
|
|
||||||
just clean # remove zig-out/ and .zig-cache/
|
|
||||||
just fmt # format all C and Zig source files
|
|
||||||
just fmt-check # check formatting
|
|
||||||
```
|
|
||||||
|
|
||||||
If the project gets more complicated, new tasks might be added.
|
|
||||||
|
|
||||||
## Future Plans
|
|
||||||
|
|
||||||
The game is currently **playable end-to-end** but it lacks _serious_ polish to
|
|
||||||
claim its place as a fun roguelike. Some of the features I'd like to introduce,
|
|
||||||
in no particular order, are as follows:
|
|
||||||
|
|
||||||
- [ ] **Save / Load system** - Persist and restore game state between sessions
|
|
||||||
- [ ] **More enemy variety** - Additional enemy types with unique abilities
|
|
||||||
- [ ] **More item variety** - Rings, wands, scrolls, and cursed items
|
|
||||||
- [ ] **Multiple floors beyond 5** - Endless or configurable depth
|
|
||||||
- [ ] **Test suite** - Unit tests for combat math, dungeon generation, and RNG
|
|
||||||
- [ ] **Field of view** - Fog of war and line-of-sight mechanics
|
|
||||||
- [ ] **Level transitions** - Upward stairs and floor teleportation
|
|
||||||
- [ ] **Achievements / death log** - Track runs and causes of death with a
|
|
||||||
leaderboard
|
|
||||||
- [ ] **UI polish** - Better message log history, item descriptions, death
|
|
||||||
screen
|
|
||||||
|
|
||||||
In addition, it might be interesting to allow customizing the "world state" by
|
|
||||||
as scripting API. Though, that is for much later.
|
|
||||||
|
|
||||||
## Attributions
|
|
||||||
|
|
||||||
[Shattered Pixel Dungeon]: https://github.com/00-Evan/shattered-pixel-dungeon
|
|
||||||
[Raylib]: https://www.raylib.com
|
|
||||||
|
|
||||||
This project draws a fair bit of inspiration from [Shattered Pixel Dungeon].
|
|
||||||
While the mechanics are generally different, and commit to remain as such, I
|
|
||||||
cannot deny the amount of inspiration & ideas Shattered Pixel Dungeon has given
|
|
||||||
me. It's a GPL licensed project, and no code was borrowed. Still, some
|
|
||||||
resemblance may occur.
|
|
||||||
|
|
||||||
Additionally, _huge_ thanks to [Raylib] for how easy it made graphics and audio.
|
|
||||||
This was perhaps my best experience in developing a graphical application, and
|
|
||||||
CERTAINLY the most ergonomic when it comes to writing a game.
|
|
||||||
|
|
||||||
_I got rogged :/_
|
|
||||||
|
|
|
||||||
Binary file not shown.
28
src/audio.c
28
src/audio.c
|
|
@ -94,29 +94,7 @@ void audio_play_player_damage(void) {
|
||||||
|
|
||||||
void audio_play_stairs(void) {
|
void audio_play_stairs(void) {
|
||||||
// Ascending stairs sound
|
// Ascending stairs sound
|
||||||
Sound staircase = LoadSound("./assets/sounds/levelcomplete.wav");
|
play_tone(400.0f, 0.1f, 0.3f);
|
||||||
PlaySound(staircase);
|
play_tone(600.0f, 0.1f, 0.3f);
|
||||||
}
|
play_tone(800.0f, 0.15f, 0.3f);
|
||||||
|
|
||||||
void audio_play_dodge(void) {
|
|
||||||
// High-pitched whoosh
|
|
||||||
play_tone(900.0f, 0.08f, 0.3f);
|
|
||||||
}
|
|
||||||
|
|
||||||
void audio_play_block(void) {
|
|
||||||
// Low-then-mid metallic clang
|
|
||||||
play_tone(250.0f, 0.06f, 0.5f);
|
|
||||||
play_tone(350.0f, 0.04f, 0.3f);
|
|
||||||
}
|
|
||||||
|
|
||||||
void audio_play_crit(void) {
|
|
||||||
// Sharp crack with high-pitched follow
|
|
||||||
play_tone(600.0f, 0.05f, 0.7f);
|
|
||||||
play_tone(900.0f, 0.1f, 0.5f);
|
|
||||||
}
|
|
||||||
|
|
||||||
void audio_play_proc(void) {
|
|
||||||
// Ascending two-tone proc chime
|
|
||||||
play_tone(500.0f, 0.08f, 0.4f);
|
|
||||||
play_tone(700.0f, 0.1f, 0.35f);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
12
src/audio.h
12
src/audio.h
|
|
@ -25,16 +25,4 @@ void audio_play_player_damage(void);
|
||||||
// Play stairs/level change sound
|
// Play stairs/level change sound
|
||||||
void audio_play_stairs(void);
|
void audio_play_stairs(void);
|
||||||
|
|
||||||
// Play dodge sound
|
|
||||||
void audio_play_dodge(void);
|
|
||||||
|
|
||||||
// Play block sound
|
|
||||||
void audio_play_block(void);
|
|
||||||
|
|
||||||
// Play critical hit sound
|
|
||||||
void audio_play_crit(void);
|
|
||||||
|
|
||||||
// Play status effect proc sound
|
|
||||||
void audio_play_proc(void);
|
|
||||||
|
|
||||||
#endif // AUDIO_H
|
#endif // AUDIO_H
|
||||||
|
|
|
||||||
|
|
@ -108,8 +108,6 @@ typedef struct {
|
||||||
int value;
|
int value;
|
||||||
int lifetime; // frames remaining
|
int lifetime; // frames remaining
|
||||||
int is_critical;
|
int is_critical;
|
||||||
char label[8]; // non-empty -> show label instead of numeric value
|
|
||||||
StatusEffectType effect_type; // used to pick color for proc labels
|
|
||||||
} FloatingText;
|
} FloatingText;
|
||||||
|
|
||||||
// GameState - encapsulates all game state for testability and save/load
|
// GameState - encapsulates all game state for testability and save/load
|
||||||
|
|
|
||||||
105
src/main.c
105
src/main.c
|
|
@ -23,20 +23,21 @@ static void add_log(GameState *gs, const char *msg) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reuse an expired float slot, or claim the next free one
|
|
||||||
static int float_slot(GameState *gs) {
|
|
||||||
if (gs->floating_count < 8)
|
|
||||||
return gs->floating_count++;
|
|
||||||
for (int i = 0; i < 8; i++) {
|
|
||||||
if (gs->floating_texts[i].lifetime <= 0)
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// spawn floating damage text
|
// spawn floating damage text
|
||||||
static void spawn_floating_text(GameState *gs, int x, int y, int value, int is_critical) {
|
static void spawn_floating_text(GameState *gs, int x, int y, int value, int is_critical) {
|
||||||
int slot = float_slot(gs);
|
// Reuse an expired slot if all slots are taken
|
||||||
|
int slot = -1;
|
||||||
|
if (gs->floating_count < 8) {
|
||||||
|
slot = gs->floating_count;
|
||||||
|
gs->floating_count++;
|
||||||
|
} else {
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
if (gs->floating_texts[i].lifetime <= 0) {
|
||||||
|
slot = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if (slot < 0)
|
if (slot < 0)
|
||||||
return;
|
return;
|
||||||
gs->floating_texts[slot].x = x;
|
gs->floating_texts[slot].x = x;
|
||||||
|
|
@ -44,34 +45,6 @@ static void spawn_floating_text(GameState *gs, int x, int y, int value, int is_c
|
||||||
gs->floating_texts[slot].value = value;
|
gs->floating_texts[slot].value = value;
|
||||||
gs->floating_texts[slot].lifetime = 60;
|
gs->floating_texts[slot].lifetime = 60;
|
||||||
gs->floating_texts[slot].is_critical = is_critical;
|
gs->floating_texts[slot].is_critical = is_critical;
|
||||||
gs->floating_texts[slot].label[0] = '\0'; // numeric, no label
|
|
||||||
gs->floating_texts[slot].effect_type = EFFECT_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// spawn floating label text (DODGE, BLOCK, CRIT!, proc names, SLAIN)
|
|
||||||
static void spawn_floating_label(GameState *gs, int x, int y, const char *label, StatusEffectType effect_type) {
|
|
||||||
int slot = float_slot(gs);
|
|
||||||
if (slot < 0)
|
|
||||||
return;
|
|
||||||
gs->floating_texts[slot].x = x;
|
|
||||||
gs->floating_texts[slot].y = y;
|
|
||||||
gs->floating_texts[slot].value = 0;
|
|
||||||
gs->floating_texts[slot].lifetime = 60;
|
|
||||||
gs->floating_texts[slot].is_critical = 0;
|
|
||||||
gs->floating_texts[slot].effect_type = effect_type;
|
|
||||||
strncpy(gs->floating_texts[slot].label, label, 7);
|
|
||||||
gs->floating_texts[slot].label[7] = '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
static const char *proc_label_for(StatusEffectType effect) {
|
|
||||||
switch (effect) {
|
|
||||||
case EFFECT_POISON: return "POISON!";
|
|
||||||
case EFFECT_BLEED: return "BLEED!";
|
|
||||||
case EFFECT_BURN: return "BURN!";
|
|
||||||
case EFFECT_STUN: return "STUN!";
|
|
||||||
case EFFECT_WEAKEN: return "WEAKEN!";
|
|
||||||
default: return "";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// update floating texts and screen shake
|
// update floating texts and screen shake
|
||||||
|
|
@ -157,8 +130,7 @@ static void tick_all_effects(GameState *gs) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// attacked_enemy: the enemy the player attacked this turn, or NULL if player only moved
|
static void post_action(GameState *gs) {
|
||||||
static void post_action(GameState *gs, Enemy *attacked_enemy) {
|
|
||||||
gs->turn_count++;
|
gs->turn_count++;
|
||||||
|
|
||||||
// Tick status effects at the start of this turn
|
// Tick status effects at the start of this turn
|
||||||
|
|
@ -174,33 +146,13 @@ static void post_action(GameState *gs, Enemy *attacked_enemy) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// combat feedback - player attacked an enemy this turn
|
// combat feedback - player attacked enemy
|
||||||
if (attacked_enemy != NULL) {
|
if (combat_get_last_damage() > 0 && !combat_was_player_damage()) {
|
||||||
int ex = attacked_enemy->x * TILE_SIZE + 8;
|
for (int i = 0; i < gs->enemy_count; i++) {
|
||||||
int ey = attacked_enemy->y * TILE_SIZE;
|
if (!gs->enemies[i].alive) {
|
||||||
|
spawn_floating_text(gs, gs->enemies[i].x * TILE_SIZE + 8, gs->enemies[i].y * TILE_SIZE,
|
||||||
if (combat_was_dodged()) {
|
combat_get_last_damage(), combat_was_critical());
|
||||||
spawn_floating_label(gs, ex, ey, "DODGE", EFFECT_NONE);
|
break;
|
||||||
audio_play_dodge();
|
|
||||||
} else {
|
|
||||||
if (combat_get_last_damage() > 0)
|
|
||||||
spawn_floating_text(gs, ex, ey, combat_get_last_damage(), combat_was_critical());
|
|
||||||
if (combat_was_blocked()) {
|
|
||||||
spawn_floating_label(gs, ex, ey - 10, "BLOCK", EFFECT_NONE);
|
|
||||||
audio_play_block();
|
|
||||||
}
|
|
||||||
if (combat_was_critical()) {
|
|
||||||
spawn_floating_label(gs, ex + 8, ey - 10, "CRIT!", EFFECT_NONE);
|
|
||||||
audio_play_crit();
|
|
||||||
}
|
|
||||||
StatusEffectType applied = combat_get_applied_effect();
|
|
||||||
if (applied != EFFECT_NONE) {
|
|
||||||
spawn_floating_label(gs, ex, ey - 20, proc_label_for(applied), applied);
|
|
||||||
audio_play_proc();
|
|
||||||
}
|
|
||||||
if (!attacked_enemy->alive) {
|
|
||||||
spawn_floating_label(gs, ex, ey - 20, "SLAIN", EFFECT_NONE);
|
|
||||||
audio_play_enemy_death();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -431,7 +383,7 @@ static int handle_movement_input(GameState *gs) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action)
|
if (action)
|
||||||
post_action(gs, target); // target is NULL on move, enemy ptr on attack
|
post_action(gs);
|
||||||
|
|
||||||
return action;
|
return action;
|
||||||
}
|
}
|
||||||
|
|
@ -504,18 +456,15 @@ static void game_loop(void) {
|
||||||
BeginDrawing();
|
BeginDrawing();
|
||||||
ClearBackground(BLACK);
|
ClearBackground(BLACK);
|
||||||
|
|
||||||
// Draw game world with screen shake applied via camera offset
|
// Draw game elements (with screen shake offset)
|
||||||
Camera2D cam = {0};
|
if (gs.screen_shake > 0) {
|
||||||
cam.zoom = 1.0f;
|
// Apply shake offset to drawing
|
||||||
cam.offset = (Vector2){(float)gs.shake_x, (float)gs.shake_y};
|
}
|
||||||
BeginMode2D(cam);
|
|
||||||
render_map(&gs.map);
|
render_map(&gs.map);
|
||||||
render_items(gs.items, gs.item_count);
|
render_items(gs.items, gs.item_count);
|
||||||
render_enemies(gs.enemies, gs.enemy_count);
|
render_enemies(gs.enemies, gs.enemy_count);
|
||||||
render_player(&gs.player);
|
render_player(&gs.player);
|
||||||
EndMode2D();
|
|
||||||
|
|
||||||
// Floating texts follow world shake
|
|
||||||
render_floating_texts(gs.floating_texts, gs.floating_count, gs.shake_x, gs.shake_y);
|
render_floating_texts(gs.floating_texts, gs.floating_count, gs.shake_x, gs.shake_y);
|
||||||
render_ui(&gs.player);
|
render_ui(&gs.player);
|
||||||
|
|
||||||
|
|
|
||||||
73
src/render.c
73
src/render.c
|
|
@ -61,20 +61,12 @@ void render_enemies(const Enemy *enemies, int count) {
|
||||||
|
|
||||||
DrawRectangleRec(rect, enemy_color);
|
DrawRectangleRec(rect, enemy_color);
|
||||||
|
|
||||||
// Draw hp bar above enemy, color-coded by health remaining
|
// Draw hp bar above enemy
|
||||||
int hp_pixels = (enemies[i].hp * TILE_SIZE) / enemies[i].max_hp;
|
int hp_percent = (enemies[i].hp * TILE_SIZE) / enemies[i].max_hp;
|
||||||
if (hp_pixels > 0) {
|
if (hp_percent > 0) {
|
||||||
float hp_ratio = (float)enemies[i].hp / (float)enemies[i].max_hp;
|
Rectangle hp_bar = {(float)(enemies[i].x * TILE_SIZE), (float)(enemies[i].y * TILE_SIZE - 4), (float)hp_percent,
|
||||||
Color bar_color;
|
|
||||||
if (hp_ratio > 0.5f)
|
|
||||||
bar_color = (Color){60, 180, 60, 255}; // green
|
|
||||||
else if (hp_ratio > 0.25f)
|
|
||||||
bar_color = (Color){200, 180, 40, 255}; // yellow
|
|
||||||
else
|
|
||||||
bar_color = (Color){200, 60, 60, 255}; // red
|
|
||||||
Rectangle hp_bar = {(float)(enemies[i].x * TILE_SIZE), (float)(enemies[i].y * TILE_SIZE - 4), (float)hp_pixels,
|
|
||||||
3};
|
3};
|
||||||
DrawRectangleRec(hp_bar, bar_color);
|
DrawRectangleRec(hp_bar, GREEN);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -295,35 +287,6 @@ void render_inventory_overlay(const Player *p, int selected) {
|
||||||
(Color){65, 65, 65, 255});
|
(Color){65, 65, 65, 255});
|
||||||
}
|
}
|
||||||
|
|
||||||
static Color label_color(FloatingText *ft, int alpha) {
|
|
||||||
if (ft->label[0] == '\0')
|
|
||||||
return (Color){255, 100, 100, alpha}; // numeric damage default
|
|
||||||
if (strcmp(ft->label, "DODGE") == 0)
|
|
||||||
return (Color){160, 160, 160, alpha};
|
|
||||||
if (strcmp(ft->label, "BLOCK") == 0)
|
|
||||||
return (Color){80, 130, 220, alpha};
|
|
||||||
if (strcmp(ft->label, "CRIT!") == 0)
|
|
||||||
return (Color){255, 200, 50, alpha};
|
|
||||||
if (strcmp(ft->label, "SLAIN") == 0)
|
|
||||||
return (Color){220, 50, 50, alpha};
|
|
||||||
|
|
||||||
// Proc label, color driven by effect_type stored in the struct
|
|
||||||
switch (ft->effect_type) {
|
|
||||||
case EFFECT_POISON:
|
|
||||||
return (Color){50, 200, 50, alpha};
|
|
||||||
case EFFECT_BLEED:
|
|
||||||
return (Color){200, 50, 50, alpha};
|
|
||||||
case EFFECT_BURN:
|
|
||||||
return (Color){230, 130, 30, alpha};
|
|
||||||
case EFFECT_STUN:
|
|
||||||
return (Color){200, 200, 50, alpha};
|
|
||||||
case EFFECT_WEAKEN:
|
|
||||||
return (Color){120, 120, 120, alpha};
|
|
||||||
default:
|
|
||||||
return (Color){200, 200, 200, alpha};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void render_floating_texts(FloatingText *texts, int count, int shake_x, int shake_y) {
|
void render_floating_texts(FloatingText *texts, int count, int shake_x, int shake_y) {
|
||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
if (texts[i].lifetime <= 0)
|
if (texts[i].lifetime <= 0)
|
||||||
|
|
@ -331,23 +294,15 @@ void render_floating_texts(FloatingText *texts, int count, int shake_x, int shak
|
||||||
|
|
||||||
int x = texts[i].x + shake_x;
|
int x = texts[i].x + shake_x;
|
||||||
int y = texts[i].y + shake_y - (60 - texts[i].lifetime); // rise over time
|
int y = texts[i].y + shake_y - (60 - texts[i].lifetime); // rise over time
|
||||||
float alpha = (float)texts[i].lifetime / 60.0f;
|
|
||||||
int a = (int)(255 * alpha);
|
|
||||||
|
|
||||||
if (texts[i].label[0] != '\0') {
|
float alpha = (float)texts[i].lifetime / 60.0f;
|
||||||
// Label text (DODGE, BLOCK, CRIT!, proc name, SLAIN)
|
Color color =
|
||||||
int font_size = (texts[i].label[0] == 'C') ? 16 : 14; // CRIT! slightly larger
|
texts[i].is_critical ? (Color){255, 200, 50, (int)(255 * alpha)} : (Color){255, 100, 100, (int)(255 * alpha)};
|
||||||
Color color = label_color(&texts[i], a);
|
|
||||||
int text_w = MeasureText(texts[i].label, font_size);
|
char text[16];
|
||||||
DrawText(texts[i].label, x - text_w / 2, y, font_size, color);
|
snprintf(text, sizeof(text), "%d", texts[i].value);
|
||||||
} else {
|
int text_w = MeasureText(text, 18);
|
||||||
// Numeric damage
|
DrawText(text, x - text_w / 2, y, 18, color);
|
||||||
Color color = texts[i].is_critical ? (Color){255, 200, 50, a} : (Color){255, 100, 100, a};
|
|
||||||
char text[16];
|
|
||||||
snprintf(text, sizeof(text), "%d", texts[i].value);
|
|
||||||
int text_w = MeasureText(text, 18);
|
|
||||||
DrawText(text, x - text_w / 2, y, 18, color);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -372,8 +327,8 @@ void render_message(const char *message) {
|
||||||
|
|
||||||
int msg_len = strlen(message);
|
int msg_len = strlen(message);
|
||||||
float msg_ratio = 13.5;
|
float msg_ratio = 13.5;
|
||||||
|
|
||||||
// Draw message box
|
// Draw message box
|
||||||
|
|
||||||
// TODO: Separate out the calculation of the x/y and width/height so that if a message takes up more than, say,
|
// TODO: Separate out the calculation of the x/y and width/height so that if a message takes up more than, say,
|
||||||
// 75% of the screen width, we add a line break and increase the height. That would then require calculating the
|
// 75% of the screen width, we add a line break and increase the height. That would then require calculating the
|
||||||
// width based on the longest line.
|
// width based on the longest line.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue