Compare commits

...

21 Commits

Author SHA1 Message Date
Cameron Murphy Reikes f780a14406 Basic particles 1 year ago
Cameron Murphy Reikes 8ac5b43a59 More whitebox tomfoolery 1 year ago
Cameron Murphy Reikes 4da8973998 Fix stuff 1 year ago
Cameron Murphy Reikes 60cb8661aa Define min and max 1 year ago
Cameron Murphy Reikes d190594b38 Lightning towards platonic, fix BAD qsort bugs 1 year ago
Cameron Murphy Reikes 291235e992 Initial build elf objects for whitebox 1 year ago
Cameron Murphy Reikes 9275927cc7 Add recorded inputs integration testt 1 year ago
Cameron Murphy Reikes cb661bd98f Serialization refactor, FIX BAD RADAR BUG 1 year ago
Cameron Murphy Reikes 1fa9e497d1 LIhgnting release 1 year ago
Cameron Murphy Reikes d570a7db21 Check for errors on windows release scripts 1 year ago
root eb2bf3b996 Exit linux scripts on failure 1 year ago
Cameron Murphy Reikes b537b38bb0 Basic arg parsing library 1 year ago
Cameron Murphy Reikes 170de8f748 Lightning cosmetic on unlearned blueprint in radar 1 year ago
Cameron Murphy Reikes 8727b0f3f6 Tune some stuff, radar more zoomed in 1 year ago
Cameron Murphy Reikes a7b3a6c0a3 UI refactor, more intelligent right click prompt 1 year ago
Cameron Murphy Reikes 5565c75423 Correctly log assertion failures 1 year ago
Cameron Murphy Reikes b4603a8a2c Fix crash 1 year ago
Cameron Murphy Reikes 78ce158cd8 Don't crash on weird bug that's hard to reproduce 1 year ago
Cameron Murphy Reikes 86fa62ce1d Release with radar 1 year ago
Cameron Murphy Reikes dc05af2834 Tune 1 year ago
Cameron Murphy Reikes 2feb571c1d Add radar 1 year ago

3
.gitignore vendored

@ -1,3 +1,5 @@
# elf objects
elf_objects/
# profiling results
*.spall
# logs
@ -8,6 +10,7 @@ enc_temp_folder/
*.spall # profiling
*.bin # world files
debug_world.bin
no-suns.bin
flight*.zip
flight_server
flight.zip

@ -0,0 +1,4 @@
@Cosmetic - places where cosmetics could be added
@Robust - things to do to increase robustness of program
@BeforeShip - important things to handle before seriously shipping
no checkin - without the space between no and checkin, should never appear in commits, use a pre commit hook for this

@ -0,0 +1,4 @@
@REM for whitebox
call shadergen.bat
set compileopts=/Zi /DDEBUG /DLL /OUT:flight_dll /LD
call build_msvc.bat

@ -0,0 +1,30 @@
@echo off
@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=%~dp0thirdparty\opus\win32\VS2015\x64\Release\opus.lib
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
set MUNKSRC=
for %%x in (*.c) do set MUNKSRC=!MUNKSRC! thirdparty\Chipmunk2D\src\%%x
popd
@REM /DENET_DEBUG=1^
clang -target x86_64-pc-windows-elf -c^
-I"thirdparty" -I"thirdparty\minilzo" -I"thirdparty\enet\include" -I"thirdparty\Chipmunk2D\include\chipmunk" -I"thirdparty\Chipmunk2D\include" -I"thirdparty\opus\include" -I"thirdparty\opus\src"^
%MUNKSRC%
rmdir /S /Q elf_objects
mkdir elf_objects
move *.o elf_objects
@REM main.c gamestate.c server.c debugdraw.c^
@REM thirdparty\minilzo\minilzo.c^
@REM %OPUSLIB%
@REM thirdparty\enet\callbacks.c thirdparty\enet\compress.c thirdparty\enet\host.c thirdparty\enet\list.c thirdparty\enet\packet.c thirdparty\enet\peer.c thirdparty\enet\protocol.c thirdparty\enet\win32.c Ws2_32.lib winmm.lib^

@ -1,9 +1,9 @@
#!/usr/bin/env bash
mkdir thirdparty/opus/build
cd thirdparty/opus/build
cmake ..
cmake --build .
cd -
mkdir -p thirdparty/opus/build || exit 1
cd thirdparty/opus/build || exit 1
cmake .. || exit 1
cmake --build . || exit 1
cd - || exit 1
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
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 || exit 1

@ -6,7 +6,7 @@
#define SERVER_PORT 2551
// must be unsigned integer
#define GIT_RELEASE_TAG 25
#define GIT_RELEASE_TAG 29
#ifdef DEBUG
@ -16,17 +16,17 @@
// #define PROFILING
// Intensive profiling means profiling a lot of little tiny stuff. Not always enabled because tanks performance
#define INTENSIVE_PROFILING
// #define INTENSIVE_PROFILING
// #define DEBUG_RENDERING
#define DEBUG_WORLD
#define UNLOCK_ALL
// #define DEBUG_WORLD
// #define UNLOCK_ALL
#define TIME_BETWEEN_WORLD_SAVE 1000000.0f
// #define TIME_BETWEEN_WORLD_SAVE 1.0f
// #define INFINITE_RESOURCES
#define DEBUG_TOOLS
#define CHIPMUNK_INTEGRITY_CHECK
// #define FAT_THRUSTERS
// #define NO_GRAVITY
#define NO_GRAVITY
// #define NO_SUNS
#else

Binary file not shown.

