Compare commits

...

28 Commits

Author SHA1 Message Date
Cameron Murphy Reikes 449b0a889a Fix name conflict with linux stdlib 1 year ago
Cameron Murphy Reikes db7ce69b0e Big netcode refactor to fixed timestep 1 year ago
Cameron Murphy Reikes 720b1f23ed Build system fixes 1 year ago
Cameron Murphy Reikes 8475393c33 Commit build settings to repo, consolodate 1 year ago
Cameron Murphy Reikes 17cebee22e Xtra profiling blocks 1 year ago
Cameron Murphy Reikes a8157f076b Profiling, but also SLOWS DOWN Game??? 1 year ago
Cameron Murphy Reikes 9259536370 Inline some stuff 1 year ago
root fccfed5c6b Fix broken linux build 1 year ago
Cameron Murphy Reikes 7ec58bea5a Bunch of visual fixes 1 year ago
Cameron Murphy Reikes 53afb37e52 Remove V2 from project 1 year ago
Cameron Murphy Reikes af2bf08607 Fix build warnings and remove V2 functions 1 year ago
Cameron Murphy Reikes ba27f9b72f Serialize some fields as floats instead of doubles 1 year ago
Cameron Murphy Reikes d8bfe6d879 Change all gamestate to double precision floats 1 year ago
Cameron Murphy Reikes 72e36e9b3d Fix star and galaxy rendering 1 year ago
Cameron Murphy Reikes 144a2a532f No idea what to do about this bad bug 1 year ago
Cameron Murphy Reikes 42711478e1 Fixes 1 year ago
Cameron Murphy Reikes 3a3614a9f2 Add new art 1 year ago
Cameron Murphy Reikes 847e4b835d More missile safety 1 year ago
Cameron Murphy Reikes 082d3a8e42 Many suns 1 year ago
Cameron Murphy Reikes 7b2b09c42f Sun just added more mass christmas fatass 1 year ago
Cameron Murphy Reikes c546f5214c Sun uses mass now 1 year ago
Cameron Murphy Reikes 51b5c1494e Merge block fixes. Holy fuck! 1 year ago
Cameron Murphy Reikes a7232b0831 Generate merge block station 2 years ago
Cameron Murphy Reikes d257b99b15 Clamp reprediction on time spent computing 2 years ago
Cameron Murphy Reikes a9691e1f3a Add merge box 2 years ago
Cameron Murphy Reikes 04057fd873 Missile velocity fix 2 years ago
Cameron Murphy Reikes 3f97bdfea8 Fix release script and add missile station 2 years ago
Cameron Murphy Reikes b45974af1d Add missile block 2 years ago

@ -1,4 +1,5 @@
BasedOnStyle: LLVM
ColumnLimit: 0
SpacesInParentheses: false
BreakBeforeBraces: Allman
BreakBeforeBraces: Allman
IndentWidth: 2

3
.gitignore vendored

@ -1,3 +1,5 @@
# profiling results
*.spall
compile_commands.json
.cache/
enc_temp_folder/
@ -7,7 +9,6 @@ debug_world.bin
flight*.zip
flight_server
flight.zip
ipsettings.h
releases/
.vs/
x64/

@ -1 +1,2 @@
thirdparty/
thirdparty/opus/
thirdparty/minilzo/

@ -0,0 +1,22 @@
{
"configurations": [
{
"name": "Win32",
"includePath": [
"${workspaceFolder}/**"
],
"defines": [
"_DEBUG",
"UNICODE",
"_UNICODE"
],
"windowsSdkVersion": "10.0.22000.0",
"compilerPath": "C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Tools/MSVC/14.33.31629/bin/Hostx64/x64/cl.exe",
"cStandard": "c17",
"cppStandard": "c++17",
"intelliSenseMode": "windows-msvc-x64",
"compileCommands": "${workspaceFolder}/compile_commands.json"
}
],
"version": 4
}

@ -52,6 +52,13 @@
"xtr1common": "c",
"xtree": "c",
"stdint.h": "c",
"ipsettings.h": "c"
"ipsettings.h": "c",
"cpvect.h": "c",
"miniaudio.h": "c",
"bit": "c",
"condition_variable": "c",
"ctime": "c",
"limits": "c",
"xlocinfo": "c"
}
}

@ -140,10 +140,11 @@
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions);DEBUG_RENDERING;SERVER_ADDRESS="127.0.0.1"</PreprocessorDefinitions>
<PreprocessorDefinitions>_DEBUG;DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>$(ProjectDir)\thirdparty\enet\include;$(ProjectDir)\thirdparty\Chipmunk2D\include\chipmunk;$(ProjectDir)\thirdparty;$(ProjectDir)\thirdparty\Chipmunk2D\include;$(ProjectDir)\thirdparty\minilzo;$(ProjectDir)\thirdparty\opus\include</AdditionalIncludeDirectories>
<TreatWarningAsError>true</TreatWarningAsError>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
@ -155,7 +156,7 @@
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions);DEBUG_RENDERING;</PreprocessorDefinitions>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>C:\Users\Cameron\Documents\flight\thirdparty\enet\include;C:\Users\Cameron\Documents\flight\thirdparty\Chipmunk2D\include\chipmunk;C:\Users\Cameron\Documents\flight\thirdparty;C:\Users\Cameron\Documents\flight\thirdparty\Chipmunk2D\include;C:\Users\Cameron\Documents\flight\thirdparty\minilzo</AdditionalIncludeDirectories>
<TreatWarningAsError>true</TreatWarningAsError>
@ -172,7 +173,7 @@
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>NDEBUG;RELEASE;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>$(ProjectDir)\thirdparty\enet\include;$(ProjectDir)\thirdparty\Chipmunk2D\include\chipmunk;$(ProjectDir)\thirdparty;$(ProjectDir)\thirdparty\Chipmunk2D\include;$(ProjectDir)\thirdparty\minilzo;$(ProjectDir)\thirdparty\opus\include</AdditionalIncludeDirectories>
</ClCompile>
@ -235,6 +236,7 @@
<ClCompile Include="thirdparty\minilzo\minilzo.c" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="buildsettings.h" />
<ClInclude Include="goodpixel.gen.h" />
<ClInclude Include="hueshift.gen.h" />
<ClInclude Include="ipsettings.h" />

@ -200,6 +200,9 @@
<ClInclude Include="goodpixel.gen.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="buildsettings.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="thirdparty\enet\enet_dll.cbp" />

@ -4,7 +4,7 @@
<ShowAllFiles>false</ShowAllFiles>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LocalDebuggerCommandArguments>--serve</LocalDebuggerCommandArguments>
<LocalDebuggerCommandArguments>--host</LocalDebuggerCommandArguments>
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug No Host|x64'">

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

