Compare commits

...

23 Commits

Author SHA1 Message Date
Cameron Murphy Reikes 0ebd7edb45 Fix misprediction on grid hole detection bug
- Could cause problems! uh oh! But SHOULD be fine
1 year ago
Cameron Murphy Reikes bffd3615a3 BeforePatreon small tweaks 1 year ago
Cameron Murphy Reikes 63dff1ae7e Server lagging in log 1 year ago
Cameron Murphy Reikes 0b29976ced Consolodate to @BeforePatreon, some @Robust work 1 year ago
Cameron Murphy Reikes 25ac2f3794 Merge branch 'main' of https://github.com/creikey/flight 1 year ago
Cameron Murphy Reikes da855c9632 Tweaks 1 year ago
Phillip Trudeau-Tavara 9f744cc396 Merge branch 'main' of https://github.com/creikey/flight 1 year ago
Phillip Trudeau-Tavara 83d4f13695 Initial crash dump reporting (broken!) 1 year ago
Cameron Murphy Reikes 7c96b1bb51 Increment release tag 1 year ago
Cameron Murphy Reikes 5e87a41832 Merge branch 'main' of https://github.com/creikey/flight 1 year ago
Cameron Murphy Reikes 41192181f6 More stable solar system, gyro by default 1 year ago
Phillip Trudeau-Tavara 5b0ddb847e Add sokol-shdc.exe to repo 1 year ago
Cameron Murphy Reikes 34f5c03f38 Press tab to easy zoom 1 year ago
Cameron Murphy Reikes dbb22ecffd More robustness 1 year ago
Cameron Murphy Reikes be1e694ad2 Tiny stuff 1 year ago
Cameron Murphy Reikes cc184ad7f8 Add license 1 year ago
Cameron Murphy Reikes 1add4a580c Fix borked dialog box on release, defined unicode 1 year ago
Cameron Murphy Reikes 6d50ebc50a Remove some quits, replace with clean dialog 1 year ago
Cameron Murphy Reikes 59b4363b45 Don't include windows on linux! 1 year ago
Cameron Murphy Reikes fcaafff993 Git release tag stuff 1 year ago
Cameron Murphy Reikes 5de1a39c21 Updated release flow instructions 1 year ago
Cameron Murphy Reikes eb7ae3c3c5 Rename assert, popup box on assert failure 1 year ago
Cameron Murphy Reikes 3410df7941 Move server port to build settings 1 year ago

6
.gitignore vendored

@ -1,5 +1,7 @@
# profiling results
*.spall
# logs
astris_log.txt
compile_commands.json
.cache/
enc_temp_folder/
@ -12,10 +14,12 @@ flight.zip
releases/
.vs/
x64/
*.exe
flight_debug.exe
flight_release.exe
*.obj
*.pdb
*.exp
*.lib
*.ilk
*.gen.h
CrashDump_*.dmp

@ -186,6 +186,7 @@
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="crash_handler.cpp" />
<ClCompile Include="debugdraw.c" />
<ClCompile Include="gamestate.c" />
<ClCompile Include="main.c" />

@ -153,6 +153,9 @@
<ClCompile Include="thirdparty\minilzo\minilzo.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="crash_handler.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="ipsettings.h">

@ -0,0 +1,32 @@
End User License Agreement (EULA)
The Software ("Astris", "astris.exe") is Copyright 2022 by Cameron Murphy Reikes,
("Licensor"), all rights are reserved. Anyone who has purchased a copy of
the Software, ("Licensee"), is granted a personal, non-assignable,
non-transferable, license to use the Software for personal or commercial
uses.
By installing, copying, or otherwise using the Software, Licensee agrees to be
bound by the terms and conditions set forth in this EULA. If Licensee does not
agree to the terms and conditions set forth in this EULA, then Licensee may not
use the Software.
The User may not redistribute, sub-license, or resell the Software. The User is
permitted to make an unlimited number of copies of the Software which are
subject to the aforementioned restrictions.
Warranty Disclaimer. Licensor hereby expressly disclaim any warranty for the
Software. The Software is provided "as is" without warranty of any kind, either
express or implied, including: without limitation, the implied warranties of
merchantability, fitness for a particular purpose, or non-infringement. Licensee
accepts any and all risk arising out of use or performance of the Software.
Limitation of Liability. Licensor shall not be liable to Licensee, or any other
person or entity claiming through Licensee any loss of profits, income, savings,
or any other consequential, incidental, special, punitive, direct or
indirect damage, whether arising in contract, tort, warranty, or
otherwise. These limitations shall apply regardless of the essential
purpose of any limited remedy. Under no circumstances shall Licensor's
aggregate liability to Licensee, or any other person or entity claiming
through Licensee, exceed the financial amount actually paid by Licensee
to Licensor for the Software.

@ -1,3 +1,3 @@
call shadergen.bat
set compileopts=/Fe"flight_debug" /Zi /FS /Fd"flight.pdb" /DDEBUG_RENDERING /DDEBUG
set compileopts=/Fe"flight_debug" /Zi /FS /Fd"flight.pdb" /DDEBUG
call build_msvc.bat

@ -1,30 +1,39 @@
#pragma once
#ifdef SERVER_ADDRESS
#error Don't define server address from the build system, use the build settings header
#error Dont define server address from the build system, use the build settings header
#endif
#define SERVER_PORT 2551
// must be unsigned integer
#define GIT_RELEASE_TAG 23
#ifdef DEBUG
#define SERVER_ADDRESS "127.0.0.1"
#define ASSERT_DO_POPUP_AND_CRASH
// #define SERVER_ADDRESS "207.246.80.160"
//#define PROFILING
// #define PROFILING
#define DEBUG_RENDERING
//#define DEBUG_WORLD
//#define UNLOCK_ALL
//#define INFINITE_RESOURCES
//#define NO_GRAVITY
//#define NO_SUNS
// #define DEBUG_WORLD
// #define UNLOCK_ALL
#define INFINITE_RESOURCES
// #define DEBUG_TOOLS
// #define FAT_THRUSTERS
// #define NO_GRAVITY
// #define NO_SUNS
#else
#ifdef RELEASE
// DANGER modifying these, make sure to change them back before releasing
// #define PROFILING
// #define SERVER_ADDRESS "127.0.0.1"
#define SERVER_ADDRESS "207.246.80.160"
#define ASSERT_DO_POPUP_AND_CRASH
#else
#error Define either DEBUG or RELEASE

