diff options
author | WlodekM <[email protected]> | 2024-06-16 10:35:45 +0300 |
---|---|---|
committer | WlodekM <[email protected]> | 2024-06-16 10:35:45 +0300 |
commit | abef6da56913f1c55528103e60a50451a39628b1 (patch) | |
tree | b3c8092471ecbb73e568cd0d336efa0e7871ee8d /src/Game.c |
initial commit
Diffstat (limited to 'src/Game.c')
-rw-r--r-- | src/Game.c | 796 |
1 files changed, 796 insertions, 0 deletions
diff --git a/src/Game.c b/src/Game.c new file mode 100644 index 0000000..185eec4 --- /dev/null +++ b/src/Game.c @@ -0,0 +1,796 @@ +#include "Game.h" +#include "Block.h" +#include "World.h" +#include "Lighting.h" +#include "MapRenderer.h" +#include "Graphics.h" +#include "Camera.h" +#include "Options.h" +#include "Funcs.h" +#include "ExtMath.h" +#include "Gui.h" +#include "Window.h" +#include "Event.h" +#include "Utils.h" +#include "Logger.h" +#include "Entity.h" +#include "Chat.h" +#include "Commands.h" +#include "Drawer2D.h" +#include "Model.h" +#include "Particle.h" +#include "Http.h" +#include "Inventory.h" +#include "Input.h" +#include "Server.h" +#include "TexturePack.h" +#include "Screens.h" +#include "SelectionBox.h" +#include "AxisLinesRenderer.h" +#include "EnvRenderer.h" +#include "HeldBlockRenderer.h" +#include "SelOutlineRenderer.h" +#include "Menus.h" +#include "Audio.h" +#include "Stream.h" +#include "Builder.h" +#include "Protocol.h" +#include "Picking.h" +#include "Animations.h" +#include "SystemFonts.h" +#include "Formats.h" +#include "EntityRenderers.h" + +struct _GameData Game; +cc_uint64 Game_FrameStart; +cc_bool Game_UseCPEBlocks; + +struct RayTracer Game_SelectedPos; +int Game_ViewDistance = DEFAULT_VIEWDIST; +int Game_UserViewDistance = DEFAULT_VIEWDIST; +int Game_MaxViewDistance = DEFAULT_MAX_VIEWDIST; + +int Game_FpsLimit, Game_Vertices; +cc_bool Game_SimpleArmsAnim; +static cc_bool gameRunning; + +cc_bool Game_ClassicMode, Game_ClassicHacks; +cc_bool Game_AllowCustomBlocks; +cc_bool Game_AllowServerTextures; +cc_bool Game_Anaglyph3D; + +cc_bool Game_ViewBobbing, Game_HideGui; +cc_bool Game_BreakableLiquids, Game_ScreenshotRequested; +struct GameVersion Game_Version; + +static char usernameBuffer[STRING_SIZE]; +static char mppassBuffer[STRING_SIZE]; +cc_string Game_Username = String_FromArray(usernameBuffer); +cc_string Game_Mppass = String_FromArray(mppassBuffer); +#ifdef CC_BUILD_SPLITSCREEN +int Game_NumLocalPlayers = 1; +#endif + +const char* const FpsLimit_Names[FPS_LIMIT_COUNT] = { + "LimitVSync", "Limit30FPS", "Limit60FPS", "Limit120FPS", "Limit144FPS", "LimitNone", +}; + +static struct IGameComponent* comps_head; +static struct IGameComponent* comps_tail; +void Game_AddComponent(struct IGameComponent* comp) { + LinkedList_Append(comp, comps_head, comps_tail); +} + +#define TASKS_DEF_ELEMS 6 +static struct ScheduledTask defaultTasks[TASKS_DEF_ELEMS]; +static int tasksCapacity = TASKS_DEF_ELEMS, tasksCount, entTaskI; +static struct ScheduledTask* tasks = defaultTasks; + +int ScheduledTask_Add(double interval, ScheduledTaskCallback callback) { + struct ScheduledTask task; + task.accumulator = 0.0; + task.interval = interval; + task.Callback = callback; + + if (tasksCount == tasksCapacity) { + Utils_Resize((void**)&tasks, &tasksCapacity, + sizeof(struct ScheduledTask), TASKS_DEF_ELEMS, TASKS_DEF_ELEMS); + } + + tasks[tasksCount++] = task; + return tasksCount - 1; +} + + +void Game_ToggleFullscreen(void) { + int state = Window_GetWindowState(); + cc_result res; + + if (state == WINDOW_STATE_FULLSCREEN) { + res = Window_ExitFullscreen(); + if (res) Logger_SysWarn(res, "leaving fullscreen"); + } else { + res = Window_EnterFullscreen(); + if (res) Logger_SysWarn(res, "going fullscreen"); + } +} + +static void CycleViewDistanceForwards(const short* viewDists, int count) { + int i, dist; + for (i = 0; i < count; i++) { + dist = viewDists[i]; + + if (dist > Game_UserViewDistance) { + Game_UserSetViewDistance(dist); return; + } + } + Game_UserSetViewDistance(viewDists[0]); +} + +static void CycleViewDistanceBackwards(const short* viewDists, int count) { + int i, dist; + for (i = count - 1; i >= 0; i--) { + dist = viewDists[i]; + + if (dist < Game_UserViewDistance) { + Game_UserSetViewDistance(dist); return; + } + } + Game_UserSetViewDistance(viewDists[count - 1]); +} + +static const short normalDists[] = { 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096 }; +static const short classicDists[] = { 8, 32, 128, 512 }; +void Game_CycleViewDistance(void) { + const short* dists = Gui.ClassicMenu ? classicDists : normalDists; + int count = Gui.ClassicMenu ? Array_Elems(classicDists) : Array_Elems(normalDists); + + if (Input_IsShiftPressed()) { + CycleViewDistanceBackwards(dists, count); + } else { + CycleViewDistanceForwards(dists, count); + } +} + +cc_bool Game_ReduceVRAM(void) { + if (Game_UserViewDistance <= 16) return false; + Game_UserViewDistance /= 2; + Game_UserViewDistance = max(16, Game_UserViewDistance); + + MapRenderer_Refresh(); + Game_SetViewDistance(Game_UserViewDistance); + Chat_AddRaw("&cOut of VRAM! Halving view distance.."); + return true; +} + + +void Game_SetViewDistance(int distance) { + distance = min(distance, Game_MaxViewDistance); + if (distance == Game_ViewDistance) return; + Game_ViewDistance = distance; + + Event_RaiseVoid(&GfxEvents.ViewDistanceChanged); + Camera_UpdateProjection(); +} + +void Game_UserSetViewDistance(int distance) { + Game_UserViewDistance = distance; + Options_SetInt(OPT_VIEW_DISTANCE, distance); + Game_SetViewDistance(distance); +} + +void Game_Disconnect(const cc_string* title, const cc_string* reason) { + Event_RaiseVoid(&NetEvents.Disconnected); + Game_Reset(); + DisconnectScreen_Show(title, reason); +} + +void Game_Reset(void) { + struct IGameComponent* comp; + World_NewMap(); + + for (comp = comps_head; comp; comp = comp->next) { + if (comp->Reset) comp->Reset(); + } +} + +void Game_UpdateBlock(int x, int y, int z, BlockID block) { + BlockID old = World_GetBlock(x, y, z); + World_SetBlock(x, y, z, block); + + if (Weather_Heightmap) { + EnvRenderer_OnBlockChanged(x, y, z, old, block); + } + Lighting.OnBlockChanged(x, y, z, old, block); + MapRenderer_OnBlockChanged(x, y, z, block); +} + +void Game_ChangeBlock(int x, int y, int z, BlockID block) { + BlockID old = World_GetBlock(x, y, z); + Game_UpdateBlock(x, y, z, block); + Server.SendBlock(x, y, z, old, block); +} + +cc_bool Game_CanPick(BlockID block) { + if (Blocks.Draw[block] == DRAW_GAS) return false; + if (Blocks.Draw[block] == DRAW_SPRITE) return true; + return Blocks.Collide[block] != COLLIDE_LIQUID || Game_BreakableLiquids; +} + +cc_bool Game_UpdateTexture(GfxResourceID* texId, struct Stream* src, const cc_string* file, + cc_uint8* skinType, int* heightDivisor) { + struct Bitmap bmp; + cc_bool success; + cc_result res; + + res = Png_Decode(&bmp, src); + if (res) { Logger_SysWarn2(res, "decoding", file); } + + /* E.g. gui.png only need top half of the texture loaded */ + if (heightDivisor && bmp.height >= *heightDivisor) + bmp.height /= *heightDivisor; + + success = !res && Game_ValidateBitmap(file, &bmp); + if (success) { + if (skinType) { *skinType = Utils_CalcSkinType(&bmp); } + Gfx_RecreateTexture(texId, &bmp, TEXTURE_FLAG_MANAGED, false); + } + + Mem_Free(bmp.scan0); + return success; +} + +cc_bool Game_ValidateBitmap(const cc_string* file, struct Bitmap* bmp) { + int maxWidth = Gfx.MaxTexWidth, maxHeight = Gfx.MaxTexHeight; + float texSize, maxSize; + + if (!bmp->scan0) { + Chat_Add1("&cError loading %s from the texture pack.", file); + return false; + } + + if (bmp->width > maxWidth || bmp->height > maxHeight) { + Chat_Add1("&cUnable to use %s from the texture pack.", file); + + Chat_Add4("&c Its size is (%i,%i), your GPU supports (%i,%i) at most.", + &bmp->width, &bmp->height, &maxWidth, &maxHeight); + return false; + } + + if (Gfx.MaxTexSize && (bmp->width * bmp->height > Gfx.MaxTexSize)) { + Chat_Add1("&cUnable to use %s from the texture pack.", file); + texSize = (bmp->width * bmp->height) / (1024.0f * 1024.0f); + maxSize = Gfx.MaxTexSize / (1024.0f * 1024.0f); + + Chat_Add2("&c Its size is %f3 MB, your GPU supports %f3 MB at most.", + &texSize, &maxSize); + return false; + } + + return Game_ValidateBitmapPow2(file, bmp); +} + +cc_bool Game_ValidateBitmapPow2(const cc_string* file, struct Bitmap* bmp) { + if (!Math_IsPowOf2(bmp->width) || !Math_IsPowOf2(bmp->height)) { + Chat_Add1("&cUnable to use %s from the texture pack.", file); + + Chat_Add2("&c Its size is (%i,%i), which is not a power of two size.", + &bmp->width, &bmp->height); + return false; + } + return true; +} + +void Game_UpdateDimensions(void) { + Game.Width = max(Window_Main.Width, 1); + Game.Height = max(Window_Main.Height, 1); +} + +static void Game_OnResize(void* obj) { + Game_UpdateDimensions(); + Gfx_OnWindowResize(); + Camera_UpdateProjection(); +} + +static void HandleOnNewMap(void* obj) { + struct IGameComponent* comp; + for (comp = comps_head; comp; comp = comp->next) { + if (comp->OnNewMap) comp->OnNewMap(); + } +} + +static void HandleOnNewMapLoaded(void* obj) { + struct IGameComponent* comp; + for (comp = comps_head; comp; comp = comp->next) { + if (comp->OnNewMapLoaded) comp->OnNewMapLoaded(); + } +} + +static void HandleInactiveChanged(void* obj) { + if (Window_Main.Inactive) { + Chat_AddOf(&Gfx_LowPerfMessage, MSG_TYPE_EXTRASTATUS_2); + Gfx_SetFpsLimit(false, 1000 / 1.0f); + Gfx.ReducedPerfMode = true; + } else { + Chat_AddOf(&String_Empty, MSG_TYPE_EXTRASTATUS_2); + Game_SetFpsLimit(Game_FpsLimit); + + Gfx.ReducedPerfMode = false; + Gfx.ReducedPerfModeCooldown = 2; + } + +#ifdef CC_BUILD_WEB + extern void emscripten_resume_main_loop(void); + emscripten_resume_main_loop(); +#endif +} + +static void Game_WarnFunc(const cc_string* msg) { + cc_string str = *msg, line; + while (str.length) { + String_UNSAFE_SplitBy(&str, '\n', &line); + Chat_Add1("&c%s", &line); + } +} + +static void LoadOptions(void) { + Game_ClassicMode = Options_GetBool(OPT_CLASSIC_MODE, false); + Game_ClassicHacks = Options_GetBool(OPT_CLASSIC_HACKS, false); + Game_Anaglyph3D = Options_GetBool(OPT_ANAGLYPH3D, false); + Game_ViewBobbing = Options_GetBool(OPT_VIEW_BOBBING, true); + + Game_AllowCustomBlocks = !Game_ClassicMode && Options_GetBool(OPT_CUSTOM_BLOCKS, true); + Game_SimpleArmsAnim = !Game_ClassicMode && Options_GetBool(OPT_SIMPLE_ARMS_ANIM, false); + Game_BreakableLiquids = !Game_ClassicMode && Options_GetBool(OPT_MODIFIABLE_LIQUIDS, false); + Game_AllowServerTextures = !Game_ClassicMode && Options_GetBool(OPT_SERVER_TEXTURES, true); + + Game_ViewDistance = Options_GetInt(OPT_VIEW_DISTANCE, 8, 4096, DEFAULT_VIEWDIST); + Game_UserViewDistance = Game_ViewDistance; + /* TODO: Do we need to support option to skip SSL */ + /*cc_bool skipSsl = Options_GetBool("skip-ssl-check", false); + if (skipSsl) { + ServicePointManager.ServerCertificateValidationCallback = delegate { return true; }; + Options.Set("skip-ssl-check", false); + }*/ +} + +#ifdef CC_BUILD_PLUGINS +static void LoadPlugin(const cc_string* path, void* obj) { + void* lib; + void* verSym; /* EXPORT int Plugin_ApiVersion = GAME_API_VER; */ + void* compSym; /* EXPORT struct IGameComponent Plugin_Component = { (whatever) } */ + int ver; + + /* ignore accepted.txt, deskop.ini, .pdb files, etc */ + if (!String_CaselessEnds(path, &DynamicLib_Ext)) return; + /* don't try to load 32 bit plugins on 64 bit OS or vice versa */ + if (sizeof(void*) == 4 && String_ContainsConst(path, "_64.")) return; + if (sizeof(void*) == 8 && String_ContainsConst(path, "_32.")) return; + + lib = DynamicLib_Load2(path); + if (!lib) { Logger_DynamicLibWarn("loading", path); return; } + + verSym = DynamicLib_Get2(lib, "Plugin_ApiVersion"); + if (!verSym) { Logger_DynamicLibWarn("getting version of", path); return; } + compSym = DynamicLib_Get2(lib, "Plugin_Component"); + if (!compSym) { Logger_DynamicLibWarn("initing", path); return; } + + ver = *((int*)verSym); + if (ver < GAME_API_VER) { + Chat_Add1("&c%s plugin is outdated! Try getting a more recent version.", path); + return; + } else if (ver > GAME_API_VER) { + Chat_Add1("&cYour game is too outdated to use %s plugin! Try updating it.", path); + return; + } + + Game_AddComponent((struct IGameComponent*)compSym); +} + +static void LoadPlugins(void) { + static const cc_string dir = String_FromConst("plugins"); + cc_result res; + + Utils_EnsureDirectory("plugins"); + res = Directory_Enum(&dir, NULL, LoadPlugin); + if (res) Logger_SysWarn(res, "enumerating plugins directory"); +} +#else +static void LoadPlugins(void) { } +#endif + +static void Game_Free(void* obj); +static void Game_Load(void) { + struct IGameComponent* comp; + Game_UpdateDimensions(); + Game_SetFpsLimit(Options_GetEnum(OPT_FPS_LIMIT, 0, FpsLimit_Names, FPS_LIMIT_COUNT)); + Gfx_Create(); + + Logger_WarnFunc = Game_WarnFunc; + LoadOptions(); + GameVersion_Load(); + Utils_EnsureDirectory("maps"); + + Event_Register_(&WorldEvents.NewMap, NULL, HandleOnNewMap); + Event_Register_(&WorldEvents.MapLoaded, NULL, HandleOnNewMapLoaded); + Event_Register_(&WindowEvents.Resized, NULL, Game_OnResize); + Event_Register_(&WindowEvents.Closing, NULL, Game_Free); + Event_Register_(&WindowEvents.InactiveChanged, NULL, HandleInactiveChanged); + + Game_AddComponent(&World_Component); + Game_AddComponent(&Textures_Component); + Game_AddComponent(&Input_Component); + Game_AddComponent(&Camera_Component); + Game_AddComponent(&Gfx_Component); + Game_AddComponent(&Blocks_Component); + Game_AddComponent(&Drawer2D_Component); + Game_AddComponent(&SystemFonts_Component); + + Game_AddComponent(&Chat_Component); + Game_AddComponent(&Commands_Component); + Game_AddComponent(&Particles_Component); + Game_AddComponent(&TabList_Component); + Game_AddComponent(&Models_Component); + Game_AddComponent(&Entities_Component); + Game_AddComponent(&Http_Component); + Game_AddComponent(&Lighting_Component); + + Game_AddComponent(&Animations_Component); + Game_AddComponent(&Inventory_Component); + Game_AddComponent(&Builder_Component); + Game_AddComponent(&MapRenderer_Component); + Game_AddComponent(&EnvRenderer_Component); + Game_AddComponent(&Server_Component); + Game_AddComponent(&Protocol_Component); + + Game_AddComponent(&Gui_Component); + Game_AddComponent(&Selections_Component); + Game_AddComponent(&HeldBlockRenderer_Component); + /* Gfx_SetDepthWrite(true) */ + Game_AddComponent(&SelOutlineRenderer_Component); + Game_AddComponent(&Audio_Component); + Game_AddComponent(&AxisLinesRenderer_Component); + Game_AddComponent(&Formats_Component); + Game_AddComponent(&EntityRenderers_Component); + + LoadPlugins(); + for (comp = comps_head; comp; comp = comp->next) { + if (comp->Init) comp->Init(); + } + + TexturePack_ExtractCurrent(true); + if (TexturePack_DefaultMissing) { + Window_ShowDialog("Missing file", + "Both default.zip and classicube.zip are missing,\n try downloading resources first.\n\nClassiCube will still run, but without any textures."); + } + + entTaskI = ScheduledTask_Add(GAME_DEF_TICKS, Entities_Tick); + if (Gfx_WarnIfNecessary()) EnvRenderer_SetMode(EnvRenderer_Minimal | ENV_LEGACY); + Server.BeginConnect(); +} + +void Game_SetFpsLimit(int method) { + float minFrameTime = 0; + Game_FpsLimit = method; + + switch (method) { + case FPS_LIMIT_144: minFrameTime = 1000/144.0f; break; + case FPS_LIMIT_120: minFrameTime = 1000/120.0f; break; + case FPS_LIMIT_60: minFrameTime = 1000/60.0f; break; + case FPS_LIMIT_30: minFrameTime = 1000/30.0f; break; + } + Gfx_SetFpsLimit(method == FPS_LIMIT_VSYNC, minFrameTime); +} + +static void UpdateViewMatrix(void) { + Camera.Active->GetView(&Gfx.View); + FrustumCulling_CalcFrustumEquations(&Gfx.Projection, &Gfx.View); +} + +static void Render3DFrame(float delta, float t) { + Vec3 pos; + Gfx_LoadMatrix(MATRIX_PROJECTION, &Gfx.Projection); + Gfx_LoadMatrix(MATRIX_VIEW, &Gfx.View); + if (EnvRenderer_ShouldRenderSkybox()) EnvRenderer_RenderSkybox(); + + AxisLinesRenderer_Render(); + Entities_RenderModels(delta, t); + EntityNames_Render(); + + Particles_Render(t); + EnvRenderer_RenderSky(); + EnvRenderer_RenderClouds(); + + MapRenderer_Update(delta); + MapRenderer_RenderNormal(delta); + EnvRenderer_RenderMapSides(); + + EntityShadows_Render(); + if (Game_SelectedPos.valid && !Game_HideGui) { + SelOutlineRenderer_Render(&Game_SelectedPos, true); + } + + /* Render water over translucent blocks when under the water outside the map for proper alpha blending */ + pos = Camera.CurrentPos; + if (pos.y < Env.EdgeHeight && (pos.x < 0 || pos.z < 0 || pos.x > World.Width || pos.z > World.Length)) { + MapRenderer_RenderTranslucent(delta); + EnvRenderer_RenderMapEdges(); + } else { + EnvRenderer_RenderMapEdges(); + MapRenderer_RenderTranslucent(delta); + } + + /* Need to render again over top of translucent block, as the selection outline */ + /* is drawn without writing to the depth buffer */ + if (Game_SelectedPos.valid && !Game_HideGui && Blocks.Draw[Game_SelectedPos.block] == DRAW_TRANSLUCENT) { + SelOutlineRenderer_Render(&Game_SelectedPos, false); + } + + Selections_Render(); + EntityNames_RenderHovered(); + if (!Game_HideGui) HeldBlockRenderer_Render(delta); +} + +static void Render3D_Anaglyph(float delta, float t) { + struct Matrix proj = Gfx.Projection; + struct Matrix view = Gfx.View; + + Gfx_Set3DLeft(&proj, &view); + Render3DFrame(delta, t); + + Gfx_Set3DRight(&proj, &view); + Render3DFrame(delta, t); + + Gfx_End3D(&proj, &view); +} + +static void PerformScheduledTasks(double time) { + struct ScheduledTask* task; + int i; + + for (i = 0; i < tasksCount; i++) { + task = &tasks[i]; + task->accumulator += time; + + while (task->accumulator >= task->interval) { + task->Callback(task); + task->accumulator -= task->interval; + } + } +} + +void Game_TakeScreenshot(void) { + cc_string filename; char fileBuffer[STRING_SIZE]; + cc_string path; char pathBuffer[FILENAME_SIZE]; + struct DateTime now; + cc_result res; +#ifdef CC_BUILD_WEB + char str[NATIVE_STR_LEN]; +#else + struct Stream stream; +#endif + Game_ScreenshotRequested = false; + DateTime_CurrentLocal(&now); + + String_InitArray(filename, fileBuffer); + String_Format3(&filename, "screenshot_%p4-%p2-%p2", &now.year, &now.month, &now.day); + String_Format3(&filename, "-%p2-%p2-%p2.png", &now.hour, &now.minute, &now.second); + +#ifdef CC_BUILD_WEB + extern void interop_TakeScreenshot(const char* path); + String_EncodeUtf8(str, &filename); + interop_TakeScreenshot(str); +#else + if (!Utils_EnsureDirectory("screenshots")) return; + String_InitArray(path, pathBuffer); + String_Format1(&path, "screenshots/%s", &filename); + + res = Stream_CreateFile(&stream, &path); + if (res) { Logger_SysWarn2(res, "creating", &path); return; } + + res = Gfx_TakeScreenshot(&stream); + if (res) { + Logger_SysWarn2(res, "saving to", &path); stream.Close(&stream); return; + } + + res = stream.Close(&stream); + if (res) { Logger_SysWarn2(res, "closing", &path); return; } + Chat_Add1("&eTaken screenshot as: %s", &filename); + +#ifdef CC_BUILD_MOBILE + Platform_ShareScreenshot(&filename); +#endif +#endif +} + +static CC_INLINE void Game_DrawFrame(float delta, float t) { + UpdateViewMatrix(); + + if (!Gui_GetBlocksWorld()) { + Camera.Active->GetPickedBlock(&Game_SelectedPos); /* TODO: only pick when necessary */ + Camera_KeyLookUpdate(delta); + InputHandler_Tick(); + + if (Game_Anaglyph3D) { + Render3D_Anaglyph(delta, t); + } else { + Render3DFrame(delta, t); + } + } else { + RayTracer_SetInvalid(&Game_SelectedPos); + } + + Gfx_Begin2D(Game.Width, Game.Height); + Gui_RenderGui(delta); + OnscreenKeyboard_Draw3D(); +/* TODO find a better solution than this */ +#ifdef CC_BUILD_3DS + if (Game_Anaglyph3D) { + extern void Gfx_SetTopRight(void); + Gfx_SetTopRight(); + Gui_RenderGui(delta); + } +#endif + Gfx_End2D(); +} + +#ifdef CC_BUILD_SPLITSCREEN +static void DrawSplitscreen(float delta, float t, int i, int x, int y, int w, int h) { + Gfx_SetViewport(x, y, w, h); + + Entities.CurPlayer = &LocalPlayer_Instances[i]; + LocalPlayer_SetInterpPosition(Entities.CurPlayer, t); + Camera.CurrentPos = Camera.Active->GetPosition(t); + + Game_DrawFrame(delta, t); +} +#endif + +static CC_INLINE void Game_RenderFrame(double delta) { + struct ScheduledTask entTask; + float t; + + /* TODO: Should other tasks get called back too? */ + /* Might not be such a good idea for the http_clearcache, */ + /* don't really want all skins getting lost */ + if (Gfx.LostContext) { + if (Gfx_TryRestoreContext()) { + Gfx_RecreateContext(); + /* all good, context is back */ + } else { + Game.Time += delta; /* TODO: Not set in two places? */ + Server.Tick(NULL); + Thread_Sleep(16); + return; + } + } + + Gfx_BeginFrame(); + Gfx_BindIb(Gfx_defaultIb); + Game.Time += delta; + Game_Vertices = 0; + + if (Input.Sources & INPUT_SOURCE_GAMEPAD) Gamepad_Tick(delta); + Camera.Active->UpdateMouse(Entities.CurPlayer, delta); + + if (!Window_Main.Focused && !Gui.InputGrab) Gui_ShowPauseMenu(); + + if (InputBind_IsPressed(BIND_ZOOM_SCROLL) && !Gui.InputGrab) { + InputHandler_SetFOV(Camera.ZoomFov); + } + + PerformScheduledTasks(delta); + entTask = tasks[entTaskI]; + t = (float)(entTask.accumulator / entTask.interval); + LocalPlayer_SetInterpPosition(Entities.CurPlayer, t); + + Camera.CurrentPos = Camera.Active->GetPosition(t); + /* NOTE: EnvRenderer_UpdateFog also also sets clear color */ + EnvRenderer_UpdateFog(); + AudioBackend_Tick(); + + /* TODO: Not calling Gfx_EndFrame doesn't work with Direct3D9 */ + if (Window_Main.Inactive) return; + Gfx_ClearBuffers(GFX_BUFFER_COLOR | GFX_BUFFER_DEPTH); + +#ifdef CC_BUILD_SPLITSCREEN + switch (Game_NumLocalPlayers) { + case 1: + Game_DrawFrame(delta, t); break; + case 2: + DrawSplitscreen(delta, t, 0, 0, 0, Game.Width, Game.Height / 2); + DrawSplitscreen(delta, t, 1, 0, Game.Height / 2, Game.Width, Game.Height / 2); + break; + case 3: + DrawSplitscreen(delta, t, 0, 0, 0, Game.Width , Game.Height / 2); + DrawSplitscreen(delta, t, 1, 0, Game.Height / 2, Game.Width / 2, Game.Height / 2); + DrawSplitscreen(delta, t, 2, Game.Width / 2, Game.Height / 2, Game.Width / 2, Game.Height / 2); + break; + case 4: + DrawSplitscreen(delta, t, 0, 0, 0, Game.Width / 2, Game.Height / 2); + DrawSplitscreen(delta, t, 1, Game.Width / 2, 0, Game.Width / 2, Game.Height / 2); + DrawSplitscreen(delta, t, 2, 0, Game.Height / 2, Game.Width / 2, Game.Height / 2); + DrawSplitscreen(delta, t, 3, Game.Width / 2, Game.Height / 2, Game.Width / 2, Game.Height / 2); + break; + } +#else + Game_DrawFrame(delta, t); +#endif + + if (Game_ScreenshotRequested) Game_TakeScreenshot(); + Gfx_EndFrame(); +} + +static void Game_Free(void* obj) { + struct IGameComponent* comp; + /* Most components will call OnContextLost in their Free functions */ + /* Set to false so components will always free managed textures too */ + Gfx.ManagedTextures = false; + Event_UnregisterAll(); + tasksCount = 0; + + for (comp = comps_head; comp; comp = comp->next) { + if (comp->Free) comp->Free(); + } + + gameRunning = false; + Logger_WarnFunc = Logger_DialogWarn; + Gfx_Free(); + Options_SaveIfChanged(); + Window_DisableRawMouse(); +} + +#define Game_DoFrameBody() \ + render = Stopwatch_Measure();\ + delta = Stopwatch_ElapsedMicroseconds(Game_FrameStart, render) / (1000.0 * 1000.0);\ + \ + Window_ProcessEvents(delta);\ + if (!gameRunning) return;\ + \ + if (delta > 5.0) delta = 5.0; /* avoid large delta with suspended process */ \ + if (delta > 0.0) { Game_FrameStart = render; Game_RenderFrame(delta); } + +#ifdef CC_BUILD_WEB +void Game_DoFrame(void) { + cc_uint64 render; + double delta; + Game_DoFrameBody() +} + +static void Game_RunLoop(void) { + Game_FrameStart = Stopwatch_Measure(); + /* Window_Web.c sets Game_DoFrame as the main loop callback function */ + /* (i.e. web browser is in charge of calling Game_DoFrame, not us) */ +} + +cc_bool Game_ShouldClose(void) { + if (Server.IsSinglePlayer) { + /* Close if map was saved within last 5 seconds */ + return World.LastSave + 5 >= Game.Time; + } + + /* Try to intercept Ctrl+W or Cmd+W for multiplayer */ + if (Input_IsCtrlPressed() || Input_IsWinPressed()) return false; + /* Also try to intercept mouse back button (Mouse4) */ + return !Input.Pressed[CCMOUSE_X1]; +} +#else +static void Game_RunLoop(void) { + cc_uint64 render; + double delta; + + Game_FrameStart = Stopwatch_Measure(); + for (;;) { Game_DoFrameBody() } +} +#endif + +void Game_Run(int width, int height, const cc_string* title) { + Window_Create3D(width, height); + Window_SetTitle(title); + Window_Show(); + gameRunning = true; + + Game_Load(); + Event_RaiseVoid(&WindowEvents.Resized); + Game_RunLoop(); +} |