@ -6,4 +6,4 @@ cmake ..
cmake --build .
cd -
gcc -o flight_server -Wall -O2 -Ithirdparty -Ithirdparty/opus/include -Ithirdparty/enet/include -Ithirdparty/minilzo -Ithirdparty/Chipmunk2D/include -Ithirdparty/Chipmunk2D/include/chipmunk server_main.c server.c debugdraw.c gamestate.c sokol_impl.c thirdparty/minilzo/minilzo.c thirdparty/enet/*.c thirdparty/Chipmunk2D/src/*.c -lm -lpthread -ldl thirdparty/opus/build/libopus.a
gcc -o flight_server -Wall -O2 -DNDEBUG -DRELEASE -Ithirdparty -Ithirdparty/opus/include -Ithirdparty/enet/include -Ithirdparty/minilzo -Ithirdparty/Chipmunk2D/include -Ithirdparty/Chipmunk2D/include/chipmunk server_main.c server.c debugdraw.c gamestate.c sokol_impl.c thirdparty/minilzo/minilzo.c thirdparty/enet/*.c thirdparty/Chipmunk2D/src/*.c -lm -lpthread -ldl thirdparty/opus/build/libopus.a

@ -3,10 +3,11 @@
@REM what all the compile flags mean: https://learn.microsoft.com/en-us/cpp/build/reference/compiler-options-listed-by-category?view=msvc-170
set OPUSLIB=%~dp0\thirdparty\opus\win32\VS2015\x64\Release\opus.lib
set OPUSLIB=%~dp0thirdparty\opus\win32\VS2015\x64\Release\opus.lib
WHERE %OPUSLIB%
IF %ERRORLEVEL% NEQ 0 ECHO ERROR Couldn't find %OPUSLIB% compile opus by opening the visual studio project in win32\VS2015 and building the release setting
if not exist %OPUSLIB% (
ECHO ERROR Couldn't find %OPUSLIB% compile opus by opening the visual studio project in win32\VS2015 and building the release setting
)
setlocal enabledelayedexpansion enableextensions
pushd thirdparty\Chipmunk2D\src

@ -1,2 +1,3 @@
set compileopts=/Fe"flight_release" /O2
call shadergen.bat
set compileopts=/Fe"flight_release" /O2 /DRELEASE /DNDEBUG
call build_msvc.bat

@ -0,0 +1,32 @@
#pragma once
#ifdef SERVER_ADDRESS
#error Don't define server address from the build system, use the build settings header
#endif
#ifdef DEBUG
#define SERVER_ADDRESS "127.0.0.1"
// #define PROFILING
#define DEBUG_RENDERING
#define DEBUG_WORLD
#define UNLOCK_ALL
#define INFINITE_RESOURCES
#define NO_GRAVITY
#define NO_SUNS
#else
#ifdef RELEASE
// #define PROFILING
// #define SERVER_ADDRESS "127.0.0.1"
#define SERVER_ADDRESS "207.246.80.160"
#else
#error Define either DEBUG or RELEASE
#endif
#endif

@ -1,8 +1,10 @@
#include "types.h"
#include "buildsettings.h"
#ifdef DEBUG_RENDERING
#include "sokol_gfx.h"
#include "sokol_gp.h"
#endif
#include "types.h"
#define MAX_COMMANDS 64
@ -16,13 +18,13 @@ typedef struct Command
union
{
// rect
V2 center;
cpVect center;
// line
struct
{
V2 from;
V2 to;
cpVect from;
cpVect to;
};
};
} Command;
@ -36,27 +38,25 @@ static THREADLOCAL int command_i = 0;
void dbg_drawall()
{
// return;
#ifdef DEBUG_RENDERING
sgp_set_color(0.4f, 0.8f, 0.2f, 0.8f);
for (int i = 0; i < command_i; i++)
{
const float size = 0.05f;
const double size = 0.05;
switch (commands[i].type)
{
case rect:
{
V2 center = commands[i].center;
V2 upper_left = V2add(center, (V2){.x = -size / 2.0f, .y = -size / 2.0f});
sgp_draw_filled_rect(upper_left.x, upper_left.y, size, size);
cpVect center = commands[i].center;
cpVect upper_left = cpvadd(center, (cpVect){.x = -size / 2.0, .y = -size / 2.0});
sgp_draw_filled_rect((float)upper_left.x, (float)upper_left.y, (float)size, (float)size);
break;
}
case line:
{
V2 from = commands[i].from;
V2 to = commands[i].to;
sgp_draw_line(from.x, from.y, to.x, to.y);
cpVect from = commands[i].from;
cpVect to = commands[i].to;
sgp_draw_line((float)from.x, (float)from.y, (float)to.x, (float)to.y);
break;
}
}
@ -65,7 +65,7 @@ void dbg_drawall()
command_i = 0;
}
void dbg_line(V2 from, V2 to)
void dbg_line(cpVect from, cpVect to)
{
commands[command_i] = (Command){
.type = line,
@ -76,7 +76,7 @@ void dbg_line(V2 from, V2 to)
command_i %= MAX_COMMANDS;
}
void dbg_rect(V2 center)
void dbg_rect(cpVect center)
{
commands[command_i] = (Command){
.type = rect,

Binary file not shown.

File diff suppressed because it is too large Load Diff

@ -14,6 +14,7 @@ uniform sampler2D iChannel0;
uniform uniforms {
int is_colorless; // if greater than zero, no color
float target_hue;
float alpha;
};
in vec2 texUV;
out vec4 fragColor;
@ -47,14 +48,19 @@ vec3 hsv2rgb(vec3 c)
void main() {
vec4 outColor = texture2DAA(iChannel0, texUV);
outColor.a *= alpha;
vec3 hsv = rgb2hsv(outColor.rgb);
if(is_colorless > 0)
{
hsv.y = 0.0f;
} else if(hsv.y > 0.5) {
hsv.x = target_hue;
float green_hue = 118.0f / 360.0f;
if(abs(hsv.r - green_hue) < 0.15) {
if(is_colorless > 0)
{
hsv.y = 0.0f;
} else {
hsv.x = target_hue;
}
}
fragColor = vec4(hsv2rgb(hsv), outColor.a);
}
@end

Binary file not shown.

Before

Width:  |  Height:  |  Size: 430 B

After

Width:  |  Height:  |  Size: 341 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 289 B

After

Width:  |  Height:  |  Size: 560 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 428 B

After

Width:  |  Height:  |  Size: 644 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 323 B

After

Width:  |  Height:  |  Size: 542 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 294 B

After

Width:  |  Height:  |  Size: 497 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 270 B

After

Width:  |  Height:  |  Size: 404 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 373 B

After

Width:  |  Height:  |  Size: 667 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 778 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 537 B

After

Width:  |  Height:  |  Size: 427 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 503 B

After

Width:  |  Height:  |  Size: 446 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 192 B

After

Width:  |  Height:  |  Size: 212 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 366 B

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 381 B

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 363 B

After

Width:  |  Height:  |  Size: 455 B

1757
main.c

File diff suppressed because it is too large Load Diff

@ -0,0 +1,98 @@
#include "types.h"
#include <stdlib.h> // malloc the profiling buffer
#ifdef PROFILING_H
#error only include profiling.h once
#endif
#define PROFILING_H
#ifdef PROFILING
#define PROFILING_BUFFER_SIZE (1 * 1024 * 1024)
#ifdef PROFILING_IMPL
#define SPALL_IMPLEMENTATION
#pragma warning(disable : 4996) // spall uses fopen
#include "spall.h"
#define WIN32_LEAN_AND_MEAN
#define VC_EXTRALEAN
#define NOMINMAX
#include <Windows.h>
// This is slow, if you can use RDTSC and set the multiplier in SpallInit, you'll have far better timing accuracy
double get_time_in_micros()
{
static double invfreq;
if (!invfreq)
{
LARGE_INTEGER frequency;
QueryPerformanceFrequency(&frequency);
invfreq = 1000000.0 / frequency.QuadPart;
}
LARGE_INTEGER counter;
QueryPerformanceCounter(&counter);
return counter.QuadPart * invfreq;
}
SpallProfile spall_ctx;
THREADLOCAL SpallBuffer spall_buffer;
THREADLOCAL unsigned char *buffer_data = NULL;
THREADLOCAL uint32_t my_thread_id = 0;
void init_profiling(const char *filename)
{
spall_ctx = SpallInit(filename, 1);
}
void init_profiling_mythread(uint32_t id)
{
my_thread_id = id;
if (buffer_data != NULL)
{
*(int *)0 = 0;
}
buffer_data = malloc(PROFILING_BUFFER_SIZE);
spall_buffer = (SpallBuffer){
.length = PROFILING_BUFFER_SIZE,
.data = buffer_data,
};
SpallBufferInit(&spall_ctx, &spall_buffer);
}
void end_profiling_mythread()
{
SpallBufferFlush(&spall_ctx, &spall_buffer);
SpallBufferQuit(&spall_ctx, &spall_buffer);
free(buffer_data);
}
void end_profiling()
{
SpallQuit(&spall_ctx);
}
#endif // PROFILING_IMPL
#include "spall.h"
extern SpallProfile spall_ctx;
extern THREADLOCAL SpallBuffer spall_buffer;
extern THREADLOCAL uint32_t my_thread_id;
double get_time_in_micros();
void init_profiling(const char *filename);
// you can pass anything to id as long as it's different from other threads
void init_profiling_mythread(uint32_t id);
void end_profiling();
void end_profiling_mythread();
#define PROFILE_SCOPE(name) DeferLoop(SpallTraceBeginLenTidPid(&spall_ctx, &spall_buffer, name, sizeof(name) - 1, my_thread_id, 0, get_time_in_micros()), SpallTraceEndTidPid(&spall_ctx, &spall_buffer, my_thread_id, 0, get_time_in_micros()))
#else // PROFILING
void inline init_profiling(const char *filename) { (void)filename; }
// you can pass anything to id as long as it's different from other threads
void inline init_profiling_mythread(uint32_t id) { (void)id; }
void inline end_profiling() {}
void inline end_profiling_mythread() {}
#define PROFILE_SCOPE(name)
#endif

@ -1,4 +1,3 @@
vcvars
git push
call build_release.bat
call update_server.bat

@ -1,5 +1,5 @@
#include "chipmunk.h" // initializing bodies
#include "sokol_time.h"
#include <chipmunk.h> // initializing bodies
#include "types.h"
#include <enet/enet.h>
#include <errno.h>
@ -20,55 +20,15 @@
for (ENetPeer *cur = host->peers; cur < host->peers + host->peerCount; cur++) \
if (cur->state == ENET_PEER_STATE_CONNECTED)
#ifdef PROFILING
#define SPALL_IMPLEMENTATION
#pragma warning(disable : 4996) // spall uses fopen
#include "spall.h"
#define WIN32_LEAN_AND_MEAN
#define VC_EXTRALEAN
#define NOMINMAX
#include <Windows.h>
// This is slow, if you can use RDTSC and set the multiplier in SpallInit, you'll have far better timing accuracy
double get_time_in_micros()
{
static double invfreq;
if (!invfreq)
{
LARGE_INTEGER frequency;
QueryPerformanceFrequency(&frequency);
invfreq = 1000000.0 / frequency.QuadPart;
}
LARGE_INTEGER counter;
QueryPerformanceCounter(&counter);
return counter.QuadPart * invfreq;
}
static SpallProfile spall_ctx;
static SpallBuffer spall_buffer;
#define PROFILE_SCOPE(name) DeferLoop(SpallTraceBeginLenTidPid(&spall_ctx, &spall_buffer, name, sizeof(name) - 1, 0, 0, get_time_in_micros()), SpallTraceEndTidPid(&spall_ctx, &spall_buffer, 0, 0, get_time_in_micros()))
#else // PROFILING
#define PROFILE_SCOPE(name)
#endif
#include "profiling.h"
// started in a thread from host
void server(void *info_raw)
{
init_profiling_mythread(1);
ServerThreadInfo *info = (ServerThreadInfo *)info_raw;
const char *world_save_name = info->world_save;
#ifdef PROFILING
#define BUFFER_SIZE (1 * 1024 * 1024)
spall_ctx = SpallInit("server.spall", 1);
unsigned char *buffer = malloc(BUFFER_SIZE);
spall_buffer = (SpallBuffer){
.length = BUFFER_SIZE,
.data = buffer,
};
SpallBufferInit(&spall_ctx, &spall_buffer);
#endif
@ -79,8 +39,9 @@ void server(void *info_raw)
gs.server_side_computing = true;
Log("Allocated %zu bytes for entities\n", entities_size);
create_initial_world(&gs);
// inputs
Queue player_input_queues[MAX_PLAYERS] = {0};
size_t input_queue_data_size = QUEUE_SIZE_FOR_ELEMENTS(sizeof(InputFrame), INPUT_QUEUE_MAX);
@ -95,9 +56,9 @@ void server(void *info_raw)
OpusEncoder *player_encoders[MAX_PLAYERS] = {0};
OpusDecoder *player_decoders[MAX_PLAYERS] = {0};
#ifdef DEBUG_WORLD
#ifdef DEBUG_WORLD
world_save_name = NULL;
#endif
#endif
if (world_save_name != NULL)
{
size_t read_game_data_buffer_size = entities_size;
@ -141,9 +102,9 @@ void server(void *info_raw)
{
Entity *grid = new_entity(&gs);
grid_create(&gs, grid);
entity_set_pos(grid, (V2){-BOX_SIZE * 2, 0.0f});
entity_set_pos(grid, (cpVect){-BOX_SIZE * 2, 0.0});
Entity *box = new_entity(&gs);
box_create(&gs, box, grid, (V2){0});
box_create(&gs, box, grid, (cpVect){0});
}
// rotation test
@ -151,16 +112,16 @@ void server(void *info_raw)
{
Entity *grid = new_entity(&gs);
grid_create(&gs, grid);
entity_set_pos(grid, (V2){-BOX_SIZE * 2, 0.0f});
entity_set_rotation(grid, PI / 1.7f);
entity_set_pos(grid, (cpVect){-BOX_SIZE * 2, 0.0});
entity_set_rotation(grid, PI / 1.7);
cpBodySetVelocity(grid->body, cpv(-0.1, 0.0));
cpBodySetAngularVelocity(grid->body, 1.0f);
cpBodySetAngularVelocity(grid->body, 1.0);
BOX_AT(grid, ((V2){0}));
BOX_AT(grid, ((V2){BOX_SIZE, 0}));
BOX_AT(grid, ((V2){2.0 * BOX_SIZE, 0}));
BOX_AT(grid, ((V2){2.0 * BOX_SIZE, BOX_SIZE}));
BOX_AT(grid, ((V2){0.0 * BOX_SIZE, -BOX_SIZE}));
BOX_AT(grid, ((cpVect){0}));
BOX_AT(grid, ((cpVect){BOX_SIZE, 0}));
BOX_AT(grid, ((cpVect){2.0 * BOX_SIZE, 0}));
BOX_AT(grid, ((cpVect){2.0 * BOX_SIZE, BOX_SIZE}));
BOX_AT(grid, ((cpVect){0.0 * BOX_SIZE, -BOX_SIZE}));
}
if (enet_initialize() != 0)
@ -171,7 +132,7 @@ void server(void *info_raw)
ENetAddress address;
ENetHost *enet_host;
int sethost = enet_address_set_host_ip(&address, LOCAL_SERVER_ADDRESS);
int sethost = enet_address_set_host_ip(&address, "0.0.0.0");
if (sethost != 0)
{
Log("Fishy return value from set host: %d\n", sethost);
@ -196,336 +157,339 @@ void server(void *info_raw)
uint64_t last_saved_world_time = stm_now();
uint64_t last_sent_audio_time = stm_now();
uint64_t last_sent_gamestate_time = stm_now();
float audio_time_to_send = 0.0f;
float total_time = 0.0f;
double audio_time_to_send = 0.0;
double total_time = 0.0;
unsigned char *world_save_buffer = malloc(entities_size);
while (true)
PROFILE_SCOPE("Serving")
{
ma_mutex_lock(&info->info_mutex);
if (info->should_quit)
{
ma_mutex_unlock(&info->info_mutex);
break;
}
ma_mutex_unlock(&info->info_mutex);
// @Speed handle enet messages and simulate gamestate in parallel, then sync... must clone gamestate for this
while (true)
{
int ret = enet_host_service(enet_host, &event, 0);
if (ret == 0)
break;
if (ret < 0)
ma_mutex_lock(&info->info_mutex);
if (info->should_quit)
{
fprintf(stderr, "Enet host service error %d\n", ret);
ma_mutex_unlock(&info->info_mutex);
break;
}
if (ret > 0)
ma_mutex_unlock(&info->info_mutex);
// @Speed handle enet messages and simulate gamestate in parallel, then sync... must clone gamestate for this
while (true)
{
switch (event.type)
int ret = enet_host_service(enet_host, &event, 0);
if (ret == 0)
break;
if (ret < 0)
{
case ENET_EVENT_TYPE_CONNECT:
fprintf(stderr, "Enet host service error %d\n", ret);
}
if (ret > 0)
{
Log("A new client connected from %x:%u.\n",
event.peer->address.host,
event.peer->address.port);
int64_t player_slot = -1;
for (int i = 0; i < MAX_PLAYERS; i++)
switch (event.type)
{
if (!gs.players[i].connected)
case ENET_EVENT_TYPE_CONNECT:
{
Log("A new client connected from %x:%u.\n",
event.peer->address.host,
event.peer->address.port);
int64_t player_slot = -1;
for (int i = 0; i < MAX_PLAYERS; i++)
{
player_slot = i;
break;
if (!gs.players[i].connected)
{
player_slot = i;
break;
}
}
}
if (player_slot == -1)
{
enet_peer_disconnect_now(event.peer, 69);
}
else
{
event.peer->data = (void *)player_slot;
gs.players[player_slot] = (struct Player){0};
gs.players[player_slot].connected = true;
create_player(&gs.players[player_slot]);
int error;
player_encoders[player_slot] = opus_encoder_create(VOIP_SAMPLE_RATE, 1, OPUS_APPLICATION_VOIP, &error);
if (error != OPUS_OK)
Log("Failed to create encoder: %d\n", error);
player_decoders[player_slot] = opus_decoder_create(VOIP_SAMPLE_RATE, 1, &error);
if (error != OPUS_OK)
Log("Failed to create decoder: %d\n", error);
if (player_slot == -1)
{
enet_peer_disconnect_now(event.peer, 69);
}
else
{
event.peer->data = (void *)player_slot;
gs.players[player_slot] = (struct Player){0};
gs.players[player_slot].connected = true;
create_player(&gs.players[player_slot]);
int error;
player_encoders[player_slot] = opus_encoder_create(VOIP_SAMPLE_RATE, 1, OPUS_APPLICATION_VOIP, &error);
if (error != OPUS_OK)
Log("Failed to create encoder: %d\n", error);
player_decoders[player_slot] = opus_decoder_create(VOIP_SAMPLE_RATE, 1, &error);
if (error != OPUS_OK)
Log("Failed to create decoder: %d\n", error);
}
}
}
break;
break;
case ENET_EVENT_TYPE_RECEIVE:
{
// Log("A packet of length %zu was received on channel %u.\n",
// event.packet->dataLength,
// event.channelID);
if (event.packet->dataLength == 0)
case ENET_EVENT_TYPE_RECEIVE:
{
Log("Wtf an empty packet from enet?\n");
}
else
{
int64_t player_slot = (int64_t)event.peer->data;
size_t length = event.packet->dataLength;
// Log("A packet of length %zu was received on channel %u.\n",
// event.packet->dataLength,
// event.channelID);
if (event.packet->dataLength == 0)
{
Log("Wtf an empty packet from enet?\n");
}
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}; \
queue_init(&queue_name, sizeof(OpusPacket), queue_data_name, QUEUE_SIZE_FOR_ELEMENTS(sizeof(OpusPacket), VOIP_PACKET_BUFFER_SIZE))
VOIP_QUEUE_DECL(throwaway_buffer, throwaway_buffer_data);
Queue *buffer_to_fill = &player_voip_buffers[player_slot];
if (get_entity(&gs, gs.players[player_slot].entity) == NULL)
buffer_to_fill = &throwaway_buffer;
VOIP_QUEUE_DECL(throwaway_buffer, throwaway_buffer_data);
Queue *buffer_to_fill = &player_voip_buffers[player_slot];
if (get_entity(&gs, gs.players[player_slot].entity) == NULL)
buffer_to_fill = &throwaway_buffer;
queue_clear(&player_input_queues[player_slot]);
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);
queue_clear(&player_input_queues[player_slot]);
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);
int return_value = lzo1x_decompress_safe(event.packet->data, event.packet->dataLength, decompressed, &decompressed_max_len, NULL);
int return_value = lzo1x_decompress_safe(event.packet->data, event.packet->dataLength, decompressed, &decompressed_max_len, NULL);
if (return_value == LZO_E_OK)
{
if (!client_to_server_deserialize(&gs, &received, decompressed, decompressed_max_len))
if (return_value == LZO_E_OK)
{
if (!client_to_server_deserialize(&gs, &received, decompressed, decompressed_max_len))
{
Log("Bad packet from client %d\n", (int)player_slot);
}
}
else
{
Log("Bad packet from client %d\n", (int)player_slot);
Log("Couldn't decompress player packet, error code %d from lzo\n", return_value);
}
}
else
/* Clean up the packet now that we're done using it. */
enet_packet_destroy(event.packet);
}
break;
case ENET_EVENT_TYPE_DISCONNECT:
{
int player_index = (int)(int64_t)event.peer->data;
Log("%" PRId64 " disconnected player index %d.\n", (int64_t)event.peer->data, player_index);
Entity *player_body = get_entity(&gs, gs.players[player_index].entity);
if (player_body != NULL)
{
Log("Couldn't decompress player packet, error code %d from lzo\n", return_value);
entity_destroy(&gs, player_body);
}
opus_encoder_destroy(player_encoders[player_index]);
player_encoders[player_index] = NULL;
opus_decoder_destroy(player_decoders[player_index]);
player_decoders[player_index] = NULL;
gs.players[player_index].connected = false;
queue_clear(&player_voip_buffers[player_index]);
event.peer->data = NULL;
}
/* Clean up the packet now that we're done using it. */
enet_packet_destroy(event.packet);
}
break;
break;
case ENET_EVENT_TYPE_DISCONNECT:
{
int player_index = (int)(int64_t)event.peer->data;
Log("%" PRId64 " disconnected player index %d.\n", (int64_t)event.peer->data, player_index);
Entity *player_body = get_entity(&gs, gs.players[player_index].entity);
if (player_body != NULL)
case ENET_EVENT_TYPE_NONE:
{
entity_destroy(&gs, player_body);
}
opus_encoder_destroy(player_encoders[player_index]);
player_encoders[player_index] = NULL;
opus_decoder_destroy(player_decoders[player_index]);
player_decoders[player_index] = NULL;
gs.players[player_index].connected = false;
queue_clear(&player_voip_buffers[player_index]);
event.peer->data = NULL;
}
break;
case ENET_EVENT_TYPE_NONE:
{
}
break;
break;
}
}
}
}
total_time += (float)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 float max_time = 5.0f * TIMESTEP;
if (total_time > max_time)
{
Log("Abnormally large total time %f, clamping\n", total_time);
total_time = max_time;
}
total_time += (float)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);
total_time = max_time;
}
PROFILE_SCOPE("World Processing")
{
while (total_time > TIMESTEP)
{
CONNECTED_PEERS(enet_host, cur)
PROFILE_SCOPE("World Processing")
{
int this_player_index = (int)(int64_t)cur->data;
QUEUE_ITER(&player_input_queues[this_player_index], cur_header)
CONNECTED_PEERS(enet_host, cur)
{
InputFrame *cur = (InputFrame *)cur_header->data;
if (cur->tick == tick(&gs))
int this_player_index = (int)(int64_t)cur->data;
QUEUE_ITER(&player_input_queues[this_player_index], cur_header)
{
gs.players[this_player_index].input = *cur;
break;
InputFrame *cur = (InputFrame *)cur_header->data;
if (cur->tick == tick(&gs))
{
gs.players[this_player_index].input = *cur;
break;
}
}
}
}
process(&gs, TIMESTEP);
total_time -= TIMESTEP;
process(&gs, TIMESTEP);
total_time -= TIMESTEP;
}
}
}
if (world_save_name != NULL && (stm_sec(stm_diff(stm_now(), last_saved_world_time))) > TIME_BETWEEN_WORLD_SAVE)
{
PROFILE_SCOPE("Save World")
if (world_save_name != NULL && (stm_sec(stm_diff(stm_now(), last_saved_world_time))) > TIME_BETWEEN_WORLD_SAVE)
{
last_saved_world_time = stm_now();
ServerToClient msg = (ServerToClient){
.cur_gs = &gs,
};
size_t out_len = 0;
if (server_to_client_serialize(&msg, world_save_buffer, &out_len, entities_size, NULL, true))
PROFILE_SCOPE("Save World")
{
FILE *save_file = NULL;
fopen_s(&save_file, (const char *)world_save_name, "wb");
if (save_file == NULL)
{
Log("Could not open save file: errno %d\n", errno);
}
else
last_saved_world_time = stm_now();
ServerToClient msg = (ServerToClient){
.cur_gs = &gs,
};
size_t out_len = 0;
if (server_to_client_serialize(&msg, world_save_buffer, &out_len, entities_size, NULL, true))
{
size_t data_written = fwrite(world_save_buffer, sizeof(*world_save_buffer), out_len, save_file);
if (data_written != out_len)
FILE *save_file = NULL;
fopen_s(&save_file, (const char *)world_save_name, "wb");
if (save_file == NULL)
{
Log("Failed to save world data, wanted to write %zu but could only write %zu\n", out_len, data_written);
Log("Could not open save file: errno %d\n", errno);
}
else
{
Log("Saved game world to %s\n", (const char *)world_save_name);
size_t data_written = fwrite(world_save_buffer, sizeof(*world_save_buffer), out_len, save_file);
if (data_written != out_len)
{
Log("Failed to save world data, wanted to write %zu but could only write %zu\n", out_len, data_written);
}
else
{
Log("Saved game world to %s\n", (const char *)world_save_name);
}
fclose(save_file);
}
fclose(save_file);
}
}
else
{
Log("URGENT: FAILED TO SAVE WORLD FILE!\n");
else
{
Log("URGENT: FAILED TO SAVE WORLD FILE!\n");
}
}
}
}
if (stm_sec(stm_diff(stm_now(), last_sent_gamestate_time)) > TIME_BETWEEN_SEND_GAMESTATE)
{
last_sent_gamestate_time = stm_now();
PROFILE_SCOPE("send_data")
if (stm_sec(stm_diff(stm_now(), last_sent_gamestate_time)) > TIME_BETWEEN_SEND_GAMESTATE)
{
static char lzo_working_mem[LZO1X_1_MEM_COMPRESS] = {0};
last_sent_gamestate_time = stm_now();
PROFILE_SCOPE("send_data")
{
static char lzo_working_mem[LZO1X_1_MEM_COMPRESS] = {0};
audio_time_to_send += (float)stm_sec(stm_diff(stm_now(), last_sent_audio_time));
last_sent_audio_time = stm_now();
int num_audio_packets = (int)floor(1.0f / (VOIP_TIME_PER_PACKET / audio_time_to_send));
audio_time_to_send += (float)stm_sec(stm_diff(stm_now(), last_sent_audio_time));
last_sent_audio_time = stm_now();
int num_audio_packets = (int)floor(1.0 / (VOIP_TIME_PER_PACKET / audio_time_to_send));
#define MAX_AUDIO_PACKETS_TO_SEND 12
if (num_audio_packets > MAX_AUDIO_PACKETS_TO_SEND)
{
Log("Wants %d, this is too many packets. Greater than the maximum %d\n", num_audio_packets, MAX_AUDIO_PACKETS_TO_SEND);
num_audio_packets = MAX_AUDIO_PACKETS_TO_SEND;
}
if (num_audio_packets > MAX_AUDIO_PACKETS_TO_SEND)
{
Log("Wants %d, this is too many packets. Greater than the maximum %d\n", num_audio_packets, MAX_AUDIO_PACKETS_TO_SEND);
num_audio_packets = MAX_AUDIO_PACKETS_TO_SEND;
}
opus_int16 decoded_audio_packets[MAX_PLAYERS][MAX_AUDIO_PACKETS_TO_SEND][VOIP_EXPECTED_FRAME_COUNT] = {0};
opus_int16 decoded_audio_packets[MAX_PLAYERS][MAX_AUDIO_PACKETS_TO_SEND][VOIP_EXPECTED_FRAME_COUNT] = {0};
audio_time_to_send -= num_audio_packets * VOIP_TIME_PER_PACKET;
audio_time_to_send -= num_audio_packets * VOIP_TIME_PER_PACKET;
// decode what everybody said
CONNECTED_PEERS(enet_host, cur)
{
int this_player_index = (int)(int64_t)cur->data;
for (int packet_i = 0; packet_i < num_audio_packets; packet_i++)
// decode what everybody said
CONNECTED_PEERS(enet_host, cur)
{
opus_int16 *to_dump_to = decoded_audio_packets[this_player_index][packet_i];
OpusPacket *cur_packet = (OpusPacket *)queue_pop_element(&player_voip_buffers[this_player_index]);
if (cur_packet == NULL)
opus_decode(player_decoders[this_player_index], NULL, 0, to_dump_to, VOIP_EXPECTED_FRAME_COUNT, 0);
else
opus_decode(player_decoders[this_player_index], cur_packet->data, cur_packet->length, to_dump_to, VOIP_EXPECTED_FRAME_COUNT, 0);
int this_player_index = (int)(int64_t)cur->data;
for (int packet_i = 0; packet_i < num_audio_packets; packet_i++)
{
opus_int16 *to_dump_to = decoded_audio_packets[this_player_index][packet_i];
OpusPacket *cur_packet = (OpusPacket *)queue_pop_element(&player_voip_buffers[this_player_index]);
if (cur_packet == NULL)
opus_decode(player_decoders[this_player_index], NULL, 0, to_dump_to, VOIP_EXPECTED_FRAME_COUNT, 0);
else
opus_decode(player_decoders[this_player_index], cur_packet->data, cur_packet->length, to_dump_to, VOIP_EXPECTED_FRAME_COUNT, 0);
}
}
}
// send gamestate to each player
CONNECTED_PEERS(enet_host, cur)
{
int this_player_index = (int)(int64_t)cur->data;
Entity *this_player_entity = get_entity(&gs, gs.players[this_player_index].entity);
if (this_player_entity == NULL)
continue;
// @Speed don't recreate the packet for every peer, gets expensive copying gamestate over and over again
unsigned char *bytes_buffer = malloc(sizeof *bytes_buffer * MAX_SERVER_TO_CLIENT);
unsigned char *compressed_buffer = malloc(sizeof *compressed_buffer * MAX_SERVER_TO_CLIENT);
// mix audio to be sent
VOIP_QUEUE_DECL(buffer_to_play, buffer_to_play_data);
// send gamestate to each player
CONNECTED_PEERS(enet_host, cur)
{
for (int packet_i = 0; packet_i < num_audio_packets; packet_i++)
int this_player_index = (int)(int64_t)cur->data;
Entity *this_player_entity = get_entity(&gs, gs.players[this_player_index].entity);
if (this_player_entity == NULL)
continue;
// @Speed don't recreate the packet for every peer, gets expensive copying gamestate over and over again
unsigned char *bytes_buffer = malloc(sizeof *bytes_buffer * MAX_SERVER_TO_CLIENT);
unsigned char *compressed_buffer = malloc(sizeof *compressed_buffer * MAX_SERVER_TO_CLIENT);
// mix audio to be sent
VOIP_QUEUE_DECL(buffer_to_play, buffer_to_play_data);
{
opus_int16 to_send_to_cur[VOIP_EXPECTED_FRAME_COUNT] = {0}; // mix what other players said into this buffer
CONNECTED_PEERS(enet_host, other_player)
for (int packet_i = 0; packet_i < num_audio_packets; packet_i++)
{
if (other_player != cur)
opus_int16 to_send_to_cur[VOIP_EXPECTED_FRAME_COUNT] = {0}; // mix what other players said into this buffer
CONNECTED_PEERS(enet_host, other_player)
{
int other_player_index = (int)(int64_t)other_player->data;
Entity *other_player_entity = get_entity(&gs, gs.players[other_player_index].entity);
if (other_player_entity != NULL)
if (other_player != cur)
{
float dist = V2dist(entity_pos(this_player_entity), entity_pos(other_player_entity));
float volume = lerp(1.0f, 0.0f, clamp01(dist / VOIP_DISTANCE_WHEN_CANT_HEAR));
if (volume > 0.01f)
int other_player_index = (int)(int64_t)other_player->data;
Entity *other_player_entity = get_entity(&gs, gs.players[other_player_index].entity);
if (other_player_entity != NULL)
{
for (int frame_i = 0; frame_i < VOIP_EXPECTED_FRAME_COUNT; frame_i++)
double dist = cpvdist(entity_pos(this_player_entity), entity_pos(other_player_entity));
double volume = lerp(1.0, 0.0, clamp01(dist / VOIP_DISTANCE_WHEN_CANT_HEAR));
if (volume > 0.01)
{
to_send_to_cur[frame_i] += (opus_int16)((float)decoded_audio_packets[other_player_index][packet_i][frame_i] * volume);
for (int frame_i = 0; frame_i < VOIP_EXPECTED_FRAME_COUNT; frame_i++)
{
to_send_to_cur[frame_i] += (opus_int16)((float)decoded_audio_packets[other_player_index][packet_i][frame_i] * volume);
}
}
}
}
}
}
OpusPacket *this_packet = (OpusPacket *)queue_push_element(&buffer_to_play);
opus_int32 ret = opus_encode(player_encoders[this_player_index], to_send_to_cur, VOIP_EXPECTED_FRAME_COUNT, this_packet->data, VOIP_PACKET_MAX_SIZE);
if (ret < 0)
{
Log("Failed to encode audio packet for player %d: opus error code %d\n", this_player_index, ret);
}
else
{
this_packet->length = ret;
OpusPacket *this_packet = (OpusPacket *)queue_push_element(&buffer_to_play);
opus_int32 ret = opus_encode(player_encoders[this_player_index], to_send_to_cur, VOIP_EXPECTED_FRAME_COUNT, this_packet->data, VOIP_PACKET_MAX_SIZE);
if (ret < 0)
{
Log("Failed to encode audio packet for player %d: opus error code %d\n", this_player_index, ret);
}
else
{
this_packet->length = ret;
}
}
}
}
ServerToClient to_send = (ServerToClient){
.cur_gs = &gs,
.your_player = this_player_index,
.audio_playback_buffer = &buffer_to_play,
};
size_t len = 0;
if (server_to_client_serialize(&to_send, bytes_buffer, &len, MAX_SERVER_TO_CLIENT, this_player_entity, false))
{
if (len > MAX_SERVER_TO_CLIENT - 8)
ServerToClient to_send = (ServerToClient){
.cur_gs = &gs,
.your_player = this_player_index,
.audio_playback_buffer = &buffer_to_play,
};
size_t len = 0;
if (server_to_client_serialize(&to_send, bytes_buffer, &len, MAX_SERVER_TO_CLIENT, this_player_entity, false))
{
Log("Too much data quitting!\n");
exit(-1);
}
if (len > MAX_SERVER_TO_CLIENT - 8)
{
Log("Too much data quitting!\n");
exit(-1);
}
size_t compressed_len = 0;
lzo1x_1_compress(bytes_buffer, len, compressed_buffer, &compressed_len, (void *)lzo_working_mem);
size_t compressed_len = 0;
lzo1x_1_compress(bytes_buffer, len, compressed_buffer, &compressed_len, (void *)lzo_working_mem);
#ifdef LOG_GAMESTATE_SIZE
Log("Size of gamestate packet before comrpession: %zu | After: %zu\n", len, compressed_len);
Log("Size of gamestate packet before comrpession: %zu | After: %zu\n", len, compressed_len);
#endif
ENetPacket *gamestate_packet = enet_packet_create((void *)compressed_buffer, compressed_len, ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT);
int err = enet_peer_send(cur, 0, gamestate_packet);
if (err < 0)
ENetPacket *gamestate_packet = enet_packet_create((void *)compressed_buffer, compressed_len, ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT);
int err = enet_peer_send(cur, 0, gamestate_packet);
if (err < 0)
{
Log("Enet failed to send packet error %d\n", err);
enet_packet_destroy(gamestate_packet);
}
}
else
{
Log("Enet failed to send packet error %d\n", err);
enet_packet_destroy(gamestate_packet);
Log("Failed to serialize data for client %d\n", this_player_index);
}
free(bytes_buffer);
free(compressed_buffer);
}
else
{
Log("Failed to serialize data for client %d\n", this_player_index);
}
free(bytes_buffer);
free(compressed_buffer);
}
}
}
@ -547,10 +511,9 @@ void server(void *info_raw)
enet_host_destroy(enet_host);
enet_deinitialize();
end_profiling_mythread();
printf("Cleanup\n");
#ifdef PROFILING
SpallBufferQuit(&spall_ctx, &spall_buffer);
SpallQuit(&spall_ctx);
#endif
}

