Code reformat and CLIENT SIDE PREDICTION!!

main
Cameron Murphy Reikes 2 years ago
parent 6c0211436e
commit e972537e89

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

@ -30,11 +30,6 @@ typedef struct Command
// thread local variables so debug drawing in server thread // thread local variables so debug drawing in server thread
// doesn't fuck up main thread // doesn't fuck up main thread
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__)
#define THREADLOCAL __declspec(thread)
#else
#define THREADLOCAL __thread
#endif
static THREADLOCAL Command commands[MAX_COMMANDS] = {0}; static THREADLOCAL Command commands[MAX_COMMANDS] = {0};
static THREADLOCAL int command_i = 0; static THREADLOCAL int command_i = 0;

File diff suppressed because it is too large Load Diff

3087
main.c

File diff suppressed because it is too large Load Diff

@ -1,112 +1,137 @@
#pragma once #pragma once
#include <stdbool.h>
#ifndef QUEUE_ASSERT #ifndef QUEUE_ASSERT
void __assert(bool cond, const char* file, int line, const char* cond_string); void __assert(bool cond, const char *file, int line, const char *cond_string);
#define QUEUE_ASSERT(condition) __assert(condition, __FILE__, __LINE__, #condition) #define QUEUE_ASSERT(condition) __assert(condition, __FILE__, __LINE__, #condition)
#endif #endif
typedef struct QueueElementHeader { typedef struct QueueElementHeader
bool exists; {
struct QueueElementHeader* next; bool exists;
char data[]; struct QueueElementHeader *next;
char data[];
} QueueElementHeader; } QueueElementHeader;
typedef struct Queue { typedef struct Queue
char* data; {
size_t data_length; // must be a multiple of sizeof(QueueElementHeader) + element_size char *data;
size_t element_size; size_t data_length; // must be a multiple of sizeof(QueueElementHeader) + element_size
QueueElementHeader* next; size_t element_size;
QueueElementHeader *next;
} Queue; } Queue;
#define QUEUE_SIZE_FOR_ELEMENTS(element_size, max_elements) ((sizeof(QueueElementHeader) + element_size) * max_elements) #define QUEUE_SIZE_FOR_ELEMENTS(element_size, max_elements) ((sizeof(QueueElementHeader) + element_size) * max_elements)
void queue_init(Queue* q, size_t element_size, char* data, size_t data_length); // oldest to newest
void queue_clear(Queue* q); #define QUEUE_ITER(q_ptr, cur_header) for (QueueElementHeader *cur_header = (q_ptr)->next; cur_header != NULL; cur_header = cur_header->next)
void* queue_push_element(Queue* q); void queue_init(Queue *q, size_t element_size, char *data, size_t data_length);
size_t queue_num_elements(Queue* q); void queue_clear(Queue *q);
void* queue_pop_element(Queue* q); void *queue_push_element(Queue *q);
size_t queue_num_elements(Queue *q);
void *queue_pop_element(Queue *q);
void *queue_most_recent_element(Queue *q);
#ifdef QUEUE_IMPL #ifdef QUEUE_IMPL
void queue_init(Queue* q, size_t element_size, char* data, size_t data_length) void queue_init(Queue *q, size_t element_size, char *data, size_t data_length)
{ {
q->data = data; q->data = data;
q->data_length = data_length; q->data_length = data_length;
q->element_size = element_size; q->element_size = element_size;
QUEUE_ASSERT(data_length % (sizeof(QueueElementHeader) + element_size) == 0); QUEUE_ASSERT(data_length % (sizeof(QueueElementHeader) + element_size) == 0);
} }
void queue_clear(Queue* q) void queue_clear(Queue *q)
{ {
QUEUE_ASSERT(q->data != NULL); QUEUE_ASSERT(q->data != NULL);
for (size_t i = 0; i < q->data_length; i++) for (size_t i = 0; i < q->data_length; i++)
{ {
q->data[i] = 0; q->data[i] = 0;
} }
q->next = NULL; q->next = NULL;
} }
#define QUEUE_ELEM_ITER(cur) for(QueueElementHeader *cur = (QueueElementHeader*)q->data; (char*)cur < q->data + q->data_length; (char*)cur += (sizeof(QueueElementHeader) + q->element_size)) #define QUEUE_ELEM_ITER(cur) for (QueueElementHeader *cur = (QueueElementHeader *)q->data; (char *)cur < q->data + q->data_length; (char *)cur += (sizeof(QueueElementHeader) + q->element_size))
// you push an element, get the return value, cast it to your type, and fill it with data. It's that easy! // you push an element, get the return value, cast it to your type, and fill it with data. It's that easy!
// if it's null the queue is out of space // if it's null the queue is out of space
void* queue_push_element(Queue* q) void *queue_push_element(Queue *q)
{ {
QUEUE_ASSERT(q->data != NULL); QUEUE_ASSERT(q->data != NULL);
QueueElementHeader* to_return = NULL; QueueElementHeader *to_return = NULL;
QUEUE_ELEM_ITER(cur) QUEUE_ELEM_ITER(cur)
{ {
if (!cur->exists) if (!cur->exists)
{ {
to_return = cur; to_return = cur;
break; break;
} }
} }
// no free packet found in the buffer // no free packet found in the buffer
if (to_return == NULL) if (to_return == NULL)
{ {
return NULL; return NULL;
} }
else { else
to_return->exists = true; {
to_return->next = NULL; // very important. to_return->exists = true;
for (size_t i = 0; i < q->element_size; i++) to_return->next = NULL; // very important.
to_return->data[i] = 0; for (size_t i = 0; i < q->element_size; i++)
to_return->data[i] = 0;
// add to the end of the linked list chain
if (q->next != NULL) // add to the end of the linked list chain
{ if (q->next != NULL)
QueueElementHeader* cur = q->next; {
while (cur->next != NULL) cur = cur->next; QueueElementHeader *cur = q->next;
cur->next = to_return; while (cur->next != NULL)
} cur = cur->next;
else { cur->next = to_return;
q->next = to_return; }
} else
{
return (void*)to_return->data; q->next = to_return;
} }
return (void *)to_return->data;
}
} }
size_t queue_num_elements(Queue* q) size_t queue_num_elements(Queue *q)
{ {
QUEUE_ASSERT(q->data != NULL); QUEUE_ASSERT(q->data != NULL);
size_t to_return = 0; size_t to_return = 0;
QUEUE_ELEM_ITER(cur) QUEUE_ELEM_ITER(cur)
if (cur->exists) to_return++; if (cur->exists)
return to_return; to_return++;
return to_return;
} }
// returns null if the queue is empty // returns null if the queue is empty
void* queue_pop_element(Queue* q) void *queue_pop_element(Queue *q)
{ {
QUEUE_ASSERT(q->data != NULL); QUEUE_ASSERT(q->data != NULL);
QueueElementHeader* to_return = q->next; QueueElementHeader *to_return = q->next;
if (q->next != NULL) q->next = q->next->next; if (q->next != NULL)
if (to_return != NULL) to_return->exists = false; // jank! q->next = q->next->next;
return to_return == NULL ? NULL : (void*)to_return->data; if (to_return != NULL)
to_return->exists = false; // jank!
return to_return == NULL ? NULL : (void *)to_return->data;
} }
void *queue_most_recent_element(Queue *q)
{
if (q->next == NULL)
return NULL;
else
{
QueueElementHeader *cur = q->next;
while (cur->next != NULL)
cur = cur->next;
return (void *)cur->data;
}
}
#undef QUEUE_ELEM_ITER #undef QUEUE_ELEM_ITER
#endif #endif

