#define ALLEGRO_UNSTABLE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "globalTypes.h" #include "globalVars.h" #include "renderer.h" #include "console.h" #include "../game/menus.h" #include "../media/graphics.h" #include "../media/music.h" #include "../game/objects.h" #include "world.h" #include "../../include/algif5/algif.h" namespace globals { renderer::renderer(r_backends r_backend/*, int r_samples*/) { // create event handlers r_inputQueue = al_create_event_queue(); must_init(r_inputQueue, "input listener event queue"); // set renderer options c_printer() << "INFO: building display configuration, vsync " + std::to_string(isVsync) + "\n"; switch (r_backend) { case R_WANT_D3D9: c_printer() << "INFO: user requested Direct3D 9 mode\n"; if (os_name == "NT x86" || os_name == "NT amd64") { al_set_new_display_flags(ALLEGRO_DIRECT3D_INTERNAL); } else c_printer() << "ERRO: DirectX is not available on this platform! Ignoring.\n"; break; case R_WANT_OPENGL: c_printer() << "INFO: user requested OpenGL mode\n"; al_set_new_display_flags(ALLEGRO_OPENGL); break; default: c_printer() << "INFO: using the default graphics APIs for this platform\n"; break; } al_set_new_display_option(ALLEGRO_VSYNC, 1 + !isVsync, ALLEGRO_SUGGEST); al_set_new_display_option(ALLEGRO_SAMPLE_BUFFERS, 0, ALLEGRO_SUGGEST); al_set_new_display_option(ALLEGRO_SAMPLES, 1, ALLEGRO_SUGGEST); //al_set_new_bitmap_flags(ALLEGRO_MIN_LINEAR | ALLEGRO_MAG_LINEAR); if (isFullscreen) {al_set_new_display_flags(ALLEGRO_FULLSCREEN_WINDOW);} else if (fscrSupersampling > 1) {c_printer() << "WARN: supersampling preferences ignored, allegro display is not fullscreen\n";} // create new window al_set_new_window_title("TIME FALCON"); r_display = al_create_display(resX * resScale, resY * resScale); must_init(r_display, "display"); r_backbuffer = al_get_target_bitmap(); // configure fullscreen scaling if (isFullscreen) {resScale = (float)al_get_display_height(r_display) / (double)resY * fscrSupersampling;} // force native resolution rendering const float scale_factor_x = ((float)al_get_display_height(r_display)) / (resY * resScale); // keep aspect ratio const float scale_factor_y = ((float)al_get_display_height(r_display)) / (resY * resScale); al_identity_transform(&r_wallpaperTransform); al_scale_transform(&r_wallpaperTransform, 1, 1); al_translate_transform(&r_wallpaperTransform, 0, 0); al_identity_transform(&r_displayTransform); al_scale_transform(&r_displayTransform, scale_factor_x, scale_factor_y); al_translate_transform(&r_displayTransform, ((float)al_get_display_width(r_display) * 0.5f) - (resX * resScale * scale_factor_x * 0.5f), 0); al_use_transform(&r_displayTransform); r_regenerateMaskBuffer(); // set shorthands - keeps the code clean rrx = resX * resScale; rry = resY * resScale; // set up audio engine c_printer() << "INFO: creating audio mixer hierarchy\n"; al_set_default_mixer(a_mixer_master); al_attach_mixer_to_voice(a_mixer_master, a_voice); al_attach_mixer_to_mixer(a_mixer_music, a_mixer_master); al_attach_mixer_to_mixer(a_mixer_sounds, a_mixer_master); al_set_mixer_gain(a_mixer_music, a_vol_music); al_set_mixer_gain(a_mixer_sounds, a_vol_sounds); al_set_default_mixer(a_mixer_sounds); al_reserve_samples(64); // listen for events al_register_event_source(r_inputQueue, al_get_keyboard_event_source()); al_register_event_source(r_inputQueue, al_get_display_event_source(r_display)); // load bitmap graphics & create fonts if (!noVRAM) al_set_new_bitmap_flags(ALLEGRO_VIDEO_BITMAP); // use VRAM instead of RAM r_reloadFonts(); r_reloadBitmaps(); c_printer() << "INFO: renderer setup OK\n"; stateBirthTime = std::chrono::steady_clock::now(); totalBirthTime = std::chrono::steady_clock::now(); } ALLEGRO_BITMAP* r_loadBitmap(std::string file) { ALLEGRO_BITMAP* bitmap = al_load_bitmap(file.c_str()); if(!bitmap) { c_printer() << "couldn't load texture " << file << "\n"; throw std::exception(); } return bitmap; } void renderer::r_reloadBitmaps() { c_printer() << "INFO: loading textures from disk\n"; al_set_new_bitmap_flags(ALLEGRO_MIN_LINEAR | ALLEGRO_MAG_LINEAR); // filtered g_fsWallpaper = r_loadBitmap("resource/graphics/wp_station.jpg"); g_glyphs = r_loadBitmap("resource/graphics/ui_glyphs.png"); g_buttons = r_loadBitmap("resource/graphics/ui_buttons.png"); // eh? al_set_new_bitmap_flags(0); // unfiltered g_missing = r_loadBitmap("resource/graphics/sp_missing2.png"); g_publogo = r_loadBitmap("resource/graphics/ui_canithesis.png"); //g_buttons = r_loadBitmap("resource/graphics/ui_buttons.png"); g_clock = r_loadBitmap("resource/graphics/sp_clock.png"); } void renderer::r_reloadFonts() { c_printer() << "INFO: loading fonts\n"; g_font = al_create_builtin_font(); must_init(g_font, "Allegro 5 Built-In Bitmap Font"); g_conTTF = al_load_ttf_font("resource/typeface/mono.ttf", 9 * resScale, 0); must_init(g_font, "Console Font"); g_medTTF = al_load_ttf_font("resource/typeface/small.ttf", 24 * resScale, 0); must_init(g_medTTF, "Arial Narrow 24px"); g_smallTTF = al_load_ttf_font("resource/typeface/small.ttf", 16 * resScale, 0); must_init(g_smallTTF, "Arial Narrow 16px"); g_smallerTTF = al_load_ttf_font("resource/typeface/small.ttf", 12 * resScale, 0); must_init(g_smallerTTF, "Arial Narrow 12px"); } void renderer::r_regenerateMaskBuffer() { int f = al_get_new_bitmap_flags(); al_set_new_bitmap_flags(ALLEGRO_MIN_LINEAR); // filter when supersampling r_mask = al_create_bitmap(rrx, rry); al_set_new_bitmap_flags(f); } void renderer::a_resetMixerVolumes() { al_set_mixer_gain(a_mixer_music, a_vol_music); al_set_mixer_gain(a_mixer_sounds, a_vol_sounds); } renderer::~renderer() { c_printer() << "INFO: exited renderer loop, cleaning up\n"; delete r_currentBackground; al_destroy_bitmap(g_publogo); al_destroy_font(g_font); al_destroy_font(g_smallTTF); al_destroy_font(g_smallerTTF); al_destroy_event_queue(r_inputQueue); al_destroy_display(r_display); if (musicStream != nullptr) { c_printer() << "INFO: draining audio buffers\n"; al_detach_audio_stream(musicStream); //al_drain_audio_stream(musicTrack); // probably doesn't do anything because of detach c_printer() << "INFO: clearing memory used by allegro_audio\n"; al_destroy_audio_stream(musicStream); } al_uninstall_audio(); al_shutdown_video_addon(); c_printer() << "INFO: terminating\n"; } void renderer::r_startLoop () { // main thread loop #define KEY_SEEN 1 #define KEY_RELEASED 2 unsigned char key[ALLEGRO_KEY_MAX]; memset(key, 0, sizeof(key)); r_currentBackground = new graphicsVariants::backgrounds::g_BackgroundStars3D(); const double wrs = resScale; // used by r_changeResolution() while(1) { if(done) break; ALLEGRO_EVENT event; auto now = std::chrono::steady_clock::now(); const double delta = std::chrono::duration(now - lastUpdate).count(); if (isFramerateUnlocked || isVsync) { framerate = delta / 0.016666667; speedScale = 60.0 / framerate; durationScale = framerate / 60.0; } if(redraw || isVsync || isFramerateUnlocked || (!isFramerateUnlocked && delta >= (1 / framerate))) { // handle inputs for(int i = 0; i < ALLEGRO_KEY_MAX; i++) key[i] &= KEY_SEEN; menuButtons = 0; /// The events in the queue need to be preserved for a second /// pass by the world simulation in it's own queue, otherwise we risk /// dropping inputs. This is less likely to happen in single-thread mode /// but in multi-threaded mode where the W ticks and R frames are out /// of sync it will become a problem very quickly. /// An easy way to implement this would be to copy all unprocessed /// bits in the input int to a buffer (w_buttons = w_buttons | g) and /// then clear those bits once they have been read by o_player. while (!al_event_queue_is_empty(r_inputQueue)){ // we can move this to another thread to keep a consistent input rate al_get_next_event(r_inputQueue, &event); // even when we drop frames - consider switch(event.type) { case ALLEGRO_EVENT_DISPLAY_CLOSE: done = true; break; case ALLEGRO_EVENT_KEY_DOWN: if (!console) { key[event.keyboard.keycode] = KEY_SEEN | KEY_RELEASED; if(event.keyboard.keycode == ALLEGRO_KEY_SPACE || event.keyboard.keycode == ALLEGRO_KEY_C || event.keyboard.keycode == ALLEGRO_KEY_L) menuButtons = menuButtons + 16; if(event.keyboard.keycode == ALLEGRO_KEY_SEMICOLON || event.keyboard.keycode == ALLEGRO_KEY_X) menuButtons = menuButtons + 32; } if (event.keyboard.keycode == ALLEGRO_KEY_TILDE && debug) console = !console; break; case ALLEGRO_EVENT_KEY_UP: key[event.keyboard.keycode] &= KEY_RELEASED; break; case ALLEGRO_EVENT_KEY_CHAR: if (!console) { if(event.keyboard.keycode == ALLEGRO_KEY_UP) menuButtons = menuButtons + 1; if(event.keyboard.keycode == ALLEGRO_KEY_DOWN) menuButtons = menuButtons + 2; if(event.keyboard.keycode == ALLEGRO_KEY_LEFT) menuButtons = menuButtons + 4; if(event.keyboard.keycode == ALLEGRO_KEY_RIGHT) menuButtons = menuButtons + 8; } else { switch (event.keyboard.keycode) { case ALLEGRO_KEY_TILDE: break; // no case ALLEGRO_KEY_TAB: c_autocomplete(); break; case ALLEGRO_KEY_ENTER: c_executeInput(); break; case ALLEGRO_KEY_BACKSPACE: //if (strlen(c_input) > 0) c_input[strlen(c_input)-1] = '\0'; // why is there a difference between ' and " ? does anything in this language make sense??? if (!c_input.empty()) c_input.pop_back(); break; default: ALLEGRO_USTR* unicodeStr = al_ustr_new(""); al_ustr_append_chr(unicodeStr, event.keyboard.unichar); c_input += al_cstr(unicodeStr); break; } } } } // todo: figure out remappable keys, controller support uint16_t g = 0; // buffer the inputs to avoid microscopic race conditions when w_ is threaded if(key[ALLEGRO_KEY_UP] || key[ALLEGRO_KEY_W]) g = g | DUP; if(key[ALLEGRO_KEY_DOWN] || key[ALLEGRO_KEY_S]) g = g | DDOWN; if(key[ALLEGRO_KEY_LEFT] || key[ALLEGRO_KEY_A]) g = g | DLEFT; if(key[ALLEGRO_KEY_RIGHT] || key[ALLEGRO_KEY_D]) g = g | DRIGHT; if(key[ALLEGRO_KEY_SPACE] || key[ALLEGRO_KEY_C] || key[ALLEGRO_KEY_L]) g = g | CROSS; if(key[ALLEGRO_KEY_SEMICOLON] || key[ALLEGRO_KEY_X]) g = g | CIRCLE; if(key[ALLEGRO_KEY_QUOTE] || key[ALLEGRO_KEY_Z]) g = g | SQUARE; if(key[ALLEGRO_KEY_LSHIFT] || key[ALLEGRO_KEY_RSHIFT]) g = g | TRIANGLE; if(key[ALLEGRO_KEY_LCTRL] || key[ALLEGRO_KEY_RCTRL]) g = g | LBUMP; if(key[ALLEGRO_KEY_ALT] || key[ALLEGRO_KEY_MENU]) g = g | RBUMP; if(key[ALLEGRO_KEY_ESCAPE]) g = g | MENU; if(key[ALLEGRO_KEY_TAB]) g = g | SELECT; gameButtons = g; // clear the backbuffer al_clear_to_color(al_map_rgb(0, 0, 0)); // render wallpaper if (isFullscreen && !noWallpaper) { al_use_transform(&r_wallpaperTransform); al_draw_scaled_bitmap(g_fsWallpaper, 0, 0, 2560, 1440, 0, 0, al_get_display_width(r_display), al_get_display_height(r_display), 0); //al_draw_filled_rectangle(0, 0, 2000, 2000, al_map_rgba_f(0.5, 0.2, 0.3, 1)); // test al_use_transform(&r_displayTransform); } // render the screen //if (isFullscreen) r_clearCanvasOnly(); if (isFullscreen) { if (r_mask == nullptr || al_get_bitmap_width(r_mask) != rrx || al_get_bitmap_height(r_mask) != rry) r_regenerateMaskBuffer(); al_set_target_bitmap(r_mask); al_clear_to_color(al_map_rgb(0, 0, 0)); } if (stateFrame == 0) { // snap beginning of state to the time the frame is rendered stateBirthTime = std::chrono::steady_clock::now(); // fixes some animation bugs especially on slower PCs stateTick = 0; } if (state == INTRO) r_drawIntroSequence(); else if (state == TITLE) r_drawTitleSequence(); else if (state == TRANSITION) r_drawTransitionSequence(); else if (state == LOADING) r_drawLoadingSequence(); else if (state == INGAME_PLAYING) { r_drawWorld(simulation); r_drawWorldUI(simulation); } if (isFullscreen) {al_set_target_bitmap(r_backbuffer);al_draw_bitmap(r_mask, 0, 0, 0);} if (debug) { const char* d_modeString = "null"; switch(state) { case INTRO: d_modeString = "INTRO";break; case TITLE: d_modeString = "TITLE";break; case INGAME_PAUSED: d_modeString = "PAUSED";break; case INGAME_PLAYING: d_modeString = "PLAYING";break; case INTERMISSION: d_modeString = "INTERMISSION";break; case LOADING: d_modeString = "LOADING";break; case TRANSITION: d_modeString = "TRANSITION";break; } std::string f = std::to_string((1.0 / delta) + 1); f = f.substr(0, f.find(".")); al_draw_text(g_font, al_map_rgb(255, 255, 255), 0, 0, ALLEGRO_ALIGN_LEFT, d_modeString); al_draw_text(g_font, al_map_rgb(255, 255, 255), rrx, 0, ALLEGRO_ALIGN_RIGHT, (std::to_string(delta) + " LAT").c_str()); // time to render al_draw_text(g_font, al_map_rgb(255, 255, 255), rrx, 10, ALLEGRO_ALIGN_RIGHT, (f + " FPS").c_str());// frame rate if (isVsync) al_draw_text(g_font, al_map_rgb(255, 255, 255), rrx, 20, ALLEGRO_ALIGN_RIGHT, "VSYNC ON"); else al_draw_text(g_font, al_map_rgb(255, 255, 255), rrx, 20, ALLEGRO_ALIGN_RIGHT, (std::to_string(trunc(framerate)).substr(0, std::to_string(trunc(framerate)).find(".")) + " TGT").c_str()); // target frame rate if (isVsync) al_draw_text(g_font, al_map_rgb(255, 255, 255), rrx, 30, ALLEGRO_ALIGN_RIGHT, (std::to_string(trunc(totalTicks)).substr(0, std::to_string(trunc(totalTicks)).find(".")) + " UPT").c_str());// todo https://liballeg.org/a5docs/trunk/monitor.html#al_get_monitor_info else al_draw_text(g_font, al_map_rgb(255, 255, 255), rrx, 30, ALLEGRO_ALIGN_RIGHT, (std::to_string(trunc(totalTicks)).substr(0, std::to_string(trunc(totalTicks)).find(".")) + " UPT").c_str()); // frames since start al_draw_text(g_font, al_map_rgb(255, 255, 255), 0, rry - 10, ALLEGRO_ALIGN_LEFT, ("BUT: " + std::bitset<16>(gameButtons).to_string()).c_str()); al_draw_text(g_font, al_map_rgb(255, 255, 255), 0, rry - 20, ALLEGRO_ALIGN_LEFT, ("OSN: " + os_name).c_str()); al_draw_text(g_font, al_map_rgb(255, 255, 255), 0, rry - 30, ALLEGRO_ALIGN_LEFT, ("DPC: " + os_display_proc).c_str()); al_draw_text(g_font, al_map_rgb(255, 255, 255), 0, rry - 40, ALLEGRO_ALIGN_LEFT, ("RRY: " + std::to_string(trunc(resY * resScale)).substr(0, std::to_string(trunc(resY * resScale)).find("."))).c_str()); al_draw_text(g_font, al_map_rgb(255, 255, 255), 0, rry - 50, ALLEGRO_ALIGN_LEFT, ("RRX: " + std::to_string(trunc(resX * resScale)).substr(0, std::to_string(trunc(resX * resScale)).find("."))).c_str()); al_draw_text(g_font, al_map_rgb(255, 255, 255), 0, rry - 60, ALLEGRO_ALIGN_LEFT, ("FUL: " + std::to_string(isFullscreen) + "," + std::to_string((int)(fscrSupersampling * 100)) + "%").c_str()); if (console) { constexpr float consoleWindowOpacity = 0.7; int flh = al_get_font_line_height(g_conTTF); int charw = al_get_glyph_width(g_conTTF, '$'); //int th; al_get_text_dimensions(g_conTTF, c_history.c_str(), nullptr, nullptr, nullptr, &th); // why can't you just like return an array or something al_draw_filled_rectangle(0, rry-c_length*flh-(flh*4), rrx, rry, al_map_rgba_f(0, 0, 0.2, consoleWindowOpacity)); al_draw_multiline_text(g_conTTF, al_map_rgb(255, 200, 20), 10*resScale, rry-c_length*flh-(flh*2), rrx-10*resScale, flh, 0, c_history.c_str()); al_draw_text(g_conTTF, al_map_rgb(100, 255, 30), 10*resScale, rry-flh-flh*0.5-2*resScale, 0, (std::string("$ ") + c_input).c_str()); if (wrapInt(totalTicks, 60) < 30) // draw blinking caret al_draw_line(10*resScale + c_input.length()*charw + charw*2 + resScale, rry - flh - flh * 0.5 - 2 * resScale, 10*resScale + c_input.length()*charw + charw*2 + resScale, (rry - flh - flh * 0.5 - 2 * resScale) + flh, al_map_rgb(100, 255, 30), 1); } } al_flip_display(); redraw = false; lastUpdate = now; stateFrame++; totalFrames++; stateTick = std::chrono::duration(now - stateBirthTime).count() / 0.016666667; totalTicks = std::chrono::duration(now - stateBirthTime).count() / 0.016666667; } std::vector::iterator sqe = soundQueue.end(); // process sound for (soundEffect s : soundQueue) { al_play_sample(al_load_sample(s.file.c_str()), s.gain, s.pan, s.speed, s.mode, nullptr); } soundQueue.erase(soundQueue.begin(), sqe); s_calculateMusicFadeVolume(); } } void renderer::r_drawIntroSequence() { // select correct animation frame int i = stateTick - ((stateTick / (60)) * 60); float a = 0; if (stateTick == 0) musicStream = (new musicTracks::m_Title())->playFile(a_mixer_music); if (stateTick < 60) { // fade in a = 1.0f - (stateTick * 0.05f); } if (stateTick >= 60) { i = 59; } if (stateTick >= 180) { // 3 seconds before fade a = (stateTick - 180) * 0.05f; } if (stateTick >= 240 || noPubIntro) { // 4 seconds before cut r_resetStateBirthTime(); menuData::selection = 0; menuData::menuCurrent = new menuData::menus::menuRoot; state = TITLE; } if (a < 0) {a = 0;} if (a > 1) {a = 1;} // draw al_draw_scaled_bitmap(g_publogo, 0, 0, 1024, 1024, // static //al_draw_scaled_bitmap(surf, 200 * (i / (4) /*15 FPS*/), 0, 200, 200, // animated ((rrx) / 2) - 100 * resScale, ((rry) / 2) - 100 * resScale, 200 * resScale, 200 * resScale, 0); al_draw_text(g_smallerTTF, al_map_rgb(255, 255, 255), (rrx) / 2, ((resY / 2 + 100) * resScale), ALLEGRO_ALIGN_CENTRE, "PRESENTS"); al_draw_filled_rectangle(0, 0, rrx, rry, al_map_rgba_f(0, 0, 0, a)); if (debug) { if (isVsync) al_draw_text(g_font, al_map_rgb(255, 255, 255), 0, 10, ALLEGRO_ALIGN_LEFT, "TIC: N/A"); else al_draw_text(g_font, al_map_rgb(255, 255, 255), 0, 10, ALLEGRO_ALIGN_LEFT, ("TIC: " + std::to_string(trunc(stateTick)).substr(0, std::to_string(trunc(stateTick)).find("."))).c_str()); al_draw_text(g_font, al_map_rgb(255, 255, 255), 0, 20, ALLEGRO_ALIGN_LEFT, ("ALP: " + std::to_string(trunc(a * 10) / 10).substr(0, 4)).c_str()); } } /// I apologize in advance to anyone who will try to read all of this void renderer::r_drawTitleSequence() { if (musicStream == nullptr) // ensure title theme still plays if we skip the intro sequence musicStream = (new musicTracks::m_Title())->playFile(a_mixer_music); // process inputs if (std::bitset<8>(menuButtons)[0] // up - acts weird af when framerate is very low?? && menuData::selection > 0) { menuData::selection--; } if (std::bitset<8>(menuButtons)[1] // down && menuData::selection < menuData::menuSize - 1) { menuData::selection++; } if (std::bitset<8>(menuButtons)[4] && menuData::menuSize > 0) { // confirm // todo: execute function pointers on menuItemButton press if (menuData::menuCurrent->content[menuData::selection]->type == menuData::menus::BUTTON) { const auto b = (menuData::menus::menuItemButton*)menuData::menuCurrent->content[menuData::selection]; if (!b->silent) { if (!b->frozen && !b->isCancel) al_play_sample(al_load_sample("resource/sound/ui_confirm.ogg"), 0.7, 0, 1, ALLEGRO_PLAYMODE_ONCE, NULL); else if (!b->frozen && b->isCancel) al_play_sample(al_load_sample("resource/sound/ui_back.ogg"), 1, 0, 1.3, ALLEGRO_PLAYMODE_ONCE, NULL); else al_play_sample(al_load_sample("resource/sound/ui_nope.ogg"), 1, 0, 1, ALLEGRO_PLAYMODE_ONCE, NULL); } if (b->actionmid != 255 && !b->frozen) { const int m = b->actionmid; delete menuData::menuCurrent; menuData::menuCurrent = menuData::menus::initializeMenu(m); menuData::selection = 0; } else if (b->actionptr != nullptr && !b->frozen) { b->actionptr(); if (b->suicideOnAction) delete menuData::menuCurrent; // CAREFUL! } } } if (std::bitset<8>(menuButtons)[5]) { // cancel if (menuData::menuCurrent->backactionptr != nullptr) { menuData::menuCurrent->backactionptr(); al_play_sample(al_load_sample("resource/sound/ui_back.ogg"), 1, 0, 1.3, ALLEGRO_PLAYMODE_ONCE, NULL); } else if (menuData::menuCurrent->backmid != 255) { menuData::selection = menuData::menuCurrent->backind; const int m = menuData::menuCurrent->backmid; delete menuData::menuCurrent; menuData::menuCurrent = menuData::menus::initializeMenu(m); al_play_sample(al_load_sample("resource/sound/ui_back.ogg"), 1, 0, 1.3, ALLEGRO_PLAYMODE_ONCE, NULL); } } if (std::bitset<8>(menuButtons)[2]) { // left if (menuData::menuCurrent->content[menuData::selection]->type == menuData::menus::DIALER) { al_play_sample(al_load_sample("resource/sound/ui_select.ogg"), 0.7, 0, 1, ALLEGRO_PLAYMODE_ONCE, NULL); const menuData::menus::menuItemDial* gptr = ((menuData::menus::menuItemDial*)menuData::menuCurrent->content[menuData::selection]); if (gptr->value - gptr->increment > gptr->lowerlimit) gptr->value = gptr->value - gptr->increment; else gptr->value = gptr->lowerlimit; } } if (std::bitset<8>(menuButtons)[3]) { // right if (menuData::menuCurrent->content[menuData::selection]->type == menuData::menus::DIALER) { al_play_sample(al_load_sample("resource/sound/ui_select.ogg"), 0.7, 0, 1, ALLEGRO_PLAYMODE_ONCE, NULL); const menuData::menus::menuItemDial* gptr = ((menuData::menus::menuItemDial*)menuData::menuCurrent->content[menuData::selection]); if (gptr->value + gptr->increment < gptr->limit) gptr->value = gptr->value + gptr->increment; else gptr->value = gptr->limit; } } menuData::menuCurrent->update(); // draw background al_draw_scaled_bitmap(r_currentBackground->getFrame(stateTick, preferGif), 0, 0, rrx, rry, 0, 0, rrx, rry, 0); // draw background clock al_set_blender(ALLEGRO_ADD, ALLEGRO_ONE, ALLEGRO_ONE); const float clcksc = 1.5; const float clcksp = 1.4; al_draw_tinted_scaled_rotated_bitmap_region(g_clock, 0, 0, 512, 512, al_map_rgba_f(.5, .5, .5, 1), 256, 256, 180*resScale, 320*resScale, clcksc*resScale, clcksc*resScale, 0.005*(wrapInt(stateTick * clcksp, 72000)), 0); // clockface al_draw_tinted_scaled_rotated_bitmap_region(g_clock, 512, 0, 512, 512, al_map_rgba_f(.5, .5, .5, 1), 256, 256, 180*resScale, 320*resScale, clcksc*resScale, clcksc*resScale, 0.03*(wrapInt(stateTick * clcksp, 12000)), 0); // minutes - WRONG! WRONG WRONG WRONG WRONG! al_draw_tinted_scaled_rotated_bitmap_region(g_clock, 1024, 0, 512, 512, al_map_rgba_f(.5, .5, .5, 1), 256, 256, 180*resScale, 320*resScale, clcksc*resScale*0.6, clcksc*resScale*0.6, 0.01*(wrapInt(stateTick * clcksp, 36000)), 0); // hours al_set_blender(ALLEGRO_ADD, ALLEGRO_ONE, ALLEGRO_INVERSE_ALPHA); menuData::menuSize = menuData::menuCurrent->content.size(); // menuSize kept for legacy reasons, might strip out later if (menuData::menuSize > 0) { // prevent crash when menu is empty if (menuData::selection >= menuData::menuSize) menuData::selection = menuData::menuSize - 1; if (menuData::menuCurrent->content[menuData::selection]->type == menuData::menus::SPRITE || menuData::menuCurrent->content[menuData::selection]->type == menuData::menus::TEXT_FIELD || menuData::menuCurrent->content[menuData::selection]->type == menuData::menus::TEXT_FIELD_MINI) { if (menuData::selection < menuData::menuSize - 1) menuData::selection++; else menuData::selection--; // todo: make it smarter, also take current input into account please } if (lastMenuSelection != menuData::selection && (std::bitset<8>(menuButtons)[0] || std::bitset<8>(menuButtons)[1] || std::bitset<8>(menuButtons)[2] || std::bitset<8>(menuButtons)[3])) al_play_sample(al_load_sample("resource/sound/ui_select.ogg"), 1, 0, 1, ALLEGRO_PLAYMODE_ONCE, NULL); lastMenuSelection = menuData::selection; // draw menu uint8_t si = 0; // used to see if current item is menuData::selection for (const menuData::menus::menuItemGeneric* i : menuData::menuCurrent->content) { if (i->type == menuData::menus::SPRITE) { const menuData::menus::menuItemPicture* gptr = ((menuData::menus::menuItemPicture*)i); // shorthand al_draw_scaled_bitmap(gptr->sprite, 0, 0, gptr->sw, gptr->sh, gptr->x*resScale, gptr->y*resScale, gptr->w*resScale, gptr->h*resScale, 0); } else if (i->type == menuData::menus::BUTTON) { // THIS MATH IS GAY but i'll probably rewrite it on the next art pass so ¯\_(ツ)_/¯ const menuData::menus::menuItemButton* gptr = ((menuData::menus::menuItemButton*)i); al_draw_scaled_bitmap(g_buttons, 0, 65, 54-1, 67, gptr->x*resScale, gptr->y*resScale, gptr->h*resScale*0.79411765, gptr->h*resScale, 0); // left - float is 54/68 uint8_t u = 55; if (si == menuData::selection) u = u+55; al_draw_scaled_bitmap(g_buttons, u+1, 65, 54-1, 70, (gptr->x+gptr->w-gptr->h*0.79411765)*resScale, (gptr->y)*resScale, gptr->h*resScale*0.79411765, (gptr->h+2)*resScale, 0); // right al_draw_scaled_bitmap(g_buttons, 0, 0, 1, 63, (gptr->x+gptr->h*0.79411765)*resScale, (gptr->y+3)*resScale, (gptr->w - gptr->h*0.79411765*2)*resScale, (gptr->h-6)*resScale, 0); // middle ALLEGRO_COLOR tc; // text color if (gptr->frozen && menuData::selection == si) tc = al_map_rgb(140, 132, 100); else if (gptr->frozen) tc = al_map_rgb(110, 110, 110); else if (menuData::selection == si) tc = al_map_rgb(255, 200, 40); else tc = al_map_rgb(217, 199, 100); al_draw_text(g_medTTF, tc, (gptr->x+gptr->w/2)*resScale, ((gptr->y+gptr->h/2)*resScale - al_get_font_line_height(g_medTTF)/2), ALLEGRO_ALIGN_CENTRE, gptr->label.c_str()); } else if (i->type == menuData::menus::TEXT_FIELD) { const menuData::menus::menuItemText* gptr = ((menuData::menus::menuItemText*)i); al_draw_multiline_text(g_medTTF, al_map_rgb(255, 255, 255), gptr->x*resScale, gptr->y*resScale, rrx-30*resScale, al_get_font_line_height(g_medTTF), ALLEGRO_ALIGN_CENTRE, gptr->text.c_str()); } else if (i->type == menuData::menus::TEXT_FIELD_MINI) { const menuData::menus::menuItemText* gptr = ((menuData::menus::menuItemText*)i); al_draw_multiline_text(g_smallTTF, al_map_rgb(255, 255, 255), gptr->x*resScale, gptr->y*resScale, rrx-30*resScale, al_get_font_line_height(g_smallTTF), ALLEGRO_ALIGN_CENTRE, gptr->text.c_str()); } else if (i->type == menuData::menus::DIALER) { const menuData::menus::menuItemDial* gptr = ((menuData::menus::menuItemDial*)i); al_draw_scaled_bitmap(g_buttons, 0, 65, 54-1, 67, gptr->x*resScale, gptr->y*resScale, gptr->h*resScale*0.79411765, gptr->h*resScale, 0); // 54/68 uint8_t u = 55; if (si == menuData::selection) u = u+55; al_draw_scaled_bitmap(g_buttons, u+1, 65, 54-1, 70, (gptr->x+gptr->w-gptr->h*0.79411765)*resScale, (gptr->y)*resScale, gptr->h*resScale*0.79411765, (gptr->h+2)*resScale, 0); al_draw_scaled_bitmap(g_buttons, 0, 0, 1, 63, (gptr->x+gptr->h*0.79411765)*resScale, (gptr->y+3)*resScale, (gptr->w - gptr->h*0.79411765*2)*resScale, (gptr->h-6)*resScale, 0); ALLEGRO_COLOR tc; // text color if (menuData::selection == si) tc = al_map_rgb(255, 200, 40); else tc = al_map_rgb(217, 199, 100); std::string displayValue; if (gptr->indexMode) { if (gptr->value >= gptr->displayValuesOffset) { const int dvi = gptr->value - gptr->displayValuesOffset; displayValue = gptr->displayValues[dvi]; } } else displayValue = std::to_string(gptr->value); al_draw_text(g_medTTF, tc, (gptr->x+20)*resScale, ((gptr->y+gptr->h/2)*resScale - al_get_font_line_height(g_medTTF)/2), ALLEGRO_ALIGN_LEFT, gptr->label.c_str()); al_draw_text(g_medTTF, tc, (gptr->w+35)*resScale, ((gptr->y+gptr->h/2)*resScale - al_get_font_line_height(g_medTTF)/2), ALLEGRO_ALIGN_RIGHT, displayValue.c_str()); } si++; } } switch (menuData::menuCurrent->promptType) { case 0: break; case 1: al_draw_scaled_bitmap(g_glyphs, 128, 0, 64, 64, 32*resScale, 605*resScale, 32*resScale, 32*resScale, 0); al_draw_text(g_medTTF, al_map_rgb(255, 255, 255), 72*resScale, 608*resScale, ALLEGRO_ALIGN_LEFT, "Confirm"); break; case 2: al_draw_scaled_bitmap(g_glyphs, 128, 0, 64, 64, 32*resScale, 605*resScale, 32*resScale, 32*resScale, 0); al_draw_text(g_medTTF, al_map_rgb(255, 255, 255), 72*resScale, 608*resScale, ALLEGRO_ALIGN_LEFT, "Confirm"); al_draw_scaled_bitmap(g_glyphs, 64, 0, 64, 64, 156*resScale, 605*resScale, 32*resScale, 32*resScale, 0); al_draw_text(g_medTTF, al_map_rgb(255, 255, 255), 196*resScale, 608*resScale, ALLEGRO_ALIGN_LEFT, "Back"); break; case 3: c_printer() << "WARN: menu \"" << menuData::menuCurrent->name << "\" drawn by renderer had unimplemented button prompt type, falling back to 4\n"; menuData::menuCurrent->promptType = 4; break; case 4: al_draw_scaled_bitmap(g_glyphs, 128, 192, 64, 64, 32*resScale, 605*resScale, 32*resScale, 32*resScale, 0); al_draw_scaled_bitmap(g_glyphs, 192, 192, 64, 64, 68*resScale, 605*resScale, 32*resScale, 32*resScale, 0); al_draw_text(g_medTTF, al_map_rgb(255, 255, 255), 108*resScale, 608*resScale, ALLEGRO_ALIGN_LEFT, "Edit"); al_draw_scaled_bitmap(g_glyphs, 64, 0, 64, 64, 156*resScale, 605*resScale, 32*resScale, 32*resScale, 0); al_draw_text(g_medTTF, al_map_rgb(255, 255, 255), 196*resScale, 608*resScale, ALLEGRO_ALIGN_LEFT, "Back"); break; default: c_printer() << "WARN: menu \"" << menuData::menuCurrent->name << "\" drawn by renderer had invalid button prompt type, resetting to 0\n"; menuData::menuCurrent->promptType = 0; break; } if (stateTick < 60) { // fade in al_draw_filled_rectangle(0, 0, rrx, rry, al_map_rgba_f(0, 0, 0, 1.0f - (stateTick * 0.05f))); } if (debug) { al_draw_text(g_smallerTTF, al_map_rgb(180, 180, 180), 30 * resScale, 0 * resScale, ALLEGRO_ALIGN_LEFT, (std::bitset<8>(menuButtons).to_string()).c_str()); al_draw_text(g_smallerTTF, al_map_rgb(180, 180, 180), 30 * resScale, 10 * resScale, ALLEGRO_ALIGN_LEFT, (std::bitset<8>(menuData::selection).to_string()).c_str()); al_draw_text(g_smallerTTF, al_map_rgb(180, 180, 180), 30 * resScale, 20 * resScale, ALLEGRO_ALIGN_LEFT, (std::bitset<8>(menuData::menuSize).to_string()).c_str()); al_draw_text(g_smallerTTF, al_map_rgb(180, 180, 180), 30 * resScale, 30 * resScale, ALLEGRO_ALIGN_LEFT, menuData::menuCurrent->name.c_str()); al_draw_text(g_font, al_map_rgb(255, 255, 255), 0, 10, ALLEGRO_ALIGN_LEFT, ("TIC: " + std::to_string(trunc(stateTick)).substr(0, std::to_string(trunc(stateTick)).find("."))).c_str()); } } void renderer::r_drawTransitionSequence() { const float tf = std::chrono::duration(std::chrono::steady_clock::now() - transitionBirthTime).count() / 0.016666667; if (musicTrack != nullptr) musicTrack = nullptr; if (musicStream != nullptr) {al_destroy_audio_stream(musicStream);musicStream = nullptr;} if (tf >= 240) { r_resetStateBirthTime(); state = LOADING; } else if (tf < 180) { const uint64_t f = stateTick + tf*tf*0.0455; // draw background al_draw_scaled_bitmap(r_currentBackground->getFrame(f, preferGif), 0, 0, rrx, rry, 0, 0, rrx, rry, 0); // draw background clock al_set_blender(ALLEGRO_ADD, ALLEGRO_ONE, ALLEGRO_ONE); constexpr float clcksc = 1.5; constexpr float clcksp = 1.4; al_draw_tinted_scaled_rotated_bitmap_region(g_clock, 0, 0, 512, 512, al_map_rgba_f(.5, .5, .5, 1), 256, 256, 180*resScale, 320*resScale, clcksc*resScale, clcksc*resScale, 0.005*(wrapFloat(f * clcksp, 72000)), 0); // clockface al_draw_tinted_scaled_rotated_bitmap_region(g_clock, 512, 0, 512, 512, al_map_rgba_f(.5, .5, .5, 1), 256, 256, 180*resScale, 320*resScale, clcksc*resScale, clcksc*resScale, 0.03*(wrapFloat(f * clcksp, 12000)), 0); // minutes - WRONG! WRONG WRONG WRONG WRONG! al_draw_tinted_scaled_rotated_bitmap_region(g_clock, 1024, 0, 512, 512, al_map_rgba_f(.5, .5, .5, 1), 256, 256, 180*resScale, 320*resScale, clcksc*resScale*0.6, clcksc*resScale*0.6, 0.01*(wrapFloat(f * clcksp, 36000)), 0); // hours al_set_blender(ALLEGRO_ADD, ALLEGRO_ONE, ALLEGRO_INVERSE_ALPHA); // draw fade out const float fosp = 0.1 * tf; al_draw_filled_rectangle(0, 0, rrx, rry, al_map_rgba_f( 0.01 * fosp * fosp, 0.01 * fosp * fosp, 0.01 * fosp * fosp, 0.01 * fosp * fosp )); } else { // draw fade out const float fosp = 1.0 - 0.05*(tf - 180); al_draw_filled_rectangle(0, 0, rrx, rry, al_map_rgba_f( fosp, fosp, fosp, fosp )); } if (debug) { if (isVsync) al_draw_text(g_font, al_map_rgb(255, 255, 255), 0, 10, ALLEGRO_ALIGN_LEFT, "TIC: N/A"); else al_draw_text(g_font, al_map_rgb(255, 255, 255), 0, 10, ALLEGRO_ALIGN_LEFT, ("TIC: " + std::to_string(trunc(stateTick)).substr(0, std::to_string(trunc(stateTick)).find("."))).c_str()); } } void renderer::r_drawLoadingSequence() { state = INGAME_PLAYING; // TESTING simulation->w_frozen = false; return; al_draw_text(g_medTTF, al_map_rgb(255, 255, 255), rrx/2, rry/2 - al_get_font_line_height(g_medTTF)/2, ALLEGRO_ALIGN_CENTER, "LOADING"); float a = 0; if (stateTick < 30) a = 1.0f - (stateTick * 0.1f); al_draw_filled_rectangle(0, 0, rrx, rry, al_map_rgba_f(0, 0, 0, a)); if (debug) { if (isVsync) al_draw_text(g_font, al_map_rgb(255, 255, 255), 0, 10, ALLEGRO_ALIGN_LEFT, "TIC: N/A"); else al_draw_text(g_font, al_map_rgb(255, 255, 255), 0, 10, ALLEGRO_ALIGN_LEFT, ("TIC: " + std::to_string(std::trunc(stateTick)).substr(0, std::to_string(trunc(stateTick)).find("."))).c_str()); } } void renderer::r_drawWorld(world* w) { // single-threaded mode int w_ticksPerFrame; if (debug) w_ticksPerFrame = w->w_delta / (1.0f / w->w_tickrate); w->w_singleThreaded_updateAll(); // todo: implement (purely cosmetic) animation keyframing system with std::chrono { int i = 0; while(!w->w_content.empty() // prevent out-of-bounds crash && i <= w->w_content.size() - 1) { // get every object int i2 = 0; while(i2 <= w->w_content[i]->sprites.size() - 1) { // get all sprites for an object objects::o_worldObjectGeneric* o = w->w_content[i]; objects::o_sprite spr = w->w_content[i]->sprites[i2]; if (w->w_content[i]->sprites[i2].spritePtr == nullptr) spr = objects::o_sprite {g_missing, w->w_content[i]->sprites[i2].spriteBounds}; al_draw_scaled_rotated_bitmap(spr.spritePtr, // draw the sprite spr.spriteBounds.pivotX*al_get_bitmap_width(spr.spritePtr), spr.spriteBounds.pivotY*al_get_bitmap_height(spr.spritePtr), (spr.spriteBounds.posX+o->transform.posX)*resScale, (spr.spriteBounds.posY+o->transform.posY)*resScale, (al_get_bitmap_width(spr.spritePtr)/spr.spriteBounds.sizeX)*resScale, (al_get_bitmap_height(spr.spritePtr)/spr.spriteBounds.sizeY)*resScale, spr.spriteBounds.rotation+o->transform.rotation,0); i2++; } i++; } } // draw flashbang effect const float r_flashbangTimer = std::chrono::duration(std::chrono::steady_clock::now() - w->w_hudFlashbangTime).count(); float r_flashbangIntensity = 0; if (w->w_hudFlashbangDuration > r_flashbangTimer) { r_flashbangIntensity = w->w_hudFlashbangDuration - r_flashbangTimer; } al_draw_filled_rectangle(0, 0, rrx, rry, al_map_rgba_f( r_flashbangIntensity, r_flashbangIntensity, r_flashbangIntensity, r_flashbangIntensity )); if (r_drawHitboxes) { int i = 0; while(!w->w_content.empty() && i <= w->w_content.size() - 1) { int i2 = 0; while(i2 <= w->w_content[i]->hitboxes.size() - 1) { objects::o_bounds* ob = &w->w_content[i]->hitboxes[i2]; objects::o_transform* ot = &w->w_content[i]->transform; //al_draw_rectangle( // ob->posX+ot->posX-(ob->pivotX*ob->sizeX), ob->posY+ot->posY-(ob->pivotY*ob->sizeY), // ob->posX+ob->sizeX+ot->posX-(ob->pivotY*ob->sizeY), ob->posY+ob->sizeY+ot->posY-(ob->pivotY*ob->sizeY), // al_map_rgb(255, 0, 80), 2); struct verts { float x; float y; }; verts v[4]; v[0].x = 50; v[0].y = 50; //todo v[1].x = 120; v[1].y = 50; v[2].x = 50; v[2].y = 120; v[3].x = 50; v[3].y = 120; al_draw_polygon((float*)v, 4, ALLEGRO_LINE_JOIN_BEVEL, al_map_rgb(255, 0, 80), 2, 30); i2++; } i++; } } if (debug) { al_draw_text(g_font, al_map_rgb(255, 255, 255), 0, 10, ALLEGRO_ALIGN_LEFT, ("TIC: " + std::to_string(std::trunc(w->w_totalTicks)).substr(0, std::to_string(trunc(w->w_totalTicks)).find("."))).c_str()); al_draw_text(g_font, al_map_rgb(255, 255, 255), 0, 20, ALLEGRO_ALIGN_LEFT, ("TPS: " + std::to_string(std::trunc(w->w_tickrate)).substr(0, std::to_string(trunc(w->w_tickrate)).find("."))).c_str()); al_draw_text(g_font, al_map_rgb(255, 255, 255), 0, 30, ALLEGRO_ALIGN_LEFT, ("TPF: " + std::to_string(w_ticksPerFrame)).c_str()); al_draw_text(g_font, al_map_rgb(255, 255, 255), 0, 40, ALLEGRO_ALIGN_LEFT, ("WFT: " + std::to_string(std::trunc(w->w_freezeTimer)).substr(0, std::to_string(trunc(w->w_freezeTimer)).find("."))).c_str()); al_draw_text(g_font, al_map_rgb(255, 255, 255), 0, 50, ALLEGRO_ALIGN_LEFT, ("POS: " + std::to_string(std::trunc(w->w_levelScrollPos)).substr(0, std::to_string(trunc(w->w_levelScrollPos)).find("."))).c_str()); al_draw_text(g_font, al_map_rgb(255, 255, 255), 0, 60, ALLEGRO_ALIGN_LEFT, ("LSS: " + std::to_string(std::trunc(w->w_levelScrollSpeed)).substr(0, std::to_string(trunc(w->w_levelScrollSpeed)).find("."))).c_str()); } } void renderer::r_drawWorldUI(world* w) { // todo: draw hudContent stored in worldThread - handle menu navigation for intermissions } void renderer::r_clearCanvasOnly() { al_draw_filled_rectangle(0, 0, resX * resScale, resY * resScale, al_map_rgba_f(0, 0, 0, 1)); } void renderer::r_changeResolution(float scale, bool fs) { // TODO: causes black screen, please fix if (fs) { // redo this whole part. it's busted c_printer() << "ERRO: fullscreen resolution changes unimplemented\n"; } else { //globals::resScale = r_windowedRS; resScale = scale; al_build_transform(&r_displayTransform, 0, 0, 1, 1, 0); al_set_display_flag(r_display, ALLEGRO_FULLSCREEN_WINDOW, false); isFullscreen = false; al_resize_display(r_display, resX * resScale, resY * resScale); } rrx = resX * resScale; rry = resY * resScale; c_printer() << "INFO: resolution changed, re-rasterizing truetype fonts...\n"; r_reloadFonts(); } void renderer::s_calculateMusicFadeVolume() { // TODO } /// In the World, ask the renderer to play a sound as soon as possible instead of doing it directly. /// Allegro only initializes per-thread, so this helps with multi-threading. void renderer::s_playSound(std::string file, float gain, float pan, float speed, ALLEGRO_PLAYMODE mode) { soundEffect s = {std::move(file), gain, pan, speed, mode}; soundQueue.push_back(std::move(s)); } //todo: add a way to stop looping sounds from World void renderer::s_fadeOutMusic(float duration) { musicFadeTimer = stdclk::now().time_since_epoch().count(); musicFadeLength = duration; } }