/* * Copyright (C) 2018 Vito Caputo - * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3 as published * by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include /* vsnprintf */ #include "aabb.h" #include "whale.h" #include "whale-svg.h" #include "macros.h" #include "stage.h" #include "svg-node.h" /* the game */ typedef enum whale_game_fsm_t { WHALE_GAME_SETTING_UP, WHALE_GAME_STARTING, WHALE_GAME_TARGETING, WHALE_GAME_LAUNCHING, WHALE_GAME_FLYING, WHALE_GAME_INSIDE, WHALE_GAME_OUTSIDE, WHALE_GAME_LEAVE } whale_game_fsm_t; static Mix_Chunk *launch_sfx, *crash_sfx, *winner_sfx; static stage_node_t *bobby, *crater, *astro, *planets; static unsigned bobby_armed; static whale_game_fsm_t state; void whale_game_event(whale_t *whale, SDL_Event *ev) { stage_t *stage = whale_get_stage(whale); switch (ev->type) { case SDL_KEYDOWN: switch (ev->key.keysym.sym) { case SDLK_ESCAPE: state = WHALE_GAME_LEAVE; break; case SDLK_SPACE: if (state == WHALE_GAME_INSIDE || state == WHALE_GAME_OUTSIDE) { /* resetup game */ state = WHALE_GAME_SETTING_UP; } default: break; } break; case SDL_MOUSEBUTTONDOWN: if (state == WHALE_GAME_TARGETING) { if (!bobby_armed) bobby_armed = whale_ticks(whale, WHALE_TICKS_TIMER); } else if (state == WHALE_GAME_INSIDE || state == WHALE_GAME_OUTSIDE) { /* resetup game */ state = WHALE_GAME_SETTING_UP; } break; case SDL_MOUSEBUTTONUP: if (state == WHALE_GAME_TARGETING) { if (bobby_armed) { /* launch bobby */ Mix_PlayChannel(-1, launch_sfx, 0); state = WHALE_GAME_LAUNCHING; } } break; case SDL_MOUSEMOTION: if (state == WHALE_GAME_TARGETING) { /* convert the mouse X, Y coordinates into an angle, it's * trivial since the whale is always in the bottom left */ int width, height; double angle; v2f_t v; stage_get_output_size(stage, &width, &height); v.x = (float)ev->motion.x; v.y = (float)height - ev->motion.y; v = v2f_normalize(&v); angle = -atan2f(v.y, v.x) * 57.4712f /* rad2deg */; stage_node_set_angle(stage, bobby, angle); } break; default: break; } } #if 0 stage_node_t * text_node(stage_t *stage, stage_node_t *node, const char *name, int layer, aabb_t aabb, int width, int height, int x, int y, const char *anchor, int font_size, const char *fmt, ...) { char buf[2048]; char str[1024]; va_list ap; va_start(ap, fmt); vsnprintf(str, sizeof(str), fmt, ap); va_end(ap); snprintf(buf, sizeof(buf), "%s", width, height, x, y, font_size, anchor, str); if (!node) { node = svg_node_new_buffer(stage, name, buf, strlen(buf), layer, aabb); fatal_if(!node, "Unable to create quesiton svg \"%s\"", name); } else { svg_node_replace_buffer(stage, node, name, buf, strlen(buf)); } return node; } #endif stage_node_t * meter_node(stage_t *stage, const aabb_t aabb, float level) { /* TODO: power meter */ } void whale_game_update(whale_t *whale) { static const aabb_t bobby_end_aabb = {{-1.f, -1.f}, {-.8f, -.8f}}; static aabb_t crater_end_aabb = {{0.f, -1.f}, {0.f, -.7f}}; static aabb_t bobby_start_aabb, crater_start_aabb; static int bobby_keyboard_armed, prev; static whale_svg_t *bobby_svg, *dead_svg; static int initialized; static v2f_t bobby_vector; stage_t *stage = whale_get_stage(whale); unsigned now = whale_ticks(whale, WHALE_TICKS_TIMER); if (!initialized) { bobby = stage_node_lookup_name(stage, "bobby"); fatal_if(!bobby, "Unable to lookup whale node"); stage_node_get_object(stage, bobby, (void **)&bobby_svg); /* XXX FIXME: NOO */ dead_svg = whale_svg_new_file("assets/dead.svg"); fatal_if(!dead_svg, "Unable to load dead bobby svg"); crater = stage_node_lookup_name(stage, "crater"); fatal_if(!crater, "Unable to lookup crater node"); astro = stage_node_lookup_name(stage, "astro"); fatal_if(!astro, "Unable to lookup astro node"); planets = svg_node_new_file(stage, "planets", "assets/planets.svg", 0, (aabb_t){{-1.f, -.7f}, {1.f, 1.f}}); fatal_if(!planets, "Unable to load planets svg"); stage_node_set_active(stage, planets); stage_node_set_alpha(stage, bobby, 1.0f); stage_node_set_alpha(stage, crater, 1.0f); winner_sfx = Mix_LoadWAV("assets/winner.wav"); fatal_if(!winner_sfx, "Unable to load winner sfx"); launch_sfx = Mix_LoadWAV("assets/launch.wav"); fatal_if(!launch_sfx, "Unable to load launch sfx"); crash_sfx = Mix_LoadWAV("assets/crash.wav"); fatal_if(!crash_sfx, "Unable to load crash sfx"); initialized = 1; } switch (state) { case WHALE_GAME_SETTING_UP: { float crater_x; stage_node_set_angle(stage, bobby, 0); stage_node_get_aabb(stage, bobby, &bobby_start_aabb); stage_node_get_aabb(stage, crater, &crater_start_aabb); stage_node_set_object(stage, bobby, bobby_svg); /* place crater destination randomly along the bottom */ crater_x = ((float)rand()) / RAND_MAX * .6f; crater_end_aabb.min.x = crater_x - .2f; crater_end_aabb.max.x = crater_x + .2f; bobby_keyboard_armed = bobby_armed = 0; whale_ticks_reset(whale, WHALE_TICKS_TIMER); prev = now = whale_ticks(whale, WHALE_TICKS_TIMER); state++; break; } case WHALE_GAME_STARTING: if (now < 500) { float t = ((float)now) * (1.0f / 500.0f); aabb_t aabb; t *= t; aabb = aabb_lerp(&bobby_start_aabb, &bobby_end_aabb, t); stage_node_set_aabb(stage, bobby, &aabb); aabb = aabb_lerp(&crater_start_aabb, &crater_end_aabb, t); stage_node_set_aabb(stage, crater, &aabb); if (astro) { /* XXX this is ugly; astro is serving as a "first start" flag */ stage_node_set_alpha(stage, astro, 1.f - t); stage_node_set_alpha(stage, planets, t); } } else { stage_node_set_aabb(stage, bobby, &bobby_end_aabb); stage_node_set_aabb(stage, crater, &crater_end_aabb); if (astro) { astro = stage_node_free(stage, astro); stage_node_set_alpha(stage, planets, 1.f); /* XXX: see above comment about astro abuse */ } state++; } break; case WHALE_GAME_TARGETING: { const Uint8 *key_state = SDL_GetKeyboardState(NULL); float t = ((float)(now - prev)) * .05f; /* scale simulation things according to time passed */ if (key_state[SDL_SCANCODE_LEFT] || key_state[SDL_SCANCODE_A]) { double angle; stage_node_get_angle(stage, bobby, &angle); angle += -3.f * t; stage_node_set_angle(stage, bobby, angle); } if (key_state[SDL_SCANCODE_RIGHT] || key_state[SDL_SCANCODE_D]) { double angle; stage_node_get_angle(stage, bobby, &angle); angle += 3.f * t; stage_node_set_angle(stage, bobby, angle); } if (key_state[SDL_SCANCODE_SPACE]) { /* arm bobby */ if (!bobby_keyboard_armed) { bobby_keyboard_armed = 1; bobby_armed = now; } } else if (bobby_keyboard_armed) { /* launch bobby */ Mix_PlayChannel(-1, launch_sfx, 0); state = WHALE_GAME_LAUNCHING; } break; } case WHALE_GAME_LAUNCHING: { double angle; double power = (now - bobby_armed) * (1.0f / 5000); /* convert angle and power into a vector */ stage_node_get_angle(stage, bobby, &angle); angle *= -(M_PI / 180.f); /* to radians */ bobby_vector.x = cos(angle) * power; bobby_vector.y = sin(angle) * power; state++; break; } case WHALE_GAME_FLYING: { float t = ((float)(now - prev)) * .05f; /* scale simulation things according to time passed */ double angle; v2f_t move; aabb_t aabb; move = v2f_mult_scalar(&bobby_vector, t); /* spin the bobby while he's flying */ stage_node_get_angle(stage, bobby, &angle); angle += 4.0f * t; stage_node_set_angle(stage, bobby, angle); /* move him on his trajectory */ /* XXX: this is pretty silly, but it's the interfaces I have at the moment */ stage_node_get_aabb(stage, bobby, &aabb); aabb.min = v2f_add(&aabb.min, &move); aabb.max = v2f_add(&aabb.max, &move); stage_node_set_aabb(stage, bobby, &aabb); /* check if we're inside the crater (crater aabb is .4f wide) */ /* FIXME: I should really add some generic aabb overlap test type stuff to aabb.h */ /* FIXME XXX: yes I know the collision detection is janky/ad-hoc, there was no time! */ if (aabb.min.x > (crater_end_aabb.min.x + .08f) && aabb.max.x < (crater_end_aabb.max.x - .08f) && aabb.min.y < (crater_end_aabb.max.y - .06f)) { state = WHALE_GAME_INSIDE; Mix_PlayChannel(-1, winner_sfx, 0); } else if (aabb.min.y < -1.f) { /* or if we're in the ground */ state = WHALE_GAME_OUTSIDE; Mix_PlayChannel(-1, crash_sfx, 0); stage_node_set_object(stage, bobby, dead_svg); } /* alter trajectory w/gravity */ bobby_vector.y += -.01f * t; break; } case WHALE_GAME_INSIDE: { aabb_t aabb; /* bobby simming inside crater lake */ aabb.min.x = crater_end_aabb.min.x + .1f; aabb.max.x = crater_end_aabb.max.x - .1f; aabb.min.y = crater_end_aabb.max.y - .2f; aabb.max.y = crater_end_aabb.max.y; stage_node_set_aabb(stage, bobby, &aabb); stage_node_set_angle(stage, bobby, cos((double)now * .001) * 10.0); break; } case WHALE_GAME_OUTSIDE: break; case WHALE_GAME_LEAVE: whale_set_context(whale, WHALE_CONTEXT_CREDITS); break; } /* last 20 seconds of song are credits */ if (whale_ticks(whale, WHALE_TICKS_MUSIC) > 206000) whale_set_context(whale, WHALE_CONTEXT_CREDITS); prev = now; }