Binary file not shown.

@ -14,17 +14,7 @@ WinActivate, flightbuild
If WinActive("flightbuild")
{
Send, {Enter}
Send, remedybg continue-execution && sleep 0.1 && remedybg.exe stop-debugging && msbuild && remedybg.exe start-debugging {Enter}
Send, remedybg continue-execution && timeout 1 && remedybg.exe stop-debugging && msbuild && remedybg.exe start-debugging {Enter}
}
Send, {Blind} ; So it doesn't hold down ctrl after running! WTF
return
^+b::
WinKill, Flight Hosting
WinKill, Flight Not Hosting
WinActivate, flightbuild
If WinActive("flightbuild")
{
Send, {Enter}
Send, remedybg continue-execution && sleep 0.1 && remedybg.exe stop-debugging && msbuild && remedybg.exe start-debugging && sleep 0.2 && x64\Debug\Flight.exe {Enter}
}
return

@ -1,15 +1,30 @@
#pragma once
#include "ipsettings.h"
#include "buildsettings.h"
#define MAX_BOX_TYPES 64
#define ZOOM_MIN 0.25f
#define ZOOM_MAX 1500.0f
#define MAX_PLAYERS 16
#define MAX_SUNS 8
#define MAX_ENTITIES 1024 * 25
#define BOX_SIZE 0.25f
#define PLAYER_SIZE ((V2){.x = BOX_SIZE, .y = BOX_SIZE})
#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 1.5f
#define PLAYER_JETPACK_FORCE 2.0f
#define PLAYER_JETPACK_TORQUE 0.05f
#define MISSILE_RANGE 4.0f
#define MISSILE_BURN_TIME 1.5f
#define MISSILE_ARM_TIME 0.5f
#define MISSILE_BURN_FORCE 4.0f
#define MISSILE_MASS 1.0f
// how many missiles grown per second
#define MISSILE_DAMAGE_THRESHOLD 0.2f
#define MISSILE_CHARGE_RATE 0.5f
// 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
@ -22,47 +37,47 @@
#define BUILD_BOX_SNAP_DIST_TO_SHIP 0.2f
#define BOX_MASS 1.0f
#define COLLISION_DAMAGE_SCALING 0.15f
#define THRUSTER_FORCE 12.0f
#define THRUSTER_FORCE 24.0f
#define THRUSTER_ENERGY_USED_PER_SECOND 0.005f
#define GYROSCOPE_ENERGY_USED_PER_SECOND 0.005f
#define GYROSCOPE_TORQUE 0.5f
#define CLOAKING_ENERGY_USE 0.1f
#define CLOAKING_PANEL_SIZE BOX_SIZE*3.0f
#define CLOAKING_PANEL_SIZE BOX_SIZE * 3.0f
#define VISION_RADIUS 12.0f
#define MAX_SERVER_TO_CLIENT 1024 * 512 // maximum size of serialized gamestate buffer
#define MAX_CLIENT_TO_SERVER 1024 * 10 // maximum size of serialized inputs and mic data
#define SUN_RADIUS 10.0f
#define SUN_NO_MORE_ELECTRICITY_OR_GRAVITY 200.0f
#define INSTANT_DEATH_DISTANCE_FROM_SUN 2000.0f
#define SUN_POS ((V2){50.0f, 0.0f})
#ifdef NO_GRAVITY
#define SUN_GRAVITY_STRENGTH 0.0f
#else
#define SUN_GRAVITY_STRENGTH (11.0e2f)
#endif
#define GRAVITY_CONSTANT 0.1f
#define GRAVITY_SMALLEST 0.01f // used to determine when gravity is clamped to 0.0f
#define INSTANT_DEATH_DISTANCE_FROM_CENTER 4000.0f
#define SOLAR_ENERGY_PER_SECOND 0.09f
#define DAMAGE_TO_PLAYER_PER_BLOCK 0.1f
#define BATTERY_CAPACITY 1.5f
#define PLAYER_ENERGY_RECHARGE_PER_SECOND 0.2f
#define PLAYER_ENERGY_RECHARGE_PER_SECOND 0.2
#define EXPLOSION_TIME 0.5f
#define EXPLOSION_PUSH_STRENGTH 5.0f
#define EXPLOSION_DAMAGE_PER_SEC 10.0f
#define EXPLOSION_RADIUS 1.0f
#define EXPLOSION_DAMAGE_THRESHOLD 0.2f // how much damage until it explodes
#define GOLD_UNLOCK_RADIUS 1.0f
#define TIME_BETWEEN_WORLD_SAVE 30.0f
#define MISSILE_EXPLOSION_PUSH 2.5f
#define MISSILE_EXPLOSION_RADIUS 0.4f
#define BOMB_EXPLOSION_PUSH 5.0f
#define BOMB_EXPLOSION_RADIUS 1.0f
// VOIP
#define VOIP_PACKET_BUFFER_SIZE 15 // audio. Must be bigger than 2
#define VOIP_EXPECTED_FRAME_COUNT 480
#define VOIP_SAMPLE_RATE 48000
#define VOIP_EXPECTED_FRAME_COUNT 240
#define VOIP_SAMPLE_RATE (48000 / 2)
// in seconds
#define VOIP_TIME_PER_PACKET (1.0f / ((float)((float)VOIP_SAMPLE_RATE / VOIP_EXPECTED_FRAME_COUNT)))
#define VOIP_TIME_PER_PACKET (1.0f / ((float)((float)VOIP_SAMPLE_RATE / VOIP_EXPECTED_FRAME_COUNT)))
#define VOIP_PACKET_MAX_SIZE 4000
#define VOIP_DISTANCE_WHEN_CANT_HEAR (VISION_RADIUS * 0.8f)
// multiplayer
#define MAX_REPREDICTION_TIME (TIMESTEP * 50.0f)
#define TICKS_BEHIND_DO_SNAP 6 // when this many ticks behind, instead of dilating time SNAP to the healthy ticks ahead
#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
@ -80,36 +95,23 @@
#define THREADLOCAL __thread
#endif
// must make this header and set the target address, just #define SERVER_ADDRESS "127.0.0.1"
#include "ipsettings.h" // don't leak IP!
#define ARRLEN(x) (sizeof(x) / sizeof((x)[0]))
#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 <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
// including headers from headers bad
#ifndef SOKOL_GP_INCLUDED
void sgp_set_color(float, float, float, float);
// @Robust use double precision for all vectors, when passed back to sokol
// somehow automatically or easily cast to floats
typedef struct sgp_vec2
{
float x, y;
} sgp_vec2;
typedef sgp_vec2 sgp_point;
#endif
#ifndef CHIPMUNK_H
typedef void cpSpace;
@ -132,9 +134,6 @@ typedef int opus_int32;
#endif
typedef sgp_vec2 V2;
typedef sgp_point P2;
#define Log(...) \
{ \
fprintf(stdout, "%s:%d | ", __FILE__, __LINE__); \
@ -143,7 +142,7 @@ typedef sgp_point P2;
enum BoxType
{
BoxInvalid, // zero initialized box is invalid!
BoxInvalid, // zero initialized box is invalid!
BoxHullpiece,
BoxThruster,
BoxBattery,
@ -154,6 +153,8 @@ enum BoxType
BoxScanner,
BoxGyroscope,
BoxCloaking,
BoxMissileLauncher,
BoxMerge,
BoxLast,
};
@ -183,7 +184,7 @@ typedef struct EntityID
unsigned int index; // index into the entity arena
} EntityID;
static bool entityids_same(EntityID a, EntityID b)
static inline bool entityids_same(EntityID a, EntityID b)
{
return (a.generation == b.generation) && (a.index == b.index);
}
@ -193,8 +194,8 @@ static bool entityids_same(EntityID a, EntityID b)
typedef struct InputFrame
{
uint64_t tick;
V2 movement;
float rotation;
cpVect movement;
double rotation;
int take_over_squad; // -1 means not taking over any squad
bool accept_cur_squad_invite;
@ -202,7 +203,7 @@ typedef struct InputFrame
EntityID invite_this_player; // null means inviting nobody! @Robust make it so just sends interact pos input, and server processes who to invite. This depends on client side prediction + proper input processing at the right tick.
bool seat_action;
V2 hand_pos; // local to player transationally but not rotationally
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;
@ -218,79 +219,95 @@ typedef struct Entity
bool no_save_to_disk; // stuff generated later on, like player's bodies or space stations that respawn.
float damage; // used by box and player
double damage; // used by box and player
cpBody *body; // used by grid, player, and box
cpShape *shape; // must be a box so shape_size can be set appropriately, and serialized
// players and boxes can be cloaked
// If this is within 2 timesteps of the current game time, the entity is invisible.
// If this is within 2 timesteps of the current game time, the entity is invisible.
double time_was_last_cloaked;
enum Squad last_cloaked_by_squad;
// for serializing the shape
// @Robust remove shape_parent_entity from this struct, use the shape's body to figure out
// what the shape's parent entity is
EntityID shape_parent_entity; // can't be zero if shape is nonzero
V2 shape_size;
cpVect shape_size;
// player
bool is_player;
enum Squad presenting_squad; // also controls what the player can see, because of cloaking!
enum Squad owning_squad; // also controls what the player can see, because of cloaking!
EntityID currently_inside_of_box;
enum Squad squad_invited_to; // if squad none, then no squad invite
float goldness; // how much the player is a winner
double goldness; // how much the player is a winner
// explosion
bool is_explosion;
V2 explosion_pos;
V2 explosion_vel;
float explosion_progresss; // in seconds
cpVect explosion_pos;
cpVect explosion_vel;
double explosion_progress; // in seconds
double explosion_push_strength;
double explosion_radius;
// sun
bool is_sun;
cpVect sun_vel;
cpVect sun_pos;
double sun_mass;
double sun_radius;
// missile
bool is_missile;
double time_burned_for; // until MISSILE_BURN_TIME. Before MISSILE_ARM_TIME cannot explode
// grids
bool is_grid;
float total_energy_capacity;
double total_energy_capacity;
EntityID boxes;
// boxes
bool is_box;
enum Squad owning_squad; // which squad owns this box
enum BoxType box_type;
bool is_platonic; // can't be destroyed, unaffected by physical forces
bool is_platonic; // can't be destroyed, unaffected by physical forces
bool always_visible; // always serialized to the player. @Robust check if not used
EntityID next_box; // for the grid!
EntityID prev_box; // doubly linked so can remove in middle of chain
EntityID next_box; // for the grid!
EntityID prev_box; // doubly linked so can remove in middle of chain
enum CompassRotation compass_rotation;
bool indestructible;
// merger
bool wants_disconnect; // don't serialized, termporary value not used across frames
// missile launcher
double missile_construction_charge;
// used by medbay and cockpit
EntityID player_who_is_inside_of_me;
EntityID player_who_is_inside_of_me;
// only serialized when box_type is thruster or gyroscope, used for both. Thrust
// can mean rotation thrust!
float wanted_thrust; // the thrust command applied to the thruster
float thrust; // the actual thrust it can provide based on energy sources in the grid
double wanted_thrust; // the thrust command applied to the thruster
double thrust; // the actual thrust it can provide based on energy sources in the grid
// only serialized when box_type is battery
float energy_used; // battery, between 0 battery capacity. You have to look through code to figure out what that is! haha sucker!
double energy_used; // battery, between 0 battery capacity. You have to look through code to figure out what that is! haha sucker!
// only serialized when box_type is solar panel
float sun_amount; // solar panel, between 0 and 1
double sun_amount; // solar panel, between 0 and 1
// cloaking only
float cloaking_power; // 0.0 if unable to be used because no power, 1.0 if fully cloaking!
double cloaking_power; // 0.0 if unable to be used because no power, 1.0 if fully cloaking!
// scanner only stuff!
EntityID currently_scanning;
float currently_scanning_progress; // when 1.0, scans it!
double currently_scanning_progress; // when 1.0, scans it!
BOX_UNLOCKS_TYPE blueprints_learned; // @Robust make this same type as blueprints
float scanner_head_rotate_speed; // not serialized, cosmetic
float scanner_head_rotate;
V2 platonic_nearest_direction; // normalized
float platonic_detection_strength; // from zero to one
double scanner_head_rotate_speed; // not serialized, cosmetic
double scanner_head_rotate;
cpVect platonic_nearest_direction; // normalized
double platonic_detection_strength; // from zero to one
} Entity;
typedef struct Player
{
bool connected;
@ -300,21 +317,35 @@ typedef struct Player
EntityID last_used_medbay;
InputFrame input;
} Player;
// use i.sun to access the current sun's pointer
typedef struct SunIter
{
int i;
Entity *sun;
} SunIter;
#define SUNS_ITER(gs_ptr) \
for (SunIter i = {0}; i.i < MAX_SUNS; i.i++) \
if ((i.sun = get_entity(gs_ptr, (gs_ptr)->suns[i.i])) != NULL)
// gotta update the serialization functions when this changes
typedef struct GameState
{
cpSpace *space;
// @Robust for the integer tick, also store a float for how much time has been processed.
// Like a whole timestep then a float for subtimestep
double time; // @Robust separate tick integer not prone to precision issues. Could be very large as is saved to disk!
// @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;
V2 goldpos;
cpVect goldpos;
Player players[MAX_PLAYERS];
V2 platonic_positions[MAX_BOX_TYPES]; // don't want to search over every entity to get the nearest platonic box!
EntityID suns[MAX_SUNS]; // can't have holes in it for serialization
cpVect platonic_positions[MAX_BOX_TYPES]; // don't want to search over every entity to get the nearest platonic box!
bool server_side_computing; // some things only the server should know and calculate, like platonic locations
// Entity arena
@ -335,7 +366,7 @@ typedef struct GameState
#define TAU (PI * 2.0f)
// returns in radians
static float rotangle(enum CompassRotation rot)
static inline double rotangle(enum CompassRotation rot)
{
switch (rot)
{
@ -390,51 +421,62 @@ void create_initial_world(GameState *gs);
void initialize(struct GameState *gs, void *entity_arena, size_t entity_arena_size);
void destroy(struct GameState *gs);
void process_fixed_timestep(GameState *gs);
void process(struct GameState *gs, float dt); // does in place
Entity *closest_box_to_point_in_radius(struct GameState *gs, V2 point, float radius, bool(*filter_func)(Entity*));
// if is subframe, doesn't always increment the tick. When enough
// subframe time has been processed, increments the tick
void process(struct GameState *gs, double dt); // does in place
Entity *closest_box_to_point_in_radius(struct GameState *gs, cpVect point, double radius, bool (*filter_func)(Entity *));
uint64_t tick(struct GameState *gs);
double elapsed_time(GameState *gs);
double sun_dist_no_gravity(Entity *sun);
// 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);
bool client_to_server_deserialize(GameState *gs, struct ClientToServer *msg, unsigned char*bytes, size_t max_len);
bool client_to_server_serialize(GameState *gs, struct ClientToServer *msg, unsigned char*bytes, size_t *out_len, size_t max_len);
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);
bool client_to_server_deserialize(GameState *gs, struct ClientToServer *msg, unsigned char *bytes, size_t max_len);
bool client_to_server_serialize(GameState *gs, struct ClientToServer *msg, unsigned char *bytes, size_t *out_len, size_t max_len);
// entities
bool is_burning(Entity *missile);
Entity *get_entity(struct GameState *gs, EntityID id);
Entity *new_entity(struct GameState *gs);
EntityID get_id(struct GameState *gs, Entity *e);
V2 entity_pos(Entity *e);
void entity_set_rotation(Entity *e, float rot);
void entity_set_pos(Entity *e, V2 pos);
float entity_rotation(Entity *e);
void entity_ensure_in_orbit(Entity *e);
cpVect entity_pos(Entity *e);
void entity_set_rotation(Entity *e, double rot);
void entity_set_pos(Entity *e, cpVect pos);
double entity_rotation(Entity *e);
void entity_ensure_in_orbit(GameState *gs, Entity *e);
void entity_destroy(GameState *gs, Entity *e);
#define BOX_CHAIN_ITER(gs, cur, starting_box) for (Entity *cur = get_entity(gs, starting_box); cur != NULL; cur = get_entity(gs, cur->next_box))
#define BOXES_ITER(gs, cur, grid_entity_ptr) BOX_CHAIN_ITER(gs, cur, (grid_entity_ptr)->boxes)
typedef struct LauncherTarget
{
bool target_found;
double facing_angle; // in global coords
} LauncherTarget;
LauncherTarget missile_launcher_target(GameState *gs, Entity *launcher);
// grid
void grid_create(struct GameState *gs, Entity *e);
void box_create(struct GameState *gs, Entity *new_box, Entity *grid, V2 pos);
void box_create(struct GameState *gs, Entity *new_box, Entity *grid, cpVect pos);
Entity *box_grid(Entity *box);
V2 grid_com(Entity *grid);
V2 grid_vel(Entity *grid);
V2 box_vel(Entity *box);
V2 grid_local_to_world(Entity *grid, V2 local);
V2 grid_world_to_local(Entity *grid, V2 world);
V2 grid_snapped_box_pos(Entity *grid, V2 world); // returns the snapped pos in world coords
float entity_angular_velocity(Entity *grid);
V2 entity_shape_pos(Entity *box);
float box_rotation(Entity *box);
cpVect grid_com(Entity *grid);
cpVect grid_vel(Entity *grid);
cpVect box_vel(Entity *box);
cpVect grid_local_to_world(Entity *grid, cpVect local);
cpVect grid_world_to_local(Entity *grid, cpVect world);
cpVect grid_snapped_box_pos(Entity *grid, cpVect world); // returns the snapped pos in world coords
double entity_angular_velocity(Entity *grid);
cpVect entity_shape_pos(Entity *box);
double box_rotation(Entity *box);
// thruster
V2 box_facing_vector(Entity *box);
V2 thruster_force(Entity *box);
cpVect box_facing_vector(Entity *box);
cpVect thruster_force(Entity *box);
// debug draw
void dbg_drawall();
void dbg_line(V2 from, V2 to);
void dbg_rect(V2 center);
void dbg_line(cpVect from, cpVect to);
void dbg_rect(cpVect center);
typedef struct ServerThreadInfo
{
@ -446,10 +488,10 @@ typedef struct ServerThreadInfo
typedef struct AABB
{
float x, y, width, height;
double x, y, width, height;
} AABB;
static AABB centered_at(V2 point, V2 size)
static inline AABB centered_at(cpVect point, cpVect size)
{
return (AABB){
.x = point.x - size.x / 2.0f,
@ -459,101 +501,46 @@ static AABB centered_at(V2 point, V2 size)
};
}
static bool has_point(AABB aabb, V2 point)
static inline bool has_point(AABB aabb, cpVect point)
{
return point.x > aabb.x && point.x < aabb.x + aabb.width && point.y > aabb.y && point.y < aabb.y + aabb.height;
}
static V2 V2add(V2 a, V2 b)
static inline double cpvprojectval(cpVect vec, cpVect onto)
{
return (V2){
.x = a.x + b.x,
.y = a.y + b.y,
};
double length_onto = cpvlength(onto);
return cpvdot(vec, onto) / (length_onto * length_onto);
}
static V2 V2scale(V2 a, float f)
// spins around by theta
static inline cpVect cpvspin(cpVect vec, double theta)
{
return (V2){
.x = a.x * f,
.y = a.y * f,
};
}
static float V2lengthsqr(V2 v)
{
return v.x * v.x + v.y * v.y;
}
static float V2length(V2 v)
{
return sqrtf(V2lengthsqr(v));
}
static V2 V2normalize(V2 v)
{
return V2scale(v, 1.0f / V2length(v));
}
static float V2dot(V2 a, V2 b)
{
return a.x * b.x + a.y * b.y;
}
static float V2projectvalue(V2 vec, V2 onto)
{
float length_onto = V2length(onto);
return V2dot(vec, onto) / (length_onto * length_onto);
}
static V2 V2project(V2 vec, V2 onto)
{
return V2scale(onto, V2projectvalue(vec, onto));
}
static V2 V2rotate(V2 vec, float theta)
{
return (V2){
.x = vec.x * cosf(theta) - vec.y * sinf(theta),
.y = vec.x * sinf(theta) + vec.y * cosf(theta),
return (cpVect){
.x = vec.x * cos(theta) - vec.y * sin(theta),
.y = vec.x * sin(theta) + vec.y * cos(theta),
};
}
// also known as atan2
static float V2angle(V2 vec)
static inline double cpvangle(cpVect vec)
{
return atan2f(vec.y, vec.x);
return atan2(vec.y, vec.x);
}
static V2 V2sub(V2 a, V2 b)
static double sign(double f)
{
return (V2){
.x = a.x - b.x,
.y = a.y - b.y,
};
}
static bool V2equal(V2 a, V2 b, float eps)
{
return V2length(V2sub(a, b)) < eps;
}
static inline float clamp01(float f)
{
return fmaxf(0.0f, fminf(f, 1.0f));
if (f >= 0.0f)
return 1.0f;
else
return -1.0f;
}
static float V2distsqr(V2 from, V2 to)
static inline double clamp01(double f)
{
return V2lengthsqr(V2sub(to, from));
return fmax(0.0f, fmin(f, 1.0f));
}
static float V2dist(V2 from, V2 to)
{
return sqrtf(V2distsqr(from, to));
}
static inline float clamp(float f, float minimum, float maximum)
static inline double clamp(double f, double minimum, double maximum)
{
if (f < minimum)
return minimum;
@ -562,77 +549,43 @@ static inline float clamp(float f, float minimum, float maximum)
return f;
}
static float fract(float f)
{
return f - floorf(f);
}
static float lerp(float a, float b, float f)
static inline double cpvanglediff(cpVect a, cpVect b)
{
return a * (1.0f - f) + (b * f);
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);
return acos(acos_input) * sign(cpvdot(a, b));
}
static float lerp_angle(float p_from, float p_to, float p_weight)
static inline double fract(double f)
{
float difference = fmodf(p_to - p_from, (float)TAU);
float distance = fmodf(2.0f * difference, (float)TAU) - difference;
return p_from + distance * p_weight;
return f - floor(f);
}
static V2 V2floor(V2 p)
static inline double lerp(double a, double b, double f)
{
return (V2){floorf(p.x), floorf(p.y)};
return a * (1.0f - f) + (b * f);
}
static V2 V2fract(V2 p)
static inline double lerp_angle(double p_from, double p_to, double p_weight)
{
return (V2){fract(p.x), fract(p.y)};
}
/*
float noise(V2 p)
{
V2 id = V2floor(p);
V2 f = V2fract(p);
V2 u = V2dot(f, f) * (3.0f - 2.0f * f);
return mix(mix(random(id + V2(0.0, 0.0)),
random(id + V2(1.0, 0.0)), u.x),
mix(random(id + V2(0.0, 1.0)),
random(id + V2(1.0, 1.0)), u.x),
u.y);
double difference = fmod(p_to - p_from, (float)TAU);
double distance = fmod(2.0f * difference, (float)TAU) - difference;
return p_from + distance * p_weight;
}
float fbm(V2 p)
static inline cpVect cpvfloor(cpVect p)
{
float f = 0.0;
float gat = 0.0;
for (float octave = 0.; octave < 5.; ++octave)
{
float la = pow(2.0, octave);
float ga = pow(0.5, octave + 1.);
f += ga * noise(la * p);
gat += ga;
}
f = f / gat;
return f;
return (cpVect){floor(p.x), floor(p.y)};
}
*/
static V2 V2lerp(V2 a, V2 b, float factor)
static inline cpVect cpvfract(cpVect p)
{
V2 to_return = {0};
to_return.x = lerp(a.x, b.x, factor);
to_return.y = lerp(a.y, b.y, factor);
return to_return;
return (cpVect){fract(p.x), fract(p.y)};
}
// for random generation
static float hash11(float p)
static inline double hash11(double p)
{
p = fract(p * .1031f);
p *= p + 33.33f;
@ -640,50 +593,7 @@ static float hash11(float p)
return fract(p);
}
typedef struct Color
{
float r, g, b, a;
} Color;
static Color colhex(int r, int g, int b)
{
return (Color){
.r = (float)r / 255.0f,
.g = (float)g / 255.0f,
.b = (float)b / 255.0f,
.a = 1.0f,
};
}
static Color colhexcode(int hexcode)
static inline double deg2rad(double deg)
{
// 0x020509;
int r = (hexcode >> 16) & 0xFF;
int g = (hexcode >> 8) & 0xFF;
int b = (hexcode >> 0) & 0xFF;
return colhex(r, g, b);
return (deg / 360.0f) * 2.0f * PI;
}
static Color Collerp(Color a, Color b, float factor)
{
Color to_return = {0};
to_return.r = lerp(a.r, b.r, factor);
to_return.g = lerp(a.g, b.g, factor);
to_return.b = lerp(a.b, b.b, factor);
to_return.a = lerp(a.a, b.a, factor);
return to_return;
}
static void set_color(Color c)
{
sgp_set_color(c.r, c.g, c.b, c.a);
}
#define WHITE \
(Color) { .r = 1.0f, .g = 1.0f, .b = 1.0f, .a = 1.0f }
#define RED \
(Color) { .r = 1.0f, .g = 0.0f, .b = 0.0f, .a = 1.0f }
#define BLUE \
(Color) { .r = 0.0f, .g = 0.0f, .b = 1.0f, .a = 1.0f }
#define GOLD colhex(255, 215, 0)

Loading…
Cancel
Save