@ -0,0 +1,573 @@
#include "buildsettings.h"
#define _CRT_SECURE_NO_WARNINGS
#include <Windows.h>
#include <winternl.h>
#include <DbgHelp.h>
#include <commctrl.h>
#include <winhttp.h>
#include <shellapi.h>
#include <shlwapi.h>
#include <stdio.h>
#include <time.h>
#include <stdint.h>
#ifndef defer
struct defer_dummy
{
};
template <class F>
struct deferrer
{
F f;
~deferrer() { f(); }
};
template <class F>
deferrer<F> operator*(defer_dummy, F f) { return {f}; }
#define DEFER_(LINE) zz_defer##LINE
#define DEFER(LINE) DEFER_(LINE)
#define defer auto DEFER(__LINE__) = defer_dummy{} *[&]()
#endif // defer
#include <Shlobj.h>
#pragma comment(lib, "Ole32.lib")
#pragma comment(lib, "Oleaut32.lib")
#pragma comment(lib, "Shell32.lib")
bool view_file_in_system_file_browser(const wchar_t *wfullpath)
{
bool res = false;
if (wfullpath)
{
{ // Good version of the functionality
const wchar_t *wpath = wfullpath;
if (!wcsncmp(wfullpath, L"\\\\?\\", 4))
{
wpath += 4;
}
if (wpath)
{
HRESULT hr = CoInitialize(nullptr);
if (hr == S_OK || hr == S_FALSE)
{
PIDLIST_ABSOLUTE pidl;
SFGAOF flags;
if (SHParseDisplayName(wpath, nullptr, &pidl, 0, &flags) == S_OK)
{
if (SHOpenFolderAndSelectItems(pidl, 0, nullptr, 0) == S_OK)
{
res = true;
}
CoTaskMemFree(pidl);
}
CoUninitialize();
}
}
}
if (!res)
{ // Worse Fallback if good version fails
WCHAR wcmd[65536];
wnsprintfW(wcmd, ARRAYSIZE(wcmd), L"explorer /select,\"%s\"", wfullpath);
if (_wsystem(wcmd) >= 0)
{
res = true;
}
}
}
return res;
}
#define DISCORD_WEBHOOK_ENDPOINT "/api/webhooks/922366591751053342/pR59Za5xL1HcvGSrxJ7hb1UCa85zfdtZyDet15I_CZgrY9RkAq73uAQ2Obo1Zi9QBvvX"
#define ErrBox(msg, flags) MessageBoxW(nullptr, L"" msg, L"Fatal Error", flags | MB_SYSTEMMODAL | MB_SETFOREGROUND)
#define fatal_init_error(s) ErrBox("The game had a fatal error and must close.\n\"" s "\"\nPlease report this to team@happenlance.com or the Happenlance Discord.", MB_OK | MB_ICONERROR)
extern "C" {
// TODO: Internationalization for error messages.
#pragma comment(lib, "DbgHelp.lib")
#pragma comment(lib, "ComCtl32.lib")
#pragma comment(lib, "WinHTTP.lib")
#pragma comment(lib, "Shlwapi.lib")
void do_crash_handler()
{
// Get the command line
int argc = 0;
wchar_t *cmd = GetCommandLineW();
if (!cmd || !cmd[0])
{
return; // Error: just run the app without a crash handler.
}
wchar_t **wargv = CommandLineToArgvW(cmd, &argc); // Passing nullptr here crashes!
if (!wargv || !wargv[0])
{
return; // Error: just run the app without a crash handler.
}
// Parse the command line for -no-crash-handler
bool crashHandler = true;
for (int i = 0; i < argc; ++i)
{
if (!wcscmp(wargv[i], L"-no-crash-handler"))
{
crashHandler = false;
}
}
if (!crashHandler)
{ // We already *are* the subprocess - continue with the main program!
return;
}
// Concatenate -no-crash-handler onto the command line for the subprocess
int cmdLen = 0;
while (cmd[cmdLen])
{ // could use wcslen() here, but Clang ASan's wcslen() can be bugged sometimes
cmdLen++;
}
const wchar_t *append = L" -no-crash-handler";
int appendLen = 0;
while (append[appendLen])
{
appendLen++;
}
wchar_t *cmdNew = (wchar_t *)calloc(cmdLen + appendLen + 1, sizeof(wchar_t)); // @Leak
if (!cmdNew)
{
return; // Error: just run the app without a crash handler.
}
memcpy(cmdNew, cmd, cmdLen * sizeof(wchar_t));
memcpy(cmdNew + cmdLen, append, appendLen * sizeof(wchar_t));
// Crash handler loop: run the program until it succeeds or the user chooses not to restart it
restart:;
// Parameters for starting the subprocess
STARTUPINFOW siw = {};
siw.cb = sizeof(siw);
siw.dwFlags = STARTF_USESTDHANDLES;
siw.hStdInput = GetStdHandle(STD_INPUT_HANDLE); // @Leak: CloseHandle()
siw.hStdOutput = GetStdHandle(STD_ERROR_HANDLE);
siw.hStdError = GetStdHandle(STD_OUTPUT_HANDLE);
PROCESS_INFORMATION pi = {}; // @Leak: CloseHandle()
// Launch suspended, then read-modify-write the PEB (see below), then resume -p 2022-03-04
if (!CreateProcessW(nullptr, cmdNew, nullptr, nullptr, true,
CREATE_SUSPENDED | DEBUG_ONLY_THIS_PROCESS, nullptr, nullptr, &siw, &pi))
{
// If we couldn't create a subprocess, then just run the program without a crash handler.
// That's not great, but it's presumably better than stopping the user from running at all!
return;
}
// NOTE: SteamAPI_Init() takes WAY longer On My Machine(tm) when a debugger is present.
// (The DLL file steam_api64.dll does indeed call IsDebuggerPresent() sometimes.)
// It's clear that Steam does extra niceness for us when debugging, but we DO NOT
// want this to destroy our load times; I measure 3.5x slowdown (0.6s -> 2.1s).
// The only way I know to trick the child process into thinking it is free of a
// debugger is to clear the BeingDebugged byte in the Process Environment Block.
// If we are unable to perform this advanced maneuver, we will gracefully step back
// and allow Steam to ruin our loading times. -p 2022-03-04
auto persuade_process_no_debugger_is_present = [](HANDLE hProcess)
{
// Load NTDLL
HMODULE ntdll = LoadLibraryA("ntdll.dll");
if (!ntdll)
return;
// Get NtQueryInformationProcess function
auto NtQueryInformationProcess =
(/*__kernel_entry*/ NTSTATUS(*)(HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG))
GetProcAddress(ntdll, "NtQueryInformationProcess");
if (!NtQueryInformationProcess)
return;
// Query process information to find the PEB address
PROCESS_BASIC_INFORMATION pbi = {};
DWORD queryBytesRead = 0;
if (NtQueryInformationProcess(hProcess, ProcessBasicInformation, &pbi, sizeof(pbi), &queryBytesRead) != 0 || queryBytesRead != sizeof(pbi))
return;
// Read the PEB of the child process
PEB peb = {};
SIZE_T processBytesRead = NULL;
if (!ReadProcessMemory(hProcess, pbi.PebBaseAddress, &peb, sizeof(peb), &processBytesRead) || processBytesRead != sizeof(peb))
return;
printf("Child process's peb.BeingDebugged is %d, setting to 0...\n", peb.BeingDebugged);
// Gaslight the child into believing we are not watching
peb.BeingDebugged = 0;
// Write back the modified PEB
SIZE_T processBytesWritten = NULL;
if (!WriteProcessMemory(hProcess, pbi.PebBaseAddress, &peb, sizeof(peb), &processBytesWritten) || processBytesWritten != sizeof(peb))
return;
};
persuade_process_no_debugger_is_present(pi.hProcess);
// Helper function to destroy the subprocess
auto exit_child = [&]
{
TerminateProcess(pi.hProcess, 1); // Terminate before detaching, so you don't see Windows Error Reporting.
DebugActiveProcessStop(GetProcessId(pi.hProcess)); // Detach
WaitForSingleObject(pi.hProcess, 2000); // Wait for child to die, but not forever.
};
// Kick off the subprocess
if (ResumeThread(pi.hThread) != 1)
{
exit_child();
fatal_init_error("Could not start main game thread");
ExitProcess(1); // @Note: could potentially "return;" here instead if you wanted.
}
// Debugger loop: catch (and ignore) all debug events until the program exits or hits a last-chance exception
char filename[65536] = {0};
WCHAR wfilename[65536] = {0};
HANDLE file = nullptr;
for (;;)
{
// Get debug event
DEBUG_EVENT de = {};
if (!WaitForDebugEvent(&de, INFINITE))
{
exit_child();
fatal_init_error("Waiting for debug event failed");
ExitProcess(1);
}
// If the process exited, nag about failure, or silently exit on success
if (de.dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT && de.dwProcessId == pi.dwProcessId)
{
// If the process exited unsuccessfully, prompt to restart it
// @Todo: in these cases, no dump can be made, so upload just the stdout log and profiling trace
if (de.u.ExitThread.dwExitCode != 0)
{
// Terminate & detach just to be safe
exit_child();
// Prompt to restart
MessageBeep(MB_ICONINFORMATION); // MB_ICONQUESTION makes no sound
if (MessageBoxW(nullptr,
L"The game had a fatal error and must close.\n"
"Unfortunately, a crash report could not be generated. Sorry!\n"
"Please report this to team@happenlance.com or the Happenlance Discord.\n"
"Restart the game?\n",
L"Fatal Error",
MB_YESNO | MB_ICONQUESTION | MB_SYSTEMMODAL | MB_SETFOREGROUND) == IDYES)
{
goto restart;
}
}
// Bubble up the failure code - this is where successful program runs will end up!
ExitProcess(de.u.ExitThread.dwExitCode);
}
// If the process had some other debug stuff, we don't care.
if (de.dwDebugEventCode != EXCEPTION_DEBUG_EVENT)
{
ContinueDebugEvent(de.dwProcessId, de.dwThreadId, DBG_CONTINUE);
continue;
}
// Skip first-chance exceptions or exceptions for processes we don't care about (shouldn't ever happen).
if (de.u.Exception.dwFirstChance || de.dwProcessId != GetProcessId(pi.hProcess))
{
ContinueDebugEvent(de.dwProcessId, de.dwThreadId, DBG_EXCEPTION_NOT_HANDLED);
continue;
}
// By here, we have hit a real, last-chance exception. This is a crash we should generate a dump for.
#define crash_report_failure(str) \
ErrBox( \
"The game had a fatal error and must close.\n" \
"A crash report could not be produced:\n\"" str "\"\n" \
"Please report this to team@happenlance.com or the Happenlance Discord.", \
MB_OK | MB_ICONERROR);
// Create crash dump filename
snprintf(filename, sizeof(filename), "./CrashDump_%d.dmp", (int)time(NULL));
// Convert filename to UTF-16
MultiByteToWideChar(CP_UTF8, 0, filename, -1, wfilename, ARRAYSIZE(wfilename));
// Create crash dump file
file = CreateFileW(wfilename, GENERIC_WRITE | GENERIC_READ, 0, nullptr,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
if (file == INVALID_HANDLE_VALUE)
{
exit_child();
crash_report_failure("The crash dump file could not be created. Sorry!");
ExitProcess(1);
}
// Generate exception pointers out of excepting thread context
CONTEXT c = {};
if (HANDLE thread = OpenThread(THREAD_ALL_ACCESS, true, de.dwThreadId))
{
c.ContextFlags = CONTEXT_ALL;
GetThreadContext(thread, &c);
CloseHandle(thread);
}
EXCEPTION_POINTERS ep = {};
ep.ExceptionRecord = &de.u.Exception.ExceptionRecord;
ep.ContextRecord = &c;
MINIDUMP_EXCEPTION_INFORMATION mei = {};
mei.ThreadId = de.dwThreadId;
mei.ExceptionPointers = &ep;
mei.ClientPointers = false;
// You could add some others here, but these should be good.
int flags = MiniDumpNormal | MiniDumpWithHandleData | MiniDumpScanMemory | MiniDumpWithUnloadedModules | MiniDumpWithProcessThreadData | MiniDumpWithThreadInfo | MiniDumpIgnoreInaccessibleMemory;
// Write minidump
if (!MiniDumpWriteDump(pi.hProcess, GetProcessId(pi.hProcess), file,
(MINIDUMP_TYPE)flags, &mei, nullptr, nullptr))
{
exit_child();
crash_report_failure("The crash dump could not be written. Sorry!");
ExitProcess(1);
}
// @Todo: ZIP compress the crash dump files here, with graceful fallback to uncompressed dumps.
// Cleanup: Destroy subprocess now that we have a dump.
// Note that we want to do this before doing any blocking interface dialogs,
// because otherwise you would leave an arbitrarily broken program lying around
// longer than you need to.
exit_child();
break;
}
// Prompt to upload crash dump
int res = 0;
bool uploaded = false;
if (!(res = ErrBox(
"The game had a fatal error and must close.\n"
"Send anonymous crash report?\n"
"This will go directly to the developers on Discord,\n"
"and help fix the problem.",
MB_YESNO | MB_ICONERROR)))
ExitProcess(1);
// Upload crash dump
if (res == IDYES)
{
// Setup window class for progress window
WNDCLASSEXW wcex = {sizeof(wcex)};
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpszClassName = L"bar";
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW);
wcex.hCursor = LoadCursor(GetModuleHandleA(nullptr), IDC_ARROW);
wcex.lpfnWndProc = [](HWND h, UINT m, WPARAM w, LPARAM l) -> LRESULT
{
return m == WM_QUIT || m == WM_CLOSE || m == WM_DESTROY ? 0 : DefWindowProcW(h, m, w, l);
};
wcex.hInstance = GetModuleHandleA(nullptr);
if (!RegisterClassExW(&wcex))
{
ExitProcess(1);
}
HWND hWnd = nullptr;
HWND ctrl = nullptr;
// Initialize common controls for progress bar
INITCOMMONCONTROLSEX iccex = {sizeof(iccex)};
iccex.dwICC = ICC_PROGRESS_CLASS;
if (InitCommonControlsEx(&iccex))
{
// Create progress window and progress bar child-window
hWnd = CreateWindowExW(0, wcex.lpszClassName, L"Uploading...",
WS_SYSMENU | WS_CAPTION | WS_VISIBLE, CW_USEDEFAULT, SW_SHOW,
320, 80, nullptr, nullptr, GetModuleHandleA(nullptr), nullptr);
ctrl = CreateWindowExW(0, PROGRESS_CLASSW, L"",
WS_CHILD | WS_VISIBLE | PBS_SMOOTH, 10, 10,
280, 20, hWnd, (HMENU)12345, GetModuleHandleA(nullptr), nullptr);
}
else
{
ExitProcess(1);
}
// Infinite loop: Attempt to upload the crash dump until the user cancels or it succeeds
do
{
// Position the progress window to the centre of the screen
RECT r;
GetWindowRect(hWnd, &r);
int ww = r.right - r.left, wh = r.bottom - r.top;
int sw = GetSystemMetrics(SM_CXSCREEN), sh = GetSystemMetrics(SM_CYSCREEN);
SetWindowPos(hWnd, HWND_TOP, (sw - ww) / 2, (sh - wh) / 2, 0, 0, SWP_NOSIZE);
// Helper function to set the loading bar to a certain position.
auto update_loading_bar = [&](float amt)
{
if (hWnd && ctrl)
{
SendMessageW(ctrl, PBM_SETPOS, (WPARAM)(amt * 100), 0);
ShowWindow(hWnd, SW_SHOW);
UpdateWindow(hWnd);
MSG msg = {};
while (PeekMessageW(&msg, nullptr, 0, 0, 1) > 0)
{
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
}
};
auto try_upload = [&]() -> bool
{
float x = 0;
update_loading_bar(x);
// Build MIME multipart-form payload
static char body[1 << 23]; // ouch that's a big static buffer!!!
const char *bodyPrefix =
"--19024605111143684786787635207\r\n"
"Content-Disposition: form-data; name=\"payload_json\"\r\n\r\n{\"content\":\""
"Astris v"
#define STRINGIZE_(N) #N
#define STRINGIZE(N) STRINGIZE_(N)
STRINGIZE(GIT_RELEASE_TAG) " Crash Report"
"\"}\r\n--19024605111143684786787635207\r\n"
"Content-Disposition: form-data; name=\"files[0]\"; filename=\"";
const char *bodyInfix = "\"\r\n"
"Content-Type: application/octet-stream\r\n"
"\r\n";
const char *bodyPostfix = "\r\n--19024605111143684786787635207--\r\n";
// Printf the prefix, filename, infix
int headerLen = snprintf(body, sizeof(body), "%s%s%s", bodyPrefix, filename, bodyInfix);
if (headerLen != strlen(bodyPrefix) + strlen(filename) + strlen(bodyInfix))
return false;
update_loading_bar(x += 0.1f);
// Get crash dump file size
LARGE_INTEGER fileSizeInt = {};
GetFileSizeEx(file, &fileSizeInt);
uint64_t fileSize = fileSizeInt.QuadPart;
if (fileSize >= 8000000)
return false; // discord limit
int bodyLen = headerLen + (int)fileSize + (int)(sizeof(bodyPostfix)-1);
if (bodyLen >= sizeof(body))
return false; // buffer overflow
update_loading_bar(x += 0.1f);
// Seek file to start
if (SetFilePointer(file, 0, nullptr, FILE_BEGIN) != 0)
return false;
// Copy entire file into the space after the body infix
DWORD bytesRead = 0;
if (!ReadFile(file, body + headerLen, (DWORD)fileSize, &bytesRead, nullptr))
return false;
if (bytesRead != fileSize)
return false;
update_loading_bar(x += 0.1f);
// Print the body postfix after the data file (overflow already checked)
strncpy(body + headerLen + fileSize, bodyPostfix, sizeof(body) - headerLen - fileSize);
update_loading_bar(x += 0.1f);
// Windows HTTPS stuff from here on out...
HINTERNET hSession = WinHttpOpen(L"Discord Crashdump Webhook",
WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME,
WINHTTP_NO_PROXY_BYPASS, 0);
if (!hSession)
return false;
defer { WinHttpCloseHandle(hSession); };
update_loading_bar(x += 0.1f);
// Connect to domain
HINTERNET hConnect = WinHttpConnect(hSession, L"discord.com", INTERNET_DEFAULT_HTTPS_PORT, 0);
if (!hConnect)
return false;
defer { WinHttpCloseHandle(hConnect); };
update_loading_bar(x += 0.1f);
// Begin POST request to the discord webhook endpoint
HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"POST",
L"" DISCORD_WEBHOOK_ENDPOINT,
nullptr, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, WINHTTP_FLAG_SECURE);
if (!hRequest)
return false;
defer { WinHttpCloseHandle(hRequest); };
update_loading_bar(x += 0.1f);
// Send request once - don't handle auth challenge, credentials, reauth, redirects
const wchar_t ContentType[] = L"Content-Type: multipart/form-data; boundary=19024605111143684786787635207";
if (!WinHttpSendRequest(hRequest, ContentType, ARRAYSIZE(ContentType),
body, bodyLen, bodyLen, 0))
return false;
update_loading_bar(x += 0.1f);
// Wait for response
if (!WinHttpReceiveResponse(hRequest, nullptr))
return false;
update_loading_bar(x += 0.1f);
// Pull headers from response
DWORD dwStatusCode, dwSize = sizeof(dwStatusCode);
if (!WinHttpQueryHeaders(hRequest,
WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
nullptr, &dwStatusCode, &dwSize, nullptr))
return false;
if (dwStatusCode != 200)
return false;
update_loading_bar(x += 0.1f);
return true;
};
res = 0;
uploaded = try_upload();
if (!uploaded)
{
if (!(res = MessageBoxW(hWnd, L"Sending failed. Retry?", L"Fatal Error", MB_RETRYCANCEL | MB_ICONWARNING | MB_SYSTEMMODAL)))
ExitProcess(1);
}
} while (res == IDRETRY);
// Cleanup
if (hWnd)
{
DestroyWindow(hWnd);
}
UnregisterClassW(wcex.lpszClassName, GetModuleHandleA(nullptr));
}
// Cleanup
CloseHandle(file);
// Prompt to restart
MessageBeep(MB_ICONINFORMATION); // MB_ICONQUESTION makes no sound
if (MessageBoxW(nullptr, uploaded ? L"Thank you for sending the crash report.\n"
"You can send more info to the #bugs channel\n"
"in the Astris Discord.\n"
"Restart the game?\n"
: view_file_in_system_file_browser(wfilename) ? L"The crash report folder has been opened.\n"
"You can send the report to the #bugs channel\n"
"in the Astris Discord.\n"
"Restart the game?\n"
: L"The crash report can be found in the program installation directory.\n"
"You can send the report to the #bugs channel\n"
"in the Astris Discord.\n"
"Restart the game?\n",
L"Fatal Error",
MB_YESNO | MB_ICONQUESTION | MB_SYSTEMMODAL | MB_SETFOREGROUND) == IDYES)
{
goto restart;
}
// Return 1 because the game crashed, not because the crash report failed
ExitProcess(1);
}
}

