mirror of
https://github.com/ClassiCube/ClassiCube.git
synced 2025-09-07 22:34:43 -04:00
549 lines
18 KiB
C
549 lines
18 KiB
C
#include "Launcher.h"
|
|
#ifndef CC_BUILD_WEB
|
|
#include "String.h"
|
|
#include "LScreens.h"
|
|
#include "LWidgets.h"
|
|
#include "LWeb.h"
|
|
#include "Resources.h"
|
|
#include "Drawer2D.h"
|
|
#include "Game.h"
|
|
#include "Deflate.h"
|
|
#include "Stream.h"
|
|
#include "Utils.h"
|
|
#include "Input.h"
|
|
#include "Window.h"
|
|
#include "Event.h"
|
|
#include "Http.h"
|
|
#include "ExtMath.h"
|
|
#include "Funcs.h"
|
|
#include "Logger.h"
|
|
#include "Options.h"
|
|
#include "LBackend.h"
|
|
#include "PackedCol.h"
|
|
#include "SystemFonts.h"
|
|
#include "TexturePack.h"
|
|
|
|
struct LScreen* Launcher_Active;
|
|
cc_bool Launcher_ShouldExit, Launcher_ShouldUpdate;
|
|
static char hashBuffer[STRING_SIZE], userBuffer[STRING_SIZE];
|
|
cc_string Launcher_AutoHash = String_FromArray(hashBuffer);
|
|
cc_string Launcher_Username = String_FromArray(userBuffer);
|
|
cc_bool Launcher_ShowEmptyServers;
|
|
|
|
static cc_bool useBitmappedFont, hasBitmappedFont;
|
|
static struct Bitmap dirtBmp, stoneBmp;
|
|
#define TILESIZE 48
|
|
|
|
static void CloseActiveScreen(void) {
|
|
Window_CloseKeyboard();
|
|
if (!Launcher_Active) return;
|
|
|
|
Launcher_Active->Deactivated(Launcher_Active);
|
|
LBackend_CloseScreen(Launcher_Active);
|
|
Launcher_Active = NULL;
|
|
}
|
|
|
|
void Launcher_SetScreen(struct LScreen* screen) {
|
|
CloseActiveScreen();
|
|
Launcher_Active = screen;
|
|
|
|
screen->Activated(screen);
|
|
screen->Layout(screen);
|
|
|
|
if (!screen->everShown) screen->LoadState(screen);
|
|
screen->everShown = true;
|
|
|
|
LBackend_SetScreen(screen);
|
|
LBackend_Redraw();
|
|
}
|
|
|
|
void Launcher_DisplayHttpError(struct HttpRequest* req, const char* action, cc_string* dst) {
|
|
cc_result res = req->result;
|
|
int status = req->statusCode;
|
|
|
|
if (res) {
|
|
/* Non HTTP error - this is not good */
|
|
Http_LogError(action, req);
|
|
String_Format2(dst, res >= 0x80000000 ? "&cError %h when %c" : "&cError %i when %c",
|
|
&res, action);
|
|
} else if (status != 200) {
|
|
String_Format2(dst, "&c%i error when %c", &status, action);
|
|
} else {
|
|
String_Format1(dst, "&cEmpty response when %c", action);
|
|
}
|
|
}
|
|
|
|
|
|
/*########################################################################################################################*
|
|
*--------------------------------------------------------Starter/Updater--------------------------------------------------*
|
|
*#########################################################################################################################*/
|
|
static cc_uint64 lastJoin;
|
|
cc_bool Launcher_StartGame(const cc_string* user, const cc_string* mppass, const cc_string* ip, const cc_string* port, const cc_string* server) {
|
|
cc_string args[4]; int numArgs;
|
|
cc_uint64 now;
|
|
cc_result res;
|
|
|
|
now = Stopwatch_Measure();
|
|
if (Stopwatch_ElapsedMS(lastJoin, now) < 1000) return false;
|
|
lastJoin = now;
|
|
|
|
/* Save resume info */
|
|
if (server->length) {
|
|
Options_PauseSaving();
|
|
Options_Set(ROPT_SERVER, server);
|
|
Options_Set(ROPT_USER, user);
|
|
Options_Set(ROPT_IP, ip);
|
|
Options_Set(ROPT_PORT, port);
|
|
Options_SetSecure(ROPT_MPPASS, mppass);
|
|
Options_ResumeSaving();
|
|
}
|
|
/* Save options BEFORE starting new game process */
|
|
/* Otherwise can get 'file already in use' errors on startup */
|
|
Options_SaveIfChanged();
|
|
|
|
args[0] = *user;
|
|
numArgs = 1;
|
|
if (mppass->length) {
|
|
args[1] = *mppass;
|
|
args[2] = *ip;
|
|
args[3] = *port;
|
|
numArgs = 4;
|
|
}
|
|
|
|
res = Process_StartGame2(args, numArgs);
|
|
if (res) { Logger_SysWarn(res, "starting game"); return false; }
|
|
|
|
Launcher_ShouldExit = Platform_SingleProcess || Options_GetBool(LOPT_AUTO_CLOSE, false);
|
|
return true;
|
|
}
|
|
|
|
CC_NOINLINE static void StartFromInfo(struct ServerInfo* info) {
|
|
cc_string port; char portBuffer[STRING_INT_CHARS];
|
|
String_InitArray(port, portBuffer);
|
|
|
|
String_AppendInt(&port, info->port);
|
|
Launcher_StartGame(&Launcher_Username, &info->mppass, &info->ip, &port, &info->name);
|
|
}
|
|
|
|
static void ConnectToServerError(struct HttpRequest* req) {
|
|
cc_string logMsg = String_Init(NULL, 0, 0);
|
|
Launcher_DisplayHttpError(req, "fetching server info", &logMsg);
|
|
}
|
|
|
|
cc_bool Launcher_ConnectToServer(const cc_string* hash) {
|
|
struct ServerInfo* info;
|
|
int i;
|
|
if (!hash->length) return false;
|
|
|
|
for (i = 0; i < FetchServersTask.numServers; i++) {
|
|
info = &FetchServersTask.servers[i];
|
|
if (!String_Equals(hash, &info->hash)) continue;
|
|
|
|
StartFromInfo(info);
|
|
return true;
|
|
}
|
|
|
|
/* Fallback to private server handling */
|
|
/* TODO: Rewrite to be async */
|
|
FetchServerTask_Run(hash);
|
|
|
|
while (!FetchServerTask.Base.completed) {
|
|
LWebTask_Tick(&FetchServerTask.Base, ConnectToServerError);
|
|
Thread_Sleep(10);
|
|
}
|
|
|
|
if (FetchServerTask.server.hash.length) {
|
|
StartFromInfo(&FetchServerTask.server);
|
|
return true;
|
|
} else if (FetchServerTask.Base.success) {
|
|
Window_ShowDialog("Failed to connect", "No server has that hash");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/*########################################################################################################################*
|
|
*---------------------------------------------------------Event handler---------------------------------------------------*
|
|
*#########################################################################################################################*/
|
|
static void OnResize(void* obj) {
|
|
LBackend_FreeFramebuffer();
|
|
LBackend_InitFramebuffer();
|
|
|
|
if (Launcher_Active) Launcher_Active->Layout(Launcher_Active);
|
|
LBackend_Redraw();
|
|
}
|
|
|
|
static cc_bool IsShutdown(int key) {
|
|
if (key == CCKEY_F4 && Input_IsAltPressed()) return true;
|
|
|
|
/* On macOS, Cmd+Q should also end the process */
|
|
#ifdef CC_BUILD_DARWIN
|
|
return key == 'Q' && Input_IsWinPressed();
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
static void OnInputDown(void* obj, int key, cc_bool was) {
|
|
if (IsShutdown(key)) Launcher_ShouldExit = true;
|
|
Launcher_Active->KeyDown(Launcher_Active, key, was);
|
|
}
|
|
|
|
static void OnMouseWheel(void* obj, float delta) {
|
|
Launcher_Active->MouseWheel(Launcher_Active, delta);
|
|
}
|
|
|
|
|
|
/*########################################################################################################################*
|
|
*-----------------------------------------------------------Main body-----------------------------------------------------*
|
|
*#########################################################################################################################*/
|
|
static void Launcher_Init(void) {
|
|
Event_Register_(&WindowEvents.Resized, NULL, OnResize);
|
|
Event_Register_(&WindowEvents.StateChanged, NULL, OnResize);
|
|
|
|
Event_Register_(&InputEvents.Down, NULL, OnInputDown);
|
|
Event_Register_(&InputEvents.Wheel, NULL, OnMouseWheel);
|
|
|
|
Utils_EnsureDirectory("texpacks");
|
|
Utils_EnsureDirectory("audio");
|
|
}
|
|
|
|
static void Launcher_Free(void) {
|
|
Event_UnregisterAll();
|
|
LBackend_Free();
|
|
Flags_Free();
|
|
hasBitmappedFont = false;
|
|
|
|
CloseActiveScreen();
|
|
LBackend_FreeFramebuffer();
|
|
}
|
|
|
|
void Launcher_Run(void) {
|
|
static const cc_string title = String_FromConst(GAME_APP_TITLE);
|
|
Window_Create2D(640, 400);
|
|
#ifdef CC_BUILD_MOBILE
|
|
Window_LockLandscapeOrientation(Options_GetBool(OPT_LANDSCAPE_MODE, false));
|
|
#endif
|
|
Window_SetTitle(&title);
|
|
Window_Show();
|
|
LWidget_CalcOffsets();
|
|
|
|
#ifdef CC_BUILD_WIN
|
|
/* clean leftover exe from updating */
|
|
if (Options_GetBool("update-dirty", false) && Updater_Clean()) {
|
|
Options_Set("update-dirty", NULL);
|
|
}
|
|
#endif
|
|
|
|
Drawer2D_Component.Init();
|
|
SystemFonts_Component.Init();
|
|
Drawer2D.BitmappedText = false;
|
|
Drawer2D.BlackTextShadows = true;
|
|
|
|
LBackend_Init();
|
|
LBackend_InitFramebuffer();
|
|
Launcher_ShowEmptyServers = Options_GetBool(LOPT_SHOW_EMPTY, true);
|
|
Options_Get(LOPT_USERNAME, &Launcher_Username, "");
|
|
|
|
LWebTasks_Init();
|
|
Session_Load();
|
|
Launcher_LoadTheme();
|
|
Launcher_Init();
|
|
|
|
GameVersion_Load();
|
|
Launcher_TryLoadTexturePack();
|
|
|
|
Http_Component.Init();
|
|
CheckUpdateTask_Run();
|
|
|
|
#ifdef CC_BUILD_RESOURCES
|
|
Resources_CheckExistence();
|
|
|
|
if (Resources_Count) {
|
|
CheckResourcesScreen_SetActive();
|
|
} else {
|
|
MainScreen_SetActive();
|
|
}
|
|
#else
|
|
MainScreen_SetActive();
|
|
#endif
|
|
|
|
for (;;) {
|
|
Window_ProcessEvents(10 / 1000.0);
|
|
if (!WindowInfo.Exists || Launcher_ShouldExit) break;
|
|
|
|
Launcher_Active->Tick(Launcher_Active);
|
|
LBackend_Tick();
|
|
Thread_Sleep(10);
|
|
}
|
|
|
|
Options_SaveIfChanged();
|
|
Launcher_Free();
|
|
Launcher_ShouldExit = false;
|
|
|
|
#ifdef CC_BUILD_MOBILE
|
|
/* Reset components */
|
|
Platform_LogConst("undoing components");
|
|
Drawer2D_Component.Free();
|
|
Http_Component.Free();
|
|
#else
|
|
if (Launcher_ShouldUpdate) {
|
|
const char* action;
|
|
cc_result res = Updater_Start(&action);
|
|
if (res) Logger_SysWarn(res, action);
|
|
}
|
|
|
|
if (WindowInfo.Exists) Window_RequestClose();
|
|
#endif
|
|
}
|
|
|
|
|
|
/*########################################################################################################################*
|
|
*---------------------------------------------------------Colours/Skin----------------------------------------------------*
|
|
*#########################################################################################################################*/
|
|
struct LauncherTheme Launcher_Theme;
|
|
const struct LauncherTheme Launcher_ModernTheme = {
|
|
false,
|
|
BitmapColor_RGB(153, 127, 172), /* background */
|
|
BitmapColor_RGB( 97, 81, 110), /* button border */
|
|
BitmapColor_RGB(189, 168, 206), /* active button */
|
|
BitmapColor_RGB(141, 114, 165), /* button foreground */
|
|
BitmapColor_RGB(162, 131, 186), /* button highlight */
|
|
};
|
|
const struct LauncherTheme Launcher_ClassicTheme = {
|
|
true,
|
|
BitmapColor_RGB( 41, 41, 41), /* background */
|
|
BitmapColor_RGB( 0, 0, 0), /* button border */
|
|
BitmapColor_RGB(126, 136, 191), /* active button */
|
|
BitmapColor_RGB(111, 111, 111), /* button foreground */
|
|
BitmapColor_RGB(168, 168, 168), /* button highlight */
|
|
};
|
|
const struct LauncherTheme Launcher_NordicTheme = {
|
|
false,
|
|
BitmapColor_RGB( 46, 52, 64), /* background */
|
|
BitmapColor_RGB( 59, 66, 82), /* button border */
|
|
BitmapColor_RGB( 66, 74, 90), /* active button */
|
|
BitmapColor_RGB( 59, 66, 82), /* button foreground */
|
|
BitmapColor_RGB( 76, 86, 106), /* button highlight */
|
|
};
|
|
|
|
CC_NOINLINE static void ParseColor(const char* key, BitmapCol* color) {
|
|
cc_uint8 rgb[3];
|
|
if (!Options_GetColor(key, rgb)) return;
|
|
|
|
*color = BitmapColor_RGB(rgb[0], rgb[1], rgb[2]);
|
|
}
|
|
|
|
void Launcher_LoadTheme(void) {
|
|
if (Options_GetBool(OPT_CLASSIC_MODE, false)) {
|
|
Launcher_Theme = Launcher_ClassicTheme;
|
|
return;
|
|
}
|
|
Launcher_Theme = Launcher_ModernTheme;
|
|
Launcher_Theme.ClassicBackground = Options_GetBool("nostalgia-classicbg", false);
|
|
|
|
ParseColor("launcher-back-col", &Launcher_Theme.BackgroundColor);
|
|
ParseColor("launcher-btn-border-col", &Launcher_Theme.ButtonBorderColor);
|
|
ParseColor("launcher-btn-fore-active-col", &Launcher_Theme.ButtonForeActiveColor);
|
|
ParseColor("launcher-btn-fore-inactive-col", &Launcher_Theme.ButtonForeColor);
|
|
ParseColor("launcher-btn-highlight-inactive-col", &Launcher_Theme.ButtonHighlightColor);
|
|
}
|
|
|
|
CC_NOINLINE static void SaveColor(const char* key, BitmapCol color) {
|
|
cc_string value; char valueBuffer[6];
|
|
|
|
String_InitArray(value, valueBuffer);
|
|
String_AppendHex(&value, BitmapCol_R(color));
|
|
String_AppendHex(&value, BitmapCol_G(color));
|
|
String_AppendHex(&value, BitmapCol_B(color));
|
|
Options_Set(key, &value);
|
|
}
|
|
|
|
void Launcher_SaveTheme(void) {
|
|
Options_PauseSaving();
|
|
SaveColor("launcher-back-col", Launcher_Theme.BackgroundColor);
|
|
SaveColor("launcher-btn-border-col", Launcher_Theme.ButtonBorderColor);
|
|
SaveColor("launcher-btn-fore-active-col", Launcher_Theme.ButtonForeActiveColor);
|
|
SaveColor("launcher-btn-fore-inactive-col", Launcher_Theme.ButtonForeColor);
|
|
SaveColor("launcher-btn-highlight-inactive-col", Launcher_Theme.ButtonHighlightColor);
|
|
Options_SetBool("nostalgia-classicbg", Launcher_Theme.ClassicBackground);
|
|
Options_ResumeSaving();
|
|
}
|
|
|
|
|
|
/*########################################################################################################################*
|
|
*---------------------------------------------------------Texture pack----------------------------------------------------*
|
|
*#########################################################################################################################*/
|
|
/* Tints the given area, linearly interpolating from a to b */
|
|
/* Note that this only tints RGB, A is not tinted */
|
|
static void TintBitmap(struct Bitmap* bmp, cc_uint8 tintA, cc_uint8 tintB, int width, int height) {
|
|
BitmapCol* row;
|
|
cc_uint8 tint;
|
|
int xx, yy;
|
|
|
|
for (yy = 0; yy < height; yy++) {
|
|
row = Bitmap_GetRow(bmp, yy);
|
|
tint = (cc_uint8)Math_Lerp(tintA, tintB, (float)yy / height);
|
|
|
|
for (xx = 0; xx < width; xx++) {
|
|
/* TODO: Not shift when multiplying */
|
|
row[xx] = BitmapColor_RGB(
|
|
BitmapCol_R(row[xx]) * tint / 255,
|
|
BitmapCol_G(row[xx]) * tint / 255,
|
|
BitmapCol_B(row[xx]) * tint / 255);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void ExtractTerrainTiles(struct Bitmap* bmp) {
|
|
int tileSize = bmp->width / 16;
|
|
Bitmap_Allocate(&dirtBmp, TILESIZE, TILESIZE);
|
|
Bitmap_Allocate(&stoneBmp, TILESIZE, TILESIZE);
|
|
|
|
/* Precompute the scaled background */
|
|
Bitmap_Scale(&dirtBmp, bmp, 2 * tileSize, 0, tileSize, tileSize);
|
|
Bitmap_Scale(&stoneBmp, bmp, 1 * tileSize, 0, tileSize, tileSize);
|
|
|
|
TintBitmap(&dirtBmp, 128, 64, TILESIZE, TILESIZE);
|
|
TintBitmap(&stoneBmp, 96, 96, TILESIZE, TILESIZE);
|
|
}
|
|
|
|
static cc_bool Launcher_SelectZipEntry(const cc_string* path) {
|
|
return
|
|
String_CaselessEqualsConst(path, "default.png") ||
|
|
String_CaselessEqualsConst(path, "terrain.png");
|
|
}
|
|
|
|
static cc_result Launcher_ProcessZipEntry(const cc_string* path, struct Stream* data, struct ZipEntry* source) {
|
|
struct Bitmap bmp;
|
|
cc_result res;
|
|
|
|
if (String_CaselessEqualsConst(path, "default.png")) {
|
|
if (hasBitmappedFont) return 0;
|
|
hasBitmappedFont = false;
|
|
res = Png_Decode(&bmp, data);
|
|
|
|
if (res) {
|
|
Logger_SysWarn(res, "decoding default.png"); return res;
|
|
} else if (Font_SetBitmapAtlas(&bmp)) {
|
|
useBitmappedFont = !Options_GetBool(OPT_USE_CHAT_FONT, false);
|
|
hasBitmappedFont = true;
|
|
} else {
|
|
Mem_Free(bmp.scan0);
|
|
}
|
|
} else if (String_CaselessEqualsConst(path, "terrain.png")) {
|
|
if (dirtBmp.scan0 != NULL) return 0;
|
|
res = Png_Decode(&bmp, data);
|
|
|
|
if (res) {
|
|
Logger_SysWarn(res, "decoding terrain.png"); return res;
|
|
} else {
|
|
ExtractTerrainTiles(&bmp);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static cc_result ExtractTexturePack(const cc_string* path) {
|
|
struct Stream stream;
|
|
cc_result res;
|
|
|
|
res = Stream_OpenFile(&stream, path);
|
|
if (res == ReturnCode_FileNotFound) return res;
|
|
if (res) { Logger_SysWarn(res, "opening texture pack"); return res; }
|
|
|
|
res = Zip_Extract(&stream,
|
|
Launcher_SelectZipEntry, Launcher_ProcessZipEntry);
|
|
|
|
if (res) { Logger_SysWarn(res, "extracting texture pack"); }
|
|
/* No point logging error for closing readonly file */
|
|
(void)stream.Close(&stream);
|
|
return res;
|
|
}
|
|
|
|
void Launcher_TryLoadTexturePack(void) {
|
|
cc_string path; char pathBuffer[FILENAME_SIZE];
|
|
cc_string texPack;
|
|
|
|
/* TODO: Not duplicate TexturePack functionality */
|
|
if (Options_UNSAFE_Get(OPT_DEFAULT_TEX_PACK, &texPack)) {
|
|
String_InitArray(path, pathBuffer);
|
|
String_Format1(&path, "texpacks/%s", &texPack);
|
|
(void)ExtractTexturePack(&path);
|
|
}
|
|
|
|
/* user selected texture pack is missing some required .png files */
|
|
if (!hasBitmappedFont || dirtBmp.scan0 == NULL)
|
|
TexturePack_ExtractDefault(ExtractTexturePack);
|
|
|
|
LBackend_UpdateTitleFont();
|
|
}
|
|
|
|
|
|
/*########################################################################################################################*
|
|
*----------------------------------------------------------Background-----------------------------------------------------*
|
|
*#########################################################################################################################*/
|
|
/* Fills the given area using pixels from the source bitmap, by repeatedly tiling the bitmap */
|
|
CC_NOINLINE static void ClearTile(int x, int y, int width, int height,
|
|
struct Context2D* ctx, struct Bitmap* src) {
|
|
struct Bitmap* dst = (struct Bitmap*)ctx;
|
|
BitmapCol* dstRow;
|
|
BitmapCol* srcRow;
|
|
int xx, yy;
|
|
if (!Drawer2D_Clamp(ctx, &x, &y, &width, &height)) return;
|
|
|
|
for (yy = 0; yy < height; yy++) {
|
|
srcRow = Bitmap_GetRow(src, (y + yy) % TILESIZE);
|
|
dstRow = Bitmap_GetRow(dst, y + yy) + x;
|
|
|
|
for (xx = 0; xx < width; xx++) {
|
|
dstRow[xx] = srcRow[(x + xx) % TILESIZE];
|
|
}
|
|
}
|
|
}
|
|
|
|
void Launcher_DrawBackground(struct Context2D* ctx, int x, int y, int width, int height) {
|
|
if (Launcher_Theme.ClassicBackground && dirtBmp.scan0) {
|
|
ClearTile(x, y, width, height, ctx, &stoneBmp);
|
|
} else {
|
|
Gradient_Noise(ctx, Launcher_Theme.BackgroundColor, 6, x, y, width, height);
|
|
}
|
|
}
|
|
|
|
void Launcher_DrawBackgroundAll(struct Context2D* ctx) {
|
|
if (Launcher_Theme.ClassicBackground && dirtBmp.scan0) {
|
|
ClearTile(0, 0, ctx->width, TILESIZE, ctx, &dirtBmp);
|
|
ClearTile(0, TILESIZE, ctx->width, ctx->height - TILESIZE, ctx, &stoneBmp);
|
|
} else {
|
|
Launcher_DrawBackground(ctx, 0, 0, ctx->width, ctx->height);
|
|
}
|
|
}
|
|
|
|
cc_bool Launcher_BitmappedText(void) {
|
|
return (useBitmappedFont || Launcher_Theme.ClassicBackground) && hasBitmappedFont;
|
|
}
|
|
|
|
void Launcher_DrawTitle(struct FontDesc* font, const char* text, struct Context2D* ctx) {
|
|
cc_string title = String_FromReadonly(text);
|
|
struct DrawTextArgs args;
|
|
int x;
|
|
|
|
/* Skip dragging logo when very small window to save space */
|
|
if (WindowInfo.Height < 300) return;
|
|
|
|
DrawTextArgs_Make(&args, &title, font, false);
|
|
x = ctx->width / 2 - Drawer2D_TextWidth(&args) / 2;
|
|
|
|
Drawer2D.Colors['f'] = BITMAPCOLOR_BLACK;
|
|
Context2D_DrawText(ctx, &args, x + Display_ScaleX(4), Display_ScaleY(4));
|
|
Drawer2D.Colors['f'] = BITMAPCOLOR_WHITE;
|
|
Context2D_DrawText(ctx, &args, x, 0);
|
|
}
|
|
|
|
void Launcher_MakeTitleFont(struct FontDesc* font) {
|
|
Drawer2D.BitmappedText = Launcher_BitmappedText();
|
|
Font_Make(font, 32, FONT_FLAGS_NONE);
|
|
Drawer2D.BitmappedText = false;
|
|
}
|
|
#endif
|