@ -88,9 +88,8 @@ void __flight_assert(bool cond, const char *file, int line, const char *cond_str
{
#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);
Log("%s", message_buffer);
if (log_file != NULL)
{
fprintf(log_file, "%s", message_buffer);
@ -288,7 +287,7 @@ typedef struct QueryResult
cpVect pointA;
cpVect pointB;
} QueryResult;
static THREADLOCAL char query_result_data[QUEUE_SIZE_FOR_ELEMENTS(sizeof(QueryResult), 128)] = {0};
static THREADLOCAL char query_result_data[QUEUE_SIZE_FOR_ELEMENTS(sizeof(QueryResult), 256)] = {0};
// the data starts off NULL, on the first call sets it to result data
static THREADLOCAL Queue query_result = {0};
@ -348,6 +347,24 @@ static void raycast_query_callback(cpShape *shape, cpVect point, cpVect normal,
}
}
static THREADLOCAL cpVect from_point = {0};
static int sort_bodies_callback(const void *a, const void *b)
{
cpVect a_pos = cpBodyGetPosition(* (cpBody **)a);
cpVect b_pos = cpBodyGetPosition(* (cpBody **)b);
double a_dist = cpvdist(a_pos, from_point);
double b_dist = cpvdist(b_pos, from_point);
if (a_dist - b_dist < 0.0)
{
return -1;
}
else
{
return 1;
}
// return (int)(a_dist - b_dist);
}
LauncherTarget missile_launcher_target(GameState *gs, Entity *launcher)
{
double to_face = 0.0;
@ -514,7 +531,8 @@ cpVect player_vel(GameState *gs, Entity *player)
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");
flight_assert(potential_seat->is_box);
return (cpBodyGetVelocity(player->body));
// flight_assert(potential_seat->is_box);
}
else
{
@ -678,6 +696,29 @@ void box_create(GameState *gs, Entity *new_box, Entity *grid, cpVect pos, enum B
box_add_to_boxes(gs, grid, new_box);
}
int platonic_detection_compare(const void *a, const void *b)
{
PlatonicDetection *a_detection = (PlatonicDetection *)a;
PlatonicDetection *b_detection = (PlatonicDetection *)b;
double a_intensity = a_detection->intensity;
double b_intensity = b_detection->intensity;
if(a_detection->intensity == 0.0) a_intensity = INFINITY;
if(b_detection->intensity == 0.0) b_intensity = INFINITY;
double result = (a_intensity - b_intensity);
if (result < 0.0)
{
return -1;
}
else if (result > 0.0)
{
return 1;
}
else
{
return 0;
}
}
cpVect box_compass_vector(Entity *box)
{
@ -716,6 +757,68 @@ void fill_time_string(char *to_fill, size_t max_length)
// to_fill[filled_length - 4] = '\0'; // remove the newline
}
Entity *grid_box_at_local_pos(GameState *gs, Entity *grid, cpVect wanted_local_pos)
{
Entity *box_in_direction = NULL;
BOXES_ITER(gs, cur, grid)
{
if (cpvnear(entity_shape_pos(cur), wanted_local_pos, 0.01))
{
box_in_direction = cur;
break;
}
}
if (box_in_direction != NULL)
flight_assert(box_in_direction->is_box);
if (box_in_direction != NULL)
flight_assert(box_grid(box_in_direction) == grid);
return box_in_direction;
}
bool merge_box_is_merged(GameState *gs, Entity *merge_box)
{
flight_assert(merge_box->is_box);
flight_assert(merge_box->box_type == BoxMerge);
cpVect facing = box_compass_vector(merge_box);
Entity *potentially_merged_with = grid_box_at_local_pos(gs, box_grid(merge_box), cpvadd(entity_shape_pos(merge_box), cpvmult(facing, BOX_SIZE)));
if (potentially_merged_with != NULL && cpvnear(box_compass_vector(potentially_merged_with), cpvmult(facing, -1.0), 0.01))
{
return true;
}
else
{
return false;
}
}
bool could_learn_from_scanner(Player *for_player, Entity *box)
{
flight_assert(box->is_box);
flight_assert(box->box_type == BoxScanner);
return (for_player->box_unlocks | box->blueprints_learned) != for_player->box_unlocks;
}
bool box_interactible(GameState *gs, Player *for_player, Entity *box)
{
flight_assert(box->is_box);
if (box->box_type == BoxCockpit || box->box_type == BoxMedbay)
{
return true;
}
else
{
if (box->box_type == BoxMerge)
{
return merge_box_is_merged(gs, box);
}
else
{
return false;
}
}
}
// removes boxes from grid, then ensures that the rule that grids must not have
// holes in them is applied.
static void grid_correct_for_holes(GameState *gs, struct Entity *grid)
@ -795,20 +898,13 @@ static void grid_correct_for_holes(GameState *gs, struct Entity *grid)
// @Robust @Speed faster method, not O(N^2), of getting the box
// in the direction currently needed
cpVect compass_vect = box_compass_vector(N);
if (N->box_type == BoxMerge && N->wants_disconnect && cpvnear(compass_vect, dir, 0.01))
if (N->box_type == BoxMerge && N->wants_disconnect && cpvnear(compass_vect, dir, 0.01) && merge_box_is_merged(gs, N))
{
}
else
{
cpVect wanted_local_pos = cpvadd(cur_local_pos, cpvmult(dir, BOX_SIZE));
BOXES_ITER(gs, cur, grid)
{
if (cpvnear(entity_shape_pos(cur), wanted_local_pos, 0.01))
{
box_in_direction = get_id(gs, cur);
break;
}
}
box_in_direction = get_id(gs, grid_box_at_local_pos(gs, grid, wanted_local_pos));
}
Entity *newbox = get_entity(gs, box_in_direction);
@ -1081,28 +1177,6 @@ void update_from(cpBody *body, struct BodyData *data)
cpBodySetAngularVelocity(body, data->angular_velocity);
}
typedef struct SerState
{
unsigned char *bytes;
bool serializing;
size_t cursor; // points to next available byte, is the size of current message after serializing something
size_t max_size;
Entity *for_player;
size_t max_entity_index; // for error checking
bool write_varnames;
bool save_or_load_from_disk;
// output
uint32_t version;
uint32_t git_release_tag;
} SerState;
typedef struct SerMaybeFailure
{
bool failed;
int line;
const char *expression;
} SerMaybeFailure;
const static SerMaybeFailure ser_ok = {0};
#define SER_ASSERT(cond) \
if (!(cond)) \
@ -1227,6 +1301,7 @@ SerMaybeFailure ser_var(SerState *ser, char *var_pointer, size_t var_size, const
enum GameVersion
{
VInitial,
VNoGold,
VMax, // this minus one will be the version used
};
@ -1352,6 +1427,7 @@ SerMaybeFailure ser_player(SerState *ser, Player *p)
SerMaybeFailure ser_entity(SerState *ser, GameState *gs, Entity *e)
{
SER_VAR(&e->no_save_to_disk);
SER_VAR(&e->always_visible);
SER_VAR(&e->generation);
SER_MAYBE_RETURN(ser_f(ser, &e->damage));
@ -1436,7 +1512,12 @@ SerMaybeFailure ser_entity(SerState *ser, GameState *gs, Entity *e)
SER_MAYBE_RETURN(ser_entityid(ser, &e->currently_inside_of_box));
SER_VAR(&e->squad_invited_to);
SER_MAYBE_RETURN(ser_f(ser, &e->goldness));
if (ser->version < VNoGold)
{
double goldness;
SER_VAR_NAME(&goldness, "&e->goldness");
}
}
SER_VAR(&e->is_explosion);
@ -1484,7 +1565,6 @@ SerMaybeFailure ser_entity(SerState *ser, GameState *gs, Entity *e)
SER_VAR(&e->owning_squad);
SER_VAR(&e->always_visible);
SER_MAYBE_RETURN(ser_entityid(ser, &e->next_box));
SER_MAYBE_RETURN(ser_entityid(ser, &e->prev_box));
SER_VAR(&e->compass_rotation);
@ -1517,8 +1597,15 @@ SerMaybeFailure ser_entity(SerState *ser, GameState *gs, Entity *e)
SER_MAYBE_RETURN(ser_f(ser, &e->currently_scanning_progress));
SER_VAR(&e->blueprints_learned);
SER_MAYBE_RETURN(ser_f(ser, &e->scanner_head_rotate));
SER_MAYBE_RETURN(ser_fV2(ser, &e->platonic_nearest_direction));
SER_MAYBE_RETURN(ser_f(ser, &e->platonic_detection_strength));
for (int i = 0; i < SCANNER_MAX_PLATONICS; i++)
{
SER_MAYBE_RETURN(ser_V2(ser, &e->detected_platonics[i].direction));
SER_MAYBE_RETURN(ser_f(ser, &e->detected_platonics[i].intensity));
}
for (int i = 0; i < SCANNER_MAX_POINTS; i++)
{
SER_VAR(&e->scanner_points[i]);
}
break;
case BoxCloaking:
SER_MAYBE_RETURN(ser_f(ser, &e->cloaking_power));
@ -1774,6 +1861,14 @@ SerMaybeFailure ser_server_to_client(SerState *ser, ServerToClient *s)
return ser_ok;
}
// On serialize:
// 1. put struct's data into bytes, for a specific player or not, and to disk or not
// 2. output the number of bytes it took to put the struct's data into bytes
// On deserialize:
// 1. put bytes into a struct for a specific player, from disk or not
// 2. perform operations on those bytes to initialize the struct
// 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)
{
@ -1886,60 +1981,47 @@ SerMaybeFailure ser_client_to_server(SerState *ser, ClientToServer *msg)
}
return ser_ok;
}
size_t ser_size(SerState *ser)
{
flight_assert(ser->cursor > 0); // if this fails, haven't serialized anything yet!
return ser->cursor + 1;
}
bool client_to_server_serialize(GameState *gs, struct ClientToServer *msg, unsigned char *bytes, size_t *out_len, size_t max_len)
SerState init_serializing(GameState *gs, unsigned char *bytes, size_t max_size, Entity *for_player, bool to_disk)
{
SerState ser = (SerState){
bool write_varnames = to_disk;
#ifdef WRITE_VARNAMES
write_varnames = true;
#endif
return (SerState){
.bytes = bytes,
.serializing = true,
.cursor = 0,
.max_size = max_len,
.for_player = NULL,
.max_entity_index = gs->cur_next_entity,
.max_size = max_size,
.for_player = for_player,
.version = VMax - 1,
.save_or_load_from_disk = to_disk,
.write_varnames = write_varnames,
};
#ifdef WRITE_VARNAMES
ser.write_varnames = true;
#endif
SerMaybeFailure result = ser_client_to_server(&ser, msg);
*out_len = ser.cursor + 1; // see other comment for server to client
if (result.failed)
{
Log("Failed to serialize client to server because %s was false, line %d\n", result.expression, result.line);
return false;
}
else
{
return true;
}
}
bool client_to_server_deserialize(GameState *gs, struct ClientToServer *msg, unsigned char *bytes, size_t max_len)
SerState init_deserializing(GameState *gs, unsigned char *bytes, size_t max_size, bool from_disk)
{
SerState servar = (SerState){
bool has_varnames = from_disk;
#ifdef WRITE_VARNAMES
has_varnames = true;
#endif
return (SerState){
.bytes = bytes,
.serializing = false,
.cursor = 0,
.max_size = max_len,
.max_entity_index = gs->cur_next_entity,
.save_or_load_from_disk = false,
.max_size = max_size,
.max_entity_index = gs->max_entities,
.for_player = NULL,
.save_or_load_from_disk = from_disk,
.write_varnames = has_varnames,
};
#ifdef WRITE_VARNAMES
servar.write_varnames = true;
#endif
SerState *ser = &servar;
SerMaybeFailure result = ser_client_to_server(ser, msg);
if (result.failed)
{
Log("Failed to deserialize client to server on line %d because of %s\n", result.line, result.expression);
return false;
}
else
{
return true;
}
}
// filter func null means everything is ok, if it's not null and returns false, that means
@ -2306,9 +2388,9 @@ void create_initial_world(GameState *gs)
{
const double mass_multiplier = 10.0;
EntityID suns[] = {
create_sun(gs, new_entity(gs), ((cpVect){800.0, 0.0}), ((cpVect){0.0, 0.0}), 1000000.0 * mass_multiplier, 30.0),
create_sun(gs, new_entity(gs), ((cpVect){800.0, 100.0}), ((cpVect){60.0, 0.0}), 10000.0 * mass_multiplier, 20.0),
create_sun(gs, new_entity(gs), ((cpVect){800.0, -100.0}), ((cpVect){-60.0, 0.0}), 10000.0 * mass_multiplier, 20.0),
create_sun(gs, new_entity(gs), ((cpVect){500.0, 0.0}), ((cpVect){0.0, 0.0}), 1000000.0 * mass_multiplier, 30.0),
create_sun(gs, new_entity(gs), ((cpVect){500.0, 100.0}), ((cpVect){30.0, 0.0}), 10000.0 * mass_multiplier, 20.0),
create_sun(gs, new_entity(gs), ((cpVect){500.0, -100.0}), ((cpVect){-30.0, 0.0}), 10000.0 * mass_multiplier, 20.0),
create_sun(gs, new_entity(gs), ((cpVect){-7000.0, -50.0}), ((cpVect){0.0, 0.0}), 100000.0 * mass_multiplier, 20.0),
};
@ -2329,25 +2411,51 @@ void create_initial_world(GameState *gs)
{
ORB_AT(cpv(x, 0.0));
}
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){-7000.0, 200.0}, BoxMerge);
#define SUN_POS(index) (entity_pos(get_entity(gs, suns[index])))
create_bomb_station(gs, cpvadd(SUN_POS(0), cpv(0.0, 300.0)), BoxExplosive);
create_bomb_station(gs, cpvadd(SUN_POS(0), cpv(0.0, -300.0)), BoxCloaking);
create_bomb_station(gs, cpvadd(SUN_POS(0), cpv(300.0, 0.0)), BoxMissileLauncher);
create_bomb_station(gs, cpvadd(SUN_POS(3), cpv(0.0, 300.0)), BoxMerge);
#undef SUN_POS
#else
#if 1 // present the stations
Log("Creating debug world\n");
// pos, mass, radius
create_bomb_station(gs, (cpVect){-5.0, 0.0}, BoxExplosive);
create_bomb_station(gs, (cpVect){0.0, 5.0}, BoxGyroscope);
create_hard_shell_station(gs, (cpVect){-5.0, 5.0}, BoxCloaking);
#endif
#if 1 // scanner box
bool indestructible = false;
enum CompassRotation rot = Right;
{
Entity *grid = new_entity(gs);
grid_create(gs, grid);
entity_set_pos(grid, cpv(1.5, 0.0));
BOX_AT_TYPE(grid, cpv(0.0, 0.0), BoxExplosive);
BOX_AT_TYPE(grid, cpv(-BOX_SIZE, 0.0), BoxScanner);
BOX_AT_TYPE(grid, cpv(-BOX_SIZE * 2.0, 0.0), BoxSolarPanel);
BOX_AT_TYPE(grid, cpv(-BOX_SIZE * 3.0, 0.0), BoxSolarPanel);
entity_ensure_in_orbit(gs, grid);
}
double theta = deg2rad(65.0);
{
Entity *grid = new_entity(gs);
grid_create(gs, grid);
entity_set_pos(grid, cpv(0.0, 10.0));
BOX_AT_TYPE(grid, cpv(0.0, 0.0), BoxHullpiece);
entity_ensure_in_orbit(gs, grid);
}
cpVect from = (cpVect){BOX_SIZE * 4.0, -1};
#endif
#if 0 // merge box
bool indestructible = false;
double theta = deg2rad(65.0);
cpVect from = (cpVect){BOX_SIZE * 4.0, -1};
enum CompassRotation rot = Right;
{
Entity *grid = new_entity(gs);
@ -2375,7 +2483,9 @@ void create_initial_world(GameState *gs)
cpBodySetVelocity(grid->body, (cpvspin((cpVect){-0.4, 0.0}, theta)));
entity_ensure_in_orbit(gs, grid);
}
#endif
#endif // grid tests
#endif // debug world
}
void exit_seat(GameState *gs, Entity *seat_in, Entity *p)
@ -2545,14 +2655,14 @@ void process(struct GameState *gs, double dt)
Entity *potential_seat = cp_shape_entity(result);
flight_assert(potential_seat->is_box);
// IMPORTANT: if you update these, make sure you update box_interactible so
// the button prompt still works
if (potential_seat->box_type == BoxScanner) // learn everything from the scanner
{
flight_assert(box_interactible(potential_seat->box_type));
player->box_unlocks |= potential_seat->blueprints_learned;
}
if (potential_seat->box_type == BoxMerge) // disconnect!
{
flight_assert(box_interactible(potential_seat->box_type));
potential_seat->wants_disconnect = true;
grid_correct_for_holes(gs, box_grid(potential_seat));
flight_assert(potential_seat->exists);
@ -2561,7 +2671,6 @@ void process(struct GameState *gs, double dt)
}
if (potential_seat->box_type == BoxCockpit || potential_seat->box_type == BoxMedbay)
{
flight_assert(box_interactible(potential_seat->box_type));
// 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)
{
@ -2583,6 +2692,20 @@ void process(struct GameState *gs, double dt)
}
#endif
// general player logic
{
circle_query(gs->space, entity_pos(p), SCANNER_RADIUS);
QUEUE_ITER(&query_result, QueryResult, res)
{
Entity *maybe_scanner = cp_shape_entity(res->shape);
if (maybe_scanner->box_type == BoxScanner && could_learn_from_scanner(player, maybe_scanner))
{
player->box_unlocks |= maybe_scanner->blueprints_learned;
// @Cosmetic here
}
}
}
// process movement
{
// no cheating by making movement bigger than length 1
@ -3123,38 +3246,100 @@ void process(struct GameState *gs, double dt)
// only the server knows all the positions of all the solids
if (gs->server_side_computing)
{
if (cur_box->energy_effectiveness < 1.0)
{
cur_box->platonic_detection_strength = 0.0;
cur_box->platonic_nearest_direction = (cpVect){0};
}
else
for (int i = 0; i < SCANNER_MAX_POINTS; i++)
cur_box->scanner_points[i] = (struct ScannerPoint){0};
for (int i = 0; i < SCANNER_MAX_PLATONICS; i++)
cur_box->detected_platonics[i] = (PlatonicDetection){0};
if (cur_box->energy_effectiveness >= 1.0)
{
cpVect from_pos = entity_pos(cur_box);
cpVect nearest = {0};
double nearest_dist = INFINITY;
PlatonicDetection detections[MAX_BOX_TYPES] = {0};
for (int i = 0; i < MAX_BOX_TYPES; i++)
{
cpVect cur_pos = gs->platonic_positions[i];
if (cpvlength(cur_pos) > 0.0) // zero is uninitialized, the platonic solid doesn't exist (probably) @Robust do better
{
double length_to_cur = cpvdist(from_pos, cur_pos);
if (length_to_cur < nearest_dist)
{
nearest_dist = length_to_cur;
nearest = cur_pos;
}
cpVect towards = cpvsub(cur_pos, from_pos);
double length_to_cur = cpvlength(towards);
detections[i].direction = cpvnormalize(towards);
detections[i].intensity = length_to_cur; // so it sorts correctly, changed to intensity correctly after sorting
}
}
if (nearest_dist < INFINITY)
qsort(detections, MAX_BOX_TYPES, sizeof(detections[0]), platonic_detection_compare);
for (int i = 0; i < SCANNER_MAX_PLATONICS; i++)
{
cur_box->platonic_nearest_direction = cpvnormalize(cpvsub(nearest, from_pos));
cur_box->platonic_detection_strength = fmax(0.1, 1.0 - fmin(1.0, nearest_dist / 100.0));
cur_box->detected_platonics[i] = detections[i];
if (cur_box->detected_platonics[i].intensity > 0.0)
{
cur_box->detected_platonics[i].intensity = max(0.1, 1.0 - clamp01(cur_box->detected_platonics[i].intensity / 100.0));
}
}
circle_query(gs->space, entity_pos(cur_box), SCANNER_MAX_RANGE);
cpBody *body_results[512] = {0};
size_t cur_results_len = 0;
QUEUE_ITER(&query_result, QueryResult, res)
{
cpBody *cur_body = cpShapeGetBody(res->shape);
bool unique_body = true;
for (int i = 0; i < cur_results_len; i++)
{
if (body_results[i] == cur_body)
{
unique_body = false;
break;
}
}
if (unique_body && cur_results_len < ARRLEN(body_results))
{
body_results[cur_results_len] = cur_body;
cur_results_len++;
}
}
else
from_point = entity_pos(cur_box);
size_t sizeof_element = sizeof(body_results[0]);
qsort(body_results, cur_results_len, sizeof_element, sort_bodies_callback);
size_t bodies_detected = cur_results_len < SCANNER_MAX_POINTS ? cur_results_len : SCANNER_MAX_POINTS;
for (int i = 0; i < bodies_detected; i++)
{
cur_box->platonic_nearest_direction = (cpVect){0};
cur_box->platonic_detection_strength = 0.0;
cpVect rel_vect = cpvsub(cpBodyGetPosition(body_results[i]), from_point);
double vect_length = cpvlength(rel_vect);
if (SCANNER_MIN_RANGE < vect_length && vect_length < SCANNER_MAX_RANGE)
{
cpVect radar_vect = cpvmult(cpvnormalize(rel_vect), clamp01(vect_length / SCANNER_MAX_VIEWPORT_RANGE));
enum ScannerPointKind kind = Platonic;
Entity *body_entity = cp_body_entity(body_results[i]);
if (body_entity->is_grid)
{
kind = Neutral;
BOXES_ITER(gs, cur_potential_platonic, body_entity)
{
if (cur_potential_platonic->is_platonic)
{
kind = Platonic;
break;
}
}
}
else if (body_entity->is_player)
{
kind = Neutral;
}
else
{
kind = Enemy;
}
cpVect into_char_vect = cpvmult(radar_vect, 126.0);
flight_assert(fabs(into_char_vect.x) <= 126.0);
flight_assert(fabs(into_char_vect.y) <= 126.0);
cur_box->scanner_points[i] = (struct ScannerPoint){
.kind = (char)kind,
.x = (char)(into_char_vect.x),
.y = (char)(into_char_vect.y),
};
}
}
}
}
@ -3173,7 +3358,8 @@ void process(struct GameState *gs, double dt)
cur_box->currently_scanning = new_id;
}
double target_head_rotate_speed = cur_box->platonic_detection_strength > 0.0 ? 3.0 : 0.0;
// double target_head_rotate_speed = cur_box->platonic_detection_strength > 0.0 ? 3.0 : 0.0;
double target_head_rotate_speed = 3.0 * cur_box->energy_effectiveness;
if (to_learn != NULL)
{
cur_box->currently_scanning_progress += dt * SCANNER_SCAN_RATE;

@ -0,0 +1,145 @@
@module horizontal_lightning
@vs vs
in vec4 coord;
out vec2 texUV;
void main() {
gl_Position = vec4(coord.xy, 0.0, 1.0);
texUV = coord.zw;
}
@end
@fs fs
uniform uniforms {
float iTime;
float alpha;
};
in vec2 texUV;
out vec4 fragColor;
vec4 permute(vec4 t) {
return t * (t * 34.0 + 133.0);
}
// Gradient set is a normalized expanded rhombic dodecahedron
vec3 grad(float hash) {
// Random vertex of a cube, +/- 1 each
vec3 cube = mod(floor(hash / vec3(1.0, 2.0, 4.0)), 2.0) * 2.0 - 1.0;
// Random edge of the three edges connected to that vertex
// Also a cuboctahedral vertex
// And corresponds to the face of its dual, the rhombic dodecahedron
vec3 cuboct = cube;
int selected_edge =int(hash / 16.0) ;
if(selected_edge == 0)
cuboct.x = 0.0;
if(selected_edge == 1)
cuboct.y = 0.0;
if(selected_edge == 2)
cuboct.z = 0.0;
// In a funky way, pick one of the four points on the rhombic face
float type = mod(floor(hash / 8.0), 2.0);
vec3 rhomb = (1.0 - type) * cube + type * (cuboct + cross(cube, cuboct));
// Expand it so that the new edges are the same length
// as the existing ones
vec3 grad = cuboct * 1.22474487139 + rhomb;
// To make all gradients the same length, we only need to shorten the
// second type of vector. We also put in the whole noise scale constant.
// The compiler should reduce it into the existing floats. I think.
grad *= (1.0 - 0.042942436724648037 * type) * 3.5946317686139184;
return grad;
}
// BCC lattice split up into 2 cube lattices
vec4 bccNoiseDerivativesPart(vec3 X) {
vec3 b = floor(X);
vec4 i4 = vec4(X - b, 2.5);
// Pick between each pair of oppposite corners in the cube.
vec3 v1 = b + floor(dot(i4, vec4(.25)));
vec3 v2 = b + vec3(1, 0, 0) + vec3(-1, 1, 1) * floor(dot(i4, vec4(-.25, .25, .25, .35)));
vec3 v3 = b + vec3(0, 1, 0) + vec3(1, -1, 1) * floor(dot(i4, vec4(.25, -.25, .25, .35)));
vec3 v4 = b + vec3(0, 0, 1) + vec3(1, 1, -1) * floor(dot(i4, vec4(.25, .25, -.25, .35)));
// Gradient hashes for the four vertices in this half-lattice.
vec4 hashes = permute(mod(vec4(v1.x, v2.x, v3.x, v4.x), 289.0));
hashes = permute(mod(hashes + vec4(v1.y, v2.y, v3.y, v4.y), 289.0));
hashes = mod(permute(mod(hashes + vec4(v1.z, v2.z, v3.z, v4.z), 289.0)), 48.0);
// Gradient extrapolations & kernel function
vec3 d1 = X - v1; vec3 d2 = X - v2; vec3 d3 = X - v3; vec3 d4 = X - v4;
vec4 a = max(0.75 - vec4(dot(d1, d1), dot(d2, d2), dot(d3, d3), dot(d4, d4)), 0.0);
vec4 aa = a * a; vec4 aaaa = aa * aa;
vec3 g1 = grad(hashes.x); vec3 g2 = grad(hashes.y);
vec3 g3 = grad(hashes.z); vec3 g4 = grad(hashes.w);
vec4 extrapolations = vec4(dot(d1, g1), dot(d2, g2), dot(d3, g3), dot(d4, g4));
// Derivatives of the noise
vec3 derivative = -8.0 * mat4x3(d1, d2, d3, d4) * (aa * a * extrapolations)
+ mat4x3(g1, g2, g3, g4) * aaaa;
// Return it all as a vec4
return vec4(derivative, dot(aaaa, extrapolations));
}
// Gives X and Y a triangular alignment, and lets Z move up the main diagonal.
// Might be good for terrain, or a time varying X/Y plane. Z repeats.
vec4 bccNoiseDerivatives_XYBeforeZ(vec3 X) {
// Not a skew transform.
mat3 orthonormalMap = mat3(
0.788675134594813, -0.211324865405187, -0.577350269189626,
-0.211324865405187, 0.788675134594813, -0.577350269189626,
0.577350269189626, 0.577350269189626, 0.577350269189626);
X = orthonormalMap * X;
vec4 result = bccNoiseDerivativesPart(X) + bccNoiseDerivativesPart(X + 144.5);
return vec4(result.xyz * orthonormalMap, result.w);
}
void main()
{
vec2 uv = texUV;
vec2 p = uv;
uv = uv * 2. -1.;
float tickle = 0.001*1000*iTime;
vec3 offset = vec3(cos(tickle), sin(tickle), 0.0);
vec3 p3 = vec3(p, 0.0) + offset;
vec3 noise_input = vec3(p3*25.0+12.0);
//float intensity = noise(noise_input);
float intensity = 0.0;
intensity += bccNoiseDerivatives_XYBeforeZ(noise_input).w*0.4;
intensity += bccNoiseDerivatives_XYBeforeZ(noise_input*0.55).w*0.7;
intensity += bccNoiseDerivatives_XYBeforeZ(noise_input*0.44).w*0.8;
float t = clamp((uv.x * -uv.x * 0.16) + 0.15, 0., 1.);
//fragColor.rgb = vec3(t);
float dist = length(uv);
//float y = abs( dist - 0.5 - intensity * 0.1);
float y = abs(uv.y/1.5 - intensity * 0.1);
float g = pow(y, 0.2);
vec3 col = vec3(2.0, 1.8, 0.5);
col = col * -g + col;
col = col * col;
col = col * col;
fragColor.rgb = col;
fragColor.a = ((col.r + col.g + col.b)/3.0) * alpha;
}
@end
@program program vs fs

Binary file not shown.

Binary file not shown.

@ -0,0 +1,4 @@
set EXECUTABLE=x64\Debug\Flight.exe
START /b %EXECUTABLE% host=true replay_inputs_from=input_go_up.bin
%EXECUTABLE% replay_inputs_from=input_go_down.bin

@ -0,0 +1,144 @@
@module lightning
@vs vs
in vec4 coord;
out vec2 texUV;
void main() {
gl_Position = vec4(coord.xy, 0.0, 1.0);
texUV = coord.zw;
}
@end
@fs fs
uniform uniforms {
float iTime;
};
in vec2 texUV;
out vec4 fragColor;
vec4 permute(vec4 t) {
return t * (t * 34.0 + 133.0);
}
// Gradient set is a normalized expanded rhombic dodecahedron
vec3 grad(float hash) {
// Random vertex of a cube, +/- 1 each
vec3 cube = mod(floor(hash / vec3(1.0, 2.0, 4.0)), 2.0) * 2.0 - 1.0;
// Random edge of the three edges connected to that vertex
// Also a cuboctahedral vertex
// And corresponds to the face of its dual, the rhombic dodecahedron
vec3 cuboct = cube;
int selected_edge =int(hash / 16.0) ;
if(selected_edge == 0)
cuboct.x = 0.0;
if(selected_edge == 1)
cuboct.y = 0.0;
if(selected_edge == 2)
cuboct.z = 0.0;
// In a funky way, pick one of the four points on the rhombic face
float type = mod(floor(hash / 8.0), 2.0);
vec3 rhomb = (1.0 - type) * cube + type * (cuboct + cross(cube, cuboct));
// Expand it so that the new edges are the same length
// as the existing ones
vec3 grad = cuboct * 1.22474487139 + rhomb;
// To make all gradients the same length, we only need to shorten the
// second type of vector. We also put in the whole noise scale constant.
// The compiler should reduce it into the existing floats. I think.
grad *= (1.0 - 0.042942436724648037 * type) * 3.5946317686139184;
return grad;
}
// BCC lattice split up into 2 cube lattices
vec4 bccNoiseDerivativesPart(vec3 X) {
vec3 b = floor(X);
vec4 i4 = vec4(X - b, 2.5);
// Pick between each pair of oppposite corners in the cube.
vec3 v1 = b + floor(dot(i4, vec4(.25)));
vec3 v2 = b + vec3(1, 0, 0) + vec3(-1, 1, 1) * floor(dot(i4, vec4(-.25, .25, .25, .35)));
vec3 v3 = b + vec3(0, 1, 0) + vec3(1, -1, 1) * floor(dot(i4, vec4(.25, -.25, .25, .35)));
vec3 v4 = b + vec3(0, 0, 1) + vec3(1, 1, -1) * floor(dot(i4, vec4(.25, .25, -.25, .35)));
// Gradient hashes for the four vertices in this half-lattice.
vec4 hashes = permute(mod(vec4(v1.x, v2.x, v3.x, v4.x), 289.0));
hashes = permute(mod(hashes + vec4(v1.y, v2.y, v3.y, v4.y), 289.0));
hashes = mod(permute(mod(hashes + vec4(v1.z, v2.z, v3.z, v4.z), 289.0)), 48.0);
// Gradient extrapolations & kernel function
vec3 d1 = X - v1; vec3 d2 = X - v2; vec3 d3 = X - v3; vec3 d4 = X - v4;
vec4 a = max(0.75 - vec4(dot(d1, d1), dot(d2, d2), dot(d3, d3), dot(d4, d4)), 0.0);
vec4 aa = a * a; vec4 aaaa = aa * aa;
vec3 g1 = grad(hashes.x); vec3 g2 = grad(hashes.y);
vec3 g3 = grad(hashes.z); vec3 g4 = grad(hashes.w);
vec4 extrapolations = vec4(dot(d1, g1), dot(d2, g2), dot(d3, g3), dot(d4, g4));
// Derivatives of the noise
vec3 derivative = -8.0 * mat4x3(d1, d2, d3, d4) * (aa * a * extrapolations)
+ mat4x3(g1, g2, g3, g4) * aaaa;
// Return it all as a vec4
return vec4(derivative, dot(aaaa, extrapolations));
}
// Gives X and Y a triangular alignment, and lets Z move up the main diagonal.
// Might be good for terrain, or a time varying X/Y plane. Z repeats.
vec4 bccNoiseDerivatives_XYBeforeZ(vec3 X) {
// Not a skew transform.
mat3 orthonormalMap = mat3(
0.788675134594813, -0.211324865405187, -0.577350269189626,
-0.211324865405187, 0.788675134594813, -0.577350269189626,
0.577350269189626, 0.577350269189626, 0.577350269189626);
X = orthonormalMap * X;
vec4 result = bccNoiseDerivativesPart(X) + bccNoiseDerivativesPart(X + 144.5);
return vec4(result.xyz * orthonormalMap, result.w);
}
void main()
{
vec2 uv = texUV;
vec2 p = uv;
uv = uv * 2. -1.;
float tickle = 0.001*1000*iTime;
vec3 offset = vec3(cos(tickle), sin(tickle), 0.0);
vec3 p3 = vec3(p, 0.0) + offset;
vec3 noise_input = vec3(p3*25.0+12.0);
//float intensity = noise(noise_input);
float intensity = 0.0;
intensity += bccNoiseDerivatives_XYBeforeZ(noise_input).w*0.4;
intensity += bccNoiseDerivatives_XYBeforeZ(noise_input*0.55).w*0.7;
intensity += bccNoiseDerivatives_XYBeforeZ(noise_input*0.44).w*0.8;
float t = clamp((uv.x * -uv.x * 0.16) + 0.15, 0., 1.);
//fragColor.rgb = vec3(t);
float dist = length(uv);
float y = abs(dist - 0.5 - intensity * 0.1);
float g = pow(y, 0.2);
vec3 col = vec3(1.50, 1.48, 1.78);
col = col * -g + col;
col = col * col;
col = col * col;
fragColor.rgb = col;
fragColor.a = (col.r + col.g + col.b)/3.0;
}
@end
@program program vs fs

@ -1,8 +1,8 @@
#!/usr/bin/env bash
systemctl stop flight
./build_linux_server_release.sh
cp flight.service /etc/systemd/system/
systemctl enable flight
systemctl start flight
systemctl restart flight
systemctl stop flight || exit 1
./build_linux_server_release.sh || exit 1
cp flight.service /etc/systemd/system/ || exit 1
systemctl enable flight || exit 1
systemctl start flight || exit 1
systemctl restart flight || exit 1

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 B

557
main.c

@ -14,6 +14,7 @@
#define SOKOL_IMPL
#define SOKOL_D3D11
#include "sokol_app.h"
#include "sokol_args.h"
#include "sokol_gfx.h"
#include "sokol_glue.h"
#include "sokol_gp.h"
@ -37,9 +38,13 @@
// shaders
#include "goodpixel.gen.h"
#include "horizontal_lightning.gen.h"
#include "hueshift.gen.h"
#include "lightning.gen.h"
static sg_pipeline hueshift_pipeline;
static sg_pipeline goodpixel_pipeline;
static sg_pipeline lightning_pipeline;
static sg_pipeline horizontal_lightning_pipeline;
static struct GameState gs = {0};
static int my_player_index = -1;
@ -56,15 +61,18 @@ typedef struct KeyPressed
} KeyPressed;
static KeyPressed keypressed[MAX_KEYDOWN] = {0};
static cpVect mouse_pos = {0};
static FILE *record_inputs_to = NULL;
static FILE *replay_inputs_from = NULL;
static bool fullscreened = false;
static bool picking_new_boxtype = false;
static double exec_time = 0.0; // cosmetic bouncing, network stats
static float iTime = 0.0; // fmodded to 1000, shader trick http://the-witness.net/news/2022/02/a-shader-trick/
// for network statistics, printed to logs with F3
static uint64_t total_bytes_sent = 0;
static uint64_t total_bytes_received = 0;
static double dilating_time_factor = 1.0;
static bool build_pressed = false;
static double dilating_time_factor = 1.0;
static double time_to_process = 0.0;
static bool interact_pressed = false;
#define MAX_MOUSEBUTTON (SAPP_MOUSEBUTTON_MIDDLE + 1)
@ -75,6 +83,7 @@ typedef struct MousePressed
uint64_t frame;
} MousePressed;
static MousePressed mousepressed[MAX_MOUSEBUTTON] = {0};
static EntityID maybe_inviting_this_player = {0};
static EntityID hovering_this_player = {0};
bool confirm_invite_this_player = false;
@ -127,6 +136,8 @@ static sg_image image_gyrospin;
static sg_image image_noenergy;
static sg_image image_orb;
static sg_image image_orb_frozen;
static sg_image image_radardot;
static sg_image image_pip;
static enum BoxType toolbar[TOOLBAR_SLOTS] = {
BoxHullpiece,
@ -251,6 +262,42 @@ static struct SquadMeta
},
};
typedef struct Particle
{
bool alive;
cpVect pos;
cpVect vel;
double alive_for;
double scaling;
} Particle;
#define MAX_PARTICLES 1000
static Particle particles[MAX_PARTICLES] = {0};
#define PARTICLES_ITER(p) for (Particle *p = &particles[0]; p < particles + MAX_PARTICLES; p++)
static void new_particle(cpVect pos, cpVect vel)
{
bool created = false;
PARTICLES_ITER(p)
{
if (!p->alive)
{
*p = (Particle){0};
p->pos = pos;
p->vel = vel;
p->alive = true;
p->scaling = 1.0 + hash11(exec_time) * 0.2;
created = true;
break;
}
}
if (!created)
{
Log("TOO MANY PARTICLES");
}
}
struct SquadMeta squad_meta(enum Squad squad)
{
for (int i = 0; i < ARRLEN(squad_metas); i++)
@ -262,6 +309,16 @@ struct SquadMeta squad_meta(enum Squad squad)
return (struct SquadMeta){0};
}
static size_t serialized_inputframe_length()
{
InputFrame frame = {0};
unsigned char serialized[1024 * 5] = {0};
SerState ser = init_serializing(&gs, serialized, ARRLEN(serialized), NULL, false);
ser_inputframe(&ser, &frame);
flight_assert(ser_size(&ser) > 1);
return ser_size(&ser);
}
static enum BoxType currently_building()
{
flight_assert(cur_toolbar_slot >= 0);
@ -494,6 +551,7 @@ static void init(void)
{
fopen_s(&log_file, "astris_log.txt", "a");
Log("Another day, another game of astris! Git release tag %d\n", GIT_RELEASE_TAG);
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,
@ -501,6 +559,49 @@ static void init(void)
queue_init(&input_queue, sizeof(InputFrame), input_queue_data,
ARRLEN(input_queue_data));
// commandline
{
printf(
"Usage: astris.exe [option]=data , the =stuff is required\n"
"host - hosts a server locally if exists in commandline, like `astris.exe host=yes`\n"
"record_inputs_to - records inputs to the file specified\n"
"replay_inputs_from - replays inputs from the file specified\n");
if (sargs_exists("host"))
{
server_thread_handle = (void *)_beginthread(server, 0, (void *)&server_info);
sapp_set_window_title("Flight Hosting");
}
if (sargs_exists("record_inputs_to"))
{
const char *filename = sargs_value("record_inputs_to");
Log("Recording inputs to %s\n", filename);
if (filename == NULL)
{
quit_with_popup("Failed to record inputs, filename not specified", "Failed to record inputs");
}
errno_t error = fopen_s(&record_inputs_to, filename, "wb");
Log("%d\n", error);
if (record_inputs_to == NULL)
{
quit_with_popup("Failed to open file to record inputs into", "Failed to record inputs");
}
}
if (sargs_exists("replay_inputs_from"))
{
const char *filename = sargs_value("replay_inputs_from");
Log("Replaying inputs from %s\n", filename);
if (filename == NULL)
{
quit_with_popup("Failed to replay inputs, filename not specified", "Failed to replay inputs");
}
fopen_s(&replay_inputs_from, filename, "rb");
if (replay_inputs_from == NULL)
{
quit_with_popup("Failed to open file to replay inputs from", "Failed to replay inputs");
}
}
}
// audio
{
// opus
@ -617,6 +718,35 @@ static void init(void)
quit_with_popup("Couldn't make a shader! Uhhh ooooohhhhhh!!!", "Shader error BONED");
}
}
{
sgp_pipeline_desc pip_desc = {
.shader = *lightning_program_shader_desc(sg_query_backend()),
.blend_mode = SGP_BLENDMODE_BLEND,
};
lightning_pipeline = sgp_make_pipeline(&pip_desc);
sg_resource_state errstate = sg_query_pipeline_state(lightning_pipeline);
if (errstate != SG_RESOURCESTATE_VALID)
{
Log("Failed to make lightning pipeline\n");
quit_with_popup("Couldn't make a shader! Uhhh ooooohhhhhh!!!", "Shader error BONED");
}
}
{
sgp_pipeline_desc pip_desc = {
.shader = *horizontal_lightning_program_shader_desc(sg_query_backend()),
.blend_mode = SGP_BLENDMODE_BLEND,
};
horizontal_lightning_pipeline = sgp_make_pipeline(&pip_desc);
sg_resource_state errstate = sg_query_pipeline_state(horizontal_lightning_pipeline);
if (errstate != SG_RESOURCESTATE_VALID)
{
Log("Failed to make horizontal_lightning pipeline\n");
quit_with_popup("Couldn't make a shader! Uhhh ooooohhhhhh!!!", "Shader error BONED");
}
}
}
// images loading
@ -657,6 +787,8 @@ static void init(void)
image_noenergy = load_image("loaded/no_energy.png");
image_orb = load_image("loaded/orb.png");
image_orb_frozen = load_image("loaded/orb_frozen.png");
image_radardot = load_image("loaded/radardot.png");
image_pip = load_image("loaded/pip.png");
}
// socket initialization
@ -733,6 +865,23 @@ static void draw_texture_centered(cpVect center, double size)
draw_texture_rectangle_centered(center, (cpVect){size, size});
}
static void draw_flipped_texture_rectangle_centered(cpVect center, cpVect width_height)
{
transform_scope()
{
scale_at(1.0, -1.0, center.x, center.y);
draw_texture_rectangle_centered(center, width_height);
}
}
static void draw_flipped_texture_centered(cpVect center, double size)
{
transform_scope()
{
scale_at(1.0, -1.0, center.x, center.y);
draw_texture_centered(center, size);
}
}
sgp_point V2point(cpVect v)
{
return (sgp_point){.x = (float)v.x, .y = (float)v.y};
@ -825,14 +974,9 @@ static void ui(bool draw, double dt, double width, double height)
set_color_values(1.0, 1.0, 1.0, alpha);
sgp_set_image(0, image_rothelp);
cpVect draw_at = cpv(width / 2.0, height * 0.25);
transform_scope()
{
scale_at(1.0, -1.0, draw_at.x, draw_at.y);
pipeline_scope(goodpixel_pipeline)
draw_texture_centered(draw_at, 200.0);
sgp_reset_image(0);
}
pipeline_scope(goodpixel_pipeline)
draw_flipped_texture_centered(cpv(width / 2.0, height * 0.25), 200.0);
sgp_reset_image(0);
}
// zooming zoomeasy
@ -840,14 +984,9 @@ static void ui(bool draw, double dt, double width, double height)
double alpha = 1.0 - clamp01(zoomeasy_learned);
set_color_values(1.0, 1.0, 1.0, alpha);
sgp_set_image(0, image_zoomeasyhelp);
cpVect draw_at = cpv(width * 0.1, height * 0.5);
transform_scope()
{
scale_at(1.0, -1.0, draw_at.x, draw_at.y);
pipeline_scope(goodpixel_pipeline)
draw_texture_centered(draw_at, 200.0);
sgp_reset_image(0);
}
pipeline_scope(goodpixel_pipeline)
draw_flipped_texture_centered(cpv(width * 0.1, height * 0.5), 200.0);
sgp_reset_image(0);
}
}
@ -939,13 +1078,9 @@ static void ui(bool draw, double dt, double width, double height)
{
sgp_set_image(0, image_mystery);
}
transform_scope()
{
scale_at(1.0, -1.0, item_x + item_width / 2.0, item_y + item_height / 2.0);
pipeline_scope(goodpixel_pipeline)
draw_textured_rect(item_x, item_y, item_width, item_height);
sgp_reset_image(0);
}
pipeline_scope(goodpixel_pipeline)
draw_flipped_texture_rectangle_centered(cpv(item_x + item_width / 2.0, item_y + item_width / 2.0), cpv(item_width, item_height));
sgp_reset_image(0);
}
cur_column++;
@ -996,45 +1131,32 @@ static void ui(bool draw, double dt, double width, double height)
if (invited)
draw_as_squad = myentity()->squad_invited_to;
transform_scope()
pipeline_scope(hueshift_pipeline)
{
pipeline_scope(hueshift_pipeline)
{
set_color_values(1.0, 1.0, 1.0, 1.0);
sgp_set_image(0, image_squad_invite);
setup_hueshift(draw_as_squad);
scale_at(1.0, -1.0, x,
invite_y); // images upside down by default :(
draw_texture_centered((cpVect){x, invite_y}, size);
sgp_reset_image(0);
}
set_color_values(1.0, 1.0, 1.0, 1.0);
sgp_set_image(0, image_squad_invite);
setup_hueshift(draw_as_squad);
draw_flipped_texture_centered(cpv(x, invite_y), size);
sgp_reset_image(0);
}
// yes
transform_scope()
set_color_values(1.0, 1.0, 1.0, 1.0);
sgp_set_image(0, image_check);
pipeline_scope(goodpixel_pipeline)
{
set_color_values(1.0, 1.0, 1.0, 1.0);
scale_at(1.0, -1.0, yes_x, buttons_y);
sgp_set_image(0, image_check);
pipeline_scope(goodpixel_pipeline)
{
draw_texture_centered((cpVect){yes_x, buttons_y}, yes_size);
}
sgp_reset_image(0);
draw_flipped_texture_centered(cpv(yes_x, buttons_y), yes_size);
}
sgp_reset_image(0);
// no
transform_scope()
set_color_values(1.0, 1.0, 1.0, 1.0);
sgp_set_image(0, image_no);
pipeline_scope(goodpixel_pipeline)
{
set_color_values(1.0, 1.0, 1.0, 1.0);
scale_at(1.0, -1.0, no_x, buttons_y);
sgp_set_image(0, image_no);
pipeline_scope(goodpixel_pipeline)
{
draw_texture_centered((cpVect){no_x, buttons_y}, no_size);
}
sgp_reset_image(0);
draw_flipped_texture_centered(cpv(no_x, buttons_y), no_size);
}
sgp_reset_image(0);
}
}
@ -1048,16 +1170,9 @@ static void ui(bool draw, double dt, double width, double height)
draw_circle(pos, 28.0 + sin(exec_time * 5.0) * 6.5);
sgp_set_image(0, image_rightclick);
cpVect draw_at = cpvadd(pos, cpv(0, 50.0));
transform_scope()
pipeline_scope(goodpixel_pipeline)
{
pipeline_scope(goodpixel_pipeline)
{
scale_at(1.0, -1.0, draw_at.x, draw_at.y);
draw_texture_centered(draw_at, 100.0 + sin(exec_time * 5.0) * 10.0);
}
draw_flipped_texture_centered(cpvadd(pos, cpv(0, 50.0)), 100.0 + sin(exec_time * 5.0) * 10.0);
}
sgp_reset_image(0);
@ -1082,28 +1197,24 @@ static void ui(bool draw, double dt, double width, double height)
confirm_invite_this_player = true;
}
if (draw)
transform_scope()
{
const double size = 64.0;
if (selecting_to_invite)
{
set_color_values(0.5, 0.5, 0.5, 0.4);
draw_filled_rect(pos.x - size / 2.0, pos.y - size / 2.0, size,
size);
set_color_values(1.0, 1.0, 1.0, 1.0);
}
pipeline_scope(hueshift_pipeline)
{
setup_hueshift(myplayer()->squad);
{
const double size = 64.0;
scale_at(1.0, -1.0, pos.x,
pos.y); // images upside down by default :(
sgp_set_image(0, image_squad_invite);
draw_texture_centered(pos, size);
sgp_reset_image(0);
}
if (selecting_to_invite)
{
set_color_values(0.5, 0.5, 0.5, 0.4);
draw_filled_rect(pos.x - size / 2.0, pos.y - size / 2.0, size,
size);
set_color_values(1.0, 1.0, 1.0, 1.0);
}
pipeline_scope(hueshift_pipeline)
{
setup_hueshift(myplayer()->squad);
sgp_set_image(0, image_squad_invite);
draw_flipped_texture_centered(pos, size);
sgp_reset_image(0);
}
}
}
}
@ -1222,9 +1333,7 @@ static void ui(bool draw, double dt, double width, double height)
setup_hueshift(this_squad);
rotate_at(flag_rot[i], flag_pos[i].x, flag_pos[i].y);
scale_at(1.0, -1.0, flag_pos[i].x,
flag_pos[i].y); // images upside down by default :(
draw_texture_centered(flag_pos[i], size);
draw_flipped_texture_centered(flag_pos[i], size);
sgp_reset_image(0);
}
@ -1264,9 +1373,7 @@ static void ui(bool draw, double dt, double width, double height)
sgp_set_image(0, image_mic_unmuted);
transform_scope()
{
scale_at(1.0, -1.0, button.x + button.width / 2.0,
button.y + button.height / 2.0);
draw_textured_rect(button.x, button.y, button.width, button.height);
draw_flipped_texture_rectangle_centered(cpv(button.x + button.width / 2.0, button.y + button.height / 2.0), cpv(button.width, button.height));
sgp_reset_image(0);
}
}
@ -1346,8 +1453,6 @@ static void ui(bool draw, double dt, double width, double height)
{
double item_x = x + item_offset_x;
double item_y = y + item_offset_y;
scale_at(1.0, -1.0, item_x + item_width / 2.0,
item_y + item_height / 2.0);
pipeline_scope(goodpixel_pipeline)
{
@ -1358,7 +1463,7 @@ static void ui(bool draw, double dt, double width, double height)
sgp_set_image(0, info.image);
else
sgp_set_image(0, image_mystery);
draw_textured_rect(item_x, item_y, item_width, item_height);
draw_flipped_texture_rectangle_centered(cpv(item_x + item_width / 2.0, item_y + item_height / 2.0), cpv(item_width, item_height));
sgp_reset_image(0);
}
@ -1369,7 +1474,7 @@ static void ui(bool draw, double dt, double width, double height)
double switch_item_height = item_height * switch_scaling;
item_x -= (switch_item_width - item_width) / 2.0;
item_y -= (switch_item_height - item_height) / 2.0;
draw_textured_rect(item_x, item_y + 20.0, switch_item_width, switch_item_height);
draw_flipped_texture_rectangle_centered(cpv(item_x + switch_item_width / 2.0, item_y - 20.0 + switch_item_height / 2.0), cpv(switch_item_width, switch_item_height));
sgp_reset_image(0);
}
}
@ -1495,6 +1600,7 @@ static void frame(void)
double width = (float)sapp_width(), height = (float)sapp_height();
double dt = sapp_frame_duration();
exec_time += dt;
iTime = (float)fmod(exec_time, 1000.0);
// pressed input management
{
@ -1562,8 +1668,12 @@ static void frame(void)
{
PROFILE_SCOPE("Deserializing data")
{
server_to_client_deserialize(&msg, decompressed,
decompressed_max_len, false);
SerState ser = init_deserializing(&gs, decompressed, decompressed_max_len, false);
SerMaybeFailure maybe_fail = ser_server_to_client(&ser, &msg);
if (maybe_fail.failed)
{
Log("Failed to deserialize game state packet line %d %s\n", maybe_fail.line, maybe_fail.expression);
}
applied_gamestate_packet = true;
}
my_player_index = msg.your_player;
@ -1606,11 +1716,8 @@ static void frame(void)
if (applied_gamestate_packet)
{
uint64_t server_current_tick = tick(&gs);
int ticks_should_repredict = (int)predicted_to_tick - (int)server_current_tick;
int healthy_num_ticks_ahead = (int)ceil((((double)peer->roundTripTime + (double)peer->roundTripTimeVariance * CAUTIOUS_MULTIPLIER) / 1000.0) / TIMESTEP) + 6;
int ticks_to_repredict = ticks_should_repredict;
if (ticks_should_repredict < healthy_num_ticks_ahead - 1)
@ -1716,15 +1823,9 @@ static void frame(void)
interact_pressed = false;
}
}
ENTITIES_ITER(cur)
{
{
}
}
// Create and send input packet, and predict a frame of gamestate
static InputFrame cur_input_frame = {
0}; // keep across frames for high refresh rate screens
static InputFrame cur_input_frame = {0}; // keep across frames for high refresh rate screens
static size_t last_input_committed_tick = 0;
{
// prepare the current input frame, such that when processed next,
@ -1791,10 +1892,36 @@ static void frame(void)
do
{
// "commit" the input. each input must be on a successive tick.
if (tick(&gs) > last_input_committed_tick)
// if (tick(&gs) > last_input_committed_tick)
while (tick(&gs) > last_input_committed_tick)
{
cur_input_frame.tick = tick(&gs);
last_input_committed_tick = tick(&gs);
if (replay_inputs_from != NULL)
{
unsigned char deserialized[2048] = {0};
flight_assert(ARRLEN(deserialized) >= serialized_inputframe_length());
size_t bytes_read = fread(deserialized, 1, serialized_inputframe_length(), replay_inputs_from);
if (bytes_read != serialized_inputframe_length())
{
// no more inputs in the saved file
flight_assert(myentity() != NULL);
Entity *should_be_medbay = get_entity(&gs, myentity()->currently_inside_of_box);
flight_assert(should_be_medbay != NULL);
flight_assert(should_be_medbay->is_box && should_be_medbay->box_type == BoxMedbay);
exit(0);
}
SerState ser = init_deserializing(&gs, deserialized, serialized_inputframe_length(), false);
SerMaybeFailure maybe_fail = ser_inputframe(&ser, &cur_input_frame);
flight_assert(!maybe_fail.failed);
flight_assert(serialized_inputframe_length() == ser_size(&ser));
}
else
{
cur_input_frame.tick = last_input_committed_tick + 1;
// cur_input_frame.tick = tick(&gs);
}
last_input_committed_tick = cur_input_frame.tick;
InputFrame *to_push_to = queue_push_element(&input_queue);
if (to_push_to == NULL)
@ -1807,9 +1934,40 @@ static void frame(void)
*to_push_to = cur_input_frame;
/*
optionally save the current input frame for debugging purposes
le informative comment
*/
if (record_inputs_to != NULL)
{
unsigned char serialized[2048] = {0};
SerState ser = init_serializing(&gs, serialized, ARRLEN(serialized), NULL, false);
SerMaybeFailure maybe_fail = ser_inputframe(&ser, &cur_input_frame);
flight_assert(!maybe_fail.failed);
flight_assert(serialized_inputframe_length() == ser_size(&ser));
size_t written = fwrite(serialized, 1, ser_size(&ser), record_inputs_to);
flight_assert(written == ser_size(&ser));
}
if (myplayer() != NULL)
myplayer()->input =
cur_input_frame; // for the client side prediction!
{
myplayer()->input = cur_input_frame; // for the client side prediction!
}
cur_input_frame = (InputFrame){0};
cur_input_frame.take_over_squad = -1; // @Robust make this zero initialized
@ -1840,9 +1998,10 @@ static void frame(void)
.input_data = &input_queue,
};
unsigned char serialized[MAX_CLIENT_TO_SERVER] = {0};
size_t out_len = 0;
if (client_to_server_serialize(&gs, &to_send, serialized, &out_len,
MAX_CLIENT_TO_SERVER))
SerState ser = init_serializing(&gs, serialized, MAX_CLIENT_TO_SERVER, NULL, false);
SerMaybeFailure maybe_fail = ser_client_to_server(&ser, &to_send);
size_t out_len = ser_size(&ser);
if (!maybe_fail.failed)
{
unsigned char compressed[MAX_CLIENT_TO_SERVER] = {0};
char lzo_working_mem[LZO1X_1_MEM_COMPRESS] = {0};
@ -1868,7 +2027,7 @@ static void frame(void)
}
else
{
Log("Failed to serialize client to server!\n");
Log("Failed to serialize client to server: %d %s\n", maybe_fail.line, maybe_fail.expression);
}
ma_mutex_unlock(&send_packets_mutex);
}
@ -2027,6 +2186,28 @@ static void frame(void)
player_scaling = lerp(player_scaling, player_scaling_target, dt * 15.0);
hovering_this_player = (EntityID){0};
// draw particles drawn in world space
set_color(WHITE);
PARTICLES_ITER(p)
{
if (p->alive)
{
p->alive_for += dt;
p->pos = cpvadd(p->pos, cpvmult(p->vel, dt));
if (p->alive_for > 1.0)
{
p->alive = false;
}
set_color_values(1.0, 1.0, 1.0, 1.0 - clamp01(p->alive_for));
pipeline_scope(goodpixel_pipeline)
{
sgp_set_image(0, image_pip);
draw_texture_centered(p->pos, 0.15 * p->scaling);
sgp_reset_image(0);
}
}
}
// draw all types of entities
ENTITIES_ITER(e)
{
@ -2049,11 +2230,14 @@ static void frame(void)
}
transform_scope()
{
rotate_at(entity_rotation(g) + rotangle(b->compass_rotation),
entity_pos(b).x, entity_pos(b).y);
rotate_at(entity_rotation(g) + rotangle(b->compass_rotation), entity_pos(b).x, entity_pos(b).y);
if (b->box_type == BoxThruster)
{
cpVect particle_vel = box_vel(b);
particle_vel = cpvadd(particle_vel, cpvmult(box_facing_vector(b), 0.5 + hash11(exec_time)*0.2)); // move outwards from thruster
particle_vel = cpvspin(particle_vel, hash11(exec_time)*0.1);
new_particle(cpvadd(entity_pos(b), cpvmult(box_facing_vector(b), BOX_SIZE * 0.5)), particle_vel);
transform_scope()
{
set_color_values(1.0, 1.0, 1.0, 1.0);
@ -2088,7 +2272,7 @@ static void frame(void)
set_color_values(1.0, 1.0, 1.0, 1.0 - b->sun_amount);
}
if (box_interactible(b->box_type))
if (myplayer() != NULL && box_interactible(&gs, myplayer(), b))
{
if (box_has_point((BoxCentered){
.pos = entity_pos(b),
@ -2142,6 +2326,20 @@ static void frame(void)
if (b->box_type == BoxScanner)
{
if (myplayer() != NULL && could_learn_from_scanner(myplayer(), b))
{
set_color(WHITE);
pipeline_scope(lightning_pipeline)
{
sgp_set_image(0, (sg_image){0});
lightning_uniforms_t uniform = {
.iTime = iTime,
};
sgp_set_uniform(&uniform, sizeof(uniform));
draw_color_rect_centered(entity_pos(b), BOX_SIZE * 2.0);
sgp_reset_image(0);
}
}
sgp_set_image(0, image_scanner_head);
transform_scope()
{
@ -2171,13 +2369,6 @@ static void frame(void)
sgp_reset_image(0);
}
// scanner range, visualizes what scanner can scan
if (b->box_type == BoxScanner)
{
set_color(BLUE);
draw_circle(entity_pos(b), SCANNER_RADIUS);
set_color(WHITE);
}
set_color_values(0.5, 0.1, 0.1, b->damage);
draw_color_rect_centered(entity_pos(b), BOX_SIZE);
@ -2215,13 +2406,73 @@ static void frame(void)
if (b->box_type == BoxScanner)
{
if (b->platonic_detection_strength > 0.0)
if (b->energy_effectiveness >= 1.0)
{
// maximum scanner range
set_color(BLUE);
draw_circle(entity_pos(b), SCANNER_RADIUS);
set_color_values(0.0, 0.0, 1.0, 0.3);
draw_circle(entity_pos(b), SCANNER_RADIUS * 0.25);
draw_circle(entity_pos(b), SCANNER_RADIUS * 0.5);
draw_circle(entity_pos(b), SCANNER_RADIUS * 0.75);
set_color(WHITE);
for (int i = 0; i < SCANNER_MAX_PLATONICS; i++)
if (b->detected_platonics[i].intensity > 0.0)
{
pipeline_scope(horizontal_lightning_pipeline)
{
sgp_set_image(0, (sg_image){0});
horizontal_lightning_uniforms_t uniform = {
.iTime = (float)(iTime + hash11((double)get_id(&gs, b).index)),
.alpha = (float)b->detected_platonics[i].intensity,
};
sgp_set_uniform(&uniform, sizeof(uniform));
transform_scope()
{
cpVect pos = cpvadd(entity_pos(b), cpvmult(b->detected_platonics[i].direction, SCANNER_RADIUS / 2.0));
rotate_at(cpvangle(b->detected_platonics[i].direction), pos.x, pos.y);
draw_color_rect_centered(pos, SCANNER_RADIUS);
}
sgp_reset_image(0);
}
}
set_color(colhexcode(0xf2d75c));
cpVect to = cpvadd(entity_pos(b), cpvmult(b->platonic_nearest_direction, b->platonic_detection_strength));
dbg_rect(to);
dbg_rect(entity_pos(b));
draw_line(entity_pos(b).x, entity_pos(b).y, to.x, to.y);
sgp_set_image(0, image_radardot);
for (int i = 0; i < SCANNER_MAX_POINTS; i++)
{
if (b->scanner_points[i].x != 0 || b->scanner_points[i].y != 0)
{
struct ScannerPoint point = b->scanner_points[i];
switch (point.kind)
{
case Platonic:
set_color(GOLD);
break;
case Neutral:
set_color(WHITE);
break;
case Enemy:
set_color(RED);
break;
default:
set_color(WHITE); // @Robust assert false in serialization if unexpected kind
break;
}
cpVect rel = cpv(
((double)point.x / 128.0) * SCANNER_RADIUS,
((double)point.y / 128.0) * SCANNER_RADIUS);
cpVect to = cpvadd(entity_pos(b), rel);
dbg_rect(to);
dbg_rect(entity_pos(b));
pipeline_scope(goodpixel_pipeline)
draw_texture_centered(to, BOX_SIZE / 2.0);
}
}
sgp_reset_image(0);
// draw_line(entity_pos(b).x, entity_pos(b).y, to.x, to.y);
}
}
if (b->box_type == BoxMissileLauncher)
@ -2296,21 +2547,25 @@ static void frame(void)
{
if (e != myentity() && has_point(centered_at(entity_pos(e), cpvmult(PLAYER_SIZE, player_scaling)), world_mouse_pos))
hovering_this_player = get_id(&gs, e);
if (get_entity(&gs, e->currently_inside_of_box) == NULL)
transform_scope()
{
rotate_at(entity_rotation(e), entity_pos(e).x, entity_pos(e).y);
set_color_values(1.0, 1.0, 1.0, 1.0);
pipeline_scope(hueshift_pipeline)
// the body
{
if (get_entity(&gs, e->currently_inside_of_box) == NULL)
transform_scope()
{
setup_hueshift(e->owning_squad);
sgp_set_image(0, image_player);
draw_texture_rectangle_centered(
entity_pos(e), cpvmult(PLAYER_SIZE, player_scaling));
sgp_reset_image(0);
rotate_at(entity_rotation(e), entity_pos(e).x, entity_pos(e).y);
set_color_values(1.0, 1.0, 1.0, 1.0);
pipeline_scope(hueshift_pipeline)
{
setup_hueshift(e->owning_squad);
sgp_set_image(0, image_player);
draw_texture_rectangle_centered(
entity_pos(e), cpvmult(PLAYER_SIZE, player_scaling));
sgp_reset_image(0);
}
}
}
}
}
// draw explosion
@ -2356,6 +2611,7 @@ static void frame(void)
set_color_values(1.0, 1.0, 1.0, 1.0);
dbg_drawall();
} // world space transform end
// low health
@ -2383,7 +2639,14 @@ static void frame(void)
void cleanup(void)
{
sargs_shutdown();
fclose(log_file);
if (record_inputs_to != NULL)
fclose(record_inputs_to);
if (replay_inputs_from != NULL)
fclose(replay_inputs_from);
sg_destroy_pipeline(hueshift_pipeline);
ma_mutex_lock(&server_info.info_mutex);
@ -2524,21 +2787,15 @@ extern void do_crash_handler();
sapp_desc sokol_main(int argc, char *argv[])
{
// do_crash_handler();
sargs_setup(&(sargs_desc){
.argc = argc,
.argv = argv});
bool hosting = false;
stm_setup();
ma_mutex_init(&server_info.info_mutex);
server_info.world_save = "debug_world.bin";
init_profiling("astris.spall");
init_profiling_mythread(0);
if (argc > 1)
{
server_thread_handle =
(void *)_beginthread(server, 0, (void *)&server_info);
hosting = true;
}
(void)argv;
return (sapp_desc){
.init_cb = init,
.frame_cb = frame,
@ -2546,7 +2803,7 @@ sapp_desc sokol_main(int argc, char *argv[])
.width = 640,
.height = 480,
.gl_force_gles2 = true,
.window_title = hosting ? "Flight Hosting" : "Flight Not Hosting",
.window_title = "Flight Not Hosting",
.icon.sokol_default = true,
.event_cb = event,
.win32_console_attach = true,

@ -1,9 +1,15 @@
git push
call build_release.bat
call update_server.bat
tar.exe -a -c -f releases\flight-nonumber.zip flight_release.exe loaded LICENSE.txt
git push || goto :error
call build_release.bat || goto :error
call update_server.bat || goto :error
tar.exe -a -c -f releases\flight-nonumber.zip flight_release.exe loaded LICENSE.txt || goto :error
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)"
echo "4. Push everything, then update all the servers (@TODO make this a script that works for multiple servers)"
goto :EOF
:error
echo Failed to release with error %errorlevel%
exit /b %errorlevel%

@ -22,6 +22,12 @@
#include "profiling.h"
static void panicquit()
{
flight_assert(false);
exit(-1);
}
// started in a thread from host
void server(void *info_raw)
{
@ -39,7 +45,6 @@ void server(void *info_raw)
gs.server_side_computing = true;
Log("Allocated %zu bytes for entities\n", entities_size);
create_initial_world(&gs);
// inputs
@ -56,9 +61,6 @@ void server(void *info_raw)
OpusEncoder *player_encoders[MAX_PLAYERS] = {0};
OpusDecoder *player_decoders[MAX_PLAYERS] = {0};
#ifdef DEBUG_WORLD
world_save_name = NULL;
#endif
if (world_save_name != NULL)
{
size_t read_game_data_buffer_size = entities_size;
@ -76,13 +78,18 @@ void server(void *info_raw)
if (actual_length <= 1)
{
Log("Could only read %zu bytes, error: errno %d\n", actual_length, errno);
exit(-1);
panicquit();
}
Log("Read %zu bytes from save file\n", actual_length);
ServerToClient msg = (ServerToClient){
.cur_gs = &gs,
};
server_to_client_deserialize(&msg, read_game_data, actual_length, true);
SerState ser = init_deserializing(&gs, read_game_data, actual_length, true);
SerMaybeFailure maybe_fail = ser_server_to_client(&ser, &msg);
if (maybe_fail.failed)
{
Log("Failed to deserialize game world from save file: %d %s\n", maybe_fail.line, maybe_fail.expression);
}
fclose(file);
}
@ -97,11 +104,10 @@ void server(void *info_raw)
}
#define BOX_AT(grid, pos) BOX_AT_TYPE(grid, pos, BoxHullpiece)
if (enet_initialize() != 0)
{
fprintf(stderr, "An error occurred while initializing ENet.\n");
exit(-1);
panicquit();
}
ENetAddress address;
@ -122,7 +128,7 @@ void server(void *info_raw)
{
fprintf(stderr,
"An error occurred while trying to create an ENet server host.\n");
exit(-1);
panicquit();
}
Log("Serving on port %d...\n", SERVER_PORT);
@ -219,8 +225,11 @@ void server(void *info_raw)
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]};
Queue new_inputs = {0};
char new_inputs_data[QUEUE_SIZE_FOR_ELEMENTS(sizeof(InputFrame), INPUT_QUEUE_MAX)] = {0};
queue_init(&new_inputs, sizeof(InputFrame), new_inputs_data, ARRLEN(new_inputs_data));
struct ClientToServer received = {.mic_data = buffer_to_fill, .input_data = &new_inputs};
unsigned char decompressed[MAX_CLIENT_TO_SERVER] = {0};
size_t decompressed_max_len = MAX_CLIENT_TO_SERVER;
flight_assert(LZO1X_MEM_DECOMPRESS == 0);
@ -229,9 +238,31 @@ void server(void *info_raw)
if (return_value == LZO_E_OK)
{
if (!client_to_server_deserialize(&gs, &received, decompressed, decompressed_max_len))
SerState ser = init_deserializing(&gs, decompressed, decompressed_max_len, false);
SerMaybeFailure maybe_fail = ser_client_to_server(&ser, &received);
if (maybe_fail.failed)
{
Log("Bad packet from client %d\n", (int)player_slot);
Log("Bad packet from client %d | %d %s\n", (int)player_slot, maybe_fail.line, maybe_fail.expression);
}
else
{
QUEUE_ITER(&new_inputs, InputFrame, new_input)
{
QUEUE_ITER(&player_input_queues[player_slot], InputFrame, existing_input)
{
if (existing_input->tick == new_input->tick && existing_input->been_processed)
{
new_input->been_processed = true;
}
}
}
queue_clear(&player_input_queues[player_slot]);
QUEUE_ITER(&new_inputs, InputFrame, cur)
{
InputFrame *new_elem = queue_push_element(&player_input_queues[player_slot]);
flight_assert(new_elem != NULL);
*new_elem = *cur;
}
}
}
else
@ -283,16 +314,21 @@ void server(void *info_raw)
{
PROFILE_SCOPE("World Processing")
{
CONNECTED_PEERS(enet_host, cur)
CONNECTED_PEERS(enet_host, cur_peer)
{
int this_player_index = (int)(int64_t)cur->data;
int this_player_index = (int)(int64_t)cur_peer->data;
QUEUE_ITER(&player_input_queues[this_player_index], InputFrame, cur)
{
if (cur->tick == tick(&gs))
{
gs.players[this_player_index].input = *cur;
cur->been_processed = true;
break;
}
if (cur->tick < tick(&gs) && !cur->been_processed)
{
Log("Did not process input from client %d %llu ticks ago!\n", this_player_index,tick(&gs) - cur->tick);
}
}
}
@ -309,8 +345,10 @@ void server(void *info_raw)
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))
SerState ser = init_serializing(&gs, world_save_buffer, entities_size, NULL, true);
SerMaybeFailure maybe_fail = ser_server_to_client(&ser, &msg);
size_t out_len = ser_size(&ser);
if (!maybe_fail.failed)
{
FILE *save_file = NULL;
fopen_s(&save_file, (const char *)world_save_name, "wb");
@ -334,7 +372,7 @@ void server(void *info_raw)
}
else
{
Log("URGENT: FAILED TO SAVE WORLD FILE!\n");
Log("URGENT: FAILED TO SAVE WORLD FILE! Failed at line %d expression %s\n", maybe_fail.line, maybe_fail.expression);
}
}
}
@ -431,13 +469,15 @@ void server(void *info_raw)
.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))
SerState ser = init_serializing(&gs, bytes_buffer, MAX_SERVER_TO_CLIENT, this_player_entity, false);
SerMaybeFailure maybe_fail = ser_server_to_client(&ser, &to_send);
size_t len = ser_size(&ser);
if (!maybe_fail.failed)
{
if (len > MAX_SERVER_TO_CLIENT - 8)
{
Log("Too much data quitting!\n");
exit(-1);
panicquit();
}
size_t compressed_len = 0;

@ -5,4 +5,6 @@ IF %ERRORLEVEL% NEQ 0 ECHO ERROR download sokol-shdc from https://github.com/flo
@REM example of how to compile shaders: sokol-shdc.exe --input triangle.glsl --output triangle.gen.h --slang glsl330:hlsl5:metal_macos
sokol-shdc.exe --format sokol --input hueshift.glsl --output hueshift.gen.h --slang glsl330:hlsl5:metal_macos
sokol-shdc.exe --format sokol --input lightning.glsl --output lightning.gen.h --slang glsl330:hlsl5:metal_macos
sokol-shdc.exe --format sokol --input horizontal_lightning.glsl --output horizontal_lightning.gen.h --slang glsl330:hlsl5:metal_macos
sokol-shdc.exe --format sokol --input goodpixel.glsl --output goodpixel.gen.h --slang glsl330:hlsl5:metal_macos

@ -0,0 +1,845 @@
#if defined(SOKOL_IMPL) && !defined(SOKOL_ARGS_IMPL)
#define SOKOL_ARGS_IMPL
#endif
#ifndef SOKOL_ARGS_INCLUDED
/*
sokol_args.h -- cross-platform key/value arg-parsing for web and native
Project URL: https://github.com/floooh/sokol
Do this:
#define SOKOL_IMPL or
#define SOKOL_ARGS_IMPL
before you include this file in *one* C or C++ file to create the
implementation.
Optionally provide the following defines with your own implementations:
SOKOL_ASSERT(c) - your own assert macro (default: assert(c))
SOKOL_ARGS_API_DECL - public function declaration prefix (default: extern)
SOKOL_API_DECL - same as SOKOL_ARGS_API_DECL
SOKOL_API_IMPL - public function implementation prefix (default: -)
If sokol_args.h is compiled as a DLL, define the following before
including the declaration or implementation:
SOKOL_DLL
On Windows, SOKOL_DLL will define SOKOL_ARGS_API_DECL as __declspec(dllexport)
or __declspec(dllimport) as needed.
OVERVIEW
========
sokol_args.h provides a simple unified argument parsing API for WebAssembly and
native apps.
When running as WebAssembly app, arguments are taken from the page URL:
https://floooh.github.io/tiny8bit/kc85.html?type=kc85_3&mod=m022&snapshot=kc85/jungle.kcc
The same arguments provided to a command line app:
kc85 type=kc85_3 mod=m022 snapshot=kc85/jungle.kcc
ARGUMENT FORMATTING
===================
On the web platform, arguments must be formatted as a valid URL query string
with 'percent encoding' used for special characters.
Strings are expected to be UTF-8 encoded (although sokol_args.h doesn't
contain any special UTF-8 handling). See below on how to obtain
UTF-8 encoded argc/argv values on Windows when using WinMain() as
entry point.
On native platforms the following rules must be followed:
Arguments have the general form
key=value
Key/value pairs are separated by 'whitespace', valid whitespace
characters are space and tab.
Whitespace characters in front and after the separating '=' character
are ignored:
key = value
...is the same as
key=value
The 'key' string must be a simple string without escape sequences or whitespace.
Currently 'single keys' without values are not allowed, but may be
in the future.
The 'value' string can be quoted, and quoted value strings can contain
whitespace:
key = 'single-quoted value'
key = "double-quoted value"
Single-quoted value strings can contain double quotes, and vice-versa:
key = 'single-quoted value "can contain double-quotes"'
key = "double-quoted value 'can contain single-quotes'"
Note that correct quoting can be tricky on some shells, since command
shells may remove quotes, unless they're escaped.
Value strings can contain a small selection of escape sequences:
\n - newline
\r - carriage return
\t - tab
\\ - escaped backslash
(more escape codes may be added in the future).
CODE EXAMPLE
============
int main(int argc, char* argv[]) {
// initialize sokol_args with default parameters
sargs_setup(&(sargs_desc){
.argc = argc,
.argv = argv
});
// check if a key exists...
if (sargs_exists("bla")) {
...
}
// get value string for key, if not found, return empty string ""
const char* val0 = sargs_value("bla");
// get value string for key, or default string if key not found
const char* val1 = sargs_value_def("bla", "default_value");
// check if a key matches expected value
if (sargs_equals("type", "kc85_4")) {
...
}
// check if a key's value is "true", "yes" or "on"
if (sargs_boolean("joystick_enabled")) {
...
}
// iterate over keys and values
for (int i = 0; i < sargs_num_args(); i++) {
printf("key: %s, value: %s\n", sargs_key_at(i), sargs_value_at(i));
}
// lookup argument index by key string, will return -1 if key
// is not found, sargs_key_at() and sargs_value_at() will return
// an empty string for invalid indices
int index = sargs_find("bla");
printf("key: %s, value: %s\n", sargs_key_at(index), sargs_value_at(index));
// shutdown sokol-args
sargs_shutdown();
}
WINMAIN AND ARGC / ARGV
=======================
On Windows with WinMain() based apps, getting UTF8-encoded command line
arguments is a bit more complicated:
First call GetCommandLineW(), this returns the entire command line
as UTF-16 string. Then call CommandLineToArgvW(), this parses the
command line string into the usual argc/argv format (but in UTF-16).
Finally convert the UTF-16 strings in argv[] into UTF-8 via
WideCharToMultiByte().
See the function _sapp_win32_command_line_to_utf8_argv() in sokol_app.h
for example code how to do this (if you're using sokol_app.h, it will
already convert the command line arguments to UTF-8 for you of course,
so you can plug them directly into sokol_app.h).
API DOCUMENTATION
=================
void sargs_setup(const sargs_desc* desc)
Initialize sokol_args, desc contains the following configuration
parameters:
int argc - the main function's argc parameter
char** argv - the main function's argv parameter
int max_args - max number of key/value pairs, default is 16
int buf_size - size of the internal string buffer, default is 16384
Note that on the web, argc and argv will be ignored and the arguments
will be taken from the page URL instead.
sargs_setup() will allocate 2 memory chunks: one for keeping track
of the key/value args of size 'max_args*8', and a string buffer
of size 'buf_size'.
void sargs_shutdown(void)
Shutdown sokol-args and free any allocated memory.
bool sargs_isvalid(void)
Return true between sargs_setup() and sargs_shutdown()
bool sargs_exists(const char* key)
Test if a key arg exists.
const char* sargs_value(const char* key)
Return value associated with key. Returns an empty
string ("") if the key doesn't exist.
const char* sargs_value_def(const char* key, const char* default)
Return value associated with key, or the provided default
value if the value doesn't exist.
bool sargs_equals(const char* key, const char* val);
Return true if the value associated with key matches
the 'val' argument.
bool sargs_boolean(const char* key)
Return true if the value string of 'key' is one
of 'true', 'yes', 'on'.
int sargs_find(const char* key)
Find argument by key name and return its index, or -1 if not found.
int sargs_num_args(void)
Return number of key/value pairs.
const char* sargs_key_at(int index)
Return the key name of argument at index. Returns empty string if
is index is outside range.
const char* sargs_value_at(int index)
Return the value of argument at index. Returns empty string
if index is outside range.
MEMORY ALLOCATION OVERRIDE
==========================
You can override the memory allocation functions at initialization time
like this:
void* my_alloc(size_t size, void* user_data) {
return malloc(size);
}
void my_free(void* ptr, void* user_data) {
free(ptr);
}
...
sargs_setup(&(sargs_desc){
// ...
.allocator = {
.alloc = my_alloc,
.free = my_free,
.user_data = ...,
}
});
...
If no overrides are provided, malloc and free will be used.
This only affects memory allocation calls done by sokol_args.h
itself though, not any allocations in OS libraries.
TODO
====
- parsing errors?
LICENSE
=======
zlib/libpng license
Copyright (c) 2018 Andre Weissflog
This software is provided 'as-is', without any express or implied warranty.
In no event will the authors be held liable for any damages arising from the
use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software in a
product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not
be misrepresented as being the original software.
3. This notice may not be removed or altered from any source
distribution.
*/
#define SOKOL_ARGS_INCLUDED (1)
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h> // size_t
#if defined(SOKOL_API_DECL) && !defined(SOKOL_ARGS_API_DECL)
#define SOKOL_ARGS_API_DECL SOKOL_API_DECL
#endif
#ifndef SOKOL_ARGS_API_DECL
#if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_ARGS_IMPL)
#define SOKOL_ARGS_API_DECL __declspec(dllexport)
#elif defined(_WIN32) && defined(SOKOL_DLL)
#define SOKOL_ARGS_API_DECL __declspec(dllimport)
#else
#define SOKOL_ARGS_API_DECL extern
#endif
#endif
#ifdef __cplusplus
extern "C" {
#endif
/*
sargs_allocator
Used in sargs_desc to provide custom memory-alloc and -free functions
to sokol_args.h. If memory management should be overridden, both the
alloc and free function must be provided (e.g. it's not valid to
override one function but not the other).
*/
typedef struct sargs_allocator {
void* (*alloc)(size_t size, void* user_data);
void (*free)(void* ptr, void* user_data);
void* user_data;
} sargs_allocator;
typedef struct sargs_desc {
int argc;
char** argv;
int max_args;
int buf_size;
sargs_allocator allocator;
} sargs_desc;
/* setup sokol-args */
SOKOL_ARGS_API_DECL void sargs_setup(const sargs_desc* desc);
/* shutdown sokol-args */
SOKOL_ARGS_API_DECL void sargs_shutdown(void);
/* true between sargs_setup() and sargs_shutdown() */
SOKOL_ARGS_API_DECL bool sargs_isvalid(void);
/* test if an argument exists by key name */
SOKOL_ARGS_API_DECL bool sargs_exists(const char* key);
/* get value by key name, return empty string if key doesn't exist */
SOKOL_ARGS_API_DECL const char* sargs_value(const char* key);
/* get value by key name, return provided default if key doesn't exist */
SOKOL_ARGS_API_DECL const char* sargs_value_def(const char* key, const char* def);
/* return true if val arg matches the value associated with key */
SOKOL_ARGS_API_DECL bool sargs_equals(const char* key, const char* val);
/* return true if key's value is "true", "yes" or "on" */
SOKOL_ARGS_API_DECL bool sargs_boolean(const char* key);
/* get index of arg by key name, return -1 if not exists */
SOKOL_ARGS_API_DECL int sargs_find(const char* key);
/* get number of parsed arguments */
SOKOL_ARGS_API_DECL int sargs_num_args(void);
/* get key name of argument at index, or empty string */
SOKOL_ARGS_API_DECL const char* sargs_key_at(int index);
/* get value string of argument at index, or empty string */
SOKOL_ARGS_API_DECL const char* sargs_value_at(int index);
#ifdef __cplusplus
} /* extern "C" */
/* reference-based equivalents for c++ */
inline void sargs_setup(const sargs_desc& desc) { return sargs_setup(&desc); }
#endif
#endif // SOKOL_ARGS_INCLUDED
/*--- IMPLEMENTATION ---------------------------------------------------------*/
#ifdef SOKOL_ARGS_IMPL
#define SOKOL_ARGS_IMPL_INCLUDED (1)
#if defined(SOKOL_MALLOC) || defined(SOKOL_CALLOC) || defined(SOKOL_FREE)
#error "SOKOL_MALLOC/CALLOC/FREE macros are no longer supported, please use sargs_desc.allocator to override memory allocation functions"
#endif
#include <string.h> // memset, strcmp
#include <stdlib.h> // malloc, free
#if defined(__EMSCRIPTEN__)
#include <emscripten/emscripten.h>
#endif
#ifndef SOKOL_API_IMPL
#define SOKOL_API_IMPL
#endif
#ifndef SOKOL_DEBUG
#ifndef NDEBUG
#define SOKOL_DEBUG
#endif
#endif
#ifndef SOKOL_ASSERT
#include <assert.h>
#define SOKOL_ASSERT(c) assert(c)
#endif
#ifndef _SOKOL_PRIVATE
#if defined(__GNUC__) || defined(__clang__)
#define _SOKOL_PRIVATE __attribute__((unused)) static
#else
#define _SOKOL_PRIVATE static
#endif
#endif
#define _sargs_def(val, def) (((val) == 0) ? (def) : (val))
#define _SARGS_MAX_ARGS_DEF (16)
#define _SARGS_BUF_SIZE_DEF (16*1024)
/* parser state */
#define _SARGS_EXPECT_KEY (1<<0)
#define _SARGS_EXPECT_SEP (1<<1)
#define _SARGS_EXPECT_VAL (1<<2)
#define _SARGS_PARSING_KEY (1<<3)
#define _SARGS_PARSING_VAL (1<<4)
#define _SARGS_ERROR (1<<5)
/* a key/value pair struct */
typedef struct {
int key; /* index to start of key string in buf */
int val; /* index to start of value string in buf */
} _sargs_kvp_t;
/* sokol-args state */
typedef struct {
int max_args; /* number of key/value pairs in args array */
int num_args; /* number of valid items in args array */
_sargs_kvp_t* args; /* key/value pair array */
int buf_size; /* size of buffer in bytes */
int buf_pos; /* current buffer position */
char* buf; /* character buffer, first char is reserved and zero for 'empty string' */
bool valid;
uint32_t parse_state;
char quote; /* current quote char, 0 if not in a quote */
bool in_escape; /* currently in an escape sequence */
sargs_allocator allocator;
} _sargs_state_t;
static _sargs_state_t _sargs;
/*== PRIVATE IMPLEMENTATION FUNCTIONS ========================================*/
_SOKOL_PRIVATE void _sargs_clear(void* ptr, size_t size) {
SOKOL_ASSERT(ptr && (size > 0));
memset(ptr, 0, size);
}
_SOKOL_PRIVATE void* _sargs_malloc(size_t size) {
SOKOL_ASSERT(size > 0);
void* ptr;
if (_sargs.allocator.alloc) {
ptr = _sargs.allocator.alloc(size, _sargs.allocator.user_data);
}
else {
ptr = malloc(size);
}
SOKOL_ASSERT(ptr);
return ptr;
}
_SOKOL_PRIVATE void* _sargs_malloc_clear(size_t size) {
void* ptr = _sargs_malloc(size);
_sargs_clear(ptr, size);
return ptr;
}
_SOKOL_PRIVATE void _sargs_free(void* ptr) {
if (_sargs.allocator.free) {
_sargs.allocator.free(ptr, _sargs.allocator.user_data);
}
else {
free(ptr);
}
}
_SOKOL_PRIVATE void _sargs_putc(char c) {
if ((_sargs.buf_pos+2) < _sargs.buf_size) {
_sargs.buf[_sargs.buf_pos++] = c;
}
}
_SOKOL_PRIVATE const char* _sargs_str(int index) {
SOKOL_ASSERT((index >= 0) && (index < _sargs.buf_size));
return &_sargs.buf[index];
}
/*-- argument parser functions ------------------*/
_SOKOL_PRIVATE void _sargs_expect_key(void) {
_sargs.parse_state = _SARGS_EXPECT_KEY;
}
_SOKOL_PRIVATE bool _sargs_key_expected(void) {
return 0 != (_sargs.parse_state & _SARGS_EXPECT_KEY);
}
_SOKOL_PRIVATE void _sargs_expect_val(void) {
_sargs.parse_state = _SARGS_EXPECT_VAL;
}
_SOKOL_PRIVATE bool _sargs_val_expected(void) {
return 0 != (_sargs.parse_state & _SARGS_EXPECT_VAL);
}
_SOKOL_PRIVATE void _sargs_expect_sep(void) {
_sargs.parse_state = _SARGS_EXPECT_SEP;
}
_SOKOL_PRIVATE bool _sargs_any_expected(void) {
return 0 != (_sargs.parse_state & (_SARGS_EXPECT_KEY | _SARGS_EXPECT_VAL | _SARGS_EXPECT_SEP));
}
_SOKOL_PRIVATE bool _sargs_is_separator(char c) {
return c == '=';
}
_SOKOL_PRIVATE bool _sargs_is_quote(char c) {
if (0 == _sargs.quote) {
return (c == '\'') || (c == '"');
}
else {
return c == _sargs.quote;
}
}
_SOKOL_PRIVATE void _sargs_begin_quote(char c) {
_sargs.quote = c;
}
_SOKOL_PRIVATE void _sargs_end_quote(void) {
_sargs.quote = 0;
}
_SOKOL_PRIVATE bool _sargs_in_quotes(void) {
return 0 != _sargs.quote;
}
_SOKOL_PRIVATE bool _sargs_is_whitespace(char c) {
return !_sargs_in_quotes() && ((c == ' ') || (c == '\t'));
}
_SOKOL_PRIVATE void _sargs_start_key(void) {
SOKOL_ASSERT(_sargs.num_args < _sargs.max_args);
_sargs.parse_state = _SARGS_PARSING_KEY;
_sargs.args[_sargs.num_args].key = _sargs.buf_pos;
}
_SOKOL_PRIVATE void _sargs_end_key(void) {
SOKOL_ASSERT(_sargs.num_args < _sargs.max_args);
_sargs_putc(0);
_sargs.parse_state = 0;
}
_SOKOL_PRIVATE bool _sargs_parsing_key(void) {
return 0 != (_sargs.parse_state & _SARGS_PARSING_KEY);
}
_SOKOL_PRIVATE void _sargs_start_val(void) {
SOKOL_ASSERT(_sargs.num_args < _sargs.max_args);
_sargs.parse_state = _SARGS_PARSING_VAL;
_sargs.args[_sargs.num_args].val = _sargs.buf_pos;
}
_SOKOL_PRIVATE void _sargs_end_val(void) {
SOKOL_ASSERT(_sargs.num_args < _sargs.max_args);
_sargs_putc(0);
_sargs.num_args++;
_sargs.parse_state = 0;
}
_SOKOL_PRIVATE bool _sargs_is_escape(char c) {
return '\\' == c;
}
_SOKOL_PRIVATE void _sargs_start_escape(void) {
_sargs.in_escape = true;
}
_SOKOL_PRIVATE bool _sargs_in_escape(void) {
return _sargs.in_escape;
}
_SOKOL_PRIVATE char _sargs_escape(char c) {
switch (c) {
case 'n': return '\n';
case 't': return '\t';
case 'r': return '\r';
case '\\': return '\\';
default: return c;
}
}
_SOKOL_PRIVATE void _sargs_end_escape(void) {
_sargs.in_escape = false;
}
_SOKOL_PRIVATE bool _sargs_parsing_val(void) {
return 0 != (_sargs.parse_state & _SARGS_PARSING_VAL);
}
_SOKOL_PRIVATE bool _sargs_parse_carg(const char* src) {
char c;
while (0 != (c = *src++)) {
if (_sargs_in_escape()) {
c = _sargs_escape(c);
_sargs_end_escape();
}
else if (_sargs_is_escape(c)) {
_sargs_start_escape();
continue;
}
if (_sargs_any_expected()) {
if (!_sargs_is_whitespace(c)) {
/* start of key, value or separator */
if (_sargs_key_expected()) {
/* start of new key */
_sargs_start_key();
}
else if (_sargs_val_expected()) {
/* start of value */
if (_sargs_is_quote(c)) {
_sargs_begin_quote(c);
continue;
}
_sargs_start_val();
}
else {
/* separator */
if (_sargs_is_separator(c)) {
_sargs_expect_val();
continue;
}
}
}
else {
/* skip white space */
continue;
}
}
else if (_sargs_parsing_key()) {
if (_sargs_is_whitespace(c) || _sargs_is_separator(c)) {
/* end of key string */
_sargs_end_key();
if (_sargs_is_separator(c)) {
_sargs_expect_val();
}
else {
_sargs_expect_sep();
}
continue;
}
}
else if (_sargs_parsing_val()) {
if (_sargs_in_quotes()) {
/* when in quotes, whitespace is a normal character
and a matching quote ends the value string
*/
if (_sargs_is_quote(c)) {
_sargs_end_quote();
_sargs_end_val();
_sargs_expect_key();
continue;
}
}
else if (_sargs_is_whitespace(c)) {
/* end of value string (no quotes) */
_sargs_end_val();
_sargs_expect_key();
continue;
}
}
_sargs_putc(c);
}
if (_sargs_parsing_key()) {
_sargs_end_key();
_sargs_expect_sep();
}
else if (_sargs_parsing_val() && !_sargs_in_quotes()) {
_sargs_end_val();
_sargs_expect_key();
}
return true;
}
_SOKOL_PRIVATE bool _sargs_parse_cargs(int argc, const char** argv) {
_sargs_expect_key();
bool retval = true;
for (int i = 1; i < argc; i++) {
retval &= _sargs_parse_carg(argv[i]);
}
_sargs.parse_state = 0;
return retval;
}
/*-- EMSCRIPTEN IMPLEMENTATION -----------------------------------------------*/
#if defined(__EMSCRIPTEN__)
#ifdef __cplusplus
extern "C" {
#endif
#if defined(EM_JS_DEPS)
EM_JS_DEPS(sokol_audio, "$withStackSave,$allocateUTF8OnStack");
#endif
EMSCRIPTEN_KEEPALIVE void _sargs_add_kvp(const char* key, const char* val) {
SOKOL_ASSERT(_sargs.valid && key && val);
if (_sargs.num_args >= _sargs.max_args) {
return;
}
/* copy key string */
char c;
_sargs.args[_sargs.num_args].key = _sargs.buf_pos;
const char* ptr = key;
while (0 != (c = *ptr++)) {
_sargs_putc(c);
}
_sargs_putc(0);
/* copy value string */
_sargs.args[_sargs.num_args].val = _sargs.buf_pos;
ptr = val;
while (0 != (c = *ptr++)) {
_sargs_putc(c);
}
_sargs_putc(0);
_sargs.num_args++;
}
#ifdef __cplusplus
} /* extern "C" */
#endif
/* JS function to extract arguments from the page URL */
EM_JS(void, sargs_js_parse_url, (void), {
const params = new URLSearchParams(window.location.search).entries();
for (let p = params.next(); !p.done; p = params.next()) {
const key = p.value[0];
const val = p.value[1];
withStackSave(() => {
const key_cstr = allocateUTF8OnStack(key);
const val_cstr = allocateUTF8OnStack(val);
__sargs_add_kvp(key_cstr, val_cstr)
});
}
});
#endif /* EMSCRIPTEN */
/*== PUBLIC IMPLEMENTATION FUNCTIONS =========================================*/
SOKOL_API_IMPL void sargs_setup(const sargs_desc* desc) {
SOKOL_ASSERT(desc);
_sargs_clear(&_sargs, sizeof(_sargs));
_sargs.max_args = _sargs_def(desc->max_args, _SARGS_MAX_ARGS_DEF);
_sargs.buf_size = _sargs_def(desc->buf_size, _SARGS_BUF_SIZE_DEF);
SOKOL_ASSERT(_sargs.buf_size > 8);
_sargs.args = (_sargs_kvp_t*) _sargs_malloc_clear((size_t)_sargs.max_args * sizeof(_sargs_kvp_t));
_sargs.buf = (char*) _sargs_malloc_clear((size_t)_sargs.buf_size * sizeof(char));
/* the first character in buf is reserved and always zero, this is the 'empty string' */
_sargs.buf_pos = 1;
_sargs.allocator = desc->allocator;
_sargs.valid = true;
/* parse argc/argv */
_sargs_parse_cargs(desc->argc, (const char**) desc->argv);
#if defined(__EMSCRIPTEN__)
/* on emscripten, also parse the page URL*/
sargs_js_parse_url();
#endif
}
SOKOL_API_IMPL void sargs_shutdown(void) {
SOKOL_ASSERT(_sargs.valid);
if (_sargs.args) {
_sargs_free(_sargs.args);
_sargs.args = 0;
}
if (_sargs.buf) {
_sargs_free(_sargs.buf);
_sargs.buf = 0;
}
_sargs.valid = false;
}
SOKOL_API_IMPL bool sargs_isvalid(void) {
return _sargs.valid;
}
SOKOL_API_IMPL int sargs_find(const char* key) {
SOKOL_ASSERT(_sargs.valid && key);
for (int i = 0; i < _sargs.num_args; i++) {
if (0 == strcmp(_sargs_str(_sargs.args[i].key), key)) {
return i;
}
}
return -1;
}
SOKOL_API_IMPL int sargs_num_args(void) {
SOKOL_ASSERT(_sargs.valid);
return _sargs.num_args;
}
SOKOL_API_IMPL const char* sargs_key_at(int index) {
SOKOL_ASSERT(_sargs.valid);
if ((index >= 0) && (index < _sargs.num_args)) {
return _sargs_str(_sargs.args[index].key);
}
else {
/* index 0 is always the empty string */
return _sargs_str(0);
}
}
SOKOL_API_IMPL const char* sargs_value_at(int index) {
SOKOL_ASSERT(_sargs.valid);
if ((index >= 0) && (index < _sargs.num_args)) {
return _sargs_str(_sargs.args[index].val);
}
else {
/* index 0 is always the empty string */
return _sargs_str(0);
}
}
SOKOL_API_IMPL bool sargs_exists(const char* key) {
SOKOL_ASSERT(_sargs.valid && key);
return -1 != sargs_find(key);
}
SOKOL_API_IMPL const char* sargs_value(const char* key) {
SOKOL_ASSERT(_sargs.valid && key);
return sargs_value_at(sargs_find(key));
}
SOKOL_API_IMPL const char* sargs_value_def(const char* key, const char* def) {
SOKOL_ASSERT(_sargs.valid && key && def);
int arg_index = sargs_find(key);
if (-1 != arg_index) {
return sargs_value_at(arg_index);
}
else {
return def;
}
}
SOKOL_API_IMPL bool sargs_equals(const char* key, const char* val) {
SOKOL_ASSERT(_sargs.valid && key && val);
return 0 == strcmp(sargs_value(key), val);
}
SOKOL_API_IMPL bool sargs_boolean(const char* key) {
const char* val = sargs_value(key);
return (0 == strcmp("true", val)) ||
(0 == strcmp("yes", val)) ||
(0 == strcmp("on", val));
}
#endif /* SOKOL_ARGS_IMPL */

@ -10,14 +10,7 @@
#define MAX_ENTITIES 1024 * 25
#define BOX_SIZE 0.25f
#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
#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
#define MISSILE_ARM_TIME 0.5f
@ -30,6 +23,17 @@
#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 MISSILE_SPAWN_DIST (sqrt((BOX_SIZE / 2.0) * (BOX_SIZE / 2.0) * 2.0) + MISSILE_COLLIDER_SIZE.x / 2.0 + 0.1)
#define MISSILE_EXPLOSION_PUSH 2.5f
#define MISSILE_EXPLOSION_RADIUS 0.4f
#define PLAYER_SIZE ((cpVect){.x = BOX_SIZE, .y = BOX_SIZE})
#define PLAYER_MASS 0.5f
#ifdef FAT_THRUSTERS
#define PLAYER_JETPACK_FORCE 200.0f
#else
#define PLAYER_JETPACK_FORCE 3.5f
#endif
#define PLAYER_JETPACK_TORQUE 0.05f
#define PLAYER_JETPACK_ROTATION_ENERGY_PER_SECOND 0.2f
#define PLAYER_JETPACK_SPICE_PER_SECOND 0.08f
#define PLAYER_BIG_SCALING 300.0
@ -43,26 +47,36 @@
#define ORB_HEAL_RATE 0.2
#define ORB_MAX_FORCE 200.0
#define SCANNER_ENERGY_USE 0.05f
#define VISION_RADIUS 20.0f
#define MAX_HAND_REACH 1.0f
#define SCANNER_SCAN_RATE 0.5f
#define SCANNER_RADIUS 1.0f
#define GOLD_COLLECT_RADIUS 0.3f
#define BUILD_BOX_SNAP_DIST_TO_SHIP 0.2f
#define BOX_MASS 1.0f
#define COLLISION_DAMAGE_SCALING 0.15f
#define THRUSTER_FORCE 24.0f
#define THRUSTER_ENERGY_USED_PER_SECOND 0.005f
#define GYROSCOPE_ENERGY_USED_PER_SECOND 0.005f
#define GYROSCOPE_TORQUE 1.5f
#define GYROSCOPE_PROPORTIONAL_INERTIAL_RESPONSE 0.7 // between 0-1. How strongly responds to rotation, to stop the rotation
#define CLOAKING_ENERGY_USE 0.1f
#define CLOAKING_PANEL_SIZE BOX_SIZE * 3.0f
#define VISION_RADIUS 12.0f
#define SCANNER_ENERGY_USE 0.05f
#define SCANNER_SCAN_RATE 0.5f
#define SCANNER_RADIUS 1.0f
#define SCANNER_MAX_RANGE 2000.0
#define SCANNER_MAX_VIEWPORT_RANGE 400.0
#define SCANNER_MIN_RANGE 1.0
#define SCANNER_MAX_POINTS 10
#define SCANNER_MAX_PLATONICS 3
#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 GRAVITY_CONSTANT 0.05f
#define GRAVITY_SMALLEST 0.01f // used to determine when gravity is clamped to 0.0f
#define GRAVITY_CONSTANT 0.01f
#define GRAVITY_SMALLEST 0.05f // used to determine when gravity is clamped to 0.0f
#define INSTANT_DEATH_DISTANCE_FROM_CENTER 10000.0f
#define SOLAR_ENERGY_PER_SECOND 0.09f
#define DAMAGE_TO_PLAYER_PER_BLOCK 0.1f
@ -77,9 +91,6 @@
#define TIME_BETWEEN_WORLD_SAVE 30.0f
#endif
#define MISSILE_EXPLOSION_PUSH 2.5f
#define MISSILE_EXPLOSION_RADIUS 0.4f
#define BOMB_EXPLOSION_PUSH 5.0f
#define BOMB_EXPLOSION_RADIUS 1.0f
@ -95,7 +106,7 @@
// multiplayer
#define CAUTIOUS_MULTIPLIER 0.8 // how overboard to go with the time ahead predicting, makes it less likely that inputs are lost
#define TICKS_BEHIND_DO_SNAP 6 // when this many ticks behind, instead of dilating time SNAP to the healthy ticks ahead
#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)
@ -179,20 +190,6 @@ enum BoxType
BoxLast,
};
static inline bool box_interactible(enum BoxType type)
{
enum BoxType types[] = {
BoxCockpit,
BoxMedbay,
BoxMerge,
BoxScanner,
};
for (int i = 0; i < ARRLEN(types); i++)
if (types[i] == type)
return true;
return false;
}
enum CompassRotation
{
Right,
@ -221,14 +218,25 @@ 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);
}
enum ScannerPointKind
{
Platonic,
Neutral,
Enemy,
};
// when updated, must update serialization, comparison in main.c, and the server
// on input received processing function
typedef struct InputFrame
{
uint64_t tick;
bool been_processed; // not serialized, used by server just to keep track of what inputs have been processed
cpVect movement;
double rotation;
@ -245,11 +253,18 @@ typedef struct InputFrame
enum CompassRotation build_rotation;
} InputFrame;
typedef struct PlatonicDetection
{
cpVect direction;
double intensity;
} PlatonicDetection;
typedef struct Entity
{
bool exists;
EntityID next_free_entity;
unsigned int generation;
bool always_visible; // always serialized to the player.
bool no_save_to_disk; // stuff generated later on, like player's bodies or space stations that respawn.
@ -267,16 +282,15 @@ typedef struct Entity
// what the shape's parent entity is
bool is_circle_shape;
EntityID shape_parent_entity; // can't be zero if shape is nonzero
double shape_radius; // only when circle shape
cpVect shape_size; // only when rect shape
double shape_radius; // only when circle shape
cpVect shape_size; // only when rect shape
// player
bool is_player;
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
double goldness; // how much the player is a winner
// explosion
bool is_explosion;
cpVect explosion_pos;
@ -308,7 +322,6 @@ typedef struct Entity
bool is_box;
enum BoxType box_type;
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
enum CompassRotation compass_rotation;
@ -328,12 +341,11 @@ typedef struct Entity
// used by medbay and cockpit
EntityID player_who_is_inside_of_me;
// only serialized when box_type is thruster or gyroscope, used for both. Thrust
// can mean rotation thrust!
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 gyroscope, velocity not serialized. Cosmetic
double gyrospin_angle;
double gyrospin_velocity;
@ -353,8 +365,15 @@ typedef struct Entity
BOX_UNLOCKS_TYPE blueprints_learned;
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
PlatonicDetection detected_platonics[SCANNER_MAX_PLATONICS]; // intensity of 0.0 means undetected
struct ScannerPoint
{
char kind; // is of ScannerPointKind
char x;
char y;
} scanner_points[SCANNER_MAX_POINTS];
} Entity;
typedef struct Player
@ -475,11 +494,37 @@ double sun_dist_no_gravity(Entity *sun);
void quit_with_popup(const char *message_utf8, const char *title_utf8);
// serialization stuff
typedef struct SerState
{
unsigned char *bytes;
bool serializing;
size_t cursor; // points to next available byte, is the size of current message after serializing something
size_t max_size;
Entity *for_player;
size_t max_entity_index; // for error checking
bool write_varnames;
bool save_or_load_from_disk;
// output
uint32_t version;
uint32_t git_release_tag; // release tag, unlike version, is about the game version not the serialization verson
} SerState;
typedef struct SerMaybeFailure
{
bool failed;
int line;
const char *expression;
} SerMaybeFailure;
// 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);
size_t ser_size(SerState *ser);
SerState init_serializing(GameState *gs, unsigned char *bytes, size_t max_size, Entity *for_player, bool to_disk);
SerState init_deserializing(GameState *gs, unsigned char *bytes, size_t max_size, bool from_disk);
SerMaybeFailure ser_server_to_client(SerState *ser, ServerToClient *s);
SerMaybeFailure ser_client_to_server(SerState *ser, ClientToServer *msg);
SerMaybeFailure ser_inputframe(SerState *ser, InputFrame *i);
// entities
bool is_burning(Entity *missile);
@ -487,7 +532,9 @@ Entity *get_entity(struct GameState *gs, EntityID id);
Entity *new_entity(struct GameState *gs);
EntityID get_id(struct GameState *gs, Entity *e);
cpVect entity_pos(Entity *e);
bool box_interactible(GameState *gs, Player *for_player, Entity *box);
void entity_set_rotation(Entity *e, double rot);
bool could_learn_from_scanner(Player *for_player, Entity *box);
void entity_set_pos(Entity *e, cpVect pos);
double entity_rotation(Entity *e);
void entity_ensure_in_orbit(GameState *gs, Entity *e);
@ -583,7 +630,7 @@ typedef struct BoxCentered
static inline bool box_has_point(BoxCentered box, cpVect point)
{
cpVect local_point = cpvspin(cpvsub(point, box.pos), -box.rotation);
return has_point((AABB){.x = -box.size.x/2.0, .y = -box.size.y/2.0, .width = box.size.x, .height = box.size.y}, local_point);
return has_point((AABB){.x = -box.size.x / 2.0, .y = -box.size.y / 2.0, .width = box.size.x, .height = box.size.y}, local_point);
}
static double sign(double f)
@ -649,10 +696,13 @@ static inline double hash11(double p)
p = fract(p * .1031f);
p *= p + 33.33f;
p *= p + p;
return fract(p);
return 2.0 * fract(p) - 1.0;
}
static inline double deg2rad(double deg)
{
return (deg / 360.0f) * 2.0f * PI;
}
#define min(X,Y) (((X) < (Y)) ? (X) : (Y))
#define max(X,Y) (((X) > (Y)) ? (X) : (Y))

@ -1 +1,2 @@
ssh astris "cd flight; git pull; ./linux_server_install.sh"
@echo off
ssh astris "cd flight || exit 1 ; git pull || exit 1 ; ./linux_server_install.sh || exit 1 ; echo Successfully updated" || exit /b %errorlevel%

@ -0,0 +1,80 @@
includes:
-IC:\Users\Cameron\Documents\flight\
thirdparty
thirdparty\minilzo
thirdparty\enet\include
thirdparty\Chipmunk2D\include\chipmunk
thirdparty\Chipmunk2D\include
thirdparty\opus\include
thirdparty\opus\src
objects:
C:\Users\Cameron\Documents\flight\elf_objects\
chipmunk.o
cpArbiter.o
cpArray.o
cpBBTree.o
cpBody.o
cpCollision.o
cpConstraint.o
cpDampedRotarySpring.o
cpDampedSpring.o
cpGearJoint.o
cpGrooveJoint.o
cpHashSet.o
cpHastySpace.o
cpMarch.o
cpPinJoint.o
cpPivotJoint.o
cpPolyline.o
cpPolyShape.o
cpRatchetJoint.o
cpRobust.o
cpRotaryLimitJoint.o
cpShape.o
cpSimpleMotor.o
cpSlideJoint.o
cpSpace.o
cpSpaceComponent.o
cpSpaceDebug.o
cpSpaceHash.o
cpSpaceQuery.o
cpSpaceStep.o
cpSpatialIndex.o
cpSweep1D.o
C:\Users\Cameron\Documents\flight\elf_objects\
C:\Users\Cameron\Documents\flight\elf_objects\chipmunk.o
C:\Users\Cameron\Documents\flight\elf_objects\cpArbiter.o
C:\Users\Cameron\Documents\flight\elf_objects\cpArray.o
C:\Users\Cameron\Documents\flight\elf_objects\cpBBTree.o
C:\Users\Cameron\Documents\flight\elf_objects\cpBody.o
C:\Users\Cameron\Documents\flight\elf_objects\cpCollision.o
C:\Users\Cameron\Documents\flight\elf_objects\cpConstraint.o
C:\Users\Cameron\Documents\flight\elf_objects\cpDampedRotarySpring.o
C:\Users\Cameron\Documents\flight\elf_objects\cpDampedSpring.o
C:\Users\Cameron\Documents\flight\elf_objects\cpGearJoint.o
C:\Users\Cameron\Documents\flight\elf_objects\cpGrooveJoint.o
C:\Users\Cameron\Documents\flight\elf_objects\cpHashSet.o
C:\Users\Cameron\Documents\flight\elf_objects\cpHastySpace.o
C:\Users\Cameron\Documents\flight\elf_objects\cpMarch.o
C:\Users\Cameron\Documents\flight\elf_objects\cpPinJoint.o
C:\Users\Cameron\Documents\flight\elf_objects\cpPivotJoint.o
C:\Users\Cameron\Documents\flight\elf_objects\cpPolyline.o
C:\Users\Cameron\Documents\flight\elf_objects\cpPolyShape.o
C:\Users\Cameron\Documents\flight\elf_objects\cpRatchetJoint.o
C:\Users\Cameron\Documents\flight\elf_objects\cpRobust.o
C:\Users\Cameron\Documents\flight\elf_objects\cpRotaryLimitJoint.o
C:\Users\Cameron\Documents\flight\elf_objects\cpShape.o
C:\Users\Cameron\Documents\flight\elf_objects\cpSimpleMotor.o
C:\Users\Cameron\Documents\flight\elf_objects\cpSlideJoint.o
C:\Users\Cameron\Documents\flight\elf_objects\cpSpace.o
C:\Users\Cameron\Documents\flight\elf_objects\cpSpaceComponent.o
C:\Users\Cameron\Documents\flight\elf_objects\cpSpaceDebug.o
C:\Users\Cameron\Documents\flight\elf_objects\cpSpaceHash.o
C:\Users\Cameron\Documents\flight\elf_objects\cpSpaceQuery.o
C:\Users\Cameron\Documents\flight\elf_objects\cpSpaceStep.o
C:\Users\Cameron\Documents\flight\elf_objects\cpSpatialIndex.o
C:\Users\Cameron\Documents\flight\elf_objects\cpSweep1D.o
Loading…
Cancel
Save