Binary file not shown.

@ -8,7 +8,7 @@
#include "buildsettings.h" // debug/developer settings
#include <stdio.h> // assert logging
#include <stdio.h> // flight_assert logging
#include <string.h> // memset
// do not use any global variables to process gamestate
@ -18,23 +18,100 @@
// - debug.c for debug drawing
// - chipmunk
#ifdef ASSERT_DO_POPUP_AND_CRASH
#ifdef _WIN32
#ifndef UNICODE
#define UNICODE // I think?
#endif
#include <windows.h>
LPWSTR
fromUTF8(
const char *src,
size_t src_length, /* = 0 */
size_t *out_length /* = NULL */
)
{
if (!src)
{
return NULL;
}
if (src_length == 0)
{
src_length = strlen(src);
}
int length = MultiByteToWideChar(CP_UTF8, 0, src, (int)src_length, 0, 0);
LPWSTR output_buffer = (LPWSTR)malloc((length + 1) * sizeof(wchar_t));
if (output_buffer)
{
MultiByteToWideChar(CP_UTF8, 0, src, (int)src_length, output_buffer, (int)length);
output_buffer[length] = L'\0';
}
if (out_length)
{
*out_length = length;
}
return output_buffer;
}
#endif // win32
#endif // ASSERT_DO_POPUP_AND_CRASH
enum
{
PLAYERS = 1 << 0,
BOXES = 1 << 1,
};
FILE *log_file = NULL;
void quit_with_popup(const char *message_utf8, const char *title_utf8)
{
#ifdef _WIN32
size_t message_out_len = 0;
size_t title_out_len = 0;
LPWSTR message_wchar = fromUTF8(message_utf8, strlen(message_utf8), &message_out_len);
LPWSTR title_wchar = fromUTF8(title_utf8, strlen(title_utf8), &title_out_len);
int msgboxID = MessageBox(
NULL,
message_wchar,
title_wchar,
MB_ICONEXCLAMATION | MB_OK);
(void)msgboxID;
free(message_wchar);
free(title_wchar);
(void)message_out_len;
(void)title_out_len;
#endif
exit(0);
}
void __flight_assert(bool cond, const char *file, int line, const char *cond_string)
{
if (!cond)
{
fprintf(stderr, "%s:%d | Assertion %s failed\n", file, line, cond_string);
#define MESSAGE_BUFFER_SIZE 2048
char message_buffer[MESSAGE_BUFFER_SIZE] = {0};
Log("Assertion failure\n"); // so that I have the time in the logs for when the assertion failed. Too lazy to fill a time string here
snprintf(message_buffer, MESSAGE_BUFFER_SIZE, "%s:%d | Assertion %s failed\n", file, line, cond_string);
fprintf(stderr, "%s", message_buffer);
if (log_file != NULL)
{
fprintf(log_file, "%s", message_buffer);
}
#ifdef ASSERT_DO_POPUP_AND_CRASH
char dialogbox_message[MESSAGE_BUFFER_SIZE] = {0};
snprintf(dialogbox_message, MESSAGE_BUFFER_SIZE, "Critical error! Please report this in #bug-reports with a screenshot, description of what you were doing, and the file 'atris.log' located next to the executable\n%s\nClosing now.\n", message_buffer);
quit_with_popup(dialogbox_message, "Assertion Failed");
#endif
}
}
bool is_burning(Entity *missile)
{
assert(missile->is_missile);
flight_assert(missile->is_missile);
return missile->time_burned_for < MISSILE_BURN_TIME;
}
@ -80,8 +157,8 @@ bool cloaking_active(GameState *gs, Entity *e)
bool is_cloaked(GameState *gs, Entity *e, Entity *this_players_perspective)
{
assert(this_players_perspective != NULL);
assert(this_players_perspective->is_player);
flight_assert(this_players_perspective != NULL);
flight_assert(this_players_perspective->is_player);
bool cloaked = cloaking_active(gs, e);
if (e->is_player)
{
@ -95,7 +172,7 @@ bool is_cloaked(GameState *gs, Entity *e, Entity *this_players_perspective)
static BOX_UNLOCKS_TYPE box_unlock_number(enum BoxType box)
{
assert((BOX_UNLOCKS_TYPE)box < 64);
flight_assert((BOX_UNLOCKS_TYPE)box < 64);
return (BOX_UNLOCKS_TYPE)((BOX_UNLOCKS_TYPE)1 << ((BOX_UNLOCKS_TYPE)box));
}
@ -106,14 +183,14 @@ static bool learned_boxes_has_box(BOX_UNLOCKS_TYPE learned, enum BoxType box)
void unlock_box(Player *player, enum BoxType box)
{
assert(box < MAX_BOX_TYPES);
assert(box != BoxInvalid);
flight_assert(box < MAX_BOX_TYPES);
flight_assert(box != BoxInvalid);
player->box_unlocks |= box_unlock_number(box);
}
bool box_unlocked(Player *player, enum BoxType box)
{
assert(box < MAX_BOX_TYPES);
flight_assert(box < MAX_BOX_TYPES);
if (box == BoxInvalid)
return false;
return learned_boxes_has_box(player->box_unlocks, box);
@ -125,8 +202,8 @@ EntityID get_id(GameState *gs, Entity *e)
return (EntityID){0};
size_t index = (e - gs->entities);
assert(index >= 0);
assert(index < gs->cur_next_entity);
flight_assert(index >= 0);
flight_assert(index < gs->cur_next_entity);
return (EntityID){
.generation = e->generation,
@ -151,7 +228,7 @@ static GameState *cp_space_gs(cpSpace *space)
static GameState *entitys_gamestate(Entity *e)
{
assert(e->body != NULL || e->shape != NULL);
flight_assert(e->body != NULL || e->shape != NULL);
if (e->shape != NULL)
{
return cp_space_gs(cpShapeGetSpace(e->shape));
@ -165,7 +242,7 @@ static GameState *entitys_gamestate(Entity *e)
int grid_num_boxes(GameState *gs, Entity *e)
{
assert(e->is_grid);
flight_assert(e->is_grid);
int to_return = 0;
BOXES_ITER(gs, cur, e)
@ -176,7 +253,7 @@ int grid_num_boxes(GameState *gs, Entity *e)
void box_remove_from_boxes(GameState *gs, Entity *box)
{
assert(box->is_box);
flight_assert(box->is_box);
Entity *prev_box = get_entity(gs, box->prev_box);
Entity *next_box = get_entity(gs, box->next_box);
if (prev_box != NULL)
@ -188,7 +265,7 @@ void box_remove_from_boxes(GameState *gs, Entity *box)
}
if (next_box != NULL)
{
assert(next_box->is_box);
flight_assert(next_box->is_box);
next_box->prev_box = get_id(gs, prev_box);
}
box->next_box = (EntityID){0};
@ -198,7 +275,7 @@ void box_remove_from_boxes(GameState *gs, Entity *box)
cpVect player_vel(GameState *gs, Entity *e);
cpVect entity_vel(GameState *gs, Entity *e)
{
assert(e->is_box || e->is_player || e->body != NULL || e->is_explosion);
flight_assert(e->is_box || e->is_player || e->body != NULL || e->is_explosion);
if (e->is_box)
return box_vel(e);
if (e->is_player)
@ -207,7 +284,7 @@ cpVect entity_vel(GameState *gs, Entity *e)
return (cpBodyGetVelocity(e->body));
if (e->is_explosion)
return e->explosion_vel;
assert(false);
flight_assert(false);
return (cpVect){0};
}
@ -219,7 +296,7 @@ static void on_missile_shape(cpShape *shape, cpContactPointSet *points, void *da
Entity *launcher = (Entity *)data;
Entity *other = cp_shape_entity(shape);
GameState *gs = entitys_gamestate(launcher);
assert(other->is_box || other->is_player || other->is_missile);
flight_assert(other->is_box || other->is_player || other->is_missile);
cpVect to = cpvsub(entity_pos(other), entity_pos(launcher));
bool should_attack = true;
@ -279,9 +356,10 @@ static void destroy_body(GameState *gs, cpBody **body)
*body = NULL;
}
// will destroy all shapes which are attached to the body in the entity
void entity_destroy(GameState *gs, Entity *e)
{
assert(e->exists);
flight_assert(e->exists);
if (e->is_grid)
{
@ -303,7 +381,7 @@ void entity_destroy(GameState *gs, Entity *e)
Entity *front_of_free_list = get_entity(gs, gs->free_list);
if (front_of_free_list != NULL)
assert(!front_of_free_list->exists);
flight_assert(!front_of_free_list->exists);
int gen = e->generation;
*e = (Entity){0};
e->generation = gen;
@ -313,7 +391,18 @@ void entity_destroy(GameState *gs, Entity *e)
void on_entity_child_shape(cpBody *body, cpShape *shape, void *data)
{
entity_destroy((GameState *)data, cp_shape_entity(shape));
GameState *gs = (GameState *)data;
if (cp_shape_entity(shape) == NULL)
{
// support the case where no parent entity *SPECIFICALLY* for grid_correct_for_holes,
// where the entities that are part of the old grid are reused so entityids are preserved
cpSpaceRemoveShape(gs->space, shape);
cpShapeFree(shape);
}
else
{
entity_destroy(gs, cp_shape_entity(shape));
}
}
Entity *new_entity(GameState *gs)
@ -322,14 +411,14 @@ Entity *new_entity(GameState *gs)
Entity *possible_free_list = get_entity_even_if_dead(gs, gs->free_list);
if (possible_free_list != NULL)
{
assert(possible_free_list->generation == gs->free_list.generation);
flight_assert(possible_free_list->generation == gs->free_list.generation);
to_return = possible_free_list;
assert(!to_return->exists);
flight_assert(!to_return->exists);
gs->free_list = to_return->next_free_entity;
}
else
{
assert(gs->cur_next_entity < gs->max_entities); // too many entities if fails
flight_assert(gs->cur_next_entity < gs->max_entities); // too many entities if fails
to_return = &gs->entities[gs->cur_next_entity];
gs->cur_next_entity++;
}
@ -342,7 +431,7 @@ Entity *new_entity(GameState *gs)
// pos, mass, radius
EntityID create_sun(GameState *gs, Entity *new_sun, cpVect pos, cpVect vel, double mass, double radius)
{
assert(new_sun != NULL);
flight_assert(new_sun != NULL);
new_sun->is_sun = true;
new_sun->sun_pos = pos;
new_sun->sun_vel = vel;
@ -354,7 +443,7 @@ EntityID create_sun(GameState *gs, Entity *new_sun, cpVect pos, cpVect vel, doub
void create_body(GameState *gs, Entity *e)
{
assert(gs->space != NULL);
flight_assert(gs->space != NULL);
if (e->body != NULL)
{
@ -370,12 +459,12 @@ void create_body(GameState *gs, Entity *e)
cpVect player_vel(GameState *gs, Entity *player)
{
assert(player->is_player);
flight_assert(player->is_player);
Entity *potential_seat = get_entity(gs, player->currently_inside_of_box);
if (potential_seat != NULL && !potential_seat->is_box)
{
Log("Weird ass motherfucking bug where the seat inside of is an explosion or some shit\n");
assert(potential_seat->is_box);
flight_assert(potential_seat->is_box);
}
else
{
@ -395,14 +484,14 @@ void grid_create(GameState *gs, Entity *e)
void entity_set_rotation(Entity *e, double rot)
{
assert(e->body != NULL);
flight_assert(e->body != NULL);
cpBodySetAngle(e->body, rot);
}
void entity_set_pos(Entity *e, cpVect pos)
{
assert(e->is_grid);
assert(e->body != NULL);
flight_assert(e->is_grid);
flight_assert(e->body != NULL);
cpBodySetPosition(e->body, (pos));
}
@ -445,6 +534,7 @@ void create_player(Player *player)
unlock_box(player, BoxThruster);
unlock_box(player, BoxBattery);
unlock_box(player, BoxCockpit);
unlock_box(player, BoxGyroscope);
unlock_box(player, BoxMedbay);
unlock_box(player, BoxSolarPanel);
unlock_box(player, BoxScanner);
@ -483,8 +573,8 @@ void box_add_to_boxes(GameState *gs, Entity *grid, Entity *box_to_add)
void box_create(GameState *gs, Entity *new_box, Entity *grid, cpVect pos)
{
new_box->is_box = true;
assert(gs->space != NULL);
assert(grid->is_grid);
flight_assert(gs->space != NULL);
flight_assert(grid->is_grid);
double halfbox = BOX_SIZE / 2.0;
@ -498,12 +588,40 @@ void box_create(GameState *gs, Entity *new_box, Entity *grid, cpVect pos)
cpVect box_compass_vector(Entity *box)
{
assert(box->is_box);
flight_assert(box->is_box);
cpVect to_return = (cpVect){.x = 1.0, .y = 0.0};
to_return = cpvspin(to_return, rotangle(box->compass_rotation));
return to_return;
}
#include <time.h>
void fill_time_string(char *to_fill, size_t max_length)
{
#ifdef _WIN32
time_t rawtime;
struct tm timeinfo = {0};
time(&rawtime);
localtime_s(&timeinfo, &rawtime);
asctime_s(to_fill, max_length, &timeinfo);
#else
time_t rawtime;
struct tm *timeinfo;
time(&rawtime);
timeinfo = localtime(&rawtime);
char *output = asctime(timeinfo);
size_t length = strlen(output);
strncpy(to_fill, output, length);
#endif
size_t filled_length = strlen(to_fill);
// to_fill[filled_length - 1] = '\0'; // remove the newline
to_fill[filled_length - 2] = '\0'; // remove the newline
to_fill[filled_length - 3] = '\0'; // remove the newline
// to_fill[filled_length - 4] = '\0'; // remove the newline
}
// removes boxes from grid, then ensures that the rule that grids must not have
// holes in them is applied.
@ -537,8 +655,8 @@ static void grid_correct_for_holes(GameState *gs, struct Entity *grid)
{
// grab an unprocessed box, one not in separate_grids, to start the flood fill
Entity *unprocessed = get_entity(gs, grid->boxes);
assert(unprocessed != NULL);
assert(unprocessed->is_box);
flight_assert(unprocessed != NULL);
flight_assert(unprocessed->is_box);
box_remove_from_boxes(gs, unprocessed); // no longer in the boxes list of the grid
uint32_t biggest_box_index = 0;
@ -552,7 +670,7 @@ static void grid_correct_for_holes(GameState *gs, struct Entity *grid)
Entity *N = NULL;
while (true)
{
assert(!was_entity_deleted(gs, Q));
flight_assert(!was_entity_deleted(gs, Q));
N = get_entity(gs, Q);
if (N == NULL) // must mean that the queue is empty
break;
@ -629,7 +747,7 @@ static void grid_correct_for_holes(GameState *gs, struct Entity *grid)
biggest_separate_grid_index = cur_separate_grid_index;
}
cur_separate_grid_index++;
assert(cur_separate_grid_index < MAX_SEPARATE_GRIDS);
flight_assert(cur_separate_grid_index < MAX_SEPARATE_GRIDS);
cur_separate_grid_size = 0;
}
@ -643,41 +761,38 @@ static void grid_correct_for_holes(GameState *gs, struct Entity *grid)
continue; // this separate grid is empty
Entity *new_grid;
if (sepgrid_i == biggest_separate_grid_index)
{
new_grid = grid;
}
else
{
new_grid = new_entity(gs);
grid_create(gs, new_grid);
cpBodySetPosition(new_grid->body, cpBodyGetPosition(grid->body));
cpBodySetAngle(new_grid->body, cpBodyGetAngle(grid->body));
}
new_grid = new_entity(gs);
grid_create(gs, new_grid);
cpBodySetPosition(new_grid->body, cpBodyGetPosition(grid->body));
cpBodySetAngle(new_grid->body, cpBodyGetAngle(grid->body));
Entity *cur = get_entity(gs, cur_separate_grid);
while (cur != NULL)
{
Entity *next = get_entity(gs, cur->next_box);
box_create(gs, cur, new_grid, entity_shape_pos(cur)); // destroys next/prev fields on cur
cpVect new_shape_position = entity_shape_pos(cur);
// leaks the allocated shape for the box so center of mass calcs from the original grid are correct. Shapes are freed when grid is destroyed after construction of new replacement grids
// important that a new entity isn't created for the shapes so entity references to those shapes are still valid
cpShapeSetUserData(cur->shape, NULL);
cur->shape = NULL;
box_create(gs, cur, new_grid, new_shape_position); // destroys next/prev fields on cur
cur = next;
}
// @Robust do the momentum stuff properly here so no matter which grid stays as the current grid,
// the *SAME RESULT* happens. VERY IMPORTANT for client side prediction to match what the server says.
// Tried to use something consistent on the server and client like current entity index but DID NOT WORK
if (sepgrid_i != biggest_separate_grid_index)
{
cpBodySetVelocity(new_grid->body, cpBodyGetVelocityAtWorldPoint(grid->body, (grid_com(new_grid))));
cpBodySetAngularVelocity(new_grid->body, entity_angular_velocity(grid) / fmax(1.0, cpvdist(entity_pos(new_grid), entity_pos(grid))));
}
cpBodySetVelocity(new_grid->body, cpBodyGetVelocityAtWorldPoint(grid->body, (grid_com(new_grid))));
cpBodySetAngularVelocity(new_grid->body, cpBodyGetAngularVelocity(grid->body));
}
// destroys all the box shapes and the entities attached to those shapes
entity_destroy(gs, grid);
}
static void grid_remove_box(GameState *gs, struct Entity *grid, struct Entity *box)
{
assert(grid->is_grid);
assert(box->is_box);
flight_assert(grid->is_grid);
flight_assert(box->is_box);
entity_destroy(gs, box);
grid_correct_for_holes(gs, grid);
}
@ -747,8 +862,8 @@ void initialize(GameState *gs, void *entity_arena, size_t entity_arena_size)
gs->entities = (Entity *)entity_arena;
gs->max_entities = (unsigned int)(entity_arena_size / sizeof(Entity));
gs->space = cpSpaceNew();
cpSpaceSetUserData(gs->space, (cpDataPointer)gs); // needed in the handler
cpCollisionHandler *handler = cpSpaceAddCollisionHandler(gs->space, 0, 0); // @Robust limit collision type to just blocks that can be damaged
cpSpaceSetUserData(gs->space, (cpDataPointer)gs); // needed in the handler
cpCollisionHandler *handler = cpSpaceAddCollisionHandler(gs->space, 0, 0);
handler->postSolveFunc = on_damage;
gs->server_side_computing = is_server_side;
}
@ -784,7 +899,7 @@ cpVect grid_world_to_local(Entity *grid, cpVect world)
}
cpVect grid_local_to_world(Entity *grid, cpVect local)
{
assert(grid->is_grid);
flight_assert(grid->is_grid);
return (cpBodyLocalToWorld(grid->body, (local)));
}
// returned snapped position is in world coordinates
@ -804,7 +919,7 @@ cpVect grid_snapped_box_pos(Entity *grid, cpVect world)
// for boxes does not include box's compass rotation
double entity_rotation(Entity *e)
{
assert(e->body != NULL || e->shape != NULL);
flight_assert(e->body != NULL || e->shape != NULL);
if (e->body != NULL)
return (float)cpBodyGetAngle(e->body);
else
@ -819,7 +934,7 @@ Entity *box_grid(Entity *box)
{
if (box == NULL)
return NULL;
assert(box->is_box);
flight_assert(box->is_box);
return (Entity *)cpBodyGetUserData(cpShapeGetBody(box->shape));
}
// in local space
@ -829,7 +944,7 @@ cpVect entity_shape_pos(Entity *box)
}
double entity_shape_mass(Entity *box)
{
assert(box->shape != NULL);
flight_assert(box->shape != NULL);
return (float)cpShapeGetMass(box->shape);
}
double box_rotation(Entity *box)
@ -853,7 +968,7 @@ cpVect entity_pos(Entity *e)
}
else
{
assert(e->body != NULL);
flight_assert(e->body != NULL);
return (cpBodyGetPosition(e->body));
}
}
@ -895,6 +1010,7 @@ typedef struct SerState
// output
uint32_t version;
uint32_t git_release_tag;
} SerState;
typedef struct SerMaybeFailure
@ -1004,7 +1120,6 @@ enum GameVersion
VMax, // this minus one will be the version used
};
// @Robust probably get rid of this as separate function, just use SER_VAR
SerMaybeFailure ser_V2(SerState *ser, cpVect *var)
{
SER_VAR(&var->x);
@ -1116,7 +1231,7 @@ SerMaybeFailure ser_player(SerState *ser, Player *p)
SerMaybeFailure ser_entity(SerState *ser, GameState *gs, Entity *e)
{
SER_VAR(&e->no_save_to_disk); // @Robust this is always false when saving to disk?
SER_VAR(&e->no_save_to_disk);
SER_VAR(&e->generation);
SER_MAYBE_RETURN(ser_f(ser, &e->damage));
@ -1325,6 +1440,15 @@ SerMaybeFailure ser_server_to_client(SerState *ser, ServerToClient *s)
SER_VAR(&ser->version);
SER_ASSERT(ser->version >= 0);
SER_ASSERT(ser->version < VMax);
SER_VAR(&ser->git_release_tag);
if (ser->git_release_tag > GIT_RELEASE_TAG)
{
char msg[2048] = {0};
snprintf(msg, 2048, "Current game build %d is old, download the server's build %d! The most recent one in discord!\n", GIT_RELEASE_TAG, ser->git_release_tag);
quit_with_popup(msg, "Old Game Build");
SER_ASSERT(ser->git_release_tag <= GIT_RELEASE_TAG);
}
if (!ser->save_or_load_from_disk)
SER_MAYBE_RETURN(ser_opus_packets(ser, s->audio_playback_buffer));
@ -1332,9 +1456,9 @@ SerMaybeFailure ser_server_to_client(SerState *ser, ServerToClient *s)
GameState *gs = s->cur_gs;
// completely reset and destroy all gamestate data
PROFILE_SCOPE("Destroy old gamestate")
if (!ser->serializing)
{
if (!ser->serializing)
PROFILE_SCOPE("Destroy old gamestate")
{
// avoid a memset here very expensive. que rico!
destroy(gs);
@ -1358,7 +1482,6 @@ SerMaybeFailure ser_server_to_client(SerState *ser, ServerToClient *s)
if (!ser->save_or_load_from_disk) // don't save player info to disk, this is filled on connection/disconnection
{
// @Robust save player data with their ID or something somehow. Like local backup of their account
for (size_t i = 0; i < MAX_PLAYERS; i++)
{
if (get_entity(gs, gs->players[i].entity) != NULL && is_cloaked(gs, get_entity(gs, gs->players[i].entity), ser->for_player))
@ -1426,7 +1549,7 @@ SerMaybeFailure ser_server_to_client(SerState *ser, ServerToClient *s)
EntityID cur_id = get_id(gs, cur_box);
SER_ASSERT(cur_id.index < gs->max_entities);
SER_VAR(&entities_done);
size_t the_index = (size_t)cur_id.index; // super critical. Type of &i is size_t. @Robust add debug info in serialization for what size the expected type is, maybe string nameof the type
size_t the_index = (size_t)cur_id.index; // super critical. Type of &i is size_t. @BeforePatreon add debug info in serialization for what size the expected type is, maybe string nameof the type
SER_VAR_NAME(&the_index, "&i");
SER_MAYBE_RETURN(ser_entity(ser, gs, cur_box));
}
@ -1500,8 +1623,8 @@ SerMaybeFailure ser_server_to_client(SerState *ser, ServerToClient *s)
// for_this_player can be null then the entire world will be sent
bool server_to_client_serialize(struct ServerToClient *msg, unsigned char *bytes, size_t *out_len, size_t max_len, Entity *for_this_player, bool to_disk)
{
assert(msg->cur_gs != NULL);
assert(msg != NULL);
flight_assert(msg->cur_gs != NULL);
flight_assert(msg != NULL);
SerState ser = (SerState){
.bytes = bytes,
@ -1511,20 +1634,16 @@ bool server_to_client_serialize(struct ServerToClient *msg, unsigned char *bytes
.for_player = for_this_player,
.max_entity_index = msg->cur_gs->cur_next_entity,
.version = VMax - 1,
.save_or_load_from_disk = to_disk,
};
if (for_this_player == NULL) // @Robust jank
{
ser.save_or_load_from_disk = true;
}
ser.write_varnames = to_disk;
#ifdef WRITE_VARNAMES
ser.write_varnames = true;
#endif
SerMaybeFailure result = ser_server_to_client(&ser, msg);
*out_len = ser.cursor + 1; // @Robust not sure why I need to add one to cursor, ser.cursor should be the length..
*out_len = ser.cursor + 1; // not sure why I need to add one to cursor, ser.cursor should be the length. It seems to work without the +1 but I have no way to ensure that it works completely when removing the +1...
if (result.failed)
{
Log("Failed to serialize on line %d because of %s\n", result.line, result.expression);
@ -1538,8 +1657,8 @@ bool server_to_client_serialize(struct ServerToClient *msg, unsigned char *bytes
bool server_to_client_deserialize(struct ServerToClient *msg, unsigned char *bytes, size_t max_len, bool from_disk)
{
assert(msg->cur_gs != NULL);
assert(msg != NULL);
flight_assert(msg->cur_gs != NULL);
flight_assert(msg != NULL);
SerState servar = (SerState){
.bytes = bytes,
@ -1673,8 +1792,8 @@ bool client_to_server_deserialize(GameState *gs, struct ClientToServer *msg, uns
static THREADLOCAL Entity *grid_to_exclude = NULL;
static bool merge_filter(Entity *potential_merge)
{
assert(grid_to_exclude != NULL);
assert(grid_to_exclude->is_grid);
flight_assert(grid_to_exclude != NULL);
flight_assert(grid_to_exclude->is_grid);
return potential_merge->is_box && potential_merge->box_type == BoxMerge && box_grid(potential_merge) != grid_to_exclude;
}
@ -1694,7 +1813,7 @@ static THREADLOCAL double closest_to_point_in_radius_result_largest_dist = 0.0;
static THREADLOCAL bool (*closest_to_point_in_radius_filter_func)(Entity *);
static void closest_point_callback_func(cpShape *shape, cpContactPointSet *points, void *data)
{
assert(points->count == 1);
flight_assert(points->count == 1);
Entity *e = cp_shape_entity(shape);
if (!e->is_box)
return;
@ -1726,7 +1845,6 @@ Entity *closest_box_to_point_in_radius(struct GameState *gs, cpVect point, doubl
if (closest_to_point_in_radius_result != NULL)
{
// @Robust query here for only boxes that are part of ships, could get nasty...
return cp_shape_entity(closest_to_point_in_radius_result);
}
@ -1753,7 +1871,7 @@ static void explosion_callback_func(cpShape *shape, cpContactPointSet *points, v
Entity *parent = get_entity(gs, cp_shape_entity(shape)->shape_parent_entity);
cpVect from_pos = entity_pos(cp_shape_entity(shape));
cpVect impulse = cpvmult(cpvnormalize(cpvsub(from_pos, explosion_origin)), explosion_push_strength);
assert(parent->body != NULL);
flight_assert(parent->body != NULL);
cpBodyApplyImpulseAtWorldPoint(parent->body, (impulse), (from_pos));
}
@ -1774,7 +1892,7 @@ static void do_explosion(GameState *gs, Entity *explosion, double dt)
cpVect box_facing_vector(Entity *box)
{
assert(box->is_box);
flight_assert(box->is_box);
cpVect to_return = (cpVect){.x = 1.0, .y = 0.0};
to_return = box_compass_vector(box);
@ -1785,8 +1903,8 @@ cpVect box_facing_vector(Entity *box)
enum CompassRotation facing_vector_to_compass(Entity *grid_to_transplant_to, Entity *grid_facing_vector_from, cpVect facing_vector)
{
assert(grid_to_transplant_to->body != NULL);
assert(grid_to_transplant_to->is_grid);
flight_assert(grid_to_transplant_to->body != NULL);
flight_assert(grid_to_transplant_to->is_grid);
cpVect from_target = cpvadd(entity_pos(grid_to_transplant_to), facing_vector);
cpVect local_target = grid_world_to_local(grid_to_transplant_to, from_target);
@ -1810,7 +1928,7 @@ enum CompassRotation facing_vector_to_compass(Entity *grid_to_transplant_to, Ent
smallest = i;
}
}
assert(smallest != -1);
flight_assert(smallest != -1);
return dirs[smallest];
}
@ -1846,6 +1964,11 @@ cpVect potentially_snap_hand_pos(GameState *gs, cpVect world_hand_pos)
cpVect get_world_hand_pos(GameState *gs, InputFrame *input, Entity *player)
{
if (cpvlength(input->hand_pos) > MAX_HAND_REACH)
{
// no cheating with long hand!
input->hand_pos = cpvmult(cpvnormalize(input->hand_pos), MAX_HAND_REACH);
}
return potentially_snap_hand_pos(gs, cpvadd(entity_pos(player), input->hand_pos));
}
@ -1908,7 +2031,7 @@ double entity_mass(Entity *m)
return m->sun_mass;
else
{
assert(false);
flight_assert(false);
return 0.0;
}
}
@ -1923,7 +2046,7 @@ cpVect sun_gravity_accel_for_entity(Entity *entity_with_gravity, Entity *sun)
return (cpVect){0};
cpVect rel_vector = cpvsub(entity_pos(entity_with_gravity), entity_pos(sun));
double mass = entity_mass(entity_with_gravity);
assert(mass != 0.0);
flight_assert(mass != 0.0);
double distance_sqr = cpvlengthsq(rel_vector);
// return (GRAVITY_CONSTANT * (SUN_MASS * mass / (distance * distance))) / mass;
// the mass divides out
@ -1948,7 +2071,7 @@ void entity_set_velocity(Entity *e, cpVect vel)
else if (e->is_sun)
e->sun_vel = vel;
else
assert(false);
flight_assert(false);
}
void entity_ensure_in_orbit(GameState *gs, Entity *e)
@ -1976,7 +2099,7 @@ void entity_ensure_in_orbit(GameState *gs, Entity *e)
cpVect box_vel(Entity *box)
{
assert(box->is_box);
flight_assert(box->is_box);
Entity *grid = box_grid(box);
return (cpBodyGetVelocityAtWorldPoint(grid->body, (entity_pos(box))));
}
@ -2064,19 +2187,24 @@ void create_hard_shell_station(GameState *gs, cpVect pos, enum BoxType platonic_
void create_initial_world(GameState *gs)
{
EntityID suns[] = {
create_sun(gs, new_entity(gs), ((cpVect){100.0, 0.0}), ((cpVect){0.0, 0.0}), 100000.0, 20.0),
create_sun(gs, new_entity(gs), ((cpVect){100.0, 50.0}), ((cpVect){10.0, 0.0}), 10000.0, 10.0),
create_sun(gs, new_entity(gs), ((cpVect){100.0, -50.0}), ((cpVect){-10.0, 0.0}), 10000.0, 10.0),
create_sun(gs, new_entity(gs), ((cpVect){50.0, 200.0}), ((cpVect){5.0, 0.0}), 400000.0, 30.0),
create_sun(gs, new_entity(gs), ((cpVect){-200.0, 200.0}), ((cpVect){-15.0, 0.0}), 900000.0, 60.0),
create_sun(gs, new_entity(gs), ((cpVect){800.0, 0.0}), ((cpVect){0.0, 0.0}), 1000000.0, 30.0),
create_sun(gs, new_entity(gs), ((cpVect){800.0, 50.0}), ((cpVect){50.0, 0.0}), 10000.0, 20.0),
create_sun(gs, new_entity(gs), ((cpVect){800.0, -50.0}), ((cpVect){-50.0, 0.0}), 10000.0, 20.0),
create_sun(gs, new_entity(gs), ((cpVect){-2500.0, -50.0}), ((cpVect){0.0, 0.0}), 100000.0, 20.0),
};
for (int i = 0; i < ARRLEN(suns); i++)
{
gs->suns[i] = suns[i];
}
#ifdef DEBUG_WORLD
#ifndef DEBUG_WORLD
Log("Creating release world\n");
create_bomb_station(gs, (cpVect){800.0, 800.0}, BoxExplosive);
// create_hard_shell_station(gs, (cpVect){800.0, 400.0}, BoxGyroscope);
create_bomb_station(gs, (cpVect){800.0, -800.0}, BoxCloaking);
create_bomb_station(gs, (cpVect){1600.0, 800.0}, BoxMissileLauncher);
create_hard_shell_station(gs, (cpVect){-2500.0, 200.0}, BoxMerge);
#else
Log("Creating debug world\n");
// pos, mass, radius
create_bomb_station(gs, (cpVect){-5.0, 0.0}, BoxExplosive);
@ -2091,7 +2219,6 @@ void create_initial_world(GameState *gs)
enum CompassRotation rot = Right;
{
Entity *grid = new_entity(gs);
grid_create(gs, grid);
entity_set_pos(grid, cpvadd(from, cpvspin((cpVect){.x = -BOX_SIZE * 9.0}, theta)));
@ -2117,13 +2244,6 @@ void create_initial_world(GameState *gs)
cpBodySetVelocity(grid->body, (cpvspin((cpVect){-0.4, 0.0}, theta)));
entity_ensure_in_orbit(gs, grid);
}
#else
create_bomb_station(gs, (cpVect){-200.0, 0.0}, BoxExplosive);
create_hard_shell_station(gs, (cpVect){0.0, 400.0}, BoxGyroscope);
create_bomb_station(gs, (cpVect){0.0, -150.0}, BoxCloaking);
create_bomb_station(gs, (cpVect){300.0, 300.0}, BoxMissileLauncher);
create_hard_shell_station(gs, (cpVect){50.0, 100.0}, BoxMerge);
#endif
}
@ -2139,7 +2259,7 @@ void process(struct GameState *gs, double dt)
{
PROFILE_SCOPE("Gameplay processing")
{
assert(gs->space != NULL);
flight_assert(gs->space != NULL);
gs->tick++;
@ -2221,7 +2341,7 @@ void process(struct GameState *gs, double dt)
p->damage = 0.95;
}
}
assert(p->is_player);
flight_assert(p->is_player);
p->owning_squad = player->squad;
if (p->squad_invited_to != SquadNone)
@ -2262,7 +2382,7 @@ void process(struct GameState *gs, double dt)
if (result != NULL)
{
Entity *potential_seat = cp_shape_entity(result);
assert(potential_seat->is_box);
flight_assert(potential_seat->is_box);
if (potential_seat->box_type == BoxScanner) // learn everything from the scanner
{
@ -2272,11 +2392,11 @@ void process(struct GameState *gs, double dt)
{
potential_seat->wants_disconnect = true;
grid_correct_for_holes(gs, box_grid(potential_seat));
assert(potential_seat->exists);
assert(potential_seat->is_box);
assert(potential_seat->box_type == BoxMerge);
flight_assert(potential_seat->exists);
flight_assert(potential_seat->is_box);
flight_assert(potential_seat->box_type == BoxMerge);
}
if (potential_seat->box_type == BoxCockpit || potential_seat->box_type == BoxMedbay) // @Robust check by feature flag instead of box type
if (potential_seat->box_type == BoxCockpit || potential_seat->box_type == BoxMedbay)
{
// don't let players get inside of cockpits that somebody else is already inside of
if (get_entity(gs, potential_seat->player_who_is_inside_of_me) == NULL)
@ -2342,7 +2462,7 @@ void process(struct GameState *gs, double dt)
}
else
{
assert(seat_inside_of->is_box);
flight_assert(seat_inside_of->is_box);
cpShapeSetFilter(p->shape, CP_SHAPE_FILTER_NONE); // no collisions while in a seat
cpBodySetPosition(p->body, (entity_pos(seat_inside_of)));
cpBodySetVelocity(p->body, (box_vel(seat_inside_of)));
@ -2387,7 +2507,6 @@ void process(struct GameState *gs, double dt)
cpPointQueryInfo info = {0};
cpVect world_build = world_hand_pos;
// @Robust sanitize this input so player can't build on any grid in the world
Entity *target_grid = grid_to_build_on(gs, world_hand_pos);
cpShape *maybe_box_to_destroy = cpSpacePointQueryNearest(gs->space, (world_build), 0.01, cpShapeFilterNew(CP_NO_GROUP, CP_ALL_CATEGORIES, BOXES), &info);
if (maybe_box_to_destroy != NULL)
@ -2484,7 +2603,7 @@ void process(struct GameState *gs, double dt)
}
}
// sun processing for this current entity
// sun processing for this current entity
#ifndef NO_SUNS
PROFILE_SCOPE("this entity sun processing")
{
@ -2578,7 +2697,7 @@ void process(struct GameState *gs, double dt)
if (e->box_type == BoxMerge)
{
Entity *from_merge = e;
assert(from_merge != NULL);
flight_assert(from_merge != NULL);
grid_to_exclude = box_grid(from_merge);
Entity *other_merge = closest_box_to_point_in_radius(gs, entity_pos(from_merge), MERGE_MAX_DIST, merge_filter);
@ -2588,7 +2707,7 @@ void process(struct GameState *gs, double dt)
if (!from_merge->wants_disconnect && other_merge != NULL && !other_merge->wants_disconnect)
{
assert(box_grid(from_merge) != box_grid(other_merge));
flight_assert(box_grid(from_merge) != box_grid(other_merge));
Entity *from_grid = box_grid(from_merge);
Entity *other_grid = box_grid(other_merge);
@ -2609,7 +2728,7 @@ void process(struct GameState *gs, double dt)
double angle_diff = cpvanglediff(current_facing_vector, facing_vector_needed);
if (angle_diff == FLT_MIN)
angle_diff = 0.0;
assert(!isnan(angle_diff));
flight_assert(!isnan(angle_diff));
cpBodySetAngle(other_grid->body, cpBodyGetAngle(other_grid->body) + angle_diff);
@ -2630,7 +2749,7 @@ void process(struct GameState *gs, double dt)
cur->compass_rotation = new_rotation;
cpVect new_cur_pos = grid_snapped_box_pos(from_grid, cpvadd(snap_movement_vect, world));
box_create(gs, cur, from_grid, grid_world_to_local(from_grid, new_cur_pos)); // destroys next/prev fields on cur
assert(box_grid(cur) == box_grid(from_merge));
flight_assert(box_grid(cur) == box_grid(from_merge));
cur = next;
}
entity_destroy(gs, other_grid);
@ -2679,7 +2798,7 @@ void process(struct GameState *gs, double dt)
cur->energy_used -= energy_sucked_up_by_battery;
energy_to_add -= energy_sucked_up_by_battery;
}
assert(energy_to_add >= 0.0);
flight_assert(energy_to_add >= 0.0);
}
// any energy_to_add existing now can also be used to power thrusters/medbay
@ -2816,7 +2935,7 @@ void process(struct GameState *gs, double dt)
scanner_has_learned = cur_box->blueprints_learned;
Entity *to_learn = closest_box_to_point_in_radius(gs, entity_pos(cur_box), SCANNER_RADIUS, scanner_filter);
if (to_learn != NULL)
assert(to_learn->is_box);
flight_assert(to_learn->is_box);
EntityID new_id = get_id(gs, to_learn);

336
main.c

@ -1,25 +1,25 @@
//------------------------------------------------------------------------------
// Take flight
//------------------------------------------------------------------------------
#include "types.h"
#define SOKOL_IMPL
#define SOKOL_D3D11
#include <enet/enet.h>
#include <process.h> // starting server thread
#define TOOLBAR_SLOTS 9
#pragma warning(disable : 33010) // this warning is so broken, doesn't
// understand assert()
#pragma warning(disable : 33010) // this warning is so broken, doesn't understand flight_assert()
#define SOKOL_LOG Log
#define SOKOL_ASSERT flight_assert
#define SOKOL_IMPL
#define SOKOL_D3D11
#include "sokol_app.h"
#include "sokol_gfx.h"
#include "sokol_glue.h"
#include "sokol_gp.h"
#include "sokol_time.h"
#pragma warning(default : 33010)
#pragma warning(disable : 6262) // warning about using a lot of stack, lol
// that's how stb image is
#pragma warning(disable : 6262) // warning about using a lot of stack, lol that's how stb image is
#define STB_IMAGE_IMPLEMENTATION
#include <inttypes.h>
#include <string.h> // errno error message on file open
@ -28,7 +28,6 @@
#include "opus.h"
#include "queue.h"
#include "stb_image.h"
#include "types.h"
#define MINIAUDIO_IMPLEMENTATION
#include "miniaudio.h"
@ -77,16 +76,14 @@ static cpVect camera_pos = {0}; // it being a global variable keeps camera at sa
// position after player death
static double player_scaling = 1.0;
static bool mouse_frozen = false; // @BeforeShip make this debug only thing
static double funval =
0.0; // easy to play with value controlled by left mouse button when held
// down @BeforeShip remove on release builds
static bool mouse_frozen = false;
static Queue input_queue = {0};
char input_queue_data[QUEUE_SIZE_FOR_ELEMENTS(sizeof(InputFrame), LOCAL_INPUT_QUEUE_MAX)] = {0};
static ENetHost *client;
static ENetPeer *peer;
static double zoom_target = 300.0;
static double zoom = 300.0;
#define DEFAULT_ZOOM 300.0
static double zoom_target = DEFAULT_ZOOM;
static double zoom = DEFAULT_ZOOM;
static enum Squad take_over_squad = (enum Squad) - 1; // -1 means not taking over any squad
// images
@ -251,8 +248,8 @@ struct SquadMeta squad_meta(enum Squad squad)
static enum BoxType currently_building()
{
assert(cur_toolbar_slot >= 0);
assert(cur_toolbar_slot < TOOLBAR_SLOTS);
flight_assert(cur_toolbar_slot >= 0);
flight_assert(cur_toolbar_slot < TOOLBAR_SLOTS);
return toolbar[cur_toolbar_slot];
}
@ -279,9 +276,8 @@ static sg_image load_image(const char *path)
stbi_uc *image_data = stbi_load(path, &x, &y, &comp, desired_channels);
if (!image_data)
{
fprintf(stderr, "Failed to load %s image: %s\n", path,
stbi_failure_reason());
exit(-1);
Log("Failed to load %s image: %s\n", path, stbi_failure_reason());
quit_with_popup("Couldn't load an image", "Failed to load image");
}
sg_init_image(to_return,
&(sg_image_desc){.width = x,
@ -303,7 +299,7 @@ static sg_image load_image(const char *path)
void microphone_data_callback(ma_device *pDevice, void *pOutput,
const void *pInput, ma_uint32 frameCount)
{
assert(frameCount == VOIP_EXPECTED_FRAME_COUNT);
flight_assert(frameCount == VOIP_EXPECTED_FRAME_COUNT);
#if 0 // print audio data
Log("Mic data: ");
for (ma_uint32 i = 0; i < VOIP_EXPECTED_FRAME_COUNT; i++)
@ -321,7 +317,7 @@ void microphone_data_callback(ma_device *pDevice, void *pOutput,
queue_clear(&packets_to_send);
packet = queue_push_element(&packets_to_send);
}
assert(packet != NULL);
flight_assert(packet != NULL);
{
opus_int16 muted_audio[VOIP_EXPECTED_FRAME_COUNT] = {0};
const opus_int16 *audio_buffer = (const opus_int16 *)pInput;
@ -340,7 +336,7 @@ void microphone_data_callback(ma_device *pDevice, void *pOutput,
void speaker_data_callback(ma_device *pDevice, void *pOutput,
const void *pInput, ma_uint32 frameCount)
{
assert(frameCount == VOIP_EXPECTED_FRAME_COUNT);
flight_assert(frameCount == VOIP_EXPECTED_FRAME_COUNT);
ma_mutex_lock(&play_packets_mutex);
OpusPacket *cur_packet = (OpusPacket *)queue_pop_element(&packets_to_play);
if (cur_packet != NULL)
@ -371,7 +367,7 @@ static Entity *myentity()
return NULL;
Entity *to_return = get_entity(&gs, myplayer()->entity);
if (to_return != NULL)
assert(to_return->is_player);
flight_assert(to_return->is_player);
return to_return;
}
@ -480,6 +476,8 @@ void draw_textured_rect(double x, double y, double w, double h)
static void init(void)
{
fopen_s(&log_file, "astris_log.txt", "a");
Log("Another day, another game of astris!\n");
queue_init(&packets_to_play, sizeof(OpusPacket), packets_to_play_data,
ARRLEN(packets_to_play_data));
queue_init(&packets_to_send, sizeof(OpusPacket), packets_to_send_data,
@ -494,9 +492,9 @@ static void init(void)
int error;
enc = opus_encoder_create(VOIP_SAMPLE_RATE, 1, OPUS_APPLICATION_VOIP,
&error);
assert(error == OPUS_OK);
flight_assert(error == OPUS_OK);
dec = opus_decoder_create(VOIP_SAMPLE_RATE, 1, &error);
assert(error == OPUS_OK);
flight_assert(error == OPUS_OK);
}
ma_device_config microphone_config =
@ -519,29 +517,30 @@ static void init(void)
result = ma_device_init(NULL, &microphone_config, &microphone_device);
if (result != MA_SUCCESS)
{
Log("Failed to initialize capture device.\n");
exit(-1);
quit_with_popup("Failed to initialize microphone\n", "Failed to initialize audio");
Log("Cap device fail\n");
return;
}
result = ma_device_init(NULL, &speaker_config, &speaker_device);
if (result != MA_SUCCESS)
{
ma_device_uninit(&microphone_device);
quit_with_popup("Failed to initialize speaker/headphones\n", "Failed to initialize audio");
Log("Failed to init speaker\n");
exit(-1);
return;
}
if (ma_mutex_init(&send_packets_mutex) != MA_SUCCESS)
Log("Failed to init send mutex\n");
if (ma_mutex_init(&play_packets_mutex) != MA_SUCCESS)
Log("Failed to init play mutex\n");
flight_assert(ma_mutex_init(&send_packets_mutex) == MA_SUCCESS);
flight_assert(ma_mutex_init(&play_packets_mutex) == MA_SUCCESS);
result = ma_device_start(&microphone_device);
if (result != MA_SUCCESS)
{
ma_device_uninit(&microphone_device);
Log("Failed to start device.\n");
exit(-1);
Log("Failed to start mic.\n");
quit_with_popup("Failed to start microphone device\n", "Failed to start audio");
return;
}
result = ma_device_start(&speaker_device);
@ -550,16 +549,13 @@ static void init(void)
ma_device_uninit(&microphone_device);
ma_device_uninit(&speaker_device);
Log("Failed to start speaker\n");
exit(-1);
quit_with_popup("Failed to start speaker device\n", "Failed to start audio");
return;
}
Log("Initialized audio\n");
}
// @BeforeShip make all fprintf into logging to file, warning dialog grids on
// failure instead of exit(-1), replace the macros in sokol with this as well,
// like assert
Entity *entity_data = malloc(sizeof *entity_data * MAX_ENTITIES);
initialize(&gs, entity_data, sizeof *entity_data * MAX_ENTITIES);
@ -567,131 +563,123 @@ static void init(void)
sg_setup(&sgdesc);
if (!sg_isvalid())
{
fprintf(stderr, "Failed to create Sokol GFX context!\n");
exit(-1);
Log("Failed to create Sokol GFX context!\n");
quit_with_popup("Failed to start sokol gfx context, something is really boned", "Failed to start graphics");
}
sgp_desc sgpdesc = {0};
sgp_setup(&sgpdesc);
if (!sgp_is_valid())
{
fprintf(stderr, "Failed to create Sokol GP context: %s\n",
sgp_get_error_message(sgp_get_last_error()));
exit(-1);
Log("Failed to create Sokol GP context: %s\n", sgp_get_error_message(sgp_get_last_error()));
quit_with_popup("Failed to create Sokol GP context, something is really boned", "Failed to start graphics");
}
// shaders
// initialize shaders
{
// initialize shader
sgp_pipeline_desc pip_desc = {
.shader = *hueshift_program_shader_desc(sg_query_backend()),
.blend_mode = SGP_BLENDMODE_BLEND,
};
hueshift_pipeline = sgp_make_pipeline(&pip_desc);
if (sg_query_pipeline_state(hueshift_pipeline) != SG_RESOURCESTATE_VALID)
{
{
sgp_pipeline_desc pip_desc = {
.shader = *hueshift_program_shader_desc(sg_query_backend()),
.blend_mode = SGP_BLENDMODE_BLEND,
};
hueshift_pipeline = sgp_make_pipeline(&pip_desc);
if (sg_query_pipeline_state(hueshift_pipeline) != SG_RESOURCESTATE_VALID)
{
fprintf(stderr, "failed to make hueshift pipeline\n");
exit(-1);
}
}
{
sgp_pipeline_desc pip_desc = {
.shader = *goodpixel_program_shader_desc(sg_query_backend()),
.blend_mode = SGP_BLENDMODE_BLEND,
};
Log("Failed to make hueshift pipeline\n");
quit_with_popup("Failed to make hueshift pipeline", "Boned Hueshift Shader");
}
goodpixel_pipeline = sgp_make_pipeline(&pip_desc);
if (sg_query_pipeline_state(goodpixel_pipeline) != SG_RESOURCESTATE_VALID)
{
fprintf(stderr, "failed to make goodpixel pipeline\n");
exit(-1);
}
}
{
sgp_pipeline_desc pip_desc = {
.shader = *goodpixel_program_shader_desc(sg_query_backend()),
.blend_mode = SGP_BLENDMODE_BLEND,
};
// images loading
goodpixel_pipeline = sgp_make_pipeline(&pip_desc);
if (sg_query_pipeline_state(goodpixel_pipeline) != SG_RESOURCESTATE_VALID)
{
for (int i = 0; i < ARRLEN(boxes); i++)
{
boxes[i].image = load_image(boxes[i].image_path);
}
image_thrusterburn = load_image("loaded/thrusterburn.png");
image_itemframe = load_image("loaded/itemframe.png");
image_itemframe_selected = load_image("loaded/itemframe_selected.png");
image_player = load_image("loaded/player.png");
image_cockpit_used = load_image("loaded/cockpit_used.png");
image_stars = load_image("loaded/stars.png");
image_stars2 = load_image("loaded/stars2.png");
image_sun = load_image("loaded/sun.png");
image_medbay_used = load_image("loaded/medbay_used.png");
image_mystery = load_image("loaded/mystery.png");
image_explosion = load_image("loaded/explosion.png");
image_low_health = load_image("loaded/low_health.png");
image_mic_muted = load_image("loaded/mic_muted.png");
image_mic_unmuted = load_image("loaded/mic_unmuted.png");
image_flag_available = load_image("loaded/flag_available.png");
image_flag_taken = load_image("loaded/flag_ripped.png");
image_squad_invite = load_image("loaded/squad_invite.png");
image_check = load_image("loaded/check.png");
image_no = load_image("loaded/no.png");
image_solarpanel_charging = load_image("loaded/solarpanel_charging.png");
image_scanner_head = load_image("loaded/scanner_head.png");
image_itemswitch = load_image("loaded/itemswitch.png");
image_cloaking_panel = load_image("loaded/cloaking_panel.png");
image_missile_burning = load_image("loaded/missile_burning.png");
image_missile = load_image("loaded/missile.png");
Log("Failed to make goodpixel pipeline\n");
quit_with_popup("Couldn't make a shader! Uhhh ooooohhhhhh!!!", "Shader error BONED");
}
}
}
// socket initialization
{
if (enet_initialize() != 0)
{
fprintf(stderr, "An error occurred while initializing ENet.\n");
exit(-1);
}
client = enet_host_create(NULL /* create a client host */,
1 /* only allow 1 outgoing connection */,
2 /* allow up 2 channels to be used, 0 and 1 */,
0 /* assume any amount of incoming bandwidth */,
0 /* assume any amount of outgoing bandwidth */);
if (client == NULL)
{
fprintf(
stderr,
"An error occurred while trying to create an ENet client host.\n");
exit(-1);
}
ENetAddress address;
ENetEvent event;
enet_address_set_host(&address, SERVER_ADDRESS);
Log("Connecting to %s:%d\n", SERVER_ADDRESS, SERVER_PORT);
address.port = SERVER_PORT;
peer = enet_host_connect(client, &address, 2, 0);
if (peer == NULL)
{
fprintf(stderr,
"No available peers for initiating an ENet connection.\n");
exit(-1);
}
// the timeout is the third parameter here
if (enet_host_service(client, &event, 5000) > 0 &&
event.type == ENET_EVENT_TYPE_CONNECT)
{
Log("Connected\n");
}
else
{
/* Either the 5 seconds are up or a disconnect event was */
/* received. Reset the peer in the event the 5 seconds */
/* had run out without any significant event. */
enet_peer_reset(peer);
fprintf(stderr, "Connection to server failed.");
exit(-1);
}
}
// images loading
{
for (int i = 0; i < ARRLEN(boxes); i++)
{
boxes[i].image = load_image(boxes[i].image_path);
}
image_thrusterburn = load_image("loaded/thrusterburn.png");
image_itemframe = load_image("loaded/itemframe.png");
image_itemframe_selected = load_image("loaded/itemframe_selected.png");
image_player = load_image("loaded/player.png");
image_cockpit_used = load_image("loaded/cockpit_used.png");
image_stars = load_image("loaded/stars.png");
image_stars2 = load_image("loaded/stars2.png");
image_sun = load_image("loaded/sun.png");
image_medbay_used = load_image("loaded/medbay_used.png");
image_mystery = load_image("loaded/mystery.png");
image_explosion = load_image("loaded/explosion.png");
image_low_health = load_image("loaded/low_health.png");
image_mic_muted = load_image("loaded/mic_muted.png");
image_mic_unmuted = load_image("loaded/mic_unmuted.png");
image_flag_available = load_image("loaded/flag_available.png");
image_flag_taken = load_image("loaded/flag_ripped.png");
image_squad_invite = load_image("loaded/squad_invite.png");
image_check = load_image("loaded/check.png");
image_no = load_image("loaded/no.png");
image_solarpanel_charging = load_image("loaded/solarpanel_charging.png");
image_scanner_head = load_image("loaded/scanner_head.png");
image_itemswitch = load_image("loaded/itemswitch.png");
image_cloaking_panel = load_image("loaded/cloaking_panel.png");
image_missile_burning = load_image("loaded/missile_burning.png");
image_missile = load_image("loaded/missile.png");
}
// socket initialization
{
if (enet_initialize() != 0)
{
Log("An error occurred while initializing ENet.\n");
quit_with_popup("Failed to initialize networking, enet error", "Networking Error");
}
client = enet_host_create(NULL /* create a client host */,
1 /* only allow 1 outgoing connection */,
2 /* allow up 2 channels to be used, 0 and 1 */,
0 /* assume any amount of incoming bandwidth */,
0 /* assume any amount of outgoing bandwidth */);
if (client == NULL)
{
Log("An error occurred while trying to create an ENet client host.\n");
quit_with_popup("Failed to initialize the networking. Mama mia!", "Networking uh oh");
}
ENetAddress address;
ENetEvent event;
enet_address_set_host(&address, SERVER_ADDRESS);
Log("Connecting to %s:%d\n", SERVER_ADDRESS, SERVER_PORT);
address.port = SERVER_PORT;
peer = enet_host_connect(client, &address, 2, 0);
if (peer == NULL)
{
Log("No available peers for initiating an ENet connection.\n");
quit_with_popup("Failed to initialize the networking. Mama mia!", "Networking uh oh");
}
// the timeout is the third parameter here
if (enet_host_service(client, &event, 5000) > 0 &&
event.type == ENET_EVENT_TYPE_CONNECT)
{
Log("Connected\n");
}
else
{
/* Either the 5 seconds are up or a disconnect event was */
/* received. Reset the peer in the event the 5 seconds */
/* had run out without any significant event. */
enet_peer_reset(peer);
Log("Failed to connect to server. It might be too full\n");
quit_with_popup("Failed to connect to server. Is your wifi down? It took too long. The server could also be too full. I unfortunately do not have information as to which issue it is at this time. It could also be that the server is down. I hate this networking library", "Connection Failure");
}
}
}
@ -1423,7 +1411,6 @@ static void frame(void)
PROFILE_SCOPE("frame")
{
double width = (float)sapp_width(), height = (float)sapp_height();
double ratio = width / height;
double exec_time = sapp_frame_count() * sapp_frame_duration();
double dt = sapp_frame_duration();
@ -1478,7 +1465,7 @@ static void frame(void)
unsigned char *decompressed = malloc(
sizeof *decompressed * MAX_SERVER_TO_CLIENT); // @Robust no malloc
size_t decompressed_max_len = MAX_SERVER_TO_CLIENT;
assert(LZO1X_MEM_DECOMPRESS == 0);
flight_assert(LZO1X_MEM_DECOMPRESS == 0);
ma_mutex_lock(&play_packets_mutex);
ServerToClient msg = (ServerToClient){
@ -1513,8 +1500,8 @@ static void frame(void)
case ENET_EVENT_TYPE_DISCONNECT:
{
fprintf(stderr, "Disconnected from server\n");
exit(-1);
Log("Disconnected from server\n");
quit_with_popup("Disconnected from server", "Disconnected");
break;
}
}
@ -1525,7 +1512,7 @@ static void frame(void)
}
else if (enet_status < 0)
{
fprintf(stderr, "Error receiving enet events: %d\n", enet_status);
Log("Error receiving enet events: %d\n", enet_status);
break;
}
}
@ -1556,7 +1543,6 @@ static void frame(void)
dilating_time_factor = 1.0;
}
// snap in dire cases
if (healthy_num_ticks_ahead >= TICKS_BEHIND_DO_SNAP && ticks_should_repredict < healthy_num_ticks_ahead - TICKS_BEHIND_DO_SNAP)
{
@ -1700,7 +1686,7 @@ static void frame(void)
InputFrame *to_discard = queue_pop_element(&input_queue);
(void)to_discard;
to_push_to = queue_push_element(&input_queue);
assert(to_push_to != NULL);
flight_assert(to_push_to != NULL);
}
*to_push_to = cur_input_frame;
@ -1723,7 +1709,7 @@ static void frame(void)
} while (time_to_process >= TIMESTEP);
cpVect after = my_player_pos();
// use theses variables to suss out reprediction errors, enables you to
// use theses variables to suss out reprediction errors, enables you to
// breakpoint on when they happen
(void)before;
(void)after;
@ -1752,7 +1738,12 @@ static void frame(void)
ENetPacket *packet =
enet_packet_create((void *)compressed, compressed_len,
ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT);
enet_peer_send(peer, 0, packet); // @Robust error check this
int err = enet_peer_send(peer, 0, packet);
if (err < 0)
{
Log("Failed to send packet error %d\n", err);
enet_packet_destroy(packet);
}
last_sent_input_time = stm_now();
}
else
@ -1912,8 +1903,10 @@ static void frame(void)
}
}
player_scaling =
lerp(player_scaling, zoom < 6.5 ? 100.0 : 1.0, dt * 7.0);
double player_scaling_target = zoom < 6.5 ? 100.0 : 1.0;
if (zoom > 50.0)
player_scaling = player_scaling_target; // For press tab zoom shortcut. Bad hack to make zooming in not jarring with the bigger player. Comment this out and press tab to see!
player_scaling = lerp(player_scaling, player_scaling_target, dt * 15.0);
ENTITIES_ITER(e)
{
// draw grid
@ -2187,6 +2180,7 @@ static void frame(void)
void cleanup(void)
{
fclose(log_file);
sg_destroy_pipeline(hueshift_pipeline);
ma_mutex_lock(&server_info.info_mutex);
@ -2226,6 +2220,17 @@ void event(const sapp_event *e)
mouse_frozen = !mouse_frozen;
}
#endif
if (e->key_code == SAPP_KEYCODE_TAB)
{
if (zoom_target < DEFAULT_ZOOM)
{
zoom_target = DEFAULT_ZOOM;
}
else
{
zoom_target = ZOOM_MIN;
}
}
if (e->key_code == SAPP_KEYCODE_R)
{
cur_editing_rotation += 1;
@ -2297,11 +2302,6 @@ void event(const sapp_event *e)
{
mouse_pos = (cpVect){.x = e->mouse_x, .y = e->mouse_y};
}
if (right_mouse_down)
{
funval += e->mouse_dx;
Log("Funval %f\n", funval);
}
break;
default:
{
@ -2309,8 +2309,12 @@ void event(const sapp_event *e)
}
}
extern void do_crash_handler();
sapp_desc sokol_main(int argc, char *argv[])
{
// do_crash_handler();
bool hosting = false;
stm_setup();
ma_mutex_init(&server_info.info_mutex);
@ -2337,4 +2341,4 @@ sapp_desc sokol_main(int argc, char *argv[])
.win32_console_attach = true,
.sample_count = 4, // anti aliasing
};
}
}

@ -1,4 +1,9 @@
git push
call build_release.bat
call update_server.bat
tar.exe -a -c -f releases\flight-nonumber.zip flight_release.exe loaded
tar.exe -a -c -f releases\flight-nonumber.zip flight_release.exe loaded LICENSE.txt
echo "Now test flight-nonumber and make sure it works. Once everything is confirmed to be working:"
echo "1. Increment the GIT_RELEASE_TAG in buildsettings.h"
echo "2. Add everything to git and commit"
echo "3. Tag the new commit the _exact same_ as the previously mentioned GIT_RELEASE_TAG"
echo "4. Push everything, then update all the servers (@TODO make this a script that works for multiple servers)"

@ -236,7 +236,6 @@ void server(void *info_raw)
else
{
int64_t player_slot = (int64_t)event.peer->data;
size_t length = event.packet->dataLength;
#define VOIP_QUEUE_DECL(queue_name, queue_data_name) \
Queue queue_name = {0}; \
char queue_data_name[QUEUE_SIZE_FOR_ELEMENTS(sizeof(OpusPacket), VOIP_PACKET_BUFFER_SIZE)] = {0}; \
@ -250,7 +249,7 @@ void server(void *info_raw)
struct ClientToServer received = {.mic_data = buffer_to_fill, .input_data = &player_input_queues[player_slot]};
unsigned char decompressed[MAX_CLIENT_TO_SERVER] = {0};
size_t decompressed_max_len = MAX_CLIENT_TO_SERVER;
assert(LZO1X_MEM_DECOMPRESS == 0);
flight_assert(LZO1X_MEM_DECOMPRESS == 0);
int return_value = lzo1x_decompress_safe(event.packet->data, event.packet->dataLength, decompressed, &decompressed_max_len, NULL);
@ -297,13 +296,12 @@ void server(void *info_raw)
}
}
}
total_time += (float)stm_sec(stm_diff(stm_now(), last_processed_time));
total_time += stm_sec(stm_diff(stm_now(), last_processed_time));
last_processed_time = stm_now();
// @Robost @BeforeShip if can't process quick enough will be stuck being lagged behind, think of a solution for this...
const double max_time = 5.0 * TIMESTEP;
if (total_time > max_time)
{
Log("Abnormally large total time %f, clamping\n", total_time);
Log("SERVER LAGGING Abnormally large total time %f, clamping\n", total_time);
total_time = max_time;
}

Binary file not shown.

@ -3,8 +3,8 @@
#include "buildsettings.h"
#define MAX_BOX_TYPES 64
#define ZOOM_MIN 0.25f
#define ZOOM_MAX 1500.0f
#define ZOOM_MIN 0.10 // smaller means you can zoom out more
#define ZOOM_MAX 1500.0 // bigger means you can zoom in more
#define MAX_PLAYERS 16
#define MAX_SUNS 8
#define MAX_ENTITIES 1024 * 25
@ -12,7 +12,11 @@
#define MERGE_MAX_DIST (BOX_SIZE / 2.0f + 0.01f)
#define PLAYER_SIZE ((cpVect){.x = BOX_SIZE, .y = BOX_SIZE})
#define PLAYER_MASS 0.5f
#define PLAYER_JETPACK_FORCE 2.0f
#ifdef FAT_THRUSTERS
#define PLAYER_JETPACK_FORCE 200.0f
#else
#define PLAYER_JETPACK_FORCE 3.5f
#endif
#define PLAYER_JETPACK_TORQUE 0.05f
#define MISSILE_RANGE 4.0f
#define MISSILE_BURN_TIME 1.5f
@ -25,10 +29,9 @@
// centered on the sprite
#define MISSILE_SPRITE_SIZE ((cpVect){.x = BOX_SIZE, .y = BOX_SIZE})
#define MISSILE_COLLIDER_SIZE ((cpVect){.x = BOX_SIZE * 0.5f, .y = BOX_SIZE * 0.5f})
// #define PLAYER_JETPACK_FORCE 20.0f
// distance at which things become geostationary and no more solar power!
#define PLAYER_JETPACK_ROTATION_ENERGY_PER_SECOND 0.2f
#define PLAYER_JETPACK_SPICE_PER_SECOND 0.2f
#define PLAYER_JETPACK_SPICE_PER_SECOND 0.08f
#define SCANNER_ENERGY_USE 0.05f
#define MAX_HAND_REACH 1.0f
#define SCANNER_SCAN_RATE 0.5f
@ -80,8 +83,7 @@
#define MAX_MS_SPENT_REPREDICTING 30.0f
#define TIME_BETWEEN_SEND_GAMESTATE (1.0f / 20.0f)
#define TIME_BETWEEN_INPUT_PACKETS (1.0f / 20.0f)
#define TIMESTEP (1.0f / 60.0f) // server required to simulate at this, defines what tick the game is on
#define SERVER_PORT 2551
#define TIMESTEP (1.0f / 60.0f) // server required to simulate at this, defines what tick the game is on
#define LOCAL_INPUT_QUEUE_MAX 90 // please god let you not have more than 90 frames of game latency
#define INPUT_QUEUE_MAX 15
@ -97,19 +99,15 @@
#define ARRLEN(x) (sizeof(x) / sizeof((x)[0]))
#include "cpVect.h" // offers vector functions and types for the structs
#include "miniaudio.h" // @Robust BAD. using miniaudio mutex construct for server thread synchronization. AWFUL!
#include "cpVect.h" // offers vector functions and types for the structs
// @Robust remove this include somehow, needed for sqrt and cos
#include <math.h>
#include <math.h> // sqrt and cos vector functions
#include <stdint.h> // tick is unsigned integer
#include <stdio.h> // logging on errors for functions
// defined in gamestate.c. Janky
#ifndef assert
#define assert(condition) __flight_assert(condition, __FILE__, __LINE__, #condition)
#endif
#define flight_assert(condition) __flight_assert(condition, __FILE__, __LINE__, #condition)
// including headers from headers bad
@ -134,10 +132,22 @@ typedef int opus_int32;
#endif
#define Log(...) \
{ \
fprintf(stdout, "%s:%d | ", __FILE__, __LINE__); \
fprintf(stdout, __VA_ARGS__); \
extern FILE *log_file;
#include <time.h> // the time in logging!
void fill_time_string(char *to_fill, size_t max_length);
#define Log(...) \
{ \
char time_string[2048] = {0}; \
fill_time_string(time_string, 2048); \
fprintf(stdout, "%s | %s:%d | ", time_string, __FILE__, __LINE__); \
fprintf(stdout, __VA_ARGS__); \
if (log_file != NULL) \
{ \
fprintf(log_file, "%s | %s:%d | ", time_string, __FILE__, __LINE__); \
fprintf(log_file, __VA_ARGS__); \
} \
}
enum BoxType
@ -186,7 +196,7 @@ typedef struct EntityID
static inline bool entityids_same(EntityID a, EntityID b)
{
return (a.generation == b.generation) && (a.index == b.index);
return (a.generation == b.generation) && (a.index == b.index);
}
// when updated, must update serialization, comparison in main.c, and the server
@ -204,7 +214,6 @@ typedef struct InputFrame
bool seat_action;
cpVect hand_pos; // local to player transationally but not rotationally
// @BeforeShip bounds check on the hand_pos so that players can't reach across the entire map
bool dobuild;
enum BoxType build_type;
@ -301,10 +310,10 @@ typedef struct Entity
// scanner only stuff!
EntityID currently_scanning;
double currently_scanning_progress; // when 1.0, scans it!
BOX_UNLOCKS_TYPE blueprints_learned; // @Robust make this same type as blueprints
BOX_UNLOCKS_TYPE blueprints_learned;
double scanner_head_rotate_speed; // not serialized, cosmetic
double scanner_head_rotate;
cpVect platonic_nearest_direction; // normalized
cpVect platonic_nearest_direction; // normalized
double platonic_detection_strength; // from zero to one
} Entity;
@ -333,11 +342,8 @@ typedef struct GameState
{
cpSpace *space;
// @Robust for the integer tick, also store a double for how much time has been processed.
// Like a whole timestep then a double for subtimestep
uint64_t tick;
double subframe_time;
double subframe_time; // @Robust remove this, I don't think it's used anymore
cpVect goldpos;
@ -429,6 +435,8 @@ uint64_t tick(struct GameState *gs);
double elapsed_time(GameState *gs);
double sun_dist_no_gravity(Entity *sun);
void quit_with_popup(const char *message_utf8, const char *title_utf8);
// all of these return if successful or not
bool server_to_client_serialize(struct ServerToClient *msg, unsigned char *bytes, size_t *out_len, size_t max_len, Entity *for_this_player, bool to_disk);
bool server_to_client_deserialize(struct ServerToClient *msg, unsigned char *bytes, size_t max_len, bool from_disk);
@ -553,7 +561,7 @@ static inline double cpvanglediff(cpVect a, cpVect b)
{
double acos_input = cpvdot(a, b) / (cpvlength(a) * cpvlength(b));
acos_input = clamp(acos_input, -1.0f, 1.0f);
assert(acos_input >= -1.0f && acos_input <= 1.0f);
flight_assert(acos_input >= -1.0f && acos_input <= 1.0f);
return acos(acos_input) * sign(cpvdot(a, b));
}

Loading…
Cancel
Save