#ifndef LIB_FLIXEL_IMPLEMENTATION #define LIB_FLIXEL_IMPLEMENTATION #include #include #include #include #include #include #include #include #include #include #include #include "SDL2/SDL.h" #include "SDL2/SDL_ttf.h" #include "SDL2/SDL_FontCache.h" #include "SoLoud/soloud.h" #include "SoLoud/soloud_wav.h" #include "SoLoud/soloud_wavstream.h" #include "SoLoud/soloud_modplug.h" #include "SoLoud/soloud_openmpt.h" #include "SoLoud/MIDI/soloud_midi.h" #include "SoLoud/soloud_speech.h" #include "SoLoud/soloud_vizsn.h" #include "SoLoud/soloud_waveshaperfilter.h" #include "SoLoud/soloud_biquadresonantfilter.h" #include "SoLoud/soloud_fftfilter.h" #include "SDL2/SDL_syswm.h" #include "SDL2/SDL_stbimage.h" #include "sdfml_lua.hpp" #include "toml.hpp" #include #include "guicon.h" #include "sdfml/music.hpp" #include "sdfml/shader.hpp" #include "SDL_gpu/SDL_gpu.h" #include // std::this_thread::sleep_for #include // std::chrono::seconds #ifdef _WIN32 #define __FILENAME__ (strrchr(__FILE__, '\\') ? strrchr(__FILE__, '\\') + 1 : __FILE__) #else #define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) #endif using namespace std; #define DEFAULT_WINDOW_WIDTH 640 #define DEFAULT_WINDOW_HEIGHT 480 #define FRAMERATE 60 #define DEFAULT_SF "data/gm.sf2" struct Vector2f { float x = 0; float y = 0; }; struct Vector2 { int x = 0; int y = 0; }; template struct Vector3 { T r; T g; T b; }; enum lLOG_TYPE { NORMAL, WARNING, ERROR_ }; enum AXIS { X, Y, XY }; typedef HWND mWin; inline std::string _GetCurrentDirectory() { char buffer[MAX_PATH]; GetModuleFileNameA(NULL, buffer, MAX_PATH); std::string::size_type pos = std::string(buffer).find_last_of("\\/"); return std::string(buffer).substr(0, pos); } namespace sdfml { static double elapsed; inline int llog(string prefix, string msg, lLOG_TYPE type = NORMAL, string file = "???.cpp", int line = 0) { clock_t now = std::clock(); double now_n = (double)now / CLOCKS_PER_SEC; string typeName = "LOG"; switch (type) { case NORMAL: break; case WARNING: typeName = "WARNING"; break; case ERROR_: typeName = "ERROR"; break; } std::stringstream buf; buf << (int)(now_n/60) << ":" << std::setfill('0') << std::setw(2) << (int)((int)now_n % 60) << "." << std::setfill('0') << std::setw(3) << (int)((now_n - (int)now_n) * 1000) << " " << typeName << ": (" << file << ":" << line << ") " << prefix << msg << endl; std::ofstream logFile; logFile.open("log.txt", std::ios::app); logFile << buf.str(); logFile.close(); if (type != ERROR_) { cout << buf.str(); return 0; } cerr << buf.str(); return 1; } static Vector2f win_size = {DEFAULT_WINDOW_WIDTH, DEFAULT_WINDOW_HEIGHT}; static string soundfont = DEFAULT_SF; struct context { GPU_Target* gpu_render; SDL_Window* window; SDL_Renderer* renderer; SDL_Event events; mWin direct_win; Vector2f size; }; struct sdCam { Vector2f pos; Vector2f size; }; static context mContext; template inline int getIndex(vector v, T K) { auto it = find(v.begin(), v.end(), K); // If element was found if (it != v.end()) { // calculating the index // of K int index = it - v.begin(); return index; } else { return -1; } } class sdSprite { public: int x, y, width, height; double angle = 0.000001; double alpha = 1.0; Vector2f scale = {1, 1}; Vector2 offset; Vector3 color; ShaderProg shader; bool antialiasing = true; virtual void create(int x, int y, string path) { this->x = x; this->y = y; SDL_Surface* temp_surf = STBIMG_Load(path.c_str()); _tex_gpu = GPU_CopyImageFromSurface(temp_surf); GPU_SetBlendMode(_tex_gpu, GPU_BLEND_NORMAL); _tex_gpu->is_alias = antialiasing; width = _tex_gpu->w; height = _tex_gpu->h; color.r = 255; color.g = 255; color.b = 255; SDL_FreeSurface(temp_surf); } virtual void addShader(std::string path) { shader.loadShader(path, FRAGMENT, mContext.gpu_render, _tex_gpu); } GPU_Rect *r; virtual void update(float elapsed) { _x = x+offset.x; _y = y+offset.y; GPU_SetRGBA(_tex_gpu, color.r, color.g, color.b, alpha*255); r = &_src_rect; if (r->w == 0) r = NULL; shader.updateShader(static_cast(sdfml::elapsed)); GPU_Rect dst = {static_cast(_x-_camera->x), static_cast(_y-_camera->y), width*scale.x, height*scale.y}; GPU_BlitRectX(_tex_gpu, r, mContext.gpu_render, &dst, angle, NULL, NULL, GPU_FLIP_NONE); shader.postUpdate(); } virtual void destroy() { _x = 0; _y = 0; _w = 0; _h = 0; shader.freeShader(); GPU_FreeImage(_tex_gpu); } virtual void updateCamera(SDL_Rect* camera) { _camera = camera; } virtual void screenCenter(AXIS axis = XY) { switch (axis) { case X: x = (mContext.size.x/2) - (width*scale.x/2); break; case Y: y = (mContext.size.y/2) - (height*scale.y/2); break; case XY: x = (mContext.size.x/2) - (width*scale.x/2); y = (mContext.size.y/2) - (height*scale.y/2); break; } } protected: int _x, _y, _w, _h; GPU_Rect _src_rect = {0, 0, 0, 0}; SDL_Rect dummy = {0, 0, 0, 0}; sdCam _cam; SDL_Rect* _camera = &dummy; GPU_Image* _tex_gpu; }; inline void focusCamera(SDL_Rect* camera, sdSprite sprite) { camera->x = ( sprite.x + (sprite.width / 2) ) - mContext.size.x / 2; camera->y = ( sprite.y + (sprite.height / 2) ) - mContext.size.y / 2; if( camera->x < 0 ) { camera->x = 0; } if( camera->y < 0 ) { camera->y = 0; } if( camera->x > camera->w ) { camera->x = camera->w; } if( camera->y > camera->h ) { camera->y = camera->h; } } class sdAnimatedSprite : public sdSprite { public: virtual void create(int x, int y, string path) { sdSprite::create(x, y, path); startTime = SDL_GetTicks(); } void AddAnimation(string anim_name, vector points) { frameRects.insert({anim_name, points}); } void PlayAnimation(string anim_name) { current_framename = anim_name; } virtual void update(float elapsed) { sdSprite::update(elapsed); if (current_framename != "") { // this will make it so that current_frame will only advance when it needs to int frameToDraw = ((SDL_GetTicks() - startTime) * framerate / 1000) % frameRects[current_framename].size(); current_frame = frameToDraw; int sx = frameRects[current_framename][current_frame].x; int sy = frameRects[current_framename][current_frame].y; int sw = frameRects[current_framename][current_frame].w; int sh = frameRects[current_framename][current_frame].h; // support scaling :) width = (frameRects[current_framename][current_frame].w)*scale.x; height = (frameRects[current_framename][current_frame].h)*scale.y; // after setting shit up, we then store it in src_rect. _src_rect = {static_cast(sx), static_cast(sy), static_cast(sw), static_cast(sh)}; } } int framerate = 24; map> frameRects; string current_framename = ""; int current_frame = 0; private: int startTime; }; class sdState { public: virtual void create() { } virtual void update(float elapsed) { } virtual void draw(float elapsed) { GPU_Clear(mContext.gpu_render); for (int i = 0; i < _sprites.size(); i++) { _sprites[i]->update(elapsed); } } virtual void add(sdSprite* p_spr, bool mut = false) { // a mutable sprite cannot be changed after being added. if (mut) { _mut_sprites.push_back(*p_spr); _sprites.push_back(&_mut_sprites[_mut_sprites.size() - 1]); return; } _sprites.push_back(p_spr); } vector get_spr() { return _sprites; } vector get_mspr() { return _mut_sprites; } void freeSprites() { _mut_sprites.clear(); _sprites.clear(); } vector _mut_sprites; vector _sprites; }; static sdState* curState = nullptr; // Parse a TOML at a given path with a table and key. // Setting table name to "" will look for the key without a table. template T tomlParse(string path, string table_name = "", string key = "") { auto parsed = toml::parse(path); if (table_name != "") { auto& config_table = toml::find(parsed, table_name); return toml::find(config_table, key); } return toml::find(parsed, key); } // "touch" a file at a given path. // Returns false if file is empty, returns true if not inline bool touchFile(string path) { std::ofstream file; file.open(path, std::ios::app); file.close(); fstream oFile(path); oFile.seekg(0,std::ios::end); unsigned int size = oFile.tellg(); if (!size) { oFile.close(); return false; } oFile.close(); return true; } // Export a TOML file to a path from a toml::value. inline void tomlExport(string path, toml::value values) { string export_ = toml::format(values, 0, 2); std::ofstream config; config.open(path, std::ofstream::out | std::ofstream::trunc); config << export_ << endl; config.close(); } static GPU_Camera camera; inline int init(float width = DEFAULT_WINDOW_WIDTH, float height = DEFAULT_WINDOW_HEIGHT, string window_name = "Unknown", int win_flags = SDL_WINDOW_OPENGL|SDL_WINDOW_ALLOW_HIGHDPI|SDL_WINDOW_SHOWN) { std::ofstream logFile; logFile.open("log.txt", std::ofstream::out | std::ofstream::trunc); logFile.close(); if (!touchFile("conf.toml")) tomlExport("conf.toml", { {"config", {{"soundfont", "data/gm.sf2"}}} }); soundfont = tomlParse("conf.toml", "config", "soundfont"); if (SDL_Init(SDL_INIT_EVERYTHING) < 0) return llog("SDL", " has failed to initialize.", ERROR_, __FILENAME__, __LINE__); llog("SDL", " is now initialized.", NORMAL, __FILENAME__, __LINE__); if (TTF_Init() < 0) return llog("SDL_ttf", " has failed to initialize.", ERROR_, __FILENAME__, __LINE__); llog("SDL_ttf", " is now initialized.", NORMAL, __FILENAME__, __LINE__); llog("", "Initialized libraries. Creating a window context.", NORMAL, __FILENAME__, __LINE__); GPU_SetPreInitFlags(GPU_INIT_DISABLE_VSYNC); mContext.gpu_render = GPU_Init(width, height, GPU_DEFAULT_INIT_FLAGS); SDL_SetWindowTitle(SDL_GetWindowFromID(mContext.gpu_render->context->windowID), window_name.c_str()); //sound.music.loadSoundfont(soundfont); SDL_SysWMinfo wmInfo; SDL_VERSION(&wmInfo.version); SDL_GetWindowWMInfo(SDL_GetWindowFromID(mContext.gpu_render->context->windowID), &wmInfo); mContext.direct_win = wmInfo.info.win.window; sound.init(); llog("SoLoud", " is now initialized.", NORMAL, __FILENAME__, __LINE__); camera = GPU_GetDefaultCamera(); GPU_EnableCamera(mContext.gpu_render, true); GPU_SetCamera(mContext.gpu_render, &camera); SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE); mContext.size = {width, height}; return llog("", "Fully finalized initialization. Command over.", NORMAL, __FILENAME__, __LINE__); } template using Func = std::function; static vector _sec; static vector> _call; static vector _repeats; static vector _ticks; class sdTimer { public: void start(float seconds, Func callback, bool repeat = false) { _sec.push_back(seconds); _call.push_back(callback); _repeats.push_back(repeat); _ticks.push_back(0); } }; static const Uint8* kb; static const Uint8* kb_jp; static const Uint8* kb_jr; static bool lockKeys = false; inline bool key_just_pressed(SDL_Scancode code) { return kb_jp[code] && !lockKeys; } inline bool key_just_released(SDL_Scancode code) { return kb_jr[code] && !lockKeys; } inline bool key_pressed(SDL_Scancode code) { return kb[code] && !lockKeys; } inline int Sec2Tick(float time) { return FRAMERATE*time; } inline int update() { int lastUpdate = SDL_GetTicks(); bool run = true; kb_jp = SDL_GetKeyboardState(NULL); kb_jr = SDL_GetKeyboardState(NULL); while (run) { while(SDL_PollEvent(&mContext.events)) { if(mContext.events.type == SDL_QUIT) { run = false; break; } if (mContext.events.type == SDL_KEYDOWN) { kb_jp = SDL_GetKeyboardState(NULL); break; } if (mContext.events.type == SDL_KEYUP) { kb_jr = SDL_GetKeyboardState(NULL); break; } } int start = SDL_GetPerformanceCounter(); int current = SDL_GetTicks(); float dT = (current - lastUpdate) / 1000.0f; kb = SDL_GetKeyboardState(NULL); if (curState != nullptr) curState->update(dT); // I know this is a shitty way to do this // but bear with me // it was hard to work with lambdas properly man // let me have this just one please :) if (_sec.size() > 0) { for (int i = 0; i < _sec.size(); i++) { if (_ticks[i] != -1) _ticks[i]++; if (_sec[i] != -1) { if (!(_ticks[i] < Sec2Tick(_sec[i]))) { if (_call[i] != NULL) _call[i](); if (!_repeats[i] && _repeats[i] != NULL) { _ticks[i] = -1; _sec[i] = -1; _call[i] = NULL; _repeats[i] = NULL; } } } // cout << i << endl; } } lastUpdate = current; if (curState != nullptr) curState->draw(dT); int end = SDL_GetPerformanceCounter(); float elapsedMS = (float)(end - start) / SDL_GetPerformanceFrequency() * 1000.0f; elapsed = SDL_GetTicks()/1000.0f; SDL_Delay(floor((1000.0f/FRAMERATE) - elapsedMS)); GPU_SetCamera(mContext.gpu_render, &camera); GPU_Flip(mContext.gpu_render); } if (curState->get_spr().size() > 0) { for (auto texture : curState->get_spr()) { texture->destroy(); } } if (curState->get_mspr().size() > 0) { for (auto texture : curState->get_mspr()) { texture.destroy(); } } GPU_Quit(); sound.deinit(); ReleaseConsole(); TTF_Quit(); SDL_Quit(); return 0; } static sdSprite transitionSprite; static sdSprite transitionSprite2; static sdTimer fadeTimer; static sdTimer switchTimer; inline double clamp(double d, double min, double max) { const double t = d < min ? min : d; return t > max ? max : t; } inline int lmao(sdState* state) { if (curState != nullptr) { if (curState->get_spr().size() > 0) { for (auto texture : curState->get_spr()) { texture->destroy(); } } if (curState->get_mspr().size() > 0) { for (auto texture : curState->get_mspr()) { texture.destroy(); } } curState->freeSprites(); } curState = state; _ticks = {}; _call = {}; _repeats = {}; _sec = {}; curState->create(); transitionSprite2.create(0, 0, "data/images/black.png"); curState->add(&transitionSprite2); fadeTimer.start(0, []() { transitionSprite2.x = clamp(transitionSprite2.x - (mContext.size.x/FRAMERATE), -mContext.size.x, 0); return 0; }, true); lockKeys = false; return 0; } inline void switchState(sdState* state) { lockKeys = true; try { if (curState != nullptr) { transitionSprite.create(mContext.size.x, 0, "data/images/black.png"); curState->add(&transitionSprite); fadeTimer.start(0, []() { transitionSprite.x = clamp(transitionSprite.x - (mContext.size.x/FRAMERATE), 0, mContext.size.x); return 0; }, true); switchTimer.start(1, [state](){ lmao(state); return 0; }); } else { lmao(state); } } catch (...) { lmao(state); } } } #endif