├── .snitch.yaml ├── assets ├── images │ ├── cursor.bmp │ ├── charmap-oldschool.bmp │ ├── cursor-resize-vert.bmp │ ├── cursor-resize-diag1.bmp │ ├── cursor-resize-diag2.bmp │ ├── cursor-resize-horis.bmp │ ├── charmap-cellphone_black.bmp │ ├── charmap-cellphone_white_0.bmp │ └── charmap-futuristic_black_0.bmp ├── sounds │ ├── dev │ │ ├── ding.wav │ │ ├── save.wav │ │ └── click.wav │ ├── nothing.wav │ └── something.wav └── levels │ ├── platforms.txt │ ├── level-02.txt │ └── level-01.txt ├── .gitignore ├── src ├── math │ ├── pi.h │ ├── rand.h │ ├── rand.c │ ├── triangle.h │ ├── extrema.h │ ├── triangle.c │ ├── mat3x3.h │ ├── vec.h │ └── rect.h ├── system │ ├── lt_adapters.h │ ├── nth_alloc.h │ ├── log.h │ ├── lt_adapters.c │ ├── str.h │ ├── nth_alloc.c │ ├── stacktrace.h │ ├── file.h │ ├── memory.h │ ├── stacktrace.c │ ├── str.c │ ├── log.c │ ├── file.c │ ├── s.h │ └── lt.h ├── sdl │ ├── texture.h │ ├── renderer.h │ ├── texture.c │ └── renderer.c ├── game │ ├── level │ │ ├── action.h │ │ ├── background.h │ │ ├── lava │ │ │ ├── wavy_rect.h │ │ │ └── wavy_rect.c │ │ ├── lava.h │ │ ├── regions.h │ │ ├── labels.h │ │ ├── explosion.h │ │ ├── level_editor │ │ │ ├── layer.h │ │ │ ├── undo_history.h │ │ │ ├── layer_picker.h │ │ │ ├── player_layer.h │ │ │ ├── background_layer.h │ │ │ ├── color_picker.h │ │ │ ├── undo_history.c │ │ │ ├── point_layer.h │ │ │ ├── layer.c │ │ │ ├── label_layer.h │ │ │ ├── rect_layer.h │ │ │ ├── background_layer.c │ │ │ ├── color_picker.c │ │ │ ├── player_layer.c │ │ │ └── layer_picker.c │ │ ├── boxes.h │ │ ├── phantom_platforms.h │ │ ├── platforms.h │ │ ├── goals.h │ │ ├── player.h │ │ ├── phantom_platforms.c │ │ ├── level_editor.h │ │ ├── rigid_bodies.h │ │ ├── background.c │ │ ├── lava.c │ │ ├── explosion.c │ │ ├── platforms.c │ │ ├── boxes.c │ │ ├── regions.c │ │ ├── labels.c │ │ └── goals.c │ ├── credits.h │ ├── sound_samples.h │ ├── settings.h │ ├── level.h │ ├── sprite_font.h │ ├── credits.c │ ├── level_picker.h │ ├── settings.c │ ├── camera.h │ └── sprite_font.c ├── ui │ ├── slider.h │ ├── history.h │ ├── console.h │ ├── console_log.h │ ├── cursor.c │ ├── edit_field.h │ ├── cursor.h │ ├── wiggly_text.c │ ├── wiggly_text.h │ ├── slider.c │ ├── history.c │ └── console_log.c ├── color.h ├── ring_buffer.h ├── config.h ├── ring_buffer.c ├── game.h ├── dynarray.h ├── dynarray.c └── color.c ├── default.nix ├── credits.org ├── docs └── opaque.md ├── LICENSE ├── appveyor.yml ├── nothing.c ├── CONTRIBUTING.md ├── .github └── workflows │ └── ci.yml └── README.md /.snitch.yaml: -------------------------------------------------------------------------------- 1 | title: 2 | transforms: 3 | - match: (.*) \*/ 4 | replace: $1 5 | -------------------------------------------------------------------------------- /assets/images/cursor.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsoding/nothing/HEAD/assets/images/cursor.bmp -------------------------------------------------------------------------------- /assets/sounds/dev/ding.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsoding/nothing/HEAD/assets/sounds/dev/ding.wav -------------------------------------------------------------------------------- /assets/sounds/dev/save.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsoding/nothing/HEAD/assets/sounds/dev/save.wav -------------------------------------------------------------------------------- /assets/sounds/nothing.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsoding/nothing/HEAD/assets/sounds/nothing.wav -------------------------------------------------------------------------------- /assets/sounds/dev/click.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsoding/nothing/HEAD/assets/sounds/dev/click.wav -------------------------------------------------------------------------------- /assets/sounds/something.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsoding/nothing/HEAD/assets/sounds/something.wav -------------------------------------------------------------------------------- /assets/images/charmap-oldschool.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsoding/nothing/HEAD/assets/images/charmap-oldschool.bmp -------------------------------------------------------------------------------- /assets/images/cursor-resize-vert.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsoding/nothing/HEAD/assets/images/cursor-resize-vert.bmp -------------------------------------------------------------------------------- /assets/images/cursor-resize-diag1.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsoding/nothing/HEAD/assets/images/cursor-resize-diag1.bmp -------------------------------------------------------------------------------- /assets/images/cursor-resize-diag2.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsoding/nothing/HEAD/assets/images/cursor-resize-diag2.bmp -------------------------------------------------------------------------------- /assets/images/cursor-resize-horis.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsoding/nothing/HEAD/assets/images/cursor-resize-horis.bmp -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | builkd/ 3 | SDL2/ 4 | *vscode* 5 | GPATH 6 | GRTAGS 7 | GTAGS 8 | nothing 9 | gmon.out 10 | *.output -------------------------------------------------------------------------------- /assets/images/charmap-cellphone_black.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsoding/nothing/HEAD/assets/images/charmap-cellphone_black.bmp -------------------------------------------------------------------------------- /assets/images/charmap-cellphone_white_0.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsoding/nothing/HEAD/assets/images/charmap-cellphone_white_0.bmp -------------------------------------------------------------------------------- /assets/images/charmap-futuristic_black_0.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsoding/nothing/HEAD/assets/images/charmap-futuristic_black_0.bmp -------------------------------------------------------------------------------- /src/math/pi.h: -------------------------------------------------------------------------------- 1 | #ifndef PI_H_ 2 | #define PI_H_ 3 | 4 | #define PI 3.14159265359f 5 | #define PI_2 (2.0f * PI) 6 | 7 | #endif // PI_H_ 8 | -------------------------------------------------------------------------------- /src/math/rand.h: -------------------------------------------------------------------------------- 1 | #ifndef RAND_H_ 2 | #define RAND_H_ 3 | 4 | float rand_float(float max_value); 5 | float rand_float_range(float lower, float upper); 6 | 7 | #endif // RAND_H_ 8 | -------------------------------------------------------------------------------- /src/system/lt_adapters.h: -------------------------------------------------------------------------------- 1 | #ifndef LT_ADAPTERS_H_ 2 | #define LT_ADAPTERS_H_ 3 | 4 | void fclose_lt(void* file); 5 | void closedir_lt(void *dir); 6 | 7 | #endif // LT_ADAPTERS_H_ 8 | -------------------------------------------------------------------------------- /src/system/nth_alloc.h: -------------------------------------------------------------------------------- 1 | #ifndef NTH_ALLOC_H_ 2 | #define NTH_ALLOC_H_ 3 | 4 | #include 5 | 6 | void *nth_calloc(size_t num, size_t size); 7 | 8 | #endif // NTH_ALLOC_H_ 9 | -------------------------------------------------------------------------------- /src/system/log.h: -------------------------------------------------------------------------------- 1 | #ifndef LOG_H_ 2 | #define LOG_H_ 3 | 4 | int log_fail(const char *format, ...); 5 | int log_warn(const char *format, ...); 6 | int log_info(const char *format, ...); 7 | 8 | #endif // LOG_H_ 9 | -------------------------------------------------------------------------------- /src/sdl/texture.h: -------------------------------------------------------------------------------- 1 | #ifndef TEXTURE_H_ 2 | #define TEXTURE_H_ 3 | 4 | SDL_Texture *texture_from_bmp(const char *bmp_file_name, 5 | SDL_Renderer *renderer); 6 | 7 | #endif // TEXTURE_H_ 8 | -------------------------------------------------------------------------------- /src/system/lt_adapters.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "file.h" 4 | #include "lt_adapters.h" 5 | 6 | void fclose_lt(void* file) 7 | { 8 | fclose(file); 9 | } 10 | 11 | void closedir_lt(void *dir) 12 | { 13 | closedir(dir); 14 | } 15 | -------------------------------------------------------------------------------- /src/system/str.h: -------------------------------------------------------------------------------- 1 | #ifndef STR_H_ 2 | #define STR_H_ 3 | 4 | #define STRINGIFY(x) STRINGIFY2(x) 5 | #define STRINGIFY2(x) #x 6 | 7 | char *string_duplicate(const char *str, 8 | const char *str_end); 9 | char *trim_endline(char *s); 10 | 11 | #endif // STR_H_ 12 | -------------------------------------------------------------------------------- /src/math/rand.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "math/rand.h" 4 | 5 | float rand_float(float max_value) 6 | { 7 | return (float) rand() / ((float) RAND_MAX / max_value); 8 | } 9 | 10 | float rand_float_range(float lower, float upper) 11 | { 12 | return rand_float(upper - lower) + lower; 13 | } 14 | -------------------------------------------------------------------------------- /src/system/nth_alloc.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "nth_alloc.h" 3 | #include "log.h" 4 | 5 | void *nth_calloc(size_t num, size_t size) 6 | { 7 | void *mem = calloc(num, size); 8 | 9 | if (mem == NULL) { 10 | log_fail("nth_calloc(%lu, %lu) failed", num, size); 11 | } 12 | 13 | return mem; 14 | } 15 | -------------------------------------------------------------------------------- /src/game/level/action.h: -------------------------------------------------------------------------------- 1 | #ifndef ACTION_H_ 2 | #define ACTION_H_ 3 | 4 | #include "config.h" 5 | 6 | typedef enum { 7 | ACTION_NONE = 0, 8 | ACTION_HIDE_LABEL, 9 | ACTION_TOGGLE_GOAL, 10 | 11 | ACTION_N 12 | } ActionType; 13 | 14 | typedef struct { 15 | ActionType type; 16 | char entity_id[ENTITY_MAX_ID_SIZE]; 17 | } Action; 18 | 19 | #endif // ACTION_H_ 20 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | with import {}; { 2 | nothingEnv = gcc8Stdenv.mkDerivation { 3 | name = "nothing-env"; 4 | buildInputs = [ stdenv 5 | gcc 6 | gdb 7 | SDL2 8 | pkgconfig 9 | cmake 10 | valgrind 11 | ]; 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /src/ui/slider.h: -------------------------------------------------------------------------------- 1 | #ifndef SLIDER_H_ 2 | #define SLIDER_H_ 3 | 4 | typedef struct { 5 | int drag; 6 | float value; 7 | float max_value; 8 | } Slider; 9 | 10 | typedef union SDL_Event SDL_Event; 11 | 12 | int slider_render(const Slider *slider, const Camera *camera, Rect boundary); 13 | int slider_event(Slider *slider, const SDL_Event *event, Rect boundary, int *selected); 14 | 15 | #endif // SLIDER_H_ 16 | -------------------------------------------------------------------------------- /credits.org: -------------------------------------------------------------------------------- 1 | - Sounds: 2 | - https://freesound.org/people/Unaxete/ 3 | - sounds/nothing.mp3 4 | - sounds/something.mp3 5 | - Fonts: 6 | - https://opengameart.org/content/ascii-bitmap-font-oldschool 7 | - https://opengameart.org/content/ascii-bitmap-font-futuristic 8 | - https://opengameart.org/content/ascii-bitmap-font-cellphone 9 | - https://opengameart.org/content/ascii-bitmap-font-pixel-simplicity-grey 10 | -------------------------------------------------------------------------------- /src/ui/history.h: -------------------------------------------------------------------------------- 1 | #ifndef HISTORY_H_ 2 | #define HISTORY_H_ 3 | 4 | typedef struct History History; 5 | 6 | History *create_history(size_t capacity); 7 | void destroy_history(History *history); 8 | 9 | int history_push(History *history, const char *command); 10 | const char *history_current(History *history); 11 | void history_prev(History *history); 12 | void history_next(History *history); 13 | 14 | #endif // HISTORY_H_ 15 | -------------------------------------------------------------------------------- /src/system/stacktrace.h: -------------------------------------------------------------------------------- 1 | #ifndef STACKTRACE_H_ 2 | #define STACKTRACE_H_ 3 | 4 | #ifndef NDEBUG 5 | #define trace_assert(condition) (void)((condition) || (__trace_assert(__FILE__, __LINE__, __func__, #condition), 0)) 6 | #else 7 | #define trace_assert(condition) (void)(condition) 8 | #endif 9 | void __trace_assert(const char *file, int line, const char *function, const char *message); 10 | 11 | void print_stacktrace(void); 12 | 13 | #endif // STACKTRACE_H_ 14 | -------------------------------------------------------------------------------- /src/math/triangle.h: -------------------------------------------------------------------------------- 1 | #ifndef TRIANGLE_H_ 2 | #define TRIANGLE_H_ 3 | 4 | #include "math/vec.h" 5 | #include "math/rect.h" 6 | 7 | typedef struct Triangle { 8 | Vec2f p1, p2, p3; 9 | } Triangle; 10 | 11 | Triangle triangle(Vec2f p1, Vec2f p2, Vec2f p3); 12 | Triangle equilateral_triangle(void); 13 | Triangle random_triangle(float radius); 14 | Triangle triangle_sorted_by_y(Triangle t); 15 | void rect_as_triangles(Rect rect, Triangle triangles[2]); 16 | 17 | #endif // TRIANGLE_H_ 18 | -------------------------------------------------------------------------------- /src/sdl/renderer.h: -------------------------------------------------------------------------------- 1 | #ifndef RENDERER_H_ 2 | #define RENDERER_H_ 3 | 4 | #include 5 | 6 | #include "color.h" 7 | #include "math/vec.h" 8 | #include "math/triangle.h" 9 | 10 | // TODO(#474): there are no logging SDL wrappers (similar to system/nth_alloc) 11 | int draw_triangle(SDL_Renderer *render, 12 | Triangle t); 13 | 14 | int fill_triangle(SDL_Renderer *render, 15 | Triangle t); 16 | 17 | int fill_rect(SDL_Renderer *render, 18 | Rect r, 19 | Color c); 20 | 21 | #endif // RENDERER_H_ 22 | -------------------------------------------------------------------------------- /src/game/level/background.h: -------------------------------------------------------------------------------- 1 | #ifndef BACKGROUND_H_ 2 | #define BACKGROUND_H_ 3 | 4 | #include 5 | 6 | #include "color.h" 7 | #include "game/camera.h" 8 | 9 | typedef struct { 10 | Color base_color; 11 | } Background; 12 | 13 | static inline 14 | Background create_background(Color base_color) 15 | { 16 | Background result = {base_color}; 17 | return result; 18 | } 19 | 20 | int background_render(const Background *background, 21 | const Camera *camera); 22 | 23 | Color background_base_color(const Background *background); 24 | 25 | #endif // BACKGROUND_H_ 26 | -------------------------------------------------------------------------------- /src/system/file.h: -------------------------------------------------------------------------------- 1 | #ifndef FILE_H_ 2 | #define FILE_H_ 3 | 4 | #include 5 | #ifdef _WIN32 6 | #define WIN32_LEAN_AND_MEAN 7 | #include 8 | #endif 9 | #ifndef _WIN32 10 | #include 11 | #endif 12 | 13 | #include "system/s.h" 14 | 15 | #ifdef _WIN32 16 | struct dirent 17 | { 18 | char d_name[MAX_PATH+1]; 19 | }; 20 | 21 | typedef struct DIR DIR; 22 | 23 | DIR *opendir(const char *name); 24 | struct dirent *readdir(DIR *dirp); 25 | void closedir(DIR *dirp); 26 | #endif 27 | 28 | String read_whole_file(Memory *memory, const char *filepath); 29 | 30 | #endif // FILE_H_ 31 | -------------------------------------------------------------------------------- /src/game/level/lava/wavy_rect.h: -------------------------------------------------------------------------------- 1 | #ifndef WAVY_RECT_H_ 2 | #define WAVY_RECT_H_ 3 | 4 | #include 5 | 6 | #include "color.h" 7 | #include "game/camera.h" 8 | #include "math/rect.h" 9 | 10 | typedef struct Wavy_rect Wavy_rect; 11 | 12 | Wavy_rect *create_wavy_rect(Rect rect, Color color); 13 | void destroy_wavy_rect(Wavy_rect *wavy_rect); 14 | 15 | int wavy_rect_render(const Wavy_rect *wavy_rect, 16 | const Camera *camera); 17 | int wavy_rect_update(Wavy_rect *wavy_rect, 18 | float delta_time); 19 | 20 | Rect wavy_rect_hitbox(const Wavy_rect *wavy_rect); 21 | 22 | #endif // WAVY_RECT_H_ 23 | -------------------------------------------------------------------------------- /docs/opaque.md: -------------------------------------------------------------------------------- 1 | # Opaque Entities 2 | 3 | See [Opaque data type](https://en.wikipedia.org/wiki/Opaque_data_type) first. 4 | 5 | ## Examples 6 | 7 | - [level](../src/game/level.h) 8 | - [player](../src/game/level/player.h) 9 | - ... 10 | 11 | ## Conventions 12 | 13 | - One translation unit per entity 14 | - Entity stores its state in an opaque forward declared struct 15 | - Constructors are `create_` or `create__from_` (for example `create_level_from_file`) 16 | - Single destructor with name `destroy_` 17 | - Regular methods are `_` (for example `player_render`) 18 | -------------------------------------------------------------------------------- /src/game/credits.h: -------------------------------------------------------------------------------- 1 | #ifndef CREDITS_H_ 2 | #define CREDITS_H_ 3 | 4 | #include 5 | 6 | #include "game/camera.h" 7 | #include "game/level/background.h" 8 | #include "ui/wiggly_text.h" 9 | 10 | typedef struct { 11 | Background background; 12 | Vec2f camera_position; 13 | WigglyText wiggly_text; 14 | } Credits; 15 | 16 | 17 | Credits create_credits(void); 18 | void destroy_credits(Credits *credits); 19 | 20 | int credits_render(const Credits *credits, 21 | const Camera *camera); 22 | int credits_update(Credits *credits, 23 | Camera *camera, float dt); 24 | 25 | #endif // CREDITS_H_ 26 | -------------------------------------------------------------------------------- /src/ui/console.h: -------------------------------------------------------------------------------- 1 | #ifndef CONSOLE_H_ 2 | #define CONSOLE_H_ 3 | 4 | #include 5 | #include "game/sprite_font.h" 6 | 7 | typedef struct Console Console; 8 | typedef struct Game Game; 9 | 10 | Console *create_console(Game *game); 11 | void destroy_console(Console *console); 12 | 13 | int console_handle_event(Console *console, 14 | const SDL_Event *event); 15 | 16 | int console_render(const Console *console, 17 | const Camera *camera); 18 | 19 | int console_update(Console *console, 20 | float delta_time); 21 | 22 | void console_slide_down(Console *console); 23 | 24 | #endif // CONSOLE_H_ 25 | -------------------------------------------------------------------------------- /src/game/level/lava.h: -------------------------------------------------------------------------------- 1 | #ifndef LAVA_H_ 2 | #define LAVA_H_ 3 | 4 | #include 5 | 6 | #include "game/camera.h" 7 | #include "game/level/rigid_bodies.h" 8 | #include "math/rect.h" 9 | 10 | typedef struct Lava Lava; 11 | typedef struct RectLayer RectLayer; 12 | 13 | Lava *create_lava_from_rect_layer(const RectLayer *rect_layer); 14 | void destroy_lava(Lava *lava); 15 | 16 | int lava_render(const Lava *lava, 17 | const Camera *camera); 18 | int lava_update(Lava *lava, float delta_time); 19 | 20 | bool lava_overlaps_rect(const Lava *lava, Rect rect); 21 | 22 | void lava_float_rigid_body(Lava *lava, RigidBodies *rigid_bodies, RigidBodyId id); 23 | 24 | #endif // LAVA_H_ 25 | -------------------------------------------------------------------------------- /src/game/sound_samples.h: -------------------------------------------------------------------------------- 1 | #ifndef SOUND_SAMPLES_H_ 2 | #define SOUND_SAMPLES_H_ 3 | 4 | #include "math/vec.h" 5 | 6 | typedef struct Sound_samples Sound_samples; 7 | 8 | Sound_samples *create_sound_samples(const char *sample_files[], 9 | size_t sample_files_count); 10 | void destroy_sound_samples(Sound_samples *sound_samples); 11 | 12 | int sound_samples_play_sound(Sound_samples *sound_samples, 13 | size_t sound_index); 14 | 15 | int sound_samples_toggle_pause(Sound_samples *sound_samples); 16 | 17 | void sound_samples_update_volume(Sound_samples *sound_samples, 18 | float volume); 19 | 20 | #endif // SOUND_SAMPLES_H_ 21 | -------------------------------------------------------------------------------- /src/game/level/regions.h: -------------------------------------------------------------------------------- 1 | #ifndef REGIONS_H_ 2 | #define REGIONS_H_ 3 | 4 | #include "math/rect.h" 5 | #include "action.h" 6 | 7 | typedef struct Regions Regions; 8 | typedef struct Player Player; 9 | typedef struct Level Level; 10 | typedef struct RectLayer RectLayer; 11 | typedef struct Labels Labels; 12 | typedef struct Goals Goals; 13 | 14 | Regions *create_regions_from_rect_layer(const RectLayer *rect_layer, Labels *labels, Goals *goals); 15 | void destroy_regions(Regions *regions); 16 | 17 | int regions_render(Regions *regions, const Camera *camera); 18 | 19 | void regions_player_enter(Regions *regions, Player *player); 20 | void regions_player_leave(Regions *regions, Player *player); 21 | 22 | #endif // REGIONS_H_ 23 | -------------------------------------------------------------------------------- /src/game/level/labels.h: -------------------------------------------------------------------------------- 1 | #ifndef LABELS_H_ 2 | #define LABELS_H_ 3 | 4 | #include "math/vec.h" 5 | #include "color.h" 6 | #include "config.h" 7 | #include "game/level/level_editor/label_layer.h" 8 | 9 | typedef struct Labels Labels; 10 | 11 | Labels *create_labels_from_label_layer(const LabelLayer *label_layer); 12 | void destroy_labels(Labels *label); 13 | 14 | int labels_render(const Labels *label, 15 | const Camera *camera); 16 | void labels_update(Labels *label, 17 | float delta_time); 18 | void labels_enter_camera_event(Labels *label, 19 | const Camera *camera); 20 | void labels_hide(Labels *label, char id[ENTITY_MAX_ID_SIZE]); 21 | 22 | #endif // LABELS_H_ 23 | -------------------------------------------------------------------------------- /src/game/settings.h: -------------------------------------------------------------------------------- 1 | #ifndef SETTINGS_H_ 2 | #define SETTINGS_H_ 3 | 4 | #include "game/camera.h" 5 | #include "game/level/background.h" 6 | #include "ui/slider.h" 7 | 8 | typedef struct { 9 | Background background; 10 | // TODO(#1123): the volume_slider is not fully synced with the volume of sound_samples 11 | Slider volume_slider; 12 | Vec2f volume_slider_scale; 13 | Vec2f camera_position; 14 | } Settings; 15 | 16 | Settings create_settings(void); 17 | 18 | void settings_render(const Settings *settings, const Camera *camera); 19 | void settings_event(Settings *settings, Camera *camera, const SDL_Event *event); 20 | void settings_update(Settings *settings, Camera *camera, float dt); 21 | 22 | #endif // SETTINGS_H_ 23 | -------------------------------------------------------------------------------- /src/game/level/explosion.h: -------------------------------------------------------------------------------- 1 | #ifndef EXPLOSION_H_ 2 | #define EXPLOSION_H_ 3 | 4 | #include 5 | 6 | #include "color.h" 7 | #include "game/camera.h" 8 | #include "math/rect.h" 9 | 10 | typedef struct Explosion Explosion; 11 | 12 | Explosion *create_explosion(Color color, 13 | float duration); 14 | void destroy_explosion(Explosion *explosion); 15 | 16 | int explosion_render(const Explosion *explosion, 17 | const Camera *camera); 18 | int explosion_update(Explosion *explosion, 19 | float delta_time); 20 | 21 | int explosion_is_done(const Explosion *explosion); 22 | 23 | void explosion_start(Explosion *explosion, Vec2f position); 24 | 25 | #endif // EXPLOSION_H_ 26 | -------------------------------------------------------------------------------- /src/game/level/level_editor/layer.h: -------------------------------------------------------------------------------- 1 | #ifndef LAYER_H_ 2 | #define LAYER_H_ 3 | 4 | #include "game/camera.h" 5 | #include "undo_history.h" 6 | 7 | typedef enum { 8 | LAYER_RECT, 9 | LAYER_POINT, 10 | LAYER_PLAYER, 11 | LAYER_BACKGROUND, 12 | LAYER_LABEL 13 | } LayerType; 14 | 15 | typedef struct { 16 | LayerType type; 17 | void *ptr; 18 | } LayerPtr; 19 | 20 | typedef struct Game Game; 21 | 22 | int layer_render(LayerPtr layer, const Camera *camera, int active); 23 | int layer_event(LayerPtr layer, 24 | const SDL_Event *event, 25 | const Camera *camera, 26 | UndoHistory *undo_history); 27 | int layer_dump_stream(LayerPtr layer, FILE *stream); 28 | 29 | #endif // LAYER_H_ 30 | -------------------------------------------------------------------------------- /src/game/level/boxes.h: -------------------------------------------------------------------------------- 1 | #ifndef BOXES_H_ 2 | #define BOXES_H_ 3 | 4 | #include "game/camera.h" 5 | #include "game/level/platforms.h" 6 | #include "lava.h" 7 | 8 | typedef struct Boxes Boxes; 9 | typedef struct Player Player; 10 | typedef struct Player Player; 11 | typedef struct RectLayer RectLayer; 12 | 13 | Boxes *create_boxes_from_rect_layer(const RectLayer *layer, RigidBodies *rigid_bodies); 14 | void destroy_boxes(Boxes *boxes); 15 | 16 | int boxes_render(Boxes *boxes, const Camera *camera); 17 | int boxes_update(Boxes *boxes, float delta_time); 18 | 19 | void boxes_float_in_lava(Boxes *boxes, Lava *lava); 20 | 21 | int boxes_add_box(Boxes *boxes, Rect rect, Color color); 22 | int boxes_delete_at(Boxes *boxes, Vec2f position); 23 | 24 | #endif // BOXES_H_ 25 | -------------------------------------------------------------------------------- /src/system/memory.h: -------------------------------------------------------------------------------- 1 | #ifndef MEMORY_H_ 2 | #define MEMORY_H_ 3 | 4 | #include 5 | #include 6 | 7 | #define KILO 1024L 8 | #define MEGA (1024L * KILO) 9 | #define GIGA (1024L * MEGA) 10 | 11 | typedef struct { 12 | size_t capacity; 13 | size_t size; 14 | uint8_t *buffer; 15 | } Memory; 16 | 17 | static inline 18 | void *memory_alloc(Memory *memory, size_t size) 19 | { 20 | assert(memory); 21 | assert(memory->size + size <= memory->capacity); 22 | 23 | void *result = memory->buffer + memory->size; 24 | memory->size += size; 25 | 26 | return result; 27 | } 28 | 29 | static inline 30 | void memory_clean(Memory *memory) 31 | { 32 | assert(memory); 33 | memory->size = 0; 34 | } 35 | 36 | #endif // MEMORY_H_ 37 | -------------------------------------------------------------------------------- /src/game/level/phantom_platforms.h: -------------------------------------------------------------------------------- 1 | #ifndef PHANTOM_PLATFORMS_H_ 2 | #define PHANTOM_PLATFORMS_H_ 3 | 4 | #include 5 | #include "math/rect.h" 6 | #include "color.h" 7 | #include "game/level/level_editor/rect_layer.h" 8 | 9 | typedef struct { 10 | size_t size; 11 | Rect *rects; 12 | Color *colors; 13 | int *hiding; 14 | } Phantom_Platforms; 15 | 16 | Phantom_Platforms create_phantom_platforms(RectLayer *rect_layer); 17 | void destroy_phantom_platforms(Phantom_Platforms pp); 18 | 19 | void phantom_platforms_render(const Phantom_Platforms *pp, const Camera *camera); 20 | void phantom_platforms_update(Phantom_Platforms *pp, float dt); 21 | void phantom_platforms_hide_at(Phantom_Platforms *pp, Vec2f position); 22 | 23 | #endif // PHANTOM_PLATFORMS_H_ 24 | -------------------------------------------------------------------------------- /src/game/level/platforms.h: -------------------------------------------------------------------------------- 1 | #ifndef PLATFORMS_H_ 2 | #define PLATFORMS_H_ 3 | 4 | #include 5 | 6 | #include "game/camera.h" 7 | #include "math/rect.h" 8 | 9 | typedef struct Platforms Platforms; 10 | typedef struct RectLayer RectLayer; 11 | 12 | Platforms *create_platforms_from_rect_layer(const RectLayer *layer); 13 | void destroy_platforms(Platforms *platforms); 14 | 15 | int platforms_render(const Platforms *platforms, 16 | const Camera *camera); 17 | 18 | void platforms_touches_rect_sides(const Platforms *platforms, 19 | Rect object, 20 | int sides[RECT_SIDE_N]); 21 | Vec2f platforms_snap_rect(const Platforms *platforms, 22 | Rect *object); 23 | 24 | #endif // PLATFORMS_H_ 25 | -------------------------------------------------------------------------------- /src/ui/console_log.h: -------------------------------------------------------------------------------- 1 | #ifndef CONSOLE_LOG_H_ 2 | #define CONSOLE_LOG_H_ 3 | 4 | #include "math/vec.h" 5 | #include "game/camera.h" 6 | 7 | typedef struct Console_Log Console_Log; 8 | 9 | Console_Log *create_console_log(Vec2f font_size, 10 | size_t capacity); 11 | void destroy_console_log(Console_Log *console_log); 12 | 13 | void console_log_render(const Console_Log *console_log, 14 | const Camera *camera, 15 | Vec2f position); 16 | 17 | int console_log_push_line(Console_Log *console_log, 18 | const char *line, 19 | const char *line_end, 20 | Color color); 21 | 22 | void console_log_clear(Console_Log *console_log); 23 | 24 | #endif // CONSOLE_LOG_H_ 25 | -------------------------------------------------------------------------------- /src/game/level/level_editor/undo_history.h: -------------------------------------------------------------------------------- 1 | #ifndef UNDO_HISTORY_H_ 2 | #define UNDO_HISTORY_H_ 3 | 4 | #include "ring_buffer.h" 5 | 6 | typedef void (*RevertAction)(void *context, size_t context_size); 7 | 8 | typedef struct { 9 | RingBuffer actions; 10 | Memory *memory; 11 | } UndoHistory; 12 | 13 | UndoHistory *create_undo_history(Memory *memory); 14 | 15 | void undo_history_push(UndoHistory *undo_history, 16 | RevertAction revert, 17 | void *context_data, 18 | size_t context_data_size); 19 | void undo_history_pop(UndoHistory *undo_history); 20 | 21 | void undo_history_clean(UndoHistory *undo_history); 22 | 23 | static inline 24 | int undo_history_empty(UndoHistory *undo_history) 25 | { 26 | return undo_history->actions.count == 0; 27 | } 28 | 29 | #endif // UNDO_HISTORY_H_ 30 | -------------------------------------------------------------------------------- /src/math/extrema.h: -------------------------------------------------------------------------------- 1 | #ifndef EXTREMA_H_ 2 | #define EXTREMA_H_ 3 | 4 | #include 5 | 6 | #define MAX_INSTANCE(type) \ 7 | static inline \ 8 | type max_##type(type a, type b) { \ 9 | return a > b ? a : b; \ 10 | } \ 11 | 12 | MAX_INSTANCE(int64_t) 13 | MAX_INSTANCE(size_t) 14 | #define MAX(type, a, b) max_##type(a, b) 15 | 16 | #define MIN_INSTANCE(type) \ 17 | static inline \ 18 | type min_##type(type a, type b) { \ 19 | return a < b ? a : b; \ 20 | } \ 21 | 22 | MIN_INSTANCE(int64_t) 23 | MIN_INSTANCE(size_t) 24 | #define MIN(type, a, b) min_##type(a, b) 25 | 26 | #endif // EXTREMA_H_ 27 | -------------------------------------------------------------------------------- /src/game/level/level_editor/layer_picker.h: -------------------------------------------------------------------------------- 1 | #ifndef LAYER_PICKER_H_ 2 | #define LAYER_PICKER_H_ 3 | 4 | #include "game/camera.h" 5 | 6 | typedef struct RectLayer RectLayer; 7 | 8 | typedef enum { 9 | LAYER_PICKER_BACKGROUND = 0, 10 | LAYER_PICKER_PLAYER, 11 | LAYER_PICKER_BACK_PLATFORMS, 12 | LAYER_PICKER_PLATFORMS, 13 | LAYER_PICKER_GOALS, 14 | LAYER_PICKER_LAVA, 15 | LAYER_PICKER_BOXES, 16 | LAYER_PICKER_LABELS, 17 | LAYER_PICKER_REGIONS, 18 | LAYER_PICKER_PP, 19 | 20 | LAYER_PICKER_N 21 | } LayerPicker; 22 | 23 | int layer_picker_render(const LayerPicker *layer_picker, 24 | const Camera *camera); 25 | int layer_picker_event(LayerPicker *layer_picker, 26 | const SDL_Event *event, 27 | const Camera *camera, 28 | bool *selected); 29 | 30 | #endif // LAYER_PICKER_H_ 31 | -------------------------------------------------------------------------------- /src/system/stacktrace.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #if defined(__GNUC__) && defined(__linux__) 4 | #include 5 | #include 6 | #define N 100 7 | #endif 8 | 9 | #include "./stacktrace.h" 10 | 11 | 12 | void print_stacktrace(void) 13 | { 14 | #if defined(__GNUC__) && defined(__linux__) 15 | void *array[N]; 16 | int size; 17 | 18 | size = backtrace(array, N); 19 | 20 | if (size <= 0) { 21 | return; 22 | } 23 | 24 | fprintf(stderr, "Stacktrace: \n"); 25 | backtrace_symbols_fd(array + 1, size - 1, STDERR_FILENO); 26 | #endif 27 | } 28 | 29 | void __trace_assert(const char *file, int line, const char *function, const char *message) 30 | { 31 | fprintf( 32 | stderr, 33 | "%s:%d: %s: Assertion `%s' failed\n", 34 | file, line, 35 | function, 36 | message); 37 | print_stacktrace(); 38 | abort(); 39 | } 40 | -------------------------------------------------------------------------------- /src/color.h: -------------------------------------------------------------------------------- 1 | #ifndef COLOR_H_ 2 | #define COLOR_H_ 3 | 4 | #include 5 | #include 6 | #include "./system/s.h" 7 | 8 | #define COLOR_BLACK rgba(0.0f, 0.0f, 0.0f, 1.0f) 9 | #define COLOR_WHITE rgba(1.0f, 1.0f, 1.0f, 1.0f) 10 | #define COLOR_RED rgba(1.0f, 0.0f, 0.0f, 1.0f) 11 | 12 | typedef struct Color { 13 | float r, g, b, a; 14 | } Color; 15 | 16 | Color rgba(float r, float g, float b, float a); 17 | Color hsla(float h, float s, float l, float a); 18 | Color rgba_to_hsla(Color color); 19 | Color hexstr(const char *hexstr); 20 | Color hexs(String input); 21 | SDL_Color color_for_sdl(Color color); 22 | 23 | int color_hex_to_stream(Color color, FILE *stream); 24 | int color_hex_to_string(Color color, char *buffer, size_t buffer_size); 25 | 26 | Color color_darker(Color color, float d); 27 | 28 | Color color_desaturate(Color color); 29 | 30 | Color color_invert(Color c); 31 | 32 | Color color_scale(Color c, Color fc); 33 | 34 | #endif // COLOR_H_ 35 | -------------------------------------------------------------------------------- /src/ui/cursor.c: -------------------------------------------------------------------------------- 1 | #include "system/stacktrace.h" 2 | #include "cursor.h" 3 | #include "game.h" 4 | 5 | int cursor_render(const Cursor *cursor, SDL_Renderer *renderer) 6 | { 7 | trace_assert(cursor); 8 | trace_assert(renderer); 9 | 10 | int cursor_x, cursor_y; 11 | SDL_GetMouseState(&cursor_x, &cursor_y); 12 | cursor_x = (int) ((float) cursor_x * get_display_scale()); 13 | cursor_y = (int) ((float) cursor_y * get_display_scale()); 14 | 15 | const SDL_Rect src = {0, 0, CURSOR_ICON_WIDTH, CURSOR_ICON_HEIGHT}; 16 | const SDL_Rect dest = { 17 | cursor_x - cursor_style_tex_pivots[cursor->style][0], 18 | cursor_y - cursor_style_tex_pivots[cursor->style][1], 19 | CURSOR_ICON_WIDTH, 20 | CURSOR_ICON_HEIGHT 21 | }; 22 | 23 | if (SDL_RenderCopy( 24 | renderer, 25 | cursor->texs[cursor->style], 26 | &src, &dest) < 0) { 27 | return -1; 28 | } 29 | 30 | return 0; 31 | } 32 | -------------------------------------------------------------------------------- /src/system/str.c: -------------------------------------------------------------------------------- 1 | #include "system/stacktrace.h" 2 | #include 3 | #include 4 | #include 5 | #include "system/stacktrace.h" 6 | 7 | #include "str.h" 8 | #include "system/nth_alloc.h" 9 | 10 | char *string_duplicate(const char *str, 11 | const char *str_end) 12 | { 13 | trace_assert(str); 14 | 15 | if (str_end != NULL && str > str_end) { 16 | return NULL; 17 | } 18 | 19 | const size_t n = str_end == NULL ? strlen(str) : (size_t) (str_end - str); 20 | char *dup_str = nth_calloc(1, sizeof(char) * (n + 1)); 21 | if (dup_str == NULL) { 22 | return NULL; 23 | } 24 | 25 | memcpy(dup_str, str, n); 26 | dup_str[n] = '\0'; 27 | 28 | return dup_str; 29 | } 30 | 31 | char *trim_endline(char *s) 32 | { 33 | const size_t n = strlen(s); 34 | 35 | if (n == 0) { 36 | return s; 37 | } 38 | 39 | if (s[n - 1] == '\n') { 40 | s[n - 1] = '\0'; 41 | } 42 | 43 | return s; 44 | } 45 | -------------------------------------------------------------------------------- /src/game/level/goals.h: -------------------------------------------------------------------------------- 1 | #ifndef GOALS_H_ 2 | #define GOALS_H_ 3 | 4 | #include 5 | 6 | #include "game/camera.h" 7 | #include "game/level/player.h" 8 | #include "game/sound_samples.h" 9 | #include "game/level/level_editor/point_layer.h" 10 | #include "config.h" 11 | 12 | typedef struct Goals Goals; 13 | 14 | Goals *create_goals_from_point_layer(const PointLayer *point_layer); 15 | void destroy_goals(Goals *goals); 16 | 17 | Rect goals_hitbox(const Goals *goals); 18 | 19 | int goals_render(const Goals *goals, 20 | const Camera *camera); 21 | int goals_sound(Goals *goals, 22 | Sound_samples *sound_samples); 23 | void goals_update(Goals *goals, 24 | float delta_time); 25 | void goals_checkpoint(const Goals *goals, 26 | Player *player); 27 | void goals_cue(Goals *goals, 28 | const Camera *camera); 29 | 30 | void goals_hide(Goals *goals, char goal_id[ENTITY_MAX_ID_SIZE]); 31 | void goals_show(Goals *goals, char goal_id[ENTITY_MAX_ID_SIZE]); 32 | 33 | #endif // GOALS_H_ 34 | -------------------------------------------------------------------------------- /src/game/level/level_editor/player_layer.h: -------------------------------------------------------------------------------- 1 | #ifndef PLAYER_LAYER_H_ 2 | #define PLAYER_LAYER_H_ 3 | 4 | #include "color_picker.h" 5 | #include "layer.h" 6 | #include "system/memory.h" 7 | #include "system/s.h" 8 | 9 | typedef struct { 10 | Vec2f position; 11 | ColorPicker color_picker; 12 | Color prev_color; 13 | } PlayerLayer; 14 | 15 | PlayerLayer create_player_layer(Vec2f position, Color color); 16 | PlayerLayer chop_player_layer(Memory *memory, String *input); 17 | 18 | LayerPtr player_layer_as_layer(PlayerLayer *player_layer); 19 | int player_layer_render(const PlayerLayer *player_layer, 20 | const Camera *camera, 21 | int active); 22 | int player_layer_event(PlayerLayer *player_layer, 23 | const SDL_Event *event, 24 | const Camera *camera, 25 | UndoHistory *undo_history); 26 | 27 | int player_layer_dump_stream(const PlayerLayer *player_layer, 28 | FILE *filedump); 29 | 30 | #endif // PLAYER_LAYER_H_ 31 | -------------------------------------------------------------------------------- /src/ring_buffer.h: -------------------------------------------------------------------------------- 1 | #ifndef RING_BUFFER_H_ 2 | #define RING_BUFFER_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "system/memory.h" 11 | 12 | typedef void (*RingBufferDtor)(void *element); 13 | 14 | typedef struct { 15 | size_t element_size; 16 | size_t capacity; 17 | size_t count; 18 | size_t begin; 19 | uint8_t *data; 20 | } RingBuffer; 21 | 22 | static inline 23 | RingBuffer create_ring_buffer_from_buffer(Memory *memory, 24 | size_t element_size, 25 | size_t capacity) 26 | { 27 | RingBuffer result = {0}; 28 | result.element_size = element_size; 29 | result.capacity = capacity; 30 | result.data = memory_alloc(memory, result.element_size * result.capacity); 31 | return result; 32 | } 33 | 34 | void ring_buffer_push(RingBuffer *buffer, void *element); 35 | int ring_buffer_pop(RingBuffer *buffer); 36 | void *ring_buffer_top(RingBuffer *buffer); 37 | 38 | #endif // RING_BUFFER_H_ 39 | -------------------------------------------------------------------------------- /src/game/level.h: -------------------------------------------------------------------------------- 1 | #ifndef LEVEL_H_ 2 | #define LEVEL_H_ 3 | 4 | #include 5 | 6 | #include "game/camera.h" 7 | #include "game/level/platforms.h" 8 | #include "game/level/player.h" 9 | #include "sound_samples.h" 10 | 11 | typedef struct Level Level; 12 | typedef struct LevelEditor LevelEditor; 13 | 14 | Level *create_level_from_level_editor(const LevelEditor *level_editor); 15 | void destroy_level(Level *level); 16 | 17 | int level_render(const Level *level, const Camera *camera); 18 | 19 | int level_sound(Level *level, Sound_samples *sound_samples); 20 | int level_update(Level *level, float delta_time); 21 | 22 | int level_event(Level *level, const SDL_Event *event, 23 | Camera *camera, Sound_samples *sound_samples); 24 | int level_input(Level *level, 25 | const Uint8 *const keyboard_state, 26 | SDL_Joystick *the_stick_of_joy); 27 | int level_enter_camera_event(Level *level, Camera *camera); 28 | 29 | void level_disable_pause_mode(Level *level, Camera *camera, 30 | Sound_samples *sound_samples); 31 | 32 | #endif // LEVEL_H_ 33 | -------------------------------------------------------------------------------- /src/system/log.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "./log.h" 6 | 7 | #define SEVERITY_FAIL "FAIL" 8 | #define SEVERITY_WARN "WARN" 9 | #define SEVERITY_INFO "INFO" 10 | 11 | static int log_core(const char *severity, const char *format, va_list args) 12 | { 13 | int err = fprintf(stderr, "[%s] ", severity); 14 | if (err < 0) { 15 | return err; 16 | } 17 | 18 | return vfprintf(stderr, format, args); 19 | } 20 | 21 | int log_fail(const char *format, ...) 22 | { 23 | va_list args; 24 | va_start(args, format); 25 | int err = log_core(SEVERITY_FAIL, format, args); 26 | va_end(args); 27 | return err; 28 | } 29 | 30 | int log_warn(const char *format, ...) 31 | { 32 | va_list args; 33 | va_start(args, format); 34 | int err = log_core(SEVERITY_WARN, format, args); 35 | va_end(args); 36 | return err; 37 | } 38 | 39 | int log_info(const char *format, ...) 40 | { 41 | va_list args; 42 | va_start(args, format); 43 | int err = log_core(SEVERITY_INFO, format, args); 44 | va_end(args); 45 | return err; 46 | } 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2017-2019 Nothing Developers 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /src/game/level/level_editor/background_layer.h: -------------------------------------------------------------------------------- 1 | #ifndef BACKGROUND_LAYER_H_ 2 | #define BACKGROUND_LAYER_H_ 3 | 4 | #include "color_picker.h" 5 | #include "system/s.h" 6 | 7 | typedef struct { 8 | ColorPicker color_picker; 9 | Color prev_color; 10 | } BackgroundLayer; 11 | 12 | BackgroundLayer create_background_layer(Color color); 13 | BackgroundLayer chop_background_layer(String *input); 14 | 15 | static inline 16 | LayerPtr background_layer_as_layer(BackgroundLayer *layer) 17 | { 18 | return (LayerPtr) { 19 | .ptr = layer, 20 | .type = LAYER_BACKGROUND 21 | }; 22 | } 23 | 24 | int background_layer_render(BackgroundLayer *layer, 25 | const Camera *camera, 26 | int active); 27 | int background_layer_event(BackgroundLayer *layer, 28 | const SDL_Event *event, 29 | const Camera *camera, 30 | UndoHistory *undo_history); 31 | int background_layer_dump_stream(BackgroundLayer *layer, 32 | FILE *stream); 33 | 34 | #endif // BACKGROUND_LAYER_H_ 35 | -------------------------------------------------------------------------------- /src/ui/edit_field.h: -------------------------------------------------------------------------------- 1 | #ifndef EDIT_FIELD_H_ 2 | #define EDIT_FIELD_H_ 3 | 4 | #include 5 | 6 | #include "color.h" 7 | #include "math/vec.h" 8 | #include "game/camera.h" 9 | 10 | typedef struct { 11 | char buffer[EDIT_FIELD_CAPACITY]; 12 | size_t buffer_size; 13 | size_t cursor; 14 | Vec2f font_size; 15 | Color font_color; 16 | } Edit_field; 17 | 18 | int edit_field_render_screen(const Edit_field *edit_field, 19 | const Camera *camera, 20 | Vec2f screen_position); 21 | 22 | int edit_field_render_world(const Edit_field *edit_field, 23 | const Camera *camera, 24 | Vec2f world_position); 25 | 26 | int edit_field_event(Edit_field *edit_field, const SDL_Event *event); 27 | 28 | const char *edit_field_as_text(const Edit_field *edit_field); 29 | 30 | void edit_field_replace(Edit_field *edit_field, const char *text); 31 | void edit_field_clean(Edit_field *edit_field); 32 | void edit_field_restyle(Edit_field *edit_field, 33 | Vec2f font_size, 34 | Color font_color); 35 | 36 | #endif // EDIT_FIELD_H_ 37 | -------------------------------------------------------------------------------- /src/game/level/level_editor/color_picker.h: -------------------------------------------------------------------------------- 1 | #ifndef COLOR_PICKER_H_ 2 | #define COLOR_PICKER_H_ 3 | 4 | #include 5 | #include "layer.h" 6 | #include "ui/slider.h" 7 | 8 | typedef enum { 9 | COLOR_SLIDER_HUE = 0, 10 | COLOR_SLIDER_SAT, 11 | COLOR_SLIDER_LIT, 12 | COLOR_SLIDER_N 13 | } ColorPickerSlider; 14 | 15 | typedef struct { 16 | Slider sliders[COLOR_SLIDER_N]; 17 | } ColorPicker; 18 | 19 | 20 | ColorPicker create_color_picker_from_rgba(Color color); 21 | 22 | int color_picker_render(const ColorPicker *color_picker, 23 | const Camera *camera); 24 | int color_picker_event(ColorPicker *color_picker, 25 | const SDL_Event *event, 26 | const Camera *camera, 27 | int *selected); 28 | 29 | Color color_picker_rgba(const ColorPicker *color_picker); 30 | 31 | static inline 32 | int color_picker_drag(const ColorPicker *color_picker) 33 | { 34 | int result = 0; 35 | 36 | for (int i = 0; i < COLOR_SLIDER_N; ++i) { 37 | result = result || color_picker->sliders[i].drag; 38 | } 39 | 40 | return result; 41 | } 42 | 43 | #endif // COLOR_PICKER_H_ 44 | -------------------------------------------------------------------------------- /src/sdl/texture.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "system/stacktrace.h" 4 | #include "system/log.h" 5 | #include "texture.h" 6 | 7 | SDL_Texture *texture_from_bmp(const char *bmp_file_name, 8 | SDL_Renderer *renderer) 9 | { 10 | trace_assert(bmp_file_name); 11 | trace_assert(renderer); 12 | 13 | SDL_Surface * surface = SDL_LoadBMP(bmp_file_name); 14 | if (surface == NULL) { 15 | log_fail("Could not load %s: %s\n", bmp_file_name, SDL_GetError()); 16 | goto fail; 17 | } 18 | 19 | if (SDL_SetColorKey(surface, 20 | SDL_TRUE, 21 | SDL_MapRGB(surface->format, 0, 0, 0)) < 0) { 22 | log_fail("SDL_SetColorKey: %s\n", SDL_GetError()); 23 | goto fail; 24 | } 25 | 26 | SDL_Texture *texture = SDL_CreateTextureFromSurface(renderer, surface); 27 | if (texture == NULL) { 28 | log_fail("SDL_CreateTextureFromSurface: %s\n", SDL_GetError()); 29 | goto fail; 30 | } 31 | 32 | SDL_FreeSurface(surface); 33 | 34 | return texture; 35 | 36 | fail: 37 | if (surface != NULL) { 38 | SDL_FreeSurface(surface); 39 | } 40 | 41 | return NULL; 42 | } 43 | -------------------------------------------------------------------------------- /src/config.h: -------------------------------------------------------------------------------- 1 | #ifndef CONFIG_H_ 2 | #define CONFIG_H_ 3 | 4 | // TODO(#1085): PLAYER_DEATH_LEVEL is hardcoded 5 | // Should be customizable in the Level Editor 6 | #define PLAYER_DEATH_LEVEL 1000.0f 7 | 8 | #define SOUND_SAMPLES_DEFAULT_VOLUME 80.0f 9 | 10 | #define LEVEL_EDITOR_DETH_LEVEL_COLOR hsla(0.0f, 0.8f, 0.6f, 1.0f) 11 | 12 | #define BACKGROUND_LAYERS_COUNT 3 13 | #define BACKGROUND_LAYERS_STEP 0.2f 14 | #define BACKGROUND_TURDS_PER_CHUNK 5 15 | #define BACKGROUND_CHUNK_WIDTH 500.0f 16 | #define BACKGROUND_CHUNK_HEIGHT 500.0f 17 | 18 | #define ENTITY_MAX_ID_SIZE 36 19 | 20 | #define SNAPPING_THRESHOLD 10.0f 21 | 22 | #define CAMERA_RATIO_X 16.0f 23 | #define CAMERA_RATIO_Y 9.0f 24 | 25 | #define METADATA_TITLE_MAX_SIZE 256 26 | #define METADATA_VERSION_MAX_SIZE 256 27 | #define METADATA_FILEPATH_MAX_SIZE 512 28 | 29 | #define VERSION "2" 30 | 31 | // #define RENDERER_CONFIG SDL_RENDERER_SOFTWARE 32 | #define RENDERER_CONFIG (SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC) 33 | 34 | #define UNDO_HISTORY_CAPACITY 256 35 | 36 | #define EDIT_FIELD_CAPACITY 256 37 | 38 | #define TMPMEM_CAPACITY (640 * KILO) 39 | 40 | #define LEVEL_EDITOR_MEMORY_CAPACITY (640 * KILO) 41 | 42 | #endif // CONFIG_H_ 43 | -------------------------------------------------------------------------------- /src/ring_buffer.c: -------------------------------------------------------------------------------- 1 | #include "ring_buffer.h" 2 | #include "system/stacktrace.h" 3 | 4 | void ring_buffer_push(RingBuffer *buffer, 5 | void *element) 6 | { 7 | trace_assert(buffer); 8 | trace_assert(element); 9 | 10 | size_t i = (buffer->begin + buffer->count) % buffer->capacity; 11 | 12 | if (buffer->count < buffer->capacity) { 13 | memcpy( 14 | buffer->data + i * buffer->element_size, 15 | element, 16 | buffer->element_size); 17 | buffer->count += 1; 18 | } else { 19 | memcpy( 20 | buffer->data + i * buffer->element_size, 21 | element, 22 | buffer->element_size); 23 | buffer->begin = (buffer->begin + 1) % buffer->capacity; 24 | } 25 | } 26 | 27 | int ring_buffer_pop(RingBuffer *buffer) 28 | { 29 | trace_assert(buffer); 30 | 31 | if (buffer->count == 0) return 0; 32 | buffer->count--; 33 | 34 | return 1; 35 | } 36 | 37 | void *ring_buffer_top(RingBuffer *buffer) 38 | { 39 | trace_assert(buffer); 40 | if (buffer->count == 0) return NULL; 41 | size_t i = (buffer->begin + buffer->count - 1) % buffer->capacity; 42 | return buffer->data + i * buffer->element_size; 43 | } 44 | -------------------------------------------------------------------------------- /src/ui/cursor.h: -------------------------------------------------------------------------------- 1 | #ifndef CURSOR_H_ 2 | #define CURSOR_H_ 3 | 4 | #include 5 | 6 | #define CURSOR_ICON_WIDTH 32 7 | #define CURSOR_ICON_HEIGHT 32 8 | 9 | typedef enum { 10 | CURSOR_STYLE_POINTER = 0, 11 | CURSOR_STYLE_RESIZE_VERT, 12 | CURSOR_STYLE_RESIZE_HORIS, 13 | CURSOR_STYLE_RESIZE_DIAG1, 14 | CURSOR_STYLE_RESIZE_DIAG2, 15 | 16 | CURSOR_STYLE_N 17 | } Cursor_Style; 18 | 19 | static const char * const cursor_style_tex_files[CURSOR_STYLE_N] = { 20 | "./assets/images/cursor.bmp", 21 | "./assets/images/cursor-resize-vert.bmp", 22 | "./assets/images/cursor-resize-horis.bmp", 23 | "./assets/images/cursor-resize-diag1.bmp", 24 | "./assets/images/cursor-resize-diag2.bmp" 25 | }; 26 | 27 | static const int cursor_style_tex_pivots[CURSOR_STYLE_N][2] = { 28 | {0, 0}, 29 | {CURSOR_ICON_WIDTH / 2, CURSOR_ICON_HEIGHT / 2}, 30 | {CURSOR_ICON_WIDTH / 2, CURSOR_ICON_HEIGHT / 2}, 31 | {CURSOR_ICON_WIDTH / 2, CURSOR_ICON_HEIGHT / 2}, 32 | {CURSOR_ICON_WIDTH / 2, CURSOR_ICON_HEIGHT / 2} 33 | }; 34 | 35 | typedef struct { 36 | SDL_Texture *texs[CURSOR_STYLE_N]; 37 | Cursor_Style style; 38 | } Cursor; 39 | 40 | int cursor_render(const Cursor *cursor, SDL_Renderer *renderer); 41 | 42 | #endif // CURSOR_H_ 43 | -------------------------------------------------------------------------------- /src/game.h: -------------------------------------------------------------------------------- 1 | #ifndef GAME_H_ 2 | #define GAME_H_ 3 | 4 | #include 5 | 6 | #include "game/sound_samples.h" 7 | 8 | typedef struct Game Game; 9 | 10 | Game *create_game(const char *platforms_file_path, 11 | const char *sound_sample_files[], 12 | size_t sound_sample_files_count, 13 | SDL_Renderer *renderer); 14 | void destroy_game(Game *game); 15 | 16 | int game_render(const Game *game); 17 | int game_sound(Game *game); 18 | int game_update(Game *game, float delta_time); 19 | 20 | int game_event(Game *game, const SDL_Event *event); 21 | int game_input(Game *game, 22 | const Uint8 *const keyboard_state, 23 | SDL_Joystick *the_stick_of_joy); 24 | 25 | int game_over_check(const Game *game); 26 | 27 | typedef enum Game_state { 28 | GAME_STATE_LEVEL = 0, 29 | GAME_STATE_LEVEL_PICKER, 30 | GAME_STATE_LEVEL_EDITOR, 31 | GAME_STATE_CREDITS, 32 | GAME_STATE_SETTINGS, 33 | GAME_STATE_QUIT 34 | } Game_state; 35 | 36 | void game_switch_state(Game *game, Game_state state); 37 | int game_load_level(Game *game, const char *filepath); 38 | 39 | // defined in main.c. is there a better place for this to be declared? 40 | float get_display_scale(void); 41 | 42 | #endif // GAME_H_ 43 | -------------------------------------------------------------------------------- /src/game/sprite_font.h: -------------------------------------------------------------------------------- 1 | #ifndef SPRITE_FONT_H_ 2 | #define SPRITE_FONT_H_ 3 | 4 | #include "color.h" 5 | #include "math/vec.h" 6 | #include "math/rect.h" 7 | 8 | #define FONT_CHAR_WIDTH 7 9 | #define FONT_CHAR_HEIGHT 9 10 | 11 | typedef struct { 12 | SDL_Texture *texture; 13 | } Sprite_font; 14 | 15 | SDL_Texture *load_bmp_font_texture(SDL_Renderer *renderer, 16 | const char *bmp_file_path); 17 | 18 | void sprite_font_render_text(const Sprite_font *sprite_font, 19 | SDL_Renderer *renderer, 20 | Vec2f position, 21 | Vec2f size, 22 | Color color, 23 | const char *text); 24 | 25 | static inline 26 | Rect sprite_font_boundary_box(Vec2f position, Vec2f size, const char *text) 27 | { 28 | size_t num_max_col = 1, num_row = 1; 29 | for (size_t i = 0, num_col = 1; text[i] != '\0'; i++){ 30 | if (text[i] == '\n'){ 31 | num_col = 1; 32 | num_row++; 33 | continue; 34 | } 35 | if (num_col > num_max_col) 36 | num_max_col = num_col; 37 | num_col++; 38 | } 39 | return rect( 40 | position.x, position.y, 41 | size.x * FONT_CHAR_WIDTH * (float) num_max_col, 42 | size.y * FONT_CHAR_HEIGHT * (float) num_row); 43 | } 44 | 45 | #endif // SPRITE_FONT_H_ 46 | -------------------------------------------------------------------------------- /src/ui/wiggly_text.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "math/vec.h" 4 | 5 | #include "./wiggly_text.h" 6 | #include "system/lt.h" 7 | #include "system/stacktrace.h" 8 | #include "system/nth_alloc.h" 9 | #include "system/str.h" 10 | #include "game/camera.h" 11 | 12 | void wiggly_text_render(const WigglyText *wiggly_text, 13 | const Camera *camera, 14 | Vec2f position) 15 | { 16 | trace_assert(wiggly_text); 17 | trace_assert(camera); 18 | 19 | const size_t n = strlen(wiggly_text->text); 20 | char buf[2] = {0, 0}; 21 | 22 | for (size_t i = 0; i < n; ++i) { 23 | buf[0] = wiggly_text->text[i]; 24 | 25 | camera_render_text_screen( 26 | camera, 27 | buf, 28 | wiggly_text->scale, 29 | wiggly_text->color, 30 | vec_sum( 31 | position, 32 | vec( 33 | (float) (i * FONT_CHAR_WIDTH) * wiggly_text->scale.x, 34 | sinf(wiggly_text->angle + (float) i / (float) n * 10.0f) * 20.0f))); 35 | } 36 | } 37 | 38 | int wiggly_text_update(WigglyText *wiggly_text, float delta_time) 39 | { 40 | trace_assert(wiggly_text); 41 | wiggly_text->angle = fmodf(wiggly_text->angle + 10.0f * delta_time, 2 * PI); 42 | return 0; 43 | } 44 | 45 | Vec2f wiggly_text_size(const WigglyText *wiggly_text) 46 | { 47 | trace_assert(wiggly_text); 48 | 49 | const Rect boundary = sprite_font_boundary_box( 50 | vec(0.0f, 0.0f), 51 | wiggly_text->scale, 52 | wiggly_text->text); 53 | 54 | return vec(boundary.w, boundary.h); 55 | } 56 | -------------------------------------------------------------------------------- /src/game/level/player.h: -------------------------------------------------------------------------------- 1 | #ifndef PLAYER_H_ 2 | #define PLAYER_H_ 3 | 4 | #include 5 | 6 | #include "game/camera.h" 7 | #include "game/sound_samples.h" 8 | #include "lava.h" 9 | #include "platforms.h" 10 | #include "boxes.h" 11 | #include "game/level/level_editor/player_layer.h" 12 | 13 | typedef struct Player Player; 14 | typedef struct Goals Goals; 15 | typedef struct RigidBodies RigidBodies; 16 | 17 | Player *create_player_from_player_layer(const PlayerLayer *player_layer, 18 | RigidBodies *rigid_bodies); 19 | void destroy_player(Player * player); 20 | 21 | int player_render(const Player * player, 22 | const Camera *camera); 23 | void player_update(Player * player, 24 | float delta_time); 25 | void player_touches_rect_sides(Player *player, 26 | Rect object, 27 | int sides[RECT_SIDE_N]); 28 | 29 | int player_sound(Player *player, 30 | Sound_samples *sound_samples); 31 | void player_checkpoint(Player *player, 32 | Vec2f checkpoint); 33 | 34 | void player_move_left(Player *player); 35 | void player_move_right(Player *player); 36 | void player_stop(Player *player); 37 | void player_jump(Player *player); 38 | void player_die(Player *player); 39 | 40 | void player_focus_camera(Player *player, 41 | Camera *camera); 42 | void player_die_from_lava(Player *player, 43 | const Lava *lava); 44 | 45 | bool player_overlaps_rect(const Player *player, 46 | Rect rect); 47 | 48 | Rect player_hitbox(const Player *player); 49 | 50 | #endif // PLAYER_H_ 51 | -------------------------------------------------------------------------------- /src/game/credits.c: -------------------------------------------------------------------------------- 1 | #include "./credits.h" 2 | #include "game/level/background.h" 3 | #include "game/sprite_font.h" 4 | #include "system/lt.h" 5 | #include "system/nth_alloc.h" 6 | #include "system/stacktrace.h" 7 | #include "system/str.h" 8 | #include "system/log.h" 9 | #include "ui/wiggly_text.h" 10 | #include "config.h" 11 | 12 | #define TITLE_MARGIN_TOP 100.0f 13 | 14 | Credits create_credits(void) 15 | { 16 | Credits result; 17 | result.background = create_background(hexstr("250741")); 18 | result.camera_position = vec(0.0f, 0.0f); 19 | result.wiggly_text = (WigglyText) { 20 | .text = "Twitch Subs/Contributors", 21 | .scale = {8.0f, 8.0f}, 22 | .color = COLOR_WHITE, 23 | }; 24 | return result; 25 | } 26 | 27 | int credits_render(const Credits *credits, const Camera *camera) 28 | { 29 | trace_assert(credits); 30 | trace_assert(camera); 31 | 32 | const Rect viewport = camera_view_port_screen(camera); 33 | 34 | if (background_render(&credits->background, camera) < 0) { 35 | return -1; 36 | } 37 | 38 | const Vec2f title_size = wiggly_text_size(&credits->wiggly_text); 39 | 40 | wiggly_text_render( 41 | &credits->wiggly_text, 42 | camera, 43 | vec(viewport.w * 0.5f - title_size.x * 0.5f, TITLE_MARGIN_TOP)); 44 | // TODO(#1150): Credits page don't display list of subs and contributors 45 | return 0; 46 | } 47 | 48 | int credits_update(Credits *credits, Camera *camera, float dt) 49 | { 50 | trace_assert(credits); 51 | trace_assert(camera); 52 | 53 | vec_add(&credits->camera_position, 54 | vec(0.0f, 20.0f * dt)); 55 | camera_center_at(camera, credits->camera_position); 56 | 57 | if (wiggly_text_update(&credits->wiggly_text, dt) < 0) { 58 | return -1; 59 | } 60 | 61 | return 0; 62 | } 63 | -------------------------------------------------------------------------------- /src/game/level_picker.h: -------------------------------------------------------------------------------- 1 | #ifndef LEVEL_PICKER_H_ 2 | #define LEVEL_PICKER_H_ 3 | 4 | #include 5 | 6 | #include "game/camera.h" 7 | #include "game/level/background.h" 8 | #include "ui/wiggly_text.h" 9 | #include "dynarray.h" 10 | 11 | typedef struct { 12 | Background background; 13 | Vec2f camera_position; 14 | WigglyText wiggly_text; 15 | Dynarray items; 16 | size_t items_cursor; 17 | int selected_item; 18 | Vec2f items_scroll; 19 | Vec2f items_position; 20 | Vec2f items_size; 21 | } LevelPicker; 22 | 23 | // TODO(#1221): Level Picker scroll does not support mouse wheel 24 | // TODO(#1222): Level Picker scroll does not support dragging 25 | 26 | void level_picker_populate(LevelPicker *level_picker, 27 | const char *dirpath); 28 | 29 | static inline 30 | void destroy_level_picker(LevelPicker level_picker) 31 | { 32 | free(level_picker.items.data); 33 | } 34 | 35 | int level_picker_render(const LevelPicker *level_picker, 36 | const Camera *camera); 37 | int level_picker_update(LevelPicker *level, 38 | Camera *camera, 39 | float delta_time); 40 | int level_picker_event(LevelPicker *level_picker, 41 | const SDL_Event *event); 42 | int level_picker_input(LevelPicker *level_picker, 43 | const Uint8 *const keyboard_state, 44 | SDL_Joystick *the_stick_of_joy); 45 | int level_picker_enter_camera_event(LevelPicker *level_picker, 46 | Camera *camera); 47 | void level_picker_cursor_up(LevelPicker *level_picker); 48 | void level_picker_cursor_down(LevelPicker *level_picker); 49 | 50 | const char *level_picker_selected_level(const LevelPicker *level_picker); 51 | void level_picker_clean_selection(LevelPicker *level_picker); 52 | 53 | 54 | #endif // LEVEL_PICKER_H_ 55 | -------------------------------------------------------------------------------- /src/dynarray.h: -------------------------------------------------------------------------------- 1 | #ifndef DYNARRAY_H_ 2 | #define DYNARRAY_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include "system/memory.h" 8 | #include "system/stacktrace.h" 9 | 10 | #define DYNARRAY_CAPACITY 256 11 | 12 | typedef struct { 13 | size_t element_size; 14 | size_t count; 15 | void *data; 16 | } Dynarray; 17 | 18 | static inline 19 | Dynarray create_dynarray_malloc(size_t element_size) 20 | { 21 | Dynarray result = { 22 | .element_size = element_size, 23 | .count = 0, 24 | .data = malloc(DYNARRAY_CAPACITY * element_size) 25 | }; 26 | trace_assert(result.data); 27 | return result; 28 | } 29 | 30 | static inline 31 | Dynarray create_dynarray(Memory *memory, size_t element_size) 32 | { 33 | trace_assert(memory); 34 | Dynarray result = { 35 | .element_size = element_size, 36 | .count = 0, 37 | .data = memory_alloc(memory, DYNARRAY_CAPACITY * element_size) 38 | }; 39 | return result; 40 | } 41 | 42 | void *dynarray_pointer_at(const Dynarray *dynarray, size_t index); 43 | void dynarray_replace_at(Dynarray *dynarray, size_t index, void *element); 44 | void dynarray_copy_to(Dynarray *dynarray, void *dest, size_t index); 45 | void dynarray_clear(Dynarray *dynarray); 46 | // O(1) amortized 47 | // TODO(#981): dynarray_push should be called dynarray_push_copy 48 | int dynarray_push(Dynarray *dynarray, const void *element); 49 | int dynarray_push_empty(Dynarray *dynarray); 50 | void dynarray_pop(Dynarray *dynarray, void *element); 51 | bool dynarray_contains(const Dynarray *dynarray, 52 | const void *element); 53 | 54 | void dynarray_swap(Dynarray *dynarray, size_t i, size_t j); 55 | 56 | // O(N) 57 | void dynarray_delete_at(Dynarray *dynarray, size_t index); 58 | void dynarray_insert_before(Dynarray *dynarray, size_t index, void *element); 59 | 60 | #endif // DYNARRAY_H_ 61 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # TODO(#1183): appveyor linux build does not support clang 2 | image: 3 | - ubuntu1804 4 | - Visual Studio 2015 5 | - macos 6 | environment: # enable mingw build on windows image 7 | MSYSTEM: MINGW64 8 | CHERE_INVOKING: 1 9 | matrix: 10 | - BUILD_TYPE: mingw 11 | - BUILD_TYPE: other 12 | matrix: 13 | exclude: # no mingw build on linux 14 | - image: ubuntu1804 15 | BUILD_TYPE: mingw 16 | - image: macos 17 | BUILD_TYPE: mingw 18 | install: 19 | - ps: | 20 | if ($isWindows) { 21 | if ($env:BUILD_TYPE -eq 'mingw') { 22 | cmd /C 'curl -fsSL -o SDL2-devel-2.0.10-mingw.tar.gz https://www.libsdl.org/release/SDL2-devel-2.0.10-mingw.tar.gz' 23 | C:\msys64\usr\bin\bash.exe -lc "tar xzf SDL2-devel-2.0.10-mingw.tar.gz" 24 | mv SDL2-2.0.10 SDL2 25 | } else { 26 | cmd /C 'curl -fsSL -o SDL2-devel-2.0.9-VC.zip https://www.libsdl.org/release/SDL2-devel-2.0.9-VC.zip' 27 | 7z x SDL2-devel-2.0.9-VC.zip 28 | mv SDL2-2.0.9 SDL2 29 | } 30 | } elseif ($isLinux) { 31 | bash -c "sudo apt-get update -qq" 32 | bash -c "sudo apt-get install -qq libsdl2-dev" 33 | } else { 34 | bash -c "brew install sdl2" 35 | } 36 | build_script: 37 | - ps: | 38 | if ($isWindows) { 39 | mkdir build 40 | cd build 41 | if ($env:BUILD_TYPE -eq 'mingw') { 42 | C:\msys64\usr\bin\bash.exe -lc "cmake .. -G 'MSYS Makefiles' -DNOTHING_CI=ON" 43 | C:\msys64\usr\bin\bash.exe -lc "cmake --build ." 44 | } else { 45 | cmake .. -DNOTHING_CI=ON 46 | cmake --build . 47 | } 48 | } elseif ($isLinux) { 49 | bash -c "CC=gcc ./build-posix.sh" 50 | } else { 51 | bash -c "CC=clang ./build-posix.sh" 52 | } 53 | -------------------------------------------------------------------------------- /src/math/triangle.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "math/pi.h" 5 | #include "math/rand.h" 6 | #include "triangle.h" 7 | 8 | Triangle triangle(Vec2f p1, Vec2f p2, Vec2f p3) 9 | { 10 | const Triangle result = { 11 | .p1 = p1, 12 | .p2 = p2, 13 | .p3 = p3 14 | }; 15 | 16 | return result; 17 | } 18 | 19 | Triangle equilateral_triangle(void) 20 | { 21 | const float d = PI_2 / 3.0f; 22 | 23 | const Triangle result = { 24 | .p1 = vec(cosf(0.0f), sinf(0.0f)), 25 | .p2 = vec(cosf(d), sinf(d)), 26 | .p3 = vec(cosf(2.0f * d), sinf(2.0f * d)) 27 | }; 28 | 29 | return result; 30 | } 31 | 32 | Triangle random_triangle(float radius) 33 | { 34 | return triangle( 35 | vec_from_polar(rand_float(2 * PI), rand_float(radius)), 36 | vec_from_polar(rand_float(2 * PI), rand_float(radius)), 37 | vec_from_polar(rand_float(2 * PI), rand_float(radius))); 38 | } 39 | 40 | static void swap_points(Vec2f *p1, Vec2f *p2) 41 | { 42 | Vec2f t = *p1; 43 | *p1 = *p2; 44 | *p2 = t; 45 | } 46 | 47 | Triangle triangle_sorted_by_y(Triangle t) 48 | { 49 | if (t.p1.y > t.p2.y) { swap_points(&t.p1, &t.p2); } 50 | if (t.p2.y > t.p3.y) { swap_points(&t.p2, &t.p3); } 51 | if (t.p1.y > t.p2.y) { swap_points(&t.p1, &t.p2); } 52 | 53 | return t; 54 | } 55 | 56 | void rect_as_triangles(Rect rect, Triangle triangles[2]) 57 | { 58 | Triangle t1 = { 59 | .p1 = { .x = rect.x, .y = rect.y }, 60 | .p2 = { .x = rect.x + rect.w, .y = rect.y }, 61 | .p3 = { .x = rect.x, .y = rect.y + rect.h } 62 | }; 63 | 64 | Triangle t2 = { 65 | .p1 = { .x = rect.x + rect.w, .y = rect.y }, 66 | .p2 = { .x = rect.x, .y = rect.y + rect.h }, 67 | .p3 = { .x = rect.x + rect.w, .y = rect.y + rect.h } 68 | }; 69 | 70 | triangles[0] = t1; 71 | triangles[1] = t2; 72 | } 73 | -------------------------------------------------------------------------------- /src/game/level/level_editor/undo_history.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | #include "system/nth_alloc.h" 7 | #include "system/stacktrace.h" 8 | #include "undo_history.h" 9 | #include "config.h" 10 | 11 | typedef struct { 12 | RevertAction revert; 13 | void *context_data; 14 | size_t context_data_size; 15 | } HistoryItem; 16 | 17 | UndoHistory *create_undo_history(Memory *memory) 18 | { 19 | UndoHistory *result = memory_alloc(memory, sizeof(UndoHistory)); 20 | result->actions = create_ring_buffer_from_buffer( 21 | memory, 22 | sizeof(HistoryItem), 23 | UNDO_HISTORY_CAPACITY); 24 | result->memory = memory; 25 | return result; 26 | } 27 | 28 | void undo_history_push(UndoHistory *undo_history, 29 | RevertAction revert, 30 | void *context_data, 31 | size_t context_data_size) 32 | { 33 | trace_assert(undo_history); 34 | 35 | // TODO(#1244): undo_history_push kinda leaks the memory 36 | HistoryItem item = { 37 | .revert = revert, 38 | .context_data = memory_alloc(undo_history->memory, context_data_size), 39 | .context_data_size = context_data_size 40 | }; 41 | memcpy(item.context_data, context_data, context_data_size); 42 | ring_buffer_push(&undo_history->actions, &item); 43 | } 44 | 45 | void undo_history_pop(UndoHistory *undo_history) 46 | { 47 | trace_assert(undo_history); 48 | 49 | if (undo_history->actions.count > 0) { 50 | HistoryItem *item = ring_buffer_top(&undo_history->actions); 51 | item->revert(item->context_data, item->context_data_size); 52 | ring_buffer_pop(&undo_history->actions); 53 | } 54 | } 55 | 56 | void undo_history_clean(UndoHistory *undo_history) 57 | { 58 | trace_assert(undo_history); 59 | 60 | while (undo_history->actions.count) { 61 | ring_buffer_pop(&undo_history->actions); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/game/level/phantom_platforms.c: -------------------------------------------------------------------------------- 1 | #include "phantom_platforms.h" 2 | 3 | Phantom_Platforms create_phantom_platforms(RectLayer *rect_layer) 4 | { 5 | Phantom_Platforms pp; 6 | 7 | pp.size = rect_layer->rects.count; 8 | pp.rects = malloc(sizeof(pp.rects[0]) * pp.size); 9 | memcpy(pp.rects, rect_layer->rects.data, sizeof(pp.rects[0]) * pp.size); 10 | 11 | pp.colors = malloc(sizeof(pp.colors[0]) * pp.size); 12 | memcpy(pp.colors, rect_layer->colors.data, sizeof(pp.colors[0]) * pp.size); 13 | 14 | pp.hiding = calloc(1, sizeof(pp.hiding[0]) * pp.size); 15 | 16 | return pp; 17 | } 18 | 19 | void destroy_phantom_platforms(Phantom_Platforms pp) 20 | { 21 | free(pp.rects); 22 | free(pp.colors); 23 | free(pp.hiding); 24 | } 25 | 26 | void phantom_platforms_render(const Phantom_Platforms *pp, const Camera *camera) 27 | { 28 | trace_assert(pp); 29 | trace_assert(camera); 30 | 31 | for (size_t i = 0; i < pp->size; ++i) { 32 | camera_fill_rect(camera, pp->rects[i], pp->colors[i]); 33 | } 34 | } 35 | 36 | #define HIDING_SPEED 4.0f 37 | 38 | // TODO(#1247): phantom_platforms_update is O(N) even when nothing is animated 39 | void phantom_platforms_update(Phantom_Platforms *pp, float dt) 40 | { 41 | trace_assert(pp); 42 | 43 | for (size_t i = 0; i < pp->size; ++i) { 44 | if (pp->hiding[i]) { 45 | if (pp->colors[i].a > 0.0f) { 46 | pp->colors[i].a = 47 | fmaxf(0.0f, pp->colors[i].a - HIDING_SPEED * dt); 48 | } else { 49 | pp->hiding[i] = 0; 50 | } 51 | } 52 | } 53 | } 54 | 55 | // TODO(#1248): phantom_platforms_hide_at is O(N) 56 | void phantom_platforms_hide_at(Phantom_Platforms *pp, Vec2f position) 57 | { 58 | trace_assert(pp); 59 | 60 | for (size_t i = 0; i < pp->size; ++i) { 61 | if (rect_contains_point(pp->rects[i], position)) { 62 | pp->hiding[i] = 1; 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /assets/levels/platforms.txt: -------------------------------------------------------------------------------- 1 | 2 2 | 073642 3 | 117.106163 417.591919 b58900 4 | 22 5 | rect6 0.000000 658.818970 1617.752319 1014.818970 657b83 6 | rect4-3-3 -1609.375000 0.000000 1659.375000 1673.637939 657b83 7 | rect4-3-3-7 895.570679 0.000000 50.000000 680.318970 657b83 8 | rect4-3-3-7-5-3 829.608704 585.283447 65.961945 20.000000 657b83 9 | rect4-3-3-7-5-3-6 669.136475 508.793976 65.961945 20.000000 657b83 10 | rect4-3-3-7-5-3-5 829.608704 443.094025 65.961945 20.000000 657b83 11 | rect4-3-3-7-5-3-6-3 669.136536 366.604553 65.961945 20.000000 657b83 12 | rect4-3-3-7-5-3-56 829.608704 288.237640 65.961945 20.000000 657b83 13 | rect4-3-3-7-5-3-6-2 669.136536 211.748184 65.961945 20.000000 657b83 14 | rect4-3-3-7-5-3-9 829.608704 129.845734 65.961945 20.000000 657b83 15 | rect4-3-3-7-5-3-6-1 669.136536 53.356262 65.961945 20.000000 657b83 16 | rect4-3-3-7-5-3-2 945.570679 585.782593 65.961945 20.000000 657b83 17 | rect4-3-3-7-5-3-6-37 1088.098389 509.293091 65.961945 20.000000 657b83 18 | rect4-3-3-7-5-3-5-5 945.570679 443.593140 65.961945 20.000000 657b83 19 | rect4-3-3-7-5-3-6-3-9 1088.098511 367.103699 65.961945 20.000000 657b83 20 | rect4-3-3-7-5-3-56-2 945.570679 288.736755 65.961945 20.000000 657b83 21 | rect4-3-3-7-5-3-6-2-2 1088.098511 212.247314 65.961945 20.000000 657b83 22 | rect4-3-3-7-5-3-9-8 945.570679 130.344849 65.961945 20.000000 657b83 23 | rect4-3-3-7-5-3-6-1-9 1088.098511 53.855385 65.961945 20.000000 657b83 24 | rect4-3-3-7-7 1768.570801 -1131.370850 1356.000000 2805.008789 657b83 25 | rect4-3-3-7-5-3-6-36 358.756195 617.806824 65.961945 82.024391 657b83 26 | rect4-3-3-7-5-3-6-7 508.662811 617.806824 65.961945 82.024391 657b83 27 | 2 28 | goal2 973.782593 631.068970 d33682 29 | goal3 -103.338097 -136.981247 d33682 30 | 2 31 | lava1 413.910431 628.434204 105.559921 38.384777 000000 32 | lava1-3 225.220032 152.626617 357.559937 245.384781 ff9955 33 | 0 34 | 0 35 | 2 36 | label_0 150.416733 691.458435 bfe34f 37 | Label Test 38 | label_1 290.416748 691.458435 bfe34f 39 | Label Test 40 | 0 41 | -------------------------------------------------------------------------------- /src/ui/wiggly_text.h: -------------------------------------------------------------------------------- 1 | #ifndef WIGGLY_TEXT_H_ 2 | #define WIGGLY_TEXT_H_ 3 | 4 | #include 5 | 6 | #include "color.h" 7 | #include "system/stacktrace.h" 8 | #include "game/camera.h" 9 | 10 | typedef struct { 11 | const char *text; 12 | Vec2f scale; 13 | Color color; 14 | float angle; 15 | } WigglyText; 16 | 17 | void wiggly_text_render(const WigglyText *wiggly_text, 18 | const Camera *camera, 19 | Vec2f position); 20 | int wiggly_text_update(WigglyText *wiggly_text, float delta_time); 21 | Vec2f wiggly_text_size(const WigglyText *wiggly_text); 22 | 23 | typedef struct { 24 | WigglyText wiggly_text; 25 | float duration; 26 | } FadingWigglyText; 27 | 28 | static inline 29 | void fading_wiggly_text_render(const FadingWigglyText *fading_wiggly_text, 30 | const Camera *camera, 31 | Vec2f position) 32 | { 33 | wiggly_text_render( 34 | &fading_wiggly_text->wiggly_text, 35 | camera, 36 | position); 37 | } 38 | 39 | static inline 40 | int fading_wiggly_text_update(FadingWigglyText *fading_wiggly_text, 41 | float delta_time) 42 | { 43 | trace_assert(fading_wiggly_text); 44 | 45 | const float alpha = fading_wiggly_text->wiggly_text.color.a; 46 | const float duration = fading_wiggly_text->duration; 47 | 48 | fading_wiggly_text->wiggly_text.color.a = 49 | fmaxf(alpha * duration - delta_time, 0.0f) / duration; 50 | 51 | return wiggly_text_update(&fading_wiggly_text->wiggly_text, delta_time); 52 | } 53 | 54 | static inline 55 | void fading_wiggly_text_reset(FadingWigglyText *fading_wiggly_text) 56 | { 57 | trace_assert(fading_wiggly_text); 58 | fading_wiggly_text->wiggly_text.color.a = 1.0f; 59 | } 60 | 61 | static inline 62 | Vec2f fading_wiggly_text_size(const FadingWigglyText *fading_wiggly_text) 63 | { 64 | return wiggly_text_size(&fading_wiggly_text->wiggly_text); 65 | } 66 | 67 | #endif // WIGGLY_TEXT_H_ 68 | -------------------------------------------------------------------------------- /nothing.c: -------------------------------------------------------------------------------- 1 | #include "src/color.c" 2 | #include "src/game.c" 3 | #include "src/game/camera.c" 4 | #include "src/game/level.c" 5 | #include "src/game/level/background.c" 6 | #include "src/game/level/boxes.c" 7 | #include "src/game/level/goals.c" 8 | #include "src/game/level/labels.c" 9 | #include "src/game/level/lava.c" 10 | #include "src/game/level/lava/wavy_rect.c" 11 | #include "src/game/level/platforms.c" 12 | #include "src/game/level/player.c" 13 | #include "src/game/level/explosion.c" 14 | #include "src/game/level/regions.c" 15 | #include "src/game/level/rigid_bodies.c" 16 | #include "src/game/level_picker.c" 17 | #include "src/game/credits.c" 18 | #include "src/game/settings.c" 19 | #include "src/game/sound_samples.c" 20 | #include "src/game/sprite_font.c" 21 | #include "src/main.c" 22 | #include "src/math/rand.c" 23 | #include "src/math/rect.c" 24 | #include "src/math/triangle.c" 25 | #include "src/sdl/renderer.c" 26 | #include "src/sdl/texture.c" 27 | #include "src/ui/cursor.c" 28 | #include "src/ui/console.c" 29 | #include "src/ui/console_log.c" 30 | #include "src/ui/edit_field.c" 31 | #include "src/ui/history.c" 32 | #include "src/ui/wiggly_text.c" 33 | #include "src/ui/slider.c" 34 | #include "src/game/level/level_editor.c" 35 | #include "src/game/level/level_editor/color_picker.c" 36 | #include "src/game/level/level_editor/rect_layer.c" 37 | #include "src/game/level/level_editor/layer_picker.c" 38 | #include "src/game/level/level_editor/point_layer.c" 39 | #include "src/game/level/level_editor/player_layer.c" 40 | #include "src/game/level/level_editor/layer.c" 41 | #include "src/game/level/level_editor/label_layer.c" 42 | #include "src/game/level/level_editor/background_layer.c" 43 | #include "src/game/level/level_editor/undo_history.c" 44 | #include "src/system/log.c" 45 | #include "src/system/lt_adapters.c" 46 | #include "src/system/nth_alloc.c" 47 | #include "src/system/stacktrace.c" 48 | #include "src/system/str.c" 49 | #include "src/dynarray.c" 50 | #include "src/system/file.c" 51 | #include "src/ring_buffer.c" 52 | #include "src/game/level/phantom_platforms.c" 53 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Roles 2 | 3 | ## Project Leader 4 | 5 | Current Project Leader: [@rexim] 6 | 7 | - Project Leader is fully responsible for the project. 8 | - If something doesn't work blame Project Leader for that. Not the Contributor who introduced a bug. If the Project Leader did their job well they would not accept the changes in the first place. 9 | - Project Leader defines these rules and their interpretation. 10 | 11 | ## Contributor 12 | 13 | Contributors are welcome to submit Issues and Pull Requests according to the rules below. If the rules are not clear, ask the Project Leader on what to do in any particular situation. 14 | 15 | ### Issues 16 | 17 | - Feel free to submit an issues in case of 18 | - bug 19 | - suggestion 20 | - feature request 21 | - question regarding the project 22 | - unclear documentation 23 | - unreadable or straight up bad code 24 | - any problem that you ran into while setting up the project on your side 25 | - Do not submit **Tasks**. Submit **Problems**. 26 | - Bad issue: "Rewrite everything in Rust" 27 | - Good issue: "Using C makes the development difficult" 28 | - We are engineers. We don't follow anybody's direction. We solve problems. Please submit problems. 29 | 30 | ### Pull Requests 31 | 32 | - Keep your PRs nice and small around 300 changes (including adding new lines). PRs that are way over that threshold won't be merged. Exceptions may apply if the reasons for a bigger PRs are communicated properly to the Project Leader in the description of the Pull Request. 33 | - If 300 changes is not enough, 34 | - make as many changes as you can according to the PR size limitation, 35 | - make sure that the code is compilable and the application is usable, 36 | - create TODOs right in the code for the unfinished work, 37 | - submit PR with the unfinished work and TODOs, 38 | - TODOs will be converted to separate issues 39 | - Neglected PRs older than two weeks may be closed by Project Leader at any time. 40 | - Don't get discouraged if your PR was not merged. The Rules and CI are designed to keep the quality of the code high so you don't have to worry about it yourself. Keep experimenting, keep improving. 41 | 42 | [@rexim]: https://github.com/rexim 43 | -------------------------------------------------------------------------------- /src/game/level/level_editor.h: -------------------------------------------------------------------------------- 1 | #ifndef LEVEL_EDITOR_H_ 2 | #define LEVEL_EDITOR_H_ 3 | 4 | #include "game/level/level_editor/layer.h" 5 | #include "game/level/level_editor/layer_picker.h" 6 | #include "game/level/level_editor/undo_history.h" 7 | #include "game/level/level_editor/rect_layer.h" 8 | #include "game/level/level_editor/point_layer.h" 9 | #include "game/level/level_editor/label_layer.h" 10 | #include "ui/wiggly_text.h" 11 | #include "ui/cursor.h" 12 | 13 | typedef struct LevelEditor LevelEditor; 14 | typedef struct Sound_samples Sound_samples; 15 | 16 | typedef enum { 17 | LEVEL_EDITOR_IDLE = 0, 18 | LEVEL_EDITOR_SAVEAS 19 | } LevelEditorState; 20 | 21 | struct LevelEditor 22 | { 23 | LevelEditorState state; 24 | Vec2f camera_position; 25 | float camera_scale; 26 | Edit_field edit_field_filename; 27 | LayerPicker layer_picker; 28 | FadingWigglyText notice; 29 | 30 | RectLayer *boxes_layer; 31 | RectLayer *platforms_layer; 32 | RectLayer *back_platforms_layer; 33 | PointLayer *goals_layer; 34 | PlayerLayer player_layer; 35 | RectLayer *lava_layer; 36 | RectLayer *regions_layer; 37 | BackgroundLayer background_layer; 38 | LabelLayer *label_layer; 39 | RectLayer *pp_layer; 40 | 41 | LayerPtr layers[LAYER_PICKER_N]; 42 | 43 | UndoHistory *undo_history; 44 | 45 | bool drag; 46 | int bell; 47 | int click; 48 | int save; 49 | 50 | char *file_name; 51 | }; 52 | 53 | LevelEditor *create_level_editor(Memory *memory, Cursor *cursor); 54 | LevelEditor *create_level_editor_from_file(Memory *memory, Cursor *cursor, const char *file_name); 55 | 56 | int level_editor_render(const LevelEditor *level_editor, 57 | const Camera *camera); 58 | int level_editor_event(LevelEditor *level_editor, 59 | const SDL_Event *event, 60 | Camera *camera, 61 | Memory *memory); 62 | int level_editor_focus_camera(LevelEditor *level_editor, 63 | Camera *camera); 64 | int level_editor_update(LevelEditor *level_editor, float delta_time); 65 | void level_editor_sound(LevelEditor *level_editor, Sound_samples *sound_samples); 66 | 67 | #endif // LEVEL_EDITOR_H_ 68 | -------------------------------------------------------------------------------- /src/game/level/level_editor/point_layer.h: -------------------------------------------------------------------------------- 1 | #ifndef POINT_LAYER_H_ 2 | #define POINT_LAYER_H_ 3 | 4 | #include "math/vec.h" 5 | #include "color.h" 6 | #include "layer.h" 7 | #include "dynarray.h" 8 | #include "game/level/level_editor/color_picker.h" 9 | #include "ui/edit_field.h" 10 | 11 | #define ID_MAX_SIZE 36 12 | 13 | typedef enum { 14 | POINT_LAYER_IDLE = 0, 15 | POINT_LAYER_EDIT_ID, 16 | POINT_LAYER_MOVE, 17 | POINT_LAYER_RECOLOR 18 | } PointLayerState; 19 | 20 | typedef struct { 21 | PointLayerState state; 22 | Dynarray/**/ positions; 23 | Dynarray/**/ colors; 24 | Dynarray/**/ ids; 25 | int selection; 26 | ColorPicker color_picker; 27 | 28 | Vec2f inter_position; 29 | Color inter_color; 30 | Edit_field edit_field; 31 | 32 | int id_name_counter; 33 | const char *id_name_prefix; 34 | } PointLayer; 35 | 36 | 37 | LayerPtr point_layer_as_layer(PointLayer *point_layer); 38 | // NOTE: create_point_layer and create_point_layer_from_line_stream do 39 | // not own id_name_prefix 40 | PointLayer *create_point_layer(Memory *memory, const char *id_name_prefix); 41 | void point_layer_load(PointLayer *point_layer, 42 | Memory *memory, 43 | String *input); 44 | 45 | static inline 46 | void destroy_point_layer(PointLayer point_layer) 47 | { 48 | free(point_layer.positions.data); 49 | free(point_layer.colors.data); 50 | free(point_layer.ids.data); 51 | } 52 | 53 | 54 | int point_layer_render(const PointLayer *point_layer, 55 | const Camera *camera, 56 | int active); 57 | int point_layer_event(PointLayer *point_layer, 58 | const SDL_Event *event, 59 | const Camera *camera, 60 | UndoHistory *undo_history); 61 | 62 | int point_layer_dump_stream(const PointLayer *point_layer, 63 | FILE *filedump); 64 | 65 | size_t point_layer_count(const PointLayer *point_layer); 66 | const Vec2f *point_layer_positions(const PointLayer *point_layer); 67 | const Color *point_layer_colors(const PointLayer *point_layer); 68 | const char *point_layer_ids(const PointLayer *point_layer); 69 | 70 | #endif // POINT_LAYER_H_ 71 | -------------------------------------------------------------------------------- /src/game/level/lava/wavy_rect.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "system/stacktrace.h" 3 | #include 4 | #include 5 | #include 6 | 7 | #include "math/pi.h" 8 | #include "system/log.h" 9 | #include "system/lt.h" 10 | #include "system/nth_alloc.h" 11 | #include "wavy_rect.h" 12 | 13 | #define WAVE_PILLAR_WIDTH 10.0f 14 | 15 | struct Wavy_rect 16 | { 17 | Lt *lt; 18 | 19 | Rect rect; 20 | Color color; 21 | float angle; 22 | }; 23 | 24 | Wavy_rect *create_wavy_rect(Rect rect, Color color) 25 | { 26 | Lt *lt = create_lt(); 27 | 28 | Wavy_rect *wavy_rect = PUSH_LT(lt, nth_calloc(1, sizeof(Wavy_rect)), free); 29 | if (wavy_rect == NULL) { 30 | RETURN_LT(lt, NULL); 31 | } 32 | 33 | wavy_rect->rect = rect; 34 | wavy_rect->color = color; 35 | wavy_rect->angle = 0.0f; 36 | wavy_rect->lt = lt; 37 | 38 | return wavy_rect; 39 | } 40 | 41 | void destroy_wavy_rect(Wavy_rect *wavy_rect) 42 | { 43 | trace_assert(wavy_rect); 44 | RETURN_LT0(wavy_rect->lt); 45 | } 46 | 47 | int wavy_rect_render(const Wavy_rect *wavy_rect, 48 | const Camera *camera) 49 | { 50 | trace_assert(wavy_rect); 51 | trace_assert(camera); 52 | 53 | srand(42); 54 | for (float wave_scanner = 0; 55 | wave_scanner < wavy_rect->rect.w; 56 | wave_scanner += WAVE_PILLAR_WIDTH) { 57 | 58 | const float s = (float) (rand() % 50) * 0.1f; 59 | if (camera_fill_rect( 60 | camera, 61 | rect( 62 | wavy_rect->rect.x + wave_scanner, 63 | wavy_rect->rect.y + s * sinf(wavy_rect->angle + wave_scanner / WAVE_PILLAR_WIDTH), 64 | WAVE_PILLAR_WIDTH * 1.20f, 65 | wavy_rect->rect.h), 66 | wavy_rect->color) < 0) { 67 | return -1; 68 | } 69 | } 70 | srand((unsigned int) time(NULL)); 71 | 72 | return 0; 73 | } 74 | 75 | int wavy_rect_update(Wavy_rect *wavy_rect, 76 | float delta_time) 77 | { 78 | trace_assert(wavy_rect); 79 | wavy_rect->angle = fmodf(wavy_rect->angle + 2.0f * delta_time, 2 * PI); 80 | 81 | return 0; 82 | } 83 | 84 | Rect wavy_rect_hitbox(const Wavy_rect *wavy_rect) 85 | { 86 | return wavy_rect->rect; 87 | } 88 | -------------------------------------------------------------------------------- /src/system/file.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "file.h" 6 | #include "system/nth_alloc.h" 7 | #include "system/stacktrace.h" 8 | #include "lt_adapters.h" 9 | 10 | #ifdef _WIN32 11 | 12 | struct DIR 13 | { 14 | HANDLE hFind; 15 | WIN32_FIND_DATA data; 16 | struct dirent *dirent; 17 | }; 18 | 19 | DIR *opendir(const char *dirpath) 20 | { 21 | trace_assert(dirpath); 22 | 23 | char buffer[MAX_PATH]; 24 | snprintf(buffer, MAX_PATH, "%s\\*", dirpath); 25 | 26 | DIR *dir = nth_calloc(1, sizeof(DIR)); 27 | 28 | dir->hFind = FindFirstFile(buffer, &dir->data); 29 | if (dir->hFind == INVALID_HANDLE_VALUE) { 30 | goto fail; 31 | } 32 | 33 | return dir; 34 | 35 | fail: 36 | if (dir) { 37 | free(dir); 38 | } 39 | 40 | return NULL; 41 | } 42 | 43 | struct dirent *readdir(DIR *dirp) 44 | { 45 | trace_assert(dirp); 46 | 47 | if (dirp->dirent == NULL) { 48 | dirp->dirent = nth_calloc(1, sizeof(struct dirent)); 49 | } else { 50 | if(!FindNextFile(dirp->hFind, &dirp->data)) { 51 | return NULL; 52 | } 53 | } 54 | 55 | memset(dirp->dirent->d_name, 0, sizeof(dirp->dirent->d_name)); 56 | 57 | strncpy( 58 | dirp->dirent->d_name, 59 | dirp->data.cFileName, 60 | sizeof(dirp->dirent->d_name) - 1); 61 | 62 | return dirp->dirent; 63 | } 64 | 65 | void closedir(DIR *dirp) 66 | { 67 | trace_assert(dirp); 68 | 69 | FindClose(dirp->hFind); 70 | if (dirp->dirent) { 71 | free(dirp->dirent); 72 | } 73 | free(dirp); 74 | } 75 | 76 | #endif 77 | 78 | String read_whole_file(Memory *memory, const char *filepath) 79 | { 80 | trace_assert(filepath); 81 | 82 | String result = string(0, NULL); 83 | FILE *f = fopen(filepath, "rb"); 84 | if (!f) goto end; 85 | if (fseek(f, 0, SEEK_END) < 0) goto end; 86 | long m = ftell(f); 87 | if (m < 0) goto end; 88 | if (fseek(f, 0, SEEK_SET) < 0) goto end; 89 | result.count = (size_t) m; 90 | char *buffer = memory_alloc(memory, result.count); 91 | size_t n = fread(buffer, 1, result.count, f); 92 | trace_assert(n == result.count); 93 | result.data = buffer; 94 | 95 | end: 96 | if (f) fclose(f); 97 | return result; 98 | } 99 | -------------------------------------------------------------------------------- /assets/levels/level-02.txt: -------------------------------------------------------------------------------- 1 | 2 2 | 0a2b2a 3 | -21.798889 -133.284241 f2ff80 4 | 12 5 | platform_0 -185.000000 76.041672 519.166626 432.079834 80926d 6 | platform_0 -185.000000 569.579895 519.166626 1322.226074 80926d 7 | platform_0 -3988.729980 647.916809 3819.860840 432.079773 80926d 8 | platform_0 1640.416382 71.875000 519.166626 432.079834 80926d 9 | platform_1 -1307.827393 -862.996338 519.166626 2753.760742 80926d 10 | platform_2 1640.416504 569.579834 519.166626 1322.226074 80926d 11 | platform_0 -789.702515 -182.059967 303.125000 38.541687 80926d 12 | platform_1 -789.702515 -477.268341 303.125000 38.541687 80926d 13 | platform_3 -789.702515 -753.310181 303.125000 38.541687 80926d 14 | platform_4 -789.702515 -1122.060181 303.125000 38.541687 80926d 15 | platform_5 -789.702515 -1534.560303 303.125000 38.541687 80926d 16 | platform_0 -1307.807495 -5332.081543 519.166626 4358.668457 80926d 17 | 0 18 | 2 19 | lava_0 182.002197 589.132202 4415.973633 688.541870 d42b2b 20 | lava_0 -8050.391602 730.803101 4415.973633 688.541870 d42b2b 21 | 0 22 | 17 23 | box_0 120.543991 -72.326294 112.500031 107.291695 9aa034 24 | box_1 714.640747 516.840698 112.500031 107.291695 9aa034 25 | box_3 977.140808 521.007324 112.500031 107.291695 9aa034 26 | box_4 1263.946411 515.729614 112.500000 105.902771 9aa034 27 | box_0 1478.200684 515.248352 112.500000 105.902771 9aa034 28 | box_0 2291.602295 526.957825 112.500000 105.902771 9aa034 29 | box_1 2574.102539 527.791199 112.500000 105.902771 9aa034 30 | box_2 2819.102539 523.624573 112.500000 105.902771 9aa034 31 | box_3 3092.436523 518.624512 112.500000 105.902771 9aa034 32 | box_4 3345.769531 523.624451 112.500000 105.902771 9aa034 33 | box_5 3604.241455 521.679993 112.500000 105.902771 9aa034 34 | box_6 3851.463867 518.902283 112.500000 105.902771 9aa034 35 | box_7 4051.463867 514.735596 112.500000 105.902771 9aa034 36 | box_8 4312.574707 511.957825 112.500000 105.902771 9aa034 37 | box_0 -603.028076 -354.378082 112.500031 107.291695 9aa034 38 | box_1 -609.278137 -667.919678 112.500031 107.291695 9aa034 39 | box_0 -3923.780518 452.429810 112.500031 107.291695 a05034 40 | 0 41 | 0 42 | 3 43 | back_platform_0 -185.010040 442.315765 519.166626 221.875031 80926d 44 | back_platform_0 1640.383789 420.668457 519.166626 221.875031 80926d 45 | back_platform_0 -1307.840332 -1029.460205 519.166626 221.875031 80926d 46 | -------------------------------------------------------------------------------- /src/game/settings.c: -------------------------------------------------------------------------------- 1 | #include "system/stacktrace.h" 2 | #include "settings.h" 3 | #include "config.h" 4 | 5 | Settings create_settings(void) 6 | { 7 | Settings settings = { 8 | .volume_slider = { 9 | .drag = 0, 10 | .value = SOUND_SAMPLES_DEFAULT_VOLUME, 11 | .max_value = 100.0f, 12 | }, 13 | 14 | .volume_slider_scale = { 15 | 0.25f, 0.10f 16 | }, 17 | 18 | .background = { 19 | .base_color = {0.5f, 0.8f, 0.5f, 1.0f} 20 | }, 21 | 22 | .camera_position = { 23 | 0.0f, 0.0f 24 | } 25 | }; 26 | 27 | return settings; 28 | } 29 | 30 | void settings_render(const Settings *settings, const Camera *camera) 31 | { 32 | trace_assert(settings); 33 | trace_assert(camera); 34 | 35 | background_render(&settings->background, camera); 36 | 37 | const Rect viewport = camera_view_port_screen(camera); 38 | 39 | /* CSS volume */ 40 | const Rect position = { 41 | .w = viewport.w * settings->volume_slider_scale.x, 42 | .h = viewport.h * settings->volume_slider_scale.y, 43 | .x = viewport.w - viewport.w * settings->volume_slider_scale.x - 5.0f, 44 | .y = 5.0f, 45 | }; 46 | 47 | /* HTML volume */ 48 | slider_render(&settings->volume_slider, camera, position); 49 | } 50 | 51 | void settings_event(Settings *settings, Camera *camera, const SDL_Event *event) 52 | { 53 | trace_assert(settings); 54 | trace_assert(event); 55 | 56 | const Rect viewport = camera_view_port_screen(camera); 57 | const Rect position = { 58 | .w = viewport.w * settings->volume_slider_scale.x, 59 | .h = viewport.h * settings->volume_slider_scale.y, 60 | .x = viewport.w - viewport.w * settings->volume_slider_scale.x - 5.0f, 61 | .y = 5.0f, 62 | }; 63 | 64 | if (slider_event( 65 | &settings->volume_slider, 66 | event, 67 | position, NULL) < 0) { 68 | return; 69 | } 70 | } 71 | 72 | void settings_update(Settings *settings, Camera *camera, float dt) 73 | { 74 | trace_assert(settings); 75 | trace_assert(camera); 76 | 77 | vec_add(&settings->camera_position, 78 | vec(50.0f * dt, 0.0f)); 79 | camera_center_at(camera, settings->camera_position); 80 | } 81 | -------------------------------------------------------------------------------- /src/game/level/level_editor/layer.c: -------------------------------------------------------------------------------- 1 | #include "game/camera.h" 2 | #include "rect_layer.h" 3 | #include "point_layer.h" 4 | #include "player_layer.h" 5 | #include "label_layer.h" 6 | #include "background_layer.h" 7 | #include "./layer.h" 8 | 9 | int layer_render(LayerPtr layer, const Camera *camera, int active) 10 | { 11 | switch (layer.type) { 12 | case LAYER_RECT: 13 | return rect_layer_render(layer.ptr, camera, active); 14 | 15 | case LAYER_POINT: 16 | return point_layer_render(layer.ptr, camera, active); 17 | 18 | case LAYER_PLAYER: 19 | return player_layer_render(layer.ptr, camera, active); 20 | 21 | case LAYER_BACKGROUND: 22 | return background_layer_render(layer.ptr, camera, active); 23 | 24 | case LAYER_LABEL: 25 | return label_layer_render(layer.ptr, camera, active); 26 | } 27 | 28 | return -1; 29 | } 30 | 31 | int layer_event(LayerPtr layer, 32 | const SDL_Event *event, 33 | const Camera *camera, 34 | UndoHistory *undo_history) 35 | { 36 | switch (layer.type) { 37 | case LAYER_RECT: 38 | return rect_layer_event(layer.ptr, event, camera, undo_history); 39 | 40 | case LAYER_POINT: 41 | return point_layer_event(layer.ptr, event, camera, undo_history); 42 | 43 | case LAYER_PLAYER: 44 | return player_layer_event(layer.ptr, event, camera, undo_history); 45 | 46 | case LAYER_BACKGROUND: 47 | return background_layer_event(layer.ptr, event, camera, undo_history); 48 | 49 | case LAYER_LABEL: 50 | return label_layer_event(layer.ptr, event, camera, undo_history); 51 | } 52 | 53 | return -1; 54 | } 55 | 56 | int layer_dump_stream(LayerPtr layer, 57 | FILE *stream) 58 | { 59 | switch (layer.type) { 60 | case LAYER_RECT: 61 | return rect_layer_dump_stream(layer.ptr, stream); 62 | 63 | case LAYER_POINT: 64 | return point_layer_dump_stream(layer.ptr, stream); 65 | 66 | case LAYER_PLAYER: 67 | return player_layer_dump_stream(layer.ptr, stream); 68 | 69 | case LAYER_BACKGROUND: { 70 | return background_layer_dump_stream(layer.ptr, stream); 71 | } 72 | 73 | case LAYER_LABEL: 74 | return label_layer_dump_stream(layer.ptr, stream); 75 | } 76 | 77 | return -1; 78 | } 79 | -------------------------------------------------------------------------------- /src/game/level/rigid_bodies.h: -------------------------------------------------------------------------------- 1 | #ifndef RIGID_BODIES_H_ 2 | #define RIGID_BODIES_H_ 3 | 4 | #include "math/mat3x3.h" 5 | 6 | typedef struct RigidBodies RigidBodies; 7 | typedef struct Platforms Platforms; 8 | 9 | typedef size_t RigidBodyId; 10 | 11 | RigidBodies *create_rigid_bodies(size_t capacity); 12 | void destroy_rigid_bodies(RigidBodies *rigid_bodies); 13 | 14 | int rigid_bodies_collide(RigidBodies *rigid_bodies, 15 | const Platforms *platforms); 16 | 17 | int rigid_bodies_update(RigidBodies *rigid_bodies, 18 | RigidBodyId id, 19 | float delta_time); 20 | 21 | int rigid_bodies_render(RigidBodies *rigid_bodies, 22 | RigidBodyId id, 23 | Color color, 24 | const Camera *camera); 25 | RigidBodyId rigid_bodies_add(RigidBodies *rigid_bodies, 26 | Rect rect); 27 | void rigid_bodies_remove(RigidBodies *rigid_bodies, 28 | RigidBodyId id); 29 | 30 | Rect rigid_bodies_hitbox(const RigidBodies *rigid_bodies, 31 | RigidBodyId id); 32 | 33 | void rigid_bodies_move(RigidBodies *rigid_bodies, 34 | RigidBodyId id, 35 | Vec2f movement); 36 | 37 | int rigid_bodies_touches_ground(const RigidBodies *rigid_bodies, 38 | RigidBodyId id); 39 | 40 | void rigid_bodies_apply_force(RigidBodies * rigid_bodies, 41 | RigidBodyId id, 42 | Vec2f force); 43 | 44 | void rigid_bodies_apply_omniforce(RigidBodies *rigid_bodies, 45 | Vec2f force); 46 | 47 | void rigid_bodies_transform_velocity(RigidBodies *rigid_bodies, 48 | RigidBodyId id, 49 | mat3x3 trans_mat); 50 | 51 | void rigid_bodies_teleport_to(RigidBodies *rigid_bodies, 52 | RigidBodyId id, 53 | Vec2f position); 54 | 55 | void rigid_bodies_damper(RigidBodies *rigid_bodies, 56 | RigidBodyId id, 57 | Vec2f v); 58 | 59 | void rigid_bodies_disable(RigidBodies *rigid_bodies, 60 | RigidBodyId id, 61 | bool disabled); 62 | 63 | #endif // RIGID_BODIES_H_ 64 | -------------------------------------------------------------------------------- /src/game/level/level_editor/label_layer.h: -------------------------------------------------------------------------------- 1 | #ifndef LABEL_LAYER_H_ 2 | #define LABEL_LAYER_H_ 3 | 4 | #include "layer.h" 5 | #include "color.h" 6 | #include "math/vec.h" 7 | #include "dynarray.h" 8 | #include "game/level/level_editor/color_picker.h" 9 | #include "ui/edit_field.h" 10 | 11 | #define LABELS_SIZE vec(2.0f, 2.0f) 12 | #define LABEL_LAYER_ID_MAX_SIZE 36 13 | #define LABEL_LAYER_TEXT_MAX_SIZE 256 14 | 15 | typedef enum { 16 | LABEL_LAYER_IDLE = 0, 17 | LABEL_LAYER_MOVE, 18 | LABEL_LAYER_EDIT_TEXT, 19 | LABEL_LAYER_EDIT_ID, 20 | LABEL_LAYER_RECOLOR 21 | } LabelLayerState; 22 | 23 | typedef struct { 24 | LabelLayerState state; 25 | Dynarray ids; 26 | Dynarray positions; 27 | Dynarray colors; 28 | Dynarray texts; 29 | int selection; 30 | ColorPicker color_picker; 31 | Vec2f move_anchor; 32 | Edit_field edit_field; 33 | Vec2f inter_position; 34 | Color inter_color; 35 | int id_name_counter; 36 | const char *id_name_prefix; 37 | } LabelLayer; 38 | 39 | LayerPtr label_layer_as_layer(LabelLayer *label_layer); 40 | 41 | // NOTE: create_label_layer and create_label_layer_from_line_stream do 42 | // not own id_name_prefix 43 | LabelLayer *create_label_layer(Memory *memory, const char *id_name_prefix); 44 | void label_layer_load(LabelLayer *label_layer, 45 | Memory *memory, 46 | String *input); 47 | 48 | static inline 49 | void destroy_label_layer(LabelLayer label_layer) 50 | { 51 | free(label_layer.ids.data); 52 | free(label_layer.positions.data); 53 | free(label_layer.colors.data); 54 | free(label_layer.texts.data); 55 | } 56 | 57 | int label_layer_render(const LabelLayer *label_layer, 58 | const Camera *camera, 59 | int active); 60 | int label_layer_event(LabelLayer *label_layer, 61 | const SDL_Event *event, 62 | const Camera *camera, 63 | UndoHistory *undo_history); 64 | 65 | size_t label_layer_count(const LabelLayer *label_layer); 66 | 67 | int label_layer_dump_stream(const LabelLayer *label_layer, FILE *filedump); 68 | 69 | char *label_layer_ids(const LabelLayer *label_layer); 70 | Vec2f *label_layer_positions(const LabelLayer *label_layer); 71 | Color *label_layer_colors(const LabelLayer *label_layer); 72 | char *labels_layer_texts(const LabelLayer *label_layer); 73 | 74 | #endif // LABEL_LAYER_H_ 75 | -------------------------------------------------------------------------------- /src/ui/slider.c: -------------------------------------------------------------------------------- 1 | #include "system/stacktrace.h" 2 | #include "math/rect.h" 3 | #include "game/camera.h" 4 | #include "./slider.h" 5 | 6 | int slider_render(const Slider *slider, const Camera *camera, Rect boundary) 7 | { 8 | trace_assert(slider); 9 | trace_assert(camera); 10 | 11 | const Color core_color = rgba(0.0f, 0.0f, 0.0f, 1.0f); 12 | const float core_height = boundary.h * 0.33f; 13 | const Rect core = rect( 14 | boundary.x, 15 | boundary.y + boundary.h * 0.5f - core_height * 0.5f, 16 | boundary.w, 17 | core_height); 18 | if (camera_fill_rect_screen(camera, core, core_color) < 0) { 19 | return -1; 20 | } 21 | 22 | const float ratio = slider->value / slider->max_value; 23 | const float cursor_width = boundary.w * 0.1f; 24 | const Rect cursor = rect( 25 | boundary.x + ratio * (boundary.w - cursor_width), 26 | boundary.y, 27 | cursor_width, 28 | boundary.h); 29 | const Color cursor_color = rgba(1.0f, 0.0f, 0.0f, 1.0f); 30 | if (camera_fill_rect_screen(camera, cursor, cursor_color) < 0) { 31 | return -1; 32 | } 33 | 34 | return 0; 35 | } 36 | 37 | int slider_event(Slider *slider, const SDL_Event *event, Rect boundary, int *selected) 38 | { 39 | trace_assert(slider); 40 | trace_assert(event); 41 | 42 | if (!slider->drag) { 43 | switch (event->type) { 44 | case SDL_MOUSEBUTTONDOWN: { 45 | Vec2f position = vec((float) event->button.x, (float) event->button.y); 46 | if (rect_contains_point(boundary, position)) { 47 | slider->drag = 1; 48 | if (selected) { 49 | *selected = 1; 50 | } 51 | } 52 | } break; 53 | } 54 | } else { 55 | switch (event->type) { 56 | case SDL_MOUSEBUTTONUP: { 57 | slider->drag = 0; 58 | if (selected) { 59 | *selected = 1; 60 | } 61 | } break; 62 | 63 | case SDL_MOUSEMOTION: { 64 | const float x = fminf(fmaxf((float) event->button.x - boundary.x, 0.0f), (float) boundary.w); 65 | const float ratio = x / (float) boundary.w; 66 | slider->value = ratio * slider->max_value; 67 | if (selected) { 68 | *selected = 1; 69 | } 70 | } break; 71 | } 72 | } 73 | 74 | return 0; 75 | } 76 | -------------------------------------------------------------------------------- /src/game/level/level_editor/rect_layer.h: -------------------------------------------------------------------------------- 1 | #ifndef RECT_LAYER_H_ 2 | #define RECT_LAYER_H_ 3 | 4 | #include "layer.h" 5 | #include "game/level/action.h" 6 | #include "ui/cursor.h" 7 | #include "dynarray.h" 8 | #include "color_picker.h" 9 | #include "ui/edit_field.h" 10 | 11 | typedef struct RectLayer RectLayer; 12 | 13 | typedef enum { 14 | RECT_LAYER_IDLE = 0, 15 | RECT_LAYER_CREATE, 16 | RECT_LAYER_RESIZE, 17 | RECT_LAYER_MOVE, 18 | RECT_LAYER_ID_RENAME, 19 | RECT_LAYER_RECOLOR, 20 | RECT_LAYER_SUBTRACT 21 | } RectLayerState; 22 | 23 | struct RectLayer { 24 | RectLayerState state; 25 | int resize_mask; 26 | Dynarray ids; 27 | Dynarray rects; 28 | Dynarray colors; 29 | Dynarray actions; 30 | ColorPicker color_picker; 31 | Vec2f create_begin; 32 | Vec2f create_end; 33 | int selection; 34 | Vec2f move_anchor; // The mouse offset from the left-top 35 | // corner of the rect during moving it 36 | Edit_field id_edit_field; 37 | Color inter_color; 38 | Rect inter_rect; 39 | int id_name_counter; 40 | const char *id_name_prefix; 41 | Cursor *cursor; 42 | 43 | int snapping_enabled; 44 | int subtract_enabled; 45 | }; 46 | 47 | LayerPtr rect_layer_as_layer(RectLayer *layer); 48 | // NOTE: create_rect_layer and create_rect_layer_from_line_stream does 49 | // not own id_name_prefix 50 | 51 | RectLayer *create_rect_layer(Memory *memory, 52 | const char *id_name_prefix, 53 | Cursor *cursor); 54 | void rect_layer_load(RectLayer *rect_layer, Memory *memory, String *input); 55 | 56 | static inline 57 | void destroy_rect_layer(RectLayer layer) 58 | { 59 | free(layer.ids.data); 60 | free(layer.rects.data); 61 | free(layer.colors.data); 62 | free(layer.actions.data); 63 | } 64 | 65 | 66 | int rect_layer_render(const RectLayer *layer, const Camera *camera, int active); 67 | int rect_layer_event(RectLayer *layer, 68 | const SDL_Event *event, 69 | const Camera *camera, 70 | UndoHistory *undo_history); 71 | 72 | int rect_layer_dump_stream(const RectLayer *layer, FILE *filedump); 73 | 74 | size_t rect_layer_count(const RectLayer *layer); 75 | const Rect *rect_layer_rects(const RectLayer *layer); 76 | const Color *rect_layer_colors(const RectLayer *layer); 77 | const char *rect_layer_ids(const RectLayer *layer); 78 | const Action *rect_layer_actions(const RectLayer *layer); 79 | 80 | #endif // RECT_LAYER_H_ 81 | -------------------------------------------------------------------------------- /src/ui/history.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "system/stacktrace.h" 4 | 5 | #include "history.h" 6 | #include "system/str.h" 7 | #include "system/lt.h" 8 | #include "system/nth_alloc.h" 9 | 10 | struct History 11 | { 12 | Lt *lt; 13 | 14 | char **buffer; 15 | size_t begin; 16 | size_t capacity; 17 | size_t cursor; 18 | }; 19 | 20 | History *create_history(size_t capacity) 21 | { 22 | Lt *lt = create_lt(); 23 | 24 | History *history = PUSH_LT( 25 | lt, 26 | nth_calloc(1, sizeof(History)), 27 | free); 28 | if (history == NULL) { 29 | RETURN_LT(lt, NULL); 30 | } 31 | history->lt = lt; 32 | 33 | history->capacity = capacity; 34 | history->begin = 0; 35 | history->cursor = 0; 36 | 37 | history->buffer = PUSH_LT(lt, nth_calloc(capacity, sizeof(char*)), free); 38 | if (history->buffer == NULL) { 39 | RETURN_LT(lt, NULL); 40 | } 41 | 42 | return history; 43 | } 44 | 45 | void destroy_history(History *history) 46 | { 47 | trace_assert(history); 48 | 49 | for (size_t i = 0; i < history->capacity; ++i) { 50 | if (history->buffer[i] != NULL) { 51 | free(history->buffer[i]); 52 | } 53 | } 54 | 55 | RETURN_LT0(history->lt); 56 | } 57 | 58 | int history_push(History *history, const char *command) 59 | { 60 | trace_assert(history); 61 | trace_assert(command); 62 | 63 | const size_t next_begin = (history->begin + 1) % history->capacity; 64 | 65 | if (history->buffer[history->begin] != NULL) { 66 | free(history->buffer[history->begin]); 67 | } 68 | 69 | history->buffer[history->begin] = string_duplicate(command, NULL); 70 | 71 | if (history->buffer[history->begin] == NULL) { 72 | return -1; 73 | } 74 | 75 | history->begin = next_begin; 76 | history->cursor = next_begin; 77 | 78 | return 0; 79 | } 80 | 81 | const char *history_current(History *history) 82 | { 83 | trace_assert(history); 84 | return history->buffer[history->cursor]; 85 | } 86 | 87 | void history_prev(History *history) 88 | { 89 | trace_assert(history); 90 | if (history->cursor == 0) { 91 | history->cursor = history->capacity - 1; 92 | } else { 93 | history->cursor--; 94 | } 95 | } 96 | 97 | void history_next(History *history) 98 | { 99 | trace_assert(history); 100 | history->cursor = (history->cursor + 1) % history->capacity; 101 | } 102 | -------------------------------------------------------------------------------- /assets/levels/level-01.txt: -------------------------------------------------------------------------------- 1 | 2 2 | fffda5 3 | 28.578053 -456.515228 ff8080 4 | 23 5 | rect5109 -656.113831 191.771439 2545.462402 1595.238037 483737 6 | rect5109-3 -711.376221 -1262.170776 548.613525 1633.974854 483737 7 | rect5109-3-6 252.748734 -1237.278076 455.318237 1293.704102 483737 8 | rect5109-3-6-7 484.915161 14.061144 1079.945312 42.364796 483737 9 | rect5109-3-5 1718.383789 -161.473328 996.557251 613.246460 483737 10 | rect4561 1660.827271 101.435120 65.011909 18.520830 483737 11 | rect5109-3-6-6 1120.158691 -317.589874 907.196228 204.469376 483737 12 | rect5109-3-5-3 2561.680176 -321.881714 1466.951660 647.503601 483737 13 | rect4561-2-3 -938.562134 917.253967 314.320221 18.520832 483737 14 | rect5109-3-5-3-67 2561.680176 -556.348694 30.657700 139.623245 483737 15 | rect4561-2-1-5 2504.264648 -563.055054 88.073227 13.412691 483737 16 | rect5109-3-5-3-67-3 2561.680176 -563.055054 529.352417 29.707256 483737 17 | rect5109-3-5-3-67-36 2561.680176 -926.773499 30.657700 378.572083 483737 18 | rect4561-2-1-5-5 2504.264648 -723.590454 88.073227 13.412692 483737 19 | rect5109-3-5-3-67-1 2561.680176 -338.512665 30.657700 161.296707 483737 20 | rect5109-3-5-3-67-2 3799.997070 -946.805359 3102.693359 1310.393311 483737 21 | rect5109-3-5-3-67-1-7 3769.859863 -339.033478 84.632698 161.296707 483737 22 | rect5109-3-6-3 616.461792 -75.527267 306.705963 108.837868 483737 23 | rect5109-3-6-3-6 616.461792 -164.159546 201.936310 105.096077 483737 24 | rect5109-3-6-3-6-7 935.011536 -317.589874 201.936310 78.925652 483737 25 | rect5109-3-6-3-3 2244.476074 -293.351532 88.026321 88.026321 483737 26 | rect5109-3-5-3-67-3-3 3270.644531 -726.635742 573.905273 26.509399 483737 27 | platform_1 4670.228516 -1969.799561 34.585449 908.541504 483737 28 | 2 29 | goal1 976.558533 -27.401052 000000 30 | goal2 2531.730469 -590.851074 000000 31 | 1 32 | lava4590 2000.628052 -259.364716 595.262695 132.921005 d35f5f 33 | 1 34 | backrect5109-3-5-3-67-3-6 2561.680176 -1966.485474 2142.863770 1647.980103 83647d 35 | 3 36 | box-1 3495.909668 -383.481232 64.009789 61.599514 a02c2c 37 | box-1-3 1761.908203 -523.207092 64.009789 61.599514 a02c2c 38 | box_0 3367.700684 -811.861694 64.009789 61.599514 a02c2c 39 | 3 40 | label_wasd -41.109612 217.178177 fffda5 41 | WASD to move 42 | label_space_to_jump 1733.607056 146.904999 fffda5 43 | SPACE to Jump 44 | label_double_jump 950.930298 -279.062897 fffda5 45 | Double Jump 46 | 5 47 | script_goal_1 659.885925 -301.853394 904.875000 333.048859 deaa87 2 goal1 48 | script_show_goal_2 1249.832397 -862.224976 323.924408 544.635071 deaa87 49 | script_hide_label_double_jump 1033.862549 -862.224976 215.969864 569.733276 d35f5f 1 label_double_jump 50 | hide_wasd_script 260.383636 24.900909 125.925934 209.259262 a1e764 1 label_wasd 51 | hide_space_to_jump 1449.166626 -122.291687 104.166626 165.000015 1ae5a0 1 label_space_to_jump 52 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | build-linux-tcc: 6 | runs-on: ubuntu-18.04 7 | steps: 8 | - uses: actions/checkout@v1 9 | - name: install dependencies 10 | run: | 11 | sudo apt-get update 12 | sudo apt-get install -qq libsdl2-dev tcc 13 | - name: build nothing 14 | run: | 15 | ./build-posix.sh 16 | env: 17 | CC: tcc 18 | CFLAGS: -DSDL_DISABLE_IMMINTRIN_H -L/usr/lib/x86_64-linux-gnu/pulseaudio 19 | NOTHING_CI: on 20 | 21 | build-linux-gcc: 22 | runs-on: ubuntu-18.04 23 | steps: 24 | - uses: actions/checkout@v1 25 | - name: install dependencies 26 | run: | 27 | sudo apt-get update 28 | sudo apt-get install -qq libsdl2-dev 29 | - name: build nothing 30 | run: | 31 | ./build-posix.sh 32 | env: 33 | CC: gcc 34 | NOTHING_CI: on 35 | 36 | build-linux-clang: 37 | runs-on: ubuntu-18.04 38 | steps: 39 | - uses: actions/checkout@v1 40 | - name: install dependencies 41 | run: | 42 | sudo apt-get update 43 | sudo apt-get install -qq libsdl2-dev 44 | - name: build nothing 45 | run: | 46 | ./build-posix.sh 47 | env: 48 | CC: clang 49 | NOTHING_CI: on 50 | 51 | build-macos: 52 | runs-on: macOS-latest 53 | steps: 54 | - uses: actions/checkout@v1 55 | - name: install dependencies 56 | run: brew install sdl2 pkg-config 57 | - name: build nothing 58 | run: | 59 | ./build-posix.sh 60 | env: 61 | CC: clang 62 | NOTHING_CI: on 63 | 64 | # TODO(#1177): build-windows-msvc on GitHub Actions does not support SCU 65 | build-windows-msvc: 66 | runs-on: windows-2019 67 | steps: 68 | - uses: actions/checkout@v1 69 | # this runs vcvarsall for us, so we get the MSVC toolchain in PATH. 70 | - uses: seanmiddleditch/gha-setup-vsdevenv@master 71 | - name: download sdl2 72 | run: | 73 | curl -fsSL -o SDL2-devel-2.0.9-VC.zip https://www.libsdl.org/release/SDL2-devel-2.0.9-VC.zip 74 | 7z x SDL2-devel-2.0.9-VC.zip 75 | mv SDL2-2.0.9 SDL2 76 | - name: build nothing 77 | run: | 78 | mkdir build 79 | cd build 80 | cmake .. -DNOTHING_CI=ON 81 | cmake --build . 82 | 83 | # TODO(#1243): build-windows-mingw is broken 84 | # TODO(#1178): %z related warnings on build-windows-mingw GitHub Action 85 | # build-windows-mingw: 86 | # runs-on: windows-2019 87 | # steps: 88 | # - uses: actions/checkout@v1 89 | # # this gives us msys. 90 | # - uses: numworks/setup-msys2@v1 91 | # - name: install dependencies 92 | # run: msys2do pacman -S --noconfirm mingw-w64-x86_64-gcc mingw64/mingw-w64-x86_64-SDL2 make mingw-w64-x86_64-pkg-config 93 | # - name: build nothing 94 | # run: | 95 | # msys2do ./build-posix.sh 96 | # env: 97 | # CC: gcc 98 | -------------------------------------------------------------------------------- /src/game/level/level_editor/background_layer.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "system/stacktrace.h" 5 | #include "game/camera.h" 6 | #include "math/rect.h" 7 | #include "color.h" 8 | #include "background_layer.h" 9 | #include "undo_history.h" 10 | 11 | BackgroundLayer create_background_layer(Color color) 12 | { 13 | BackgroundLayer layer = { 14 | .color_picker = create_color_picker_from_rgba(color), 15 | .prev_color = color 16 | }; 17 | return layer; 18 | } 19 | 20 | BackgroundLayer chop_background_layer(String *input) 21 | { 22 | String line = trim(chop_by_delim(input, '\n')); 23 | return create_background_layer(hexs(line)); 24 | } 25 | 26 | int background_layer_render(BackgroundLayer *layer, 27 | const Camera *camera, 28 | int active) 29 | { 30 | trace_assert(layer); 31 | trace_assert(camera); 32 | 33 | if (active) { 34 | return color_picker_render( 35 | &layer->color_picker, 36 | camera); 37 | } 38 | 39 | return 0; 40 | } 41 | 42 | typedef struct { 43 | BackgroundLayer *layer; 44 | Color color; 45 | } BackgroundUndoContext; 46 | 47 | static 48 | void background_undo_color(void *context, size_t context_size) 49 | { 50 | trace_assert(context); 51 | trace_assert(sizeof(BackgroundUndoContext) == context_size); 52 | 53 | BackgroundUndoContext *undo_context = context; 54 | BackgroundLayer *background_layer = undo_context->layer; 55 | 56 | background_layer->color_picker = create_color_picker_from_rgba(undo_context->color); 57 | } 58 | 59 | int background_layer_event(BackgroundLayer *layer, 60 | const SDL_Event *event, 61 | const Camera *camera, 62 | UndoHistory *undo_history) 63 | { 64 | trace_assert(layer); 65 | trace_assert(event); 66 | trace_assert(camera); 67 | trace_assert(undo_history); 68 | 69 | int selected = 0; 70 | 71 | if (color_picker_event( 72 | &layer->color_picker, 73 | event, 74 | camera, 75 | &selected) < 0) { 76 | return -1; 77 | } 78 | 79 | if (selected && !color_picker_drag(&layer->color_picker)) { 80 | BackgroundUndoContext context = { 81 | .layer = layer, 82 | .color = layer->prev_color 83 | }; 84 | 85 | undo_history_push( 86 | undo_history, 87 | background_undo_color, 88 | &context, sizeof(context)); 89 | layer->prev_color = color_picker_rgba(&layer->color_picker); 90 | } 91 | 92 | return 0; 93 | } 94 | 95 | int background_layer_dump_stream(BackgroundLayer *layer, 96 | FILE *stream) 97 | { 98 | trace_assert(layer); 99 | trace_assert(stream); 100 | 101 | color_hex_to_stream( 102 | color_picker_rgba(&layer->color_picker), 103 | stream); 104 | 105 | return fprintf(stream, "\n"); 106 | } 107 | -------------------------------------------------------------------------------- /src/system/s.h: -------------------------------------------------------------------------------- 1 | #ifndef S_H_ 2 | #define S_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include "system/stacktrace.h" 8 | #include "system/memory.h" 9 | 10 | typedef struct { 11 | size_t count; 12 | const char *data; 13 | } String; 14 | 15 | #define STRING_LIT(literal) string(sizeof(literal) - 1, literal) 16 | 17 | static inline 18 | String string(size_t count, const char *data) 19 | { 20 | String result = { 21 | .count = count, 22 | .data = data 23 | }; 24 | 25 | return result; 26 | } 27 | 28 | static inline 29 | String string_nt(const char *data) 30 | { 31 | String result = { 32 | .count = strlen(data), 33 | .data = data 34 | }; 35 | 36 | return result; 37 | } 38 | 39 | static inline 40 | String chop_by_delim(String *input, char delim) 41 | { 42 | trace_assert(input); 43 | 44 | size_t i = 0; 45 | while (i < input->count && input->data[i] != delim) 46 | i++; 47 | 48 | if (i < input->count) { 49 | String result = string(i, input->data); 50 | input->data += i + 1; 51 | input->count -= i - 1; 52 | return result; 53 | } 54 | 55 | String result = *input; 56 | input->data += i; 57 | input->count -= i; 58 | return result; 59 | } 60 | 61 | static inline 62 | int string_equal(String a, String b) 63 | { 64 | if (a.count != b.count) return 0; 65 | return memcmp(a.data, b.data, a.count) == 0; 66 | } 67 | 68 | static inline 69 | String trim_begin(String input) 70 | { 71 | while (input.count > 0 && isspace(*input.data)) { 72 | input.data += 1; 73 | input.count -= 1; 74 | } 75 | 76 | return input; 77 | } 78 | 79 | static inline 80 | String trim_end(String input) 81 | { 82 | while (input.count > 0 && isspace(*(input.data + input.count - 1))) { 83 | input.count -= 1; 84 | } 85 | 86 | return input; 87 | } 88 | 89 | static inline 90 | String trim(String input) 91 | { 92 | return trim_end(trim_begin(input)); 93 | } 94 | 95 | static inline 96 | String chop_word(String *input) 97 | { 98 | trace_assert(input); 99 | 100 | *input = trim_begin(*input); 101 | 102 | size_t i = 0; 103 | while (i < input->count && !isspace(input->data[i])) 104 | i++; 105 | 106 | String result = string(i, input->data); 107 | input->data += i; 108 | input->count -= i; 109 | return result; 110 | } 111 | 112 | static inline 113 | char *string_to_cstr(Memory *memory, String s) 114 | { 115 | trace_assert(memory); 116 | 117 | char *result = memory_alloc(memory, s.count + 1); 118 | memset(result, 0, s.count + 1); 119 | memcpy(result, s.data, s.count); 120 | return result; 121 | } 122 | 123 | static inline 124 | char *strdup_to_memory(Memory *memory, const char *s) 125 | { 126 | trace_assert(memory); 127 | trace_assert(s); 128 | 129 | const size_t n = strlen(s) + 1; 130 | char *d = memory_alloc(memory, n); 131 | memcpy(d, s, n); 132 | return d; 133 | } 134 | 135 | #endif // S_H_ 136 | -------------------------------------------------------------------------------- /src/math/mat3x3.h: -------------------------------------------------------------------------------- 1 | #ifndef MAT3X3_H_ 2 | #define MAT3X3_H_ 3 | 4 | #include "vec.h" 5 | 6 | typedef struct mat3x3 { 7 | float M[3][3]; 8 | } mat3x3; 9 | 10 | static inline 11 | mat3x3 make_mat3x3(float a11, float a12, float a13, 12 | float a21, float a22, float a23, 13 | float a31, float a32, float a33) 14 | { 15 | const mat3x3 m = { 16 | .M = { 17 | {a11, a12, a13}, 18 | {a21, a22, a23}, 19 | {a31, a32, a33} 20 | } 21 | }; 22 | 23 | return m; 24 | } 25 | 26 | static inline 27 | mat3x3 mat3x3_product(mat3x3 m1, mat3x3 m2) 28 | { 29 | mat3x3 result; 30 | 31 | for (int i = 0; i < 3; ++i) { 32 | for (int j = 0; j < 3; ++j) { 33 | result.M[i][j] = 0; 34 | for (int k = 0; k < 3; ++k) { 35 | result.M[i][j] += m1.M[i][k] * m2.M[k][j]; 36 | } 37 | } 38 | } 39 | 40 | return result; 41 | } 42 | 43 | static inline 44 | mat3x3 mat3x3_product2(mat3x3 m1, mat3x3 m2, mat3x3 m3) 45 | { 46 | return mat3x3_product(m1, mat3x3_product(m2, m3)); 47 | } 48 | 49 | static inline 50 | mat3x3 trans_mat(float x, float y) 51 | { 52 | const mat3x3 m = { 53 | .M = { 54 | {1.0f, 0.0f, x}, 55 | {0.0f, 1.0f, y}, 56 | {0.0f, 0.0f, 1.0f} 57 | } 58 | }; 59 | 60 | return m; 61 | } 62 | 63 | static inline 64 | mat3x3 trans_mat_vec(Vec2f v) 65 | { 66 | return trans_mat(v.x, v.y); 67 | } 68 | 69 | static inline 70 | mat3x3 rot_mat(float angle) 71 | { 72 | const mat3x3 m = { 73 | .M = { 74 | {cosf(angle), -sinf(angle), 0.0f}, 75 | {sinf(angle), cosf(angle), 0.0f}, 76 | {0.0f, 0.0f, 1.0f} 77 | } 78 | }; 79 | 80 | return m; 81 | } 82 | 83 | static inline 84 | mat3x3 scale_mat(float factor) 85 | { 86 | const mat3x3 m = { 87 | .M = { 88 | {factor, 0.0f, 0.0f}, 89 | {0.0f, factor, 0.0f}, 90 | {0.0f, 0.0f, 1.0f} 91 | } 92 | }; 93 | 94 | return m; 95 | } 96 | 97 | static inline 98 | Vec2f point_mat3x3_product(Vec2f p, mat3x3 m) 99 | { 100 | /* Convert p to Homogeneous coordinates */ 101 | const float homo_p[3] = {p.x, p.y, 1}; 102 | 103 | /* Transform p with matrix m */ 104 | const float trans_p[3] = { 105 | homo_p[0] * m.M[0][0] + homo_p[1] * m.M[0][1] + homo_p[2] * m.M[0][2], 106 | homo_p[0] * m.M[1][0] + homo_p[1] * m.M[1][1] + homo_p[2] * m.M[1][2], 107 | homo_p[0] * m.M[2][0] + homo_p[1] * m.M[2][1] + homo_p[2] * m.M[2][2] 108 | }; 109 | 110 | /* Convert p back to Cartesian coordinates */ 111 | const Vec2f result_p = { 112 | .x = trans_p[0] / trans_p[2], 113 | .y = trans_p[1] / trans_p[2] 114 | }; 115 | 116 | return result_p; 117 | } 118 | 119 | static inline 120 | Triangle triangle_mat3x3_product(Triangle t, mat3x3 m) 121 | { 122 | Triangle t1 = { 123 | .p1 = point_mat3x3_product(t.p1, m), 124 | .p2 = point_mat3x3_product(t.p2, m), 125 | .p3 = point_mat3x3_product(t.p3, m) 126 | }; 127 | 128 | return t1; 129 | } 130 | 131 | #endif // MAT3X3_H_ 132 | -------------------------------------------------------------------------------- /src/game/level/background.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "game/level/background.h" 4 | #include "math/rand.h" 5 | #include "math/rect.h" 6 | #include "system/lt.h" 7 | #include "system/nth_alloc.h" 8 | #include "system/log.h" 9 | #include "system/stacktrace.h" 10 | #include "config.h" 11 | 12 | static inline 13 | Vec2i chunk_of_point(Vec2f p) 14 | { 15 | return vec2i( 16 | (int) floorf(p.x / BACKGROUND_CHUNK_WIDTH), 17 | (int) floorf(p.y / BACKGROUND_CHUNK_HEIGHT)); 18 | } 19 | 20 | int render_chunk(const Camera *camera, 21 | Vec2i chunk, 22 | Color color); 23 | 24 | int background_render(const Background *background, 25 | const Camera *camera0) 26 | { 27 | trace_assert(background); 28 | trace_assert(camera0); 29 | 30 | Camera camera = *camera0; 31 | 32 | if (camera_clear_background( 33 | &camera, 34 | background->base_color) < 0) { 35 | return -1; 36 | } 37 | 38 | camera.scale = 1.0f - BACKGROUND_LAYERS_STEP * BACKGROUND_LAYERS_COUNT; 39 | 40 | for (int l = 0; l < BACKGROUND_LAYERS_COUNT; ++l) { 41 | const Rect view_port = camera_view_port(&camera); 42 | const Vec2f position = vec(view_port.x, view_port.y); 43 | 44 | Vec2i min = chunk_of_point(position); 45 | Vec2i max = chunk_of_point(vec_sum(position, vec(view_port.w, view_port.h))); 46 | 47 | for (int x = min.x - 1; x <= max.x; ++x) { 48 | for (int y = min.y - 1; y <= max.y; ++y) { 49 | if (render_chunk( 50 | &camera, 51 | vec2i(x, y), 52 | color_darker(background->base_color, 0.05f * (float)(l + 1))) < 0) { 53 | return -1; 54 | } 55 | } 56 | } 57 | 58 | camera.scale += BACKGROUND_LAYERS_STEP; 59 | } 60 | 61 | return 0; 62 | } 63 | 64 | /* Private Function */ 65 | 66 | int render_chunk(const Camera *camera, 67 | Vec2i chunk, 68 | Color color) 69 | { 70 | trace_assert(camera); 71 | 72 | if (camera->debug_mode) { 73 | return 0; 74 | } 75 | 76 | srand((unsigned int)(roundf((float)chunk.x + (float)chunk.y + camera->scale * 10.0f))); 77 | 78 | for (size_t i = 0; i < BACKGROUND_TURDS_PER_CHUNK; ++i) { 79 | const float rect_x = rand_float_range(0.0f, BACKGROUND_CHUNK_WIDTH); 80 | const float rect_y = rand_float_range(0.0f, BACKGROUND_CHUNK_HEIGHT); 81 | 82 | const float rect_w = rand_float_range(0.0f, BACKGROUND_CHUNK_WIDTH * 0.5f); 83 | const float rect_h = rand_float_range(rect_w * 0.5f, rect_w * 1.5f); 84 | 85 | if (camera_fill_rect( 86 | camera, 87 | rect((float) chunk.x * BACKGROUND_CHUNK_WIDTH + rect_x, 88 | (float) chunk.y * BACKGROUND_CHUNK_HEIGHT + rect_y, 89 | rect_w, 90 | rect_h), 91 | color) < 0) { 92 | return -1; 93 | } 94 | } 95 | 96 | return 0; 97 | } 98 | 99 | Color background_base_color(const Background *background) 100 | { 101 | return background->base_color; 102 | } 103 | -------------------------------------------------------------------------------- /src/math/vec.h: -------------------------------------------------------------------------------- 1 | #ifndef POINT_H_ 2 | #define POINT_H_ 3 | 4 | #include 5 | #include "math/pi.h" 6 | 7 | typedef struct { 8 | float x, y; 9 | } Vec2f; 10 | 11 | static inline 12 | Vec2f vec(float x, float y) 13 | { 14 | Vec2f result = { 15 | .x = x, 16 | .y = y 17 | }; 18 | return result; 19 | } 20 | 21 | static inline 22 | Vec2f vec_mult(Vec2f v1, Vec2f v2) 23 | { 24 | return vec(v1.x * v2.x, v1.y * v2.y); 25 | } 26 | 27 | static inline 28 | Vec2f vec_scala_mult(Vec2f v, float scalar) 29 | { 30 | Vec2f result = { 31 | .x = v.x * scalar, 32 | .y = v.y * scalar 33 | }; 34 | return result; 35 | } 36 | 37 | static inline 38 | Vec2f vec_from_polar(float arg, float mag) 39 | { 40 | return vec_scala_mult( 41 | vec(cosf(arg), sinf(arg)), 42 | mag); 43 | } 44 | 45 | static inline 46 | Vec2f vec_from_ps(Vec2f p1, Vec2f p2) 47 | { 48 | Vec2f result = { 49 | .x = p2.x - p1.x, 50 | .y = p2.y - p1.y 51 | }; 52 | return result; 53 | } 54 | 55 | static inline 56 | float vec_arg(Vec2f v) 57 | { 58 | return atan2f(v.y, v.x); 59 | } 60 | 61 | static inline 62 | float vec_mag(Vec2f v) 63 | { 64 | return sqrtf(v.x * v.x + v.y * v.y); 65 | } 66 | 67 | static inline 68 | Vec2f vec_sum(Vec2f v1, Vec2f v2) 69 | { 70 | Vec2f result = { 71 | .x = v1.x + v2.x, 72 | .y = v1.y + v2.y 73 | }; 74 | return result; 75 | } 76 | 77 | static inline 78 | Vec2f vec_sub(Vec2f v1, Vec2f v2) 79 | { 80 | Vec2f result = { 81 | .x = v1.x - v2.x, 82 | .y = v1.y - v2.y 83 | }; 84 | return result; 85 | } 86 | 87 | static inline 88 | Vec2f vec_neg(Vec2f v) 89 | { 90 | Vec2f result = { 91 | .x = -v.x, 92 | .y = -v.y 93 | }; 94 | 95 | return result; 96 | } 97 | 98 | static inline 99 | float vec_length(Vec2f v) 100 | { 101 | return sqrtf(v.x * v.x + v.y * v.y); 102 | } 103 | 104 | static inline 105 | void vec_add(Vec2f *v1, Vec2f v2) 106 | { 107 | v1->x += v2.x; 108 | v1->y += v2.y; 109 | } 110 | 111 | static inline 112 | Vec2f vec_entry_mult(Vec2f v1, Vec2f v2) 113 | { 114 | Vec2f result = { 115 | .x = v1.x * v2.x, 116 | .y = v1.y * v2.y 117 | }; 118 | 119 | return result; 120 | } 121 | 122 | static inline 123 | Vec2f vec_entry_div(Vec2f v1, Vec2f v2) 124 | { 125 | Vec2f result = { 126 | .x = v1.x / v2.x, 127 | .y = v1.y / v2.y 128 | }; 129 | 130 | return result; 131 | } 132 | 133 | static inline 134 | float rad_to_deg(float a) 135 | { 136 | return 180 / PI * a; 137 | } 138 | 139 | static inline 140 | Vec2f vec_norm(Vec2f v) 141 | { 142 | // TODO(#657): math/point/vec_norm: using vec_length is too expensive 143 | // It involves multiplication and sqrt. We can just check if its components are close to 0.0f. 144 | 145 | const float l = vec_length(v); 146 | 147 | if (l < 1e-6) { 148 | return vec(0.0f, 0.0f); 149 | } 150 | 151 | return vec(v.x / l, v.y / l); 152 | } 153 | 154 | static inline 155 | float vec_sqr_norm(Vec2f v) 156 | { 157 | return v.x * v.x + v.y * v.y; 158 | } 159 | 160 | #define vec_scale vec_scala_mult 161 | 162 | typedef struct { 163 | int x, y; 164 | } Vec2i; 165 | 166 | static inline 167 | Vec2i vec2i(int x, int y) 168 | { 169 | Vec2i resoolt = { 170 | .x = x, 171 | .y = y 172 | }; 173 | return resoolt; 174 | } 175 | 176 | #endif // POINT_H_ 177 | -------------------------------------------------------------------------------- /src/game/level/lava.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "system/stacktrace.h" 3 | #include 4 | 5 | #include "color.h" 6 | #include "game/level/lava/wavy_rect.h" 7 | #include "lava.h" 8 | #include "math/rect.h" 9 | #include "system/lt.h" 10 | #include "system/nth_alloc.h" 11 | #include "system/log.h" 12 | #include "game/level/level_editor/rect_layer.h" 13 | 14 | #define LAVA_BOINGNESS 2500.0f 15 | 16 | struct Lava { 17 | Lt *lt; 18 | size_t rects_count; 19 | Wavy_rect **rects; 20 | }; 21 | 22 | Lava *create_lava_from_rect_layer(const RectLayer *rect_layer) 23 | { 24 | Lt *lt = create_lt(); 25 | 26 | Lava *lava = PUSH_LT(lt, nth_calloc(1, sizeof(Lava)), free); 27 | if (lava == NULL) { 28 | RETURN_LT(lt, NULL); 29 | } 30 | lava->lt = lt; 31 | 32 | lava->rects_count = rect_layer_count(rect_layer); 33 | lava->rects = PUSH_LT(lt, nth_calloc(lava->rects_count, sizeof(Wavy_rect*)), free); 34 | if (lava->rects == NULL) { 35 | RETURN_LT(lt, NULL); 36 | } 37 | 38 | const Rect *rects = rect_layer_rects(rect_layer); 39 | const Color *colors = rect_layer_colors(rect_layer); 40 | for (size_t i = 0; i < lava->rects_count; ++i) { 41 | lava->rects[i] = PUSH_LT(lt, create_wavy_rect(rects[i], colors[i]), destroy_wavy_rect); 42 | if (lava->rects[i] == NULL) { 43 | RETURN_LT(lt, NULL); 44 | } 45 | } 46 | 47 | return lava; 48 | } 49 | 50 | void destroy_lava(Lava *lava) 51 | { 52 | trace_assert(lava); 53 | RETURN_LT0(lava->lt); 54 | } 55 | 56 | /* TODO(#449): lava does not render its id in debug mode */ 57 | int lava_render(const Lava *lava, 58 | const Camera *camera) 59 | { 60 | trace_assert(lava); 61 | trace_assert(camera); 62 | 63 | for (size_t i = 0; i < lava->rects_count; ++i) { 64 | if (wavy_rect_render(lava->rects[i], camera) < 0) { 65 | return -1; 66 | } 67 | } 68 | 69 | return 0; 70 | } 71 | 72 | int lava_update(Lava *lava, float delta_time) 73 | { 74 | trace_assert(lava); 75 | 76 | for (size_t i = 0; i < lava->rects_count; ++i) { 77 | if (wavy_rect_update(lava->rects[i], delta_time) < 0) { 78 | return -1; 79 | } 80 | } 81 | 82 | return 0; 83 | } 84 | 85 | bool lava_overlaps_rect(const Lava *lava, 86 | Rect rect) 87 | { 88 | trace_assert(lava); 89 | 90 | for (size_t i = 0; i < lava->rects_count; ++i) { 91 | if (rects_overlap(wavy_rect_hitbox(lava->rects[i]), rect)) { 92 | return true; 93 | } 94 | } 95 | 96 | return 0; 97 | } 98 | 99 | void lava_float_rigid_body(Lava *lava, RigidBodies *rigid_bodies, RigidBodyId id) 100 | { 101 | trace_assert(lava); 102 | 103 | const Rect object_hitbox = rigid_bodies_hitbox(rigid_bodies, id); 104 | for (size_t i = 0; i < lava->rects_count; ++i) { 105 | const Rect lava_hitbox = wavy_rect_hitbox(lava->rects[i]); 106 | if (rects_overlap(object_hitbox, lava_hitbox)) { 107 | const Rect overlap_area = rects_overlap_area(object_hitbox, lava_hitbox); 108 | const float k = overlap_area.w * overlap_area.h / (object_hitbox.w * object_hitbox.h); 109 | rigid_bodies_apply_force( 110 | rigid_bodies, 111 | id, 112 | vec(0.0f, -k * LAVA_BOINGNESS)); 113 | rigid_bodies_damper(rigid_bodies, id, vec(0.0f, -0.9f)); 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/game/camera.h: -------------------------------------------------------------------------------- 1 | #ifndef CAMERA_H_ 2 | #define CAMERA_H_ 3 | 4 | #include 5 | 6 | #include "color.h" 7 | #include "game/sprite_font.h" 8 | #include "math/vec.h" 9 | #include "math/rect.h" 10 | #include "math/triangle.h" 11 | #include "config.h" 12 | 13 | typedef struct { 14 | bool debug_mode; 15 | bool blackwhite_mode; 16 | Vec2f position; 17 | float scale; 18 | SDL_Renderer *renderer; 19 | Sprite_font font; 20 | Vec2f effective_scale; 21 | } Camera; 22 | 23 | Camera create_camera(SDL_Renderer *renderer, 24 | Sprite_font font); 25 | 26 | int camera_clear_background(const Camera *camera, 27 | Color color); 28 | 29 | int camera_fill_rect(const Camera *camera, 30 | Rect rect, 31 | Color color); 32 | 33 | int camera_draw_rect(const Camera *camera, 34 | Rect rect, 35 | Color color); 36 | 37 | int camera_draw_rect_screen(const Camera *camera, 38 | Rect rect, 39 | Color color); 40 | 41 | int camera_draw_thicc_rect_screen(const Camera *camera, 42 | Rect rect, 43 | Color color, 44 | float thiccness); 45 | 46 | int camera_draw_line(const Camera *camera, 47 | Vec2f begin, Vec2f end, 48 | Color color); 49 | 50 | int camera_draw_triangle(Camera *camera, 51 | Triangle t, 52 | Color color); 53 | 54 | int camera_fill_triangle(const Camera *camera, 55 | Triangle t, 56 | Color color); 57 | 58 | int camera_render_text(const Camera *camera, 59 | const char *text, 60 | Vec2f size, 61 | Color color, 62 | Vec2f position); 63 | 64 | void camera_render_text_screen(const Camera *camera, 65 | const char *text, 66 | Vec2f size, 67 | Color color, 68 | Vec2f position); 69 | 70 | int camera_render_debug_text(const Camera *camera, 71 | const char *text, 72 | Vec2f position); 73 | 74 | int camera_render_debug_rect(const Camera *camera, 75 | Rect rect, 76 | Color color); 77 | 78 | void camera_center_at(Camera *camera, Vec2f position); 79 | void camera_scale(Camera *came, float scale); 80 | 81 | void camera_toggle_debug_mode(Camera *camera); 82 | void camera_disable_debug_mode(Camera *camera); 83 | 84 | int camera_is_point_visible(const Camera *camera, Vec2f p); 85 | int camera_is_text_visible(const Camera *camera, 86 | Vec2f size, 87 | Vec2f position, 88 | const char *text); 89 | 90 | Rect camera_view_port(const Camera *camera); 91 | 92 | Rect camera_view_port_screen(const Camera *camera); 93 | 94 | Vec2f camera_map_screen(const Camera *camera, 95 | Sint32 x, Sint32 y); 96 | 97 | Vec2f camera_point(const Camera *camera, const Vec2f p); 98 | Rect camera_rect(const Camera *camera, const Rect rect); 99 | 100 | int camera_fill_rect_screen(const Camera *camera, 101 | Rect rect, 102 | Color color); 103 | 104 | 105 | Vec2f effective_scale(const SDL_Rect *view_port); 106 | 107 | #endif // CAMERA_H_ 108 | -------------------------------------------------------------------------------- /src/game/sprite_font.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "system/stacktrace.h" 3 | #include 4 | #include 5 | 6 | #include "math/rect.h" 7 | #include "sdl/renderer.h" 8 | #include "sprite_font.h" 9 | #include "system/lt.h" 10 | #include "system/nth_alloc.h" 11 | #include "system/log.h" 12 | 13 | #define FONT_ROW_SIZE 18 14 | 15 | struct Sprite_font 16 | { 17 | SDL_Texture *texture; 18 | }; 19 | 20 | static inline 21 | void *scp(void *ptr) 22 | { 23 | if (ptr == NULL) { 24 | log_fail("SDL error: %s\n", SDL_GetError()); 25 | trace_assert(0 && "SDL error"); 26 | } 27 | 28 | return ptr; 29 | } 30 | 31 | static inline 32 | int scc(int code) 33 | { 34 | if (code < 0) { 35 | log_fail("SDL error: %s\n", SDL_GetError()); 36 | trace_assert(0 && "SDL error"); 37 | } 38 | 39 | return code; 40 | } 41 | 42 | SDL_Texture *load_bmp_font_texture(SDL_Renderer *renderer, 43 | const char *bmp_file_path) 44 | { 45 | trace_assert(renderer); 46 | trace_assert(bmp_file_path); 47 | 48 | SDL_Surface *surface = scp(SDL_LoadBMP(bmp_file_path)); 49 | scc(SDL_SetColorKey( 50 | surface, 51 | SDL_TRUE, 52 | SDL_MapRGB(surface->format, 53 | 0, 0, 0))); 54 | 55 | SDL_Texture *result = 56 | scp(SDL_CreateTextureFromSurface(renderer, surface)); 57 | 58 | SDL_FreeSurface(surface); 59 | 60 | return result; 61 | } 62 | 63 | static SDL_Rect sprite_font_char_rect(const Sprite_font *sprite_font, char x) 64 | { 65 | trace_assert(sprite_font); 66 | 67 | if (32 <= x && x <= 126) { 68 | const SDL_Rect rect = { 69 | .x = ((x - 32) % FONT_ROW_SIZE) * FONT_CHAR_WIDTH, 70 | .y = ((x - 32) / FONT_ROW_SIZE) * FONT_CHAR_HEIGHT, 71 | .w = FONT_CHAR_WIDTH, 72 | .h = FONT_CHAR_HEIGHT 73 | }; 74 | return rect; 75 | } else { 76 | return sprite_font_char_rect(sprite_font, '?'); 77 | } 78 | } 79 | 80 | void sprite_font_render_text(const Sprite_font *sprite_font, 81 | SDL_Renderer *renderer, 82 | Vec2f position, 83 | Vec2f size, 84 | Color color, 85 | const char *text) 86 | { 87 | trace_assert(sprite_font); 88 | trace_assert(renderer); 89 | trace_assert(text); 90 | 91 | const SDL_Color sdl_color = color_for_sdl(color); 92 | 93 | scc(SDL_SetTextureColorMod(sprite_font->texture, 94 | sdl_color.r, 95 | sdl_color.g, 96 | sdl_color.b)); 97 | scc(SDL_SetTextureAlphaMod(sprite_font->texture, 98 | sdl_color.a)); 99 | 100 | const size_t text_size = strlen(text); 101 | for (size_t i = 0, col = 0, row = 0; i < text_size; ++i) { 102 | if (text[i] == '\n'){ 103 | col = 0; 104 | row++; 105 | continue; 106 | } 107 | const SDL_Rect char_rect = sprite_font_char_rect(sprite_font, text[i]); 108 | const SDL_Rect dest_rect = rect_for_sdl( 109 | rect( 110 | position.x + (float) FONT_CHAR_WIDTH * (float) col * size.x, 111 | position.y + (float) FONT_CHAR_HEIGHT * (float) row * size.y, 112 | (float) char_rect.w * size.x, 113 | (float) char_rect.h * size.y)); 114 | scc(SDL_RenderCopy(renderer, sprite_font->texture, &char_rect, &dest_rect)); 115 | col++; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/ui/console_log.c: -------------------------------------------------------------------------------- 1 | #include "system/stacktrace.h" 2 | #include 3 | #include 4 | 5 | #include "color.h" 6 | #include "game/sprite_font.h" 7 | #include "console_log.h" 8 | #include "math/vec.h" 9 | #include "system/str.h" 10 | #include "system/lt.h" 11 | #include "system/nth_alloc.h" 12 | 13 | struct Console_Log 14 | { 15 | Lt *lt; 16 | 17 | Vec2f font_size; 18 | 19 | Color *colors; 20 | char **buffer; 21 | size_t cursor; 22 | size_t capacity; 23 | }; 24 | 25 | Console_Log *create_console_log(Vec2f font_size, 26 | size_t capacity) 27 | { 28 | Lt *lt = create_lt(); 29 | 30 | Console_Log *console_log = PUSH_LT(lt, nth_calloc(1, sizeof(Console_Log)), free); 31 | if (console_log == NULL) { 32 | RETURN_LT(lt, NULL); 33 | } 34 | console_log->lt = lt; 35 | console_log->font_size = font_size; 36 | console_log->capacity = capacity; 37 | 38 | console_log->buffer = PUSH_LT(lt, nth_calloc(capacity, sizeof(char*)), free); 39 | if (console_log->buffer == NULL) { 40 | RETURN_LT(lt, NULL); 41 | } 42 | 43 | console_log->colors = PUSH_LT(lt, nth_calloc(capacity, sizeof(Color)), free); 44 | if (console_log->colors == NULL) { 45 | RETURN_LT(lt, NULL); 46 | } 47 | 48 | console_log->cursor = 0; 49 | 50 | return console_log; 51 | } 52 | 53 | void destroy_console_log(Console_Log *console_log) 54 | { 55 | trace_assert(console_log); 56 | for (size_t i = 0; i < console_log->capacity; ++i) { 57 | if (console_log->buffer[i]) { 58 | free(console_log->buffer[i]); 59 | } 60 | } 61 | RETURN_LT0(console_log->lt); 62 | } 63 | 64 | void console_log_render(const Console_Log *console_log, 65 | const Camera *camera, 66 | Vec2f position) 67 | { 68 | trace_assert(console_log); 69 | trace_assert(camera); 70 | 71 | for (size_t i = 0; i < console_log->capacity; ++i) { 72 | const size_t j = (i + console_log->cursor) % console_log->capacity; 73 | if (console_log->buffer[j]) { 74 | camera_render_text_screen( 75 | camera, 76 | console_log->buffer[j], 77 | console_log->font_size, 78 | console_log->colors[j], 79 | vec_sum(position, 80 | vec(0.0f, FONT_CHAR_HEIGHT * console_log->font_size.y * (float) i))); 81 | } 82 | } 83 | } 84 | 85 | int console_log_push_line(Console_Log *console_log, 86 | const char *line, 87 | const char *line_end, 88 | Color color) 89 | { 90 | trace_assert(console_log); 91 | trace_assert(line); 92 | 93 | const size_t next_cursor = (console_log->cursor + 1) % console_log->capacity; 94 | 95 | if (console_log->buffer[console_log->cursor] != NULL) { 96 | free(console_log->buffer[console_log->cursor]); 97 | } 98 | 99 | console_log->buffer[console_log->cursor] = string_duplicate(line, line_end); 100 | console_log->colors[console_log->cursor] = color; 101 | 102 | if (console_log->buffer[console_log->cursor] == NULL) { 103 | return -1; 104 | } 105 | 106 | console_log->cursor = next_cursor; 107 | 108 | return 0; 109 | } 110 | 111 | void console_log_clear(Console_Log *console_log) 112 | { 113 | trace_assert(console_log); 114 | console_log->cursor = 0; 115 | for (size_t i = 0; i < console_log->capacity; ++i) { 116 | if (console_log->buffer[i]) { 117 | free(console_log->buffer[i]); 118 | console_log->buffer[i] = 0; 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/system/lt.h: -------------------------------------------------------------------------------- 1 | #ifndef LT_H_ 2 | #define LT_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include "system/stacktrace.h" 8 | 9 | #define LT_INITIAL_CAPACITY 8 10 | 11 | typedef void (*Dtor)(void*); 12 | 13 | typedef struct { 14 | void *res; 15 | Dtor dtor; 16 | } Slot; 17 | 18 | typedef struct { 19 | Slot *slots_end; 20 | size_t capacity; 21 | Slot *slots; 22 | } Lt; 23 | 24 | static inline Lt *create_lt(void) 25 | { 26 | return calloc(1, sizeof(Lt)); 27 | } 28 | 29 | static inline void destroy_lt(Lt *lt) 30 | { 31 | trace_assert(lt); 32 | 33 | if (lt->slots_end) { 34 | for (Slot *p = lt->slots_end - 1; p >= lt->slots; --p) { 35 | if (p->res) { 36 | p->dtor(p->res); 37 | } 38 | } 39 | } 40 | 41 | if (lt->slots) { 42 | free(lt->slots); 43 | } 44 | 45 | free(lt); 46 | } 47 | 48 | #define PUSH_LT(lt, res, dtor) \ 49 | lt_push(lt, (void*)res, (Dtor)dtor) 50 | 51 | static inline void *lt_push(Lt *lt, void *res, Dtor dtor) 52 | { 53 | trace_assert(lt); 54 | size_t size = (size_t)(lt->slots_end - lt->slots); 55 | if (size >= lt->capacity) { 56 | if (lt->capacity == 0) { 57 | lt->capacity = LT_INITIAL_CAPACITY; 58 | lt->slots = calloc(LT_INITIAL_CAPACITY, sizeof(Slot)); 59 | lt->slots_end = lt->slots; 60 | } else { 61 | lt->capacity *= 2; 62 | lt->slots = realloc(lt->slots, lt->capacity * sizeof(Slot)); 63 | lt->slots_end = lt->slots + size; 64 | } 65 | } 66 | 67 | lt->slots_end->res = res; 68 | lt->slots_end->dtor = dtor; 69 | lt->slots_end++; 70 | 71 | return res; 72 | } 73 | 74 | #define RETURN_LT(lt, result) \ 75 | return (destroy_lt(lt), result) 76 | 77 | #define RETURN_LT0(lt) \ 78 | do { \ 79 | destroy_lt(lt); \ 80 | return; \ 81 | } while (0) 82 | 83 | #define RESET_LT(lt, old_res, new_res) \ 84 | lt_reset(lt, (void*) old_res, (void*) new_res) 85 | 86 | static inline void *lt_reset(Lt *lt, void *old_res, void *new_res) 87 | { 88 | trace_assert(lt); 89 | trace_assert(old_res != new_res); 90 | 91 | for(Slot *p = lt->slots; p < lt->slots_end; ++p) { 92 | if (p->res == old_res) { 93 | p->dtor(old_res); 94 | p->res = new_res; 95 | return new_res; 96 | } 97 | } 98 | 99 | trace_assert(0 && "Resource was not found"); 100 | return NULL; 101 | } 102 | 103 | 104 | #define REPLACE_LT(lt, old_res, new_res) \ 105 | lt_replace(lt, (void *)old_res, (void*)new_res) 106 | 107 | static inline void *lt_replace(Lt *lt, void *old_res, void *new_res) 108 | { 109 | trace_assert(lt); 110 | for(Slot *p = lt->slots; p < lt->slots_end; ++p) { 111 | if (p->res == old_res) { 112 | p->res = new_res; 113 | return new_res; 114 | } 115 | } 116 | 117 | trace_assert(0 && "Resource was not found"); 118 | return NULL; 119 | } 120 | 121 | #define RELEASE_LT(lt, res) \ 122 | lt_release(lt, (void*)res) 123 | 124 | static inline void *lt_release(Lt *lt, void *res) 125 | { 126 | trace_assert(lt); 127 | for(Slot *p = lt->slots; p < lt->slots_end; ++p) { 128 | if (p->res == res) { 129 | memmove(p, p + 1, (size_t)(lt->slots_end - p - 1) * sizeof(Slot)); 130 | lt->slots_end--; 131 | return res; 132 | } 133 | } 134 | 135 | trace_assert(0 && "Resource was not found"); 136 | return NULL; 137 | } 138 | 139 | #endif // LT_H_ 140 | -------------------------------------------------------------------------------- /src/game/level/explosion.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "system/stacktrace.h" 3 | 4 | #include "explosion.h" 5 | #include "math/rand.h" 6 | #include "math/mat3x3.h" 7 | #include "system/lt.h" 8 | #include "system/nth_alloc.h" 9 | 10 | #define EXPLOSION_PIECE_COUNT 20 11 | #define EXPLOSION_PIECE_SIZE 20.0f 12 | 13 | typedef struct Piece { 14 | Vec2f position; 15 | float angle; 16 | float angle_velocity; 17 | Vec2f direction; 18 | Triangle body; 19 | } Piece; 20 | 21 | struct Explosion 22 | { 23 | Lt *lt; 24 | 25 | Vec2f position; 26 | Color color; 27 | float duration; 28 | float time_passed; 29 | Piece *pieces; 30 | }; 31 | 32 | Explosion *create_explosion(Color color, 33 | float duration) 34 | { 35 | Lt *lt = create_lt(); 36 | 37 | Explosion *explosion = PUSH_LT(lt, nth_calloc(1, sizeof(Explosion)), free); 38 | if (explosion == NULL) { 39 | RETURN_LT(lt, NULL); 40 | } 41 | 42 | explosion->lt = lt; 43 | explosion->position = vec(0.0f, 0.0f); 44 | explosion->color = color; 45 | explosion->duration = duration; 46 | explosion->time_passed = duration; 47 | 48 | explosion->pieces = PUSH_LT(lt, nth_calloc(1, sizeof(Piece) * EXPLOSION_PIECE_COUNT), free); 49 | if (explosion->pieces == NULL) { 50 | RETURN_LT(lt, NULL); 51 | } 52 | 53 | return explosion; 54 | } 55 | 56 | void destroy_explosion(Explosion *explosion) 57 | { 58 | trace_assert(explosion); 59 | RETURN_LT0(explosion->lt); 60 | } 61 | 62 | int explosion_render(const Explosion *explosion, 63 | const Camera *camera) 64 | { 65 | trace_assert(explosion); 66 | trace_assert(camera); 67 | 68 | for (size_t i = 0; i < EXPLOSION_PIECE_COUNT; ++i) { 69 | Color color = explosion->color; 70 | color.a = fminf(1.0f, 4.0f - (float) explosion->time_passed / (float) explosion->duration * 4.0f); 71 | 72 | if (camera_fill_triangle( 73 | camera, 74 | triangle_mat3x3_product( 75 | explosion->pieces[i].body, 76 | mat3x3_product( 77 | trans_mat(explosion->pieces[i].position.x, 78 | explosion->pieces[i].position.y), 79 | rot_mat(explosion->pieces[i].angle))), 80 | color) < 0) { 81 | return -1; 82 | } 83 | } 84 | 85 | return 0; 86 | } 87 | 88 | int explosion_update(Explosion *explosion, 89 | float delta_time) 90 | { 91 | trace_assert(explosion); 92 | trace_assert(delta_time > 0.0f); 93 | 94 | if (explosion_is_done(explosion)) { 95 | return 0; 96 | } 97 | 98 | explosion->time_passed = explosion->time_passed + delta_time; 99 | 100 | for (size_t i = 0; i < EXPLOSION_PIECE_COUNT; ++i) { 101 | vec_add( 102 | &explosion->pieces[i].position, 103 | vec_scala_mult( 104 | explosion->pieces[i].direction, 105 | delta_time)); 106 | explosion->pieces[i].angle = fmodf( 107 | explosion->pieces[i].angle + explosion->pieces[i].angle_velocity * delta_time, 108 | 2.0f * PI); 109 | } 110 | 111 | return 0; 112 | } 113 | 114 | int explosion_is_done(const Explosion *explosion) 115 | { 116 | trace_assert(explosion); 117 | return explosion->time_passed >= explosion->duration; 118 | } 119 | 120 | void explosion_start(Explosion *explosion, 121 | Vec2f position) 122 | { 123 | explosion->position = position; 124 | explosion->time_passed = 0; 125 | 126 | for (size_t i = 0; i < EXPLOSION_PIECE_COUNT; ++i) { 127 | explosion->pieces[i].position = explosion->position; 128 | explosion->pieces[i].angle = rand_float(2 * PI); 129 | explosion->pieces[i].angle_velocity = rand_float(8.0f); 130 | explosion->pieces[i].body = random_triangle(EXPLOSION_PIECE_SIZE); 131 | explosion->pieces[i].direction = vec_from_polar( 132 | rand_float_range(-PI, 0.0f), 133 | rand_float_range(100.0f, 300.0f)); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/math/rect.h: -------------------------------------------------------------------------------- 1 | #ifndef RECT_H_ 2 | #define RECT_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "math/vec.h" 9 | #include "system/stacktrace.h" 10 | 11 | typedef enum Rect_side { 12 | RECT_SIDE_TOP = 0, 13 | RECT_SIDE_LEFT, 14 | RECT_SIDE_BOTTOM, 15 | RECT_SIDE_RIGHT, 16 | 17 | RECT_SIDE_N 18 | } Rect_side; 19 | 20 | typedef struct Rect { 21 | float x, y, w, h; 22 | } Rect; 23 | 24 | typedef struct Line { 25 | Vec2f p1; 26 | Vec2f p2; 27 | } Line; 28 | 29 | Rect horizontal_thicc_line(float x1, float x2, float y, float thiccness); 30 | Rect vertical_thicc_line(float y1, float y2, float x, float thiccness); 31 | 32 | Rect rect(float x, float y, float w, float h); 33 | Rect rect_from_vecs(Vec2f position, Vec2f size); 34 | Rect rect_from_points(Vec2f p1, Vec2f p2); 35 | Rect rect_from_sdl(const SDL_Rect *rect); 36 | 37 | Rect rects_overlap_area(Rect rect1, Rect rect2); 38 | 39 | static inline 40 | Rect rect_boundary2(Rect rect1, Rect rect2) 41 | { 42 | return rect_from_points( 43 | vec( 44 | fminf(rect1.x, rect2.x), 45 | fminf(rect1.y, rect2.y)), 46 | vec( 47 | fmaxf(rect1.x + rect1.w, rect2.x + rect2.w), 48 | fmaxf(rect1.y + rect1.h, rect2.y + rect2.h))); 49 | } 50 | 51 | static inline Vec2f rect_position(Rect rect) 52 | { 53 | return vec(rect.x, rect.y); 54 | } 55 | 56 | static inline Vec2f rect_position2(Rect rect) 57 | { 58 | return vec(rect.x + rect.w, rect.y + rect.h); 59 | } 60 | 61 | static inline Rect rect_pad(Rect rect, float d) 62 | { 63 | rect.x -= d; 64 | rect.y -= d; 65 | rect.w += d * 2.0f; 66 | rect.h += d * 2.0f; 67 | return rect; 68 | } 69 | 70 | int rect_contains_point(Rect rect, Vec2f p); 71 | 72 | int rects_overlap(Rect rect1, Rect rect2); 73 | 74 | void rect_object_impact(Rect object, 75 | Rect obstacle, 76 | int *sides); 77 | 78 | Line rect_side(Rect rect, Rect_side side); 79 | 80 | Rect rect_from_point(Vec2f p, float w, float h); 81 | 82 | float line_length(Line line); 83 | 84 | SDL_Rect rect_for_sdl(Rect rect); 85 | 86 | Vec2f rect_center(Rect rect); 87 | 88 | Vec2f rect_snap(Rect pivot, Rect *rect); 89 | Vec2f rect_impulse(Rect *r1, Rect *r2); 90 | 91 | static inline 92 | float rect_side_distance(Rect rect, Vec2f point, Rect_side side) 93 | { 94 | switch (side) { 95 | case RECT_SIDE_LEFT: { 96 | return fabsf(rect.x - point.x); 97 | } break; 98 | 99 | case RECT_SIDE_RIGHT: { 100 | return fabsf((rect.x + rect.w) - point.x); 101 | } break; 102 | 103 | case RECT_SIDE_TOP: { 104 | return fabsf(rect.y - point.y); 105 | } break; 106 | 107 | case RECT_SIDE_BOTTOM: { 108 | return fabsf((rect.y + rect.h) - point.y); 109 | } break; 110 | 111 | case RECT_SIDE_N: { 112 | trace_assert(0 && "Incorrect rect side"); 113 | } break; 114 | } 115 | 116 | return 0; 117 | } 118 | 119 | static inline 120 | int segment_overlap(Vec2f a, Vec2f b) 121 | { 122 | if (a.x > a.y) { 123 | float t = a.x; 124 | a.x = a.y; 125 | a.y = t; 126 | } 127 | 128 | if (b.x > b.y) { 129 | float t = b.x; 130 | b.x = b.y; 131 | b.y = t; 132 | } 133 | 134 | return a.y >= b.x && b.y >= a.x; 135 | } 136 | 137 | static inline 138 | int snap_var(float *x, // the value we are snapping 139 | float y, // the target we are snapping x to 140 | float xo, // x offset 141 | float yo, // y offset 142 | float st) // snap threshold 143 | { 144 | if (fabsf((*x + xo) - (y + yo)) < st) { 145 | *x = y + yo - xo; 146 | return true; 147 | } 148 | return false; 149 | } 150 | 151 | static inline 152 | int snap_var2seg(float *x, float y, 153 | float xo, float yo, 154 | float st) 155 | { 156 | // note: do not use || because we do *not* want short-circuiting, so use |. 157 | return snap_var(x, y, xo, 0, st) | snap_var(x, y, xo, yo, st); 158 | } 159 | 160 | static inline 161 | void snap_seg2seg(float *x, float y, float xo, float yo, float st) 162 | { 163 | snap_var(x, y, 0, 0, st); 164 | snap_var(x, y, 0, yo, st); 165 | snap_var(x, y, xo, 0, st); 166 | snap_var(x, y, xo, yo, st); 167 | } 168 | 169 | #endif // RECT_H_ 170 | -------------------------------------------------------------------------------- /src/game/level/level_editor/color_picker.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "game/level/boxes.h" 5 | #include "system/stacktrace.h" 6 | #include "system/log.h" 7 | #include "game/camera.h" 8 | #include "color_picker.h" 9 | #include "color.h" 10 | #include "undo_history.h" 11 | 12 | #define COLOR_SLIDER_HEIGHT 60.0f 13 | #define COLOR_PICKER_WIDTH 300.0f 14 | #define COLOR_PICKER_HEIGHT (COLOR_SLIDER_HEIGHT * COLOR_SLIDER_N) 15 | #define COLOR_PICKER_REFERENCE 1920.0f 16 | #define COLOR_PICKER_HW_RATIO (COLOR_PICKER_HEIGHT/ COLOR_PICKER_WIDTH) 17 | #define COLOR_PICKER_WR_RATIO (COLOR_PICKER_WIDTH / COLOR_PICKER_REFERENCE) 18 | 19 | const char *slider_labels[COLOR_SLIDER_N] = { 20 | "Hue", 21 | "Saturation", 22 | "Lightness" 23 | }; 24 | 25 | ColorPicker create_color_picker_from_rgba(Color color) 26 | { 27 | Color color_hsla = rgba_to_hsla(color); 28 | ColorPicker color_picker = { 29 | .sliders = { 30 | {0, color_hsla.r, 360.0f}, 31 | {0, color_hsla.g, 1.0f}, 32 | {0, color_hsla.b, 1.0f} 33 | } 34 | }; 35 | return color_picker; 36 | } 37 | 38 | int color_picker_render(const ColorPicker *color_picker, 39 | const Camera *camera) 40 | { 41 | trace_assert(color_picker); 42 | trace_assert(camera); 43 | 44 | const Rect viewport = camera_view_port_screen(camera); 45 | const Rect boundary = rect( 46 | 0.0f, 0.0f, 47 | viewport.w * COLOR_PICKER_WR_RATIO, 48 | viewport.w * COLOR_PICKER_WR_RATIO * COLOR_PICKER_HW_RATIO); 49 | 50 | const float color_slider_height = 51 | boundary.h / (COLOR_SLIDER_N + 1.0f); 52 | 53 | if (camera_fill_rect_screen( 54 | camera, 55 | rect(boundary.x, boundary.y, 56 | boundary.w, color_slider_height), 57 | color_picker_rgba(color_picker)) < 0) { 58 | return -1; 59 | } 60 | 61 | for (ColorPickerSlider index = 0; index < COLOR_SLIDER_N; ++index) { 62 | const Rect slider_rect = 63 | rect(boundary.x, 64 | boundary.y + color_slider_height * (float) (index + 1), 65 | boundary.w, color_slider_height); 66 | const float font_scale = boundary.w / COLOR_PICKER_WIDTH; 67 | const Vec2f label_size = vec(2.5f * font_scale, 2.5f * font_scale); 68 | 69 | if (slider_render( 70 | &color_picker->sliders[index], 71 | camera, 72 | slider_rect) < 0) { 73 | return -1; 74 | } 75 | 76 | camera_render_text_screen( 77 | camera, 78 | slider_labels[index], 79 | label_size, 80 | COLOR_BLACK, 81 | vec(slider_rect.x + boundary.w, 82 | slider_rect.y + color_slider_height * 0.5f - label_size.y * (float) FONT_CHAR_HEIGHT * 0.5f)); 83 | } 84 | 85 | return 0; 86 | } 87 | 88 | // TODO(#932): the `selected` event propagation control is cumbersome 89 | int color_picker_event(ColorPicker *color_picker, 90 | const SDL_Event *event, 91 | const Camera *camera, 92 | int *selected_out) 93 | { 94 | trace_assert(color_picker); 95 | trace_assert(event); 96 | trace_assert(camera); 97 | 98 | int selected = 0; 99 | 100 | const Rect viewport = camera_view_port_screen(camera); 101 | const Rect boundary = rect( 102 | 0.0f, 0.0f, 103 | viewport.w * COLOR_PICKER_WR_RATIO, 104 | viewport.w * COLOR_PICKER_WR_RATIO * COLOR_PICKER_HW_RATIO); 105 | 106 | const float color_slider_height = 107 | boundary.h / (COLOR_SLIDER_N + 1.0f); 108 | 109 | for (ColorPickerSlider index = 0; 110 | !selected && index < COLOR_SLIDER_N; 111 | ++index) { 112 | if (slider_event( 113 | &color_picker->sliders[index], 114 | event, 115 | rect(boundary.x, 116 | boundary.y + color_slider_height * (float) (index + 1), 117 | boundary.w, color_slider_height), 118 | &selected) < 0) { 119 | return -1; 120 | } 121 | } 122 | 123 | if (selected_out) { 124 | *selected_out = selected; 125 | } 126 | 127 | return 0; 128 | } 129 | 130 | Color color_picker_rgba(const ColorPicker *color_picker) 131 | { 132 | return hsla( 133 | color_picker->sliders[COLOR_SLIDER_HUE].value, 134 | color_picker->sliders[COLOR_SLIDER_SAT].value, 135 | color_picker->sliders[COLOR_SLIDER_LIT].value, 136 | 1.0f); 137 | } 138 | -------------------------------------------------------------------------------- /src/game/level/platforms.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "system/stacktrace.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "platforms.h" 9 | #include "system/lt.h" 10 | #include "system/nth_alloc.h" 11 | #include "system/log.h" 12 | #include "game/level/level_editor/rect_layer.h" 13 | #include "math/extrema.h" 14 | 15 | struct Platforms { 16 | Lt *lt; 17 | 18 | Rect *rects; 19 | Color *colors; 20 | size_t rects_size; 21 | }; 22 | 23 | Platforms *create_platforms_from_rect_layer(const RectLayer *layer) 24 | { 25 | trace_assert(layer); 26 | 27 | Lt *lt = create_lt(); 28 | 29 | Platforms *platforms = PUSH_LT( 30 | lt, 31 | nth_calloc(1, sizeof(Platforms)), 32 | free); 33 | if (platforms == NULL) { 34 | RETURN_LT(lt, NULL); 35 | } 36 | platforms->lt = lt; 37 | 38 | platforms->rects_size = rect_layer_count(layer); 39 | 40 | platforms->rects = PUSH_LT(lt, nth_calloc(1, sizeof(Rect) * platforms->rects_size), free); 41 | if (platforms->rects == NULL) { 42 | RETURN_LT(lt, NULL); 43 | } 44 | memcpy(platforms->rects, rect_layer_rects(layer), sizeof(Rect) * platforms->rects_size); 45 | 46 | 47 | platforms->colors = PUSH_LT(lt, nth_calloc(1, sizeof(Color) * platforms->rects_size), free); 48 | if (platforms->colors == NULL) { 49 | RETURN_LT(lt, NULL); 50 | } 51 | memcpy(platforms->colors, rect_layer_colors(layer), sizeof(Color) * platforms->rects_size); 52 | 53 | return platforms; 54 | } 55 | 56 | void destroy_platforms(Platforms *platforms) 57 | { 58 | trace_assert(platforms); 59 | RETURN_LT0(platforms->lt); 60 | } 61 | 62 | int platforms_render(const Platforms *platforms, 63 | const Camera *camera) 64 | { 65 | for (size_t i = 0; i < platforms->rects_size; ++i) { 66 | Rect platform_rect = platforms->rects[i]; 67 | if (camera_fill_rect( 68 | camera, 69 | platform_rect, 70 | platforms->colors[i]) < 0) { 71 | return -1; 72 | } 73 | 74 | char debug_text[256]; 75 | snprintf(debug_text, 256, 76 | "id:%zd\n" 77 | "x:%.2f\n" 78 | "y:%.2f\n" 79 | "w:%.2f\n" 80 | "h:%.2f\n", 81 | i, platform_rect.x, platform_rect.y, platform_rect.w, platform_rect.h); 82 | 83 | Vec2f text_pos = (Vec2f){.x = platform_rect.x, .y = platform_rect.y}; 84 | Rect text_rect = sprite_font_boundary_box(text_pos, vec(2.0f, 2.0f), debug_text); 85 | 86 | Rect world_viewport = camera_view_port(camera); 87 | Rect viewport = camera_view_port_screen(camera); 88 | 89 | if (rects_overlap( 90 | camera_rect( 91 | camera, 92 | platform_rect), 93 | viewport) && 94 | camera_is_point_visible( 95 | camera, 96 | text_pos) == false) { 97 | if (platform_rect.w > text_rect.w){ 98 | text_pos.x = fmaxf(fminf(world_viewport.x, platform_rect.x + platform_rect.w - text_rect.w), 99 | platform_rect.x); 100 | } 101 | if (platform_rect.h > text_rect.h){ 102 | text_pos.y = fmaxf(fminf(world_viewport.y, platform_rect.y + platform_rect.h - text_rect.h), 103 | platform_rect.y); 104 | } 105 | } 106 | 107 | if (camera_render_debug_text( 108 | camera, 109 | debug_text, 110 | text_pos) < 0) { 111 | return -1; 112 | } 113 | } 114 | 115 | return 0; 116 | } 117 | 118 | void platforms_touches_rect_sides(const Platforms *platforms, 119 | Rect object, 120 | int sides[RECT_SIDE_N]) 121 | { 122 | trace_assert(platforms); 123 | 124 | for (size_t i = 0; i < platforms->rects_size; ++i) { 125 | rect_object_impact(object, platforms->rects[i], sides); 126 | } 127 | } 128 | 129 | Vec2f platforms_snap_rect(const Platforms *platforms, 130 | Rect *object) 131 | { 132 | trace_assert(platforms); 133 | 134 | Vec2f result = vec(1.0f, 1.0f); 135 | for (size_t i = 0; i < platforms->rects_size; ++i) { 136 | if (rects_overlap(platforms->rects[i], *object)) { 137 | // TODO(#1161): can we reuse the Level Editor snapping mechanism in physics snapping 138 | result = vec_entry_mult(result, rect_snap(platforms->rects[i], object)); 139 | } 140 | } 141 | 142 | return result; 143 | } 144 | -------------------------------------------------------------------------------- /src/dynarray.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "system/stacktrace.h" 6 | #include "system/lt.h" 7 | #include "system/nth_alloc.h" 8 | #include "dynarray.h" 9 | 10 | void *dynarray_pointer_at(const Dynarray *dynarray, size_t index) 11 | { 12 | trace_assert(index < dynarray->count); 13 | return (uint8_t *)dynarray->data + index * dynarray->element_size; 14 | } 15 | 16 | void dynarray_clear(Dynarray *dynarray) 17 | { 18 | trace_assert(dynarray); 19 | dynarray->count = 0; 20 | } 21 | 22 | int dynarray_push(Dynarray *dynarray, const void *element) 23 | { 24 | trace_assert(dynarray); 25 | trace_assert(element); 26 | trace_assert(dynarray->count < DYNARRAY_CAPACITY); 27 | 28 | memcpy( 29 | (char*) dynarray->data + dynarray->count * dynarray->element_size, 30 | element, 31 | dynarray->element_size); 32 | 33 | dynarray->count++; 34 | 35 | return 0; 36 | } 37 | 38 | bool dynarray_contains(const Dynarray *dynarray, 39 | const void *element) 40 | { 41 | trace_assert(dynarray); 42 | trace_assert(element); 43 | 44 | for (size_t i = 0; i < dynarray->count; ++i) { 45 | if (memcmp((const char*)dynarray->data + i * dynarray->element_size, 46 | (const char*)element, 47 | dynarray->element_size) == 0) { 48 | return true; 49 | } 50 | } 51 | 52 | return false; 53 | } 54 | 55 | void dynarray_delete_at(Dynarray *dynarray, size_t index) 56 | { 57 | trace_assert(dynarray); 58 | trace_assert(index < dynarray->count); 59 | memmove( 60 | (uint8_t *) dynarray->data + index * dynarray->element_size, 61 | (uint8_t *) dynarray->data + (index + 1) * dynarray->element_size, 62 | dynarray->element_size * (dynarray->count - index - 1)); 63 | dynarray->count--; 64 | } 65 | 66 | void dynarray_insert_before(Dynarray *dynarray, size_t index, void *element) 67 | { 68 | trace_assert(dynarray); 69 | trace_assert(dynarray->count < DYNARRAY_CAPACITY); 70 | trace_assert(element); 71 | trace_assert(index <= dynarray->count); 72 | 73 | if (index == dynarray->count) { 74 | dynarray_push(dynarray, element); 75 | return; 76 | } 77 | 78 | memmove( 79 | (uint8_t*) dynarray->data + (index + 1) * dynarray->element_size, 80 | (uint8_t*) dynarray->data + index * dynarray->element_size, 81 | dynarray->element_size * (dynarray->count - index)); 82 | 83 | memcpy( 84 | (uint8_t*) dynarray->data + index * dynarray->element_size, 85 | element, 86 | dynarray->element_size); 87 | 88 | dynarray->count++; 89 | } 90 | 91 | int dynarray_push_empty(Dynarray *dynarray) 92 | { 93 | trace_assert(dynarray); 94 | trace_assert(dynarray->count < DYNARRAY_CAPACITY); 95 | 96 | memset( 97 | (char*) dynarray->data + dynarray->count * dynarray->element_size, 98 | 0, 99 | dynarray->element_size); 100 | 101 | dynarray->count++; 102 | 103 | return 0; 104 | } 105 | 106 | // TODO(#980): dynarray_push and dynarray_push_empty have duplicate codez 107 | 108 | void dynarray_pop(Dynarray *dynarray, void *element) 109 | { 110 | trace_assert(dynarray); 111 | trace_assert(dynarray->count > 0); 112 | 113 | dynarray->count--; 114 | 115 | if (element) { 116 | memcpy( 117 | element, 118 | (uint8_t*) dynarray->data + dynarray->count * dynarray->element_size, 119 | dynarray->element_size); 120 | } 121 | } 122 | 123 | void dynarray_replace_at(Dynarray *dynarray, size_t index, void *element) 124 | { 125 | trace_assert(dynarray); 126 | trace_assert(element); 127 | trace_assert(index < dynarray->count); 128 | 129 | memcpy( 130 | (uint8_t*) dynarray->data + index * dynarray->element_size, 131 | element, 132 | dynarray->element_size); 133 | } 134 | 135 | void dynarray_copy_to(Dynarray *dynarray, void *dest, size_t index) 136 | { 137 | trace_assert(dynarray); 138 | trace_assert(dest); 139 | trace_assert(index < dynarray->count); 140 | 141 | memcpy(dest, (uint8_t*) dynarray->data + index * dynarray->element_size, dynarray->element_size); 142 | } 143 | 144 | void dynarray_swap(Dynarray *dynarray, size_t i, size_t j) 145 | { 146 | trace_assert(dynarray); 147 | trace_assert(i < dynarray->count); 148 | trace_assert(j < dynarray->count); 149 | 150 | if (i == j) return; 151 | 152 | char *a = dynarray_pointer_at(dynarray, i); 153 | char *b = dynarray_pointer_at(dynarray, j); 154 | 155 | for (size_t k = 0; k < dynarray->element_size; ++k) { 156 | char t = a[k]; 157 | a[k] = b[k]; 158 | b[k] = t; 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/game/level/boxes.c: -------------------------------------------------------------------------------- 1 | #include "system/stacktrace.h" 2 | 3 | #include "dynarray.h" 4 | #include "game/level/boxes.h" 5 | #include "game/level/level_editor/rect_layer.h" 6 | #include "game/level/player.h" 7 | #include "game/level/rigid_bodies.h" 8 | #include "math/rand.h" 9 | #include "system/log.h" 10 | #include "system/lt.h" 11 | #include "system/nth_alloc.h" 12 | #include "system/str.h" 13 | #include "config.h" 14 | 15 | struct Boxes 16 | { 17 | Lt *lt; 18 | RigidBodies *rigid_bodies; 19 | Dynarray boxes_ids; 20 | Dynarray body_ids; 21 | Dynarray body_colors; 22 | }; 23 | 24 | Boxes *create_boxes_from_rect_layer(const RectLayer *layer, RigidBodies *rigid_bodies) 25 | { 26 | trace_assert(layer); 27 | trace_assert(rigid_bodies); 28 | 29 | Lt *lt = create_lt(); 30 | 31 | Boxes *boxes = PUSH_LT(lt, nth_calloc(1, sizeof(Boxes)), free); 32 | if (boxes == NULL) { 33 | RETURN_LT(lt, NULL); 34 | } 35 | boxes->lt = lt; 36 | 37 | boxes->boxes_ids = create_dynarray_malloc(ENTITY_MAX_ID_SIZE); 38 | boxes->body_ids = create_dynarray_malloc(sizeof(RigidBodyId)); 39 | boxes->body_colors = create_dynarray_malloc(sizeof(Color)); 40 | 41 | boxes->rigid_bodies = rigid_bodies; 42 | 43 | const size_t count = rect_layer_count(layer); 44 | Rect const *rects = rect_layer_rects(layer); 45 | Color const *colors = rect_layer_colors(layer); 46 | const char *ids = rect_layer_ids(layer); 47 | 48 | for (size_t i = 0; i < count; ++i) { 49 | RigidBodyId body_id = rigid_bodies_add(rigid_bodies, rects[i]); 50 | dynarray_push(&boxes->body_ids, &body_id); 51 | dynarray_push(&boxes->body_colors, &colors[i]); 52 | dynarray_push(&boxes->boxes_ids, ids + i * ENTITY_MAX_ID_SIZE); 53 | } 54 | 55 | return boxes; 56 | } 57 | 58 | void destroy_boxes(Boxes *boxes) 59 | { 60 | trace_assert(boxes); 61 | 62 | RigidBodyId *body_ids = (RigidBodyId *)boxes->body_ids.data; 63 | for (size_t i = 0; i < boxes->body_ids.count; ++i) { 64 | rigid_bodies_remove(boxes->rigid_bodies, body_ids[i]); 65 | } 66 | 67 | free(boxes->boxes_ids.data); 68 | free(boxes->body_ids.data); 69 | free(boxes->body_colors.data); 70 | 71 | RETURN_LT0(boxes->lt); 72 | } 73 | 74 | int boxes_render(Boxes *boxes, const Camera *camera) 75 | { 76 | trace_assert(boxes); 77 | trace_assert(camera); 78 | 79 | const size_t count = boxes->body_ids.count; 80 | RigidBodyId *body_ids = (RigidBodyId *)boxes->body_ids.data; 81 | Color *body_colors = (Color *)boxes->body_colors.data; 82 | 83 | for (size_t i = 0; i < count; ++i) { 84 | if (rigid_bodies_render( 85 | boxes->rigid_bodies, 86 | body_ids[i], 87 | body_colors[i], 88 | camera) < 0) { 89 | return -1; 90 | } 91 | } 92 | 93 | return 0; 94 | } 95 | 96 | int boxes_update(Boxes *boxes, 97 | float delta_time) 98 | { 99 | trace_assert(boxes); 100 | 101 | const size_t count = boxes->body_ids.count; 102 | RigidBodyId *body_ids = (RigidBodyId *)boxes->body_ids.data; 103 | 104 | for (size_t i = 0; i < count; ++i) { 105 | if (rigid_bodies_update(boxes->rigid_bodies, body_ids[i], delta_time) < 0) { 106 | return -1; 107 | } 108 | } 109 | 110 | return 0; 111 | } 112 | 113 | void boxes_float_in_lava(Boxes *boxes, Lava *lava) 114 | { 115 | trace_assert(boxes); 116 | trace_assert(lava); 117 | 118 | const size_t count = boxes->body_ids.count; 119 | RigidBodyId *body_ids = (RigidBodyId*)boxes->body_ids.data; 120 | 121 | for (size_t i = 0; i < count; ++i) { 122 | lava_float_rigid_body(lava, boxes->rigid_bodies, body_ids[i]); 123 | } 124 | } 125 | 126 | int boxes_add_box(Boxes *boxes, Rect rect, Color color) 127 | { 128 | trace_assert(boxes); 129 | 130 | RigidBodyId body_id = rigid_bodies_add(boxes->rigid_bodies, rect); 131 | dynarray_push(&boxes->body_ids, &body_id); 132 | dynarray_push(&boxes->body_colors, &color); 133 | 134 | return 0; 135 | } 136 | 137 | int boxes_delete_at(Boxes *boxes, Vec2f position) 138 | { 139 | trace_assert(boxes); 140 | 141 | const size_t count = boxes->body_ids.count; 142 | RigidBodyId *body_ids = (RigidBodyId*)boxes->body_ids.data; 143 | 144 | for (size_t i = 0; i < count; ++i) { 145 | const Rect hitbox = rigid_bodies_hitbox( 146 | boxes->rigid_bodies, 147 | body_ids[i]); 148 | if (rect_contains_point(hitbox, position)) { 149 | rigid_bodies_remove(boxes->rigid_bodies, body_ids[i]); 150 | dynarray_delete_at(&boxes->body_ids, i); 151 | dynarray_delete_at(&boxes->body_colors, i); 152 | return 0; 153 | } 154 | } 155 | 156 | return 0; 157 | } 158 | -------------------------------------------------------------------------------- /src/sdl/renderer.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "system/stacktrace.h" 3 | 4 | #include "renderer.h" 5 | #include "system/lt.h" 6 | #include "system/log.h" 7 | 8 | int draw_triangle(SDL_Renderer *render, 9 | Triangle t) 10 | { 11 | trace_assert(render); 12 | 13 | if (SDL_RenderDrawLine(render, 14 | (int) roundf(t.p1.x), 15 | (int) roundf(t.p1.y), 16 | (int) roundf(t.p2.x), 17 | (int) roundf(t.p2.y)) < 0) { 18 | log_fail("SDL_RenderDrawLine: %s\n", SDL_GetError()); 19 | return -1; 20 | } 21 | 22 | if (SDL_RenderDrawLine(render, 23 | (int) roundf(t.p2.x), 24 | (int) roundf(t.p2.y), 25 | (int) roundf(t.p3.x), 26 | (int) roundf(t.p3.y)) < 0) { 27 | log_fail("SDL_RenderDrawLine: %s\n", SDL_GetError()); 28 | return -1; 29 | } 30 | 31 | if (SDL_RenderDrawLine(render, 32 | (int) roundf(t.p3.x), 33 | (int) roundf(t.p3.y), 34 | (int) roundf(t.p1.x), 35 | (int) roundf(t.p1.y)) < 0) { 36 | log_fail("SDL_RenderDrawLine: %s\n", SDL_GetError()); 37 | return -1; 38 | } 39 | 40 | return 0; 41 | } 42 | 43 | static int fill_bottom_flat_triangle(SDL_Renderer *render, 44 | Triangle t) 45 | { 46 | trace_assert(render); 47 | 48 | const float invslope1 = (t.p2.x - t.p1.x) / (t.p2.y - t.p1.y); 49 | const float invslope2 = (t.p3.x - t.p1.x) / (t.p3.y - t.p1.y); 50 | 51 | const int y0 = (int) roundf(t.p1.y); 52 | const int y1 = (int) roundf(t.p2.y); 53 | 54 | float curx1 = t.p1.x; 55 | float curx2 = t.p1.x; 56 | 57 | for (int scanline = y0; scanline < y1; scanline++) { 58 | if (SDL_RenderDrawLine(render, 59 | (int) roundf(curx1), 60 | scanline, 61 | (int) roundf(curx2), 62 | scanline) < 0) { 63 | return -1; 64 | } 65 | curx1 += invslope1; 66 | curx2 += invslope2; 67 | } 68 | 69 | return 0; 70 | } 71 | 72 | static int fill_top_flat_triangle(SDL_Renderer *render, 73 | Triangle t) 74 | { 75 | trace_assert(render); 76 | 77 | const float invslope1 = (t.p3.x - t.p1.x) / (t.p3.y - t.p1.y); 78 | const float invslope2 = (t.p3.x - t.p2.x) / (t.p3.y - t.p2.y); 79 | 80 | const int y0 = (int) roundf(t.p3.y); 81 | const int y1 = (int) roundf(t.p1.y); 82 | 83 | float curx1 = t.p3.x; 84 | float curx2 = t.p3.x; 85 | 86 | for (int scanline = y0; scanline > y1; --scanline) { 87 | if (SDL_RenderDrawLine(render, 88 | (int) roundf(curx1), 89 | scanline, 90 | (int) roundf(curx2), 91 | scanline) < 0) { 92 | return -1; 93 | } 94 | 95 | curx1 -= invslope1; 96 | curx2 -= invslope2; 97 | } 98 | 99 | return 0; 100 | } 101 | 102 | int fill_triangle(SDL_Renderer *render, 103 | Triangle t) 104 | { 105 | t = triangle_sorted_by_y(t); 106 | 107 | if (fabs(t.p2.y - t.p3.y) < 1e-6) { 108 | if (fill_bottom_flat_triangle(render, t) < 0) { 109 | return -1; 110 | } 111 | } else if (fabs(t.p1.y - t.p2.y) < 1e-6) { 112 | if (fill_top_flat_triangle(render, t) < 0) { 113 | return -1; 114 | } 115 | } else { 116 | const Vec2f p4 = vec(t.p1.x + ((t.p2.y - t.p1.y) / (t.p3.y - t.p1.y)) * (t.p3.x - t.p1.x), t.p2.y); 117 | 118 | if (fill_bottom_flat_triangle(render, triangle(t.p1, t.p2, p4)) < 0) { 119 | return -1; 120 | } 121 | 122 | if (fill_top_flat_triangle(render, triangle(t.p2, p4, t.p3)) < 0) { 123 | return -1; 124 | } 125 | 126 | if (SDL_RenderDrawLine(render, 127 | (int) roundf(t.p2.x), 128 | (int) roundf(t.p2.y), 129 | (int) roundf(p4.x), 130 | (int) roundf(p4.y)) < 0) { 131 | return -1; 132 | } 133 | } 134 | 135 | return 0; 136 | } 137 | 138 | int fill_rect(SDL_Renderer *render, Rect r, Color c) 139 | { 140 | const SDL_Rect sdl_rect = rect_for_sdl(r); 141 | const SDL_Color sdl_color = color_for_sdl(c); 142 | 143 | if (SDL_SetRenderDrawColor( 144 | render, 145 | sdl_color.r, sdl_color.g, 146 | sdl_color.b, sdl_color.a) < 0) { 147 | log_fail("SDL_SetRenderDrawColor: %s\n", SDL_GetError()); 148 | return -1; 149 | } 150 | 151 | if (SDL_RenderFillRect(render, &sdl_rect) < 0) { 152 | log_fail("SDL_RenderFillRect: %s\n"); 153 | return -1; 154 | } 155 | 156 | return 0; 157 | } 158 | -------------------------------------------------------------------------------- /src/game/level/level_editor/player_layer.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include "game/camera.h" 6 | #include "system/stacktrace.h" 7 | #include "player_layer.h" 8 | #include "system/nth_alloc.h" 9 | #include "system/log.h" 10 | #include "undo_history.h" 11 | #include "system/memory.h" 12 | 13 | typedef struct { 14 | PlayerLayer *layer; 15 | Vec2f position; 16 | Color color; 17 | } PlayerUndoContext; 18 | 19 | static 20 | PlayerUndoContext player_layer_create_undo_context(PlayerLayer *player_layer) 21 | { 22 | PlayerUndoContext context = { 23 | .layer = player_layer, 24 | .position = player_layer->position, 25 | .color = player_layer->prev_color 26 | }; 27 | 28 | return context; 29 | } 30 | 31 | static 32 | void player_layer_undo(void *context, size_t context_size) 33 | { 34 | trace_assert(context); 35 | trace_assert(sizeof(PlayerUndoContext) == context_size); 36 | 37 | PlayerUndoContext *undo_context = context; 38 | PlayerLayer *player_layer = undo_context->layer; 39 | 40 | player_layer->position = undo_context->position; 41 | player_layer->color_picker = create_color_picker_from_rgba(undo_context->color); 42 | player_layer->prev_color = undo_context->color; 43 | } 44 | 45 | PlayerLayer create_player_layer(Vec2f position, Color color) 46 | { 47 | return (PlayerLayer) { 48 | .position = position, 49 | .color_picker = create_color_picker_from_rgba(color), 50 | .prev_color = color 51 | }; 52 | } 53 | 54 | PlayerLayer chop_player_layer(Memory *memory, String *input) 55 | { 56 | trace_assert(memory); 57 | trace_assert(input); 58 | 59 | String line = chop_by_delim(input, '\n'); 60 | float x = strtof(string_to_cstr(memory, chop_word(&line)), NULL); 61 | float y = strtof(string_to_cstr(memory, chop_word(&line)), NULL); 62 | Color color = hexs(chop_word(&line)); 63 | 64 | return create_player_layer(vec(x, y), color); 65 | } 66 | 67 | LayerPtr player_layer_as_layer(PlayerLayer *player_layer) 68 | { 69 | LayerPtr layer = { 70 | .type = LAYER_PLAYER, 71 | .ptr = player_layer 72 | }; 73 | return layer; 74 | } 75 | 76 | int player_layer_render(const PlayerLayer *player_layer, 77 | const Camera *camera, 78 | int active) 79 | { 80 | trace_assert(player_layer); 81 | trace_assert(camera); 82 | 83 | if (camera_fill_rect( 84 | camera, 85 | rect_from_vecs( 86 | player_layer->position, 87 | vec(25.0f, 25.0f)), 88 | color_scale( 89 | color_picker_rgba(&player_layer->color_picker), 90 | rgba(1.0f, 1.0f, 1.0f, active ? 1.0f : 0.5f))) < 0) { 91 | return -1; 92 | } 93 | 94 | if (active && color_picker_render( 95 | &player_layer->color_picker, 96 | camera)) { 97 | return -1; 98 | } 99 | 100 | return 0; 101 | } 102 | 103 | int player_layer_event(PlayerLayer *player_layer, 104 | const SDL_Event *event, 105 | const Camera *camera, 106 | UndoHistory *undo_history) 107 | { 108 | trace_assert(player_layer); 109 | trace_assert(event); 110 | trace_assert(camera); 111 | trace_assert(undo_history); 112 | 113 | int selected = 0; 114 | if (color_picker_event( 115 | &player_layer->color_picker, 116 | event, 117 | camera, 118 | &selected) < 0) { 119 | return -1; 120 | } 121 | 122 | if (selected && !color_picker_drag(&player_layer->color_picker)) { 123 | PlayerUndoContext context = 124 | player_layer_create_undo_context(player_layer); 125 | undo_history_push( 126 | undo_history, 127 | player_layer_undo, 128 | &context, 129 | sizeof(context)); 130 | player_layer->prev_color = color_picker_rgba(&player_layer->color_picker); 131 | } 132 | 133 | if (!selected && 134 | event->type == SDL_MOUSEBUTTONDOWN && 135 | event->button.button == SDL_BUTTON_LEFT) { 136 | 137 | PlayerUndoContext context = 138 | player_layer_create_undo_context(player_layer); 139 | 140 | undo_history_push( 141 | undo_history, 142 | player_layer_undo, 143 | &context, sizeof(context)); 144 | 145 | player_layer->position = 146 | camera_map_screen(camera, 147 | event->button.x, 148 | event->button.y); 149 | } 150 | 151 | return 0; 152 | } 153 | 154 | int player_layer_dump_stream(const PlayerLayer *player_layer, 155 | FILE *filedump) 156 | { 157 | trace_assert(player_layer); 158 | trace_assert(filedump); 159 | 160 | fprintf(filedump, "%f %f ", player_layer->position.x, player_layer->position.y); 161 | color_hex_to_stream(color_picker_rgba(&player_layer->color_picker), filedump); 162 | fprintf(filedump, "\n"); 163 | 164 | return 0; 165 | } 166 | -------------------------------------------------------------------------------- /src/color.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "color.h" 6 | 7 | Color rgba(float r, float g, float b, float a) 8 | { 9 | const Color result = { 10 | .r = r, 11 | .g = g, 12 | .b = b, 13 | .a = a 14 | }; 15 | 16 | return result; 17 | } 18 | 19 | /* TODO(#928): Should the Hue in HSLA representation be in degrees? */ 20 | Color hsla(float h, float s, float l, float a) 21 | { 22 | h = fmodf(h, 360.0f); 23 | 24 | const float c = (1.0f - fabsf(2.0f * l - 1.0f)) * s; 25 | const float x = c * (1 - fabsf(fmodf(h / 60.0f, 2.0f) - 1.0f)); 26 | const float m = l - c / 2.0f; 27 | 28 | Color color = {0.0f, 0.0f, 0.0f, a}; 29 | 30 | if (0.0f <= h && h < 60.0f) { 31 | color = rgba(c, x, 0.0f, a); 32 | } else if (60.0f <= h && h < 120.0f) { 33 | color = rgba(x, c, 0.0f, a); 34 | } else if (120.0f <= h && h < 180.0f) { 35 | color = rgba(0.0f, c, x, a); 36 | } else if (180.0f <= h && h < 240.0f) { 37 | color = rgba(0.0f, x, c, a); 38 | } else if (240.0f <= h && h < 300.0f) { 39 | color = rgba(x, 0.0f, c, a); 40 | } else if (300.0f <= h && h < 360.0f) { 41 | color = rgba(c, 0.0f, x, a); 42 | } 43 | 44 | color.r += m; 45 | color.g += m; 46 | color.b += m; 47 | 48 | return color; 49 | } 50 | 51 | static Uint8 hex2dec_digit(char c) 52 | { 53 | if (c >= '0' && c <= '9') { 54 | return (Uint8) (c - '0'); 55 | } 56 | 57 | if (c >= 'A' && c <= 'F') { 58 | return (Uint8) (10 + c - 'A'); 59 | } 60 | 61 | if (c >= 'a' && c <= 'f') { 62 | return (Uint8) (10 + c - 'a'); 63 | } 64 | 65 | return 0; 66 | } 67 | 68 | static Uint8 parse_color_component(const char *component) 69 | { 70 | return (Uint8) (hex2dec_digit(component[0]) * 16 + hex2dec_digit(component[1])); 71 | } 72 | 73 | Color hexstr(const char *hexstr) 74 | { 75 | if (strlen(hexstr) != 6) { 76 | return rgba(0.0f, 0.0f, 0.0f, 1.0f); 77 | } 78 | 79 | return rgba( 80 | parse_color_component(hexstr) / 255.0f, 81 | parse_color_component(hexstr + 2) / 255.0f, 82 | parse_color_component(hexstr + 4) / 255.0f, 83 | 1.0f); 84 | } 85 | 86 | Color hexs(String input) 87 | { 88 | if (input.count < 6) return COLOR_BLACK; 89 | 90 | return rgba( 91 | parse_color_component(input.data) / 255.0f, 92 | parse_color_component(input.data + 2) / 255.0f, 93 | parse_color_component(input.data + 4) / 255.0f, 94 | 1.0f); 95 | } 96 | 97 | SDL_Color color_for_sdl(Color color) 98 | { 99 | const SDL_Color result = { 100 | .r = (Uint8)roundf(color.r * 255.0f), 101 | .g = (Uint8)roundf(color.g * 255.0f), 102 | .b = (Uint8)roundf(color.b * 255.0f), 103 | .a = (Uint8)roundf(color.a * 255.0f) 104 | }; 105 | 106 | return result; 107 | } 108 | 109 | Color color_desaturate(Color c) 110 | { 111 | const float k = (c.r + c.g + c.b) / 3.0f; 112 | return rgba(k, k, k, c.a); 113 | } 114 | 115 | Color color_darker(Color c, float d) 116 | { 117 | return rgba(fmaxf(c.r - d, 0.0f), 118 | fmaxf(c.g - d, 0.0f), 119 | fmaxf(c.b - d, 0.0f), 120 | c.a); 121 | } 122 | 123 | Color color_invert(Color c) 124 | { 125 | return rgba(1.0f - c.r, 1.0f - c.g, 1.0f - c.b, c.a); 126 | } 127 | 128 | Color color_scale(Color c, Color fc) 129 | { 130 | return rgba( 131 | fmaxf(fminf(c.r * fc.r, 1.0f), 0.0f), 132 | fmaxf(fminf(c.g * fc.g, 1.0f), 0.0f), 133 | fmaxf(fminf(c.b * fc.b, 1.0f), 0.0f), 134 | fmaxf(fminf(c.a * fc.a, 1.0f), 0.0f)); 135 | } 136 | 137 | int color_hex_to_stream(Color color, FILE *stream) 138 | { 139 | SDL_Color sdl = color_for_sdl(color); 140 | return fprintf(stream, "%02x%02x%02x", sdl.r, sdl.g, sdl.b); 141 | } 142 | 143 | int color_hex_to_string(Color color, char *buffer, size_t buffer_size) 144 | { 145 | SDL_Color sdl = color_for_sdl(color); 146 | return snprintf(buffer, buffer_size, "%02x%02x%02x", sdl.r, sdl.g, sdl.b); 147 | } 148 | 149 | Color rgba_to_hsla(Color color) 150 | { 151 | const float max = fmaxf(color.r, fmaxf(color.g, color.b)); 152 | const float min = fminf(color.r, fminf(color.g, color.b)); 153 | const float c = max - min; 154 | float hue = 0.0f; 155 | float saturation = 0.0f; 156 | const float lightness = (max + min) * 0.5f; 157 | 158 | if (fabsf(c) > 1e-6) { 159 | if (fabs(max - color.r) <= 1e-6) { 160 | hue = 60.0f * fmodf((color.g - color.b) / c, 6.0f); 161 | } else if (fabs(max - color.g) <= 1e-6) { 162 | hue = 60.0f * ((color.b - color.r) / c + 2.0f); 163 | } else { 164 | hue = 60.0f * ((color.r - color.g) / c + 4.0f); 165 | } 166 | 167 | saturation = c / (1.0f - fabsf(2.0f * lightness - 1.0f)); 168 | } 169 | 170 | // TODO(#929): Color struct is used not only for RGBA 171 | // But also for HSLA. We should make another similar struct but for HSLA 172 | return rgba(hue, saturation, lightness, color.a); 173 | } 174 | -------------------------------------------------------------------------------- /src/game/level/regions.c: -------------------------------------------------------------------------------- 1 | #include "system/stacktrace.h" 2 | 3 | #include "config.h" 4 | #include "player.h" 5 | #include "regions.h" 6 | #include "system/str.h" 7 | #include "system/log.h" 8 | #include "system/lt.h" 9 | #include "system/nth_alloc.h" 10 | #include "game/level/level_editor/rect_layer.h" 11 | #include "game/level/labels.h" 12 | #include "game/level/goals.h" 13 | 14 | enum RegionState { 15 | RS_PLAYER_OUTSIDE = 0, 16 | RS_PLAYER_INSIDE 17 | }; 18 | 19 | struct Regions { 20 | Lt *lt; 21 | size_t count; 22 | char *ids; 23 | Rect *rects; 24 | Color *colors; 25 | enum RegionState *states; 26 | Action *actions; 27 | 28 | Labels *labels; 29 | Goals *goals; 30 | }; 31 | 32 | Regions *create_regions_from_rect_layer(const RectLayer *rect_layer, 33 | Labels *labels, 34 | Goals *goals) 35 | { 36 | trace_assert(rect_layer); 37 | trace_assert(labels); 38 | 39 | Lt *lt = create_lt(); 40 | 41 | Regions *regions = PUSH_LT( 42 | lt, 43 | nth_calloc(1, sizeof(Regions)), 44 | free); 45 | if (regions == NULL) { 46 | RETURN_LT(lt, NULL); 47 | } 48 | regions->lt = lt; 49 | 50 | regions->count = rect_layer_count(rect_layer); 51 | 52 | regions->ids = PUSH_LT( 53 | lt, 54 | nth_calloc(regions->count * ENTITY_MAX_ID_SIZE, sizeof(char)), 55 | free); 56 | if (regions->ids == NULL) { 57 | RETURN_LT(lt, NULL); 58 | } 59 | memcpy(regions->ids, 60 | rect_layer_ids(rect_layer), 61 | regions->count * ENTITY_MAX_ID_SIZE * sizeof(char)); 62 | 63 | 64 | regions->rects = PUSH_LT( 65 | lt, 66 | nth_calloc(1, sizeof(Rect) * regions->count), 67 | free); 68 | if (regions->rects == NULL) { 69 | RETURN_LT(lt, NULL); 70 | } 71 | memcpy(regions->rects, 72 | rect_layer_rects(rect_layer), 73 | regions->count * sizeof(Rect)); 74 | 75 | 76 | regions->colors = PUSH_LT( 77 | lt, 78 | nth_calloc(1, sizeof(Color) * regions->count), 79 | free); 80 | if (regions->colors == NULL) { 81 | RETURN_LT(lt, NULL); 82 | } 83 | memcpy(regions->colors, 84 | rect_layer_colors(rect_layer), 85 | regions->count * sizeof(Color)); 86 | 87 | regions->states = PUSH_LT( 88 | lt, 89 | nth_calloc(1, sizeof(enum RegionState) * regions->count), 90 | free); 91 | if (regions->states == NULL) { 92 | RETURN_LT(lt, NULL); 93 | } 94 | 95 | regions->actions = PUSH_LT( 96 | lt, 97 | nth_calloc(1, sizeof(Action) * regions->count), 98 | free); 99 | if (regions->actions == NULL) { 100 | RETURN_LT(lt, NULL); 101 | } 102 | memcpy(regions->actions, 103 | rect_layer_actions(rect_layer), 104 | regions->count * sizeof(Action)); 105 | 106 | // TODO(#1108): impossible to change the region action from the Level Editor 107 | 108 | 109 | regions->labels = labels; 110 | regions->goals = goals; 111 | 112 | return regions; 113 | } 114 | 115 | void destroy_regions(Regions *regions) 116 | { 117 | trace_assert(regions); 118 | RETURN_LT0(regions->lt); 119 | } 120 | 121 | void regions_player_enter(Regions *regions, Player *player) 122 | { 123 | trace_assert(regions); 124 | trace_assert(player); 125 | 126 | for (size_t i = 0; i < regions->count; ++i) { 127 | if (regions->states[i] == RS_PLAYER_OUTSIDE && 128 | player_overlaps_rect(player, regions->rects[i])) { 129 | regions->states[i] = RS_PLAYER_INSIDE; 130 | 131 | switch (regions->actions[i].type) { 132 | case ACTION_HIDE_LABEL: { 133 | labels_hide(regions->labels, regions->actions[i].entity_id); 134 | } break; 135 | 136 | case ACTION_TOGGLE_GOAL: { 137 | goals_hide(regions->goals, regions->actions[i].entity_id); 138 | } break; 139 | 140 | default: {} 141 | } 142 | } 143 | } 144 | } 145 | 146 | void regions_player_leave(Regions *regions, Player *player) 147 | { 148 | trace_assert(regions); 149 | trace_assert(player); 150 | 151 | for (size_t i = 0; i < regions->count; ++i) { 152 | if (regions->states[i] == RS_PLAYER_INSIDE && 153 | !player_overlaps_rect(player, regions->rects[i])) { 154 | regions->states[i] = RS_PLAYER_OUTSIDE; 155 | 156 | switch (regions->actions[i].type) { 157 | case ACTION_TOGGLE_GOAL: { 158 | goals_show(regions->goals, regions->actions[i].entity_id); 159 | } break; 160 | 161 | default: {} 162 | } 163 | } 164 | } 165 | } 166 | 167 | int regions_render(Regions *regions, const Camera *camera) 168 | { 169 | trace_assert(regions); 170 | trace_assert(camera); 171 | 172 | for (size_t i = 0; i < regions->count; ++i) { 173 | if (camera_render_debug_rect( 174 | camera, 175 | regions->rects[i], 176 | regions->colors[i]) < 0) { 177 | return -1; 178 | } 179 | } 180 | 181 | return 0; 182 | } 183 | -------------------------------------------------------------------------------- /src/game/level/level_editor/layer_picker.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | #include "system/stacktrace.h" 7 | #include "layer_picker.h" 8 | #include "color.h" 9 | #include "game/camera.h" 10 | #include "math/rect.h" 11 | #include "math/extrema.h" 12 | #include "game/sprite_font.h" 13 | 14 | #define LAYER_TITLE_PADDING 15.0f 15 | #define LAYER_TITLE_SIZE 3.0f 16 | #define LAYER_SELECTED_OFFSET 15.0f 17 | 18 | static const Color LAYER_CELL_BACKGROUND_COLORS[LAYER_PICKER_N] = { 19 | {1.0f, 0.0f, 0.0f, 1.0f}, // LAYER_PICKER_BACKGROUND 20 | {0.0f, 1.0f, 0.0f, 1.0f}, // LAYER_PICKER_PLAYER 21 | {0.6f, 0.6f, 1.0f, 1.0f}, // LAYER_PICKER_BACK_PLATFORMS 22 | {0.0f, 0.0f, 1.0f, 1.0f}, // LAYER_PICKER_PLATFORMS 23 | {1.0f, 1.0f, 1.0f, 1.0f}, // LAYER_PICKER_GOALS 24 | {1.0f, 0.2f, 0.6f, 1.0f}, // LAYER_PICKER_LAVA 25 | {0.2f, 1.0f, 0.6f, 1.0f}, // LAYER_PICKER_BOXES 26 | {0.2f, 0.6f, 1.0f, 1.0f}, // LAYER_PICKER_LABELS 27 | {0.2f, 1.0f, 0.6f, 1.0f}, // LAYER_PICKER_REGIONS 28 | {0.2f, 1.0f, 0.6f, 1.0f}, // LAYER_PICKER_PP 29 | }; 30 | 31 | static const char *LAYER_CELL_TITLES[LAYER_PICKER_N] = { 32 | "Background", // LAYER_PICKER_BACKGROUND 33 | "Player", // LAYER_PICKER_PLAYER 34 | "Back Platforms", // LAYER_PICKER_BACK_PLATFORMS 35 | "Platforms", // LAYER_PICKER_PLATFORMS 36 | "Goals", // LAYER_PICKER_GOALS 37 | "Lava", // LAYER_PICKER_LAVA 38 | "Boxes", // LAYER_PICKER_BOXES 39 | "Labels", // LAYER_PICKER_LABELS 40 | "Regions", // LAYER_PICKER_REGIONS 41 | "Phantom Platforms", // LAYER_PICKER_PP 42 | }; 43 | 44 | inline static float layer_picker_max_width(void) 45 | { 46 | size_t max = 0; 47 | 48 | for (size_t i = 0; i < LAYER_PICKER_N; ++i) { 49 | max = max_size_t(max, strlen(LAYER_CELL_TITLES[i])); 50 | } 51 | 52 | return (float) max * FONT_CHAR_WIDTH * LAYER_TITLE_SIZE + LAYER_TITLE_PADDING * 2.0f; 53 | } 54 | 55 | #define LAYER_CELL_WIDTH layer_picker_max_width() 56 | #define LAYER_CELL_HEIGHT (LAYER_TITLE_SIZE * FONT_CHAR_HEIGHT + LAYER_TITLE_PADDING * 2.0f) 57 | #define LAYER_CELL_REFERENCE 1980.0f 58 | #define LAYER_CELL_WR_RATIO (LAYER_CELL_WIDTH / LAYER_CELL_REFERENCE) 59 | #define LAYER_CELL_HW_RATIO (LAYER_CELL_HEIGHT / LAYER_CELL_WIDTH) 60 | #define LAYER_TITLE_SW_RATIO (LAYER_TITLE_SIZE / LAYER_CELL_WIDTH) 61 | #define LAYER_TITLE_PW_RATIO (LAYER_TITLE_PADDING / LAYER_CELL_WIDTH) 62 | #define LAYER_CELL_OW_RATIO (LAYER_SELECTED_OFFSET / LAYER_CELL_WIDTH) 63 | 64 | int layer_picker_render(const LayerPicker *layer_picker, 65 | const Camera *camera) 66 | { 67 | trace_assert(layer_picker); 68 | trace_assert(camera); 69 | 70 | const Rect viewport = camera_view_port_screen(camera); 71 | 72 | for (size_t i = 0; i < LAYER_PICKER_N; ++i) { 73 | const Vec2f size = { 74 | .x = viewport.w * LAYER_CELL_WR_RATIO, 75 | .y = viewport.w * LAYER_CELL_WR_RATIO * LAYER_CELL_HW_RATIO 76 | }; 77 | 78 | Vec2f position = { 79 | .x = 0.0f, 80 | .y = viewport.h * 0.5f - size.y * LAYER_PICKER_N * 0.5f 81 | }; 82 | 83 | Color color = LAYER_CELL_BACKGROUND_COLORS[i]; 84 | 85 | if (*layer_picker == i) { 86 | position.x += size.x * LAYER_CELL_OW_RATIO; 87 | } else { 88 | color.a *= 0.70f; 89 | } 90 | 91 | if (camera_fill_rect_screen( 92 | camera, 93 | rect( 94 | position.x, 95 | size.y * (float) i + position.y, 96 | size.x, size.y), 97 | color) < 0) { 98 | return -1; 99 | } 100 | 101 | camera_render_text_screen( 102 | camera, 103 | LAYER_CELL_TITLES[i], 104 | vec(size.x * LAYER_TITLE_SW_RATIO, 105 | size.x * LAYER_TITLE_SW_RATIO), 106 | color_invert(color), 107 | vec(position.x + size.x * LAYER_TITLE_PW_RATIO, 108 | size.y * (float) i + position.y + size.x * LAYER_TITLE_PW_RATIO)); 109 | } 110 | 111 | return 0; 112 | } 113 | 114 | int layer_picker_event(LayerPicker *layer_picker, 115 | const SDL_Event *event, 116 | const Camera *camera, 117 | bool *selected) 118 | { 119 | trace_assert(layer_picker); 120 | trace_assert(event); 121 | trace_assert(camera); 122 | 123 | const Rect viewport = camera_view_port_screen(camera); 124 | 125 | const Vec2f size = { 126 | .x = viewport.w * LAYER_CELL_WR_RATIO, 127 | .y = viewport.w * LAYER_CELL_WR_RATIO * LAYER_CELL_HW_RATIO 128 | }; 129 | 130 | const Vec2f position = { 131 | .x = 0.0f, 132 | .y = viewport.h * 0.5f - size.y * LAYER_PICKER_N * 0.5f 133 | }; 134 | 135 | switch (event->type) { 136 | case SDL_MOUSEBUTTONDOWN: { 137 | for (LayerPicker i = 0; i < LAYER_PICKER_N; ++i) { 138 | const Rect cell = rect( 139 | position.x, 140 | size.y * (float) i + position.y, 141 | size.x, size.y); 142 | if (rect_contains_point(cell, vec((float) event->button.x, (float) event->button.y))) { 143 | *layer_picker = i; 144 | *selected = true; 145 | return 0; 146 | } 147 | } 148 | } break; 149 | } 150 | 151 | return 0; 152 | } 153 | -------------------------------------------------------------------------------- /src/game/level/labels.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "config.h" 5 | #include "game/camera.h" 6 | #include "game/level/labels.h" 7 | #include "game/level/level_editor/label_layer.h" 8 | #include "system/log.h" 9 | #include "system/lt.h" 10 | #include "system/nth_alloc.h" 11 | #include "system/stacktrace.h" 12 | #include "system/str.h" 13 | 14 | enum LabelState 15 | { 16 | LABEL_STATE_VIRGIN = 0, 17 | LABEL_STATE_APPEARED, 18 | LABEL_STATE_HIDDEN 19 | }; 20 | 21 | struct Labels 22 | { 23 | Lt *lt; 24 | size_t count; 25 | char *ids; 26 | Vec2f *positions; 27 | Color *colors; 28 | char **texts; 29 | 30 | /* Animation state */ 31 | float *alphas; 32 | float *delta_alphas; 33 | enum LabelState *states; 34 | }; 35 | 36 | Labels *create_labels_from_label_layer(const LabelLayer *label_layer) 37 | { 38 | trace_assert(label_layer); 39 | 40 | Lt *lt = create_lt(); 41 | 42 | Labels *labels = PUSH_LT(lt, nth_calloc(1, sizeof(Labels)), free); 43 | if (labels == NULL) { 44 | RETURN_LT(lt, NULL); 45 | } 46 | labels->lt = lt; 47 | 48 | labels->count = label_layer_count(label_layer); 49 | 50 | labels->ids = PUSH_LT(lt, nth_calloc(labels->count, sizeof(char) * ENTITY_MAX_ID_SIZE), free); 51 | if (labels->ids == NULL) { 52 | RETURN_LT(lt, NULL); 53 | } 54 | memcpy(labels->ids, 55 | label_layer_ids(label_layer), 56 | labels->count * sizeof(char) * ENTITY_MAX_ID_SIZE); 57 | 58 | labels->positions = PUSH_LT(lt, nth_calloc(1, sizeof(Vec2f) * labels->count), free); 59 | if (labels->positions == NULL) { 60 | RETURN_LT(lt, NULL); 61 | } 62 | memcpy(labels->positions, 63 | label_layer_positions(label_layer), 64 | labels->count * sizeof(Vec2f)); 65 | 66 | labels->colors = PUSH_LT(lt, nth_calloc(1, sizeof(Color) * labels->count), free); 67 | if (labels->colors == NULL) { 68 | RETURN_LT(lt, NULL); 69 | } 70 | memcpy(labels->colors, 71 | label_layer_colors(label_layer), 72 | labels->count * sizeof(Color)); 73 | 74 | labels->texts = PUSH_LT(lt, nth_calloc(1, sizeof(char*) * labels->count), free); 75 | if (labels->texts == NULL) { 76 | RETURN_LT(lt, NULL); 77 | } 78 | 79 | char *texts = labels_layer_texts(label_layer); 80 | for (size_t i = 0; i < labels->count; ++i) { 81 | labels->texts[i] = PUSH_LT( 82 | labels->lt, 83 | string_duplicate(texts + i * LABEL_LAYER_TEXT_MAX_SIZE, NULL), 84 | free); 85 | } 86 | 87 | labels->alphas = PUSH_LT(lt, nth_calloc(1, sizeof(float) * labels->count), free); 88 | if (labels->alphas == NULL) { 89 | RETURN_LT(lt, NULL); 90 | } 91 | 92 | labels->delta_alphas = PUSH_LT(lt, nth_calloc(1, sizeof(float) * labels->count), free); 93 | if (labels->delta_alphas == NULL) { 94 | RETURN_LT(lt, NULL); 95 | } 96 | 97 | labels->states = PUSH_LT(lt, nth_calloc(1, sizeof(enum LabelState) * labels->count), free); 98 | if (labels->states == NULL) { 99 | RETURN_LT(lt, NULL); 100 | } 101 | 102 | return labels; 103 | } 104 | 105 | void destroy_labels(Labels *label) 106 | { 107 | trace_assert(label); 108 | RETURN_LT0(label->lt); 109 | } 110 | 111 | int labels_render(const Labels *label, 112 | const Camera *camera) 113 | { 114 | trace_assert(label); 115 | trace_assert(camera); 116 | 117 | for (size_t i = 0; i < label->count; ++i) { 118 | /* Easing */ 119 | const float state = label->alphas[i] * (2 - label->alphas[i]); 120 | 121 | if (camera_render_text(camera, 122 | label->texts[i], 123 | LABELS_SIZE, 124 | rgba(label->colors[i].r, 125 | label->colors[i].g, 126 | label->colors[i].b, 127 | state), 128 | vec_sum(label->positions[i], 129 | vec(0.0f, -8.0f * state))) < 0) { 130 | return -1; 131 | } 132 | } 133 | 134 | return 0; 135 | } 136 | 137 | void labels_update(Labels *label, 138 | float delta_time) 139 | { 140 | trace_assert(label); 141 | (void) delta_time; 142 | 143 | for (size_t i = 0; i < label->count; ++i) { 144 | label->alphas[i] = label->alphas[i] + label->delta_alphas[i] * delta_time; 145 | 146 | if (label->alphas[i] < 0.0f) { 147 | label->alphas[i] = 0.0f; 148 | label->delta_alphas[i] = 0.0f; 149 | } 150 | 151 | if (label->alphas[i] > 1.0f) { 152 | label->alphas[i] = 1.0f; 153 | label->delta_alphas[i] = 0.0f; 154 | } 155 | } 156 | } 157 | 158 | void labels_enter_camera_event(Labels *labels, 159 | const Camera *camera) 160 | { 161 | trace_assert(labels); 162 | trace_assert(camera); 163 | 164 | for (size_t i = 0; i < labels->count; ++i) { 165 | const int became_visible = camera_is_text_visible( 166 | camera, 167 | vec(2.0f, 2.0f), 168 | labels->positions[i], 169 | labels->texts[i]); 170 | 171 | if (labels->states[i] == LABEL_STATE_VIRGIN && became_visible) { 172 | labels->states[i] = LABEL_STATE_APPEARED; 173 | labels->alphas[i] = 0.0f; 174 | labels->delta_alphas[i] = 1.0f; 175 | } 176 | } 177 | } 178 | 179 | void labels_hide(Labels *labels, char id[ENTITY_MAX_ID_SIZE]) 180 | { 181 | trace_assert(labels); 182 | trace_assert(id); 183 | 184 | for (size_t i = 0; i < labels->count; ++i) { 185 | if (strncmp(id, labels->ids + i * ENTITY_MAX_ID_SIZE, ENTITY_MAX_ID_SIZE) == 0) { 186 | if (labels->states[i] != LABEL_STATE_HIDDEN) { 187 | labels->states[i] = LABEL_STATE_HIDDEN; 188 | labels->alphas[i] = 1.0f; 189 | labels->delta_alphas[i] = -3.0f; 190 | } 191 | return; 192 | } 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Tsoding](https://img.shields.io/badge/twitch.tv-tsoding-purple?logo=twitch&style=for-the-badge)](https://www.twitch.tv/tsoding) 2 | [![Build Status](https://ci.appveyor.com/api/projects/status/gxfgojq4ko98e0g0/branch/master?svg=true)](https://ci.appveyor.com/project/rexim/nothing/branch/master) 3 | [![Build Status](https://github.com/tsoding/nothing/workflows/CI/badge.svg)](https://github.com/tsoding/nothing/actions) 4 | 5 | # Nothing 6 | 7 | ![](https://i.imgur.com/7mECYKU.gif) 8 | ![](https://i.imgur.com/ABcJqB5.gif) 9 | 10 | ## Dependencies 11 | 12 | - [gcc] or [clang] or [MSVC 2015+][visual-studio] 13 | - [cmake] 14 | - [libsdl2-dev] 15 | 16 | ### Ubuntu 17 | 18 | ```console 19 | $ sudo apt-get install gcc cmake libsdl2-dev 20 | ``` 21 | 22 | ### MacOS 23 | 24 | ```console 25 | $ brew install gcc cmake sdl2 26 | ``` 27 | 28 | ### NixOS 29 | 30 | For [NixOS] we have a development environment defined in [default.nix] 31 | with all of the required dependencies. You can enter the environment 32 | with `nix-shell` command: 33 | 34 | ```console 35 | $ nix-shell 36 | ``` 37 | 38 | ### Arch Linux 39 | 40 | ```console 41 | $ sudo pacman -S gcc cmake sdl2 42 | ``` 43 | 44 | ### Windows 45 | 46 | #### Visual Studio 47 | 48 | - [Visual Studio 2015+](https://visualstudio.microsoft.com/) 49 | - [SDL2 VC Development Libraries](https://www.libsdl.org/release/SDL2-devel-2.0.9-VC.zip) 50 | 51 | #### MinGW 52 | - [mingw-w64](https://mingw-w64.org) 53 | - [SDL2 MinGW Development Libraries](https://www.libsdl.org/release/SDL2-devel-2.0.10-mingw.tar.gz) 54 | 55 | ## Quick Start 56 | 57 | ### Linux 58 | 59 | #### CMake 60 | 61 | ```console 62 | $ mkdir build 63 | $ cd build/ 64 | $ cmake .. 65 | $ make 66 | $ ./nothing 67 | ``` 68 | 69 | #### SCU 70 | 71 | ```console 72 | $ ./build-posix.sh 73 | $ ./nothing 74 | ``` 75 | 76 | ### Windows 77 | 78 | #### Visual Studio 79 | 80 | - Enter the Visual Studio Command Line Development Environment https://docs.microsoft.com/en-us/cpp/build/building-on-the-command-line 81 | - Basically just find `vcvarsall.bat` and run `vcvarsall.bat x64` inside of cmd 82 | - Download [SDL2 VC Development Libraries](https://www.libsdl.org/release/SDL2-devel-2.0.9-VC.zip) and copy it to `path\to\nothing` 83 | 84 | ```console 85 | > cd path\to\nothing 86 | > 7z x SDL2-devel-2.0.9-VC.zip 87 | > move SDL2-2.0.9 SDL2 88 | > mkdir build 89 | > cd build 90 | > cmake .. 91 | > cmake --build . 92 | > .\nothing 93 | ``` 94 | 95 | #### MinGW (with MSYS) 96 | 97 | ```console 98 | $ cd path/to/nothing 99 | $ wget https://www.libsdl.org/release/SDL2-devel-2.0.10-mingw.tar.gz 100 | $ tar xzf SDL2-devel-2.0.10-mingw.tar.gz 101 | $ mv SDL2-2.0.10 SDL2 102 | $ rm SDL2-devel-2.0.10-mingw.tar.gz 103 | $ mkdir build && cd build 104 | $ cmake .. -G "MSYS Makefiles" 105 | $ cmake --build . 106 | $ ./nothing 107 | ``` 108 | 109 | #### MinGW (without MSYS) 110 | - Download [SDL2 MinGW Development Libraries](https://www.libsdl.org/release/SDL2-devel-2.0.10-mingw.tar.gz) and copy it to `path\to\nothing` 111 | 112 | ```console 113 | > cd path\to\nothing 114 | > 7z x SDL2-devel-2.0.10-mingw.tar.gz -so | 7z x -si -ttar 115 | > move SDL2-2.0.10 SDL2 116 | > del SDL2-devel-2.0.10-mingw.tar.gz 117 | > mkdir build 118 | > cd build 119 | > cmake .. -G "MinGW Makefiles" 120 | > cmake --build . 121 | > .\nothing 122 | ``` 123 | 124 | ## Controls 125 | 126 | ### Game 127 | 128 | #### Keyboard 129 | 130 | | Key | Action | 131 | |---------- |-------------------------------------------------------------| 132 | | `d` | Move to the right | 133 | | `a` | Move to the left | 134 | | `w/SPACE` | Jump | 135 | | `c` | Open debug console | 136 | | `r` | Reload the current level including the Player's position | 137 | | `q` | Reload the current level preserving the Player's position | 138 | | `p` | Toggle game pause | 139 | | `l` | Toggle transparency on objects. Useful for debugging levels | 140 | | `TAB` | Switch to Level Editor | 141 | | `CTRL+q` | Quit the game | 142 | 143 | #### Gamepad 144 | 145 | | Button | Action | 146 | |--------------|------------------------| 147 | | `Left Stick` | Movement of the Player | 148 | | `1` | Jump | 149 | 150 | ### Consolé 151 | 152 | | Key | Action | 153 | |---------------------|--------------------------| 154 | | `ESC` | Exit console | 155 | | `Enter` | Evaluate the expression | 156 | | `Up/Down` | Traverse console history | 157 | | `CTRL+L` | Clear | 158 | | `Ctrl+X`, `CTRL+W` | Cut | 159 | | `Ctrl+C`, `ALT+W` | Copy | 160 | | `Ctrl+V`, `CTRL+Y` | Paste | 161 | 162 | ### Level Editor 163 | 164 | To access the Level Editor open a level and press `TAB`. 165 | 166 | | Key | Action | 167 | |-----------------|--------------------------------------------| 168 | | `s` | Save level | 169 | | `Mouse Wheel` | Zoom and pan | 170 | | `CTRL+z` | Undo | 171 | | `q` | Toggle snapping mode | 172 | | `SHIFT+Up/Down` | Change overlaping order of selected object | 173 | | `CTRL+c/v` | Copy/paste selected object | 174 | | `F2` | Rename selected object | 175 | | `DELETE` | Delete selected object | 176 | 177 | ## Support 178 | 179 | You can support my work via 180 | 181 | - Twitch channel: https://www.twitch.tv/subs/tsoding 182 | - Patreon: https://www.patreon.com/tsoding 183 | 184 | [visual-studio]: https://www.visualstudio.com/ 185 | [svg2rects.py]: ./devtools/svg2rects.py 186 | [./levels/]: ./levels/ 187 | [./levels/Makefile]: ./levels/Makefile 188 | [gcc]: https://gcc.gnu.org/ 189 | [clang]: https://clang.llvm.org/ 190 | [cmake]: https://cmake.org/ 191 | [libsdl2-dev]: https://www.libsdl.org/ 192 | [NixOS]: https://nixos.org/ 193 | [default.nix]: ./default.nix 194 | [inotify-tools]: https://github.com/rvoicilas/inotify-tools 195 | -------------------------------------------------------------------------------- /src/game/level/goals.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | #include "game/level/level_editor/point_layer.h" 7 | #include "goals.h" 8 | #include "math/pi.h" 9 | #include "math/triangle.h" 10 | #include "system/log.h" 11 | #include "system/lt.h" 12 | #include "system/nth_alloc.h" 13 | #include "system/stacktrace.h" 14 | #include "system/str.h" 15 | 16 | #define GOAL_RADIUS 10.0f 17 | 18 | static int goals_is_goal_hidden(const Goals *goals, size_t i); 19 | 20 | typedef enum Cue_state { 21 | CUE_STATE_VIRGIN = 0, 22 | CUE_STATE_HIT_NOTHING, 23 | CUE_STATE_SEEN_NOTHING 24 | } Cue_state; 25 | 26 | struct Goals { 27 | Lt *lt; 28 | char **ids; 29 | Vec2f *positions; 30 | Color *colors; 31 | Cue_state *cue_states; 32 | bool *visible; 33 | size_t count; 34 | float angle; 35 | }; 36 | 37 | Goals *create_goals_from_point_layer(const PointLayer *point_layer) 38 | { 39 | trace_assert(point_layer); 40 | 41 | Lt *lt = create_lt(); 42 | 43 | Goals *const goals = PUSH_LT(lt, nth_calloc(1, sizeof(Goals)), free); 44 | if (goals == NULL) { 45 | RETURN_LT(lt, NULL); 46 | } 47 | 48 | goals->count = point_layer_count(point_layer); 49 | 50 | goals->ids = PUSH_LT( 51 | lt, 52 | nth_calloc(1, sizeof(char*) * goals->count), 53 | free); 54 | if (goals->ids == NULL) { 55 | RETURN_LT(lt, NULL); 56 | } 57 | for (size_t i = 0; i < goals->count; ++i) { 58 | goals->ids[i] = PUSH_LT(lt, nth_calloc(1, sizeof(char) * ENTITY_MAX_ID_SIZE), free); 59 | if (goals->ids[i] == NULL) { 60 | RETURN_LT(lt, NULL); 61 | } 62 | } 63 | 64 | goals->positions = PUSH_LT(lt, nth_calloc(1, sizeof(Vec2f) * goals->count), free); 65 | if (goals->positions == NULL) { 66 | RETURN_LT(lt, NULL); 67 | } 68 | 69 | goals->colors = PUSH_LT(lt, nth_calloc(1, sizeof(Color) * goals->count), free); 70 | if (goals->colors == NULL) { 71 | RETURN_LT(lt, NULL); 72 | } 73 | 74 | goals->cue_states = PUSH_LT(lt, nth_calloc(1, sizeof(int) * goals->count), free); 75 | if (goals->cue_states == NULL) { 76 | RETURN_LT(lt, NULL); 77 | } 78 | 79 | goals->visible = PUSH_LT(lt, nth_calloc(1, sizeof(bool) * goals->count), free); 80 | if (goals->visible == NULL) { 81 | RETURN_LT(lt, NULL); 82 | } 83 | 84 | const Vec2f *positions = point_layer_positions(point_layer); 85 | const Color *colors = point_layer_colors(point_layer); 86 | const char *ids = point_layer_ids(point_layer); 87 | 88 | // TODO(#835): we could use memcpy in create_goals_from_point_layer 89 | for (size_t i = 0; i < goals->count; ++i) { 90 | goals->positions[i] = positions[i]; 91 | goals->colors[i] = colors[i]; 92 | memcpy(goals->ids[i], ids + ID_MAX_SIZE * i, ID_MAX_SIZE); 93 | goals->cue_states[i] = CUE_STATE_VIRGIN; 94 | goals->visible[i] = true; 95 | } 96 | 97 | goals->lt = lt; 98 | goals->angle = 0.0f; 99 | 100 | return goals; 101 | } 102 | 103 | void destroy_goals(Goals *goals) 104 | { 105 | trace_assert(goals); 106 | RETURN_LT0(goals->lt); 107 | } 108 | 109 | static int goals_render_core(const Goals *goals, 110 | size_t goal_index, 111 | const Camera *camera) 112 | { 113 | trace_assert(goals); 114 | trace_assert(camera); 115 | 116 | const Vec2f position = vec_sum( 117 | goals->positions[goal_index], 118 | vec(0.0f, sinf(goals->angle) * 10.0f)); 119 | 120 | if (camera_fill_triangle( 121 | camera, 122 | triangle_mat3x3_product( 123 | equilateral_triangle(), 124 | mat3x3_product2( 125 | trans_mat(position.x, position.y), 126 | rot_mat(PI * -0.5f + goals->angle), 127 | scale_mat(GOAL_RADIUS))), 128 | goals->colors[goal_index]) < 0) { 129 | return -1; 130 | } 131 | 132 | if (camera_render_debug_text( 133 | camera, 134 | goals->ids[goal_index], 135 | position) < 0) { 136 | return -1; 137 | } 138 | 139 | return 0; 140 | } 141 | 142 | int goals_render(const Goals *goals, 143 | const Camera *camera) 144 | { 145 | trace_assert(goals); 146 | trace_assert(camera); 147 | 148 | for (size_t i = 0; i < goals->count; ++i) { 149 | if (!goals_is_goal_hidden(goals, i)) { 150 | if (goals_render_core(goals, i, camera) < 0) { 151 | return -1; 152 | } 153 | } 154 | } 155 | 156 | return 0; 157 | } 158 | 159 | void goals_update(Goals *goals, 160 | float delta_time) 161 | { 162 | trace_assert(goals); 163 | trace_assert(delta_time > 0.0f); 164 | goals->angle = fmodf(goals->angle + 2.0f * delta_time, 2.0f * PI); 165 | } 166 | 167 | int goals_sound(Goals *goals, 168 | Sound_samples *sound_samples) 169 | { 170 | for (size_t i = 0; i < goals->count; ++i) { 171 | switch (goals->cue_states[i]) { 172 | case CUE_STATE_HIT_NOTHING: 173 | sound_samples_play_sound(sound_samples, 0); 174 | goals->cue_states[i] = CUE_STATE_SEEN_NOTHING; 175 | break; 176 | 177 | default: {} 178 | } 179 | } 180 | 181 | return 0; 182 | } 183 | 184 | void goals_cue(Goals *goals, 185 | const Camera *camera) 186 | { 187 | for (size_t i = 0; i < goals->count; ++i) { 188 | switch (goals->cue_states[i]) { 189 | case CUE_STATE_VIRGIN: 190 | if (goals_is_goal_hidden(goals, i) && camera_is_point_visible(camera, goals->positions[i])) { 191 | goals->cue_states[i] = CUE_STATE_HIT_NOTHING; 192 | } 193 | 194 | break; 195 | 196 | case CUE_STATE_SEEN_NOTHING: 197 | if (!goals_is_goal_hidden(goals, i) && camera_is_point_visible(camera, goals->positions[i])) { 198 | goals->cue_states[i] = CUE_STATE_VIRGIN; 199 | } 200 | break; 201 | 202 | default: {} 203 | } 204 | } 205 | } 206 | 207 | void goals_checkpoint(const Goals *goals, 208 | Player *player) 209 | { 210 | trace_assert(goals); 211 | trace_assert(player); 212 | 213 | for (size_t i = 0; i < goals->count; ++i) { 214 | if (goals->cue_states[i] == CUE_STATE_HIT_NOTHING) { 215 | player_checkpoint(player, goals->positions[i]); 216 | } 217 | } 218 | } 219 | 220 | /* Private Functions */ 221 | 222 | static int goals_is_goal_hidden(const Goals *goals, size_t i) 223 | { 224 | return !goals->visible[i]; 225 | } 226 | 227 | void goals_hide(Goals *goals, char goal_id[ENTITY_MAX_ID_SIZE]) 228 | { 229 | trace_assert(goals); 230 | trace_assert(goal_id); 231 | 232 | for (size_t i = 0; i < goals->count; ++i) { 233 | if (strncmp(goal_id, goals->ids[i], ENTITY_MAX_ID_SIZE) == 0) { 234 | goals->visible[i] = false; 235 | } 236 | } 237 | } 238 | 239 | void goals_show(Goals *goals, char goal_id[ENTITY_MAX_ID_SIZE]) 240 | { 241 | trace_assert(goals); 242 | trace_assert(goal_id); 243 | for (size_t i = 0; i < goals->count; ++i) { 244 | if (strncmp(goal_id, goals->ids[i], ENTITY_MAX_ID_SIZE) == 0) { 245 | goals->visible[i] = true; 246 | } 247 | } 248 | } 249 | --------------------------------------------------------------------------------