├── src ├── stb_image.c ├── random.h ├── vector2d.c ├── vector2d.h ├── input.h ├── quad.h ├── sound.h ├── window.h ├── engine.h ├── movie.h ├── resources.c ├── sound.c ├── shader.h ├── resources.h ├── random.c ├── theater.h ├── main.c ├── text.h ├── state.h ├── minigame.h ├── scene.h ├── dressup.h ├── menu.h ├── kumashoot.h ├── texture.c ├── sprite.h ├── text.c ├── input.c ├── minigame.c ├── window.c ├── engine.c ├── animations.h ├── animations.c ├── movie.c ├── sprite.c ├── shader.c ├── cvector.h ├── state.c ├── scene.c ├── theater.c ├── texture.h ├── cJSON.h ├── dressup.c └── menu.c ├── .clang-format ├── .gitmodules ├── .gitignore ├── .github └── workflows │ └── ci.yml ├── README.md ├── CMakeLists.txt └── scripts ├── extract_n_decompress.py ├── lzss.c └── make_game_assets.py /src/stb_image.c: -------------------------------------------------------------------------------- 1 | #define STB_IMAGE_IMPLEMENTATION 2 | #include "stb_image.h" 3 | -------------------------------------------------------------------------------- /src/random.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | unsigned int random_unsigned_int(); 4 | int random_int(); 5 | int random_int_in_range(int start, int end); 6 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: LLVM 2 | IndentWidth: 8 3 | UseTab: Always 4 | BreakBeforeBraces: Linux 5 | AllowShortIfStatementsOnASingleLine: false 6 | IndentCaseLabels: false 7 | -------------------------------------------------------------------------------- /src/vector2d.c: -------------------------------------------------------------------------------- 1 | #include "vector2d.h" 2 | 3 | #include 4 | 5 | float dist_between(Vector2D v1, Vector2D v2) 6 | { 7 | return sqrt(pow(v2.x - v1.x, 2) + pow(v2.y - v1.y, 2)); 8 | } 9 | -------------------------------------------------------------------------------- /src/vector2d.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | typedef struct { 6 | GLfloat x; 7 | GLfloat y; 8 | } Vector2D; 9 | 10 | float dist_between(Vector2D v1, Vector2D v2); 11 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "external/glfw"] 2 | path = external/glfw 3 | url = https://github.com/glfw/glfw.git 4 | [submodule "external/glew-cmake"] 5 | path = external/glew-cmake 6 | url = https://github.com/Perlmint/glew-cmake.git 7 | -------------------------------------------------------------------------------- /src/input.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | void handle_menu_click(GLFWwindow *window, int button, int action, int mods); 6 | void handle_minigame_event(GLFWwindow *window, int button, int action, int mods); 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | res/ 2 | build/ 3 | lib/ 4 | external/mpv 5 | .vs/ 6 | .cache/ 7 | scripts/* 8 | !scripts/lzss.c 9 | !scripts/make_game_assets.py 10 | !scripts/extract_n_decompress.py 11 | scripts/binaries 12 | .dir-locals.el 13 | embedded.* -------------------------------------------------------------------------------- /src/quad.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "sprite.h" 4 | 5 | void generate_quad_indices(GLuint *buffer, unsigned int index_count); 6 | unsigned int get_quad_index_count(unsigned int quad_count); 7 | GLfloat *get_sprite_vertices(GLfloat *buffer, Sprite *sprite); 8 | -------------------------------------------------------------------------------- /src/sound.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "miniaudio.h" 4 | 5 | typedef enum { 6 | SND_110, 7 | SND_111, 8 | SND_112, 9 | SND_113, 10 | SND_114, 11 | SND_115, 12 | SND_116, 13 | SND_117, 14 | SND_118, 15 | SND_119, 16 | SND_120, 17 | } SoundID; 18 | 19 | void play_sound(ma_engine *audio_engine, SoundID id); 20 | int sounds_init(ma_engine *audio_engine); 21 | -------------------------------------------------------------------------------- /src/window.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "engine.h" 4 | 5 | #define COLLAPSED_MENU_WIDTH 161 6 | #define COLLAPSED_MENU_HEIGHT 74 7 | 8 | #define EXPANDED_MENU_WIDTH 196 9 | #define EXPANDED_MENU_HEIGHT 196 10 | 11 | #define MINIGAME_WIDTH 600 12 | #define MINIGAME_HEIGHT 400 13 | 14 | int make_window(GLFWwindow **window, int width, int height, char *name, 15 | GLFWwindow *shared_ctx, _Bool centered); 16 | -------------------------------------------------------------------------------- /src/engine.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "menu.h" 4 | #include "minigame.h" 5 | #include "scene.h" 6 | #include "state.h" 7 | 8 | typedef struct engine { 9 | Resources resources; 10 | 11 | GLFWwindow *main_window; 12 | Menu menu; 13 | 14 | GLFWwindow *minigame_window; 15 | Minigame minigame; 16 | 17 | GameState game_state; 18 | } Engine; 19 | 20 | int engine_init(Engine *engine); 21 | void engine_stop(Engine *engine); 22 | void engine_run(Engine *engine); 23 | -------------------------------------------------------------------------------- /src/movie.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "shader.h" 9 | 10 | typedef struct { 11 | mpv_handle *mpv_handle; 12 | mpv_render_context *mpv_render_ctx; 13 | GLuint texture_buffer; 14 | GLuint FBO; 15 | GLuint VBO; 16 | GLuint VAO; 17 | } Movie; 18 | 19 | int movie_init(Movie *movie); 20 | _Bool movie_render(ShaderProgram shader, Movie *movie); 21 | void movie_free(Movie *movie); 22 | -------------------------------------------------------------------------------- /src/resources.c: -------------------------------------------------------------------------------- 1 | #include "resources.h" 2 | #include "sound.h" 3 | #include "texture.h" 4 | 5 | #include 6 | #include 7 | 8 | int init_resources(Resources *resources) 9 | { 10 | textures_init(resources); 11 | 12 | if (!shaders_init(resources->shaders)) { 13 | printf("Failed to initialize shaders.\n"); 14 | return 0; 15 | }; 16 | 17 | fonts_init(resources); 18 | 19 | animations_init(resources); 20 | 21 | if (!sounds_init(&resources->audio_engine)) { 22 | return 0; 23 | }; 24 | 25 | return 1; 26 | } 27 | -------------------------------------------------------------------------------- /src/sound.c: -------------------------------------------------------------------------------- 1 | #define MINIAUDIO_IMPLEMENTATION 2 | #include "miniaudio.h" 3 | 4 | #include "sound.h" 5 | 6 | void play_sound(ma_engine *audio_engine, SoundID id) 7 | { 8 | char file_path[32]; 9 | sprintf(file_path, "./res/sounds/%d.wav", id); 10 | ma_engine_play_sound(audio_engine, file_path, NULL); 11 | } 12 | 13 | int sounds_init(ma_engine *audio_engine) 14 | { 15 | if (ma_engine_init(NULL, audio_engine) != MA_SUCCESS) { 16 | printf("Failed to initialize audio engine.\n"); 17 | return 0; 18 | }; 19 | 20 | return 1; 21 | } 22 | -------------------------------------------------------------------------------- /src/shader.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #define MAX_SHADER_COUNT 8 8 | 9 | enum { SPRITE_SHADER, MOVIE_SHADER, BARRIER_SHADER }; 10 | 11 | typedef GLuint ShaderProgram; 12 | 13 | int shaders_init(ShaderProgram *shaders); 14 | void shader_program_set_texture_samplers(ShaderProgram program, 15 | const GLint *samplers, 16 | const GLint sampler_count); 17 | void shader_program_set_mat4(ShaderProgram program, const GLchar *name, 18 | const GLfloat mat4[4][4]); 19 | -------------------------------------------------------------------------------- /src/resources.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "animations.h" 4 | #include "cJSON.h" 5 | #include "shader.h" 6 | #include "sound.h" 7 | #include "text.h" 8 | #include "miniaudio.h" 9 | 10 | typedef struct resources { 11 | ShaderProgram shaders[MAX_SHADER_COUNT]; 12 | Font fonts[MAX_FONT_COUNT]; 13 | Texture textures[MAX_TEXTURE_COUNT]; 14 | cJSON *animation_data; 15 | Animation animations[MAX_ANIMATION_COUNT]; 16 | TheaterAnimation theater_animations[MAX_THEATER_ANIMATION_COUNT]; 17 | ma_engine audio_engine; 18 | } Resources; 19 | 20 | int init_resources(Resources *resources); 21 | -------------------------------------------------------------------------------- /src/random.c: -------------------------------------------------------------------------------- 1 | // original game uses its own deterministic rng 2 | // this is the decompiled version of it 3 | 4 | static int weird_global = 1; 5 | 6 | unsigned int random_unsigned_int() 7 | { 8 | weird_global = weird_global * 214013 + 2531011; 9 | return weird_global >> 0x10 & 0x7fff; 10 | } 11 | 12 | int random_int() 13 | { 14 | unsigned int uVar1; 15 | unsigned int uVar2; 16 | 17 | uVar1 = random_unsigned_int(); 18 | uVar2 = random_unsigned_int(); 19 | return uVar2 * ((-(unsigned int)((uVar1 & 1) != 0) & 2) - 1); 20 | } 21 | 22 | int random_int_in_range(int start, int end) 23 | { 24 | int iVar1; 25 | int iVar2; 26 | 27 | do { 28 | do { 29 | iVar1 = random_int(); 30 | iVar2 = iVar1 % (end - start); 31 | } while (iVar2 < start); 32 | } while (end < iVar2); 33 | 34 | return iVar2; 35 | } 36 | -------------------------------------------------------------------------------- /src/theater.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "animations.h" 4 | #include "menu.h" 5 | #include "texture.h" 6 | #include "vector2d.h" 7 | #include "movie.h" 8 | 9 | typedef enum { 10 | THEATER_CLASSROOM, 11 | THEATER_SCHOOL, 12 | THEATER_LAIN_ROOM_NIGHT, 13 | THEATER_ARISU_ROOM, 14 | THEATER_CYBERIA, 15 | THEATER_STREET, 16 | THEATER_BRIDGE, 17 | THEATER_MOVIE 18 | } TheaterType; 19 | 20 | typedef struct { 21 | TheaterType type; 22 | 23 | uint8_t layer_count; 24 | Sprite layers[5]; 25 | 26 | double last_updated; 27 | Scene scene; 28 | 29 | Movie movie; 30 | } Theater; 31 | 32 | struct minigame; 33 | 34 | int start_theater(Menu *menu, Resources *resources, GameState *game_state, 35 | struct minigame *minigame, GLFWwindow **minigame_window, 36 | GLFWwindow *main_window); 37 | void update_theater(Resources *resources, Menu *menu, GameState *game_state, 38 | GLFWwindow *window, struct minigame *minigame); 39 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "engine.h" 4 | 5 | static void error_callback(int error, const char *description) 6 | { 7 | fprintf(stderr, "Code: %d\nError: %s\n", error, description); 8 | } 9 | 10 | static int init_glfw() 11 | { 12 | if (!glfwInit()) { 13 | printf("Failed to initialize glfw.\n"); 14 | return 0; 15 | } 16 | 17 | glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); 18 | glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); 19 | glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); 20 | glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); 21 | glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); 22 | 23 | glfwSetErrorCallback(error_callback); 24 | 25 | return 1; 26 | } 27 | 28 | int main(void) 29 | { 30 | if (!init_glfw()) { 31 | return -1; 32 | } 33 | 34 | Engine engine; 35 | if (!engine_init(&engine)) { 36 | printf("Failed to initialize engine. Exiting.\n"); 37 | return -1; 38 | }; 39 | 40 | engine_run(&engine); 41 | 42 | engine_stop(&engine); 43 | 44 | return 0; 45 | } 46 | -------------------------------------------------------------------------------- /src/text.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "sprite.h" 6 | 7 | #define MAX_FONT_COUNT 2 8 | #define MAX_TEXT_LENGTH 20 9 | 10 | enum { FONT_RED, FONT_WHITE }; 11 | 12 | typedef struct { 13 | Texture *texture; 14 | float letter_spacing; 15 | Vector2D glyph_texture_size; 16 | const uint16_t *glyph_order; 17 | } Font; 18 | 19 | typedef struct { 20 | Vector2D pos; 21 | Vector2D origin_pos; // keeps track of initially passed position. 22 | _Bool visible; 23 | _Bool left_aligned; // text rendered with left-aligned set to true 24 | // expands to the left as more letters get added. 25 | // i dont know a better name for it. 26 | Vector2D glyph_size; 27 | unsigned int texture_index; 28 | Font *font; 29 | char current_text[MAX_TEXT_LENGTH]; 30 | } Text; 31 | 32 | struct resources; 33 | 34 | void fonts_init(struct resources *resources); 35 | void update_text(Text *text_obj, char *new_text); 36 | GLfloat *get_glyph_vertices(GLfloat *buffer, Text *text_obj, char letter, 37 | uint8_t nth); 38 | -------------------------------------------------------------------------------- /src/state.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "resources.h" 4 | #include "texture.h" 5 | #include "vector2d.h" 6 | 7 | typedef enum { NO_TOOLS, HOLDING_SCREWDRIVER, HOLDING_NAVI } LainToolState; 8 | 9 | typedef enum { 10 | OUTFIT_DEFAULT, 11 | OUTFIT_SCHOOL, 12 | OUTFIT_CYBERIA, 13 | OUTFIT_BEAR, 14 | OUTFIT_SWEATER, 15 | OUTFIT_ALIEN, 16 | } LainOutfit; 17 | 18 | typedef struct { 19 | LainToolState tool_state; 20 | LainOutfit outfit; 21 | Texture *standing_texture; 22 | int walk_animation; 23 | int leave_animation; 24 | } Lain; 25 | 26 | typedef struct { 27 | long score; 28 | double time; 29 | Lain lain; 30 | int current_theater_preview; 31 | _Bool school_outfit_unlocked; 32 | _Bool cyberia_outfit_unlocked; 33 | _Bool sweater_outfit_unlocked; 34 | _Bool bear_outfit_unlocked; 35 | _Bool alien_outfit_unlocked; 36 | _Bool screwdriver_unlocked; 37 | _Bool navi_unlocked; 38 | } GameState; 39 | 40 | struct engine; 41 | void reset_game_state(Resources *resources, GameState *game_state); 42 | int init_game_state(Resources *resources, GameState *game_state); 43 | int write_save_file(struct engine *engine); 44 | int load_save_file(Resources *resources, GameState *game_state); 45 | -------------------------------------------------------------------------------- /src/minigame.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "dressup.h" 4 | #include "kumashoot.h" 5 | #include "menu.h" 6 | #include "scene.h" 7 | #include "state.h" 8 | #include "theater.h" 9 | 10 | typedef enum { NO_MINIGAME, KUMASHOOT, DRESSUP, THEATER } MinigameType; 11 | 12 | typedef struct minigame { 13 | 14 | MinigameType type; 15 | 16 | union { 17 | KumaShoot kumashoot; 18 | DressUp dressup; 19 | Theater theater; 20 | } current; 21 | 22 | double last_updated; 23 | 24 | MinigameType queued_minigame; 25 | } Minigame; 26 | 27 | void destroy_minigame(Texture *textures, Menu *menu, Minigame *minigame, 28 | GLFWwindow *minigame_window); 29 | void update_minigame(Resources *resources, GameState *game_state, Menu *menu, 30 | GLFWwindow *minigame_window, Minigame *minigame); 31 | void draw_minigame(Resources *resources, GLFWwindow *minigame_window, 32 | Minigame *minigame); 33 | void start_queued_minigame(Resources *resources, GameState *game_state, 34 | Menu *menu, GLFWwindow *main_window, 35 | GLFWwindow **minigame_window, Minigame *minigame); 36 | void get_minigame_scene(Minigame *minigame, Scene *target); 37 | _Bool can_refresh(double time, Minigame *minigame); 38 | -------------------------------------------------------------------------------- /src/scene.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "cvector.h" 4 | #include "shader.h" 5 | #include "sprite.h" 6 | #include "text.h" 7 | #include "texture.h" 8 | 9 | #include "input.h" 10 | #include "vector2d.h" 11 | 12 | typedef struct { 13 | Sprite *sprite; 14 | int click_event; 15 | // in some cases sprite is contained within a parent object. if we need 16 | // access to that we can store it here 17 | void *object; 18 | } SpriteBehavior; 19 | 20 | typedef struct { 21 | int left, top, right, bottom; 22 | } ClickBarrier; 23 | 24 | typedef struct { 25 | _Bool draw_barriers; 26 | 27 | GLuint VAO; 28 | GLuint VBO; 29 | GLuint IBO; 30 | unsigned int quad_count; 31 | cvector_vector_type(Sprite *) sprites; 32 | cvector_vector_type(ClickBarrier) click_barriers; 33 | cvector_vector_type(Text *) text_objects; 34 | cvector_vector_type(SpriteBehavior) sprite_behaviors; 35 | } Scene; 36 | 37 | void init_scene(Scene *scene, Sprite **sprites, uint8_t sprite_count, 38 | SpriteBehavior *sprite_behaviors, uint8_t sprite_behavior_count, 39 | Text **text_objs, uint8_t text_obj_count, 40 | ClickBarrier *click_barriers, uint8_t click_barrier_count); 41 | void update_scene(Scene *scene); 42 | void free_scene(Scene *scene); 43 | void draw_scene(Scene *scene, GLFWwindow *window, ShaderProgram *shaders); 44 | 45 | -------------------------------------------------------------------------------- /src/dressup.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "menu.h" 4 | 5 | typedef enum { ITEM_GRAB, ITEM_RELEASE, LAIN_SWAP_CLOTHING } DressUpEvent; 6 | 7 | typedef struct { 8 | enum { STANDING, ENTERING, LEAVING } move_state; 9 | Sprite sprite; 10 | } DressUpLain; 11 | 12 | typedef struct { 13 | enum { SCREWDRIVER, NAVI, CLOTHING } type; 14 | Sprite sprite; 15 | LainOutfit outfit; 16 | } DressUpObject; 17 | 18 | typedef struct { 19 | Scene scene; 20 | 21 | DressUpLain lain; 22 | 23 | DressUpObject *currently_grabbed; 24 | 25 | DressUpObject school_outfit; 26 | DressUpObject bear_outfit; 27 | DressUpObject sweater_outfit; 28 | DressUpObject cyberia_outfit; 29 | DressUpObject ufo; 30 | DressUpObject navi; 31 | DressUpObject screwdriver; 32 | 33 | Sprite background; 34 | } DressUp; 35 | 36 | struct minigame; 37 | struct engine; 38 | 39 | void lain_set_outfit(Resources *resources, LainOutfit outfit, Lain *lain); 40 | int start_dressup(Menu *menu, Resources *resources, GameState *game_state, 41 | struct minigame *minigame, GLFWwindow **minigame_window, 42 | GLFWwindow *main_window); 43 | void handle_dressup_event(DressUpEvent event, void *object, 44 | struct engine *engine); 45 | void update_dressup(Resources *resources, Menu *menu, GameState *game_state, 46 | GLFWwindow *window, struct minigame *minigame); 47 | -------------------------------------------------------------------------------- /src/menu.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "resources.h" 4 | #include "scene.h" 5 | #include "state.h" 6 | 7 | typedef enum { LAUGH, BLINK, LAUGH_BLINK } MenuLainAnimation; 8 | 9 | typedef struct { 10 | _Bool recently_changed_laugh; 11 | _Bool laughing; 12 | _Bool blinking; 13 | int laugh_quarter; 14 | Sprite sprite; 15 | } MenuLain; 16 | 17 | // i think ideally youd have a separate contextual processor 18 | // for _CLICK events and then dispatch actions accordingly 19 | // but in our case its so simple might aswell merge them together 20 | typedef enum { 21 | MAIN_UI_BAR_CLICK, 22 | TOGGLE_THEATER_PREVIEW, 23 | TOGGLE_SCORE_PREVIEW, 24 | BEAR_ICON_CLICK, 25 | DRESSUP_TOGGLE, 26 | THEATER_TOGGLE 27 | } MenuEvent; 28 | 29 | typedef struct { 30 | struct tm *current_time; 31 | 32 | _Bool collapsed; 33 | 34 | Scene scene; 35 | 36 | MenuLain ui_lain; 37 | 38 | Sprite main_ui; 39 | Sprite main_ui_bar; 40 | Sprite dressup_button; 41 | Sprite theater_button; 42 | Sprite bear_icon; 43 | Sprite screwdriver_icon; 44 | Sprite paw_icon; 45 | Sprite theater_preview; 46 | Sprite score_preview; 47 | Sprite background; 48 | 49 | Text clock; 50 | Text score_text; 51 | } Menu; 52 | 53 | struct engine; 54 | 55 | void init_menu(Menu *menu, GameState *game_state, Resources *resources); 56 | void update_menu(Menu *menu, GameState *game_state, GLFWwindow *window, 57 | Resources *resources); 58 | void handle_menu_event(MenuEvent event, struct engine *engine); 59 | -------------------------------------------------------------------------------- /src/kumashoot.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "menu.h" 4 | #include "scene.h" 5 | #include "sprite.h" 6 | 7 | typedef enum { CHARACTER_CLICK } KumaShootEvent; 8 | 9 | typedef enum { BEAR_WHITE, BEAR_BROWN } BearType; 10 | 11 | typedef enum { 12 | NO_CHARACTER, 13 | YASUO, 14 | MIHO, 15 | MIKA, 16 | SCHOOL_LAIN_STANDING, 17 | SCHOOL_LAIN, 18 | DEFAULT_LAIN, 19 | SCREWDRIVER_LAIN, 20 | } CharacterType; 21 | 22 | typedef struct { 23 | _Bool scored; 24 | _Bool is_smoke; 25 | _Bool has_additional_sprite; 26 | _Bool exploded; 27 | 28 | CharacterType type; 29 | 30 | Sprite sprite; 31 | Sprite additional_sprite; 32 | 33 | double time_revealed; 34 | double time_scored; 35 | 36 | int score_value; 37 | } Character; 38 | 39 | typedef struct { 40 | _Bool needs_reset; 41 | _Bool revealed; 42 | _Bool is_smoke; 43 | 44 | Character hidden_character; 45 | BearType type; 46 | 47 | Sprite sprite; 48 | 49 | int vel_x, vel_y; 50 | } Bear; 51 | 52 | typedef struct { 53 | Scene scene; 54 | 55 | Bear bears[3]; 56 | Text score_displays[3]; 57 | 58 | Sprite background; 59 | Sprite bush_overlay; 60 | } KumaShoot; 61 | 62 | struct minigame; 63 | struct engine; 64 | 65 | int start_kumashoot(Menu *menu, Resources *resources, GameState *game_state, 66 | struct minigame *minigame, GLFWwindow **minigame_window, 67 | GLFWwindow *main_window); 68 | void handle_kumashoot_event(KumaShootEvent event, Bear *bear, struct engine *engine); 69 | void update_kumashoot(Resources *resources, Menu *menu, GameState *game_state, 70 | GLFWwindow *window, struct minigame *minigame); 71 | -------------------------------------------------------------------------------- /src/texture.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "resources.h" 4 | #include "sprite.h" 5 | #include "stb_image.h" 6 | #include "texture.h" 7 | 8 | static int init_texture(Texture *texture, TextureID texture_id) 9 | { 10 | texture->id = texture_id; 11 | 12 | stbi_set_flip_vertically_on_load(true); 13 | 14 | int width, height, nr_channels; 15 | 16 | char file_path[32]; 17 | sprintf(file_path, "./res/sprites/%d.png", texture_id); 18 | unsigned char *data = 19 | stbi_load(file_path, &width, &height, &nr_channels, 0); 20 | 21 | if (data == NULL) { 22 | printf("Failed to load texture.\n"); 23 | return 0; 24 | } 25 | 26 | texture->size = (Vector2D){width, height}; 27 | 28 | glGenTextures(1, &texture->gl_id); 29 | glBindTexture(GL_TEXTURE_2D, texture->gl_id); 30 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texture->size.x, 31 | texture->size.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); 32 | glGenerateMipmap(GL_TEXTURE_2D); 33 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 34 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 35 | 36 | stbi_image_free(data); 37 | 38 | return 1; 39 | } 40 | 41 | Texture *texture_get(Resources *resources, int texture_id) 42 | { 43 | Texture *texture = &resources->textures[texture_id]; 44 | 45 | if (!texture->gl_id) { 46 | if (!init_texture(texture, texture_id)) { 47 | printf("Failed to initialize texture %d.\n", 48 | texture_id); 49 | // TODO not sure what to do here 50 | }; 51 | } 52 | 53 | return texture; 54 | } 55 | 56 | void textures_init(Resources *resources) 57 | { 58 | for (int i = 0; i < MAX_TEXTURE_COUNT; i++) { 59 | resources->textures[i] = (Texture){0}; 60 | }; 61 | } 62 | -------------------------------------------------------------------------------- /src/sprite.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "animations.h" 4 | #include "sprite.h" 5 | #include "texture.h" 6 | 7 | #include 8 | #include 9 | 10 | #define SPRITE_VERTEX_COUNT 4 11 | #define SPRITE_INDEX_COUNT 6 12 | #define ROWS_PER_SPRITE_VERTEX 5 13 | 14 | #define SPRITE_VBO_SIZE SPRITE_VERTEX_COUNT *ROWS_PER_SPRITE_VERTEX 15 | 16 | typedef struct { 17 | _Bool visible; 18 | _Bool mirrored; 19 | 20 | Vector2D pos; 21 | Vector2D origin_pos; // keeps track of initially passed position. 22 | unsigned int z_index; 23 | Texture *texture; 24 | int texture_index; 25 | Vector2D hitbox_size; 26 | _Bool pivot_centered; // if true, sprite's position points to its 27 | // center. else, it points to the top left corner. 28 | Animation *animation; 29 | AnimationFrame *animation_frame; 30 | double animation_start_time; 31 | } Sprite; 32 | 33 | struct resources; 34 | 35 | void depth_sort(Sprite **sprites, unsigned int sprite_count); 36 | _Bool is_sprite_within_bounds(const Sprite *sprite, const Vector2D point); 37 | Vector2D get_sprite_center_coords(const Sprite *sprite); 38 | GLfloat *get_sprite_vertices(GLfloat *buffer, Sprite *sprite); 39 | GLfloat *get_pivot_centered_sprite_vertices(GLfloat *buffer, Sprite *sprite); 40 | void sprite_try_next_frame(struct resources *resources, double now, Sprite *sprite); 41 | void sprite_set_animation(struct resources *resources, double now, 42 | Sprite *sprite, AnimationID animation_id); 43 | void sprite_set_animation_direct(struct resources *resources, double now, 44 | Sprite *sprite, Animation *animation); 45 | void get_hitbox_range(Vector2D pos, Vector2D hitbox_size, float *top, 46 | float *left, float *right, float *bottom); 47 | void sprite_set_to_origin_pos(Sprite *sprite); 48 | void make_sprite(Sprite *target, Sprite sprite); 49 | _Bool sprite_animation_is_last_frame(Sprite *sprite); 50 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: master 6 | tags: 'v*' 7 | pull_request: 8 | branches: master 9 | 10 | defaults: 11 | run: 12 | shell: bash 13 | 14 | jobs: 15 | build: 16 | strategy: 17 | matrix: 18 | config: 19 | - { name: "Windows", os: windows-2022, mpv_version: 20211226-git-d92cf77 } 20 | - { name: "macOS", os: macos-latest } 21 | - { name: "Ubuntu", os: ubuntu-20.04 } 22 | fail-fast: false 23 | 24 | name: ${{ matrix.config.name }} 25 | runs-on: ${{ matrix.config.os }} 26 | 27 | steps: 28 | - name: Checkout 29 | uses: actions/checkout@v2 30 | with: 31 | submodules: recursive 32 | 33 | - name: Setup MSVC Toolchain [Windows] 34 | if: ${{ runner.os == 'Windows' }} 35 | uses: seanmiddleditch/gha-setup-vsdevenv@v3 36 | 37 | - name: Install Dependencies [Windows] 38 | if: ${{ runner.os == 'Windows' }} 39 | run: | 40 | curl -Ls 'https://sourceforge.net/projects/mpv-player-windows/files/libmpv/mpv-dev-x86_64-${{ matrix.config.mpv_version }}.7z/download' > mpv-dev-x86_64-${{ matrix.config.mpv_version }}.7z 41 | mkdir external/mpv 42 | pushd external/mpv 43 | 7z x ../../mpv-dev-x86_64-${{ matrix.config.mpv_version }}.7z 44 | lib /def:mpv.def /name:mpv-2.dll /out:mpv.lib /MACHINE:X64 45 | cd include 46 | mkdir mpv 47 | mv *.h mpv/ 48 | popd 49 | 50 | - name: Install Dependencies [macOS] 51 | if: ${{ runner.os == 'macOS' }} 52 | run: | 53 | export HOMEBREW_NO_INSTALL_CLEANUP=1 54 | brew update 55 | brew install pkg-config glew glfw mpv 56 | 57 | - name: Install Dependencies [Linux] 58 | if: ${{ runner.os == 'Linux' }} 59 | run: | 60 | sudo apt update 61 | sudo apt install libglew-dev libglfw3-dev libmpv-dev 62 | 63 | - name: Configure 64 | run: | 65 | # Create fake assets dir so build works 66 | mkdir res 67 | 68 | if [ ! "${{ runner.os }}" == "Windows" ]; then 69 | export WITH_SYSTEM_LIBS=ON 70 | else 71 | export WITH_SYSTEM_LIBS=OFF 72 | fi 73 | 74 | cmake \ 75 | -B ${GITHUB_WORKSPACE}/build \ 76 | -DCMAKE_BUILD_TYPE=Release \ 77 | -DSYSTEM_GLFW=${WITH_SYSTEM_LIBS} -DSYSTEM_GLEW=${WITH_SYSTEM_LIBS} 78 | 79 | - name: Build 80 | run: | 81 | cmake \ 82 | --build ${GITHUB_WORKSPACE}/build \ 83 | --config Release \ 84 | --parallel 2 85 | 86 | # - name: Install 87 | # run: | 88 | # cmake \ 89 | # --install ${GITHUB_WORKSPACE}/build \ 90 | # --config Release 91 | -------------------------------------------------------------------------------- /src/text.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "resources.h" 7 | #include "scene.h" 8 | #include "sprite.h" 9 | #include "text.h" 10 | #include "vector2d.h" 11 | 12 | static const uint16_t white_glyphs_order[256] = { 13 | ['A'] = 0, ['P'] = 1, ['0'] = 2, ['1'] = 3, ['2'] = 4, 14 | ['3'] = 5, ['4'] = 6, ['5'] = 7, ['6'] = 8, ['7'] = 9, 15 | ['8'] = 10, ['9'] = 11, [':'] = 12}; 16 | 17 | static const uint16_t red_glyphs_order[256] = { 18 | ['0'] = 0, ['1'] = 1, ['2'] = 2, ['3'] = 3, ['4'] = 4, ['5'] = 5, 19 | ['6'] = 6, ['7'] = 7, ['8'] = 8, ['9'] = 9, ['-'] = 10}; 20 | 21 | void fonts_init(Resources *resources) 22 | { 23 | resources->fonts[FONT_RED] = 24 | (Font){.letter_spacing = 10.0f, 25 | .glyph_texture_size = {1.0f / 11.0f, 1.0f}, 26 | .glyph_order = red_glyphs_order, 27 | .texture = texture_get(resources, RED_FONT_SPRITESHEET)}; 28 | 29 | resources->fonts[FONT_WHITE] = 30 | (Font){.letter_spacing = 11.4f, 31 | .glyph_texture_size = {1.0f / 13.0f, 1.0f}, 32 | .glyph_order = white_glyphs_order, 33 | .texture = texture_get(resources, WHITE_FONT_SPRITESHEET)}; 34 | } 35 | 36 | void update_text(Text *text_obj, char *new_text) 37 | { 38 | int text_len = strlen(new_text); 39 | if (text_obj->left_aligned) { 40 | float x_pad = ((text_len - 1) * text_obj->glyph_size.x); 41 | 42 | text_obj->pos.x = text_obj->origin_pos.x - x_pad; 43 | } 44 | 45 | memset(text_obj->current_text, 0, 20); 46 | memcpy(text_obj->current_text, new_text, sizeof(char) * text_len); 47 | } 48 | 49 | GLfloat *get_glyph_vertices(GLfloat *buffer, Text *text_obj, char letter, 50 | uint8_t nth) 51 | { 52 | GLfloat pos_x = text_obj->pos.x + nth * text_obj->font->letter_spacing; 53 | GLfloat pos_y = text_obj->pos.y; 54 | 55 | GLfloat offset = text_obj->font->glyph_order[(int)letter]; 56 | 57 | Vector2D glyph_size = text_obj->glyph_size; 58 | Vector2D glyph_texture_size = text_obj->font->glyph_texture_size; 59 | 60 | GLfloat vertices[] = { 61 | // top right 62 | pos_x + glyph_size.x, 63 | pos_y, 64 | offset * glyph_texture_size.x + glyph_texture_size.x, 65 | glyph_texture_size.y, 66 | text_obj->texture_index, 67 | 68 | // bottom right 69 | pos_x + glyph_size.x, 70 | pos_y + glyph_size.y, 71 | offset * glyph_texture_size.x + glyph_texture_size.x, 72 | 0.0f, 73 | text_obj->texture_index, 74 | 75 | // bottom left 76 | pos_x, 77 | pos_y + glyph_size.y, 78 | offset * glyph_texture_size.x, 79 | 0.0f, 80 | text_obj->texture_index, 81 | 82 | // top left 83 | pos_x, 84 | pos_y, 85 | offset * glyph_texture_size.x, 86 | glyph_texture_size.y, 87 | text_obj->texture_index, 88 | }; 89 | 90 | memcpy(buffer, vertices, sizeof(vertices)); 91 | buffer += sizeof(vertices) / sizeof(vertices[0]); 92 | return buffer; 93 | } 94 | -------------------------------------------------------------------------------- /src/input.c: -------------------------------------------------------------------------------- 1 | #include "dressup.h" 2 | #include "engine.h" 3 | #include "kumashoot.h" 4 | #include "minigame.h" 5 | #include "scene.h" 6 | #include "sprite.h" 7 | #include "theater.h" 8 | #include "vector2d.h" 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | static _Bool get_behavior(SpriteBehavior *behavior, 15 | SpriteBehavior *sprite_behaviors, Vector2D click_pos) 16 | { 17 | _Bool found = false; 18 | float lowest_dist = FLT_MAX; 19 | uint8_t curr_z_index = 0; 20 | 21 | for (int i = 0; i < cvector_size(sprite_behaviors); i++) { 22 | SpriteBehavior curr_behavior = sprite_behaviors[i]; 23 | Sprite *sprite = sprite_behaviors[i].sprite; 24 | 25 | if (!sprite->visible) { 26 | continue; 27 | } 28 | 29 | if (is_sprite_within_bounds(sprite, click_pos)) { 30 | Vector2D sprite_center = 31 | get_sprite_center_coords(sprite); 32 | 33 | float curr_dist = 34 | dist_between(sprite_center, click_pos); 35 | 36 | if (curr_dist < lowest_dist || 37 | sprite->z_index > curr_z_index) { 38 | found = true; 39 | *behavior = curr_behavior; 40 | lowest_dist = curr_dist; 41 | curr_z_index = sprite->z_index; 42 | } 43 | } 44 | } 45 | 46 | return found; 47 | } 48 | 49 | void handle_menu_click(GLFWwindow *window, int button, int action, int mods) 50 | { 51 | Engine *engine = (Engine *)glfwGetWindowUserPointer(window); 52 | 53 | if (!(button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_PRESS)) { 54 | return; 55 | } 56 | 57 | double x, y; 58 | glfwGetCursorPos(window, &x, &y); 59 | Vector2D click_pos = (Vector2D){x, y}; 60 | 61 | Scene scene = engine->menu.scene; 62 | 63 | SpriteBehavior behavior; 64 | _Bool behavior_found = 65 | get_behavior(&behavior, scene.sprite_behaviors, click_pos); 66 | 67 | if (behavior_found) { 68 | handle_menu_event(behavior.click_event, engine); 69 | } 70 | } 71 | 72 | void handle_minigame_event(GLFWwindow *window, int button, int action, int mods) 73 | { 74 | Engine *engine = (Engine *)glfwGetWindowUserPointer(window); 75 | Minigame *minigame = &engine->minigame; 76 | 77 | if (!(button == GLFW_MOUSE_BUTTON_LEFT) || 78 | (action == GLFW_RELEASE && engine->minigame.type != DRESSUP) || 79 | (minigame->type == THEATER && 80 | minigame->current.theater.type == THEATER_MOVIE)) { 81 | return; 82 | } 83 | 84 | double x, y; 85 | glfwGetCursorPos(window, &x, &y); 86 | Vector2D click_pos = (Vector2D){x, y}; 87 | 88 | Scene scene; 89 | get_minigame_scene(minigame, &scene); 90 | 91 | for (int i = 0; i < cvector_size(scene.click_barriers); i++) { 92 | ClickBarrier barrier = scene.click_barriers[i]; 93 | _Bool is_blocked = (barrier.left <= click_pos.x && 94 | click_pos.x <= barrier.right) && 95 | (barrier.top <= click_pos.y && 96 | click_pos.y <= barrier.bottom); 97 | if (is_blocked) { 98 | return; 99 | } 100 | } 101 | 102 | SpriteBehavior behavior; 103 | _Bool behavior_found = 104 | get_behavior(&behavior, scene.sprite_behaviors, click_pos); 105 | 106 | if (behavior_found) { 107 | int event = behavior.click_event; 108 | void *obj = behavior.object; 109 | 110 | switch (minigame->type) { 111 | case KUMASHOOT: { 112 | handle_kumashoot_event(event, obj, engine); 113 | break; 114 | } 115 | case DRESSUP: { 116 | if (action == GLFW_RELEASE) { 117 | handle_dressup_event(ITEM_RELEASE, NULL, 118 | engine); 119 | } else { 120 | handle_dressup_event(event, obj, engine); 121 | } 122 | break; 123 | } 124 | default: 125 | break; 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/minigame.c: -------------------------------------------------------------------------------- 1 | #include "minigame.h" 2 | #include "animations.h" 3 | #include "resources.h" 4 | #include "scene.h" 5 | #include "theater.h" 6 | 7 | #include 8 | #include 9 | 10 | void destroy_minigame(Texture *textures, Menu *menu, Minigame *minigame, 11 | GLFWwindow *minigame_window) 12 | { 13 | menu->bear_icon.texture = &textures[BEAR_ICON]; 14 | menu->dressup_button.texture = &textures[DRESSUP_BUTTON]; 15 | 16 | glfwDestroyWindow(minigame_window); 17 | 18 | switch (minigame->type) { 19 | case KUMASHOOT: 20 | free_scene(&minigame->current.kumashoot.scene); 21 | break; 22 | case DRESSUP: 23 | free_scene(&minigame->current.dressup.scene); 24 | break; 25 | case THEATER: { 26 | Theater *theater = &minigame->current.theater; 27 | if (theater->type == THEATER_MOVIE) { 28 | movie_free(&theater->movie); 29 | } else { 30 | free_scene(&minigame->current.theater.scene); 31 | } 32 | 33 | break; 34 | } 35 | default: 36 | break; 37 | } 38 | 39 | minigame->type = NO_MINIGAME; 40 | } 41 | 42 | void update_minigame(Resources *resources, GameState *game_state, Menu *menu, 43 | GLFWwindow *minigame_window, Minigame *minigame) 44 | { 45 | switch (minigame->type) { 46 | case KUMASHOOT: 47 | update_kumashoot(resources, menu, game_state, minigame_window, 48 | minigame); 49 | break; 50 | case DRESSUP: 51 | update_dressup(resources, menu, game_state, minigame_window, 52 | minigame); 53 | break; 54 | case THEATER: 55 | update_theater(resources, menu, game_state, minigame_window, 56 | minigame); 57 | break; 58 | default: 59 | break; 60 | } 61 | } 62 | 63 | void draw_minigame(Resources *resources, GLFWwindow *minigame_window, 64 | Minigame *minigame) 65 | { 66 | switch (minigame->type) { 67 | case KUMASHOOT: 68 | draw_scene(&minigame->current.kumashoot.scene, minigame_window, 69 | resources->shaders); 70 | break; 71 | case DRESSUP: 72 | draw_scene(&minigame->current.dressup.scene, minigame_window, 73 | resources->shaders); 74 | break; 75 | case THEATER: { 76 | Theater *theater = &minigame->current.theater; 77 | if (theater->type != THEATER_MOVIE) { 78 | draw_scene(&theater->scene, minigame_window, 79 | resources->shaders); 80 | } 81 | break; 82 | } 83 | default: 84 | break; 85 | } 86 | } 87 | 88 | void start_queued_minigame(Resources *resources, GameState *game_state, 89 | Menu *menu, GLFWwindow *main_window, 90 | GLFWwindow **minigame_window, Minigame *minigame) 91 | { 92 | // NOTE: 93 | // start functions here return 0 if they fail 94 | // i am not quite sure what to do in that case :D 95 | switch (minigame->queued_minigame) { 96 | case KUMASHOOT: 97 | start_kumashoot(menu, resources, game_state, minigame, 98 | minigame_window, main_window); 99 | break; 100 | case DRESSUP: 101 | start_dressup(menu, resources, game_state, minigame, 102 | minigame_window, main_window); 103 | break; 104 | case THEATER: 105 | start_theater(menu, resources, game_state, minigame, 106 | minigame_window, main_window); 107 | break; 108 | default: 109 | break; 110 | } 111 | minigame->queued_minigame = NO_MINIGAME; 112 | } 113 | 114 | _Bool can_refresh(double time, Minigame *minigame) 115 | { 116 | return time - minigame->last_updated > 1.0 / 25.0; 117 | } 118 | 119 | void get_minigame_scene(Minigame *minigame, Scene *target) 120 | { 121 | switch (minigame->type) { 122 | case KUMASHOOT: 123 | *target = minigame->current.kumashoot.scene; 124 | break; 125 | case DRESSUP: 126 | *target = minigame->current.dressup.scene; 127 | break; 128 | case THEATER: 129 | *target = minigame->current.theater.scene; 130 | break; 131 | default: 132 | break; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/window.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "stb_image.h" 5 | 6 | #include "menu.h" 7 | #include "scene.h" 8 | #include "window.h" 9 | 10 | #define MIN(a, b) (((a) < (b)) ? (a) : (b)) 11 | #define MAX(a, b) (((a) > (b)) ? (a) : (b)) 12 | 13 | static void GLAPIENTRY gl_debug_message_callback(GLenum source, GLenum type, 14 | GLuint id, GLenum severity, 15 | GLsizei length, 16 | const GLchar *message, 17 | const void *user_param) 18 | { 19 | fprintf(stderr, "\ntype = 0x%x, severity = 0x%x, message = %s\n", type, 20 | severity, message); 21 | } 22 | 23 | static void set_window_icon(GLFWwindow *window) 24 | { 25 | stbi_set_flip_vertically_on_load(false); 26 | 27 | GLFWimage images[1]; 28 | images[0].pixels = stbi_load("./res/window_icon.png", &images[0].width, 29 | &images[0].height, 0, 4); 30 | glfwSetWindowIcon(window, 1, images); 31 | stbi_image_free(images[0].pixels); 32 | } 33 | 34 | static GLFWmonitor *get_current_monitor(GLFWwindow *window) 35 | { 36 | int nmonitors, i; 37 | int wx, wy, ww, wh; 38 | int mx, my, mw, mh; 39 | int overlap, bestoverlap; 40 | GLFWmonitor *bestmonitor; 41 | GLFWmonitor **monitors; 42 | const GLFWvidmode *mode; 43 | 44 | bestoverlap = 0; 45 | bestmonitor = NULL; 46 | 47 | glfwGetWindowPos(window, &wx, &wy); 48 | glfwGetWindowSize(window, &ww, &wh); 49 | monitors = glfwGetMonitors(&nmonitors); 50 | 51 | for (i = 0; i < nmonitors; i++) { 52 | mode = glfwGetVideoMode(monitors[i]); 53 | glfwGetMonitorPos(monitors[i], &mx, &my); 54 | mw = mode->width; 55 | mh = mode->height; 56 | 57 | overlap = MAX(0, MIN(wx + ww, mx + mw) - MAX(wx, mx)) * 58 | MAX(0, MIN(wy + wh, my + mh) - MAX(wy, my)); 59 | 60 | if (bestoverlap < overlap) { 61 | bestoverlap = overlap; 62 | bestmonitor = monitors[i]; 63 | } 64 | } 65 | 66 | return bestmonitor; 67 | } 68 | 69 | static void get_centered_window_coords(GLFWwindow *window, int *x, int *y) 70 | { 71 | GLFWmonitor *monitor = get_current_monitor(window); 72 | const GLFWvidmode *mode = glfwGetVideoMode(monitor); 73 | 74 | int win_width, win_height; 75 | glfwGetWindowSize(window, &win_width, &win_height); 76 | 77 | *x = (mode->width - win_width) / 2; 78 | *y = (mode->height - win_height) / 2; 79 | } 80 | 81 | int make_window(GLFWwindow **window, int width, int height, char *name, 82 | GLFWwindow *shared_ctx, _Bool centered) 83 | { 84 | glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); 85 | glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); 86 | 87 | *window = glfwCreateWindow(width, height, name, NULL, shared_ctx); 88 | if (window == NULL) { 89 | printf("Failed to initialize window.\n"); 90 | glfwTerminate(); 91 | return 0; 92 | } 93 | 94 | int posx, posy; 95 | 96 | if (centered) { 97 | get_centered_window_coords(*window, &posx, &posy); 98 | } else { 99 | int left, top, right, bottom; 100 | glfwGetWindowFrameSize(*window, &left, &top, &right, &bottom); 101 | posx = left; 102 | posy = top; 103 | } 104 | 105 | glfwSetWindowPos(*window, posx, posy); 106 | set_window_icon(*window); 107 | 108 | glfwShowWindow(*window); 109 | 110 | glfwMakeContextCurrent(*window); 111 | 112 | glfwSwapInterval(1); 113 | 114 | // enable alpha support 115 | glEnable(GL_BLEND); 116 | glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 117 | 118 | if (glewInit() != GLEW_OK) { 119 | printf("Failed to initialize glew.\n"); 120 | glfwTerminate(); 121 | return 0; 122 | } 123 | 124 | glEnable(GL_DEBUG_OUTPUT); 125 | 126 | int major, minor; 127 | glGetIntegerv(GL_MAJOR_VERSION, &major); 128 | glGetIntegerv(GL_MINOR_VERSION, &minor); 129 | 130 | if (major > 4 || (major == 4 && minor >= 3)) { 131 | glDebugMessageCallback(gl_debug_message_callback, 0); 132 | } 133 | 134 | if (shared_ctx != NULL) { 135 | Engine *engine = (Engine *)glfwGetWindowUserPointer(shared_ctx); 136 | glfwSetWindowUserPointer(*window, engine); 137 | glfwSetMouseButtonCallback(*window, handle_minigame_event); 138 | } 139 | return 1; 140 | } 141 | -------------------------------------------------------------------------------- /src/engine.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "animations.h" 5 | #include "cJSON.h" 6 | #include "cvector.h" 7 | #include "dressup.h" 8 | #include "engine.h" 9 | #include "kumashoot.h" 10 | #include "menu.h" 11 | #include "minigame.h" 12 | #include "resources.h" 13 | #include "scene.h" 14 | #include "shader.h" 15 | #include "sound.h" 16 | #include "state.h" 17 | #include "texture.h" 18 | #include "theater.h" 19 | #include "window.h" 20 | 21 | #include "input.h" 22 | 23 | int engine_init(Engine *engine) 24 | { 25 | // init main (menu) window 26 | if (!(make_window(&engine->main_window, COLLAPSED_MENU_WIDTH, 27 | COLLAPSED_MENU_HEIGHT, "lain", NULL, false))) { 28 | printf("Failed to create main window.\n"); 29 | return 0; 30 | } 31 | 32 | if (!init_resources(&engine->resources)) { 33 | printf("Failed to initialize resources.\n"); 34 | return 0; 35 | }; 36 | 37 | init_game_state(&engine->resources, &engine->game_state); 38 | 39 | init_menu(&engine->menu, &engine->game_state, &engine->resources); 40 | 41 | engine->minigame_window = NULL; 42 | engine->minigame.queued_minigame = NO_MINIGAME; 43 | engine->minigame.type = NO_MINIGAME; 44 | 45 | // set user pointer to access engine inside callback function 46 | glfwSetWindowUserPointer(engine->main_window, engine); 47 | // set callbacks 48 | glfwSetMouseButtonCallback(engine->main_window, handle_menu_click); 49 | 50 | return 1; 51 | } 52 | 53 | static void engine_render(Engine *engine, double now) 54 | { 55 | GLFWwindow *main_window = engine->main_window; 56 | Resources *resources = &engine->resources; 57 | Menu *menu = &engine->menu; 58 | GameState *game_state = &engine->game_state; 59 | 60 | GLFWwindow *minigame_window = engine->minigame_window; 61 | Minigame *minigame = &engine->minigame; 62 | 63 | glfwMakeContextCurrent(main_window); 64 | 65 | glClearColor(0.0f, 0.0f, 0.0f, 0.0f); 66 | glClear(GL_COLOR_BUFFER_BIT); 67 | 68 | update_menu(menu, game_state, main_window, resources); 69 | 70 | draw_scene(&menu->scene, main_window, resources->shaders); 71 | 72 | glfwSwapBuffers(main_window); 73 | 74 | if (minigame->type != NO_MINIGAME && can_refresh(now, minigame)) { 75 | glfwMakeContextCurrent(minigame_window); 76 | 77 | glClearColor(0.0f, 0.0f, 0.0f, 0.0f); 78 | glClear(GL_COLOR_BUFFER_BIT); 79 | 80 | update_minigame(resources, game_state, menu, minigame_window, 81 | minigame); 82 | 83 | if (minigame->type != NO_MINIGAME) { 84 | draw_minigame(resources, minigame_window, minigame); 85 | 86 | glfwSwapBuffers(minigame_window); 87 | 88 | minigame->last_updated = now; 89 | } 90 | } 91 | 92 | if (minigame->type == NO_MINIGAME && 93 | minigame->queued_minigame != NO_MINIGAME) { 94 | start_queued_minigame(resources, game_state, menu, main_window, 95 | &engine->minigame_window, minigame); 96 | } 97 | 98 | glfwPollEvents(); 99 | 100 | game_state->time = now; 101 | } 102 | 103 | static void engine_renderloop(Engine *engine) 104 | { 105 | while (!glfwWindowShouldClose(engine->main_window)) { 106 | engine_render(engine, glfwGetTime()); 107 | } 108 | } 109 | 110 | void engine_stop(Engine *engine) 111 | { 112 | Resources *resources = &engine->resources; 113 | Menu *menu = &engine->menu; 114 | Minigame *minigame = &engine->minigame; 115 | 116 | cJSON_Delete(resources->animation_data); 117 | 118 | if (minigame->type != NO_MINIGAME) { 119 | destroy_minigame(resources->textures, menu, minigame, 120 | engine->minigame_window); 121 | } 122 | 123 | free_scene(&menu->scene); 124 | 125 | for (int i = 0; i < MAX_ANIMATION_COUNT; i++) { 126 | animation_free(&resources->animations[i]); 127 | } 128 | 129 | for (int i = 0; i < MAX_THEATER_ANIMATION_COUNT; i++) { 130 | TheaterAnimation anim = resources->theater_animations[i]; 131 | if (anim.initialized) { 132 | for (int j = 0; j < anim.layer_count; j++) { 133 | animation_free(&anim.layers[j]); 134 | } 135 | } 136 | } 137 | 138 | ma_engine_uninit(&engine->resources.audio_engine); 139 | glfwTerminate(); 140 | } 141 | 142 | void engine_run(Engine *engine) 143 | { 144 | engine_renderloop(engine); 145 | 146 | if (!write_save_file(engine)) { 147 | printf("Failed to write save file.\n"); 148 | }; 149 | } 150 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lain-bootleg-bootleg 2 | 3 | This project aims to reverse-engineer and remake Lain Bootleg, a Serial Experiments Lain minigame. 4 | 5 | # Extracting assets 6 | 7 | To run the Python scripts you are going to need [`opencv-python`](https://pypi.org/project/opencv-python/) and [`pefile`](https://pypi.org/project/pefile/). 8 | 9 | The repository doesn't contain the game's assets, the extraction is done by automation scripts 10 | located under the `scripts/` directory. 11 | 12 | The user will have to provide the game binaries themselves in the following format: 13 | 14 | Create directory `scripts/binaries/`, under it, place these items from the original game: 15 | `lain_win.dat`, `lain_mov.dat`, `lain_win.exe`. 16 | 17 | Afterwards, run the script `make_game_assets.py` 18 | 19 | Once you're done running the script, a directory called `res/` should appear in 20 | the root of the repo you just cloned. If that's the case, you can proceed to platform-specific 21 | instructions for building. 22 | 23 | If your goal is not to build the assets for the game but for your own use, you can also 24 | use `extract_n_decompress.py` directly, which will yield raw decompressed assets along 25 | with their mask bitmaps inside a directory called `extracted` 26 | (located under the same location as the script). 27 | 28 | # Building 29 | 30 | Before you build, you must have extracted the assets in the proper location by following 31 | the guide above. 32 | 33 | Make sure to run `git submodule update --init --recursive` before proceeding to 34 | pull external dependencies if you didn't do it along with the cloning step. 35 | 36 | ## Dependencies 37 | 38 | - **gcc** 39 | - **make** 40 | - **cmake** 41 | - **mpv** along with it's development headers 42 | 43 | GLFW and GLEW will be compiled along with the program (submodules) and statically linked. 44 | If you would prefer to use your system-installed versions of these, use the CMake options 45 | `SYSTEM_GLFW` and `SYSTEM_GLEW`. For example: 46 | 47 | ```sh 48 | cmake -DSYSTEM_GLFW=ON -DSYSTEM_GLEW=ON .. 49 | ``` 50 | 51 | ## Compiling on Linux 52 | 53 | 1. `cd` into the repo 54 | 2. `mkdir build && cd build` 55 | 3. `cmake ..` 56 | 4. `make` 57 | 58 | This should produce a binary called `lain-bootleg-bootleg`. 59 | 60 | ## Compiling on Windows using MinGW and MSYS2 61 | 62 | For Windows we will need to download `libmpv` DLLs manually. The usual place to grab them would be 63 | [shinchiro's builds](https://sourceforge.net/projects/mpv-player-windows/files/libmpv/). 64 | Download the archive for your target architecture and extract it under `external/mpv`. 65 | Afterwards, create another directory named `mpv` inside the `include/` directory and move all the contents 66 | to it so that the headers of `external/mpv/include` end up in `external/mpv/include/mpv`. 67 | 68 | So something like: 69 | 70 | 1. `cd` into the repo 71 | 2. `cd external/mpv/include` 72 | 3. `mkdir mpv` 73 | 4. `mv *.h mpv` 74 | 75 | As for building: 76 | 77 | 1. `cd` into the repo 78 | 2. `mkdir build && cd build` 79 | 3. `cmake -G "MSYS Makefiles" ..` 80 | 4. `make` 81 | 82 | This should produce an executable called `lain-bootleg-bootleg.exe`. 83 | 84 | # Editing the save file 85 | 86 | Upon closing the game for the first time, you will notice a new file in the same directory 87 | as the executable called `lain_save.json`, which will have a format similar to: 88 | 89 | ``` 90 | { 91 | "score": 0, 92 | "theater_preview": 478, 93 | "tool_state": 0, 94 | "outfit": 0, 95 | "school_outfit_unlocked": 0, 96 | "cyberia_outfit_unlocked": 0, 97 | "sweater_outfit_unlocked": 0, 98 | "bear_outfit_unlocked": 0, 99 | "alien_outfit_unlocked": 0, 100 | "screwdriver_unlocked": 0, 101 | "navi_unlocked": 0 102 | } 103 | ``` 104 | 105 | Notes - all the ranges mentioned below are inclusive, and the values are layed out in the 106 | proper order. 107 | 108 | `score` is self explanatory. 109 | 110 | `theater_preview` can range from 478 to 484 - `classroom`, `school`, `lain's room`, `arisu's room`, 111 | `cyberia`, `street` and `bridge`. 112 | 113 | `tool_state` ranges from 0 to 2 - `no tools`, `holding screwdriver`, `holding navi`. 114 | 115 | `outfit` goes from 0 to 5 - `default`, `school`, `cyberia`, `bear`, `sweater`, `alien`. 116 | 117 | # Reporting bugs/issues 118 | 119 | If you found any bugs, have questions, etc. feel free to open an issue or join the 120 | [Discord server](https://discord.com/invite/JGnEyhD6ah). 121 | 122 | # Libraries used 123 | 124 | - [`GLFW`](https://github.com/glfw/glfw) 125 | - [`glew`](https://github.com/nigels-com/glew) 126 | - [`libmpv`](https://github.com/mpv-player/mpv) 127 | - [`cJSON`](https://github.com/DaveGamble/cJSON) 128 | - [`cvector`](https://github.com/eteran/c-vector) 129 | - [`miniaudio`](https://github.com/mackron/miniaudio) 130 | - [`stb_image`](https://github.com/nothings/stb) 131 | -------------------------------------------------------------------------------- /src/animations.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "texture.h" 4 | #include "vector2d.h" 5 | 6 | #include 7 | 8 | #define MAX_ANIMATION_COUNT 128 9 | #define MAX_THEATER_ANIMATION_COUNT 16 10 | 11 | #define FOREACH_SPRITE_ANIMATION(ANIMATION) \ 12 | ANIMATION(MAIN_UI_EXPAND_ANIMATION) \ 13 | ANIMATION(MAIN_UI_COLLAPSE_ANIMATION) \ 14 | ANIMATION(UI_DEFAULT_LAIN_BLINK_ANIMATION) \ 15 | ANIMATION(UI_DEFAULT_LAIN_LAUGH_ANIMATION) \ 16 | ANIMATION(UI_DEFAULT_LAIN_LAUGH_BLINK_ANIMATION) \ 17 | ANIMATION(UI_BEAR_LAIN_BLINK_ANIMATION) \ 18 | ANIMATION(UI_BEAR_LAIN_LAUGH_ANIMATION) \ 19 | ANIMATION(UI_BEAR_LAIN_LAUGH_BLINK_ANIMATION) \ 20 | ANIMATION(KUMA_SHOOT_BROWN_BEAR_WALK_ANIMATION) \ 21 | ANIMATION(KUMA_SHOOT_WHITE_BEAR_WALK_ANIMATION) \ 22 | ANIMATION(KUMA_SHOOT_SMOKE_ANIMATION) \ 23 | ANIMATION(KUMA_SHOOT_MIHO_ANIMATION) \ 24 | ANIMATION(KUMA_SHOOT_MIKA_ANIMATION) \ 25 | ANIMATION(KUMA_SHOOT_YASUO_ANIMATION) \ 26 | ANIMATION(KUMA_SHOOT_SCHOOL_LAIN_1_ANIMATION) \ 27 | ANIMATION(KUMA_SHOOT_SCHOOL_LAIN_2_ANIMATION) \ 28 | ANIMATION(KUMA_SHOOT_DEFAULT_LAIN_ANIMATION) \ 29 | ANIMATION(KUMA_SHOOT_SCREWDRIVER_LAIN_ANIMATION) \ 30 | ANIMATION(KUMA_SHOOT_SCREW_ANIMATION) \ 31 | ANIMATION(KUMA_SHOOT_EXPLOSION_ANIMATION) \ 32 | ANIMATION(LAIN_DEFAULT_LEAVE_ANIMATION) \ 33 | ANIMATION(LAIN_DEFAULT_WALK_LEFT_ANIMATION) \ 34 | ANIMATION(LAIN_SCHOOL_LEAVE_ANIMATION) \ 35 | ANIMATION(LAIN_SCHOOL_WALK_LEFT_ANIMATION) \ 36 | ANIMATION(LAIN_CYBERIA_LEAVE_ANIMATION) \ 37 | ANIMATION(LAIN_CYBERIA_WALK_LEFT_ANIMATION) \ 38 | ANIMATION(LAIN_BEAR_LEAVE_ANIMATION) \ 39 | ANIMATION(LAIN_BEAR_WALK_LEFT_ANIMATION) \ 40 | ANIMATION(LAIN_SWEATER_LEAVE_ANIMATION) \ 41 | ANIMATION(LAIN_SWEATER_WALK_LEFT_ANIMATION) \ 42 | ANIMATION(LAIN_ALIEN_LEAVE_ANIMATION) \ 43 | ANIMATION(LAIN_ALIEN_WALK_LEFT_ANIMATION) \ 44 | ANIMATION(SCHOOL_LAIN_THEATER_WALK_ANIMATION) \ 45 | ANIMATION(BEAR_LAIN_THEATER_WALK_ANIMATION) \ 46 | ANIMATION(CYBERIA_LAIN_THEATER_WALK_ANIMATION) \ 47 | ANIMATION(ALIEN_LAIN_THEATER_WALK_ANIMATION) \ 48 | ANIMATION(SWEATER_LAIN_THEATER_WALK_ANIMATION) \ 49 | ANIMATION(DEFAULT_LAIN_THEATER_WALK_ANIMATION) 50 | 51 | #define FOREACH_THEATER_ANIMATION(ANIMATION) \ 52 | ANIMATION(THEATER_CLASSROOM_ANIMATION) \ 53 | ANIMATION(THEATER_BRIDGE_ANIMATION) \ 54 | ANIMATION(THEATER_SCHOOL_ANIMATION) \ 55 | ANIMATION(THEATER_LAIN_ROOM_NIGHT_ANIMATION) \ 56 | ANIMATION(THEATER_ARISU_ROOM_ANIMATION) \ 57 | ANIMATION(THEATER_CYBERIA_ANIMATION) \ 58 | ANIMATION(THEATER_STREET_ANIMATION) 59 | 60 | #define GENERATE_ANIMATION_ENUM(ENUM) ENUM, 61 | #define GENERATE_ANIMATION_STRING(STRING) #STRING, 62 | 63 | typedef enum { FOREACH_SPRITE_ANIMATION(GENERATE_ANIMATION_ENUM) } AnimationID; 64 | typedef enum { 65 | FOREACH_THEATER_ANIMATION(GENERATE_ANIMATION_ENUM) 66 | } TheaterAnimationID; 67 | 68 | typedef struct AnimationFrame { 69 | uint8_t index; 70 | uint16_t timing; 71 | TextureID texture_id; 72 | Vector2D pos_offset; 73 | _Bool visible; 74 | struct AnimationFrame *next; 75 | } AnimationFrame; 76 | 77 | typedef struct { 78 | AnimationFrame *first; 79 | AnimationFrame *last; 80 | 81 | // specifies on which frame the animation should continue if it reaches 82 | // the end. 83 | // NULL if not looped. 84 | _Bool looped; 85 | } Animation; 86 | 87 | typedef struct { 88 | _Bool initialized; 89 | uint8_t layer_count; 90 | Animation layers[5]; 91 | } TheaterAnimation; 92 | 93 | struct resources; 94 | 95 | void animations_init(struct resources *resources); 96 | Animation *animation_get(struct resources *resources, AnimationID animation_id); 97 | TheaterAnimation *theater_animation_get(struct resources *resources, 98 | TheaterAnimationID animation_id); 99 | void animation_free(Animation *animation); 100 | -------------------------------------------------------------------------------- /src/animations.c: -------------------------------------------------------------------------------- 1 | #include "animations.h" 2 | #include "cJSON.h" 3 | #include "resources.h" 4 | #include "texture.h" 5 | #include "vector2d.h" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | static const char *ANIMATION_STRING[] = { 12 | FOREACH_SPRITE_ANIMATION(GENERATE_ANIMATION_STRING)}; 13 | 14 | static const char *THEATER_ANIMATION_STRING[] = { 15 | FOREACH_THEATER_ANIMATION(GENERATE_ANIMATION_STRING)}; 16 | 17 | static cJSON *parse_animations(char *filename) 18 | { 19 | FILE *f = NULL; 20 | long len = 0; 21 | char *data = NULL; 22 | 23 | f = fopen(filename, "rb"); 24 | fseek(f, 0, SEEK_END); 25 | len = ftell(f); 26 | fseek(f, 0, SEEK_SET); 27 | 28 | data = (char *)malloc(len + 1); 29 | 30 | fread(data, 1, len, f); 31 | data[len] = '\0'; 32 | fclose(f); 33 | 34 | cJSON *parsed = cJSON_Parse(data); 35 | 36 | free(data); 37 | 38 | return parsed; 39 | } 40 | 41 | static int animation_load(Animation *animation, const cJSON *frames) 42 | { 43 | animation->first = NULL; 44 | animation->last = NULL; 45 | animation->looped = false; 46 | 47 | cJSON *curr; 48 | cJSON_ArrayForEach(curr, frames) 49 | { 50 | const cJSON *timing = NULL; 51 | const cJSON *rsrc_id = NULL; 52 | const cJSON *pos_x = NULL; 53 | const cJSON *pos_y = NULL; 54 | const cJSON *index = NULL; 55 | 56 | timing = cJSON_GetObjectItem(curr, "timing"); 57 | rsrc_id = cJSON_GetObjectItem(curr, "rsrc_id"); 58 | pos_x = cJSON_GetObjectItem(curr, "x_offset"); 59 | pos_y = cJSON_GetObjectItem(curr, "y_offset"); 60 | index = cJSON_GetObjectItem(curr, "index"); 61 | 62 | AnimationFrame *frame = malloc(sizeof(AnimationFrame)); 63 | if (frame == NULL) { 64 | printf("Failed to alloc memory for animation frame.\n"); 65 | return 0; 66 | } 67 | 68 | if (rsrc_id->valueint == -1) { 69 | *frame = (AnimationFrame){.visible = false, 70 | .index = index->valueint, 71 | .timing = timing->valueint}; 72 | } else { 73 | *frame = (AnimationFrame){ 74 | .index = index->valueint, 75 | .timing = timing->valueint, 76 | .texture_id = rsrc_id->valueint, 77 | .pos_offset = 78 | (Vector2D){pos_x->valueint, pos_y->valueint}, 79 | .visible = true, 80 | }; 81 | } 82 | 83 | frame->next = NULL; 84 | 85 | if (animation->first == NULL) { 86 | animation->first = frame; 87 | animation->last = frame; 88 | } else { 89 | animation->last->next = frame; 90 | animation->last = frame; 91 | } 92 | } 93 | 94 | return 1; 95 | } 96 | 97 | Animation *animation_get(Resources *resources, AnimationID animation_id) 98 | { 99 | Animation *animation = &resources->animations[animation_id]; 100 | 101 | if (animation->first == NULL) { 102 | const cJSON *sprite_animations = cJSON_GetObjectItem( 103 | resources->animation_data, "sprite_animations"); 104 | 105 | const cJSON *frames = cJSON_GetObjectItem( 106 | sprite_animations, ANIMATION_STRING[animation_id]); 107 | 108 | if (frames == NULL) { 109 | printf("Failed to find animation %s in JSON file.\n ", 110 | ANIMATION_STRING[animation_id]); 111 | return 0; 112 | } 113 | 114 | if (!animation_load(animation, frames)) { 115 | printf("Failed to load animation %s.\n ", 116 | ANIMATION_STRING[animation_id]); 117 | return 0; 118 | }; 119 | } 120 | 121 | return animation; 122 | } 123 | 124 | TheaterAnimation *theater_animation_get(Resources *resources, 125 | TheaterAnimationID animation_id) 126 | { 127 | TheaterAnimation *animation = 128 | &resources->theater_animations[animation_id]; 129 | 130 | if (!animation->initialized) { 131 | const cJSON *theater_animations = cJSON_GetObjectItem( 132 | resources->animation_data, "theater_scenes"); 133 | 134 | const cJSON *layers = cJSON_GetObjectItem( 135 | theater_animations, THEATER_ANIMATION_STRING[animation_id]); 136 | 137 | uint8_t layer_count = 0; 138 | cJSON *layer = layers->child; 139 | while (layer) { 140 | if (layer == NULL) { 141 | break; 142 | } 143 | 144 | if (!animation_load(&animation->layers[layer_count], 145 | layer)) { 146 | printf("Failed to load animation %s.\n ", 147 | THEATER_ANIMATION_STRING[animation_id]); 148 | return 0; 149 | }; 150 | 151 | layer = layer->next; 152 | layer_count++; 153 | } 154 | 155 | if (animation->layers[0].first == NULL) { 156 | printf("Failed to load animation %s.\n", 157 | ANIMATION_STRING[animation_id]); 158 | return 0; 159 | } 160 | 161 | animation->initialized = true; 162 | animation->layer_count = layer_count; 163 | } 164 | 165 | return animation; 166 | } 167 | 168 | void animations_init(Resources *resources) 169 | { 170 | resources->animation_data = parse_animations("./res/animations.json"); 171 | 172 | for (int i = 0; i < MAX_ANIMATION_COUNT; i++) { 173 | resources->animations[i] = (Animation){0}; 174 | }; 175 | 176 | for (int i = 0; i < MAX_THEATER_ANIMATION_COUNT; i++) { 177 | resources->theater_animations[i] = (TheaterAnimation){0}; 178 | }; 179 | } 180 | 181 | void animation_free(Animation *animation) 182 | { 183 | AnimationFrame *curr = animation->first; 184 | while (curr != NULL) { 185 | AnimationFrame *next = curr->next; 186 | free(curr); 187 | curr = next; 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /src/movie.c: -------------------------------------------------------------------------------- 1 | #include "movie.h" 2 | #include "window.h" 3 | 4 | #include 5 | #include 6 | 7 | #define MOVIE_WIDTH 400 8 | #define MOVIE_HEIGHT 300 9 | 10 | #ifdef HAVE_MPV_DESTROY 11 | #define MPV_DESTROY mpv_destroy 12 | #else 13 | #define MPV_DESTROY mpv_detach_destroy 14 | #endif 15 | 16 | static void *get_proc_address_mpv(void *fn_ctx, const char *name) 17 | { 18 | return (void *)glfwGetProcAddress(name); 19 | } 20 | 21 | static _Bool mpv_events = false; 22 | static void on_mpv_events(void *ctx) { mpv_events = true; } 23 | 24 | static void init_movie_buffers(Movie *movie) 25 | { 26 | glGenVertexArrays(1, &movie->VAO); 27 | glBindVertexArray(movie->VAO); 28 | 29 | glGenBuffers(1, &movie->VBO); 30 | glBindBuffer(GL_ARRAY_BUFFER, movie->VBO); 31 | 32 | float left_x = -0.75f; 33 | float right_x = 0.75f; 34 | 35 | float top_y = -0.75; 36 | float bottom_y = 0.75; 37 | 38 | // clang-format off 39 | float movie_quad_vertices[] = { 40 | left_x, bottom_y, 0.0f, 1.0f, 41 | left_x, top_y, 0.0f, 0.0f, 42 | right_x, top_y, 1.0f, 0.0f, 43 | 44 | left_x, bottom_y, 0.0f, 1.0f, 45 | right_x, top_y, 1.0f, 0.0f, 46 | right_x, bottom_y, 1.0f, 1.0f 47 | }; 48 | // clang-format on 49 | 50 | glBufferData(GL_ARRAY_BUFFER, sizeof(movie_quad_vertices), 51 | movie_quad_vertices, GL_STATIC_DRAW); 52 | glEnableVertexAttribArray(0); 53 | glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), 54 | (void *)0); 55 | glEnableVertexAttribArray(1); 56 | glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), 57 | (void *)(2 * sizeof(float))); 58 | 59 | glGenFramebuffers(1, &movie->FBO); 60 | glBindFramebuffer(GL_FRAMEBUFFER, movie->FBO); 61 | 62 | glGenTextures(1, &movie->texture_buffer); 63 | glBindTexture(GL_TEXTURE_2D, movie->texture_buffer); 64 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, MINIGAME_WIDTH, MINIGAME_HEIGHT, 65 | 0, GL_RGB, GL_UNSIGNED_BYTE, NULL); 66 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 67 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 68 | 69 | glBindTexture(GL_TEXTURE_2D, 0); 70 | 71 | glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, 72 | GL_TEXTURE_2D, movie->texture_buffer, 0); 73 | 74 | glBindFramebuffer(GL_FRAMEBUFFER, 0); 75 | } 76 | 77 | int movie_init(Movie *movie) 78 | { 79 | init_movie_buffers(movie); 80 | 81 | if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != 82 | GL_FRAMEBUFFER_COMPLETE) { 83 | printf("Framebuffer is incomplete.\n"); 84 | return 0; 85 | } 86 | 87 | movie->mpv_handle = mpv_create(); 88 | if (movie->mpv_handle == NULL) { 89 | printf("Failed to create mpv context.\n"); 90 | return 0; 91 | } 92 | 93 | if (mpv_initialize(movie->mpv_handle) < 0) { 94 | printf("Failed to initialize mpv context.\n"); 95 | return 0; 96 | } 97 | 98 | mpv_request_log_messages(movie->mpv_handle, "debug"); 99 | 100 | mpv_render_param params[] = { 101 | {MPV_RENDER_PARAM_API_TYPE, MPV_RENDER_API_TYPE_OPENGL}, 102 | {MPV_RENDER_PARAM_OPENGL_INIT_PARAMS, 103 | &(mpv_opengl_init_params){ 104 | .get_proc_address = get_proc_address_mpv, 105 | }}, 106 | {0}}; 107 | 108 | if (mpv_render_context_create(&movie->mpv_render_ctx, movie->mpv_handle, 109 | params) < 0) { 110 | 111 | printf("Failed to create mpv render context.\n"); 112 | return 0; 113 | } 114 | 115 | // set callbacks 116 | mpv_set_wakeup_callback(movie->mpv_handle, on_mpv_events, NULL); 117 | 118 | const char *cmd[] = {"loadfile", "./res/lain_mov.dat", NULL}; 119 | mpv_command_async(movie->mpv_handle, 0, cmd); 120 | 121 | return 1; 122 | } 123 | 124 | _Bool movie_render(ShaderProgram shader, Movie *movie) 125 | { 126 | glBindFramebuffer(GL_FRAMEBUFFER, movie->FBO); 127 | 128 | glClearColor(0.0f, 0.0f, 0.0f, 1.0f); 129 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 130 | 131 | if (mpv_events) { 132 | mpv_events = false; 133 | while (1) { 134 | mpv_event *event = mpv_wait_event(movie->mpv_handle, 0); 135 | 136 | if (event->event_id == MPV_EVENT_END_FILE) { 137 | return false; 138 | } 139 | 140 | if (event->event_id == MPV_EVENT_NONE) { 141 | 142 | break; 143 | } 144 | 145 | if (event->event_id == MPV_EVENT_LOG_MESSAGE) { 146 | mpv_event_log_message *msg = event->data; 147 | if (strstr(msg->text, "DR image")) 148 | printf("log: %s", msg->text); 149 | continue; 150 | } 151 | 152 | printf("event: %s\n", mpv_event_name(event->event_id)); 153 | } 154 | } 155 | 156 | mpv_render_param params[] = {{MPV_RENDER_PARAM_OPENGL_FBO, 157 | &(mpv_opengl_fbo){ 158 | .fbo = movie->FBO, 159 | .w = MINIGAME_WIDTH, 160 | .h = MINIGAME_HEIGHT, 161 | }}, 162 | {MPV_RENDER_PARAM_FLIP_Y, &(int){1}}, 163 | {0}}; 164 | mpv_render_context_render(movie->mpv_render_ctx, params); 165 | 166 | glBindFramebuffer(GL_FRAMEBUFFER, 0); 167 | 168 | glClearColor(0.0f, 0.0f, 0.0f, 1.0f); 169 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 170 | 171 | glUseProgram(shader); 172 | 173 | glBindVertexArray(movie->VAO); 174 | glBindTexture(GL_TEXTURE_2D, movie->texture_buffer); 175 | glDrawArrays(GL_TRIANGLES, 0, 6); 176 | 177 | return true; 178 | } 179 | 180 | void movie_free(Movie *movie) 181 | { 182 | mpv_render_context_free(movie->mpv_render_ctx); 183 | MPV_DESTROY(movie->mpv_handle); 184 | } 185 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | 3 | project(lain-bootleg-bootleg) 4 | 5 | set(CMAKE_C_STANDARD 11) 6 | set(CMAKE_C_STANDARD_REQUIRED ON) 7 | 8 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 9 | 10 | set(SOURCES 11 | src/main.c 12 | src/engine.c 13 | src/input.c 14 | src/menu.c 15 | src/scene.c 16 | src/shader.c 17 | src/sprite.c 18 | src/stb_image.c 19 | src/text.c 20 | src/texture.c 21 | src/window.c 22 | src/state.c 23 | src/kumashoot.c 24 | src/minigame.c 25 | src/vector2d.c 26 | src/random.c 27 | src/animations.c 28 | src/dressup.c 29 | src/theater.c 30 | src/cJSON.c 31 | src/resources.c 32 | src/sound.c 33 | src/movie.c 34 | ) 35 | 36 | add_executable(${PROJECT_NAME} ${SOURCES}) 37 | 38 | if(MSVC) 39 | target_compile_options(${PROJECT_NAME} PRIVATE /W4) 40 | else() 41 | target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wextra -Wpedantic) 42 | endif() 43 | 44 | target_include_directories(${PROJECT_NAME} PRIVATE ${PROJECT_SOURCE_DIR}/src) 45 | 46 | add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD 47 | COMMAND ${CMAKE_COMMAND} -E copy_directory 48 | ${CMAKE_SOURCE_DIR}/res $/res) 49 | 50 | option(SYSTEM_GLEW "Use system-installed GLEW instead of vendored one" OFF) 51 | 52 | if(SYSTEM_GLEW) 53 | find_package(OpenGL REQUIRED) 54 | target_include_directories(${PROJECT_NAME} PRIVATE ${OPENGL_INCLUDE_DIR}) 55 | target_link_libraries(${PROJECT_NAME} PRIVATE ${OPENGL_LIBRARIES}) 56 | find_package(PkgConfig) 57 | if(PKG_CONFIG_FOUND) 58 | pkg_check_modules(GLEW glew) 59 | if(GLEW_FOUND) 60 | target_include_directories(${PROJECT_NAME} PRIVATE ${GLEW_INCLUDE_DIRS}) 61 | target_compile_options(${PROJECT_NAME} PRIVATE ${GLEW_CFLAGS_OTHER}) 62 | target_link_libraries(${PROJECT_NAME} PRIVATE ${GLEW_LIBRARIES}) 63 | target_link_directories(${PROJECT_NAME} PRIVATE ${GLEW_LIBRARY_DIRS}) 64 | target_link_options(${PROJECT_NAME} PRIVATE ${GLEW_LDFLAGS_OTHER}) 65 | endif() 66 | endif() 67 | if(NOT GLEW_FOUND) 68 | find_package(GLEW REQUIRED) 69 | target_link_libraries(${PROJECT_NAME} PRIVATE GLEW::GLEW) 70 | endif() 71 | else() 72 | add_definitions(-DGLEW_STATIC) 73 | add_subdirectory(${CMAKE_SOURCE_DIR}/external/glew-cmake EXCLUDE_FROM_ALL) 74 | target_link_libraries(${PROJECT_NAME} PRIVATE libglew_static) 75 | endif() 76 | 77 | option(SYSTEM_GLFW "Use system-installed GLFW instead of vendored one" OFF) 78 | 79 | if(SYSTEM_GLFW) 80 | find_package(glfw3 REQUIRED) 81 | find_package(Threads REQUIRED) 82 | target_link_libraries(${PROJECT_NAME} PRIVATE glfw ${CMAKE_DL_LIBS} Threads::Threads m) 83 | else() 84 | set(GLFW_BUILD_DOCS OFF CACHE BOOL "" FORCE) 85 | set(GLFW_BUILD_TESTS OFF CACHE BOOL "" FORCE) 86 | set(GLFW_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) 87 | add_subdirectory(${CMAKE_SOURCE_DIR}/external/glfw EXCLUDE_FROM_ALL) 88 | target_link_libraries(${PROJECT_NAME} PRIVATE glfw) 89 | endif() 90 | 91 | if (UNIX) 92 | find_package(PkgConfig) 93 | if(PKG_CONFIG_FOUND) 94 | pkg_check_modules(MPV REQUIRED mpv) 95 | if(MPV_FOUND) 96 | target_include_directories(${PROJECT_NAME} PRIVATE ${MPV_INCLUDE_DIRS}) 97 | target_compile_options(${PROJECT_NAME} PRIVATE ${MPV_CFLAGS_OTHER}) 98 | target_link_libraries(${PROJECT_NAME} PRIVATE ${MPV_LIBRARIES}) 99 | target_link_directories(${PROJECT_NAME} PRIVATE ${MPV_LIBRARY_DIRS}) 100 | target_link_options(${PROJECT_NAME} PRIVATE ${MPV_LDFLAGS_OTHER}) 101 | endif() 102 | endif() 103 | if(NOT MPV_FOUND) 104 | find_library(MPV_LIBRARY mpv) 105 | message(STATUS "MPV_LIBRARY: ${MPV_LIBRARY}") 106 | find_path(MPV_INCLUDE_DIRS NAMES "render_gl.h" PATH_SUFFIXES "include/mpv") 107 | message(STATUS "MPV_INCLUDE_DIRS: ${MPV_INCLUDE_DIRS}") 108 | if(MPV_LIBRARY AND MPV_INCLUDE_DIRS) 109 | target_include_directories(${PROJECT_NAME} PRIVATE ${MPV_INCLUDE_DIRS}) 110 | target_link_libraries(${PROJECT_NAME} PRIVATE ${MPV_LIBRARY}) 111 | set(MPV_LINK_LIBRARIES ${MPV_LIBRARY}) 112 | else() 113 | message(FATAL_ERROR "Unable to find MPV!") 114 | endif() 115 | endif() 116 | endif (UNIX) 117 | 118 | if (WIN32) 119 | set(MPV_DIR ${PROJECT_SOURCE_DIR}/external/mpv) 120 | if (MINGW) 121 | set(MPV_FLAGS "-mwindows -Wl,-Bstatic,--whole-archive -lwinpthread -Wl,-Bdynamic,--no-whole-archive") 122 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${MPV_FLAGS}") 123 | endif (MINGW) 124 | set(MPV_INCLUDE_DIRS ${MPV_DIR}/include) 125 | set(MPV_LINK_LIBRARIES ${MPV_DIR}/mpv.lib) 126 | target_include_directories(${PROJECT_NAME} PRIVATE ${MPV_INCLUDE_DIRS}) 127 | target_link_libraries(${PROJECT_NAME} PRIVATE ${MPV_LINK_LIBRARIES}) 128 | add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD 129 | COMMAND ${CMAKE_COMMAND} -E copy_if_different 130 | ${MPV_DIR}/mpv-2.dll 131 | $) 132 | endif (WIN32) 133 | 134 | # Detect if mpv_detach_destroy or mpv_destroy 135 | include(CheckSymbolExists) 136 | set(CMAKE_REQUIRED_FLAGS ${MPV_FLAGS}) 137 | set(CMAKE_REQUIRED_INCLUDES ${MPV_INCLUDE_DIRS}) 138 | set(CMAKE_REQUIRED_LIBRARIES ${MPV_LINK_LIBRARIES}) 139 | check_symbol_exists(mpv_destroy "mpv/client.h" HAVE_MPV_DESTROY) 140 | 141 | if(HAVE_MPV_DESTROY) 142 | target_compile_definitions(${PROJECT_NAME} PRIVATE HAVE_MPV_DESTROY) 143 | endif() 144 | -------------------------------------------------------------------------------- /src/sprite.c: -------------------------------------------------------------------------------- 1 | #include "sprite.h" 2 | #include "animations.h" 3 | #include "engine.h" 4 | #include "resources.h" 5 | #include "texture.h" 6 | #include "vector2d.h" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | static int depth_sort_cmp(const void *a, const void *b) 14 | { 15 | const Sprite *sprite_a = *(Sprite **)a; 16 | const Sprite *sprite_b = *(Sprite **)b; 17 | return sprite_a->z_index - sprite_b->z_index; 18 | } 19 | 20 | void depth_sort(Sprite **sprites, unsigned int sprite_count) 21 | { 22 | qsort(sprites, sprite_count, sizeof(Sprite *), depth_sort_cmp); 23 | } 24 | 25 | void get_hitbox_range(Vector2D pos, Vector2D hitbox_size, float *top, 26 | float *left, float *right, float *bottom) 27 | { 28 | *left = pos.x - hitbox_size.x / 2.0f; 29 | *right = pos.x + hitbox_size.x / 2.0f; 30 | 31 | *top = pos.y - hitbox_size.y / 2.0f; 32 | *bottom = pos.y + hitbox_size.y / 2.0f; 33 | } 34 | 35 | _Bool is_sprite_within_bounds(const Sprite *sprite, const Vector2D point) 36 | { 37 | float top, left, right, bottom; 38 | 39 | if (sprite->hitbox_size.x == 0 || sprite->hitbox_size.y == 0) { 40 | return false; 41 | } 42 | 43 | if (sprite->pivot_centered) { 44 | get_hitbox_range(sprite->pos, sprite->hitbox_size, &top, &left, 45 | &right, &bottom); 46 | } else { 47 | Vector2D center_coords = get_sprite_center_coords(sprite); 48 | get_hitbox_range(center_coords, sprite->hitbox_size, &top, 49 | &left, &right, &bottom); 50 | } 51 | 52 | return (left <= point.x && point.x <= right) && 53 | (top <= point.y && point.y <= bottom); 54 | } 55 | 56 | Vector2D get_sprite_center_coords(const Sprite *sprite) 57 | { 58 | if (sprite->pivot_centered) { 59 | return sprite->pos; 60 | } else { 61 | return (Vector2D){ 62 | sprite->pos.x + sprite->texture->size.x / 2.0f, 63 | sprite->pos.y + sprite->texture->size.y / 2.0f}; 64 | } 65 | } 66 | 67 | GLfloat *get_sprite_vertices(GLfloat *buffer, Sprite *sprite) 68 | { 69 | GLfloat vertices[] = { 70 | // top right 71 | sprite->pos.x + sprite->texture->size.x, 72 | sprite->pos.y, 73 | sprite->mirrored ? -1.0f : 1.0f, 74 | 1.0f, 75 | sprite->texture_index, 76 | 77 | // bottom right 78 | sprite->pos.x + sprite->texture->size.x, 79 | sprite->pos.y + sprite->texture->size.y, 80 | sprite->mirrored ? -1.0f : 1.0f, 81 | 0.0f, 82 | sprite->texture_index, 83 | 84 | // bottom left 85 | sprite->pos.x, 86 | sprite->pos.y + sprite->texture->size.y, 87 | 0.0f, 88 | 0.0f, 89 | sprite->texture_index, 90 | 91 | // top left 92 | sprite->pos.x, 93 | sprite->pos.y, 94 | 0.0f, 95 | 1.0f, 96 | sprite->texture_index, 97 | }; 98 | 99 | memcpy(buffer, vertices, sizeof(vertices)); 100 | buffer += sizeof(vertices) / sizeof(vertices[0]); 101 | return buffer; 102 | } 103 | 104 | GLfloat *get_pivot_centered_sprite_vertices(GLfloat *buffer, Sprite *sprite) 105 | { 106 | GLfloat vertices[] = { 107 | // top right 108 | sprite->pos.x + (sprite->texture->size.x / 2), 109 | sprite->pos.y - (sprite->texture->size.y / 2), 110 | sprite->mirrored ? -1.0f : 1.0f, 111 | 1.0f, 112 | sprite->texture_index, 113 | 114 | // bottom right 115 | sprite->pos.x + (sprite->texture->size.x / 2), 116 | sprite->pos.y + (sprite->texture->size.y / 2), 117 | sprite->mirrored ? -1.0f : 1.0f, 118 | 0.0f, 119 | sprite->texture_index, 120 | 121 | // bottom left 122 | sprite->pos.x - (sprite->texture->size.x / 2), 123 | sprite->pos.y + (sprite->texture->size.y / 2), 124 | 0.0f, 125 | 0.0f, 126 | sprite->texture_index, 127 | 128 | // top left 129 | sprite->pos.x - (sprite->texture->size.x / 2), 130 | sprite->pos.y - (sprite->texture->size.y / 2), 131 | 0.0f, 132 | 1.0f, 133 | sprite->texture_index, 134 | }; 135 | 136 | memcpy(buffer, vertices, sizeof(vertices)); 137 | buffer += sizeof(vertices) / sizeof(vertices[0]); 138 | return buffer; 139 | } 140 | 141 | _Bool sprite_animation_is_last_frame(Sprite *sprite) 142 | { 143 | return sprite->animation->last == sprite->animation_frame; 144 | } 145 | 146 | static void sprite_set_frame(Resources *resources, AnimationFrame *frame, 147 | Sprite *sprite) 148 | { 149 | sprite->animation_frame = frame; 150 | 151 | if (sprite->animation_frame->visible) { 152 | sprite->visible = true; 153 | sprite->texture = 154 | texture_get(resources, sprite->animation_frame->texture_id); 155 | Vector2D pos_offset = sprite->animation_frame->pos_offset; 156 | 157 | if (pos_offset.x != -1) { 158 | sprite->pos.x = pos_offset.x; 159 | } 160 | 161 | if (pos_offset.y != -1) { 162 | sprite->pos.y = pos_offset.y; 163 | } 164 | } else { 165 | sprite->visible = false; 166 | } 167 | } 168 | 169 | void sprite_try_next_frame(Resources *resources, double now, Sprite *sprite) 170 | { 171 | if (sprite->animation_frame->next == NULL) { 172 | if (sprite->animation->looped) { 173 | sprite->animation_frame = sprite->animation->first; 174 | sprite->animation_start_time = now; 175 | } else { 176 | sprite->animation = NULL; 177 | sprite->animation_frame = NULL; 178 | } 179 | return; 180 | } 181 | 182 | if (now - sprite->animation_start_time > 183 | sprite->animation_frame->next->timing / 60.0) { 184 | sprite_set_frame(resources, sprite->animation_frame->next, 185 | sprite); 186 | } 187 | } 188 | 189 | void sprite_set_animation_direct(Resources *resources, double now, 190 | Sprite *sprite, Animation *animation) 191 | { 192 | sprite->animation = animation; 193 | sprite->animation_start_time = now; 194 | sprite_set_frame(resources, animation->first, sprite); 195 | } 196 | 197 | void sprite_set_animation(Resources *resources, double now, Sprite *sprite, 198 | AnimationID animation_id) 199 | { 200 | Animation *animation = animation_get(resources, animation_id); 201 | sprite_set_animation_direct(resources, now, sprite, animation); 202 | } 203 | 204 | void sprite_set_to_origin_pos(Sprite *sprite) 205 | { 206 | sprite->pos = sprite->origin_pos; 207 | } 208 | 209 | void make_sprite(Sprite *target, Sprite sprite) 210 | { 211 | *target = sprite; 212 | 213 | target->origin_pos = sprite.pos; 214 | target->animation = NULL; 215 | target->animation_frame = NULL; 216 | } 217 | -------------------------------------------------------------------------------- /src/shader.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "shader.h" 7 | 8 | static GLuint compile_shader(GLenum shader_type, const GLchar *shader_source); 9 | static GLint check_shader_compile_errors(GLuint shader); 10 | static GLint check_shader_program_link_errors(GLuint program); 11 | 12 | static char barrier_quad_fragment[] = "#version 330\n" 13 | "out vec4 FragColor;" 14 | 15 | "void main()" 16 | "{" 17 | "FragColor = vec4(1.0, 0.0, 0.0, 1.0);" 18 | "}"; 19 | 20 | // manually written every index case because having the index as a non-constant 21 | // expression was causing issues on very old drivers 22 | static char quad_fragment[] = 23 | "#version 330\n" 24 | "out vec4 FragColor;" 25 | 26 | "in vec2 v_TexCoord;" 27 | "in float v_TexIndex;" 28 | 29 | "uniform sampler2D u_Textures[20];" 30 | 31 | "void main()" 32 | "{" 33 | "int index = int(v_TexIndex);" 34 | "if (index == 0) FragColor = texture(u_Textures[0], v_TexCoord);" 35 | "if (index == 1) FragColor = texture(u_Textures[1], v_TexCoord);" 36 | "if (index == 2) FragColor = texture(u_Textures[2], v_TexCoord);" 37 | "if (index == 3) FragColor = texture(u_Textures[3], v_TexCoord);" 38 | "if (index == 4) FragColor = texture(u_Textures[4], v_TexCoord);" 39 | "if (index == 5) FragColor = texture(u_Textures[5], v_TexCoord);" 40 | "if (index == 6) FragColor = texture(u_Textures[6], v_TexCoord);" 41 | "if (index == 7) FragColor = texture(u_Textures[7], v_TexCoord);" 42 | "if (index == 8) FragColor = texture(u_Textures[8], v_TexCoord);" 43 | "if (index == 9) FragColor = texture(u_Textures[9], v_TexCoord);" 44 | "if (index == 10) FragColor = texture(u_Textures[10], v_TexCoord);" 45 | "if (index == 11) FragColor = texture(u_Textures[11], v_TexCoord);" 46 | "if (index == 12) FragColor = texture(u_Textures[12], v_TexCoord);" 47 | "if (index == 13) FragColor = texture(u_Textures[13], v_TexCoord);" 48 | "if (index == 14) FragColor = texture(u_Textures[14], v_TexCoord);" 49 | "if (index == 15) FragColor = texture(u_Textures[15], v_TexCoord);" 50 | "}"; 51 | 52 | static char quad_vertex[] = 53 | "#version 330\n" 54 | "layout (location = 0) in vec2 a_Pos;" 55 | "layout (location = 1) in vec2 a_TexCoord;" 56 | "layout (location = 2) in float a_TexIndex;" 57 | 58 | "out vec2 v_TexCoord;" 59 | "out float v_TexIndex;" 60 | 61 | "uniform mat4 u_Model;" 62 | "uniform mat4 u_Projection;" 63 | "uniform mat4 u_View;" 64 | 65 | "void main()" 66 | "{" 67 | "v_TexCoord = a_TexCoord;" 68 | "v_TexIndex = a_TexIndex;" 69 | "gl_Position = u_Projection * u_View * u_Model * vec4(a_Pos, 0.0, 1.0);" 70 | "}"; 71 | 72 | static char movie_fragment[] = 73 | "#version 330 core\n" 74 | "out vec4 FragColor;" 75 | 76 | "in vec2 v_TexCoords;" 77 | 78 | "uniform sampler2D u_screenTexture;" 79 | 80 | "void main()" 81 | "{ " 82 | "FragColor = texture(u_screenTexture, v_TexCoords);" 83 | "}"; 84 | 85 | static char movie_vertex[] = "#version 330\n" 86 | "layout (location = 0) in vec2 a_Pos;" 87 | "layout (location = 1) in vec2 a_TexCoords;" 88 | 89 | "out vec2 v_TexCoords;" 90 | 91 | "void main()" 92 | "{" 93 | "gl_Position = vec4(a_Pos.x, a_Pos.y, 0.0, 1.0);" 94 | "v_TexCoords = a_TexCoords;" 95 | "}"; 96 | 97 | static ShaderProgram create_shader(const char *vertex, const char *fragment) 98 | { 99 | GLuint vertex_shader = compile_shader(GL_VERTEX_SHADER, vertex); 100 | if (!vertex_shader) { 101 | return 0; 102 | } 103 | 104 | GLuint fragment_shader = compile_shader(GL_FRAGMENT_SHADER, fragment); 105 | if (!fragment_shader) { 106 | return 0; 107 | } 108 | 109 | GLuint shader_program = glCreateProgram(); 110 | glAttachShader(shader_program, vertex_shader); 111 | glAttachShader(shader_program, fragment_shader); 112 | glLinkProgram(shader_program); 113 | 114 | glDeleteShader(vertex_shader); 115 | glDeleteShader(fragment_shader); 116 | 117 | if (!check_shader_program_link_errors(shader_program)) { 118 | return 0; 119 | } 120 | 121 | return shader_program; 122 | } 123 | 124 | static GLuint compile_shader(GLenum shader_type, const GLchar *shader_source) 125 | { 126 | GLuint shader = glCreateShader(shader_type); 127 | glShaderSource(shader, 1, &shader_source, NULL); 128 | glCompileShader(shader); 129 | if (!check_shader_compile_errors(shader)) { 130 | return 0; 131 | } 132 | 133 | return shader; 134 | } 135 | 136 | static GLint check_shader_compile_errors(GLuint shader) 137 | { 138 | GLint success; 139 | GLchar log[512]; 140 | 141 | glGetShaderiv(shader, GL_COMPILE_STATUS, &success); 142 | if (!success) { 143 | glGetShaderInfoLog(shader, 512, NULL, log); 144 | printf("ERROR::SHADER::COMPILATION_FAILED\n%s\n", log); 145 | } 146 | 147 | return success; 148 | } 149 | 150 | static GLint check_shader_program_link_errors(GLuint program) 151 | { 152 | GLint success; 153 | GLchar log[512]; 154 | 155 | glGetProgramiv(program, GL_LINK_STATUS, &success); 156 | if (!success) { 157 | glGetProgramInfoLog(program, 512, NULL, log); 158 | printf("ERROR::SHADER::LINKING_FAILED\n%s\n", log); 159 | } 160 | 161 | return success; 162 | } 163 | 164 | int shaders_init(ShaderProgram *shaders) 165 | { 166 | ShaderProgram quad_shader = create_shader(quad_vertex, quad_fragment); 167 | if (!quad_shader) { 168 | printf("Failed to create quad shader.\n"); 169 | return 0; 170 | } 171 | 172 | ShaderProgram movie_shader = 173 | create_shader(movie_vertex, movie_fragment); 174 | if (!movie_shader) { 175 | printf("Failed to create movie shader.\n"); 176 | return 0; 177 | } 178 | 179 | ShaderProgram barrier_shader = 180 | create_shader(quad_vertex, barrier_quad_fragment); 181 | 182 | shaders[SPRITE_SHADER] = quad_shader; 183 | shaders[MOVIE_SHADER] = movie_shader; 184 | shaders[BARRIER_SHADER] = barrier_shader; 185 | 186 | return 1; 187 | } 188 | 189 | void shader_program_set_texture_samplers(ShaderProgram program, 190 | const GLint *samplers, 191 | const GLint sampler_count) 192 | { 193 | glUniform1iv(glGetUniformLocation(program, "u_Textures"), sampler_count, 194 | samplers); 195 | } 196 | 197 | void shader_program_set_mat4(ShaderProgram program, const GLchar *name, 198 | const GLfloat mat4[4][4]) 199 | { 200 | glUniformMatrix4fv(glGetUniformLocation(program, name), 1, GL_FALSE, 201 | (const GLfloat *)mat4); 202 | } 203 | -------------------------------------------------------------------------------- /scripts/extract_n_decompress.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import pefile 4 | import os 5 | import subprocess 6 | from PIL import Image 7 | import os, sys 8 | import pathlib 9 | import json 10 | import io 11 | 12 | EXE_FILE = "./binaries/lain_win.exe" 13 | DAT_FILE = "./binaries/lain_win.dat" 14 | 15 | SPRITES_TMP_DIR = "./extracted/sprites/tmp/" 16 | SPRITES_DECOMPRESSED_DIR = "./extracted/sprites/decompressed/" 17 | WAVES_EXTRACTED_DIR = "./extracted/sounds/" 18 | 19 | 20 | def bytes_to_str(bytes_to_convert): 21 | return "".join(map(chr, bytes_to_convert)) 22 | 23 | 24 | def read_bytes(file, n, signed=False): 25 | return int.from_bytes(file.read(n), "little", signed=signed) 26 | 27 | 28 | def read_lain_dat(file): 29 | res = {} 30 | res["lain_dress"] = read_bytes(file, 1) 31 | res["selected_theatre"] = read_bytes(file, 1) 32 | res["progress_flags"] = bin(read_bytes(file, 2))[2:] 33 | res["score"] = read_bytes(file, 4) 34 | res["total_bears_popped"] = read_bytes(file, 4) 35 | res["equipment_flags"] = bin(read_bytes(file, 4))[2:] 36 | 37 | collision_rect_count = read_bytes(file, 2) 38 | res["rectangles"] = {} 39 | for i in range(collision_rect_count): 40 | id = str(read_bytes(file, 4)) 41 | rect = {} 42 | rect["left"] = read_bytes(file, 4) 43 | rect["top"] = read_bytes(file, 4) 44 | rect["right"] = read_bytes(file, 4) 45 | rect["bottom"] = read_bytes(file, 4) 46 | res["rectangles"][id] = rect 47 | 48 | animation_count = read_bytes(file, 2) 49 | frame_count = read_bytes(file, 4) 50 | res["animations"] = {} 51 | for i in range(animation_count): 52 | id = str(read_bytes(file, 2)) 53 | anim = {} 54 | anim["frame_count"] = read_bytes(file, 2) 55 | anim["first_frame"] = read_bytes(file, 4) 56 | res["animations"][id] = anim 57 | 58 | frames = {} 59 | for i in range(frame_count): 60 | frames[i] = {} 61 | frames[i]["timing"] = read_bytes(file, 2) 62 | read_bytes(file, 1) 63 | frames[i]["layer"] = read_bytes(file, 1) 64 | frames[i]["res_id"] = read_bytes(file, 2) 65 | frames[i]["rel_x"] = read_bytes(file, 2, True) 66 | frames[i]["rel_y"] = read_bytes(file, 2, True) 67 | frames[i]["half_width"] = read_bytes(file, 1) 68 | frames[i]["half_height"] = read_bytes(file, 1) 69 | 70 | for anim in res["animations"].values(): 71 | anim["frames"] = [] 72 | for i in range(anim["first_frame"], anim["first_frame"] + anim["frame_count"]): 73 | anim["frames"].append(frames[i]) 74 | del anim["first_frame"] 75 | del anim["frame_count"] 76 | 77 | return res 78 | 79 | 80 | # taken from https://gist.github.com/jjo-sec/0ead6f9a9f91e420f7c8 81 | def extract_icon(pe): 82 | rt_string_idx = [entry.id for entry in pe.DIRECTORY_ENTRY_RESOURCE.entries].index( 83 | pefile.RESOURCE_TYPE["RT_GROUP_ICON"] 84 | ) 85 | rt_string_directory = pe.DIRECTORY_ENTRY_RESOURCE.entries[rt_string_idx] 86 | rt_string_directory = [ 87 | e 88 | for e in pe.DIRECTORY_ENTRY_RESOURCE.entries 89 | if e.id == pefile.RESOURCE_TYPE["RT_GROUP_ICON"] 90 | ][0] 91 | entry = rt_string_directory.directory.entries[-1] # gives the highest res icon 92 | offset = entry.directory.entries[0].data.struct.OffsetToData 93 | size = entry.directory.entries[0].data.struct.Size 94 | data = pe.get_memory_mapped_image()[offset : offset + size] 95 | icon = data[:18] + "\x16\x00\x00\x00".encode() 96 | 97 | rt_string_idx = [entry.id for entry in pe.DIRECTORY_ENTRY_RESOURCE.entries].index( 98 | pefile.RESOURCE_TYPE["RT_ICON"] 99 | ) 100 | rt_string_directory = pe.DIRECTORY_ENTRY_RESOURCE.entries[rt_string_idx] 101 | rt_string_directory = [ 102 | e 103 | for e in pe.DIRECTORY_ENTRY_RESOURCE.entries 104 | if e.id == pefile.RESOURCE_TYPE["RT_ICON"] 105 | ][0] 106 | entry = rt_string_directory.directory.entries[-1] # gives the highest res icon 107 | offset = entry.directory.entries[0].data.struct.OffsetToData 108 | size = entry.directory.entries[0].data.struct.Size 109 | icon += pe.get_memory_mapped_image()[offset : offset + size] 110 | return icon 111 | 112 | 113 | def main(): 114 | pathlib.Path(SPRITES_TMP_DIR).mkdir(parents=True, exist_ok=True) 115 | pathlib.Path(SPRITES_DECOMPRESSED_DIR).mkdir(parents=True, exist_ok=True) 116 | pathlib.Path(WAVES_EXTRACTED_DIR).mkdir(parents=True, exist_ok=True) 117 | 118 | if not os.path.exists(os.path.join(os.getcwd(), "lzss")): 119 | subprocess.run([os.environ.get("CC", "cc"), "lzss.c", "-o", "lzss"]) 120 | 121 | with open(EXE_FILE, "rb") as f: 122 | pe = pefile.PE(EXE_FILE) 123 | 124 | rcdata_entries = pe.DIRECTORY_ENTRY_RESOURCE.entries[3] 125 | wave_entries = pe.DIRECTORY_ENTRY_RESOURCE.entries[0] 126 | 127 | print("Extracting icon...") 128 | icon_image = Image.open(io.BytesIO(extract_icon(pe))) 129 | icon_image.save(os.path.join(os.getcwd(), "extracted", "window_icon.png"), "png") 130 | 131 | 132 | for i, entry in enumerate(rcdata_entries.directory.entries): 133 | rva = entry.directory.entries[0].data.struct.OffsetToData 134 | size = entry.directory.entries[0].data.struct.Size 135 | data = pe.get_memory_mapped_image()[rva : rva + size] 136 | 137 | tempname = "tmp-{}".format(i) 138 | tempfile_path = os.path.join(os.getcwd(), SPRITES_TMP_DIR, tempname) 139 | decompressed_path = os.path.join( 140 | os.getcwd(), SPRITES_DECOMPRESSED_DIR, str(i) 141 | ) 142 | 143 | print("Extracting sprite {}...".format(i)) 144 | 145 | if not os.path.exists(os.path.join(os.getcwd(), tempfile_path)): 146 | with open(tempfile_path, "wb") as target: 147 | target.write(data) 148 | 149 | if not os.path.exists(os.path.join(os.getcwd(), decompressed_path)): 150 | print("Decompressing sprite {}...".format(i)) 151 | subprocess.run(["./lzss", tempfile_path, decompressed_path]) 152 | 153 | for i, entry in enumerate(wave_entries.directory.entries): 154 | rva = entry.directory.entries[0].data.struct.OffsetToData 155 | size = entry.directory.entries[0].data.struct.Size 156 | data = pe.get_memory_mapped_image()[rva : rva + size] 157 | 158 | print("Extracting {}.wav...".format(i)) 159 | outfile = os.path.join(os.getcwd(), WAVES_EXTRACTED_DIR, "{}.wav".format(i)) 160 | 161 | with open(outfile, "wb") as target: 162 | target.write(data) 163 | 164 | with open(DAT_FILE, "rb") as f: 165 | data = read_lain_dat(f) 166 | outfile = os.path.join(os.getcwd(), "extracted", "lain_win.json") 167 | 168 | with open(outfile, "w") as target: 169 | target.write(json.dumps(data, indent=2)) 170 | 171 | 172 | if __name__ == "__main__": 173 | main() 174 | -------------------------------------------------------------------------------- /src/cvector.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef CVECTOR_H_ 3 | #define CVECTOR_H_ 4 | 5 | #include /* for assert */ 6 | #include /* for malloc/realloc/free */ 7 | 8 | /** 9 | * @brief cvector_vector_type - The vector type used in this library 10 | */ 11 | #define cvector_vector_type(type) type * 12 | 13 | /** 14 | * @brief cvector_set_capacity - For internal use, sets the capacity variable of the vector 15 | * @param vec - the vector 16 | * @param size - the new capacity to set 17 | * @return void 18 | */ 19 | #define cvector_set_capacity(vec, size) \ 20 | do { \ 21 | if (vec) { \ 22 | ((size_t *)(vec))[-1] = (size); \ 23 | } \ 24 | } while (0) 25 | 26 | /** 27 | * @brief cvector_set_size - For internal use, sets the size variable of the vector 28 | * @param vec - the vector 29 | * @param size - the new capacity to set 30 | * @return void 31 | */ 32 | #define cvector_set_size(vec, size) \ 33 | do { \ 34 | if (vec) { \ 35 | ((size_t *)(vec))[-2] = (size); \ 36 | } \ 37 | } while (0) 38 | 39 | /** 40 | * @brief cvector_capacity - gets the current capacity of the vector 41 | * @param vec - the vector 42 | * @return the capacity as a size_t 43 | */ 44 | #define cvector_capacity(vec) \ 45 | ((vec) ? ((size_t *)(vec))[-1] : (size_t)0) 46 | 47 | /** 48 | * @brief cvector_size - gets the current size of the vector 49 | * @param vec - the vector 50 | * @return the size as a size_t 51 | */ 52 | #define cvector_size(vec) \ 53 | ((vec) ? ((size_t *)(vec))[-2] : (size_t)0) 54 | 55 | /** 56 | * @brief cvector_empty - returns non-zero if the vector is empty 57 | * @param vec - the vector 58 | * @return non-zero if empty, zero if non-empty 59 | */ 60 | #define cvector_empty(vec) \ 61 | (cvector_size(vec) == 0) 62 | 63 | /** 64 | * @brief cvector_grow - For internal use, ensures that the vector is at least elements big 65 | * @param vec - the vector 66 | * @param count - the new capacity to set 67 | * @return void 68 | */ 69 | #define cvector_grow(vec, count) \ 70 | do { \ 71 | const size_t cv_sz = (count) * sizeof(*(vec)) + (sizeof(size_t) * 2); \ 72 | if (!(vec)) { \ 73 | size_t *cv_p = malloc(cv_sz); \ 74 | assert(cv_p); \ 75 | (vec) = (void *)(&cv_p[2]); \ 76 | cvector_set_capacity((vec), (count)); \ 77 | cvector_set_size((vec), 0); \ 78 | } else { \ 79 | size_t *cv_p1 = &((size_t *)(vec))[-2]; \ 80 | size_t *cv_p2 = realloc(cv_p1, (cv_sz)); \ 81 | assert(cv_p2); \ 82 | (vec) = (void *)(&cv_p2[2]); \ 83 | cvector_set_capacity((vec), (count)); \ 84 | } \ 85 | } while (0) 86 | 87 | /** 88 | * @brief cvector_pop_back - removes the last element from the vector 89 | * @param vec - the vector 90 | * @return void 91 | */ 92 | #define cvector_pop_back(vec) \ 93 | do { \ 94 | cvector_set_size((vec), cvector_size(vec) - 1); \ 95 | } while (0) 96 | 97 | /** 98 | * @brief cvector_erase - removes the element at index i from the vector 99 | * @param vec - the vector 100 | * @param i - index of element to remove 101 | * @return void 102 | */ 103 | #define cvector_erase(vec, i) \ 104 | do { \ 105 | if (vec) { \ 106 | const size_t cv_sz = cvector_size(vec); \ 107 | if ((i) < cv_sz) { \ 108 | cvector_set_size((vec), cv_sz - 1); \ 109 | size_t cv_x; \ 110 | for (cv_x = (i); cv_x < (cv_sz - 1); ++cv_x) { \ 111 | (vec)[cv_x] = (vec)[cv_x + 1]; \ 112 | } \ 113 | } \ 114 | } \ 115 | } while (0) 116 | 117 | /** 118 | * @brief cvector_free - frees all memory associated with the vector 119 | * @param vec - the vector 120 | * @return void 121 | */ 122 | #define cvector_free(vec) \ 123 | do { \ 124 | if (vec) { \ 125 | size_t *p1 = &((size_t *)(vec))[-2]; \ 126 | free(p1); \ 127 | } \ 128 | } while (0) 129 | 130 | /** 131 | * @brief cvector_begin - returns an iterator to first element of the vector 132 | * @param vec - the vector 133 | * @return a pointer to the first element (or NULL) 134 | */ 135 | #define cvector_begin(vec) \ 136 | (vec) 137 | 138 | /** 139 | * @brief cvector_end - returns an iterator to one past the last element of the vector 140 | * @param vec - the vector 141 | * @return a pointer to one past the last element (or NULL) 142 | */ 143 | #define cvector_end(vec) \ 144 | ((vec) ? &((vec)[cvector_size(vec)]) : NULL) 145 | 146 | /* user request to use logarithmic growth algorithm */ 147 | #ifdef CVECTOR_LOGARITHMIC_GROWTH 148 | 149 | /** 150 | * @brief cvector_push_back - adds an element to the end of the vector 151 | * @param vec - the vector 152 | * @param value - the value to add 153 | * @return void 154 | */ 155 | #define cvector_push_back(vec, value) \ 156 | do { \ 157 | size_t cv_cap = cvector_capacity(vec); \ 158 | if (cv_cap <= cvector_size(vec)) { \ 159 | cvector_grow((vec), !cv_cap ? cv_cap + 1 : cv_cap * 2); \ 160 | } \ 161 | vec[cvector_size(vec)] = (value); \ 162 | cvector_set_size((vec), cvector_size(vec) + 1); \ 163 | } while (0) 164 | 165 | #else 166 | 167 | /** 168 | * @brief cvector_push_back - adds an element to the end of the vector 169 | * @param vec - the vector 170 | * @param value - the value to add 171 | * @return void 172 | */ 173 | #define cvector_push_back(vec, value) \ 174 | do { \ 175 | size_t cv_cap = cvector_capacity(vec); \ 176 | if (cv_cap <= cvector_size(vec)) { \ 177 | cvector_grow((vec), cv_cap + 1); \ 178 | } \ 179 | vec[cvector_size(vec)] = (value); \ 180 | cvector_set_size((vec), cvector_size(vec) + 1); \ 181 | } while (0) 182 | 183 | #endif /* CVECTOR_LOGARITHMIC_GROWTH */ 184 | 185 | /** 186 | * @brief cvector_copy - copy a vector 187 | * @param from - the original vector 188 | * @param to - destination to which the function copy to 189 | * @return void 190 | */ 191 | #define cvector_copy(from, to) \ 192 | do { \ 193 | for(size_t i = 0; i < cvector_size(from); i++) { \ 194 | cvector_push_back(to, from[i]); \ 195 | } \ 196 | } while (0) \ 197 | 198 | #endif /* CVECTOR_H_ */ 199 | -------------------------------------------------------------------------------- /src/state.c: -------------------------------------------------------------------------------- 1 | #include "state.h" 2 | #include "cJSON.h" 3 | #include "dressup.h" 4 | #include "engine.h" 5 | #include "texture.h" 6 | 7 | #include 8 | 9 | #if defined(__linux__) || defined(__APPLE__) 10 | #include 11 | #else 12 | #define access _access 13 | #endif 14 | 15 | void reset_game_state(Resources *resources, GameState *game_state) 16 | { 17 | game_state->score = 0; 18 | 19 | game_state->current_theater_preview = CLASSROOM_PREVIEW; 20 | 21 | game_state->lain = (Lain){.tool_state = NO_TOOLS}; 22 | lain_set_outfit(resources, OUTFIT_DEFAULT, &game_state->lain); 23 | 24 | game_state->school_outfit_unlocked = false; 25 | game_state->alien_outfit_unlocked = false; 26 | game_state->bear_outfit_unlocked = false; 27 | game_state->sweater_outfit_unlocked = false; 28 | game_state->cyberia_outfit_unlocked = false; 29 | game_state->screwdriver_unlocked = false; 30 | game_state->navi_unlocked = false; 31 | } 32 | 33 | int init_game_state(Resources *resources, GameState *game_state) 34 | { 35 | 36 | if (!load_save_file(resources, game_state)) { 37 | printf( 38 | "Failed to load save file. Starting from a fresh state.\n"); 39 | 40 | reset_game_state(resources, game_state); 41 | } 42 | 43 | return 1; 44 | } 45 | 46 | int write_save_file(Engine *engine) 47 | { 48 | char *string = NULL; 49 | cJSON *score = NULL; 50 | cJSON *theater_preview_texture_id = NULL; 51 | cJSON *lain_tool_state = NULL; 52 | cJSON *lain_outfit = NULL; 53 | cJSON *school_outfit_unlocked = NULL; 54 | cJSON *cyberia_outfit_unlocked = NULL; 55 | cJSON *sweater_outfit_unlocked = NULL; 56 | cJSON *bear_outfit_unlocked = NULL; 57 | cJSON *alien_outfit_unlocked = NULL; 58 | cJSON *screwdriver_unlocked = NULL; 59 | cJSON *navi_unlocked = NULL; 60 | 61 | cJSON *save_state = cJSON_CreateObject(); 62 | 63 | score = cJSON_CreateNumber(engine->game_state.score); 64 | 65 | theater_preview_texture_id = 66 | cJSON_CreateNumber(engine->menu.theater_preview.texture->id); 67 | 68 | lain_outfit = cJSON_CreateNumber(engine->game_state.lain.outfit); 69 | 70 | lain_tool_state = 71 | cJSON_CreateNumber(engine->game_state.lain.tool_state); 72 | 73 | school_outfit_unlocked = 74 | cJSON_CreateNumber(engine->game_state.school_outfit_unlocked); 75 | 76 | cyberia_outfit_unlocked = 77 | cJSON_CreateNumber(engine->game_state.cyberia_outfit_unlocked); 78 | 79 | sweater_outfit_unlocked = 80 | cJSON_CreateNumber(engine->game_state.sweater_outfit_unlocked); 81 | 82 | bear_outfit_unlocked = 83 | cJSON_CreateNumber(engine->game_state.bear_outfit_unlocked); 84 | 85 | alien_outfit_unlocked = 86 | cJSON_CreateNumber(engine->game_state.alien_outfit_unlocked); 87 | 88 | screwdriver_unlocked = 89 | cJSON_CreateNumber(engine->game_state.screwdriver_unlocked); 90 | 91 | navi_unlocked = cJSON_CreateNumber(engine->game_state.navi_unlocked); 92 | 93 | cJSON *cjson_objs[] = {score, 94 | theater_preview_texture_id, 95 | lain_tool_state, 96 | lain_outfit, 97 | school_outfit_unlocked, 98 | cyberia_outfit_unlocked, 99 | sweater_outfit_unlocked, 100 | bear_outfit_unlocked, 101 | alien_outfit_unlocked, 102 | screwdriver_unlocked, 103 | navi_unlocked}; 104 | for (int i = 0; i < sizeof(cjson_objs) / sizeof(cjson_objs[0]); i++) { 105 | if (cjson_objs[i] == NULL) { 106 | goto fail; 107 | } 108 | } 109 | 110 | cJSON_AddItemToObject(save_state, "score", score); 111 | cJSON_AddItemToObject(save_state, "theater_preview", 112 | theater_preview_texture_id); 113 | cJSON_AddItemToObject(save_state, "tool_state", lain_tool_state); 114 | cJSON_AddItemToObject(save_state, "outfit", lain_outfit); 115 | cJSON_AddItemToObject(save_state, "school_outfit_unlocked", 116 | school_outfit_unlocked); 117 | cJSON_AddItemToObject(save_state, "cyberia_outfit_unlocked", 118 | cyberia_outfit_unlocked); 119 | cJSON_AddItemToObject(save_state, "sweater_outfit_unlocked", 120 | sweater_outfit_unlocked); 121 | cJSON_AddItemToObject(save_state, "bear_outfit_unlocked", 122 | bear_outfit_unlocked); 123 | cJSON_AddItemToObject(save_state, "alien_outfit_unlocked", 124 | alien_outfit_unlocked); 125 | cJSON_AddItemToObject(save_state, "screwdriver_unlocked", 126 | screwdriver_unlocked); 127 | cJSON_AddItemToObject(save_state, "navi_unlocked", navi_unlocked); 128 | 129 | string = cJSON_Print(save_state); 130 | if (string == NULL) { 131 | printf("Failed to print save state.\n"); 132 | goto fail; 133 | } 134 | 135 | FILE *f = fopen("./lain_save.json", "w"); 136 | if (f == NULL) { 137 | printf("Failed to open save file.\n"); 138 | free(string); 139 | goto fail; 140 | } 141 | 142 | fprintf(f, "%s\n", string); 143 | 144 | free(string); 145 | cJSON_Delete(save_state); 146 | 147 | return 1; 148 | fail: 149 | cJSON_Delete(save_state); 150 | return 0; 151 | } 152 | 153 | int load_save_file(Resources *resources, GameState *game_state) 154 | { 155 | if (access("./lain_save.json", 0) != 0) { 156 | return 0; 157 | } 158 | FILE *f = NULL; 159 | long len = 0; 160 | char *data = NULL; 161 | 162 | f = fopen("./lain_save.json", "rb"); 163 | fseek(f, 0, SEEK_END); 164 | len = ftell(f); 165 | fseek(f, 0, SEEK_SET); 166 | 167 | data = (char *)malloc(len + 1); 168 | 169 | fread(data, 1, len, f); 170 | data[len] = '\0'; 171 | fclose(f); 172 | 173 | cJSON *save_state = cJSON_Parse(data); 174 | 175 | cJSON *score = NULL; 176 | cJSON *theater_preview_texture_id = NULL; 177 | cJSON *lain_tool_state = NULL; 178 | cJSON *lain_outfit = NULL; 179 | cJSON *school_outfit_unlocked = NULL; 180 | cJSON *cyberia_outfit_unlocked = NULL; 181 | cJSON *sweater_outfit_unlocked = NULL; 182 | cJSON *bear_outfit_unlocked = NULL; 183 | cJSON *alien_outfit_unlocked = NULL; 184 | cJSON *screwdriver_unlocked = NULL; 185 | cJSON *navi_unlocked = NULL; 186 | 187 | score = cJSON_GetObjectItem(save_state, "score"); 188 | 189 | theater_preview_texture_id = 190 | cJSON_GetObjectItem(save_state, "theater_preview"); 191 | 192 | lain_tool_state = cJSON_GetObjectItem(save_state, "tool_state"); 193 | 194 | lain_outfit = cJSON_GetObjectItem(save_state, "outfit"); 195 | 196 | school_outfit_unlocked = 197 | cJSON_GetObjectItem(save_state, "school_outfit_unlocked"); 198 | 199 | cyberia_outfit_unlocked = 200 | cJSON_GetObjectItem(save_state, "cyberia_outfit_unlocked"); 201 | 202 | sweater_outfit_unlocked = 203 | cJSON_GetObjectItem(save_state, "sweater_outfit_unlocked"); 204 | 205 | bear_outfit_unlocked = 206 | cJSON_GetObjectItem(save_state, "bear_outfit_unlocked"); 207 | 208 | alien_outfit_unlocked = 209 | cJSON_GetObjectItem(save_state, "alien_outfit_unlocked"); 210 | 211 | screwdriver_unlocked = 212 | cJSON_GetObjectItem(save_state, "screwdriver_unlocked"); 213 | 214 | navi_unlocked = cJSON_GetObjectItem(save_state, "navi_unlocked"); 215 | 216 | cJSON *cjson_objs[] = {score, 217 | theater_preview_texture_id, 218 | lain_tool_state, 219 | lain_outfit, 220 | school_outfit_unlocked, 221 | cyberia_outfit_unlocked, 222 | sweater_outfit_unlocked, 223 | bear_outfit_unlocked, 224 | alien_outfit_unlocked, 225 | screwdriver_unlocked, 226 | navi_unlocked}; 227 | for (int i = 0; i < sizeof(cjson_objs) / sizeof(cjson_objs[0]); i++) { 228 | if (cjson_objs[i] == NULL) { 229 | goto fail; 230 | } 231 | } 232 | 233 | game_state->score = score->valueint; 234 | game_state->lain.tool_state = lain_tool_state->valueint; 235 | 236 | game_state->lain.outfit = lain_outfit->valueint; 237 | lain_set_outfit(resources, game_state->lain.outfit, &game_state->lain); 238 | 239 | game_state->current_theater_preview = 240 | theater_preview_texture_id->valueint; 241 | 242 | game_state->school_outfit_unlocked = school_outfit_unlocked->valueint; 243 | game_state->alien_outfit_unlocked = alien_outfit_unlocked->valueint; 244 | game_state->bear_outfit_unlocked = bear_outfit_unlocked->valueint; 245 | game_state->sweater_outfit_unlocked = sweater_outfit_unlocked->valueint; 246 | game_state->cyberia_outfit_unlocked = cyberia_outfit_unlocked->valueint; 247 | game_state->screwdriver_unlocked = screwdriver_unlocked->valueint; 248 | game_state->navi_unlocked = navi_unlocked->valueint; 249 | 250 | cJSON_Delete(save_state); 251 | free(data); 252 | 253 | return 1; 254 | fail: 255 | cJSON_Delete(save_state); 256 | free(data); 257 | return 0; 258 | } 259 | -------------------------------------------------------------------------------- /scripts/lzss.c: -------------------------------------------------------------------------------- 1 | /* LZSS decompression code from lain bootleg, modified output from ghidra so 2 | * some variables don't have meaningful names, but thanks to zerno, functions and most variables 3 | * do have proper names. */ 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | char *LZSS_compressed_data; 11 | unsigned char current_compressed_byte; 12 | int LZSS_bit_counter; 13 | char *LZSS_Output_Memory; 14 | int DAT_00412fe0; 15 | char BIT_MASKS[8] = {0x80, 0x40, 0x20, 0x10, 0x8, 0x4, 0x2, 0x1}; 16 | unsigned int DAT_00412fc8; 17 | unsigned int DAT_00412fcc; 18 | unsigned int DAT_00412fd0; 19 | unsigned int circular_buffer_current; 20 | unsigned int DAT_00412fd4; 21 | unsigned int DAT_00412fdc; 22 | char *LZSS_circular_buffer; 23 | 24 | void set_lzss_src(char *compressed_data) 25 | 26 | { 27 | LZSS_compressed_data = compressed_data; 28 | current_compressed_byte = 0; 29 | LZSS_bit_counter = 0; 30 | return; 31 | } 32 | 33 | void set_lzss_dst(char *output_memory) 34 | 35 | { 36 | LZSS_Output_Memory = output_memory; 37 | DAT_00412fe0 = 0; 38 | return; 39 | } 40 | 41 | bool get_next_bit(void) 42 | 43 | { 44 | int uVar1; 45 | 46 | if (LZSS_bit_counter == 0) { 47 | current_compressed_byte = *LZSS_compressed_data; 48 | LZSS_compressed_data = LZSS_compressed_data + 1; 49 | } 50 | uVar1 = LZSS_bit_counter; 51 | LZSS_bit_counter += 1; 52 | if (LZSS_bit_counter == 8) { 53 | LZSS_bit_counter = 0; 54 | } 55 | return (BIT_MASKS[uVar1] & current_compressed_byte) != 0; 56 | } 57 | 58 | unsigned int read_bits(int num_bits) 59 | 60 | { 61 | bool bVar1; 62 | unsigned int output_bits; 63 | 64 | output_bits = 0; 65 | if (num_bits != 0) { 66 | do { 67 | output_bits <<= 1; 68 | bVar1 = get_next_bit(); 69 | if (bVar1 != 0) { 70 | output_bits |= 1; 71 | } 72 | num_bits += -1; 73 | } while (num_bits != 0); 74 | } 75 | return output_bits; 76 | } 77 | 78 | char *get_circular_buffer_element_pointer(unsigned int param_1) 79 | 80 | { 81 | return (char *)((DAT_00412fd0 - 1 & param_1) + LZSS_circular_buffer); 82 | } 83 | 84 | void write_n_of_byte(unsigned int param_1, unsigned char param_2) 85 | 86 | { 87 | /* printf("writing %u of %02x\n", param_1, param_2); */ 88 | if (param_1 != 0) { 89 | do { 90 | *LZSS_Output_Memory = param_2; 91 | LZSS_Output_Memory = LZSS_Output_Memory + 1; 92 | param_1 += -1; 93 | } while (param_1 != 0); 94 | } 95 | return; 96 | } 97 | 98 | void write_output(unsigned int param_1) 99 | 100 | { 101 | /* printf("write_output %u\n", param_1); */ 102 | if (DAT_00412fe0 == 0) { 103 | DAT_00412fdc = param_1; 104 | DAT_00412fe0 = 1; 105 | return; 106 | } 107 | if (DAT_00412fe0 != 1) { 108 | if (DAT_00412fe0 == 2) { 109 | DAT_00412fe0 = param_1; 110 | if (param_1 == 0) { 111 | DAT_00412fe0 = 0x100; 112 | } 113 | param_1 = DAT_00412fdc; 114 | if (3 < DAT_00412fe0) { 115 | return; 116 | } 117 | } 118 | write_n_of_byte(DAT_00412fe0, (char)param_1); 119 | DAT_00412fe0 = 1; 120 | return; 121 | } 122 | if (param_1 != DAT_00412fdc) { 123 | write_n_of_byte(1, (char)param_1); 124 | return; 125 | } 126 | DAT_00412fe0 = 2; 127 | return; 128 | } 129 | 130 | int read_displacement(void) 131 | 132 | { 133 | bool bVar1; 134 | unsigned int uVar2; 135 | int iVar3; 136 | 137 | /* printf("DAT_00412fc8 = %i", DAT_00412fc8); */ 138 | uVar2 = read_bits(DAT_00412fc8 - 1); 139 | if (DAT_00412fd4 - 1 <= (int)uVar2) { 140 | iVar3 = uVar2 * 2; 141 | bVar1 = get_next_bit(); 142 | if (bVar1 != 0) { 143 | iVar3 += 1; 144 | } 145 | if (iVar3 == DAT_00412fd0 - 1) { 146 | return -1; 147 | } 148 | uVar2 = iVar3 + (1 - DAT_00412fd4); 149 | } 150 | return uVar2; 151 | } 152 | 153 | int read_length(void) 154 | 155 | { 156 | bool bVar1; 157 | unsigned int uVar2; 158 | int iVar3; 159 | 160 | uVar2 = read_bits(DAT_00412fcc - 1); 161 | if (uVar2 == 1) { 162 | return 2; 163 | } 164 | iVar3 = uVar2 * 2; 165 | bVar1 = get_next_bit(); 166 | if (bVar1 != 0) { 167 | iVar3 += 1; 168 | } 169 | if (iVar3 == 1) { 170 | iVar3 = 3; 171 | } 172 | if (iVar3 == 0) { 173 | iVar3 = DAT_00412fd4; 174 | } 175 | return iVar3; 176 | } 177 | 178 | void copy_from_buffer(int length, int displacement) 179 | 180 | { 181 | unsigned char bVar1; 182 | unsigned int uVar2; 183 | char *pbVar3; 184 | 185 | if (length != 0) { 186 | do { 187 | pbVar3 = get_circular_buffer_element_pointer(displacement + circular_buffer_current); 188 | uVar2 = circular_buffer_current; 189 | bVar1 = *pbVar3; 190 | circular_buffer_current += 1; 191 | pbVar3 = get_circular_buffer_element_pointer(uVar2); 192 | *pbVar3 = bVar1; 193 | /* printf("copy_from_buffer bVar1 = %02x\n", bVar1); */ 194 | write_output(bVar1); 195 | length -= 1; 196 | } while (length != 0); 197 | } 198 | return; 199 | } 200 | 201 | void DecompressLZSS(char *compressed_data, char *output_memory, 202 | void *circular_buffer) 203 | 204 | { 205 | unsigned int uVar1; 206 | bool flag; 207 | unsigned int uVar3; 208 | char *puVar4; 209 | unsigned int disp; 210 | unsigned int displacement; 211 | unsigned int length; 212 | 213 | set_lzss_src(compressed_data + 8); 214 | set_lzss_dst(output_memory); 215 | // usually b01011 (11) 216 | DAT_00412fc8 = read_bits(5); 217 | printf("DAT_00412fc8 = %i\n", DAT_00412fc8); 218 | // usually b0100 (4) 219 | DAT_00412fcc = read_bits(4); 220 | printf("DAT_00412fcc = %i\n", DAT_00412fcc); 221 | // number of possible values for a 11 bit integer 222 | DAT_00412fd0 = 1 << (DAT_00412fc8 & 0x1f); 223 | printf("DAT_00412fd0 = %i\n", DAT_00412fd0); 224 | LZSS_circular_buffer = circular_buffer; 225 | circular_buffer_current = 0; 226 | // number of possible values for a 4 bit integer 227 | DAT_00412fd4 = 1 << (DAT_00412fcc & 0x1f); 228 | printf("DAT_00412fd4 = %i\n", DAT_00412fd4); 229 | do { 230 | flag = get_next_bit(); 231 | if (flag == 0) { 232 | disp = read_displacement(); 233 | /* printf("disp = %i\n", disp); */ 234 | if (disp == -1) { 235 | return; 236 | } 237 | displacement = (DAT_00412fd0 - disp) + -1; 238 | /* printf("displacement = %i\n", displacement); */ 239 | length = read_length(); 240 | /* printf("length = %i\n", length); */ 241 | copy_from_buffer(length, displacement); 242 | } else { 243 | uVar3 = read_bits(8); 244 | /* printf("main uVar3 = %i\n", uVar3); */ 245 | uVar1 = circular_buffer_current; 246 | circular_buffer_current += 1; 247 | puVar4 = get_circular_buffer_element_pointer(uVar1); 248 | *puVar4 = (char)uVar3; 249 | write_output(uVar3); 250 | } 251 | circular_buffer_current &= DAT_00412fd0 - 1U; 252 | } while (true); 253 | } 254 | 255 | int load_file_to_memory(const char *filename, char **result) { 256 | int size = 0; 257 | FILE *f = fopen(filename, "rb"); 258 | if (f == NULL) { 259 | *result = NULL; 260 | return -1; // -1 means file opening fail 261 | } 262 | fseek(f, 0, SEEK_END); 263 | size = ftell(f); 264 | fseek(f, 0, SEEK_SET); 265 | *result = (char *)malloc(size + 1); 266 | if (size != fread(*result, sizeof(char), size, f)) { 267 | free(*result); 268 | return -2; // -2 means file reading fail 269 | } 270 | fclose(f); 271 | (*result)[size] = 0; 272 | return size; 273 | } 274 | 275 | int main(int argc, char *argv[]) { 276 | if (argc != 3) { 277 | printf("Too many or too few arguments\nUsage: lzss in_filename " 278 | "out_filename\n"); 279 | return 1; 280 | } 281 | 282 | char *compressed_data; 283 | char *uncompressed_data; 284 | int file_size; 285 | uint32_t uncompressed_size; 286 | 287 | file_size = load_file_to_memory(argv[1], &compressed_data); 288 | if (file_size < 0) { 289 | printf("Error loading input file\n"); 290 | return 1; 291 | } 292 | 293 | if (memcmp(compressed_data, "LZSS", 4) != 0) { 294 | printf("Not a valid LZSS file"); 295 | return 1; 296 | } 297 | 298 | uncompressed_size = *(uint32_t *)&compressed_data[4]; 299 | printf("uncompressed_size: %d\n", uncompressed_size); 300 | uncompressed_data = (char *)malloc(uncompressed_size); 301 | void *circular_buffer = malloc(65536); 302 | 303 | DecompressLZSS(compressed_data, uncompressed_data, circular_buffer); 304 | FILE *f = fopen(argv[2], "wb"); 305 | if (f == NULL) { 306 | printf("failed to open output file\n"); 307 | return 1; 308 | } 309 | fwrite(uncompressed_data, uncompressed_size, 1, f); 310 | fclose(f); 311 | return 0; 312 | } 313 | -------------------------------------------------------------------------------- /src/scene.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #define CVECTOR_LOGARITHMIC_GROWTH 6 | 7 | #include "scene.h" 8 | #include "shader.h" 9 | #include "sprite.h" 10 | 11 | #define MAX_SCENE_SPRITES 50 12 | #define MAX_SCENE_TEXTURES 16 13 | 14 | #define MAX_SCENE_VBO_SIZE MAX_SCENE_SPRITES *SPRITE_VBO_SIZE 15 | #define MAX_SCENE_IBO_SIZE MAX_SCENE_SPRITES *SPRITE_INDEX_COUNT 16 | 17 | // clang-format off 18 | #define IDENTITY_MAT4 {{1.0f, 0.0f, 0.0f, 0.0f}, \ 19 | {0.0f, 1.0f, 0.0f, 0.0f}, \ 20 | {0.0f, 0.0f, 1.0f, 0.0f}, \ 21 | {0.0f, 0.0f, 0.0f, 1.0f}} 22 | 23 | #define ZEROS_MAT4 {{0.0f, 0.0f, 0.0f, 0.0f}, \ 24 | {0.0f, 0.0f, 0.0f, 0.0f}, \ 25 | {0.0f, 0.0f, 0.0f, 0.0f}, \ 26 | {0.0f, 0.0f, 0.0f, 0.0f}} 27 | // clang-format on 28 | 29 | static GLfloat *get_click_barrier_vertices(GLfloat *buffer, 30 | ClickBarrier *barrier) 31 | { 32 | GLfloat vertices[] = { 33 | // top right 34 | barrier->right, 35 | barrier->top, 36 | 0.0f, 37 | 0.0f, 38 | -1.0f, 39 | 40 | // bottom right 41 | barrier->right, 42 | barrier->bottom, 43 | 0.0f, 44 | 0.0f, 45 | -1.0f, 46 | 47 | // bottom left 48 | barrier->left, 49 | barrier->bottom, 50 | 0.0f, 51 | 0.0f, 52 | -1.0f, 53 | 54 | // top left 55 | barrier->left, 56 | barrier->top, 57 | 0.0f, 58 | 0.0f, 59 | -1.0f, 60 | }; 61 | 62 | memcpy(buffer, vertices, sizeof(vertices)); 63 | buffer += sizeof(vertices) / sizeof(vertices[0]); 64 | return buffer; 65 | } 66 | 67 | static void init_scene_buffers(Scene *scene) 68 | { 69 | glGenVertexArrays(1, &scene->VAO); 70 | glBindVertexArray(scene->VAO); 71 | 72 | glGenBuffers(1, &scene->VBO); 73 | glBindBuffer(GL_ARRAY_BUFFER, scene->VBO); 74 | 75 | glGenBuffers(1, &scene->IBO); 76 | glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, scene->IBO); 77 | 78 | GLuint index_buffer[MAX_SCENE_IBO_SIZE]; 79 | GLuint *p = index_buffer; 80 | 81 | unsigned int offset = 0; 82 | for (int i = 0; i < MAX_SCENE_IBO_SIZE; i += 6) { 83 | GLuint indices[] = { 84 | 0 + offset, 1 + offset, 3 + offset, 85 | 1 + offset, 2 + offset, 3 + offset, 86 | 87 | }; 88 | 89 | memcpy(p, indices, sizeof(indices)); 90 | p += sizeof(indices) / sizeof(GLuint); 91 | 92 | offset += 4; 93 | }; 94 | 95 | glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(index_buffer), 96 | index_buffer, GL_STATIC_DRAW); 97 | 98 | // position 99 | glEnableVertexAttribArray(0); 100 | glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), 101 | (void *)0); 102 | // texture coords 103 | glEnableVertexAttribArray(1); 104 | glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), 105 | (void *)(2 * sizeof(GLfloat))); 106 | 107 | // texture id 108 | glEnableVertexAttribArray(2); 109 | glVertexAttribPointer(2, 1, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), 110 | (void *)(4 * sizeof(GLfloat))); 111 | } 112 | 113 | void init_scene(Scene *scene, Sprite **sprites, uint8_t sprite_count, 114 | SpriteBehavior *sprite_behaviors, uint8_t sprite_behavior_count, 115 | Text **text_objs, uint8_t text_obj_count, 116 | ClickBarrier *click_barriers, uint8_t click_barrier_count) 117 | { 118 | // initialize vao, vbo, ibo 119 | init_scene_buffers(scene); 120 | 121 | scene->sprites = NULL; 122 | depth_sort(sprites, sprite_count); 123 | for (int i = 0; i < sprite_count; i++) { 124 | Sprite *sprite = sprites[i]; 125 | 126 | cvector_push_back(scene->sprites, sprite); 127 | } 128 | 129 | scene->sprite_behaviors = NULL; 130 | for (int i = 0; i < sprite_behavior_count; i++) { 131 | cvector_push_back(scene->sprite_behaviors, sprite_behaviors[i]); 132 | } 133 | 134 | scene->text_objects = NULL; 135 | for (int i = 0; i < text_obj_count; i++) { 136 | Text *text = text_objs[i]; 137 | 138 | text->origin_pos = text->pos; 139 | 140 | cvector_push_back(scene->text_objects, text); 141 | } 142 | 143 | scene->click_barriers = NULL; 144 | for (int i = 0; i < click_barrier_count; i++) { 145 | cvector_push_back(scene->click_barriers, click_barriers[i]); 146 | } 147 | 148 | scene->draw_barriers = false; 149 | 150 | update_scene(scene); 151 | } 152 | 153 | void update_scene(Scene *scene) 154 | { 155 | glBindVertexArray(scene->VAO); 156 | 157 | GLfloat vertices[MAX_SCENE_VBO_SIZE]; 158 | GLfloat *buffer_ptr = vertices; 159 | 160 | uint8_t quad_count = 0; 161 | 162 | for (int i = 0; i < cvector_size(scene->sprites); i++) { 163 | Sprite *sprite = scene->sprites[i]; 164 | 165 | sprite->texture_index = i; 166 | 167 | if (!sprite->visible) { 168 | continue; 169 | } 170 | 171 | if (sprite->pivot_centered) { 172 | buffer_ptr = get_pivot_centered_sprite_vertices( 173 | buffer_ptr, sprite); 174 | } else { 175 | buffer_ptr = get_sprite_vertices(buffer_ptr, sprite); 176 | } 177 | 178 | quad_count++; 179 | }; 180 | 181 | for (int i = 0; i < cvector_size(scene->text_objects); i++) { 182 | Text *text_obj = scene->text_objects[i]; 183 | 184 | text_obj->texture_index = i + cvector_size(scene->sprites); 185 | 186 | if (!text_obj->visible) { 187 | continue; 188 | } 189 | 190 | char *text = text_obj->current_text; 191 | uint8_t text_len = strlen(text); 192 | 193 | for (int j = 0; j < text_len; j++) { 194 | buffer_ptr = get_glyph_vertices(buffer_ptr, text_obj, 195 | text[j], j); 196 | quad_count++; 197 | 198 | if (text[j] == 'A' || text[j] == 'P') { 199 | j++; 200 | } 201 | } 202 | } 203 | 204 | scene->quad_count = quad_count; 205 | 206 | glBindBuffer(GL_ARRAY_BUFFER, scene->VBO); 207 | glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, 208 | GL_STREAM_DRAW); 209 | } 210 | 211 | void draw_scene(Scene *scene, GLFWwindow *window, ShaderProgram *shaders) 212 | { 213 | ShaderProgram sprite_shader = shaders[SPRITE_SHADER]; 214 | ShaderProgram barrier_shader = shaders[BARRIER_SHADER]; 215 | 216 | // window data 217 | int w, h; 218 | glfwGetWindowSize(window, &w, &h); 219 | glViewport(0, 0, w, h); 220 | 221 | // bind appropriate textures 222 | for (int i = 0; i < cvector_size(scene->sprites); i++) { 223 | Sprite *curr = scene->sprites[i]; 224 | if (curr->visible) { 225 | glActiveTexture(GL_TEXTURE0 + curr->texture_index); 226 | glBindTexture(GL_TEXTURE_2D, curr->texture->gl_id); 227 | } 228 | } 229 | 230 | for (int i = 0; i < cvector_size(scene->text_objects); i++) { 231 | Text *curr = scene->text_objects[i]; 232 | glActiveTexture(GL_TEXTURE0 + curr->texture_index); 233 | glBindTexture(GL_TEXTURE_2D, curr->font->texture->gl_id); 234 | } 235 | 236 | // bind shader and set texture samplers 237 | glUseProgram(sprite_shader); 238 | GLint samplers[MAX_SCENE_TEXTURES]; 239 | 240 | uint8_t texture_count = 241 | cvector_size(scene->text_objects) + cvector_size(scene->sprites); 242 | 243 | for (int i = 0; i < texture_count; i++) { 244 | samplers[i] = i; 245 | } 246 | 247 | shader_program_set_texture_samplers(sprite_shader, samplers, 248 | texture_count); 249 | 250 | // set up matrices 251 | const GLfloat view[4][4] = IDENTITY_MAT4; 252 | const GLfloat model[4][4] = IDENTITY_MAT4; 253 | 254 | GLfloat proj[4][4] = ZEROS_MAT4; 255 | float rl = 1.0f / (w - 0.0f); 256 | float tb = 1.0f / (0.0f - h); 257 | float fn = -1.0f / (1.0f - -1.0f); 258 | 259 | proj[0][0] = 2.0f * rl; 260 | proj[1][1] = 2.0f * tb; 261 | proj[2][2] = 2.0f * fn; 262 | proj[3][0] = -(w + 0.0f) * rl; 263 | proj[3][1] = -(0.0f + h) * tb; 264 | proj[3][2] = (1.0f + -1.0f) * fn; 265 | proj[3][3] = 1.0f; 266 | 267 | shader_program_set_mat4(sprite_shader, "u_Projection", proj); 268 | shader_program_set_mat4(sprite_shader, "u_Model", model); 269 | shader_program_set_mat4(sprite_shader, "u_View", view); 270 | 271 | // bind vao 272 | glBindVertexArray(scene->VAO); 273 | 274 | // draw 275 | glDrawElements(GL_TRIANGLES, scene->quad_count * SPRITE_INDEX_COUNT, 276 | GL_UNSIGNED_INT, 0); 277 | 278 | // if we're debugging barriers, draw them also afterwards 279 | if (scene->draw_barriers) { 280 | glUseProgram(barrier_shader); 281 | 282 | shader_program_set_mat4(barrier_shader, "u_Projection", proj); 283 | shader_program_set_mat4(barrier_shader, "u_Model", model); 284 | shader_program_set_mat4(barrier_shader, "u_View", view); 285 | 286 | GLfloat vertices[MAX_SCENE_VBO_SIZE]; 287 | GLfloat *buffer_ptr = vertices; 288 | uint8_t barrier_quad_count = 0; 289 | 290 | for (int i = 0; i < cvector_size(scene->click_barriers); i++) { 291 | buffer_ptr = get_click_barrier_vertices( 292 | buffer_ptr, &scene->click_barriers[i]); 293 | 294 | barrier_quad_count++; 295 | 296 | glBindBuffer(GL_ARRAY_BUFFER, scene->VBO); 297 | glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), 298 | vertices, GL_STREAM_DRAW); 299 | 300 | glDrawElements(GL_TRIANGLES, 301 | barrier_quad_count * SPRITE_INDEX_COUNT, 302 | GL_UNSIGNED_INT, 0); 303 | } 304 | } 305 | } 306 | 307 | void free_scene(Scene *scene) 308 | { 309 | cvector_free(scene->sprites); 310 | cvector_free(scene->click_barriers); 311 | cvector_free(scene->text_objects); 312 | cvector_free(scene->sprite_behaviors); 313 | } 314 | -------------------------------------------------------------------------------- /src/theater.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "animations.h" 6 | #include "minigame.h" 7 | #include "resources.h" 8 | #include "scene.h" 9 | #include "sound.h" 10 | #include "sprite.h" 11 | #include "state.h" 12 | #include "texture.h" 13 | #include "theater.h" 14 | #include "vector2d.h" 15 | #include "window.h" 16 | 17 | static inline int get_lain_walk_animation(LainOutfit outfit) 18 | { 19 | switch (outfit) { 20 | case OUTFIT_SCHOOL: 21 | return SCHOOL_LAIN_THEATER_WALK_ANIMATION; 22 | case OUTFIT_DEFAULT: 23 | return DEFAULT_LAIN_THEATER_WALK_ANIMATION; 24 | case OUTFIT_CYBERIA: 25 | return CYBERIA_LAIN_THEATER_WALK_ANIMATION; 26 | case OUTFIT_ALIEN: 27 | return ALIEN_LAIN_THEATER_WALK_ANIMATION; 28 | case OUTFIT_SWEATER: 29 | return SWEATER_LAIN_THEATER_WALK_ANIMATION; 30 | case OUTFIT_BEAR: 31 | return BEAR_LAIN_THEATER_WALK_ANIMATION; 32 | } 33 | } 34 | 35 | static void load_theater_animation(GameState *game_state, Resources *resources, 36 | Theater *theater, 37 | TheaterAnimationID animation_id) 38 | { 39 | TheaterAnimation *animation = 40 | theater_animation_get(resources, animation_id); 41 | 42 | for (int i = 0; i < animation->layer_count; i++) { 43 | Animation *layer_animation = &animation->layers[i]; 44 | Sprite *layer = &theater->layers[i]; 45 | 46 | make_sprite(&theater->layers[i], (Sprite){ 47 | .z_index = i, 48 | }); 49 | 50 | sprite_set_animation_direct(resources, game_state->time, layer, 51 | layer_animation); 52 | } 53 | 54 | theater->layer_count = animation->layer_count; 55 | } 56 | 57 | static void init_theater(Resources *resources, GameState *game_state, 58 | Theater *theater, int preview) 59 | { 60 | LainOutfit lain_outfit = game_state->lain.outfit; 61 | LainToolState lain_tool_state = game_state->lain.tool_state; 62 | 63 | Sprite *sprites[5] = {&theater->layers[0], &theater->layers[1], 64 | &theater->layers[2], &theater->layers[3], 65 | &theater->layers[4]}; 66 | 67 | switch (preview) { 68 | case CLASSROOM_PREVIEW: { 69 | theater->type = THEATER_CLASSROOM; 70 | 71 | if (lain_outfit == OUTFIT_SCHOOL) { 72 | load_theater_animation(game_state, resources, theater, 73 | THEATER_CLASSROOM_ANIMATION); 74 | } else { 75 | make_sprite(&theater->layers[0], 76 | (Sprite){.visible = true, 77 | .pos = {0.0f, 0.0f}, 78 | .z_index = 0, 79 | .texture = texture_get( 80 | resources, CLASSROOM_BG)}); 81 | 82 | make_sprite(&theater->layers[1], 83 | (Sprite){.visible = true, 84 | .pos = {0.0f, 0.0f}, 85 | .z_index = 2, 86 | .texture = texture_get( 87 | resources, CLASSROOM_TABLES)}); 88 | 89 | make_sprite(&theater->layers[2], 90 | (Sprite){.visible = true, .z_index = 1}); 91 | sprite_set_animation( 92 | resources, game_state->time, &theater->layers[2], 93 | get_lain_walk_animation(lain_outfit)); 94 | 95 | theater->layer_count = 3; 96 | } 97 | break; 98 | } 99 | case SCHOOL_PREVIEW: { 100 | theater->type = THEATER_SCHOOL; 101 | 102 | if (lain_outfit == OUTFIT_SCHOOL && 103 | lain_tool_state == HOLDING_NAVI) { 104 | load_theater_animation(game_state, resources, theater, 105 | THEATER_SCHOOL_ANIMATION); 106 | } else { 107 | make_sprite(&theater->layers[0], 108 | (Sprite){.visible = true, 109 | .pos = {0.0f, 0.0f}, 110 | .z_index = 0, 111 | .texture = texture_get( 112 | resources, SCHOOL_BG)}); 113 | 114 | make_sprite(&theater->layers[1], 115 | (Sprite){.visible = true, .z_index = 1}); 116 | sprite_set_animation( 117 | resources, game_state->time, &theater->layers[1], 118 | get_lain_walk_animation(lain_outfit)); 119 | 120 | theater->layer_count = 2; 121 | } 122 | break; 123 | } 124 | case LAIN_ROOM_NIGHT_PREVIEW: { 125 | theater->type = THEATER_LAIN_ROOM_NIGHT; 126 | 127 | if (lain_outfit == OUTFIT_BEAR) { 128 | load_theater_animation( 129 | game_state, resources, theater, 130 | THEATER_LAIN_ROOM_NIGHT_ANIMATION); 131 | } else { 132 | make_sprite( 133 | &theater->layers[0], 134 | (Sprite){.visible = true, 135 | .pos = {0.0f, 0.0f}, 136 | .z_index = 0, 137 | .texture = texture_get( 138 | resources, LAIN_ROOM_NIGHT_BG)}); 139 | 140 | make_sprite(&theater->layers[1], 141 | (Sprite){.visible = true, .z_index = 1}); 142 | sprite_set_animation( 143 | resources, game_state->time, &theater->layers[1], 144 | get_lain_walk_animation(lain_outfit)); 145 | 146 | theater->layer_count = 2; 147 | } 148 | break; 149 | } 150 | case ARISU_ROOM_PREVIEW: { 151 | theater->type = THEATER_ARISU_ROOM; 152 | 153 | if (lain_outfit == OUTFIT_ALIEN) { 154 | load_theater_animation(game_state, resources, theater, 155 | THEATER_ARISU_ROOM_ANIMATION); 156 | } else { 157 | make_sprite(&theater->layers[0], 158 | (Sprite){.visible = true, 159 | .pos = {0.0f, 0.0f}, 160 | .z_index = 0, 161 | .texture = texture_get( 162 | resources, ARISU_ROOM_BG)}); 163 | 164 | make_sprite(&theater->layers[1], 165 | (Sprite){.visible = true, .z_index = 1}); 166 | sprite_set_animation( 167 | resources, game_state->time, &theater->layers[1], 168 | get_lain_walk_animation(lain_outfit)); 169 | 170 | theater->layer_count = 2; 171 | } 172 | 173 | break; 174 | } 175 | case CYBERIA_PREVIEW: { 176 | theater->type = THEATER_CYBERIA; 177 | 178 | if (lain_outfit == OUTFIT_CYBERIA) { 179 | load_theater_animation(game_state, resources, theater, 180 | THEATER_CYBERIA_ANIMATION); 181 | } else { 182 | make_sprite(&theater->layers[0], 183 | (Sprite){.visible = true, 184 | .pos = {0.0f, 0.0f}, 185 | .z_index = 0, 186 | .texture = texture_get( 187 | resources, CYBERIA_BG)}); 188 | 189 | make_sprite(&theater->layers[1], 190 | (Sprite){.visible = true, .z_index = 1}); 191 | sprite_set_animation( 192 | resources, game_state->time, &theater->layers[1], 193 | get_lain_walk_animation(lain_outfit)); 194 | 195 | theater->layer_count = 2; 196 | } 197 | break; 198 | } 199 | case STREET_PREVIEW: { 200 | theater->type = THEATER_STREET; 201 | 202 | if (lain_outfit == OUTFIT_SWEATER) { 203 | load_theater_animation(game_state, resources, theater, 204 | THEATER_STREET_ANIMATION); 205 | } else { 206 | make_sprite(&theater->layers[0], 207 | (Sprite){.visible = true, 208 | .pos = {0.0f, 0.0f}, 209 | .z_index = 0, 210 | .texture = texture_get( 211 | resources, STREET_BG_1)}); 212 | 213 | make_sprite(&theater->layers[1], 214 | (Sprite){.visible = true, .z_index = 1}); 215 | sprite_set_animation( 216 | resources, game_state->time, &theater->layers[1], 217 | get_lain_walk_animation(lain_outfit)); 218 | 219 | theater->layer_count = 2; 220 | } 221 | break; 222 | } 223 | case BRIDGE_PREVIEW: { 224 | theater->type = THEATER_BRIDGE; 225 | 226 | load_theater_animation(game_state, resources, theater, 227 | THEATER_BRIDGE_ANIMATION); 228 | 229 | break; 230 | } 231 | } 232 | 233 | init_scene(&theater->scene, sprites, theater->layer_count, NULL, 0, 234 | NULL, 0, NULL, 0); 235 | } 236 | 237 | void update_theater(Resources *resources, Menu *menu, GameState *game_state, 238 | GLFWwindow *window, Minigame *minigame) 239 | { 240 | 241 | Theater *theater = &minigame->current.theater; 242 | 243 | _Bool was_animated = false; 244 | if (theater->type == THEATER_MOVIE) { 245 | was_animated = movie_render(resources->shaders[MOVIE_SHADER], 246 | &theater->movie); 247 | } else { 248 | // hacky way of detecting whether or not the theater is over 249 | // if it wasnt animated, that means no animation in the scene 250 | // has a next frame, which means its over. 251 | for (int i = 0; i < cvector_size(theater->scene.sprites); ++i) { 252 | Sprite *curr = theater->scene.sprites[i]; 253 | if (curr->animation != NULL) { 254 | sprite_try_next_frame(resources, 255 | game_state->time, curr); 256 | was_animated = true; 257 | } 258 | } 259 | } 260 | 261 | if (!was_animated && theater->type == THEATER_BRIDGE) { 262 | free_scene(&theater->scene); 263 | 264 | movie_init(&theater->movie); 265 | theater->type = THEATER_MOVIE; 266 | 267 | reset_game_state(resources, game_state); 268 | menu->theater_preview.texture = 269 | texture_get(resources, game_state->current_theater_preview); 270 | 271 | was_animated = true; 272 | } 273 | 274 | if (!was_animated || glfwWindowShouldClose(window)) { 275 | play_sound(&resources->audio_engine, SND_111); 276 | destroy_minigame(resources->textures, menu, minigame, window); 277 | return; 278 | } 279 | 280 | if (theater->type != THEATER_MOVIE) { 281 | update_scene(&theater->scene); 282 | } 283 | } 284 | 285 | int start_theater(Menu *menu, Resources *resources, GameState *game_state, 286 | Minigame *minigame, GLFWwindow **minigame_window, 287 | GLFWwindow *main_window) 288 | { 289 | 290 | if (!(make_window(minigame_window, MINIGAME_WIDTH, MINIGAME_HEIGHT, 291 | "lain theatre", main_window, true))) { 292 | printf("Failed to create theater window.\n"); 293 | return 0; 294 | } 295 | 296 | init_theater(resources, game_state, &minigame->current.theater, 297 | menu->theater_preview.texture->id); 298 | 299 | minigame->type = THEATER; 300 | minigame->last_updated = game_state->time; 301 | 302 | return 1; 303 | } 304 | -------------------------------------------------------------------------------- /src/texture.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "vector2d.h" 4 | 5 | typedef enum { 6 | LAIN_ROOM, 7 | 8 | LAIN_DEFAULT_STANDING, 9 | 10 | LAIN_DEFAULT_LEAVE_1, 11 | LAIN_DEFAULT_LEAVE_2, 12 | LAIN_DEFAULT_LEAVE_3, 13 | LAIN_DEFAULT_LEAVE_4, 14 | LAIN_DEFAULT_LEAVE_5, 15 | LAIN_DEFAULT_LEAVE_6, 16 | LAIN_DEFAULT_LEAVE_7, 17 | LAIN_DEFAULT_LEAVE_8, 18 | LAIN_DEFAULT_LEAVE_9, 19 | LAIN_DEFAULT_LEAVE_10, 20 | LAIN_DEFAULT_LEAVE_11, 21 | 22 | LAIN_DEFAULT_WALK_LEFT_1, 23 | LAIN_DEFAULT_WALK_LEFT_2, 24 | LAIN_DEFAULT_WALK_LEFT_3, 25 | LAIN_DEFAULT_WALK_LEFT_4, 26 | LAIN_DEFAULT_WALK_LEFT_5, 27 | LAIN_DEFAULT_WALK_LEFT_6, 28 | LAIN_DEFAULT_WALK_LEFT_7, 29 | LAIN_DEFAULT_WALK_LEFT_8, 30 | 31 | LAIN_SCHOOL_LEAVE_1, 32 | LAIN_SCHOOL_LEAVE_2, 33 | LAIN_SCHOOL_LEAVE_3, 34 | LAIN_SCHOOL_LEAVE_4, 35 | LAIN_SCHOOL_LEAVE_5, 36 | LAIN_SCHOOL_LEAVE_6, 37 | LAIN_SCHOOL_LEAVE_7, 38 | LAIN_SCHOOL_LEAVE_8, 39 | LAIN_SCHOOL_LEAVE_9, 40 | LAIN_SCHOOL_LEAVE_10, 41 | LAIN_SCHOOL_LEAVE_11, 42 | 43 | LAIN_SCHOOL_WALK_LEFT_1, 44 | LAIN_SCHOOL_WALK_LEFT_2, 45 | LAIN_SCHOOL_WALK_LEFT_3, 46 | LAIN_SCHOOL_WALK_LEFT_4, 47 | LAIN_SCHOOL_WALK_LEFT_5, 48 | LAIN_SCHOOL_WALK_LEFT_6, 49 | LAIN_SCHOOL_WALK_LEFT_7, 50 | LAIN_SCHOOL_WALK_LEFT_8, 51 | 52 | LAIN_CYBERIA_LEAVE_1, 53 | LAIN_CYBERIA_LEAVE_2, 54 | LAIN_CYBERIA_LEAVE_3, 55 | LAIN_CYBERIA_LEAVE_4, 56 | LAIN_CYBERIA_LEAVE_5, 57 | LAIN_CYBERIA_LEAVE_6, 58 | LAIN_CYBERIA_LEAVE_7, 59 | LAIN_CYBERIA_LEAVE_8, 60 | LAIN_CYBERIA_LEAVE_9, 61 | LAIN_CYBERIA_LEAVE_10, 62 | LAIN_CYBERIA_LEAVE_11, 63 | 64 | LAIN_CYBERIA_WALK_LEFT_1, 65 | LAIN_CYBERIA_WALK_LEFT_2, 66 | LAIN_CYBERIA_WALK_LEFT_3, 67 | LAIN_CYBERIA_WALK_LEFT_4, 68 | LAIN_CYBERIA_WALK_LEFT_5, 69 | LAIN_CYBERIA_WALK_LEFT_6, 70 | LAIN_CYBERIA_WALK_LEFT_7, 71 | LAIN_CYBERIA_WALK_LEFT_8, 72 | 73 | LAIN_BEAR_LEAVE_1, 74 | LAIN_BEAR_LEAVE_2, 75 | LAIN_BEAR_LEAVE_3, 76 | LAIN_BEAR_LEAVE_4, 77 | LAIN_BEAR_LEAVE_5, 78 | LAIN_BEAR_LEAVE_6, 79 | LAIN_BEAR_LEAVE_7, 80 | LAIN_BEAR_LEAVE_8, 81 | LAIN_BEAR_LEAVE_9, 82 | LAIN_BEAR_LEAVE_10, 83 | LAIN_BEAR_LEAVE_11, 84 | 85 | LAIN_BEAR_WALK_LEFT_1, 86 | LAIN_BEAR_WALK_LEFT_2, 87 | LAIN_BEAR_WALK_LEFT_3, 88 | LAIN_BEAR_WALK_LEFT_4, 89 | LAIN_BEAR_WALK_LEFT_5, 90 | LAIN_BEAR_WALK_LEFT_6, 91 | LAIN_BEAR_WALK_LEFT_7, 92 | LAIN_BEAR_WALK_LEFT_8, 93 | 94 | LAIN_SWEATER_LEAVE_1, 95 | LAIN_SWEATER_LEAVE_2, 96 | LAIN_SWEATER_LEAVE_3, 97 | LAIN_SWEATER_LEAVE_4, 98 | LAIN_SWEATER_LEAVE_5, 99 | LAIN_SWEATER_LEAVE_6, 100 | LAIN_SWEATER_LEAVE_7, 101 | LAIN_SWEATER_LEAVE_8, 102 | LAIN_SWEATER_LEAVE_9, 103 | LAIN_SWEATER_LEAVE_10, 104 | LAIN_SWEATER_LEAVE_11, 105 | 106 | LAIN_SWEATER_WALK_LEFT_1, 107 | LAIN_SWEATER_WALK_LEFT_2, 108 | LAIN_SWEATER_WALK_LEFT_3, 109 | LAIN_SWEATER_WALK_LEFT_4, 110 | LAIN_SWEATER_WALK_LEFT_5, 111 | LAIN_SWEATER_WALK_LEFT_6, 112 | LAIN_SWEATER_WALK_LEFT_7, 113 | LAIN_SWEATER_WALK_LEFT_8, 114 | 115 | LAIN_ALIEN_STANDING, 116 | 117 | LAIN_ALIEN_LEAVE_1, 118 | LAIN_ALIEN_LEAVE_2, 119 | LAIN_ALIEN_LEAVE_3, 120 | LAIN_ALIEN_LEAVE_4, 121 | LAIN_ALIEN_LEAVE_5, 122 | LAIN_ALIEN_LEAVE_6, 123 | LAIN_ALIEN_LEAVE_7, 124 | LAIN_ALIEN_LEAVE_8, 125 | LAIN_ALIEN_LEAVE_9, 126 | 127 | LAIN_ALIEN_WALK_LEFT_1, 128 | LAIN_ALIEN_WALK_LEFT_2, 129 | LAIN_ALIEN_WALK_LEFT_3, 130 | LAIN_ALIEN_WALK_LEFT_4, 131 | LAIN_ALIEN_WALK_LEFT_5, 132 | LAIN_ALIEN_WALK_LEFT_6, 133 | LAIN_ALIEN_WALK_LEFT_7, 134 | LAIN_ALIEN_WALK_LEFT_8, 135 | 136 | LAIN_SCHOOL_STANDING, 137 | 138 | LAIN_CYBERIA_STANDING, 139 | 140 | LAIN_BEAR_STANDING, 141 | 142 | LAIN_SWEATER_STANDING, 143 | 144 | SCHOOL_OUTFIT, 145 | 146 | CYBERIA_OUTFIT, 147 | 148 | BEAR_OUTFIT, 149 | 150 | SWEATER_OUTFIT, 151 | 152 | CLASSROOM_BG, 153 | 154 | LAIN_CLASSROOM_1, 155 | LAIN_CLASSROOM_2, 156 | LAIN_CLASSROOM_3, 157 | LAIN_CLASSROOM_4, 158 | LAIN_CLASSROOM_5, 159 | LAIN_CLASSROOM_6, 160 | LAIN_CLASSROOM_7, 161 | LAIN_CLASSROOM_8, 162 | LAIN_CLASSROOM_9, 163 | LAIN_CLASSROOM_10, 164 | LAIN_CLASSROOM_11, 165 | LAIN_CLASSROOM_12, 166 | LAIN_CLASSROOM_13, 167 | LAIN_CLASSROOM_14, 168 | LAIN_CLASSROOM_15, 169 | LAIN_CLASSROOM_16, 170 | LAIN_CLASSROOM_17, 171 | LAIN_CLASSROOM_18, 172 | LAIN_CLASSROOM_19, 173 | LAIN_CLASSROOM_20, 174 | LAIN_CLASSROOM_21, 175 | LAIN_CLASSROOM_22, 176 | LAIN_CLASSROOM_23, 177 | LAIN_CLASSROOM_24, 178 | LAIN_CLASSROOM_25, 179 | LAIN_CLASSROOM_26, 180 | LAIN_CLASSROOM_27, 181 | LAIN_CLASSROOM_28, 182 | LAIN_CLASSROOM_29, 183 | LAIN_CLASSROOM_30, 184 | LAIN_CLASSROOM_31, 185 | 186 | CLASSROOM_TABLES, 187 | 188 | SCHOOL_BG, 189 | 190 | SCHOOL_EXPLOSION_1, 191 | SCHOOL_EXPLOSION_2, 192 | SCHOOL_EXPLOSION_3, 193 | SCHOOL_EXPLOSION_4, 194 | SCHOOL_EXPLOSION_5, 195 | SCHOOL_EXPLOSION_6, 196 | SCHOOL_EXPLOSION_7, 197 | SCHOOL_EXPLOSION_8, 198 | SCHOOL_EXPLOSION_9, 199 | SCHOOL_EXPLOSION_10, 200 | SCHOOL_EXPLOSION_11, 201 | SCHOOL_EXPLOSION_12, 202 | SCHOOL_EXPLOSION_13, 203 | SCHOOL_EXPLOSION_14, 204 | SCHOOL_EXPLOSION_15, 205 | SCHOOL_EXPLOSION_16, 206 | 207 | SCHOOL_LAIN_1, 208 | SCHOOL_LAIN_2, 209 | SCHOOL_LAIN_3, 210 | SCHOOL_LAIN_4, 211 | SCHOOL_LAIN_5, 212 | SCHOOL_LAIN_6, 213 | SCHOOL_LAIN_7, 214 | SCHOOL_LAIN_8, 215 | SCHOOL_LAIN_9, 216 | SCHOOL_LAIN_10, 217 | SCHOOL_LAIN_11, 218 | SCHOOL_LAIN_12, 219 | SCHOOL_LAIN_13, 220 | SCHOOL_LAIN_14, 221 | SCHOOL_LAIN_15, 222 | SCHOOL_LAIN_16, 223 | SCHOOL_LAIN_17, 224 | SCHOOL_LAIN_18, 225 | SCHOOL_LAIN_19, 226 | SCHOOL_LAIN_20, 227 | 228 | LAIN_ROOM_NIGHT_WINDOW, 229 | 230 | LAIN_ROOM_NIGHT_CHISA_1, 231 | LAIN_ROOM_NIGHT_CHISA_2, 232 | 233 | LAIN_ROOM_NIGHT_BG_EMPTY_WINDOW, 234 | 235 | LAIN_ROOM_NIGHT_LAIN_1, 236 | LAIN_ROOM_NIGHT_LAIN_2, 237 | LAIN_ROOM_NIGHT_LAIN_3, 238 | LAIN_ROOM_NIGHT_LAIN_4, 239 | LAIN_ROOM_NIGHT_LAIN_5, 240 | LAIN_ROOM_NIGHT_LAIN_6, 241 | LAIN_ROOM_NIGHT_LAIN_7, 242 | LAIN_ROOM_NIGHT_LAIN_8, 243 | LAIN_ROOM_NIGHT_LAIN_9, 244 | LAIN_ROOM_NIGHT_LAIN_10, 245 | LAIN_ROOM_NIGHT_LAIN_11, 246 | LAIN_ROOM_NIGHT_LAIN_12, 247 | LAIN_ROOM_NIGHT_LAIN_13, 248 | LAIN_ROOM_NIGHT_LAIN_14, 249 | LAIN_ROOM_NIGHT_LAIN_15, 250 | LAIN_ROOM_NIGHT_LAIN_16, 251 | LAIN_ROOM_NIGHT_LAIN_17, 252 | LAIN_ROOM_NIGHT_LAIN_18, 253 | LAIN_ROOM_NIGHT_LAIN_19, 254 | LAIN_ROOM_NIGHT_LAIN_20, 255 | LAIN_ROOM_NIGHT_LAIN_21, 256 | LAIN_ROOM_NIGHT_LAIN_22, 257 | LAIN_ROOM_NIGHT_LAIN_23, 258 | LAIN_ROOM_NIGHT_LAIN_24, 259 | LAIN_ROOM_NIGHT_LAIN_25, 260 | LAIN_ROOM_NIGHT_LAIN_26, 261 | LAIN_ROOM_NIGHT_LAIN_27, 262 | LAIN_ROOM_NIGHT_LAIN_28, 263 | LAIN_ROOM_NIGHT_LAIN_29, 264 | 265 | LAIN_ROOM_NIGHT_SLIPPERS, 266 | 267 | ARISU_ROOM_BG_1, 268 | ARISU_ROOM_BG_2, 269 | ARISU_ROOM_BG_3, 270 | 271 | ARISU_ROOM_DOOR_1, 272 | ARISU_ROOM_DOOR_2, 273 | ARISU_ROOM_DOOR_3, 274 | ARISU_ROOM_DOOR_4, 275 | 276 | ARISU_ROOM_UFO, 277 | 278 | ARISU_ROOM_LAIN_1, 279 | ARISU_ROOM_LAIN_2, 280 | ARISU_ROOM_LAIN_3, 281 | ARISU_ROOM_LAIN_4, 282 | ARISU_ROOM_LAIN_5, 283 | ARISU_ROOM_LAIN_6, 284 | ARISU_ROOM_LAIN_7, 285 | ARISU_ROOM_LAIN_8, 286 | ARISU_ROOM_LAIN_9, 287 | ARISU_ROOM_LAIN_10, 288 | ARISU_ROOM_LAIN_11, 289 | ARISU_ROOM_LAIN_12, 290 | ARISU_ROOM_LAIN_13, 291 | ARISU_ROOM_LAIN_14, 292 | ARISU_ROOM_LAIN_15, 293 | ARISU_ROOM_LAIN_16, 294 | 295 | CYBERIA_BG, 296 | 297 | CYBERIA_JJ_1, 298 | CYBERIA_JJ_2, 299 | 300 | CYBERIA_LAIN_1, 301 | CYBERIA_LAIN_2, 302 | CYBERIA_LAIN_3, 303 | CYBERIA_LAIN_4, 304 | CYBERIA_LAIN_5, 305 | CYBERIA_LAIN_6, 306 | CYBERIA_LAIN_7, 307 | CYBERIA_LAIN_8, 308 | CYBERIA_LAIN_9, 309 | 310 | STREET_BG_1, 311 | STREET_BG_2, 312 | 313 | STREET_EIRI_1, 314 | STREET_EIRI_2, 315 | STREET_EIRI_3, 316 | STREET_EIRI_4, 317 | STREET_EIRI_5, 318 | STREET_EIRI_6, 319 | STREET_EIRI_7, 320 | STREET_EIRI_8, 321 | STREET_EIRI_9, 322 | STREET_EIRI_10, 323 | STREET_EIRI_11, 324 | STREET_EIRI_12, 325 | STREET_EIRI_13, 326 | STREET_EIRI_14, 327 | 328 | STREET_LAIN_1, 329 | STREET_LAIN_2, 330 | STREET_LAIN_3, 331 | STREET_LAIN_4, 332 | STREET_LAIN_5, 333 | STREET_LAIN_6, 334 | STREET_LAIN_7, 335 | STREET_LAIN_8, 336 | 337 | STREET_LAIN_AND_EIRI_1, 338 | STREET_LAIN_AND_EIRI_2, 339 | STREET_LAIN_AND_EIRI_3, 340 | STREET_LAIN_AND_EIRI_4, 341 | STREET_LAIN_AND_EIRI_5, 342 | STREET_LAIN_AND_EIRI_6, 343 | STREET_LAIN_AND_EIRI_7, 344 | STREET_LAIN_AND_EIRI_8, 345 | STREET_LAIN_AND_EIRI_9, 346 | STREET_LAIN_AND_EIRI_10, 347 | STREET_LAIN_AND_EIRI_11, 348 | STREET_LAIN_AND_EIRI_12, 349 | STREET_LAIN_AND_EIRI_13, 350 | STREET_LAIN_AND_EIRI_14, 351 | STREET_LAIN_AND_EIRI_15, 352 | STREET_LAIN_AND_EIRI_16, 353 | STREET_LAIN_AND_EIRI_17, 354 | STREET_LAIN_AND_EIRI_18, 355 | STREET_LAIN_AND_EIRI_19, 356 | STREET_LAIN_AND_EIRI_20, 357 | STREET_LAIN_AND_EIRI_21, 358 | STREET_LAIN_AND_EIRI_22, 359 | 360 | BRIDGE_BG, 361 | 362 | BRIDGE_LAIN_1, 363 | BRIDGE_LAIN_2, 364 | BRIDGE_LAIN_3, 365 | BRIDGE_LAIN_4, 366 | BRIDGE_LAIN_5, 367 | 368 | BRIDGE_BIRD_1, 369 | BRIDGE_BIRD_2, 370 | BRIDGE_BIRD_3, 371 | BRIDGE_BIRD_4, 372 | 373 | EMPTY_BRIDGE, 374 | 375 | BROWN_BEAR_WALK_RIGHT_1, 376 | BROWN_BEAR_WALK_RIGHT_2, 377 | BROWN_BEAR_WALK_RIGHT_3, 378 | BROWN_BEAR_WALK_RIGHT_4, 379 | BROWN_BEAR_WALK_RIGHT_5, 380 | BROWN_BEAR_WALK_RIGHT_6, 381 | BROWN_BEAR_WALK_RIGHT_7, 382 | BROWN_BEAR_WALK_RIGHT_8, 383 | 384 | WHITE_BEAR_WALK_RIGHT_1, 385 | WHITE_BEAR_WALK_RIGHT_2, 386 | WHITE_BEAR_WALK_RIGHT_3, 387 | WHITE_BEAR_WALK_RIGHT_4, 388 | WHITE_BEAR_WALK_RIGHT_5, 389 | WHITE_BEAR_WALK_RIGHT_6, 390 | WHITE_BEAR_WALK_RIGHT_7, 391 | WHITE_BEAR_WALK_RIGHT_8, 392 | 393 | KUMA_SHOOT_LAIN_SCHOOL_1, 394 | KUMA_SHOOT_LAIN_SCHOOL_2, 395 | KUMA_SHOOT_LAIN_SCHOOL_3, 396 | KUMA_SHOOT_LAIN_SCHOOL_4, 397 | KUMA_SHOOT_LAIN_SCHOOL_5, 398 | KUMA_SHOOT_LAIN_SCHOOL_6, 399 | KUMA_SHOOT_LAIN_SCHOOL_7, 400 | KUMA_SHOOT_LAIN_SCHOOL_8, 401 | KUMA_SHOOT_LAIN_SCHOOL_9, 402 | KUMA_SHOOT_LAIN_SCHOOL_10, 403 | KUMA_SHOOT_LAIN_SCHOOL_11, 404 | KUMA_SHOOT_LAIN_SCHOOL_12, 405 | KUMA_SHOOT_LAIN_SCHOOL_13, 406 | 407 | KUMA_SHOOT_YASUO_1, 408 | KUMA_SHOOT_YASUO_2, 409 | KUMA_SHOOT_YASUO_3, 410 | 411 | KUMA_SHOOT_MIHO_1, 412 | KUMA_SHOOT_MIHO_2, 413 | KUMA_SHOOT_MIHO_3, 414 | KUMA_SHOOT_MIHO_4, 415 | KUMA_SHOOT_MIHO_5, 416 | KUMA_SHOOT_MIHO_6, 417 | KUMA_SHOOT_MIHO_7, 418 | 419 | KUMA_SHOOT_MIKA_1, 420 | KUMA_SHOOT_MIKA_2, 421 | KUMA_SHOOT_MIKA_3, 422 | 423 | KUMA_SHOOT_LAIN_SCHOOL_WALK_LEFT_1, 424 | KUMA_SHOOT_LAIN_SCHOOL_WALK_LEFT_2, 425 | KUMA_SHOOT_LAIN_SCHOOL_WALK_LEFT_3, 426 | KUMA_SHOOT_LAIN_SCHOOL_WALK_LEFT_4, 427 | KUMA_SHOOT_LAIN_SCHOOL_WALK_LEFT_5, 428 | KUMA_SHOOT_LAIN_SCHOOL_WALK_LEFT_6, 429 | KUMA_SHOOT_LAIN_SCHOOL_WALK_LEFT_7, 430 | KUMA_SHOOT_LAIN_SCHOOL_WALK_LEFT_8, 431 | 432 | KUMA_SHOOT_SMOKE_1, 433 | KUMA_SHOOT_SMOKE_2, 434 | KUMA_SHOOT_SMOKE_3, 435 | KUMA_SHOOT_SMOKE_4, 436 | 437 | KUMA_SHOOT_SCREWDRIVER_LAIN_1, 438 | KUMA_SHOOT_SCREWDRIVER_LAIN_2, 439 | KUMA_SHOOT_SCREWDRIVER_LAIN_3, 440 | KUMA_SHOOT_SCREWDRIVER_LAIN_4, 441 | KUMA_SHOOT_SCREWDRIVER_LAIN_5, 442 | KUMA_SHOOT_SCREWDRIVER_LAIN_6, 443 | KUMA_SHOOT_SCREWDRIVER_LAIN_7, 444 | KUMA_SHOOT_SCREWDRIVER_LAIN_8, 445 | KUMA_SHOOT_SCREWDRIVER_LAIN_9, 446 | 447 | KUMA_SHOOT_EXPLOSION_1, 448 | KUMA_SHOOT_EXPLOSION_2, 449 | KUMA_SHOOT_EXPLOSION_3, 450 | KUMA_SHOOT_EXPLOSION_4, 451 | KUMA_SHOOT_EXPLOSION_5, 452 | 453 | KUMA_SHOOT_THING_1, 454 | KUMA_SHOOT_THING_2, 455 | KUMA_SHOOT_THING_3, 456 | KUMA_SHOOT_THING_4, 457 | KUMA_SHOOT_THING_5, 458 | KUMA_SHOOT_THING_6, 459 | KUMA_SHOOT_THING_7, 460 | KUMA_SHOOT_THING_8, 461 | KUMA_SHOOT_THING_9, 462 | KUMA_SHOOT_THING_10, 463 | KUMA_SHOOT_THING_11, 464 | KUMA_SHOOT_THING_12, 465 | KUMA_SHOOT_THING_13, 466 | KUMA_SHOOT_THING_14, 467 | KUMA_SHOOT_THING_15, 468 | 469 | KUMA_SHOOT_DEFAULT_LAIN_1, 470 | KUMA_SHOOT_DEFAULT_LAIN_2, 471 | KUMA_SHOOT_DEFAULT_LAIN_3, 472 | KUMA_SHOOT_DEFAULT_LAIN_4, 473 | KUMA_SHOOT_DEFAULT_LAIN_5, 474 | KUMA_SHOOT_DEFAULT_LAIN_6, 475 | KUMA_SHOOT_DEFAULT_LAIN_7, 476 | KUMA_SHOOT_DEFAULT_LAIN_8, 477 | KUMA_SHOOT_DEFAULT_LAIN_9, 478 | KUMA_SHOOT_DEFAULT_LAIN_10, 479 | KUMA_SHOOT_DEFAULT_LAIN_11, 480 | KUMA_SHOOT_DEFAULT_LAIN_12, 481 | KUMA_SHOOT_DEFAULT_LAIN_13, 482 | KUMA_SHOOT_DEFAULT_LAIN_14, 483 | KUMA_SHOOT_DEFAULT_LAIN_15, 484 | KUMA_SHOOT_DEFAULT_LAIN_16, 485 | KUMA_SHOOT_DEFAULT_LAIN_17, 486 | KUMA_SHOOT_DEFAULT_LAIN_18, 487 | KUMA_SHOOT_DEFAULT_LAIN_19, 488 | KUMA_SHOOT_DEFAULT_LAIN_20, 489 | KUMA_SHOOT_DEFAULT_LAIN_21, 490 | KUMA_SHOOT_DEFAULT_LAIN_22, 491 | KUMA_SHOOT_DEFAULT_LAIN_23, 492 | 493 | KUMA_SHOOT_BROWN_BEAR_WALK_LEFT_1, 494 | KUMA_SHOOT_BROWN_BEAR_WALK_LEFT_2, 495 | KUMA_SHOOT_BROWN_BEAR_WALK_LEFT_3, 496 | KUMA_SHOOT_BROWN_BEAR_WALK_LEFT_4, 497 | KUMA_SHOOT_BROWN_BEAR_WALK_LEFT_5, 498 | KUMA_SHOOT_BROWN_BEAR_WALK_LEFT_6, 499 | KUMA_SHOOT_BROWN_BEAR_WALK_LEFT_7, 500 | KUMA_SHOOT_BROWN_BEAR_WALK_LEFT_8, 501 | 502 | KUMA_SHOOT_WHITE_BEAR_WALK_LEFT_1, 503 | KUMA_SHOOT_WHITE_BEAR_WALK_LEFT_2, 504 | KUMA_SHOOT_WHITE_BEAR_WALK_LEFT_3, 505 | KUMA_SHOOT_WHITE_BEAR_WALK_LEFT_4, 506 | KUMA_SHOOT_WHITE_BEAR_WALK_LEFT_5, 507 | KUMA_SHOOT_WHITE_BEAR_WALK_LEFT_6, 508 | KUMA_SHOOT_WHITE_BEAR_WALK_LEFT_7, 509 | KUMA_SHOOT_WHITE_BEAR_WALK_LEFT_8, 510 | 511 | UI_DEFAULT_LAIN_1, 512 | UI_DEFAULT_LAIN_2, 513 | UI_DEFAULT_LAIN_3, 514 | UI_DEFAULT_LAIN_4, 515 | UI_DEFAULT_LAIN_5, 516 | UI_DEFAULT_LAIN_6, 517 | UI_DEFAULT_LAIN_7, 518 | UI_DEFAULT_LAIN_8, 519 | UI_DEFAULT_LAIN_9, 520 | 521 | UI_BEAR_LAIN_1, 522 | UI_BEAR_LAIN_2, 523 | UI_BEAR_LAIN_3, 524 | UI_BEAR_LAIN_4, 525 | UI_BEAR_LAIN_5, 526 | UI_BEAR_LAIN_6, 527 | UI_BEAR_LAIN_7, 528 | UI_BEAR_LAIN_8, 529 | UI_BEAR_LAIN_9, 530 | 531 | MAIN_UI_1, 532 | MAIN_UI_2, 533 | MAIN_UI_3, 534 | MAIN_UI_4, 535 | MAIN_UI_5, 536 | MAIN_UI_6, 537 | 538 | THEATER_BUTTON, 539 | 540 | DRESSUP_BUTTON, 541 | 542 | THEATER_BUTTON_ACTIVE, 543 | 544 | DRESSUP_BUTTON_ACTIVE, 545 | 546 | MAIN_UI_BAR, 547 | 548 | MAIN_UI_BAR_ACTIVE, 549 | 550 | BEAR_ICON_ACTIVE, 551 | 552 | BEAR_ICON, 553 | 554 | SCREWDRIVER_ICON_ACTIVE, 555 | 556 | SCREWDRIVER_ICON, 557 | 558 | MINI_THEATER_OK, 559 | 560 | SCORE_DISPLAY, 561 | 562 | CLASSROOM_PREVIEW, 563 | 564 | SCHOOL_PREVIEW, 565 | 566 | LAIN_ROOM_NIGHT_PREVIEW, 567 | 568 | ARISU_ROOM_PREVIEW, 569 | 570 | CYBERIA_PREVIEW, 571 | 572 | STREET_PREVIEW, 573 | 574 | BRIDGE_PREVIEW, 575 | 576 | MAIN_UI_7, 577 | 578 | DRESSUP_NAVI, 579 | 580 | DRESSUP_SCREWDRIVER, 581 | 582 | LAIN_ROOM_NIGHT_BG, 583 | 584 | ARISU_ROOM_BG, 585 | 586 | DRESSUP_UFO, 587 | 588 | MENU_BG, 589 | 590 | WHITE_FONT_SPRITESHEET, 591 | 592 | RED_FONT_SPRITESHEET, 593 | 594 | PAW_ICON, 595 | 596 | KUMA_SHOOT_BUSH_OVERLAY, 597 | 598 | KUMA_SHOOT_BG, 599 | } TextureID; 600 | 601 | 602 | #define MAX_TEXTURE_COUNT 1024 603 | 604 | typedef struct { 605 | TextureID id; 606 | GLuint gl_id; 607 | Vector2D size; 608 | } Texture; 609 | 610 | struct resources; 611 | 612 | Texture make_texture(char *image_path, char *name); 613 | Texture *texture_get(struct resources *resources, int texture_id); 614 | void textures_init(struct resources *buffer); 615 | -------------------------------------------------------------------------------- /scripts/make_game_assets.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | import numpy as np 5 | import cv2 6 | from PIL import Image 7 | import os 8 | from os import path, mkdir, remove, listdir 9 | from shutil import move, copyfile 10 | from distutils.dir_util import copy_tree 11 | import json 12 | import collections 13 | import pathlib 14 | import extract_n_decompress 15 | 16 | # theater animation ids 17 | # inbetween these are "generic theaters", where lain simply walks past a scene 18 | # ["202", "216", "209", "223", "237", "244", "230"] 19 | 20 | # offsets so far: 21 | # 155 - all 0 main_ui_expand 22 | # 246 - all 0 main_ui_shrink 23 | 24 | # 143 - all 40 ui_default_lain_blink 25 | # 145 - all 40 ui_default_lain_laugh 26 | # 146 - all 40 ui_default_lain_laugh_blink 27 | 28 | # 149 - ui_bear_lain_blink 29 | # 151 - ui_bear_lain_laugh 30 | # 152 - ui_bear_laugh_blink 31 | 32 | # 128 - brown bear walk animation 33 | # 130 - white bear walk animation 34 | # 132 - kumashoot smoke 35 | # 134 - miho 36 | # 133 - yasuo 37 | # 135 - mika 38 | # 136 - school lain scratch head kumashoot 39 | # 137 - school lain kumashoot 40 | # 139 - screwdriver lain kumashoot 41 | # 140 - default lain kumashoot 42 | 43 | # 189 - dressup default laeve 44 | # 183 - dressup default walk left 45 | 46 | # 190 - dressup schol leave 47 | # 184 - dressup school walk left 48 | 49 | # 191 - dressup cyberia leave 50 | # 185 - dressup cyberia walk left 51 | 52 | # 192 - dressup bear leave 53 | # 186 - dressup bear walk left 54 | 55 | # 193 - dressup sweater leave 56 | # 187 - dressup sweater walk left 57 | 58 | # 194 - dressup alien leave 59 | # 188 - dressup alien walk left 60 | 61 | animations = [ 62 | {"id": "155", "name": "MAIN_UI_EXPAND_ANIMATION", "null_offsets": True}, 63 | {"id": "246", "name": "MAIN_UI_COLLAPSE_ANIMATION", "null_offsets": True}, 64 | { 65 | "id": "143", 66 | "name": "UI_DEFAULT_LAIN_BLINK_ANIMATION", 67 | "null_offsets": True, 68 | "additional": [ 69 | {"timing": 12, "rsrc_id": 442, "x_offset": -1, "y_offset": -1, "index": 4} 70 | ], 71 | }, 72 | {"id": "145", "name": "UI_DEFAULT_LAIN_LAUGH_ANIMATION", "null_offsets": True}, 73 | { 74 | "id": "146", 75 | "name": "UI_DEFAULT_LAIN_LAUGH_BLINK_ANIMATION", 76 | "null_offsets": True, 77 | "additional": [ 78 | {"timing": 12, "rsrc_id": 446, "x_offset": -1, "y_offset": -1, "index": 4} 79 | ], 80 | }, 81 | { 82 | "id": "149", 83 | "name": "UI_BEAR_LAIN_BLINK_ANIMATION", 84 | "null_offsets": True, 85 | "additional": [ 86 | {"timing": 12, "rsrc_id": 451, "x_offset": -1, "y_offset": -1, "index": 4} 87 | ], 88 | }, 89 | {"id": "151", "name": "UI_BEAR_LAIN_LAUGH_ANIMATION", "null_offsets": True}, 90 | { 91 | "id": "152", 92 | "name": "UI_BEAR_LAIN_LAUGH_BLINK_ANIMATION", 93 | "null_offsets": True, 94 | "additional": [ 95 | {"timing": 12, "rsrc_id": 455, "x_offset": -1, "y_offset": -1, "index": 4} 96 | ], 97 | }, 98 | {"id": "128", "name": "KUMA_SHOOT_BROWN_BEAR_WALK_ANIMATION", "null_offsets": True}, 99 | {"id": "130", "name": "KUMA_SHOOT_WHITE_BEAR_WALK_ANIMATION", "null_offsets": True}, 100 | {"id": "132", "name": "KUMA_SHOOT_SMOKE_ANIMATION", "null_offsets": True}, 101 | {"id": "133", "name": "KUMA_SHOOT_YASUO_ANIMATION", "null_offsets": True}, 102 | {"id": "134", "name": "KUMA_SHOOT_MIHO_ANIMATION", "null_offsets": True}, 103 | {"id": "135", "name": "KUMA_SHOOT_MIKA_ANIMATION", "null_offsets": True}, 104 | {"id": "136", "name": "KUMA_SHOOT_SCHOOL_LAIN_1_ANIMATION", "null_offsets": True}, 105 | {"id": "137", "name": "KUMA_SHOOT_SCHOOL_LAIN_2_ANIMATION", "null_offsets": True}, 106 | { 107 | "id": "139", 108 | "layer": "0", 109 | "name": "KUMA_SHOOT_SCREWDRIVER_LAIN_ANIMATION", 110 | "null_offsets": True, 111 | }, 112 | { 113 | "id": "139", 114 | "layer": "1", 115 | "name": "KUMA_SHOOT_SCREW_ANIMATION", 116 | "null_offsets": True, 117 | }, 118 | {"id": "140", "name": "KUMA_SHOOT_DEFAULT_LAIN_ANIMATION", "null_offsets": True}, 119 | {"id": "189", "name": "LAIN_DEFAULT_LEAVE_ANIMATION", "null_offsets": False}, 120 | {"id": "183", "name": "LAIN_DEFAULT_WALK_LEFT_ANIMATION", "null_offsets": False}, 121 | {"id": "190", "name": "LAIN_SCHOOL_LEAVE_ANIMATION", "null_offsets": False}, 122 | {"id": "184", "name": "LAIN_SCHOOL_WALK_LEFT_ANIMATION", "null_offsets": False}, 123 | {"id": "191", "name": "LAIN_CYBERIA_LEAVE_ANIMATION", "null_offsets": False}, 124 | {"id": "185", "name": "LAIN_CYBERIA_WALK_LEFT_ANIMATION", "null_offsets": False}, 125 | {"id": "192", "name": "LAIN_BEAR_LEAVE_ANIMATION", "null_offsets": False}, 126 | {"id": "186", "name": "LAIN_BEAR_WALK_LEFT_ANIMATION", "null_offsets": False}, 127 | {"id": "193", "name": "LAIN_SWEATER_LEAVE_ANIMATION", "null_offsets": False}, 128 | {"id": "187", "name": "LAIN_SWEATER_WALK_LEFT_ANIMATION", "null_offsets": False}, 129 | {"id": "194", "name": "LAIN_ALIEN_LEAVE_ANIMATION", "null_offsets": False}, 130 | {"id": "188", "name": "LAIN_ALIEN_WALK_LEFT_ANIMATION", "null_offsets": False}, 131 | { 132 | "id": "203", 133 | "layer": 1, 134 | "name": "DEFAULT_LAIN_THEATER_WALK_ANIMATION", 135 | "null_offsets": False, 136 | }, 137 | { 138 | "id": "204", 139 | "layer": 1, 140 | "name": "SCHOOL_LAIN_THEATER_WALK_ANIMATION", 141 | "null_offsets": False, 142 | }, 143 | { 144 | "id": "205", 145 | "layer": 1, 146 | "name": "CYBERIA_LAIN_THEATER_WALK_ANIMATION", 147 | "null_offsets": False, 148 | }, 149 | { 150 | "id": "206", 151 | "layer": 1, 152 | "name": "BEAR_LAIN_THEATER_WALK_ANIMATION", 153 | "null_offsets": False, 154 | }, 155 | { 156 | "id": "207", 157 | "layer": 1, 158 | "name": "SWEATER_LAIN_THEATER_WALK_ANIMATION", 159 | "null_offsets": False, 160 | }, 161 | { 162 | "id": "208", 163 | "layer": 1, 164 | "name": "ALIEN_LAIN_THEATER_WALK_ANIMATION", 165 | "null_offsets": False, 166 | }, 167 | ] 168 | 169 | theater_animations = [ 170 | {"id": "202", "name": "THEATER_CLASSROOM_ANIMATION"}, 171 | {"id": "216", "name": "THEATER_LAIN_ROOM_NIGHT_ANIMATION"}, 172 | {"id": "209", "name": "THEATER_SCHOOL_ANIMATION"}, 173 | {"id": "223", "name": "THEATER_ARISU_ROOM_ANIMATION"}, 174 | {"id": "230", "name": "THEATER_CYBERIA_ANIMATION"}, 175 | {"id": "237", "name": "THEATER_STREET_ANIMATION"}, 176 | {"id": "244", "name": "THEATER_BRIDGE_ANIMATION"}, 177 | ] 178 | 179 | 180 | def format_frames(frames, additional_frame, null_offsets): 181 | formatted_frames = [] 182 | padded_first_frame = False 183 | for i, frame in enumerate(frames): 184 | if i == 0 and frame["timing"] > 1: 185 | formatted_frames.append( 186 | {"timing": 0, "rsrc_id": -1, "x_offset": -1, "y_offset": -1, "index": 0} 187 | ) 188 | padded_first_frame = True 189 | 190 | formatted_frame = {} 191 | formatted_frame["timing"] = ( 192 | 0 if frames[i]["timing"] == 1 else frames[i]["timing"] 193 | ) 194 | if frames[i]["res_id"] == 0: 195 | formatted_frame["rsrc_id"] = -1 196 | else: 197 | formatted_frame["rsrc_id"] = frames[i]["res_id"] - 128 198 | if null_offsets: 199 | formatted_frame["x_offset"] = -1 200 | formatted_frame["y_offset"] = -1 201 | else: 202 | formatted_frame["x_offset"] = frames[i]["rel_x"] 203 | formatted_frame["y_offset"] = frames[i]["rel_y"] 204 | formatted_frame["index"] = i + 1 if padded_first_frame else i 205 | 206 | formatted_frames.append(formatted_frame) 207 | 208 | return formatted_frames 209 | 210 | 211 | def extract_layers(frames, target_layer, additional_frame, null_offsets): 212 | layers = {} 213 | for frame in frames: 214 | curr_layer = frame["layer"] - 1 if frame["layer"] - 1 > 0 else 0 215 | 216 | if curr_layer not in layers: 217 | layers[curr_layer] = [] 218 | layers[curr_layer].append(frame) 219 | else: 220 | layers[curr_layer].append(frame) 221 | 222 | for layer in layers: 223 | layers[layer] = format_frames(layers[layer], additional_frame, null_offsets) 224 | 225 | if target_layer != None: 226 | return layers[int(target_layer)] 227 | else: 228 | return layers 229 | 230 | 231 | def get_max_width_height(images): 232 | width_max = 0 233 | height_max = 0 234 | for img in images: 235 | h, w = img.shape[:2] 236 | width_max = max(width_max, w) 237 | height_max = max(height_max, h) 238 | 239 | return (width_max, height_max) 240 | 241 | 242 | def pad_images_to_same_size(images): 243 | width_max, height_max = get_max_width_height(images) 244 | 245 | images_padded = [] 246 | for img in images: 247 | h, w = img.shape[:2] 248 | diff_vert = height_max - h 249 | pad_top = diff_vert // 2 250 | pad_bottom = diff_vert - pad_top 251 | diff_hori = width_max - w 252 | pad_left = diff_hori // 2 253 | pad_right = diff_hori - pad_left 254 | img_padded = cv2.copyMakeBorder( 255 | img, pad_top, pad_bottom, pad_left, pad_right, cv2.BORDER_CONSTANT, value=0 256 | ) 257 | assert img_padded.shape[:2] == (height_max, width_max) 258 | images_padded.append(img_padded) 259 | 260 | return images_padded 261 | 262 | 263 | def make_bushes(curr_dir): 264 | img_width, img_height = 600, 400 265 | n_channels = 4 266 | 267 | res = np.zeros((img_height, img_width, n_channels), dtype=np.uint8) 268 | 269 | bush_poses = [ 270 | [55, 6], 271 | [17, 76], 272 | [216, 33], 273 | [50, 191], 274 | [24, 246], 275 | [455, 58], 276 | [419, 128], 277 | [563, 99], 278 | [400, 270], 279 | ] 280 | 281 | for i, pos in enumerate(bush_poses): 282 | sprite_id = "{}.png".format(i + 517) 283 | bush_img = cv2.imread(path.join(curr_dir, sprite_id), -1) 284 | 285 | x = pos[0] 286 | y = pos[1] 287 | 288 | x1, x2 = x, x + bush_img.shape[1] 289 | y1, y2 = y, y + bush_img.shape[0] 290 | 291 | res[y1:y2, x1:x2] = bush_img 292 | 293 | remove(path.join(curr_dir, sprite_id)) 294 | 295 | move(path.join(curr_dir, "526.png"), path.join(curr_dir, "494.png")) 296 | cv2.imwrite(path.join(curr_dir, "495.png"), res) 297 | 298 | 299 | def make_fonts(curr_dir): 300 | white_font_characters = [] 301 | for i in range(492, 505): 302 | filename = f"{i}.png" 303 | white_font_characters.append( 304 | cv2.imread(path.join(curr_dir, filename), cv2.IMREAD_UNCHANGED) 305 | ) 306 | remove(path.join(curr_dir, filename)) 307 | 308 | white_font = cv2.hconcat(pad_images_to_same_size(white_font_characters)) 309 | 310 | red_font_characters = [] 311 | for i in range(505, 516): 312 | filename = f"{i}.png" 313 | red_font_characters.append( 314 | cv2.imread(path.join(curr_dir, filename), cv2.IMREAD_UNCHANGED) 315 | ) 316 | remove(path.join(curr_dir, filename)) 317 | 318 | red_font = cv2.hconcat(pad_images_to_same_size(red_font_characters)) 319 | 320 | cv2.imwrite(path.join(curr_dir, "492.png"), white_font) 321 | cv2.imwrite(path.join(curr_dir, "493.png"), red_font) 322 | 323 | 324 | def apply_mask(img_src, mask_src): 325 | with Image.open(img_src) as img, Image.open(mask_src) as mask: 326 | mask_pixels = mask.load() 327 | mask_pixels[0, mask.size[1] - 1] = mask_pixels[1, mask.size[1] - 1] 328 | img = img.convert("RGBA") 329 | mask = mask.convert("1") 330 | img.paste(0, mask=mask) 331 | return img 332 | 333 | 334 | DECOMPRESSED_DIR = "./extracted/sprites/decompressed/" 335 | SOUNDS_DIR = "./extracted/sounds/" 336 | DAT_JSON = "./extracted/lain_win.json" 337 | MOV_FILE = "./binaries/lain_mov.dat" 338 | ICON_FILE = "./extracted/window_icon.png" 339 | 340 | SPRITES_DEST_DIR = "./res/sprites" 341 | SOUNDS_DEST_DIR = "./res/sounds" 342 | 343 | 344 | def main(): 345 | if not path.isdir(path.join(os.getcwd(), "extracted")): 346 | extract_n_decompress.main() 347 | 348 | pathlib.Path(SPRITES_DEST_DIR).mkdir(parents=True, exist_ok=True) 349 | pathlib.Path(SOUNDS_DEST_DIR).mkdir(parents=True, exist_ok=True) 350 | 351 | # apply masks 352 | for i in range(527): 353 | mask_src = os.path.join(os.getcwd(), DECOMPRESSED_DIR, str(i + 527)) 354 | img_src = os.path.join(os.getcwd(), DECOMPRESSED_DIR, str(i)) 355 | 356 | if os.path.exists(mask_src): 357 | print("Applying mask {}...".format(i)) 358 | dest_path = os.path.join(os.getcwd(), SPRITES_DEST_DIR, "{}.png".format(i)) 359 | 360 | if not os.path.exists(dest_path): 361 | newimg = apply_mask(img_src, mask_src) 362 | newimg.save(dest_path, "png") 363 | 364 | make_fonts(SPRITES_DEST_DIR) 365 | 366 | make_bushes(SPRITES_DEST_DIR) 367 | 368 | move(path.join(SPRITES_DEST_DIR, "516.png"), path.join(SPRITES_DEST_DIR, "496.png")) 369 | 370 | print("Copying sound files...") 371 | copy_tree(SOUNDS_DIR, SOUNDS_DEST_DIR) 372 | 373 | print("Copying window icon...") 374 | copyfile(ICON_FILE, os.path.join(os.getcwd(), "res", "window_icon.png")) 375 | 376 | print("Extracting animation data...") 377 | with open(DAT_JSON) as f: 378 | content = json.load(f) 379 | final = {} 380 | final["sprite_animations"] = {} 381 | final["theater_scenes"] = {} 382 | 383 | for anim in animations: 384 | frames = content["animations"][anim["id"]]["frames"] 385 | has_additional_frame = "additional" in anim 386 | if "layer" in anim: 387 | final["sprite_animations"][anim["name"]] = extract_layers( 388 | frames, anim["layer"], has_additional_frame, anim["null_offsets"] 389 | ) 390 | else: 391 | final["sprite_animations"][anim["name"]] = format_frames( 392 | frames, has_additional_frame, anim["null_offsets"] 393 | ) 394 | if has_additional_frame: 395 | for frame in anim["additional"]: 396 | final["sprite_animations"][anim["name"]].append(frame) 397 | 398 | for anim in theater_animations: 399 | frames = content["animations"][anim["id"]]["frames"] 400 | layers = extract_layers(frames, None, False, False) 401 | ordered_layers = collections.OrderedDict(sorted(layers.items())) 402 | final["theater_scenes"][anim["name"]] = ordered_layers 403 | 404 | with open(os.path.join(os.getcwd(), "res", "animations.json"), "w") as f: 405 | f.write(json.dumps(final, indent=2)) 406 | 407 | copyfile(MOV_FILE, os.path.join(os.getcwd(), "res", "lain_mov.dat")) 408 | 409 | move(os.path.join(os.getcwd(), "res"), os.path.join(os.getcwd(), "../res")) 410 | 411 | 412 | if __name__ == "__main__": 413 | main() 414 | -------------------------------------------------------------------------------- /src/cJSON.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2009-2017 Dave Gamble and cJSON contributors 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | 23 | #ifndef cJSON__h 24 | #define cJSON__h 25 | 26 | #ifdef __cplusplus 27 | extern "C" 28 | { 29 | #endif 30 | 31 | #if !defined(__WINDOWS__) && (defined(WIN32) || defined(WIN64) || defined(_MSC_VER) || defined(_WIN32)) 32 | #define __WINDOWS__ 33 | #endif 34 | 35 | #ifdef __WINDOWS__ 36 | 37 | /* When compiling for windows, we specify a specific calling convention to avoid issues where we are being called from a project with a different default calling convention. For windows you have 3 define options: 38 | 39 | CJSON_HIDE_SYMBOLS - Define this in the case where you don't want to ever dllexport symbols 40 | CJSON_EXPORT_SYMBOLS - Define this on library build when you want to dllexport symbols (default) 41 | CJSON_IMPORT_SYMBOLS - Define this if you want to dllimport symbol 42 | 43 | For *nix builds that support visibility attribute, you can define similar behavior by 44 | 45 | setting default visibility to hidden by adding 46 | -fvisibility=hidden (for gcc) 47 | or 48 | -xldscope=hidden (for sun cc) 49 | to CFLAGS 50 | 51 | then using the CJSON_API_VISIBILITY flag to "export" the same symbols the way CJSON_EXPORT_SYMBOLS does 52 | 53 | */ 54 | 55 | #define CJSON_CDECL __cdecl 56 | #define CJSON_STDCALL __stdcall 57 | 58 | /* export symbols by default, this is necessary for copy pasting the C and header file */ 59 | #if !defined(CJSON_HIDE_SYMBOLS) && !defined(CJSON_IMPORT_SYMBOLS) && !defined(CJSON_EXPORT_SYMBOLS) 60 | #define CJSON_EXPORT_SYMBOLS 61 | #endif 62 | 63 | #if defined(CJSON_HIDE_SYMBOLS) 64 | #define CJSON_PUBLIC(type) type CJSON_STDCALL 65 | #elif defined(CJSON_EXPORT_SYMBOLS) 66 | #define CJSON_PUBLIC(type) __declspec(dllexport) type CJSON_STDCALL 67 | #elif defined(CJSON_IMPORT_SYMBOLS) 68 | #define CJSON_PUBLIC(type) __declspec(dllimport) type CJSON_STDCALL 69 | #endif 70 | #else /* !__WINDOWS__ */ 71 | #define CJSON_CDECL 72 | #define CJSON_STDCALL 73 | 74 | #if (defined(__GNUC__) || defined(__SUNPRO_CC) || defined (__SUNPRO_C)) && defined(CJSON_API_VISIBILITY) 75 | #define CJSON_PUBLIC(type) __attribute__((visibility("default"))) type 76 | #else 77 | #define CJSON_PUBLIC(type) type 78 | #endif 79 | #endif 80 | 81 | /* project version */ 82 | #define CJSON_VERSION_MAJOR 1 83 | #define CJSON_VERSION_MINOR 7 84 | #define CJSON_VERSION_PATCH 15 85 | 86 | #include 87 | 88 | /* cJSON Types: */ 89 | #define cJSON_Invalid (0) 90 | #define cJSON_False (1 << 0) 91 | #define cJSON_True (1 << 1) 92 | #define cJSON_NULL (1 << 2) 93 | #define cJSON_Number (1 << 3) 94 | #define cJSON_String (1 << 4) 95 | #define cJSON_Array (1 << 5) 96 | #define cJSON_Object (1 << 6) 97 | #define cJSON_Raw (1 << 7) /* raw json */ 98 | 99 | #define cJSON_IsReference 256 100 | #define cJSON_StringIsConst 512 101 | 102 | /* The cJSON structure: */ 103 | typedef struct cJSON 104 | { 105 | /* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */ 106 | struct cJSON *next; 107 | struct cJSON *prev; 108 | /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */ 109 | struct cJSON *child; 110 | 111 | /* The type of the item, as above. */ 112 | int type; 113 | 114 | /* The item's string, if type==cJSON_String and type == cJSON_Raw */ 115 | char *valuestring; 116 | /* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */ 117 | int valueint; 118 | /* The item's number, if type==cJSON_Number */ 119 | double valuedouble; 120 | 121 | /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */ 122 | char *string; 123 | } cJSON; 124 | 125 | typedef struct cJSON_Hooks 126 | { 127 | /* malloc/free are CDECL on Windows regardless of the default calling convention of the compiler, so ensure the hooks allow passing those functions directly. */ 128 | void *(CJSON_CDECL *malloc_fn)(size_t sz); 129 | void (CJSON_CDECL *free_fn)(void *ptr); 130 | } cJSON_Hooks; 131 | 132 | typedef int cJSON_bool; 133 | 134 | /* Limits how deeply nested arrays/objects can be before cJSON rejects to parse them. 135 | * This is to prevent stack overflows. */ 136 | #ifndef CJSON_NESTING_LIMIT 137 | #define CJSON_NESTING_LIMIT 1000 138 | #endif 139 | 140 | /* returns the version of cJSON as a string */ 141 | CJSON_PUBLIC(const char*) cJSON_Version(void); 142 | 143 | /* Supply malloc, realloc and free functions to cJSON */ 144 | CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks); 145 | 146 | /* Memory Management: the caller is always responsible to free the results from all variants of cJSON_Parse (with cJSON_Delete) and cJSON_Print (with stdlib free, cJSON_Hooks.free_fn, or cJSON_free as appropriate). The exception is cJSON_PrintPreallocated, where the caller has full responsibility of the buffer. */ 147 | /* Supply a block of JSON, and this returns a cJSON object you can interrogate. */ 148 | CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value); 149 | CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length); 150 | /* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */ 151 | /* If you supply a ptr in return_parse_end and parsing fails, then return_parse_end will contain a pointer to the error so will match cJSON_GetErrorPtr(). */ 152 | CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated); 153 | CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated); 154 | 155 | /* Render a cJSON entity to text for transfer/storage. */ 156 | CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item); 157 | /* Render a cJSON entity to text for transfer/storage without any formatting. */ 158 | CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item); 159 | /* Render a cJSON entity to text using a buffered strategy. prebuffer is a guess at the final size. guessing well reduces reallocation. fmt=0 gives unformatted, =1 gives formatted */ 160 | CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt); 161 | /* Render a cJSON entity to text using a buffer already allocated in memory with given length. Returns 1 on success and 0 on failure. */ 162 | /* NOTE: cJSON is not always 100% accurate in estimating how much memory it will use, so to be safe allocate 5 bytes more than you actually need */ 163 | CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format); 164 | /* Delete a cJSON entity and all subentities. */ 165 | CJSON_PUBLIC(void) cJSON_Delete(cJSON *item); 166 | 167 | /* Returns the number of items in an array (or object). */ 168 | CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array); 169 | /* Retrieve item number "index" from array "array". Returns NULL if unsuccessful. */ 170 | CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index); 171 | /* Get item "string" from object. Case insensitive. */ 172 | CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string); 173 | CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string); 174 | CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string); 175 | /* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */ 176 | CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void); 177 | 178 | /* Check item type and return its value */ 179 | CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item); 180 | CJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item); 181 | 182 | /* These functions check the type of an item */ 183 | CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item); 184 | CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item); 185 | CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item); 186 | CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item); 187 | CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item); 188 | CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item); 189 | CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item); 190 | CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item); 191 | CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item); 192 | CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item); 193 | 194 | /* These calls create a cJSON item of the appropriate type. */ 195 | CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void); 196 | CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void); 197 | CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void); 198 | CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean); 199 | CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num); 200 | CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string); 201 | /* raw json */ 202 | CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw); 203 | CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void); 204 | CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void); 205 | 206 | /* Create a string where valuestring references a string so 207 | * it will not be freed by cJSON_Delete */ 208 | CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string); 209 | /* Create an object/array that only references it's elements so 210 | * they will not be freed by cJSON_Delete */ 211 | CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child); 212 | CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child); 213 | 214 | /* These utilities create an Array of count items. 215 | * The parameter count cannot be greater than the number of elements in the number array, otherwise array access will be out of bounds.*/ 216 | CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count); 217 | CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count); 218 | CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count); 219 | CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count); 220 | 221 | /* Append item to the specified array/object. */ 222 | CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item); 223 | CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item); 224 | /* Use this when string is definitely const (i.e. a literal, or as good as), and will definitely survive the cJSON object. 225 | * WARNING: When this function was used, make sure to always check that (item->type & cJSON_StringIsConst) is zero before 226 | * writing to `item->string` */ 227 | CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item); 228 | /* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. */ 229 | CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item); 230 | CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item); 231 | 232 | /* Remove/Detach items from Arrays/Objects. */ 233 | CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item); 234 | CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which); 235 | CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which); 236 | CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string); 237 | CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string); 238 | CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string); 239 | CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string); 240 | 241 | /* Update array items. */ 242 | CJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem); /* Shifts pre-existing items to the right. */ 243 | CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement); 244 | CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem); 245 | CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem); 246 | CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object,const char *string,cJSON *newitem); 247 | 248 | /* Duplicate a cJSON item */ 249 | CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse); 250 | /* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will 251 | * need to be released. With recurse!=0, it will duplicate any children connected to the item. 252 | * The item->next and ->prev pointers are always zero on return from Duplicate. */ 253 | /* Recursively compare two cJSON items for equality. If either a or b is NULL or invalid, they will be considered unequal. 254 | * case_sensitive determines if object keys are treated case sensitive (1) or case insensitive (0) */ 255 | CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive); 256 | 257 | /* Minify a strings, remove blank characters(such as ' ', '\t', '\r', '\n') from strings. 258 | * The input pointer json cannot point to a read-only address area, such as a string constant, 259 | * but should point to a readable and writable address area. */ 260 | CJSON_PUBLIC(void) cJSON_Minify(char *json); 261 | 262 | /* Helper functions for creating and adding items to an object at the same time. 263 | * They return the added item or NULL on failure. */ 264 | CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name); 265 | CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name); 266 | CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name); 267 | CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean); 268 | CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number); 269 | CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string); 270 | CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw); 271 | CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name); 272 | CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name); 273 | 274 | /* When assigning an integer value, it needs to be propagated to valuedouble too. */ 275 | #define cJSON_SetIntValue(object, number) ((object) ? (object)->valueint = (object)->valuedouble = (number) : (number)) 276 | /* helper for the cJSON_SetNumberValue macro */ 277 | CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number); 278 | #define cJSON_SetNumberValue(object, number) ((object != NULL) ? cJSON_SetNumberHelper(object, (double)number) : (number)) 279 | /* Change the valuestring of a cJSON_String object, only takes effect when type of object is cJSON_String */ 280 | CJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valuestring); 281 | 282 | /* Macro for iterating over an array or object */ 283 | #define cJSON_ArrayForEach(element, array) for(element = (array != NULL) ? (array)->child : NULL; element != NULL; element = element->next) 284 | 285 | /* malloc/free objects using the malloc/free functions that have been set with cJSON_InitHooks */ 286 | CJSON_PUBLIC(void *) cJSON_malloc(size_t size); 287 | CJSON_PUBLIC(void) cJSON_free(void *object); 288 | 289 | #ifdef __cplusplus 290 | } 291 | #endif 292 | 293 | #endif 294 | -------------------------------------------------------------------------------- /src/dressup.c: -------------------------------------------------------------------------------- 1 | #include "animations.h" 2 | #include "cvector.h" 3 | #include "kumashoot.h" 4 | #include "minigame.h" 5 | #include "resources.h" 6 | #include "sprite.h" 7 | #include "state.h" 8 | #include "texture.h" 9 | #include "vector2d.h" 10 | #include "window.h" 11 | 12 | #include "dressup.h" 13 | 14 | #include 15 | #include 16 | 17 | static DressUpObject *get_object_for_outfit(DressUp *dressup, LainOutfit outfit) 18 | { 19 | switch (outfit) { 20 | case OUTFIT_DEFAULT: { 21 | return NULL; 22 | } 23 | 24 | case OUTFIT_SWEATER: { 25 | return &dressup->sweater_outfit; 26 | } 27 | 28 | case OUTFIT_CYBERIA: { 29 | return &dressup->cyberia_outfit; 30 | } 31 | 32 | case OUTFIT_ALIEN: { 33 | return &dressup->ufo; 34 | } 35 | 36 | case OUTFIT_BEAR: { 37 | return &dressup->bear_outfit; 38 | } 39 | 40 | case OUTFIT_SCHOOL: { 41 | return &dressup->school_outfit; 42 | } 43 | } 44 | } 45 | 46 | void lain_set_outfit(Resources *resources, LainOutfit outfit, Lain *lain) 47 | { 48 | lain->outfit = outfit; 49 | switch (outfit) { 50 | case OUTFIT_DEFAULT: { 51 | lain->standing_texture = 52 | texture_get(resources, LAIN_DEFAULT_STANDING); 53 | lain->leave_animation = LAIN_DEFAULT_LEAVE_ANIMATION; 54 | lain->walk_animation = LAIN_DEFAULT_WALK_LEFT_ANIMATION; 55 | 56 | break; 57 | } 58 | 59 | case OUTFIT_SWEATER: { 60 | lain->standing_texture = 61 | texture_get(resources, LAIN_SWEATER_STANDING); 62 | lain->leave_animation = LAIN_SWEATER_LEAVE_ANIMATION; 63 | lain->walk_animation = LAIN_SWEATER_WALK_LEFT_ANIMATION; 64 | 65 | break; 66 | } 67 | 68 | case OUTFIT_CYBERIA: { 69 | lain->standing_texture = 70 | texture_get(resources, LAIN_CYBERIA_STANDING); 71 | lain->leave_animation = LAIN_CYBERIA_LEAVE_ANIMATION; 72 | lain->walk_animation = LAIN_CYBERIA_WALK_LEFT_ANIMATION; 73 | 74 | break; 75 | } 76 | 77 | case OUTFIT_ALIEN: { 78 | lain->standing_texture = 79 | texture_get(resources, LAIN_ALIEN_STANDING); 80 | lain->leave_animation = LAIN_ALIEN_LEAVE_ANIMATION; 81 | lain->walk_animation = LAIN_ALIEN_WALK_LEFT_ANIMATION; 82 | 83 | break; 84 | } 85 | 86 | case OUTFIT_BEAR: { 87 | lain->standing_texture = 88 | texture_get(resources, LAIN_BEAR_STANDING); 89 | lain->leave_animation = LAIN_BEAR_LEAVE_ANIMATION; 90 | lain->walk_animation = LAIN_BEAR_WALK_LEFT_ANIMATION; 91 | 92 | break; 93 | } 94 | 95 | case OUTFIT_SCHOOL: { 96 | lain->standing_texture = 97 | texture_get(resources, LAIN_SCHOOL_STANDING); 98 | lain->leave_animation = LAIN_SCHOOL_LEAVE_ANIMATION; 99 | lain->walk_animation = LAIN_SCHOOL_WALK_LEFT_ANIMATION; 100 | 101 | break; 102 | } 103 | } 104 | } 105 | 106 | static void init_dressup_sprites(Resources *resources, GameState *game_state, 107 | DressUp *dressup) 108 | { 109 | make_sprite(&dressup->bear_outfit.sprite, 110 | (Sprite){ 111 | .pos = {440.0f, 64.0f}, 112 | .hitbox_size = {40.0f, 131.0f}, 113 | .texture = texture_get(resources, BEAR_OUTFIT), 114 | .visible = game_state->bear_outfit_unlocked && 115 | game_state->lain.outfit != OUTFIT_BEAR, 116 | .z_index = 1, 117 | }); 118 | 119 | make_sprite(&dressup->school_outfit.sprite, 120 | (Sprite){ 121 | .pos = {32.0f, 64.0f}, 122 | .hitbox_size = {40.0f, 131.0f}, 123 | .texture = texture_get(resources, SCHOOL_OUTFIT), 124 | .visible = game_state->school_outfit_unlocked && 125 | game_state->lain.outfit != OUTFIT_SCHOOL, 126 | .z_index = 1, 127 | }); 128 | 129 | make_sprite(&dressup->sweater_outfit.sprite, 130 | (Sprite){ 131 | .pos = {112.0f, 64.0f}, 132 | .hitbox_size = {40.0f, 131.0f}, 133 | .texture = texture_get(resources, SWEATER_OUTFIT), 134 | .visible = game_state->sweater_outfit_unlocked && 135 | game_state->lain.outfit != OUTFIT_SWEATER, 136 | .z_index = 1, 137 | }); 138 | 139 | make_sprite(&dressup->cyberia_outfit.sprite, 140 | (Sprite){ 141 | .pos = {336.0f, 64.0f}, 142 | .hitbox_size = {40.0f, 131.0f}, 143 | .texture = texture_get(resources, CYBERIA_OUTFIT), 144 | .visible = game_state->cyberia_outfit_unlocked && 145 | game_state->lain.outfit != OUTFIT_CYBERIA, 146 | .z_index = 1, 147 | }); 148 | 149 | make_sprite(&dressup->ufo.sprite, 150 | (Sprite){ 151 | .pos = {216.0f, 8.0f}, 152 | .hitbox_size = {24.0f, 20.0f}, 153 | .texture = texture_get(resources, DRESSUP_UFO), 154 | .visible = game_state->alien_outfit_unlocked && 155 | game_state->lain.outfit != OUTFIT_ALIEN, 156 | .z_index = 1, 157 | }); 158 | 159 | make_sprite(&dressup->navi.sprite, 160 | (Sprite){ 161 | .pos = {32.0f, 112.0f}, 162 | .hitbox_size = {12.0f, 42.0f}, 163 | .texture = texture_get(resources, DRESSUP_NAVI), 164 | .visible = game_state->navi_unlocked && 165 | game_state->lain.tool_state != HOLDING_NAVI, 166 | .z_index = 1, 167 | }); 168 | 169 | make_sprite( 170 | &dressup->screwdriver.sprite, 171 | (Sprite){ 172 | .pos = {32.0f, 192.0f}, 173 | .hitbox_size = {12.0f, 42.0f}, 174 | .texture = texture_get(resources, DRESSUP_SCREWDRIVER), 175 | .visible = game_state->screwdriver_unlocked && 176 | game_state->lain.tool_state != HOLDING_SCREWDRIVER, 177 | .z_index = 1, 178 | }); 179 | 180 | make_sprite(&dressup->background, 181 | (Sprite){ 182 | .pos = {0.0f, 0.0f}, 183 | .texture = texture_get(resources, LAIN_ROOM), 184 | .visible = true, 185 | .z_index = 0, 186 | }); 187 | 188 | make_sprite(&dressup->lain.sprite, (Sprite){ 189 | .hitbox_size = {80.0f, 161.0f}, 190 | .visible = true, 191 | .z_index = 2, 192 | }); 193 | 194 | sprite_set_animation(resources, game_state->time, &dressup->lain.sprite, 195 | game_state->lain.walk_animation); 196 | } 197 | 198 | static void init_dressup_scene(Resources *resources, GameState *game_state, 199 | Scene *scene, DressUp *dressup) 200 | { 201 | init_dressup_sprites(resources, game_state, dressup); 202 | 203 | Sprite *sprites[] = {&dressup->school_outfit.sprite, 204 | &dressup->bear_outfit.sprite, 205 | &dressup->sweater_outfit.sprite, 206 | &dressup->cyberia_outfit.sprite, 207 | &dressup->ufo.sprite, 208 | &dressup->navi.sprite, 209 | &dressup->screwdriver.sprite, 210 | &dressup->background, 211 | &dressup->lain.sprite}; 212 | uint8_t sprite_count = sizeof(sprites) / sizeof(sprites[0]); 213 | 214 | dressup->school_outfit.outfit = OUTFIT_SCHOOL; 215 | dressup->school_outfit.type = CLOTHING; 216 | 217 | dressup->sweater_outfit.outfit = OUTFIT_SWEATER; 218 | dressup->sweater_outfit.type = CLOTHING; 219 | 220 | dressup->bear_outfit.outfit = OUTFIT_BEAR; 221 | dressup->bear_outfit.type = CLOTHING; 222 | 223 | dressup->cyberia_outfit.outfit = OUTFIT_CYBERIA; 224 | dressup->cyberia_outfit.type = CLOTHING; 225 | 226 | dressup->ufo.outfit = OUTFIT_ALIEN; 227 | dressup->ufo.type = CLOTHING; 228 | 229 | dressup->navi.type = NAVI; 230 | dressup->navi.outfit = -1; 231 | 232 | dressup->screwdriver.type = SCREWDRIVER; 233 | dressup->screwdriver.outfit = -1; 234 | 235 | SpriteBehavior sprite_behaviors[] = { 236 | (SpriteBehavior){.sprite = &dressup->school_outfit.sprite, 237 | .click_event = ITEM_GRAB, 238 | .object = &dressup->school_outfit}, 239 | (SpriteBehavior){.sprite = &dressup->sweater_outfit.sprite, 240 | .click_event = ITEM_GRAB, 241 | .object = &dressup->sweater_outfit}, 242 | (SpriteBehavior){.sprite = &dressup->bear_outfit.sprite, 243 | .click_event = ITEM_GRAB, 244 | .object = &dressup->bear_outfit}, 245 | (SpriteBehavior){.sprite = &dressup->cyberia_outfit.sprite, 246 | .click_event = ITEM_GRAB, 247 | .object = &dressup->cyberia_outfit}, 248 | (SpriteBehavior){.sprite = &dressup->ufo.sprite, 249 | .click_event = ITEM_GRAB, 250 | .object = &dressup->ufo}, 251 | (SpriteBehavior){.sprite = &dressup->navi.sprite, 252 | .click_event = ITEM_GRAB, 253 | .object = &dressup->navi}, 254 | (SpriteBehavior){.sprite = &dressup->screwdriver.sprite, 255 | .click_event = ITEM_GRAB, 256 | .object = &dressup->screwdriver}, 257 | (SpriteBehavior){.sprite = &dressup->lain.sprite, 258 | .click_event = LAIN_SWAP_CLOTHING, 259 | .object = &dressup->lain}, 260 | }; 261 | uint8_t sprite_behavior_count = 262 | sizeof(sprite_behaviors) / sizeof(sprite_behaviors[0]); 263 | 264 | init_scene(scene, sprites, sprite_count, sprite_behaviors, 265 | sprite_behavior_count, NULL, 0, NULL, 0); 266 | } 267 | 268 | void update_dressup(Resources *resources, Menu *menu, GameState *game_state, 269 | GLFWwindow *window, Minigame *minigame) 270 | { 271 | DressUp *dressup = &minigame->current.dressup; 272 | DressUpLain *lain = &dressup->lain; 273 | 274 | if (glfwWindowShouldClose(window) && lain->move_state != LEAVING) { 275 | play_sound(&resources->audio_engine, SND_116); 276 | 277 | lain->move_state = LEAVING; 278 | 279 | sprite_set_animation(resources, game_state->time, &lain->sprite, 280 | game_state->lain.leave_animation); 281 | 282 | depth_sort(dressup->scene.sprites, 283 | cvector_size(dressup->scene.sprites)); 284 | 285 | if (game_state->lain.tool_state == HOLDING_SCREWDRIVER) { 286 | dressup->screwdriver.sprite.visible = false; 287 | } 288 | 289 | if (game_state->lain.tool_state == HOLDING_NAVI) { 290 | dressup->navi.sprite.visible = false; 291 | } 292 | } 293 | 294 | switch (lain->move_state) { 295 | case STANDING: { 296 | DressUpObject *currently_grabbed = dressup->currently_grabbed; 297 | if (currently_grabbed != NULL) { 298 | double x, y; 299 | glfwGetCursorPos(window, &x, &y); 300 | currently_grabbed->sprite.pos = (Vector2D){ 301 | x - currently_grabbed->sprite.texture->size.x / 2, 302 | y - currently_grabbed->sprite.texture->size.y / 2}; 303 | }; 304 | 305 | break; 306 | } 307 | case ENTERING: { 308 | if (sprite_animation_is_last_frame(&lain->sprite)) { 309 | lain->move_state = STANDING; 310 | 311 | if (game_state->lain.tool_state == HOLDING_NAVI) { 312 | dressup->navi.sprite.visible = true; 313 | dressup->navi.sprite.z_index = 3; 314 | dressup->navi.sprite.pos = 315 | (Vector2D){320.0f, 249.0f}; 316 | } 317 | 318 | if (game_state->lain.tool_state == 319 | HOLDING_SCREWDRIVER) { 320 | dressup->screwdriver.sprite.visible = true; 321 | dressup->screwdriver.sprite.z_index = 3; 322 | dressup->screwdriver.sprite.pos = 323 | (Vector2D){248.0f, 232.0f}; 324 | } 325 | 326 | depth_sort(dressup->scene.sprites, 327 | cvector_size(dressup->scene.sprites)); 328 | } else { 329 | sprite_try_next_frame(resources, game_state->time, 330 | &lain->sprite); 331 | } 332 | 333 | break; 334 | } 335 | case LEAVING: { 336 | if (sprite_animation_is_last_frame(&lain->sprite)) { 337 | destroy_minigame(resources->textures, menu, minigame, 338 | window); 339 | return; 340 | } else { 341 | sprite_try_next_frame(resources, game_state->time, 342 | &lain->sprite); 343 | } 344 | 345 | break; 346 | } 347 | } 348 | 349 | update_scene(&dressup->scene); 350 | } 351 | int start_dressup(Menu *menu, Resources *resources, GameState *game_state, 352 | Minigame *minigame, GLFWwindow **minigame_window, 353 | GLFWwindow *main_window) 354 | { 355 | if (!(make_window(minigame_window, MINIGAME_WIDTH, MINIGAME_HEIGHT, 356 | "lain dress up", main_window, true))) { 357 | printf("Failed to create dressup window.\n"); 358 | return 0; 359 | } 360 | 361 | DressUp *dressup = &minigame->current.dressup; 362 | 363 | dressup->lain.move_state = ENTERING; 364 | dressup->currently_grabbed = NULL; 365 | 366 | lain_set_outfit(resources, game_state->lain.outfit, &game_state->lain); 367 | 368 | init_dressup_scene(resources, game_state, &dressup->scene, dressup); 369 | 370 | minigame->type = DRESSUP; 371 | minigame->last_updated = game_state->time; 372 | 373 | menu->dressup_button.texture = 374 | texture_get(resources, DRESSUP_BUTTON_ACTIVE); 375 | 376 | play_sound(&resources->audio_engine, SND_115); 377 | 378 | return 1; 379 | } 380 | 381 | static _Bool can_wear(GameState *game_state, DressUpObject *item, Vector2D pos) 382 | { 383 | 384 | switch (item->type) { 385 | case CLOTHING: 386 | return (256.0f <= pos.x && pos.x <= 336.0f) && 387 | (144.0f <= pos.y && pos.y <= 326.0f); 388 | case SCREWDRIVER: 389 | return (248.0f <= pos.x && pos.x <= 272.0f) && 390 | (232.0f <= pos.y && pos.y <= 274.0f) && 391 | game_state->lain.outfit == OUTFIT_DEFAULT; 392 | case NAVI: 393 | return (320.0f <= pos.x && pos.x <= 344.0f) && 394 | (249.0f <= pos.y && pos.y <= 291.0f) && 395 | game_state->lain.outfit == OUTFIT_SCHOOL; 396 | } 397 | } 398 | 399 | static void reset_tools(GameState *game_state, DressUp *dressup) 400 | { 401 | if (game_state->lain.tool_state != NO_TOOLS) { 402 | sprite_set_to_origin_pos(&dressup->navi.sprite); 403 | sprite_set_to_origin_pos(&dressup->screwdriver.sprite); 404 | 405 | game_state->lain.tool_state = NO_TOOLS; 406 | } 407 | } 408 | 409 | static void wear_item(Resources *resources, GameState *game_state, 410 | DressUp *dressup, DressUpObject *item) 411 | { 412 | 413 | switch (item->type) { 414 | case CLOTHING: 415 | if (game_state->lain.outfit != OUTFIT_DEFAULT) { 416 | // reset current clothing 417 | DressUpObject *outfit_obj = get_object_for_outfit( 418 | dressup, game_state->lain.outfit); 419 | 420 | sprite_set_to_origin_pos(&outfit_obj->sprite); 421 | outfit_obj->sprite.visible = true; 422 | outfit_obj->sprite.z_index = 1; 423 | } 424 | 425 | if (!(item->outfit == OUTFIT_SCHOOL && 426 | game_state->lain.tool_state == HOLDING_NAVI)) { 427 | 428 | reset_tools(game_state, dressup); 429 | } 430 | 431 | lain_set_outfit(resources, dressup->currently_grabbed->outfit, 432 | &game_state->lain); 433 | 434 | dressup->lain.sprite.texture = 435 | game_state->lain.standing_texture; 436 | 437 | dressup->currently_grabbed->sprite.pos = (Vector2D){0.0f, 0.0f}; 438 | dressup->currently_grabbed->sprite.visible = false; 439 | 440 | break; 441 | case SCREWDRIVER: 442 | dressup->currently_grabbed->sprite.pos = 443 | (Vector2D){248.0f, 232.0f}; 444 | game_state->lain.tool_state = HOLDING_SCREWDRIVER; 445 | dressup->currently_grabbed->sprite.z_index = 3; 446 | 447 | break; 448 | case NAVI: 449 | dressup->currently_grabbed->sprite.pos = 450 | (Vector2D){320.0f, 249.0f}; 451 | game_state->lain.tool_state = HOLDING_NAVI; 452 | dressup->currently_grabbed->sprite.z_index = 3; 453 | 454 | break; 455 | } 456 | } 457 | 458 | void handle_dressup_event(DressUpEvent event, void *object, Engine *engine) 459 | { 460 | DressUp *dressup = &engine->minigame.current.dressup; 461 | DressUpLain *lain = &dressup->lain; 462 | GameState *game_state = &engine->game_state; 463 | Resources *resources = &engine->resources; 464 | 465 | if (lain->move_state != STANDING) { 466 | return; 467 | } 468 | 469 | switch (event) { 470 | case ITEM_GRAB: { 471 | dressup->currently_grabbed = object; 472 | dressup->currently_grabbed->sprite.visible = true; 473 | dressup->currently_grabbed->sprite.z_index = 4; 474 | 475 | depth_sort(dressup->scene.sprites, 476 | cvector_size(dressup->scene.sprites)); 477 | 478 | if ((game_state->lain.tool_state == HOLDING_SCREWDRIVER && 479 | dressup->currently_grabbed->type == SCREWDRIVER) || 480 | (game_state->lain.tool_state == HOLDING_NAVI && 481 | dressup->currently_grabbed->type == NAVI)) { 482 | reset_tools(game_state, dressup); 483 | } 484 | 485 | break; 486 | } 487 | case ITEM_RELEASE: { 488 | if (dressup->currently_grabbed == NULL) { 489 | break; 490 | } 491 | 492 | double x, y; 493 | glfwGetCursorPos(engine->minigame_window, &x, &y); 494 | Vector2D pos = (Vector2D){x, y}; 495 | 496 | if (can_wear(game_state, dressup->currently_grabbed, pos)) { 497 | play_sound(&resources->audio_engine, SND_117); 498 | 499 | wear_item(resources, game_state, dressup, 500 | dressup->currently_grabbed); 501 | } else { 502 | if (dressup->currently_grabbed->outfit == 503 | OUTFIT_SCHOOL) { 504 | reset_tools(game_state, dressup); 505 | } 506 | 507 | sprite_set_to_origin_pos( 508 | &dressup->currently_grabbed->sprite); 509 | 510 | dressup->currently_grabbed->sprite.z_index = 1; 511 | } 512 | 513 | dressup->currently_grabbed = NULL; 514 | 515 | depth_sort(dressup->scene.sprites, 516 | cvector_size(dressup->scene.sprites)); 517 | break; 518 | } 519 | case LAIN_SWAP_CLOTHING: { 520 | if (game_state->lain.outfit == OUTFIT_DEFAULT) { 521 | break; 522 | } 523 | 524 | DressUpObject *outfit_obj = 525 | get_object_for_outfit(dressup, game_state->lain.outfit); 526 | 527 | handle_dressup_event(ITEM_GRAB, outfit_obj, engine); 528 | 529 | lain_set_outfit(resources, OUTFIT_DEFAULT, &game_state->lain); 530 | dressup->lain.sprite.texture = 531 | game_state->lain.standing_texture; 532 | 533 | break; 534 | } 535 | } 536 | 537 | Sprite *screwdriver_icon = &engine->menu.screwdriver_icon; 538 | if (game_state->lain.tool_state == HOLDING_SCREWDRIVER) { 539 | screwdriver_icon->texture = 540 | texture_get(resources, SCREWDRIVER_ICON_ACTIVE); 541 | } else { 542 | screwdriver_icon->texture = 543 | texture_get(resources, SCREWDRIVER_ICON); 544 | } 545 | } 546 | -------------------------------------------------------------------------------- /src/menu.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "animations.h" 8 | #include "dressup.h" 9 | #include "kumashoot.h" 10 | #include "menu.h" 11 | #include "minigame.h" 12 | #include "resources.h" 13 | #include "scene.h" 14 | #include "shader.h" 15 | #include "sound.h" 16 | #include "sprite.h" 17 | #include "state.h" 18 | #include "text.h" 19 | #include "texture.h" 20 | #include "theater.h" 21 | #include "vector2d.h" 22 | #include "window.h" 23 | 24 | #define PI 3.14159265359 25 | 26 | static inline _Bool is_bear(struct tm *current_time) 27 | { 28 | return (17 < current_time->tm_hour) || (current_time->tm_hour < 6); 29 | } 30 | 31 | static void update_menu_time(Menu *menu) 32 | { 33 | time_t t; 34 | 35 | time(&t); 36 | 37 | menu->current_time = localtime(&t); 38 | } 39 | 40 | static void get_menu_timestring(char *target, Menu *menu) 41 | { 42 | strftime(target, sizeof(char) * 16, "%p%I:%M:%S", menu->current_time); 43 | } 44 | 45 | static void init_menu_sprites(struct tm *current_time, GameState *game_state, 46 | Resources *resources, Menu *menu) 47 | { 48 | // texture for this gets set later on 49 | make_sprite( 50 | &menu->ui_lain.sprite, 51 | (Sprite){.pos = {6.0f, 6.0f}, 52 | .hitbox_size = {32.0f, 32.0f}, 53 | .texture = is_bear(current_time) 54 | ? texture_get(resources, UI_BEAR_LAIN_1) 55 | : texture_get(resources, UI_DEFAULT_LAIN_1), 56 | .z_index = 4, 57 | .visible = true}); 58 | 59 | make_sprite(&menu->main_ui, 60 | (Sprite){.pos = {-34.0f, -34.0f}, 61 | .texture = texture_get(resources, MAIN_UI_1), 62 | .z_index = 3, 63 | .visible = true}); 64 | 65 | make_sprite(&menu->main_ui_bar, 66 | (Sprite){ 67 | .pos = {102.0f, 38.0f}, 68 | .hitbox_size = {64.0f, 8.0f}, 69 | .texture = texture_get(resources, MAIN_UI_BAR), 70 | .z_index = 4, 71 | .visible = true, 72 | }); 73 | 74 | make_sprite(&menu->dressup_button, 75 | (Sprite){ 76 | .pos = {112.0f, 96.0f}, 77 | .hitbox_size = {36.0f, 36.0f}, 78 | .texture = texture_get(resources, DRESSUP_BUTTON), 79 | .z_index = 4, 80 | .visible = false, 81 | }); 82 | 83 | make_sprite(&menu->theater_preview, 84 | (Sprite){ 85 | .pos = {104.0f, 80.0f}, 86 | .hitbox_size = {96.0f, 64.0f}, 87 | .texture = texture_get( 88 | resources, game_state->current_theater_preview), 89 | .z_index = 7, 90 | .visible = false, 91 | }); 92 | 93 | make_sprite(&menu->theater_button, 94 | (Sprite){ 95 | .pos = {0.0f, 128.0f}, 96 | .hitbox_size = {64.0f, 32.0f}, 97 | .texture = texture_get(resources, THEATER_BUTTON), 98 | .z_index = 4, 99 | .visible = false, 100 | }); 101 | 102 | make_sprite(&menu->bear_icon, 103 | (Sprite){ 104 | .pos = {56.0f, 56.0f}, 105 | .hitbox_size = {32.0f, 32.0f}, 106 | .texture = texture_get(resources, BEAR_ICON), 107 | .z_index = 1, 108 | .visible = false, 109 | }); 110 | 111 | make_sprite(&menu->screwdriver_icon, 112 | (Sprite){ 113 | .pos = {56.0f, 56.0f}, 114 | .texture = texture_get(resources, SCREWDRIVER_ICON), 115 | .z_index = 2, 116 | .visible = false, 117 | }); 118 | 119 | make_sprite(&menu->paw_icon, 120 | (Sprite){ 121 | .pos = {56.0f, 56.0f}, 122 | .texture = texture_get(resources, PAW_ICON), 123 | .z_index = 1, 124 | .visible = false, 125 | }); 126 | 127 | make_sprite(&menu->score_preview, 128 | (Sprite){ 129 | .pos = {0.0f, 0.0f}, 130 | .texture = texture_get(resources, SCORE_DISPLAY), 131 | .z_index = 7, 132 | .visible = false, 133 | }); 134 | 135 | make_sprite(&menu->background, 136 | (Sprite){ 137 | .pos = {0.0f, 0.0f}, 138 | .texture = texture_get(resources, MENU_BG), 139 | .z_index = 0, 140 | .visible = true, 141 | }); 142 | } 143 | 144 | static void init_menu_text_objects(Font *fonts, GameState *game_state, 145 | Menu *menu) 146 | { 147 | menu->clock = (Text){.pos = {70.0f, 22.0f}, 148 | .glyph_size = {32.0f, 16.0f}, 149 | .visible = true, 150 | .font = &fonts[FONT_WHITE]}; 151 | get_menu_timestring(menu->clock.current_text, menu); 152 | 153 | menu->score_text = (Text){.pos = {178.0f, 16.0f}, 154 | .glyph_size = {10.0f, 16.0f}, 155 | .visible = false, 156 | .left_aligned = true, 157 | .font = &fonts[FONT_RED]}; 158 | sprintf(menu->score_text.current_text, "%ld", game_state->score); 159 | } 160 | 161 | static void animate_menu(GameState *game_state, Resources *resources, 162 | Menu *menu, GLFWwindow *window) 163 | { 164 | Sprite *main_ui = &menu->main_ui; 165 | 166 | if (menu->collapsed) { 167 | if (main_ui->animation_frame->index == 0) { 168 | glfwSetWindowSize(window, EXPANDED_MENU_WIDTH, 169 | EXPANDED_MENU_HEIGHT); 170 | 171 | main_ui->pos = (Vector2D){0.0f, 0.0f}; 172 | menu->ui_lain.sprite.pos = (Vector2D){40.0f, 40.0f}; 173 | menu->main_ui_bar.pos = (Vector2D){136.0f, 72.0f}; 174 | 175 | menu->clock.pos = (Vector2D){104.0f, 56.0f}; 176 | 177 | menu->bear_icon.visible = true; 178 | menu->screwdriver_icon.visible = true; 179 | menu->paw_icon.visible = true; 180 | } 181 | } else { 182 | if (main_ui->animation_frame->index == 1) { 183 | menu->dressup_button.visible = false; 184 | menu->theater_button.visible = false; 185 | menu->score_preview.visible = false; 186 | menu->score_text.visible = false; 187 | } 188 | if (main_ui->animation_frame->index == 5) { 189 | glfwSetWindowSize(window, COLLAPSED_MENU_WIDTH, 190 | COLLAPSED_MENU_HEIGHT); 191 | 192 | sprite_set_to_origin_pos(main_ui); 193 | sprite_set_to_origin_pos(&menu->ui_lain.sprite); 194 | sprite_set_to_origin_pos(&menu->main_ui_bar); 195 | 196 | menu->clock.pos = (Vector2D){70.0f, 22.0f}; 197 | 198 | menu->bear_icon.visible = false; 199 | menu->screwdriver_icon.visible = false; 200 | menu->paw_icon.visible = false; 201 | } 202 | } 203 | 204 | if (sprite_animation_is_last_frame(&menu->main_ui)) { 205 | menu->collapsed = !menu->collapsed; 206 | if (menu->collapsed) { 207 | menu->main_ui_bar.texture = 208 | texture_get(resources, MAIN_UI_BAR); 209 | } else { 210 | menu->main_ui_bar.texture = 211 | texture_get(resources, MAIN_UI_BAR_ACTIVE); 212 | menu->dressup_button.visible = true; 213 | menu->theater_button.visible = true; 214 | } 215 | } 216 | 217 | sprite_try_next_frame(resources, game_state->time, &menu->main_ui); 218 | } 219 | 220 | static void update_menu_icons(Menu *menu) 221 | { 222 | Sprite *bear_icon = &menu->bear_icon; 223 | Sprite *screwdriver_icon = &menu->screwdriver_icon; 224 | Sprite *paw_icon = &menu->paw_icon; 225 | 226 | struct tm *curr_time = menu->current_time; 227 | 228 | int secs = curr_time->tm_sec - 15; 229 | int mins = curr_time->tm_min - 15; 230 | int hrs = curr_time->tm_hour - 3; 231 | 232 | float deg_60 = PI / 3.0f; 233 | float deg_30 = PI / 6.0f; 234 | 235 | float secs_angle = secs * (deg_60 / 10); 236 | float mins_angle = mins * (deg_60 / 10); 237 | float hrs_angle = hrs * deg_30; 238 | 239 | float radius = 44.0f; 240 | 241 | screwdriver_icon->pos = (Vector2D){ 242 | screwdriver_icon->origin_pos.x + cos(secs_angle) * radius, 243 | screwdriver_icon->origin_pos.y + sin(secs_angle) * radius}; 244 | 245 | bear_icon->pos = 246 | (Vector2D){bear_icon->origin_pos.x + cos(mins_angle) * radius, 247 | bear_icon->origin_pos.y + sin(mins_angle) * radius}; 248 | 249 | paw_icon->pos = 250 | (Vector2D){paw_icon->origin_pos.x + cos(hrs_angle) * radius, 251 | paw_icon->origin_pos.y + sin(hrs_angle) * radius}; 252 | } 253 | 254 | static int get_menu_lain_animation(struct tm *current_time, 255 | MenuLainAnimation animation_type) 256 | { 257 | if (is_bear(current_time)) { 258 | switch (animation_type) { 259 | case LAUGH: { 260 | return UI_BEAR_LAIN_LAUGH_ANIMATION; 261 | } 262 | case BLINK: { 263 | return UI_BEAR_LAIN_BLINK_ANIMATION; 264 | } 265 | case LAUGH_BLINK: { 266 | return UI_BEAR_LAIN_LAUGH_BLINK_ANIMATION; 267 | } 268 | } 269 | } else { 270 | switch (animation_type) { 271 | case LAUGH: { 272 | return UI_DEFAULT_LAIN_LAUGH_ANIMATION; 273 | } 274 | case BLINK: { 275 | return UI_DEFAULT_LAIN_BLINK_ANIMATION; 276 | } 277 | case LAUGH_BLINK: { 278 | return UI_DEFAULT_LAIN_LAUGH_BLINK_ANIMATION; 279 | } 280 | } 281 | } 282 | } 283 | 284 | void update_menu_lain(Resources *resources, GameState *game_state, 285 | struct tm *current_time, MenuLain *lain) 286 | { 287 | if (current_time->tm_sec % 15 == 0) { 288 | if (!lain->recently_changed_laugh) { 289 | unsigned int quarter = current_time->tm_sec / 15; 290 | if (quarter == lain->laugh_quarter) { 291 | if (!lain->laughing) { 292 | sprite_set_animation( 293 | resources, game_state->time, 294 | &lain->sprite, 295 | get_menu_lain_animation( 296 | current_time, LAUGH)); 297 | 298 | lain->laughing = !lain->laughing; 299 | lain->recently_changed_laugh = true; 300 | } else { 301 | lain->laughing = !lain->laughing; 302 | lain->recently_changed_laugh = true; 303 | 304 | sprite_set_animation( 305 | resources, game_state->time, 306 | &lain->sprite, 307 | get_menu_lain_animation( 308 | current_time, BLINK)); 309 | } 310 | } else if (!lain->blinking) { 311 | if (!lain->laughing) { 312 | sprite_set_animation( 313 | resources, game_state->time, 314 | &lain->sprite, 315 | get_menu_lain_animation( 316 | current_time, BLINK)); 317 | } else { 318 | sprite_set_animation( 319 | resources, game_state->time, 320 | &lain->sprite, 321 | get_menu_lain_animation( 322 | current_time, LAUGH_BLINK)); 323 | } 324 | lain->blinking = true; 325 | } 326 | } 327 | } else { 328 | lain->recently_changed_laugh = false; 329 | lain->blinking = false; 330 | } 331 | 332 | if (lain->sprite.animation != NULL) { 333 | sprite_try_next_frame(resources, game_state->time, 334 | &lain->sprite); 335 | } 336 | } 337 | 338 | void update_menu(Menu *menu, GameState *game_state, GLFWwindow *window, 339 | Resources *resources) 340 | { 341 | update_menu_time(menu); 342 | 343 | char timestring[16]; 344 | get_menu_timestring(timestring, menu); 345 | update_text(&menu->clock, timestring); 346 | 347 | char score[16]; 348 | sprintf(score, "%ld", game_state->score); 349 | update_text(&menu->score_text, score); 350 | 351 | update_menu_lain(resources, game_state, menu->current_time, 352 | &menu->ui_lain); 353 | 354 | update_menu_icons(menu); 355 | 356 | update_scene(&menu->scene); 357 | 358 | if (menu->main_ui.animation != NULL) { 359 | animate_menu(game_state, resources, menu, window); 360 | } 361 | } 362 | 363 | void init_menu(Menu *menu, GameState *game_state, Resources *resources) 364 | { 365 | update_menu_time(menu); 366 | 367 | menu->collapsed = true; 368 | 369 | menu->ui_lain.recently_changed_laugh = false; 370 | menu->ui_lain.laughing = false; 371 | menu->ui_lain.blinking = false; 372 | menu->ui_lain.laugh_quarter = menu->current_time->tm_sec / 15; 373 | 374 | init_menu_sprites(menu->current_time, game_state, resources, menu); 375 | 376 | init_menu_text_objects(resources->fonts, game_state, menu); 377 | 378 | Sprite *sprites[] = { 379 | &menu->ui_lain.sprite, &menu->main_ui, 380 | &menu->main_ui_bar, &menu->dressup_button, 381 | &menu->theater_button, &menu->bear_icon, 382 | &menu->screwdriver_icon, &menu->paw_icon, 383 | &menu->theater_preview, &menu->score_preview, 384 | &menu->background, 385 | }; 386 | uint8_t sprite_count = sizeof(sprites) / sizeof(sprites[0]); 387 | 388 | Text *text_objs[] = {&menu->clock, &menu->score_text}; 389 | uint8_t text_obj_count = sizeof(text_objs) / sizeof(text_objs[0]); 390 | 391 | // behavior definitions for sprites 392 | SpriteBehavior sprite_behaviors[] = { 393 | (SpriteBehavior){.sprite = &menu->main_ui_bar, 394 | .click_event = MAIN_UI_BAR_CLICK}, 395 | 396 | (SpriteBehavior){.sprite = &menu->theater_button, 397 | .click_event = TOGGLE_THEATER_PREVIEW}, 398 | 399 | (SpriteBehavior){.sprite = &menu->ui_lain.sprite, 400 | .click_event = TOGGLE_SCORE_PREVIEW}, 401 | 402 | (SpriteBehavior){.sprite = &menu->bear_icon, 403 | .click_event = BEAR_ICON_CLICK}, 404 | 405 | (SpriteBehavior){.sprite = &menu->dressup_button, 406 | .click_event = DRESSUP_TOGGLE}, 407 | 408 | (SpriteBehavior){.sprite = &menu->theater_preview, 409 | .click_event = THEATER_TOGGLE} 410 | 411 | }; 412 | uint8_t sprite_behavior_count = 413 | sizeof(sprite_behaviors) / sizeof(sprite_behaviors[0]); 414 | 415 | init_scene(&menu->scene, sprites, sprite_count, sprite_behaviors, 416 | sprite_behavior_count, text_objs, text_obj_count, NULL, 0); 417 | } 418 | 419 | static TextureID get_next_theater_preview(GameState *game_state, 420 | TextureID current_texture_id) 421 | { 422 | switch (current_texture_id) { 423 | case CLASSROOM_PREVIEW: 424 | return SCHOOL_PREVIEW; 425 | case SCHOOL_PREVIEW: 426 | return LAIN_ROOM_NIGHT_PREVIEW; 427 | case LAIN_ROOM_NIGHT_PREVIEW: 428 | return ARISU_ROOM_PREVIEW; 429 | case ARISU_ROOM_PREVIEW: 430 | return CYBERIA_PREVIEW; 431 | case CYBERIA_PREVIEW: 432 | return STREET_PREVIEW; 433 | case STREET_PREVIEW: 434 | return game_state->score >= 2000 ? BRIDGE_PREVIEW 435 | : CLASSROOM_PREVIEW; 436 | case BRIDGE_PREVIEW: 437 | return CLASSROOM_PREVIEW; 438 | default: 439 | return 0; 440 | } 441 | } 442 | 443 | void handle_menu_event(MenuEvent event, Engine *engine) 444 | { 445 | Menu *menu = &engine->menu; 446 | Resources *resources = &engine->resources; 447 | 448 | switch (event) { 449 | case MAIN_UI_BAR_CLICK: { 450 | Sprite *theater_preview = &menu->theater_preview; 451 | Sprite *main_ui = &menu->main_ui; 452 | 453 | if (theater_preview->visible) { 454 | if (engine->minigame.type != THEATER) { 455 | play_sound(&resources->audio_engine, SND_114); 456 | 457 | menu->theater_preview.texture = texture_get( 458 | resources, 459 | get_next_theater_preview( 460 | &engine->game_state, 461 | menu->theater_preview.texture->id)); 462 | } 463 | } else { 464 | if (engine->menu.collapsed) { 465 | play_sound(&resources->audio_engine, SND_112); 466 | 467 | sprite_set_animation( 468 | resources, engine->game_state.time, main_ui, 469 | MAIN_UI_EXPAND_ANIMATION); 470 | } else if (engine->minigame.type != DRESSUP) { 471 | play_sound(&resources->audio_engine, SND_113); 472 | 473 | sprite_set_animation( 474 | resources, engine->game_state.time, main_ui, 475 | MAIN_UI_COLLAPSE_ANIMATION); 476 | } 477 | } 478 | break; 479 | } 480 | case TOGGLE_THEATER_PREVIEW: { 481 | if (engine->minigame.type != THEATER && 482 | engine->minigame.type != DRESSUP) { 483 | Sprite *theater_preview = &menu->theater_preview; 484 | 485 | if (theater_preview->visible) { 486 | play_sound(&resources->audio_engine, SND_111); 487 | } else { 488 | play_sound(&resources->audio_engine, SND_110); 489 | } 490 | 491 | theater_preview->visible = !theater_preview->visible; 492 | 493 | menu->theater_button.texture = 494 | texture_get(resources, theater_preview->visible 495 | ? THEATER_BUTTON_ACTIVE 496 | : THEATER_BUTTON); 497 | } 498 | break; 499 | } 500 | case TOGGLE_SCORE_PREVIEW: { 501 | Sprite *score_preview = &menu->score_preview; 502 | Text *score = &menu->score_text; 503 | 504 | if (!menu->collapsed) { 505 | if (score_preview->visible) { 506 | play_sound(&resources->audio_engine, SND_111); 507 | } else { 508 | play_sound(&resources->audio_engine, SND_110); 509 | } 510 | score_preview->visible = !score_preview->visible; 511 | score->visible = !score->visible; 512 | } 513 | break; 514 | } 515 | case BEAR_ICON_CLICK: { 516 | MinigameType minigame_type = engine->minigame.type; 517 | switch (minigame_type) { 518 | case THEATER: 519 | case DRESSUP: { 520 | play_sound(&resources->audio_engine, SND_110); 521 | 522 | glfwSetWindowShouldClose(engine->minigame_window, 1); 523 | engine->minigame.queued_minigame = KUMASHOOT; 524 | break; 525 | } 526 | case KUMASHOOT: { 527 | play_sound(&resources->audio_engine, SND_111); 528 | 529 | glfwSetWindowShouldClose(engine->minigame_window, 1); 530 | break; 531 | } 532 | case NO_MINIGAME: { 533 | play_sound(&resources->audio_engine, SND_110); 534 | 535 | engine->minigame.queued_minigame = KUMASHOOT; 536 | break; 537 | } 538 | } 539 | 540 | break; 541 | } 542 | case DRESSUP_TOGGLE: { 543 | MinigameType minigame_type = engine->minigame.type; 544 | 545 | if (menu->theater_preview.visible) { 546 | return; 547 | } 548 | 549 | switch (minigame_type) { 550 | case DRESSUP: { 551 | play_sound(&resources->audio_engine, SND_111); 552 | 553 | glfwSetWindowShouldClose(engine->minigame_window, 1); 554 | break; 555 | } 556 | case KUMASHOOT: { 557 | play_sound(&resources->audio_engine, SND_110); 558 | 559 | glfwSetWindowShouldClose(engine->minigame_window, 1); 560 | engine->minigame.queued_minigame = DRESSUP; 561 | break; 562 | } 563 | case NO_MINIGAME: { 564 | play_sound(&resources->audio_engine, SND_110); 565 | 566 | engine->minigame.queued_minigame = DRESSUP; 567 | break; 568 | } 569 | case THEATER: 570 | break; 571 | } 572 | 573 | break; 574 | } 575 | case THEATER_TOGGLE: { 576 | MinigameType minigame_type = engine->minigame.type; 577 | 578 | switch (minigame_type) { 579 | case DRESSUP: { 580 | break; 581 | } 582 | case KUMASHOOT: { 583 | play_sound(&resources->audio_engine, SND_111); 584 | 585 | glfwSetWindowShouldClose(engine->minigame_window, 1); 586 | engine->minigame.queued_minigame = THEATER; 587 | break; 588 | } 589 | case THEATER: { 590 | glfwSetWindowShouldClose(engine->minigame_window, 1); 591 | break; 592 | } 593 | case NO_MINIGAME: { 594 | play_sound(&resources->audio_engine, SND_110); 595 | 596 | engine->minigame.queued_minigame = THEATER; 597 | break; 598 | } 599 | } 600 | break; 601 | } 602 | } 603 | } 604 | --------------------------------------------------------------------------------