1009
server.c

File diff suppressed because it is too large Load Diff

@ -1,7 +1,9 @@
#pragma once #pragma once
#include "ipsettings.h"
#define MAX_PLAYERS 16 #define MAX_PLAYERS 16
#define MAX_ENTITIES 1024*25 #define MAX_ENTITIES 1024 * 25
#define BOX_SIZE 0.25f #define BOX_SIZE 0.25f
#define PLAYER_SIZE ((V2){.x = BOX_SIZE, .y = BOX_SIZE}) #define PLAYER_SIZE ((V2){.x = BOX_SIZE, .y = BOX_SIZE})
#define PLAYER_MASS 0.5f #define PLAYER_MASS 0.5f
@ -17,14 +19,18 @@
#define THRUSTER_ENERGY_USED_PER_SECOND 0.005f #define THRUSTER_ENERGY_USED_PER_SECOND 0.005f
#define VISION_RADIUS 12.0f #define VISION_RADIUS 12.0f
#define MAX_SERVER_TO_CLIENT 1024 * 512 // maximum size of serialized gamestate buffer #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 MAX_CLIENT_TO_SERVER 1024 * 10 // maximum size of serialized inputs and mic data
#define SUN_RADIUS 10.0f #define SUN_RADIUS 10.0f
#define INSTANT_DEATH_DISTANCE_FROM_SUN 2000.0f #define INSTANT_DEATH_DISTANCE_FROM_SUN 2000.0f
#define SUN_POS ((V2){50.0f,0.0f}) #define SUN_POS ((V2){50.0f, 0.0f})
#ifdef NO_GRAVITY
#define SUN_GRAVITY_STRENGTH 0.1f
#else
#define SUN_GRAVITY_STRENGTH (9.0e2f) #define SUN_GRAVITY_STRENGTH (9.0e2f)
#endif
#define SOLAR_ENERGY_PER_SECOND 0.02f #define SOLAR_ENERGY_PER_SECOND 0.02f
#define DAMAGE_TO_PLAYER_PER_BLOCK 0.1f #define DAMAGE_TO_PLAYER_PER_BLOCK 0.1f
#define BATTERY_CAPACITY DAMAGE_TO_PLAYER_PER_BLOCK*0.7f #define BATTERY_CAPACITY DAMAGE_TO_PLAYER_PER_BLOCK * 0.7f
#define PLAYER_ENERGY_RECHARGE_PER_SECOND 0.1f #define PLAYER_ENERGY_RECHARGE_PER_SECOND 0.1f
#define EXPLOSION_TIME 0.5f #define EXPLOSION_TIME 0.5f
#define EXPLOSION_PUSH_STRENGTH 5.0f #define EXPLOSION_PUSH_STRENGTH 5.0f
@ -35,18 +41,27 @@
#define TIME_BETWEEN_WORLD_SAVE 30.0f #define TIME_BETWEEN_WORLD_SAVE 30.0f
// VOIP // VOIP
#define VOIP_PACKET_BUFFER_SIZE 15 // audio. Must be bigger than 2 #define VOIP_PACKET_BUFFER_SIZE 15 // audio. Must be bigger than 2
#define VOIP_EXPECTED_FRAME_COUNT 480 #define VOIP_EXPECTED_FRAME_COUNT 480
#define VOIP_SAMPLE_RATE 48000 #define VOIP_SAMPLE_RATE 48000
#define VOIP_TIME_PER_PACKET 1.0f / ((float)(VOIP_SAMPLE_RATE/VOIP_EXPECTED_FRAME_COUNT)) // in seconds #define VOIP_TIME_PER_PACKET (1.0f / ((float)(VOIP_SAMPLE_RATE / VOIP_EXPECTED_FRAME_COUNT))) // in seconds
#define VOIP_PACKET_MAX_SIZE 4000 #define VOIP_PACKET_MAX_SIZE 4000
#define VOIP_DISTANCE_WHEN_CANT_HEAR (VISION_RADIUS*0.8f) #define VOIP_DISTANCE_WHEN_CANT_HEAR (VISION_RADIUS * 0.8f)
#define MAX_REPREDICTION_TIME (TIMESTEP * 50.0f)
#define TIME_BETWEEN_SEND_GAMESTATE (1.0f / 20.0f) #define TIME_BETWEEN_SEND_GAMESTATE (1.0f / 20.0f)
#define TIME_BETWEEN_INPUT_PACKETS (1.0f / 20.0f) #define TIME_BETWEEN_INPUT_PACKETS (1.0f / 20.0f)
#define TIMESTEP (1.0f / 60.0f) // server required to simulate at this, defines what tick the game is on #define TIMESTEP (1.0f / 60.0f) // server required to simulate at this, defines what tick the game is on
#define SERVER_PORT 2551 #define SERVER_PORT 2551
#define INPUT_BUFFER 6 #define LOCAL_INPUT_QUEUE_MAX 90 // please god let you not have more than 90 frames of game latency
#define INPUT_QUEUE_MAX 15
// cross platform threadlocal variables
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__)
#define THREADLOCAL __declspec(thread)
#else
#define THREADLOCAL __thread
#endif
// must make this header and set the target address, just #define SERVER_ADDRESS "127.0.0.1" // must make this header and set the target address, just #define SERVER_ADDRESS "127.0.0.1"
#include "ipsettings.h" // don't leak IP! #include "ipsettings.h" // don't leak IP!
@ -72,7 +87,7 @@ void sgp_set_color(float, float, float, float);
// somehow automatically or easily cast to floats // somehow automatically or easily cast to floats
typedef struct sgp_vec2 typedef struct sgp_vec2
{ {
float x, y; float x, y;
} sgp_vec2; } sgp_vec2;
typedef sgp_vec2 sgp_point; typedef sgp_vec2 sgp_point;
@ -85,8 +100,8 @@ typedef void cpBody;
typedef void cpShape; typedef void cpShape;
#endif #endif
#include <stdbool.h>
#include "queue.h" #include "queue.h"
#include <stdbool.h>
#ifndef OPUS_TYPES_H #ifndef OPUS_TYPES_H
typedef int opus_int32; typedef int opus_int32;
@ -103,509 +118,512 @@ typedef int opus_int32;
typedef sgp_vec2 V2; typedef sgp_vec2 V2;
typedef sgp_point P2; typedef sgp_point P2;
#define Log(...){ \ #define Log(...) \
{ \
fprintf(stdout, "%s:%d | ", __FILE__, __LINE__); \ fprintf(stdout, "%s:%d | ", __FILE__, __LINE__); \
fprintf(stdout, __VA_ARGS__);} fprintf(stdout, __VA_ARGS__); \
}
enum BoxType enum BoxType
{ {
BoxHullpiece, BoxHullpiece,
BoxThruster, BoxThruster,
BoxBattery, BoxBattery,
BoxCockpit, BoxCockpit,
BoxMedbay, BoxMedbay,
BoxSolarPanel, BoxSolarPanel,
BoxExplosive, BoxExplosive,
BoxLast, BoxLast,
}; };
enum CompassRotation enum CompassRotation
{ {
Right, Right,
Down, Down,
Left, Left,
Up, Up,
RotationLast, RotationLast,
}; };
enum Squad enum Squad
{ {
SquadNone, SquadNone,
SquadRed, SquadRed,
SquadGreen, SquadGreen,
SquadBlue, SquadBlue,
SquadPurple, SquadPurple,
SquadLast, SquadLast,
}; };
// when generation is 0, invalid ID // when generation is 0, invalid ID
typedef struct EntityID typedef struct EntityID
{ {
unsigned int generation; // VERY IMPORTANT if 0 then EntityID points to nothing, generation >= 1 unsigned int generation; // VERY IMPORTANT if 0 then EntityID points to nothing, generation >= 1
unsigned int index; // index into the entity arena unsigned int index; // index into the entity arena
} EntityID; } EntityID;
static bool entityids_same(EntityID a, EntityID b) static bool entityids_same(EntityID a, EntityID b)
{ {
return (a.generation == b.generation) && (a.index == b.index); return (a.generation == b.generation) && (a.index == b.index);
} }
// when updated, must update serialization, comparison in main.c, and the server // when updated, must update serialization, comparison in main.c, and the server
// on input received processing function // on input received processing function
typedef struct InputFrame typedef struct InputFrame
{ {
uint64_t tick; uint64_t tick;
size_t id; // each input has unique, incrementing, I.D, so server doesn't double process inputs. Inputs to server should be ordered from 0-max like biggest id-smallest. This is done so if packet loss server still processes input V2 movement;
V2 movement;
int take_over_squad; // -1 means not taking over any squad int take_over_squad; // -1 means not taking over any squad
bool accept_cur_squad_invite; bool accept_cur_squad_invite;
bool reject_cur_squad_invite; bool reject_cur_squad_invite;
EntityID invite_this_player; // null means inviting nobody! @Robust make it so just sends interact pos input, and server processes who to invite. This depends on client side prediction + proper input processing at the right tick. EntityID invite_this_player; // null means inviting nobody! @Robust make it so just sends interact pos input, and server processes who to invite. This depends on client side prediction + proper input processing at the right tick.
bool seat_action; bool seat_action;
EntityID seat_to_inhabit; V2 hand_pos; // local to player transationally but not rotationally
V2 hand_pos; // local to player transationally but not rotationally unless field below is not null, then it's local to that grid // @BeforeShip bounds check on the hand_pos so that players can't reach across the entire map
// @BeforeShip bounds check on the hand_pos so that players can't reach across the entire map
EntityID grid_hand_pos_local_to; // when not null, hand_pos is local to this grid. this prevents bug where at high speeds the built block is in the wrong place on the selected grid
bool dobuild; bool dobuild;
enum BoxType build_type; enum BoxType build_type;
enum CompassRotation build_rotation; enum CompassRotation build_rotation;
} InputFrame; } InputFrame;
typedef struct Entity typedef struct Entity
{ {
bool exists; bool exists;
EntityID next_free_entity; EntityID next_free_entity;
unsigned int generation; unsigned int generation;
bool no_save_to_disk; // stuff generated later on, like player's bodies or space stations that respawn. bool no_save_to_disk; // stuff generated later on, like player's bodies or space stations that respawn.
float damage; // used by box and player float damage; // used by box and player
cpBody* body; // used by grid, player, and box cpBody *body; // used by grid, player, and box
cpShape* shape; // must be a box so shape_size can be set appropriately, and serialized cpShape *shape; // must be a box so shape_size can be set appropriately, and serialized
// for serializing the shape // for serializing the shape
// @Robust remove shape_parent_entity from this struct, use the shape's body to figure out // @Robust remove shape_parent_entity from this struct, use the shape's body to figure out
// what the shape's parent entity is // what the shape's parent entity is
EntityID shape_parent_entity; // can't be zero if shape is nonzero EntityID shape_parent_entity; // can't be zero if shape is nonzero
V2 shape_size; V2 shape_size;
// player // player
bool is_player; bool is_player;
enum Squad presenting_squad; enum Squad presenting_squad;
EntityID currently_inside_of_box; EntityID currently_inside_of_box;
enum Squad squad_invited_to; // if squad none, then no squad invite enum Squad squad_invited_to; // if squad none, then no squad invite
float goldness; // how much the player is a winner float goldness; // how much the player is a winner
// explosion // explosion
bool is_explosion; bool is_explosion;
V2 explosion_pos; V2 explosion_pos;
V2 explosion_vel; V2 explosion_vel;
float explosion_progresss; // in seconds float explosion_progresss; // in seconds
// grids // grids
bool is_grid; bool is_grid;
float total_energy_capacity; float total_energy_capacity;
EntityID boxes; EntityID boxes;
// boxes // boxes
bool is_box; bool is_box;
bool always_visible; // always serialized to the player bool always_visible; // always serialized to the player
enum BoxType box_type; enum BoxType box_type;
bool is_explosion_unlock; bool is_explosion_unlock;
EntityID next_box; EntityID next_box;
EntityID prev_box; // doubly linked so can remove in middle of chain EntityID prev_box; // doubly linked so can remove in middle of chain
enum CompassRotation compass_rotation; enum CompassRotation compass_rotation;
bool indestructible; bool indestructible;
float wanted_thrust; // the thrust command applied to the thruster float wanted_thrust; // the thrust command applied to the thruster
float thrust; // the actual thrust it can provide based on energy sources in the grid float thrust; // the actual thrust it can provide based on energy sources in the grid
float energy_used; // battery float energy_used; // battery
float sun_amount; // solar panel, between 0 and 1 float sun_amount; // solar panel, between 0 and 1
EntityID player_who_is_inside_of_me; EntityID player_who_is_inside_of_me;
} Entity; } Entity;
typedef struct Player typedef struct Player
{ {
bool connected; bool connected;
bool unlocked_bombs; bool unlocked_bombs;
enum Squad squad; enum Squad squad;
EntityID entity; EntityID entity;
EntityID last_used_medbay; EntityID last_used_medbay;
InputFrame input; InputFrame input;
} Player; } Player;
// gotta update the serialization functions when this changes // gotta update the serialization functions when this changes
typedef struct GameState typedef struct GameState
{ {
cpSpace* space; cpSpace *space;
double time; double time; // @Robust separate tick integer not prone to precision issues
V2 goldpos; V2 goldpos;
Player players[MAX_PLAYERS]; Player players[MAX_PLAYERS];
EntityID cur_spacestation; EntityID cur_spacestation;
// Entity arena // Entity arena
// ent:ity pointers can't move around because of how the physics engine handles user data. // ent:ity pointers can't move around because of how the physics engine handles user data.
// if you really need this, potentially refactor to store entity IDs instead of pointers // if you really need this, potentially refactor to store entity IDs instead of pointers
// in the shapes and bodies of chipmunk. Would require editing the library I think // in the shapes and bodies of chipmunk. Would require editing the library I think
Entity* entities; Entity *entities;
unsigned int max_entities; // maximum number of entities possible in the entities list unsigned int max_entities; // maximum number of entities possible in the entities list
unsigned int cur_next_entity; // next entity to pass on request of a new entity if the free list is empty unsigned int cur_next_entity; // next entity to pass on request of a new entity if the free list is empty
EntityID free_list; EntityID free_list;
} GameState; } GameState;
#define PLAYERS_ITER(players, cur) for(Player * cur = players; cur < players+MAX_PLAYERS; cur++) if(cur->connected) #define PLAYERS_ITER(players, cur) \
for (Player *cur = players; cur < players + MAX_PLAYERS; cur++) \
if (cur->connected)
#define PI 3.14159f #define PI 3.14159f
#define TAU (PI*2.0f) #define TAU (PI * 2.0f)
// returns in radians // returns in radians
static float rotangle(enum CompassRotation rot) static float rotangle(enum CompassRotation rot)
{ {
switch (rot) switch (rot)
{ {
case Right: case Right:
return 0.0f; return 0.0f;
break; break;
case Down: case Down:
return -PI / 2.0f; return -PI / 2.0f;
break; break;
case Left: case Left:
return -PI; return -PI;
break; break;
case Up: case Up:
return -3.0f * PI / 2.0f; return -3.0f * PI / 2.0f;
break; break;
default: default:
Log("Unknown rotation %d\n", rot); Log("Unknown rotation %d\n", rot);
return -0.0f; return -0.0f;
break; break;
} }
} }
typedef struct OpusPacket { typedef struct OpusPacket
opus_int32 length; {
char data[VOIP_PACKET_MAX_SIZE]; opus_int32 length;
char data[VOIP_PACKET_MAX_SIZE];
} OpusPacket; } OpusPacket;
typedef struct ServerToClient typedef struct ServerToClient
{ {
struct GameState* cur_gs; struct GameState *cur_gs;
Queue* playback_buffer; Queue *audio_playback_buffer;
int your_player; int your_player;
} ServerToClient; } ServerToClient;
typedef struct ClientToServer typedef struct ClientToServer
{ {
Queue* mic_data; // on serialize, flushes this of packets. On deserialize, fills it Queue *mic_data; // on serialize, flushes this of packets. On deserialize, fills it
InputFrame inputs[INPUT_BUFFER]; Queue *input_data; // does not flush on serialize! must be in order of tick
} ClientToServer; } ClientToServer;
#define DeferLoop(start, end) \ #define DeferLoop(start, end) \
for (int _i_ = ((start), 0); _i_ == 0; _i_ += 1, (end)) for (int _i_ = ((start), 0); _i_ == 0; _i_ += 1, (end))
// server // server
void server(void* info); // data parameter required from thread api... void server(void *info); // data parameter required from thread api...
// gamestate // gamestate
EntityID create_spacestation(GameState* gs); EntityID create_spacestation(GameState *gs);
void initialize(struct GameState* gs, void* entity_arena, size_t entity_arena_size); void initialize(struct GameState *gs, void *entity_arena, size_t entity_arena_size);
void destroy(struct GameState* gs); void destroy(struct GameState *gs);
void process(struct GameState* gs, float dt); // does in place void process_fixed_timestep(GameState *gs);
Entity* closest_to_point_in_radius(struct GameState* gs, V2 point, float radius); void process(struct GameState *gs, float dt); // does in place
uint64_t tick(struct GameState* gs); Entity *closest_to_point_in_radius(struct GameState *gs, V2 point, float radius);
uint64_t tick(struct GameState *gs);
// all of these return if successful or not // all of these return if successful or not
bool server_to_client_serialize(struct ServerToClient* msg, char* bytes, size_t* out_len, size_t max_len, Entity* for_this_player, bool to_disk); bool server_to_client_serialize(struct ServerToClient *msg, 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, char* bytes, size_t max_len, bool from_disk); bool server_to_client_deserialize(struct ServerToClient *msg, char *bytes, size_t max_len, bool from_disk);
bool client_to_server_deserialize(GameState* gs, struct ClientToServer* msg, char* bytes, size_t max_len); bool client_to_server_deserialize(GameState *gs, struct ClientToServer *msg, char *bytes, size_t max_len);
bool client_to_server_serialize(GameState* gs, struct ClientToServer* msg, char* bytes, size_t* out_len, size_t max_len); bool client_to_server_serialize(GameState *gs, struct ClientToServer *msg, char *bytes, size_t *out_len, size_t max_len);
// entities // entities
Entity* get_entity(struct GameState* gs, EntityID id); Entity *get_entity(struct GameState *gs, EntityID id);
Entity* new_entity(struct GameState* gs); Entity *new_entity(struct GameState *gs);
EntityID get_id(struct GameState* gs, Entity* e); EntityID get_id(struct GameState *gs, Entity *e);
V2 entity_pos(Entity* e); V2 entity_pos(Entity *e);
void entity_set_rotation(Entity* e, float rot); void entity_set_rotation(Entity *e, float rot);
void entity_set_pos(Entity* e, V2 pos); void entity_set_pos(Entity *e, V2 pos);
float entity_rotation(Entity* e); float entity_rotation(Entity *e);
void entity_ensure_in_orbit(Entity* e); void entity_ensure_in_orbit(Entity *e);
void entity_destroy(GameState* gs, Entity* e); void entity_destroy(GameState *gs, Entity *e);
#define BOX_CHAIN_ITER(gs, cur, starting_box) for (Entity *cur = get_entity(gs, starting_box); cur != NULL; cur = get_entity(gs, cur->next_box)) #define BOX_CHAIN_ITER(gs, cur, starting_box) for (Entity *cur = get_entity(gs, starting_box); cur != NULL; cur = get_entity(gs, cur->next_box))
#define BOXES_ITER(gs, cur, grid_entity_ptr) BOX_CHAIN_ITER(gs, cur, (grid_entity_ptr)->boxes) #define BOXES_ITER(gs, cur, grid_entity_ptr) BOX_CHAIN_ITER(gs, cur, (grid_entity_ptr)->boxes)
// grid // grid
void grid_create(struct GameState* gs, Entity* e); void grid_create(struct GameState *gs, Entity *e);
void box_create(struct GameState* gs, Entity* new_box, Entity* grid, V2 pos); void box_create(struct GameState *gs, Entity *new_box, Entity *grid, V2 pos);
V2 grid_com(Entity* grid); V2 grid_com(Entity *grid);
V2 grid_vel(Entity* grid); V2 grid_vel(Entity *grid);
V2 box_vel(Entity* box); V2 box_vel(Entity *box);
V2 grid_local_to_world(Entity* grid, V2 local); V2 grid_local_to_world(Entity *grid, V2 local);
V2 grid_world_to_local(Entity* grid, V2 world); V2 grid_world_to_local(Entity *grid, V2 world);
V2 grid_snapped_box_pos(Entity* grid, V2 world); // returns the snapped pos in world coords V2 grid_snapped_box_pos(Entity *grid, V2 world); // returns the snapped pos in world coords
float entity_angular_velocity(Entity* grid); float entity_angular_velocity(Entity *grid);
V2 entity_shape_pos(Entity* box); V2 entity_shape_pos(Entity *box);
float box_rotation(Entity* box); float box_rotation(Entity *box);
// thruster // thruster
V2 box_facing_vector(Entity* box); V2 box_facing_vector(Entity *box);
V2 thruster_force(Entity* box); V2 thruster_force(Entity *box);
// debug draw // debug draw
void dbg_drawall(); void dbg_drawall();
void dbg_line(V2 from, V2 to); void dbg_line(V2 from, V2 to);
void dbg_rect(V2 center); void dbg_rect(V2 center);
typedef struct ServerThreadInfo { typedef struct ServerThreadInfo
ma_mutex info_mutex; {
const char* world_save; ma_mutex info_mutex;
bool should_quit; const char *world_save;
bool should_quit;
} ServerThreadInfo; } ServerThreadInfo;
// all the math is static so that it can be defined in each compilation unit its included in // all the math is static so that it can be defined in each compilation unit its included in
typedef struct AABB typedef struct AABB
{ {
float x, y, width, height; float x, y, width, height;
} AABB; } AABB;
static AABB centered_at(V2 point, V2 size) static AABB centered_at(V2 point, V2 size)
{ {
return (AABB) return (AABB){
{ .x = point.x - size.x / 2.0f,
.x = point.x - size.x / 2.0f, .y = point.y - size.y / 2.0f,
.y = point.y - size.y / 2.0f, .width = size.x,
.width = size.x, .height = size.y,
.height = size.y, };
};
} }
static bool has_point(AABB aabb, V2 point) static bool has_point(AABB aabb, V2 point)
{ {
return point.x > aabb.x && point.x < aabb.x + aabb.width && point.y > aabb.y && point.y < aabb.y + aabb.height; return point.x > aabb.x && point.x < aabb.x + aabb.width && point.y > aabb.y && point.y < aabb.y + aabb.height;
} }
static V2 V2add(V2 a, V2 b) static V2 V2add(V2 a, V2 b)
{ {
return (V2) { return (V2){
.x = a.x + b.x, .x = a.x + b.x,
.y = a.y + b.y, .y = a.y + b.y,
}; };
} }
static V2 V2scale(V2 a, float f) static V2 V2scale(V2 a, float f)
{ {
return (V2) { return (V2){
.x = a.x * f, .x = a.x * f,
.y = a.y * f, .y = a.y * f,
}; };
} }
static float V2lengthsqr(V2 v) static float V2lengthsqr(V2 v)
{ {
return v.x * v.x + v.y * v.y; return v.x * v.x + v.y * v.y;
} }
static float V2length(V2 v) static float V2length(V2 v)
{ {
return sqrtf(V2lengthsqr(v)); return sqrtf(V2lengthsqr(v));
} }
static V2 V2normalize(V2 v) static V2 V2normalize(V2 v)
{ {
return V2scale(v, 1.0f / V2length(v)); return V2scale(v, 1.0f / V2length(v));
} }
static float V2dot(V2 a, V2 b) static float V2dot(V2 a, V2 b)
{ {
return a.x * b.x + a.y * b.y; return a.x * b.x + a.y * b.y;
} }
static float V2projectvalue(V2 vec, V2 onto) static float V2projectvalue(V2 vec, V2 onto)
{ {
float length_onto = V2length(onto); float length_onto = V2length(onto);
return V2dot(vec, onto) / (length_onto * length_onto); return V2dot(vec, onto) / (length_onto * length_onto);
} }
static V2 V2project(V2 vec, V2 onto) static V2 V2project(V2 vec, V2 onto)
{ {
return V2scale(onto, V2projectvalue(vec, onto)); return V2scale(onto, V2projectvalue(vec, onto));
} }
static V2 V2rotate(V2 vec, float theta) static V2 V2rotate(V2 vec, float theta)
{ {
return (V2) { return (V2){
.x = vec.x * cosf(theta) - vec.y * sinf(theta), .x = vec.x * cosf(theta) - vec.y * sinf(theta),
.y = vec.x * sinf(theta) + vec.y * cosf(theta), .y = vec.x * sinf(theta) + vec.y * cosf(theta),
}; };
} }
// also known as atan2 // also known as atan2
static float V2angle(V2 vec) static float V2angle(V2 vec)
{ {
return atan2f(vec.y, vec.x); return atan2f(vec.y, vec.x);
} }
static V2 V2sub(V2 a, V2 b) static V2 V2sub(V2 a, V2 b)
{ {
return (V2) { return (V2){
.x = a.x - b.x, .x = a.x - b.x,
.y = a.y - b.y, .y = a.y - b.y,
}; };
} }
static bool V2equal(V2 a, V2 b, float eps) static bool V2equal(V2 a, V2 b, float eps)
{ {
return V2length(V2sub(a, b)) < eps; return V2length(V2sub(a, b)) < eps;
} }
static inline float clamp01(float f) static inline float clamp01(float f)
{ {
return fmaxf(0.0f, fminf(f, 1.0f)); return fmaxf(0.0f, fminf(f, 1.0f));
} }
static float V2distsqr(V2 from, V2 to) static float V2distsqr(V2 from, V2 to)
{ {
return V2lengthsqr(V2sub(to, from)); return V2lengthsqr(V2sub(to, from));
} }
static float V2dist(V2 from, V2 to) static float V2dist(V2 from, V2 to)
{ {
return sqrtf(V2distsqr(from, to)); return sqrtf(V2distsqr(from, to));
} }
static inline float clamp(float f, float minimum, float maximum) static inline float clamp(float f, float minimum, float maximum)
{ {
if (f < minimum) if (f < minimum)
return minimum; return minimum;
if (f > maximum) if (f > maximum)
return maximum; return maximum;
return f; return f;
} }
static float fract(float f) static float fract(float f)
{ {
return f - floorf(f); return f - floorf(f);
} }
static float lerp(float a, float b, float f) static float lerp(float a, float b, float f)
{ {
return a * (1.0f - f) + (b * f); return a * (1.0f - f) + (b * f);
} }
static float lerp_angle(float p_from, float p_to, float p_weight) { static float lerp_angle(float p_from, float p_to, float p_weight)
float difference = fmodf(p_to - p_from, (float)TAU); {
float distance = fmodf(2.0f * difference, (float)TAU) - difference; float difference = fmodf(p_to - p_from, (float)TAU);
return p_from + distance * p_weight; float distance = fmodf(2.0f * difference, (float)TAU) - difference;
return p_from + distance * p_weight;
} }
static V2 V2floor(V2 p) static V2 V2floor(V2 p)
{ {
return (V2) { floorf(p.x), floorf(p.y) }; return (V2){floorf(p.x), floorf(p.y)};
} }
static V2 V2fract(V2 p) static V2 V2fract(V2 p)
{ {
return (V2) { fract(p.x), fract(p.y) }; return (V2){fract(p.x), fract(p.y)};
} }
/* /*
float noise(V2 p) float noise(V2 p)
{ {
V2 id = V2floor(p); V2 id = V2floor(p);
V2 f = V2fract(p); V2 f = V2fract(p);
V2 u = V2dot(f, f) * (3.0f - 2.0f * f); V2 u = V2dot(f, f) * (3.0f - 2.0f * f);
return mix(mix(random(id + V2(0.0, 0.0)), return mix(mix(random(id + V2(0.0, 0.0)),
random(id + V2(1.0, 0.0)), u.x), random(id + V2(1.0, 0.0)), u.x),
mix(random(id + V2(0.0, 1.0)), mix(random(id + V2(0.0, 1.0)),
random(id + V2(1.0, 1.0)), u.x), random(id + V2(1.0, 1.0)), u.x),
u.y); u.y);
} }
float fbm(V2 p) float fbm(V2 p)
{ {
float f = 0.0; float f = 0.0;
float gat = 0.0; float gat = 0.0;
for (float octave = 0.; octave < 5.; ++octave) for (float octave = 0.; octave < 5.; ++octave)
{ {
float la = pow(2.0, octave); float la = pow(2.0, octave);
float ga = pow(0.5, octave + 1.); float ga = pow(0.5, octave + 1.);
f += ga * noise(la * p); f += ga * noise(la * p);
gat += ga; gat += ga;
} }
f = f / gat; f = f / gat;
return f; return f;
} }
*/ */
static V2 V2lerp(V2 a, V2 b, float factor) static V2 V2lerp(V2 a, V2 b, float factor)
{ {
V2 to_return = { 0 }; V2 to_return = {0};
to_return.x = lerp(a.x, b.x, factor); to_return.x = lerp(a.x, b.x, factor);
to_return.y = lerp(a.y, b.y, factor); to_return.y = lerp(a.y, b.y, factor);
return to_return; return to_return;
} }
// for random generation // for random generation
static float hash11(float p) static float hash11(float p)
{ {
p = fract(p * .1031f); p = fract(p * .1031f);
p *= p + 33.33f; p *= p + 33.33f;
p *= p + p; p *= p + p;
return fract(p); return fract(p);
} }
typedef struct Color typedef struct Color
{ {
float r, g, b, a; float r, g, b, a;
} Color; } Color;
static Color colhex(int r, int g, int b) static Color colhex(int r, int g, int b)
{ {
return (Color) { return (Color){
.r = (float)r / 255.0f, .r = (float)r / 255.0f,
.g = (float)g / 255.0f, .g = (float)g / 255.0f,
.b = (float)b / 255.0f, .b = (float)b / 255.0f,
.a = 1.0f, .a = 1.0f,
}; };
} }
static Color colhexcode(int hexcode) static Color colhexcode(int hexcode)
{ {
// 0x020509; // 0x020509;
int r = (hexcode >> 16) & 0xFF; int r = (hexcode >> 16) & 0xFF;
int g = (hexcode >> 8) & 0xFF; int g = (hexcode >> 8) & 0xFF;
int b = (hexcode >> 0) & 0xFF; int b = (hexcode >> 0) & 0xFF;
return colhex(r, g, b); return colhex(r, g, b);
} }
static Color Collerp(Color a, Color b, float factor) static Color Collerp(Color a, Color b, float factor)
{ {
Color to_return = { 0 }; Color to_return = {0};
to_return.r = lerp(a.r, b.r, factor); to_return.r = lerp(a.r, b.r, factor);
to_return.g = lerp(a.g, b.g, factor); to_return.g = lerp(a.g, b.g, factor);
to_return.b = lerp(a.b, b.b, factor); to_return.b = lerp(a.b, b.b, factor);
to_return.a = lerp(a.a, b.a, factor); to_return.a = lerp(a.a, b.a, factor);
return to_return; return to_return;
} }
static void set_color(Color c) static void set_color(Color c)
{ {
sgp_set_color(c.r, c.g, c.b, c.a); sgp_set_color(c.r, c.g, c.b, c.a);
} }
#define WHITE \ #define WHITE \
(Color) { .r = 1.0f, .g = 1.0f, .b = 1.0f, .a = 1.0f } (Color) { .r = 1.0f, .g = 1.0f, .b = 1.0f, .a = 1.0f }
#define RED \ #define RED \
(Color) { .r = 1.0f, .g = 0.0f, .b = 0.0f, .a = 1.0f } (Color) { .r = 1.0f, .g = 0.0f, .b = 0.0f, .a = 1.0f }
#define GOLD colhex(255, 215, 0) #define GOLD colhex(255, 215, 0)

Loading…
Cancel
Save