diff --git a/Flight.vcxproj b/Flight.vcxproj
index f9ead28..112d911 100644
--- a/Flight.vcxproj
+++ b/Flight.vcxproj
@@ -235,6 +235,7 @@
+
diff --git a/Flight.vcxproj.filters b/Flight.vcxproj.filters
index f9bf14c..f2583ff 100644
--- a/Flight.vcxproj.filters
+++ b/Flight.vcxproj.filters
@@ -191,6 +191,9 @@
Header Files
+
+ Source Files
+
diff --git a/build_debug.bat b/build_debug.bat
index 006924b..fb48b24 100644
--- a/build_debug.bat
+++ b/build_debug.bat
@@ -1,2 +1,3 @@
+call shadergen.bat
set compileopts=/Fe"flight_debug" /Zi /FS /Fd"flight.pdb" /DSERVER_ADDRESS="\"127.0.0.1\"" /DDEBUG_RENDERING
call build_msvc.bat
\ No newline at end of file
diff --git a/build_msvc.bat b/build_msvc.bat
index 6d19a23..7528060 100644
--- a/build_msvc.bat
+++ b/build_msvc.bat
@@ -2,16 +2,12 @@
@REM what all the compile flags mean: https://learn.microsoft.com/en-us/cpp/build/reference/compiler-options-listed-by-category?view=msvc-170
-WHERE sokol-shdc.exe
-IF %ERRORLEVEL% NEQ 0 ECHO ERROR download sokol-shdc from https://github.com/floooh/sokol-tools-bin/blob/master/bin/win32/sokol-shdc.exe and put it in this folder
set OPUSLIB=%~dp0\thirdparty\opus\win32\VS2015\x64\Release\opus.lib
WHERE %OPUSLIB%
IF %ERRORLEVEL% NEQ 0 ECHO ERROR Couldn't find %OPUSLIB% compile opus by opening the visual studio project in win32\VS2015 and building the release setting
-@REM example of how to compile shaders: sokol-shdc.exe --input triangle.glsl --output triangle.gen.h --slang glsl330:hlsl5:metal_macos
-
setlocal enabledelayedexpansion enableextensions
pushd thirdparty\Chipmunk2D\src
set MUNKSRC=
diff --git a/enc_temp_folder/2762a15775b8c934b29c84e546d5a7/main.c b/enc_temp_folder/2762a15775b8c934b29c84e546d5a7/main.c
new file mode 100644
index 0000000..5114258
--- /dev/null
+++ b/enc_temp_folder/2762a15775b8c934b29c84e546d5a7/main.c
@@ -0,0 +1,1453 @@
+//------------------------------------------------------------------------------
+// Take flight
+//------------------------------------------------------------------------------
+
+#define SOKOL_IMPL
+#define SOKOL_D3D11
+#include
+#include // starting server thread
+
+#pragma warning(disable : 33010) // this warning is so broken, doesn't understand assert()
+#include "sokol_app.h"
+#include "sokol_gfx.h"
+#include "sokol_glue.h"
+#include "sokol_gp.h"
+#include "sokol_time.h"
+#pragma warning(default : 33010)
+#pragma warning(disable : 6262) // warning about using a lot of stack, lol that's how stb image is
+#define STB_IMAGE_IMPLEMENTATION
+#include "stb_image.h"
+#include "types.h"
+
+#include "opus.h"
+
+#include
+#include // errno error message on file open
+
+#include "minilzo.h"
+
+#define MINIAUDIO_IMPLEMENTATION
+#include "miniaudio.h"
+
+// shaders
+#include "hueshift.gen.h"
+static sg_pipeline pip;
+
+static struct GameState gs = {0};
+static int myplayer = -1;
+static bool right_mouse_down = false;
+static bool keydown[SAPP_KEYCODE_MENU] = {0};
+typedef struct KeyPressed
+{
+ bool pressed;
+ uint64_t frame;
+} KeyPressed;
+static KeyPressed keypressed[SAPP_KEYCODE_MENU] = {0};
+static V2 mouse_pos = {0};
+static bool fullscreened = false;
+static bool mouse_pressed = false;
+static uint64_t mouse_pressed_frame = 0;
+static bool mouse_frozen = false; // @BeforeShip make this debug only thing
+static float funval = 0.0f; // easy to play with value controlled by left mouse button when held
+// down @BeforeShip remove on release builds
+static struct ClientToServer client_to_server = {0}; // buffer of inputs
+static ENetHost *client;
+static ENetPeer *peer;
+static float zoom_target = 300.0f;
+static float zoom = 300.0f;
+static enum Squad take_over_squad = (enum Squad) - 1; // -1 means not taking over any squad
+
+// images
+static sg_image image_itemframe;
+static sg_image image_itemframe_selected;
+static sg_image image_thrusterburn;
+static sg_image image_player;
+static sg_image image_cockpit_used;
+static sg_image image_stars;
+static sg_image image_stars2;
+static sg_image image_sun;
+static sg_image image_medbay_used;
+static sg_image image_mystery;
+static sg_image image_explosion;
+static sg_image image_low_health;
+static sg_image image_mic_muted;
+static sg_image image_flag_available;
+static sg_image image_flag_taken;
+
+static int cur_editing_boxtype = -1;
+static int cur_editing_rotation = 0;
+
+// audio
+static bool muted = false;
+static ma_device microphone_device;
+static ma_device speaker_device;
+OpusEncoder *enc;
+OpusDecoder *dec;
+OpusBuffer packets_to_send = {0};
+OpusBuffer packets_to_play = {0};
+ma_mutex send_packets_mutex = {0};
+ma_mutex play_packets_mutex = {0};
+
+// server thread
+void *server_thread_handle = 0;
+ServerThreadInfo server_info = {0};
+
+static struct BoxInfo
+{
+ enum BoxType type;
+ const char *image_path;
+ sg_image image;
+ bool needs_tobe_unlocked;
+} boxes[] = {
+ // if added to here will show up in toolbar, is placeable
+ {
+ .type = BoxHullpiece,
+ .image_path = "loaded/hullpiece.png",
+ },
+ {
+ .type = BoxThruster,
+ .image_path = "loaded/thruster.png",
+ },
+ {
+ .type = BoxBattery,
+ .image_path = "loaded/battery.png",
+ },
+ {
+ .type = BoxCockpit,
+ .image_path = "loaded/cockpit.png",
+ },
+ {
+ .type = BoxMedbay,
+ .image_path = "loaded/medbay.png",
+ },
+ {
+ .type = BoxSolarPanel,
+ .image_path = "loaded/solarpanel.png",
+ },
+ {
+ .type = BoxExplosive,
+ .image_path = "loaded/explosive.png",
+ .needs_tobe_unlocked = true,
+ },
+};
+#define ARRLEN(arr) (sizeof(arr) / sizeof(*arr))
+
+static struct SquadMeta
+{
+ enum Squad squad;
+ float hue;
+ bool is_colorless;
+} squad_metas[] = {
+ {
+ .squad = SquadNone,
+ .is_colorless = true,
+ },
+ {
+ .squad = SquadRed,
+ .hue = 21.0f / 360.0f,
+ },
+ {
+ .squad = SquadGreen,
+ .hue = 111.0f / 360.0f,
+ },
+ {
+ .squad = SquadBlue,
+ .hue = 201.0f / 360.0f,
+ },
+ {
+ .squad = SquadPurple,
+ .hue = 291.0f / 360.0f,
+ },
+};
+
+struct SquadMeta squad_meta(enum Squad squad)
+{
+ for (int i = 0; i < ARRLEN(squad_metas); i++)
+ {
+ if (squad_metas[i].squad == squad)
+ return squad_metas[i];
+ }
+ Log("Could not find squad %d!\n", squad);
+ return (struct SquadMeta){0};
+}
+
+struct BoxInfo
+boxinfo(enum BoxType type)
+{
+ for (int i = 0; i < ARRLEN(boxes); i++)
+ {
+ if (boxes[i].type == type)
+ return boxes[i];
+ }
+ Log("No box info found for type %d\n", type);
+ return (struct BoxInfo){0};
+}
+
+static sg_image
+load_image(const char *path)
+{
+ sg_image to_return = sg_alloc_image();
+
+ int x = 0;
+ int y = 0;
+ int comp = 0;
+ const int desired_channels = 4;
+ stbi_set_flip_vertically_on_load(true);
+ stbi_uc *image_data = stbi_load(path, &x, &y, &comp, desired_channels);
+ if (!image_data)
+ {
+ fprintf(stderr, "Failed to load image: %s\n", stbi_failure_reason());
+ exit(-1);
+ }
+ sg_init_image(to_return,
+ &(sg_image_desc){.width = x,
+ .height = y,
+ .pixel_format = SG_PIXELFORMAT_RGBA8,
+ .min_filter = SG_FILTER_NEAREST,
+ .mag_filter = SG_FILTER_NEAREST,
+ .data.subimage[0][0] = {
+ .ptr = image_data,
+ .size = (size_t)(x * y * desired_channels),
+ }});
+
+ stbi_image_free(image_data);
+
+ return to_return;
+}
+
+void microphone_data_callback(ma_device *pDevice, void *pOutput, const void *pInput, ma_uint32 frameCount)
+{
+ assert(frameCount == VOIP_EXPECTED_FRAME_COUNT);
+ if (peer != NULL)
+ {
+ ma_mutex_lock(&send_packets_mutex);
+ OpusPacket *packet = push_packet(&packets_to_send);
+ if (packet != NULL)
+ {
+ opus_int16 muted_audio[VOIP_EXPECTED_FRAME_COUNT] = {0};
+ const opus_int16 *audio_buffer = (const opus_int16 *)pInput;
+ if (muted)
+ audio_buffer = muted_audio;
+ opus_int32 written = opus_encode(enc, audio_buffer, VOIP_EXPECTED_FRAME_COUNT, packet->data, VOIP_PACKET_MAX_SIZE);
+ packet->length = written;
+ }
+ ma_mutex_unlock(&send_packets_mutex);
+ }
+ (void)pOutput;
+}
+
+void speaker_data_callback(ma_device *pDevice, void *pOutput, const void *pInput, ma_uint32 frameCount)
+{
+ assert(frameCount == VOIP_EXPECTED_FRAME_COUNT);
+ ma_mutex_lock(&play_packets_mutex);
+ OpusPacket *cur_packet = pop_packet(&packets_to_play);
+ if (cur_packet != NULL && cur_packet->length > 0) // length of 0 means skipped packet
+ {
+ opus_decode(dec, cur_packet->data, cur_packet->length, (opus_int16 *)pOutput, frameCount, 0);
+ }
+ else
+ {
+ opus_decode(dec, NULL, 0, (opus_int16 *)pOutput, frameCount, 0); // I think opus makes it sound good if packets are skipped with null
+ }
+ ma_mutex_unlock(&play_packets_mutex);
+ (void)pInput;
+}
+
+static void
+init(void)
+{
+
+ // audio
+ {
+ // opus
+ {
+ int error;
+ enc = opus_encoder_create(VOIP_SAMPLE_RATE, 1, OPUS_APPLICATION_VOIP, &error);
+ assert(error == OPUS_OK);
+ dec = opus_decoder_create(VOIP_SAMPLE_RATE, 1, &error);
+ assert(error == OPUS_OK);
+ }
+
+ ma_device_config microphone_config = ma_device_config_init(ma_device_type_capture);
+
+ microphone_config.capture.format = ma_format_s16;
+ microphone_config.capture.channels = 1;
+ microphone_config.sampleRate = VOIP_SAMPLE_RATE;
+ microphone_config.dataCallback = microphone_data_callback;
+
+ ma_device_config speaker_config = ma_device_config_init(ma_device_type_playback);
+ speaker_config.playback.format = ma_format_s16;
+ speaker_config.playback.channels = 1;
+ speaker_config.sampleRate = VOIP_SAMPLE_RATE;
+ speaker_config.dataCallback = speaker_data_callback;
+
+ ma_result result;
+
+ result = ma_device_init(NULL, µphone_config, µphone_device);
+ if (result != MA_SUCCESS)
+ {
+ Log("Failed to initialize capture device.\n");
+ exit(-1);
+ }
+
+ result = ma_device_init(NULL, &speaker_config, &speaker_device);
+ if (result != MA_SUCCESS)
+ {
+ ma_device_uninit(µphone_device);
+ Log("Failed to init speaker\n");
+ exit(-1);
+ }
+
+ if (ma_mutex_init(&send_packets_mutex) != MA_SUCCESS)
+ Log("Failed to init send mutex\n");
+ if (ma_mutex_init(&play_packets_mutex) != MA_SUCCESS)
+ Log("Failed to init play mutex\n");
+
+ result = ma_device_start(µphone_device);
+ if (result != MA_SUCCESS)
+ {
+ ma_device_uninit(µphone_device);
+ Log("Failed to start device.\n");
+ exit(-1);
+ }
+
+ result = ma_device_start(&speaker_device);
+ if (result != MA_SUCCESS)
+ {
+ ma_device_uninit(µphone_device);
+ ma_device_uninit(&speaker_device);
+ Log("Failed to start speaker\n");
+ exit(-1);
+ }
+
+ Log("Initialized audio\n");
+ }
+
+ // @BeforeShip make all fprintf into logging to file, warning dialog grids on
+ // failure instead of exit(-1), replace the macros in sokol with this as well,
+ // like assert
+
+ Entity *entity_data = malloc(sizeof *entity_data * MAX_ENTITIES);
+ initialize(&gs, entity_data, sizeof *entity_data * MAX_ENTITIES);
+
+ sg_desc sgdesc = {.context = sapp_sgcontext()};
+ sg_setup(&sgdesc);
+ if (!sg_isvalid())
+ {
+ fprintf(stderr, "Failed to create Sokol GFX context!\n");
+ exit(-1);
+ }
+
+ sgp_desc sgpdesc = {0};
+ sgp_setup(&sgpdesc);
+ if (!sgp_is_valid())
+ {
+ fprintf(stderr,
+ "Failed to create Sokol GP context: %s\n",
+ sgp_get_error_message(sgp_get_last_error()));
+ exit(-1);
+ }
+
+ // shaders
+ {
+ // initialize shader
+ sgp_pipeline_desc pip_desc = {
+ .shader = *hueshift_program_shader_desc(sg_query_backend()),
+ .blend_mode = SGP_BLENDMODE_BLEND,
+ };
+ pip = sgp_make_pipeline(&pip_desc);
+ if (sg_query_pipeline_state(pip) != SG_RESOURCESTATE_VALID)
+ {
+ fprintf(stderr, "failed to make custom pipeline\n");
+ exit(-1);
+ }
+ }
+
+ // images loading
+ {
+ for (int i = 0; i < ARRLEN(boxes); i++)
+ {
+ boxes[i].image = load_image(boxes[i].image_path);
+ }
+ image_thrusterburn = load_image("loaded/thrusterburn.png");
+ image_itemframe = load_image("loaded/itemframe.png");
+ image_itemframe_selected = load_image("loaded/itemframe_selected.png");
+ image_player = load_image("loaded/player.png");
+ image_cockpit_used = load_image("loaded/cockpit_used.png");
+ image_stars = load_image("loaded/stars.png");
+ image_stars2 = load_image("loaded/stars2.png");
+ image_sun = load_image("loaded/sun.png");
+ image_medbay_used = load_image("loaded/medbay_used.png");
+ image_mystery = load_image("loaded/mystery.png");
+ image_explosion = load_image("loaded/explosion.png");
+ image_low_health = load_image("loaded/low_health.png");
+ image_mic_muted = load_image("loaded/mic_muted.png");
+ image_flag_available = load_image("loaded/flag_available.png");
+ image_flag_taken = load_image("loaded/flag_ripped.png");
+ }
+
+ // socket initialization
+ {
+ if (enet_initialize() != 0)
+ {
+ fprintf(stderr, "An error occurred while initializing ENet.\n");
+ exit(-1);
+ }
+ client = enet_host_create(NULL /* create a client host */,
+ 1 /* only allow 1 outgoing connection */,
+ 2 /* allow up 2 channels to be used, 0 and 1 */,
+ 0 /* assume any amount of incoming bandwidth */,
+ 0 /* assume any amount of outgoing bandwidth */);
+ if (client == NULL)
+ {
+ fprintf(
+ stderr,
+ "An error occurred while trying to create an ENet client host.\n");
+ exit(-1);
+ }
+ ENetAddress address;
+ ENetEvent event;
+
+ enet_address_set_host(&address, SERVER_ADDRESS);
+ address.port = SERVER_PORT;
+ peer = enet_host_connect(client, &address, 2, 0);
+ if (peer == NULL)
+ {
+ fprintf(stderr,
+ "No available peers for initiating an ENet connection.\n");
+ exit(-1);
+ }
+ // the timeout is the third parameter here
+ if (enet_host_service(client, &event, 5000) > 0 && event.type == ENET_EVENT_TYPE_CONNECT)
+ {
+ Log("Connected\n");
+ }
+ else
+ {
+ /* Either the 5 seconds are up or a disconnect event was */
+ /* received. Reset the peer in the event the 5 seconds */
+ /* had run out without any significant event. */
+ enet_peer_reset(peer);
+ fprintf(stderr, "Connection to server failed.");
+ exit(-1);
+ }
+ }
+}
+
+#define transform_scope DeferLoop(sgp_push_transform(), sgp_pop_transform())
+
+static void
+draw_color_rect_centered(V2 center, float size)
+{
+ float halfbox = size / 2.0f;
+
+ sgp_draw_filled_rect(center.x - halfbox, center.y - halfbox, size, size);
+}
+
+static void
+draw_texture_rectangle_centered(V2 center, V2 width_height)
+{
+ V2 halfsize = V2scale(width_height, 0.5f);
+ sgp_draw_textured_rect(center.x - halfsize.x,
+ center.y - halfsize.y,
+ width_height.x,
+ width_height.y);
+}
+
+static void
+draw_texture_centered(V2 center, float size)
+{
+ draw_texture_rectangle_centered(center, (V2){size, size});
+}
+
+static void
+draw_circle(V2 point, float radius)
+{
+#define POINTS 64
+ sgp_line lines[POINTS];
+ for (int i = 0; i < POINTS; i++)
+ {
+ float progress = (float)i / (float)POINTS;
+ float next_progress = (float)(i + 1) / (float)POINTS;
+ lines[i].a = (V2){.x = cosf(progress * 2.0f * PI) * radius,
+ .y = sinf(progress * 2.0f * PI) * radius};
+ lines[i].b = (V2){.x = cosf(next_progress * 2.0f * PI) * radius,
+ .y = sinf(next_progress * 2.0f * PI) * radius};
+ lines[i].a = V2add(lines[i].a, point);
+ lines[i].b = V2add(lines[i].b, point);
+ }
+ sgp_draw_lines(lines, POINTS);
+}
+
+static Entity *
+myentity()
+{
+ if (myplayer == -1)
+ return NULL;
+ Entity *to_return = get_entity(&gs, gs.players[myplayer].entity);
+ if (to_return != NULL)
+ assert(to_return->is_player);
+ return to_return;
+}
+
+bool can_build(int i)
+{
+ bool allow_building = true;
+ if (boxinfo((enum BoxType)i).needs_tobe_unlocked)
+ {
+ allow_building = gs.players[myplayer].unlocked_bombs;
+ }
+ return allow_building;
+}
+
+void attempt_to_build(int i)
+{
+ if (can_build(i))
+ cur_editing_boxtype = i;
+}
+
+static void
+ui(bool draw, float dt, float width, float height)
+{
+ static float cur_opacity = 1.0f;
+ cur_opacity = lerp(cur_opacity, myentity() != NULL ? 1.0f : 0.0f, dt * 5.0f);
+ if (cur_opacity <= 0.01f)
+ {
+ return;
+ }
+
+ if (draw)
+ sgp_push_transform();
+
+ // draw flags
+ static V2 flag_pos[SquadLast] = {0};
+ static float flag_rot[SquadLast] = {0};
+ static float flag_scaling_increase[SquadLast] = {0};
+ static bool choosing_flags = false;
+ const float flag_padding = 70.0f;
+ const float center_panel_height = 200.0f;
+ static float center_panel_width = 0.0f;
+ const float target_center_panel_width = ((SquadLast) + 2) * flag_padding;
+#define FLAG_ITER(i) for (int i = 0; i < SquadLast; i++)
+ {
+ FLAG_ITER(i)
+ {
+ V2 target_pos = {0};
+ float target_rot = 0.0f;
+ float flag_progress = (float)i / (float)(SquadLast - 1.0f);
+ if (choosing_flags)
+ {
+ target_pos.x = width / 2.0f + lerp(-center_panel_width / 2.0f + flag_padding, center_panel_width / 2.0f - flag_padding, flag_progress);
+ target_pos.y = height * 0.5f;
+ target_rot = 0.0f;
+ }
+ else
+ {
+ target_pos.x = 25.0f;
+ target_pos.y = 200.0f;
+ target_rot = lerp(-PI / 3.0f, PI / 3.0f, flag_progress) + PI / 2.0f;
+ }
+ flag_pos[i] = V2lerp(flag_pos[i], target_pos, dt * 5.0f);
+ flag_rot[i] = lerp_angle(flag_rot[i], target_rot, dt * 5.0f);
+ }
+
+ center_panel_width = lerp(center_panel_width, choosing_flags ? target_center_panel_width : 0.0f, 6.0f * dt);
+
+ // center panel
+ {
+ AABB panel_rect = (AABB){
+ .x = width / 2.0f - center_panel_width / 2.0f,
+ .y = height / 2.0f - center_panel_height / 2.0f,
+ .width = center_panel_width,
+ .height = center_panel_height,
+ };
+ if (choosing_flags && mouse_pressed && !has_point(panel_rect, mouse_pos))
+ {
+ mouse_pressed = false;
+ choosing_flags = false;
+ }
+ if (draw)
+ {
+ sgp_set_color(0.7f, 0.7f, 0.7f, 0.5f);
+ sgp_draw_filled_rect(panel_rect.x, panel_rect.y, panel_rect.width, panel_rect.height);
+ }
+ }
+
+ FLAG_ITER(i)
+ {
+ enum Squad this_squad = (enum Squad)i;
+ bool this_squad_available = true;
+ if (this_squad != SquadNone)
+ PLAYERS_ITER()
+ for (int player_i = 0; player_i < MAX_PLAYERS; player_i++)
+ {
+ if (gs.players[player_i].connected && gs.players[player_i].squad == this_squad)
+ {
+ this_squad_available = false;
+ break;
+ }
+ }
+
+ float size = 128.0f;
+ bool hovering = V2dist(mouse_pos, flag_pos[i]) < size * 0.25f && this_squad_available;
+
+ if (!choosing_flags && hovering && mouse_pressed)
+ {
+ choosing_flags = true;
+ mouse_pressed = false;
+ }
+
+ if (this_squad_available && choosing_flags && hovering && mouse_pressed)
+ {
+ take_over_squad = this_squad;
+ mouse_pressed = false;
+ }
+
+ flag_scaling_increase[i] = lerp(flag_scaling_increase[i], hovering ? 0.2f : 0.0f, dt * 9.0f);
+
+ size *= 1.0f + flag_scaling_increase[i];
+
+ if (draw)
+ {
+ transform_scope
+ {
+ if (this_squad_available)
+ {
+ sgp_set_image(0, image_flag_available);
+ }
+ else
+ {
+ sgp_set_image(0, image_flag_taken);
+ }
+
+ sgp_set_pipeline(pip);
+ struct SquadMeta meta = squad_meta(this_squad);
+ hueshift_uniforms_t uniform = {0};
+ uniform.is_colorless = meta.is_colorless;
+ uniform.target_hue = meta.hue;
+ sgp_set_uniform(&uniform, sizeof(hueshift_uniforms_t));
+
+ sgp_rotate_at(flag_rot[i], flag_pos[i].x, flag_pos[i].y);
+ sgp_scale_at(1.0f, -1.0f, flag_pos[i].x, flag_pos[i].y); // images upside down by default :(
+ draw_texture_centered(flag_pos[i], size);
+
+ sgp_reset_image(0);
+ sgp_reset_pipeline();
+ }
+ }
+ }
+ }
+#undef FLAG_ITER
+
+ // draw spice bar
+ if (draw)
+ {
+ static float damage = 0.5f;
+
+ if (myentity() != NULL)
+ {
+ damage = myentity()->damage;
+ }
+
+ sgp_set_color(0.5f, 0.5f, 0.5f, cur_opacity);
+ float margin = width * 0.2f;
+ float bar_width = width - margin * 2.0f;
+ float y = height - 150.0f;
+ sgp_draw_filled_rect(margin, y, bar_width, 30.0f);
+ sgp_set_color(1.0f, 1.0f, 1.0f, cur_opacity);
+ sgp_draw_filled_rect(
+ margin, y, bar_width * (1.0f - damage), 30.0f);
+ }
+
+ // draw muted
+ static float muted_opacity = 0.0f;
+ if (draw)
+ {
+ muted_opacity = lerp(muted_opacity, muted ? 1.0f : 0.0f, 8.0f * dt);
+ sgp_set_color(1.0f, 1.0f, 1.0f, muted_opacity);
+ float size_x = 150.0f;
+ float size_y = 150.0f;
+ sgp_set_image(0, image_mic_muted);
+
+ float x = width - size_x - 40.0f;
+ float y = height - size_y - 40.0f;
+ transform_scope
+ {
+ sgp_scale_at(1.0f, -1.0f, x + size_x / 2.0f, y + size_y / 2.0f);
+ sgp_draw_textured_rect(x, y, size_x, size_y);
+ sgp_reset_image(0);
+ }
+ }
+
+ // draw item toolbar
+ {
+ float itemframe_width = (float)sg_query_image_info(image_itemframe).width * 2.0f;
+ float itemframe_height = (float)sg_query_image_info(image_itemframe).height * 2.0f;
+ float total_width = itemframe_width * ARRLEN(boxes);
+ float item_width = itemframe_width * 0.75f;
+ float item_height = itemframe_height * 0.75f;
+ float item_offset_x = (itemframe_width - item_width) / 2.0f;
+ float item_offset_y = (itemframe_height - item_height) / 2.0f;
+
+ float x = width / 2.0f - total_width / 2.0f;
+ float y = height - itemframe_height * 1.5f;
+ for (int i = 0; i < ARRLEN(boxes); i++)
+ {
+ if (has_point(
+ (AABB){
+ .x = x,
+ .y = y,
+ .width = itemframe_width,
+ .height = itemframe_height,
+ },
+ mouse_pos) &&
+ mouse_pressed)
+ {
+ // "handle" mouse pressed
+ attempt_to_build(i);
+ mouse_pressed = false;
+ }
+
+ if (draw)
+ {
+ sgp_set_color(1.0f, 1.0f, 1.0f, cur_opacity);
+ if (cur_editing_boxtype == i)
+ {
+ sgp_set_image(0, image_itemframe_selected);
+ }
+ else
+ {
+ sgp_set_image(0, image_itemframe);
+ }
+ sgp_draw_textured_rect(x, y, itemframe_width, itemframe_height);
+ struct BoxInfo info = boxinfo((enum BoxType)i);
+ if (can_build(i))
+ {
+ sgp_set_image(0, info.image);
+ }
+ else
+ {
+ sgp_set_image(0, image_mystery);
+ }
+ transform_scope
+ {
+ float item_x = x + item_offset_x;
+ float item_y = y + item_offset_y;
+ sgp_scale_at(1.0f, -1.0f, item_x + item_width / 2.0f, item_y + item_height / 2.0f);
+ // sgp_scale(1.0f, -1.0f);
+ sgp_draw_textured_rect(item_x, item_y, item_width, item_height);
+ }
+ sgp_reset_image(0);
+ }
+ x += itemframe_width;
+ }
+ }
+
+ if (draw)
+ sgp_pop_transform();
+}
+
+static void draw_dots(V2 camera_pos, float gap)
+{
+ sgp_set_color(1.0f, 1.0f, 1.0f, 1.0f);
+ const int num = 100;
+ for (int x = -num; x < num; x++)
+ {
+ for (int y = -num; y < num; y++)
+ {
+ V2 star = (V2){(float)x * gap, (float)y * gap};
+ if (V2lengthsqr(V2sub(star, camera_pos)) > VISION_RADIUS * VISION_RADIUS)
+ continue;
+
+ star.x += hash11(star.x * 100.0f + star.y * 67.0f) * gap;
+ star.y += hash11(star.y * 93.0f + star.x * 53.0f) * gap;
+ sgp_draw_point(star.x, star.y);
+ }
+ }
+}
+
+static void
+frame(void)
+{
+ float width = (float)sapp_width(), height = (float)sapp_height();
+ float ratio = width / height;
+ double time = sapp_frame_count() * sapp_frame_duration();
+ float dt = (float)sapp_frame_duration();
+
+ // pressed input management
+ {
+ for (int i = 0; i < SAPP_KEYCODE_MENU; i++)
+ {
+ if (keypressed[i].frame < sapp_frame_count())
+ {
+ keypressed[i].pressed = false;
+ }
+ }
+ if (mouse_pressed_frame < sapp_frame_count())
+ {
+ mouse_pressed = false;
+ }
+ }
+
+ // networking
+ {
+ ENetEvent event;
+ while (true)
+ {
+ int enet_status = enet_host_service(client, &event, 0);
+ if (enet_status > 0)
+ {
+ switch (event.type)
+ {
+ case ENET_EVENT_TYPE_CONNECT:
+ {
+ Log("New client from host %x\n", event.peer->address.host);
+ break;
+ }
+
+ case ENET_EVENT_TYPE_RECEIVE:
+ {
+ char *decompressed = malloc(sizeof *decompressed * MAX_SERVER_TO_CLIENT); // @Robust no malloc
+ size_t decompressed_max_len = MAX_SERVER_TO_CLIENT;
+ assert(LZO1X_MEM_DECOMPRESS == 0);
+
+ ma_mutex_lock(&play_packets_mutex);
+ ServerToClient msg = (ServerToClient){
+ .cur_gs = &gs,
+ .playback_buffer = &packets_to_play,
+ };
+ int return_value = lzo1x_decompress_safe(event.packet->data, event.packet->dataLength, decompressed, &decompressed_max_len, NULL);
+ if (return_value == LZO_E_OK)
+ {
+ server_to_client_deserialize(&msg, decompressed, decompressed_max_len, false);
+ myplayer = msg.your_player;
+ }
+ else
+ {
+ Log("Couldn't decompress gamestate packet, error code %d from lzo\n", return_value);
+ }
+ ma_mutex_unlock(&play_packets_mutex);
+ free(decompressed);
+ enet_packet_destroy(event.packet);
+ break;
+ }
+
+ case ENET_EVENT_TYPE_DISCONNECT:
+ {
+ fprintf(stderr, "Disconnected from server\n");
+ exit(-1);
+ break;
+ }
+ }
+ }
+ else if (enet_status == 0)
+ {
+ break;
+ }
+ else if (enet_status < 0)
+ {
+ fprintf(stderr, "Error receiving enet events: %d\n", enet_status);
+ break;
+ }
+ }
+ }
+
+ // gameplay
+ ui(false, dt, width, height); // handle events
+ V2 build_target_pos = {0};
+ float build_target_rotation = 0.0f;
+ static V2 camera_pos = {
+ 0}; // keeps camera at same position after player death
+ V2 world_mouse_pos = mouse_pos; // processed later in scope
+ struct BuildPreviewInfo
+ {
+ V2 grid_pos;
+ float grid_rotation;
+ } build_preview = {0};
+ V2 hand_pos = {0}; // in local space of grid when hovering over a grid
+ bool hand_at_arms_length = false;
+ {
+ // interpolate zoom
+ zoom = lerp(zoom, zoom_target, dt * 12.0f);
+
+ // calculate world position and camera
+ {
+ if (myentity() != NULL)
+ {
+ camera_pos = entity_pos(myentity());
+ }
+ world_mouse_pos = V2sub(world_mouse_pos, (V2){.x = width / 2.0f, .y = height / 2.0f});
+ world_mouse_pos.x /= zoom;
+ world_mouse_pos.y /= -zoom;
+ world_mouse_pos = V2add(world_mouse_pos, (V2){.x = camera_pos.x, .y = camera_pos.y});
+ }
+
+ // calculate build preview stuff
+ EntityID grid_to_build_on = (EntityID){0};
+ V2 possibly_local_hand_pos = (V2){0};
+ if (myentity() != NULL)
+ {
+ hand_pos = V2sub(world_mouse_pos, entity_pos(myentity()));
+ float hand_len = V2length(hand_pos);
+ if (hand_len > MAX_HAND_REACH)
+ {
+ hand_at_arms_length = true;
+ hand_len = MAX_HAND_REACH;
+ }
+ else
+ {
+ hand_at_arms_length = false;
+ }
+ hand_pos = V2scale(V2normalize(hand_pos), hand_len);
+ hand_pos = V2add(hand_pos, entity_pos(myentity()));
+
+ possibly_local_hand_pos = V2sub(hand_pos, entity_pos(myentity()));
+ Entity *placing_grid = closest_to_point_in_radius(&gs, hand_pos, BUILD_BOX_SNAP_DIST_TO_SHIP);
+ if (placing_grid == NULL)
+ {
+ build_preview = (struct BuildPreviewInfo){
+ .grid_pos = hand_pos,
+ .grid_rotation = 0.0f,
+ };
+ }
+ else
+ {
+ grid_to_build_on = get_id(&gs, placing_grid);
+ hand_pos = grid_snapped_box_pos(placing_grid, hand_pos);
+ possibly_local_hand_pos = grid_world_to_local(placing_grid, hand_pos);
+ build_preview = (struct BuildPreviewInfo){
+ .grid_pos = entity_pos(placing_grid),
+ .grid_rotation = entity_rotation(placing_grid),
+ };
+ }
+ }
+
+ // Create and send input packet
+ {
+
+ static size_t last_frame_id = 0;
+ InputFrame cur_input_frame = {0};
+ cur_input_frame.id = last_frame_id;
+ V2 input = (V2){
+ .x = (float)keydown[SAPP_KEYCODE_D] - (float)keydown[SAPP_KEYCODE_A],
+ .y = (float)keydown[SAPP_KEYCODE_W] - (float)keydown[SAPP_KEYCODE_S],
+ };
+ if (V2length(input) > 0.0)
+ input = V2normalize(input);
+ cur_input_frame.movement = input;
+ cur_input_frame.seat_action = keypressed[SAPP_KEYCODE_G].pressed;
+ cur_input_frame.grid_hand_pos_local_to = grid_to_build_on;
+ cur_input_frame.hand_pos = possibly_local_hand_pos;
+ cur_input_frame.take_over_squad = take_over_squad;
+
+ if (mouse_pressed && cur_editing_boxtype != -1)
+ {
+ cur_input_frame.dobuild = mouse_pressed;
+ cur_input_frame.build_type = cur_editing_boxtype;
+ cur_input_frame.build_rotation = cur_editing_rotation;
+ }
+
+ InputFrame latest = client_to_server.inputs[0];
+ // @Robust split this into separate lines and be very careful about testing for inequality
+
+ bool input_differs = false;
+ input_differs = input_differs || !V2equal(cur_input_frame.movement, latest.movement, 0.01f);
+
+ input_differs = input_differs || cur_input_frame.seat_action != latest.seat_action;
+ input_differs = input_differs || !entityids_same(cur_input_frame.seat_to_inhabit, latest.seat_to_inhabit);
+ input_differs = input_differs || !V2equal(cur_input_frame.hand_pos, latest.hand_pos, 0.01f);
+
+ input_differs = input_differs || cur_input_frame.dobuild != latest.dobuild;
+ input_differs = input_differs || cur_input_frame.build_type != latest.build_type;
+ input_differs = input_differs || cur_input_frame.build_rotation != latest.build_rotation;
+ input_differs = input_differs || !entityids_same(cur_input_frame.grid_hand_pos_local_to, latest.grid_hand_pos_local_to);
+
+ input_differs = input_differs || cur_input_frame.take_over_squad != latest.take_over_squad;
+
+ if (input_differs)
+ {
+ InputFrame last_frame = client_to_server.inputs[0];
+ for (int i = 0; i < INPUT_BUFFER - 1; i++)
+ {
+ InputFrame last_last_frame = last_frame;
+ last_frame = client_to_server.inputs[i + 1];
+ client_to_server.inputs[i + 1] = last_last_frame;
+
+ // these references, in old input frames, may have been deleted by the time we
+ // want to send them.
+ client_to_server.inputs[i + 1].seat_to_inhabit = cur_input_frame.seat_to_inhabit;
+ client_to_server.inputs[i + 1].grid_hand_pos_local_to = cur_input_frame.grid_hand_pos_local_to;
+ }
+ cur_input_frame.tick = tick(&gs);
+ client_to_server.inputs[0] = cur_input_frame;
+ last_frame_id += 1;
+ }
+
+ static int64_t last_sent_input_time = 0;
+ if (stm_sec(stm_diff(stm_now(), last_sent_input_time)) > TIME_BETWEEN_INPUT_PACKETS)
+ {
+ ma_mutex_lock(&send_packets_mutex);
+ client_to_server.mic_data = &packets_to_send;
+ char serialized[MAX_CLIENT_TO_SERVER] = {0};
+ size_t out_len = 0;
+ if (client_to_server_serialize(&gs, &client_to_server, serialized, &out_len, MAX_CLIENT_TO_SERVER))
+ {
+ ENetPacket *packet = enet_packet_create((void *)serialized,
+ out_len,
+ ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT);
+ enet_peer_send(peer, 0, packet); // @Robust error check this
+ last_sent_input_time = stm_now();
+ }
+ else
+ {
+ Log("Failed to serialize client to server!\n");
+ }
+ client_to_server.mic_data = NULL;
+ ma_mutex_unlock(&send_packets_mutex);
+ }
+ }
+
+ // @BeforeShip client side prediction and rollback to previous server
+ // authoritative state, then replay inputs no need to store copies of game
+ // state, just player input frame to frame. Then know how many frames ago
+ // the server game state arrived, it's that easy! process(&gs,
+ // (float)sapp_frame_duration());
+ }
+
+ // drawing
+ {
+ sgp_begin((int)width, (int)height);
+ sgp_viewport(0, 0, (int)width, (int)height);
+ sgp_project(0.0f, width, 0.0f, height);
+ sgp_set_blend_mode(SGP_BLENDMODE_BLEND);
+
+ // Draw background color
+ set_color(colhexcode(0x000000));
+ // sgp_set_color(0.1f, 0.1f, 0.1f, 1.0f);
+ sgp_clear();
+
+ // sokol drawing library draw in world space
+ // world space coordinates are +Y up, -Y down. Like normal cartesian coords
+ transform_scope
+ {
+ sgp_translate(width / 2, height / 2);
+ sgp_scale_at(zoom, -zoom, 0.0f, 0.0f);
+
+ // parllax layers, just the zooming, but not 100% of the camera panning
+#if 1 // space background
+ transform_scope
+ {
+ V2 scaled_camera_pos = V2scale(camera_pos, 0.05f); // this is how strong/weak the parallax is
+ sgp_translate(-scaled_camera_pos.x, -scaled_camera_pos.y);
+ set_color(WHITE);
+ sgp_set_image(0, image_stars);
+ float stars_height_over_width = (float)sg_query_image_info(image_stars).height / (float)sg_query_image_info(image_stars).width;
+ const float stars_width = 35.0f;
+ float stars_height = stars_width * stars_height_over_width;
+ sgp_draw_textured_rect(-stars_width / 2.0f, -stars_height / 2.0f, stars_width, stars_height);
+ // sgp_draw_textured_rect(0, 0, stars_width, stars_height);
+ sgp_reset_image(0);
+ }
+ transform_scope
+ {
+ V2 scaled_camera_pos = V2scale(camera_pos, 0.1f); // this is how strong/weak the parallax is
+ sgp_translate(-scaled_camera_pos.x, -scaled_camera_pos.y);
+ set_color(WHITE);
+ sgp_set_image(0, image_stars2);
+ float stars_height_over_width = (float)sg_query_image_info(image_stars).height / (float)sg_query_image_info(image_stars).width;
+ const float stars_width = 35.0f;
+ float stars_height = stars_width * stars_height_over_width;
+ sgp_draw_textured_rect(-stars_width / 2.0f, -stars_height / 2.0f, stars_width, stars_height);
+ // sgp_draw_textured_rect(0, 0, stars_width, stars_height);
+ sgp_reset_image(0);
+ }
+#endif
+
+#if 1 // parallaxed dots
+ transform_scope
+ {
+ V2 scaled_camera_pos = V2scale(camera_pos, 0.25f);
+ sgp_translate(-scaled_camera_pos.x, -scaled_camera_pos.y);
+ set_color(WHITE);
+ draw_dots(scaled_camera_pos, 3.0f);
+ }
+ transform_scope
+ {
+ V2 scaled_camera_pos = V2scale(camera_pos, 0.5f);
+ sgp_translate(-scaled_camera_pos.x, -scaled_camera_pos.y);
+ set_color(WHITE);
+ draw_dots(scaled_camera_pos, 2.0f);
+ }
+#endif
+
+ // camera go to player
+ sgp_translate(-camera_pos.x, -camera_pos.y);
+
+ draw_dots(camera_pos, 1.5f); // in plane dots
+
+ // hand reached limit circle
+ if (myentity() != NULL)
+ {
+ static float hand_reach_alpha = 1.0f;
+ hand_reach_alpha = lerp(hand_reach_alpha, hand_at_arms_length ? 1.0f : 0.0f, dt * 5.0f);
+ sgp_set_color(1.0f, 1.0f, 1.0f, hand_reach_alpha);
+ draw_circle(entity_pos(myentity()), MAX_HAND_REACH);
+ }
+
+ // vision circle, what player can see
+ if (myentity() != NULL)
+ {
+ set_color(colhexcode(0x4685e3));
+ draw_circle(entity_pos(myentity()), VISION_RADIUS);
+ }
+
+ float halfbox = BOX_SIZE / 2.0f;
+
+ // mouse frozen, debugging tool
+ if (mouse_frozen)
+ {
+ sgp_set_color(1.0f, 0.0f, 0.0f, 0.5f);
+ sgp_draw_filled_rect(world_mouse_pos.x, world_mouse_pos.y, 0.1f, 0.1f);
+ }
+
+ // building preview
+ if (cur_editing_boxtype != -1)
+ {
+ sgp_set_color(0.5f, 0.5f, 0.5f, (sinf((float)time * 9.0f) + 1.0f) / 3.0f + 0.2f);
+
+ transform_scope
+ {
+ sgp_set_image(0, boxinfo(cur_editing_boxtype).image);
+ sgp_rotate_at(build_preview.grid_rotation + rotangle(cur_editing_rotation),
+ hand_pos.x,
+ hand_pos.y);
+ draw_texture_centered(hand_pos, BOX_SIZE);
+ // drawbox(hand_pos, build_preview.grid_rotation, 0.0f,
+ // cur_editing_boxtype, cur_editing_rotation);
+ sgp_reset_image(0);
+ }
+ }
+
+ static float player_scaling = 1.0f;
+ player_scaling = lerp(player_scaling, zoom < 6.5f ? 100.0f : 1.0f, dt * 7.0f);
+ for (size_t i = 0; i < gs.cur_next_entity; i++)
+ {
+ Entity *e = &gs.entities[i];
+ if (!e->exists)
+ continue;
+ // draw grid
+ if (e->is_grid)
+ {
+ Entity *g = e;
+ BOXES_ITER(&gs, b, g)
+ {
+ if (b->is_explosion_unlock)
+ {
+ set_color(colhexcode(0xfcba03));
+ draw_circle(entity_pos(b), GOLD_UNLOCK_RADIUS);
+ }
+ sgp_set_color(1.0f, 1.0f, 1.0f, 1.0f);
+// debug draw force vectors for thrusters
+#if 0
+ {
+ if (b->type == BoxThruster)
+ {
+ dbg_rect(entity_pos(b));
+ dbg_line(entity_pos(b), V2add(entity_pos(b), V2scale(thruster_force(b), -1.0f)));
+ }
+ }
+#endif
+ if (b->box_type == BoxBattery)
+ {
+ float cur_alpha = sgp_get_color().a;
+ Color from = WHITE;
+ Color to = colhex(255, 0, 0);
+ Color result = Collerp(from, to, b->energy_used / BATTERY_CAPACITY);
+ sgp_set_color(result.r, result.g, result.b, cur_alpha);
+ }
+ transform_scope
+ {
+ sgp_rotate_at(entity_rotation(g) + rotangle(b->compass_rotation),
+ entity_pos(b).x,
+ entity_pos(b).y);
+
+ if (b->box_type == BoxThruster)
+ {
+ transform_scope
+ {
+ sgp_set_color(1.0f, 1.0f, 1.0f, 1.0f);
+ sgp_set_image(0, image_thrusterburn);
+ // float scaling = 1.0 + (hash11(time*3.0)/2.0)*lerp(0.0,
+ // 0.07, b->thrust); printf("%f\n", b->thrust);
+ float scaling = 0.95f + lerp(0.0f, 0.3f, b->thrust);
+ // float scaling = 1.1;
+ // sgp_translate(-(scaling*BOX_SIZE - BOX_SIZE), 0.0);
+ // sgp_scale(scaling, 1.0);
+ sgp_scale_at(scaling, 1.0f, entity_pos(b).x, entity_pos(b).y);
+ draw_texture_centered(entity_pos(b), BOX_SIZE);
+ sgp_reset_image(0);
+ }
+ }
+ sg_image img = boxinfo(b->box_type).image;
+ if (b->box_type == BoxCockpit)
+ {
+ if (get_entity(&gs, b->player_who_is_inside_of_me) != NULL)
+ img = image_cockpit_used;
+ }
+ if (b->box_type == BoxMedbay)
+ {
+ if (get_entity(&gs, b->player_who_is_inside_of_me) != NULL)
+ img = image_medbay_used;
+ }
+ sgp_set_image(0, img);
+ if (b->indestructible)
+ {
+ sgp_set_color(0.2f, 0.2f, 0.2f, 1.0f);
+ }
+ draw_texture_centered(entity_pos(b), BOX_SIZE);
+ sgp_reset_image(0);
+
+ if (b->box_type == BoxSolarPanel)
+ {
+ Color to_set = colhexcode(0xeb9834);
+ to_set.a = b->sun_amount * 0.5f;
+ set_color(to_set);
+ draw_color_rect_centered(entity_pos(b), BOX_SIZE);
+ }
+
+ sgp_set_color(0.5f, 0.1f, 0.1f, b->damage);
+ draw_color_rect_centered(entity_pos(b), BOX_SIZE);
+ }
+ }
+
+ // draw the velocity
+#if 0
+ sgp_set_color(1.0f, 0.0f, 0.0f, 1.0f);
+ V2 vel = grid_vel(g);
+ V2 to = V2add(grid_com(g), vel);
+ sgp_draw_line(grid_com(g).x, grid_com(g).y, to.x, to.y);
+#endif
+ }
+
+ // draw player
+ if (e->is_player && get_entity(&gs, e->currently_inside_of_box) == NULL)
+ {
+ transform_scope
+ {
+ sgp_rotate_at(entity_rotation(e), entity_pos(e).x, entity_pos(e).y);
+ sgp_set_color(1.0f, 1.0f, 1.0f, 1.0f);
+
+ sgp_set_pipeline(pip);
+ struct SquadMeta meta = squad_meta(e->presenting_squad);
+ hueshift_uniforms_t uniform = {0};
+ uniform.is_colorless = meta.is_colorless;
+ uniform.target_hue = meta.hue;
+ sgp_set_uniform(&uniform, sizeof(hueshift_uniforms_t));
+ sgp_set_image(0, image_player);
+ draw_texture_rectangle_centered(entity_pos(e), V2scale(PLAYER_SIZE, player_scaling));
+ sgp_reset_image(0);
+ sgp_reset_pipeline();
+ }
+ }
+ if (e->is_explosion)
+ {
+ sgp_set_image(0, image_explosion);
+ sgp_set_color(1.0f, 1.0f, 1.0f, 1.0f - (e->explosion_progresss / EXPLOSION_TIME));
+ draw_texture_centered(e->explosion_pos, EXPLOSION_RADIUS * 2.0f);
+ sgp_reset_image(0);
+ }
+ }
+
+ // gold target
+ set_color(GOLD);
+ sgp_draw_filled_rect(gs.goldpos.x, gs.goldpos.y, 0.1f, 0.1f);
+
+ // the SUN
+ transform_scope
+ {
+ sgp_translate(SUN_POS.x, SUN_POS.y);
+ set_color(WHITE);
+ sgp_set_image(0, image_sun);
+ draw_texture_centered((V2){0}, SUN_RADIUS * 2.0f);
+ sgp_reset_image(0);
+
+ // sun DEATH RADIUS
+ set_color(RED);
+ draw_circle((V2){0}, INSTANT_DEATH_DISTANCE_FROM_SUN);
+ }
+
+ sgp_set_color(1.0f, 1.0f, 1.0f, 1.0f);
+ dbg_drawall();
+ } // world space transform end
+
+ // low health
+ if (myentity() != NULL)
+ {
+ sgp_set_color(1.0f, 1.0f, 1.0f, myentity()->damage);
+ sgp_set_image(0, image_low_health);
+ draw_texture_rectangle_centered((V2){width / 2.0f, height / 2.0f}, (V2){width, height});
+ sgp_reset_image(0);
+ }
+
+ // UI drawn in screen space
+ ui(true, dt, width, height);
+ }
+
+ sg_pass_action pass_action = {0};
+ sg_begin_default_pass(&pass_action, (int)width, (int)height);
+ sgp_flush();
+ sgp_end();
+ sg_end_pass();
+ sg_commit();
+}
+
+void cleanup(void)
+{
+ sg_destroy_pipeline(pip);
+
+ ma_mutex_lock(&server_info.info_mutex);
+ server_info.should_quit = true;
+ ma_mutex_unlock(&server_info.info_mutex);
+ WaitForSingleObject(server_thread_handle, INFINITE);
+
+ ma_mutex_uninit(&send_packets_mutex);
+ ma_mutex_uninit(&play_packets_mutex);
+
+ ma_device_uninit(µphone_device);
+ ma_device_uninit(&speaker_device);
+
+ opus_encoder_destroy(enc);
+ opus_decoder_destroy(dec);
+
+ destroy(&gs);
+ free(gs.entities);
+ sgp_shutdown();
+ sg_shutdown();
+ enet_deinitialize();
+
+ ma_mutex_uninit(&server_info.info_mutex);
+}
+
+void event(const sapp_event *e)
+{
+ switch (e->type)
+ {
+ case SAPP_EVENTTYPE_KEY_DOWN:
+#ifdef DEBUG_TOOLS
+ if (e->key_code == SAPP_KEYCODE_T)
+ {
+ mouse_frozen = !mouse_frozen;
+ }
+#endif
+ if (e->key_code == SAPP_KEYCODE_R)
+ {
+ cur_editing_rotation += 1;
+ cur_editing_rotation %= RotationLast;
+ }
+ if (e->key_code == SAPP_KEYCODE_M)
+ {
+ muted = !muted;
+ }
+ if (e->key_code == SAPP_KEYCODE_F11)
+ {
+ fullscreened = !fullscreened;
+ sapp_toggle_fullscreen();
+ }
+ if (e->key_code == SAPP_KEYCODE_ESCAPE && fullscreened)
+ {
+ sapp_toggle_fullscreen();
+ fullscreened = false;
+ }
+ int key_num = e->key_code - SAPP_KEYCODE_0;
+ int target_box = key_num - 1;
+ if (target_box < BoxLast && target_box >= 0)
+ {
+ attempt_to_build(target_box);
+ }
+
+ if (!mouse_frozen)
+ {
+ keydown[e->key_code] = true;
+ if (keypressed[e->key_code].frame == 0)
+ {
+ keypressed[e->key_code].pressed = true;
+ keypressed[e->key_code].frame = e->frame_count;
+ }
+ }
+ break;
+ case SAPP_EVENTTYPE_KEY_UP:
+ if (!mouse_frozen)
+ {
+ keydown[e->key_code] = false;
+ keypressed[e->key_code].pressed = false;
+
+ keypressed[e->key_code].frame = 0;
+ }
+ break;
+ case SAPP_EVENTTYPE_MOUSE_SCROLL:
+ zoom_target *= 1.0f + (e->scroll_y / 4.0f) * 0.1f;
+ zoom_target = clamp(zoom_target, 0.5f, 900.0f);
+ break;
+ case SAPP_EVENTTYPE_MOUSE_DOWN:
+ if (e->mouse_button == SAPP_MOUSEBUTTON_LEFT)
+ {
+ mouse_pressed = true;
+ mouse_pressed_frame = e->frame_count;
+ }
+ if (e->mouse_button == SAPP_MOUSEBUTTON_RIGHT)
+ {
+ right_mouse_down = true;
+ }
+ break;
+ case SAPP_EVENTTYPE_MOUSE_UP:
+ if (e->mouse_button == SAPP_MOUSEBUTTON_LEFT)
+ {
+ mouse_pressed = false;
+ mouse_pressed_frame = 0;
+ }
+ if (e->mouse_button == SAPP_MOUSEBUTTON_RIGHT)
+ {
+ right_mouse_down = false;
+ }
+ break;
+ case SAPP_EVENTTYPE_MOUSE_MOVE:
+ if (!mouse_frozen)
+ {
+ mouse_pos = (V2){.x = e->mouse_x, .y = e->mouse_y};
+ }
+ if (right_mouse_down)
+ {
+ funval += e->mouse_dx;
+ Log("Funval %f\n", funval);
+ }
+ break;
+ }
+}
+
+sapp_desc
+sokol_main(int argc, char *argv[])
+{
+ bool hosting = false;
+ stm_setup();
+ ma_mutex_init(&server_info.info_mutex);
+ server_info.world_save = "debug_world.bin";
+ 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,
+ .cleanup_cb = cleanup,
+ .width = 640,
+ .height = 480,
+ .gl_force_gles2 = true,
+ .window_title = hosting ? "Flight Hosting" : "Flight Not Hosting",
+ .icon.sokol_default = true,
+ .event_cb = event,
+ .win32_console_attach = true,
+ .sample_count = 4, // anti aliasing
+ };
+}
\ No newline at end of file
diff --git a/gamestate.c b/gamestate.c
index 7341e89..58136db 100644
--- a/gamestate.c
+++ b/gamestate.c
@@ -770,6 +770,7 @@ enum GameVersion
VRemovedTest,
VChangedVectorSerializing,
VAddedLastUsedMedbay,
+ VAddedSquads,
VMax, // this minus one will be the version used
};
@@ -808,6 +809,7 @@ SerMaybeFailure ser_inputframe(SerState* ser, InputFrame* i)
SER_VAR(&i->tick);
SER_VAR(&i->id);
SER_MAYBE_RETURN(ser_V2(ser, &i->movement));
+ SER_VAR(&i->take_over_squad);
SER_VAR(&i->seat_action);
SER_MAYBE_RETURN(ser_entityid(ser, &i->seat_to_inhabit));
@@ -829,6 +831,8 @@ SerMaybeFailure ser_player(SerState* ser, Player* p)
if (p->connected)
{
SER_VAR(&p->unlocked_bombs);
+ if(ser->version >= VAddedSquads)
+ SER_VAR(&p->squad);
SER_MAYBE_RETURN(ser_entityid(ser, &p->entity));
if (ser->version >= VAddedLastUsedMedbay)
SER_MAYBE_RETURN(ser_entityid(ser, &p->last_used_medbay));
@@ -912,6 +916,8 @@ SerMaybeFailure ser_entity(SerState* ser, GameState* gs, Entity* e)
{
SER_ASSERT(e->no_save_to_disk);
SER_MAYBE_RETURN(ser_entityid(ser, &e->currently_inside_of_box));
+ if(ser->version >= VAddedSquads)
+ SER_VAR(&e->presenting_squad);
SER_VAR(&e->goldness);
}
@@ -1468,6 +1474,27 @@ void process(GameState* gs, float dt)
// process input
PLAYERS_ITER(gs->players, player)
{
+ if (player->input.take_over_squad >= 0)
+ {
+ if (player->input.take_over_squad == SquadNone)
+ {
+ player->squad = SquadNone;
+ }
+ else {
+ bool squad_taken = false;
+ PLAYERS_ITER(gs->players, other_player)
+ {
+ if (other_player->squad == player->input.take_over_squad)
+ {
+ squad_taken = true;
+ break;
+ }
+ }
+ if (!squad_taken)
+ player->squad = player->input.take_over_squad;
+ }
+ player->input.take_over_squad = -1;
+ }
Entity* p = get_entity(gs, player->entity);
if (p == NULL)
{
@@ -1482,6 +1509,7 @@ void process(GameState* gs, float dt)
entity_ensure_in_orbit(p);
}
assert(p->is_player);
+ p->presenting_squad = player->squad;
#ifdef INFINITE_RESOURCES
p->damage = 0.0f;
diff --git a/hueshift.glsl b/hueshift.glsl
new file mode 100644
index 0000000..672f4bd
--- /dev/null
+++ b/hueshift.glsl
@@ -0,0 +1,53 @@
+@module hueshift
+
+@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 sampler2D iChannel0;
+uniform uniforms {
+ int is_colorless; // if greater than zero, no color
+ float target_hue;
+};
+in vec2 texUV;
+out vec4 fragColor;
+
+vec3 rgb2hsv(vec3 c)
+{
+ vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
+ vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
+ vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));
+
+ float d = q.x - min(q.w, q.y);
+ float e = 1.0e-10;
+ return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
+}
+
+vec3 hsv2rgb(vec3 c)
+{
+ vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
+ vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
+ return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
+}
+
+void main() {
+ vec4 outColor = texture(iChannel0, texUV);
+ vec3 hsv = rgb2hsv(outColor.rgb);
+
+ if(is_colorless > 0)
+ {
+ hsv.y = 0.0f;
+ } else if(hsv.y > 0.5) {
+ hsv.x = target_hue;
+ }
+ fragColor = vec4(hsv2rgb(hsv), outColor.a);
+}
+@end
+
+@program program vs fs
diff --git a/loaded/flag_available.png b/loaded/flag_available.png
new file mode 100644
index 0000000..a7b4e97
Binary files /dev/null and b/loaded/flag_available.png differ
diff --git a/loaded/flag_ripped.png b/loaded/flag_ripped.png
new file mode 100644
index 0000000..9108d4a
Binary files /dev/null and b/loaded/flag_ripped.png differ
diff --git a/main.c b/main.c
index 6b40918..0762e94 100644
--- a/main.c
+++ b/main.c
@@ -7,14 +7,14 @@
#include
#include // starting server thread
-#pragma warning ( disable: 33010 ) // this warning is so broken, doesn't understand assert()
+#pragma warning(disable : 33010) // this warning is so broken, doesn't understand assert()
#include "sokol_app.h"
#include "sokol_gfx.h"
#include "sokol_glue.h"
#include "sokol_gp.h"
#include "sokol_time.h"
-#pragma warning ( default: 33010 )
-#pragma warning ( disable: 6262 ) // warning about using a lot of stack, lol that's how stb image is
+#pragma warning(default : 33010)
+#pragma warning(disable : 6262) // warning about using a lot of stack, lol that's how stb image is
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#include "types.h"
@@ -29,27 +29,35 @@
#define MINIAUDIO_IMPLEMENTATION
#include "miniaudio.h"
-static struct GameState gs = { 0 };
+// shaders
+#include "hueshift.gen.h"
+static sg_pipeline pip;
+
+static struct GameState gs = {0};
static int myplayer = -1;
static bool right_mouse_down = false;
-static bool keydown[SAPP_KEYCODE_MENU] = { 0 };
-typedef struct KeyPressed {
+static bool keydown[SAPP_KEYCODE_MENU] = {0};
+typedef struct KeyPressed
+{
bool pressed;
uint64_t frame;
} KeyPressed;
-static KeyPressed keypressed[SAPP_KEYCODE_MENU] = { 0 };
-static V2 mouse_pos = { 0 };
+static KeyPressed keypressed[SAPP_KEYCODE_MENU] = {0};
+static V2 mouse_pos = {0};
static bool fullscreened = false;
static bool mouse_pressed = false;
static uint64_t mouse_pressed_frame = 0;
static bool mouse_frozen = false; // @BeforeShip make this debug only thing
-static float funval = 0.0f; // easy to play with value controlled by left mouse button when held
+static float funval = 0.0f; // easy to play with value controlled by left mouse button when held
// down @BeforeShip remove on release builds
-static struct ClientToServer client_to_server = { 0 }; // buffer of inputs
-static ENetHost* client;
-static ENetPeer* peer;
+static struct ClientToServer client_to_server = {0}; // buffer of inputs
+static ENetHost *client;
+static ENetPeer *peer;
static float zoom_target = 300.0f;
static float zoom = 300.0f;
+static enum Squad take_over_squad = (enum Squad) - 1; // -1 means not taking over any squad
+
+// images
static sg_image image_itemframe;
static sg_image image_itemframe_selected;
static sg_image image_thrusterburn;
@@ -63,6 +71,9 @@ static sg_image image_mystery;
static sg_image image_explosion;
static sg_image image_low_health;
static sg_image image_mic_muted;
+static sg_image image_flag_available;
+static sg_image image_flag_taken;
+
static int cur_editing_boxtype = -1;
static int cur_editing_rotation = 0;
@@ -70,20 +81,21 @@ static int cur_editing_rotation = 0;
static bool muted = false;
static ma_device microphone_device;
static ma_device speaker_device;
-OpusEncoder* enc;
-OpusDecoder* dec;
-OpusBuffer packets_to_send = { 0 };
-OpusBuffer packets_to_play = { 0 };
-ma_mutex send_packets_mutex = { 0 };
-ma_mutex play_packets_mutex = { 0 };
+OpusEncoder *enc;
+OpusDecoder *dec;
+OpusBuffer packets_to_send = {0};
+OpusBuffer packets_to_play = {0};
+ma_mutex send_packets_mutex = {0};
+ma_mutex play_packets_mutex = {0};
// server thread
-void* server_thread_handle = 0;
-ServerThreadInfo server_info = { 0 };
+void *server_thread_handle = 0;
+ServerThreadInfo server_info = {0};
-static struct BoxInfo {
+static struct BoxInfo
+{
enum BoxType type;
- const char* image_path;
+ const char *image_path;
sg_image image;
bool needs_tobe_unlocked;
} boxes[] = {
@@ -118,21 +130,61 @@ static struct BoxInfo {
.needs_tobe_unlocked = true,
},
};
-const int boxes_len = sizeof(boxes) / sizeof(*boxes);
+#define ARRLEN(arr) (sizeof(arr) / sizeof(*arr))
+
+static struct SquadMeta
+{
+ enum Squad squad;
+ float hue;
+ bool is_colorless;
+} squad_metas[] = {
+ {
+ .squad = SquadNone,
+ .is_colorless = true,
+ },
+ {
+ .squad = SquadRed,
+ .hue = 21.0f / 360.0f,
+ },
+ {
+ .squad = SquadGreen,
+ .hue = 111.0f / 360.0f,
+ },
+ {
+ .squad = SquadBlue,
+ .hue = 201.0f / 360.0f,
+ },
+ {
+ .squad = SquadPurple,
+ .hue = 291.0f / 360.0f,
+ },
+};
+
+struct SquadMeta squad_meta(enum Squad squad)
+{
+ for (int i = 0; i < ARRLEN(squad_metas); i++)
+ {
+ if (squad_metas[i].squad == squad)
+ return squad_metas[i];
+ }
+ Log("Could not find squad %d!\n", squad);
+ return (struct SquadMeta){0};
+}
struct BoxInfo
- boxinfo(enum BoxType type)
+boxinfo(enum BoxType type)
{
- for (int i = 0; i < boxes_len; i++) {
+ for (int i = 0; i < ARRLEN(boxes); i++)
+ {
if (boxes[i].type == type)
return boxes[i];
}
Log("No box info found for type %d\n", type);
- return (struct BoxInfo) { 0 };
+ return (struct BoxInfo){0};
}
static sg_image
-load_image(const char* path)
+load_image(const char *path)
{
sg_image to_return = sg_alloc_image();
@@ -141,38 +193,39 @@ load_image(const char* path)
int comp = 0;
const int desired_channels = 4;
stbi_set_flip_vertically_on_load(true);
- stbi_uc* image_data = stbi_load(path, &x, &y, &comp, desired_channels);
- if (!image_data) {
+ stbi_uc *image_data = stbi_load(path, &x, &y, &comp, desired_channels);
+ if (!image_data)
+ {
fprintf(stderr, "Failed to load image: %s\n", stbi_failure_reason());
exit(-1);
}
sg_init_image(to_return,
- &(sg_image_desc) {.width = x,
- .height = y,
- .pixel_format = SG_PIXELFORMAT_RGBA8,
- .min_filter = SG_FILTER_NEAREST,
- .mag_filter = SG_FILTER_NEAREST,
- .data.subimage[0][0] = {
- .ptr = image_data,
- .size = (size_t)(x * y * desired_channels),
- } });
+ &(sg_image_desc){.width = x,
+ .height = y,
+ .pixel_format = SG_PIXELFORMAT_RGBA8,
+ .min_filter = SG_FILTER_NEAREST,
+ .mag_filter = SG_FILTER_NEAREST,
+ .data.subimage[0][0] = {
+ .ptr = image_data,
+ .size = (size_t)(x * y * desired_channels),
+ }});
stbi_image_free(image_data);
return to_return;
}
-void microphone_data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount)
+void microphone_data_callback(ma_device *pDevice, void *pOutput, const void *pInput, ma_uint32 frameCount)
{
assert(frameCount == VOIP_EXPECTED_FRAME_COUNT);
if (peer != NULL)
{
ma_mutex_lock(&send_packets_mutex);
- OpusPacket* packet = push_packet(&packets_to_send);
+ OpusPacket *packet = push_packet(&packets_to_send);
if (packet != NULL)
{
- opus_int16 muted_audio[VOIP_EXPECTED_FRAME_COUNT] = { 0 };
- const opus_int16* audio_buffer = (const opus_int16*)pInput;
+ opus_int16 muted_audio[VOIP_EXPECTED_FRAME_COUNT] = {0};
+ const opus_int16 *audio_buffer = (const opus_int16 *)pInput;
if (muted)
audio_buffer = muted_audio;
opus_int32 written = opus_encode(enc, audio_buffer, VOIP_EXPECTED_FRAME_COUNT, packet->data, VOIP_PACKET_MAX_SIZE);
@@ -183,18 +236,18 @@ void microphone_data_callback(ma_device* pDevice, void* pOutput, const void* pIn
(void)pOutput;
}
-void speaker_data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount)
+void speaker_data_callback(ma_device *pDevice, void *pOutput, const void *pInput, ma_uint32 frameCount)
{
assert(frameCount == VOIP_EXPECTED_FRAME_COUNT);
ma_mutex_lock(&play_packets_mutex);
- OpusPacket* cur_packet = pop_packet(&packets_to_play);
+ OpusPacket *cur_packet = pop_packet(&packets_to_play);
if (cur_packet != NULL && cur_packet->length > 0) // length of 0 means skipped packet
{
- opus_decode(dec, cur_packet->data, cur_packet->length, (opus_int16*)pOutput, frameCount, 0);
+ opus_decode(dec, cur_packet->data, cur_packet->length, (opus_int16 *)pOutput, frameCount, 0);
}
else
{
- opus_decode(dec, NULL, 0, (opus_int16*)pOutput, frameCount, 0); // I think opus makes it sound good if packets are skipped with null
+ opus_decode(dec, NULL, 0, (opus_int16 *)pOutput, frameCount, 0); // I think opus makes it sound good if packets are skipped with null
}
ma_mutex_unlock(&play_packets_mutex);
(void)pInput;
@@ -203,6 +256,7 @@ void speaker_data_callback(ma_device* pDevice, void* pOutput, const void* pInput
static void
init(void)
{
+
// audio
{
// opus
@@ -214,7 +268,6 @@ init(void)
assert(error == OPUS_OK);
}
-
ma_device_config microphone_config = ma_device_config_init(ma_device_type_capture);
microphone_config.capture.format = ma_format_s16;
@@ -231,7 +284,8 @@ init(void)
ma_result result;
result = ma_device_init(NULL, µphone_config, µphone_device);
- if (result != MA_SUCCESS) {
+ if (result != MA_SUCCESS)
+ {
Log("Failed to initialize capture device.\n");
exit(-1);
}
@@ -244,11 +298,14 @@ init(void)
exit(-1);
}
- if (ma_mutex_init(&send_packets_mutex) != MA_SUCCESS) Log("Failed to init send mutex\n");
- if (ma_mutex_init(&play_packets_mutex) != MA_SUCCESS) Log("Failed to init play mutex\n");
+ if (ma_mutex_init(&send_packets_mutex) != MA_SUCCESS)
+ Log("Failed to init send mutex\n");
+ if (ma_mutex_init(&play_packets_mutex) != MA_SUCCESS)
+ Log("Failed to init play mutex\n");
result = ma_device_start(µphone_device);
- if (result != MA_SUCCESS) {
+ if (result != MA_SUCCESS)
+ {
ma_device_uninit(µphone_device);
Log("Failed to start device.\n");
exit(-1);
@@ -264,35 +321,52 @@ init(void)
}
Log("Initialized audio\n");
-
}
// @BeforeShip make all fprintf into logging to file, warning dialog grids on
// failure instead of exit(-1), replace the macros in sokol with this as well,
// like assert
- Entity* entity_data = malloc(sizeof * entity_data * MAX_ENTITIES);
- initialize(&gs, entity_data, sizeof * entity_data * MAX_ENTITIES);
+ Entity *entity_data = malloc(sizeof *entity_data * MAX_ENTITIES);
+ initialize(&gs, entity_data, sizeof *entity_data * MAX_ENTITIES);
- sg_desc sgdesc = { .context = sapp_sgcontext() };
+ sg_desc sgdesc = {.context = sapp_sgcontext()};
sg_setup(&sgdesc);
- if (!sg_isvalid()) {
+ if (!sg_isvalid())
+ {
fprintf(stderr, "Failed to create Sokol GFX context!\n");
exit(-1);
}
- sgp_desc sgpdesc = { 0 };
+ sgp_desc sgpdesc = {0};
sgp_setup(&sgpdesc);
- if (!sgp_is_valid()) {
+ if (!sgp_is_valid())
+ {
fprintf(stderr,
- "Failed to create Sokol GP context: %s\n",
- sgp_get_error_message(sgp_get_last_error()));
+ "Failed to create Sokol GP context: %s\n",
+ sgp_get_error_message(sgp_get_last_error()));
exit(-1);
}
- // image loading
+ // shaders
{
- for (int i = 0; i < boxes_len; i++) {
+ // initialize shader
+ sgp_pipeline_desc pip_desc = {
+ .shader = *hueshift_program_shader_desc(sg_query_backend()),
+ .blend_mode = SGP_BLENDMODE_BLEND,
+ };
+ pip = sgp_make_pipeline(&pip_desc);
+ if (sg_query_pipeline_state(pip) != SG_RESOURCESTATE_VALID)
+ {
+ fprintf(stderr, "failed to make custom pipeline\n");
+ exit(-1);
+ }
+ }
+
+ // images loading
+ {
+ for (int i = 0; i < ARRLEN(boxes); i++)
+ {
boxes[i].image = load_image(boxes[i].image_path);
}
image_thrusterburn = load_image("loaded/thrusterburn.png");
@@ -308,20 +382,24 @@ init(void)
image_explosion = load_image("loaded/explosion.png");
image_low_health = load_image("loaded/low_health.png");
image_mic_muted = load_image("loaded/mic_muted.png");
+ image_flag_available = load_image("loaded/flag_available.png");
+ image_flag_taken = load_image("loaded/flag_ripped.png");
}
// socket initialization
{
- if (enet_initialize() != 0) {
+ if (enet_initialize() != 0)
+ {
fprintf(stderr, "An error occurred while initializing ENet.\n");
exit(-1);
}
client = enet_host_create(NULL /* create a client host */,
- 1 /* only allow 1 outgoing connection */,
- 2 /* allow up 2 channels to be used, 0 and 1 */,
- 0 /* assume any amount of incoming bandwidth */,
- 0 /* assume any amount of outgoing bandwidth */);
- if (client == NULL) {
+ 1 /* only allow 1 outgoing connection */,
+ 2 /* allow up 2 channels to be used, 0 and 1 */,
+ 0 /* assume any amount of incoming bandwidth */,
+ 0 /* assume any amount of outgoing bandwidth */);
+ if (client == NULL)
+ {
fprintf(
stderr,
"An error occurred while trying to create an ENet client host.\n");
@@ -333,16 +411,19 @@ init(void)
enet_address_set_host(&address, SERVER_ADDRESS);
address.port = SERVER_PORT;
peer = enet_host_connect(client, &address, 2, 0);
- if (peer == NULL) {
+ if (peer == NULL)
+ {
fprintf(stderr,
- "No available peers for initiating an ENet connection.\n");
+ "No available peers for initiating an ENet connection.\n");
exit(-1);
}
// the timeout is the third parameter here
- if (enet_host_service(client, &event, 5000) > 0 && event.type == ENET_EVENT_TYPE_CONNECT) {
+ if (enet_host_service(client, &event, 5000) > 0 && event.type == ENET_EVENT_TYPE_CONNECT)
+ {
Log("Connected\n");
}
- else {
+ else
+ {
/* Either the 5 seconds are up or a disconnect event was */
/* received. Reset the peer in the event the 5 seconds */
/* had run out without any significant event. */
@@ -368,15 +449,15 @@ draw_texture_rectangle_centered(V2 center, V2 width_height)
{
V2 halfsize = V2scale(width_height, 0.5f);
sgp_draw_textured_rect(center.x - halfsize.x,
- center.y - halfsize.y,
- width_height.x,
- width_height.y);
+ center.y - halfsize.y,
+ width_height.x,
+ width_height.y);
}
static void
draw_texture_centered(V2 center, float size)
{
- draw_texture_rectangle_centered(center, (V2) { size, size });
+ draw_texture_rectangle_centered(center, (V2){size, size});
}
static void
@@ -384,25 +465,26 @@ draw_circle(V2 point, float radius)
{
#define POINTS 64
sgp_line lines[POINTS];
- for (int i = 0; i < POINTS; i++) {
+ for (int i = 0; i < POINTS; i++)
+ {
float progress = (float)i / (float)POINTS;
float next_progress = (float)(i + 1) / (float)POINTS;
- lines[i].a = (V2){ .x = cosf(progress * 2.0f * PI) * radius,
- .y = sinf(progress * 2.0f * PI) * radius };
- lines[i].b = (V2){ .x = cosf(next_progress * 2.0f * PI) * radius,
- .y = sinf(next_progress * 2.0f * PI) * radius };
+ lines[i].a = (V2){.x = cosf(progress * 2.0f * PI) * radius,
+ .y = sinf(progress * 2.0f * PI) * radius};
+ lines[i].b = (V2){.x = cosf(next_progress * 2.0f * PI) * radius,
+ .y = sinf(next_progress * 2.0f * PI) * radius};
lines[i].a = V2add(lines[i].a, point);
lines[i].b = V2add(lines[i].b, point);
}
sgp_draw_lines(lines, POINTS);
}
-static Entity*
+static Entity *
myentity()
{
if (myplayer == -1)
return NULL;
- Entity* to_return = get_entity(&gs, gs.players[myplayer].entity);
+ Entity *to_return = get_entity(&gs, gs.players[myplayer].entity);
if (to_return != NULL)
assert(to_return->is_player);
return to_return;
@@ -429,18 +511,140 @@ ui(bool draw, float dt, float width, float height)
{
static float cur_opacity = 1.0f;
cur_opacity = lerp(cur_opacity, myentity() != NULL ? 1.0f : 0.0f, dt * 5.0f);
- if (cur_opacity <= 0.01f) {
+ if (cur_opacity <= 0.01f)
+ {
return;
}
if (draw)
sgp_push_transform();
+ // draw flags
+ static V2 flag_pos[SquadLast] = {0};
+ static float flag_rot[SquadLast] = {0};
+ static float flag_scaling_increase[SquadLast] = {0};
+ static bool choosing_flags = false;
+ const float flag_padding = 70.0f;
+ const float center_panel_height = 200.0f;
+ static float center_panel_width = 0.0f;
+ const float target_center_panel_width = ((SquadLast) + 2) * flag_padding;
+#define FLAG_ITER(i) for (int i = 0; i < SquadLast; i++)
+ {
+ FLAG_ITER(i)
+ {
+ V2 target_pos = {0};
+ float target_rot = 0.0f;
+ float flag_progress = (float)i / (float)(SquadLast - 1.0f);
+ if (choosing_flags)
+ {
+ target_pos.x = width / 2.0f + lerp(-center_panel_width / 2.0f + flag_padding, center_panel_width / 2.0f - flag_padding, flag_progress);
+ target_pos.y = height * 0.5f;
+ target_rot = 0.0f;
+ }
+ else
+ {
+ target_pos.x = 25.0f;
+ target_pos.y = 200.0f;
+ target_rot = lerp(-PI / 3.0f, PI / 3.0f, flag_progress) + PI / 2.0f;
+ }
+ flag_pos[i] = V2lerp(flag_pos[i], target_pos, dt * 5.0f);
+ flag_rot[i] = lerp_angle(flag_rot[i], target_rot, dt * 5.0f);
+ }
+
+ center_panel_width = lerp(center_panel_width, choosing_flags ? target_center_panel_width : 0.0f, 6.0f * dt);
+
+ // center panel
+ {
+ AABB panel_rect = (AABB){
+ .x = width / 2.0f - center_panel_width / 2.0f,
+ .y = height / 2.0f - center_panel_height / 2.0f,
+ .width = center_panel_width,
+ .height = center_panel_height,
+ };
+ if (choosing_flags && mouse_pressed && !has_point(panel_rect, mouse_pos))
+ {
+ mouse_pressed = false;
+ choosing_flags = false;
+ }
+ if (draw)
+ {
+ sgp_set_color(0.7f, 0.7f, 0.7f, 0.5f);
+ sgp_draw_filled_rect(panel_rect.x, panel_rect.y, panel_rect.width, panel_rect.height);
+ }
+ }
+
+ FLAG_ITER(i)
+ {
+ enum Squad this_squad = (enum Squad)i;
+ bool this_squad_available = true;
+ if (this_squad != SquadNone)
+ PLAYERS_ITER(gs.players, other_player)
+ {
+ if (other_player->squad == this_squad)
+ {
+ this_squad_available = false;
+ break;
+ }
+ }
+
+ float size = 128.0f;
+ bool hovering = V2dist(mouse_pos, flag_pos[i]) < size * 0.25f && this_squad_available;
+
+ if (!choosing_flags && hovering && mouse_pressed)
+ {
+ choosing_flags = true;
+ mouse_pressed = false;
+ }
+
+ if (this_squad_available && choosing_flags && hovering && mouse_pressed)
+ {
+ take_over_squad = this_squad;
+ mouse_pressed = false;
+ }
+
+ flag_scaling_increase[i] = lerp(flag_scaling_increase[i], hovering ? 0.2f : 0.0f, dt * 9.0f);
+
+ size *= 1.0f + flag_scaling_increase[i];
+
+ if (draw)
+ {
+ transform_scope
+ {
+ if (this_squad_available)
+ {
+ sgp_set_image(0, image_flag_available);
+ }
+ else
+ {
+ sgp_set_image(0, image_flag_taken);
+ }
+
+ sgp_set_pipeline(pip);
+ struct SquadMeta meta = squad_meta(this_squad);
+ hueshift_uniforms_t uniform = {0};
+ uniform.is_colorless = meta.is_colorless;
+ uniform.target_hue = meta.hue;
+ sgp_set_uniform(&uniform, sizeof(hueshift_uniforms_t));
+
+ sgp_rotate_at(flag_rot[i], flag_pos[i].x, flag_pos[i].y);
+ sgp_scale_at(1.0f, -1.0f, flag_pos[i].x, flag_pos[i].y); // images upside down by default :(
+ draw_texture_centered(flag_pos[i], size);
+
+ sgp_reset_image(0);
+ sgp_reset_pipeline();
+ }
+ }
+ }
+ }
+#undef FLAG_ITER
+
// draw spice bar
- if (draw) {
+ if (draw)
+ {
static float damage = 0.5f;
- if (myentity() != NULL) {
+ if (myentity() != NULL)
+ {
damage = myentity()->damage;
}
@@ -456,7 +660,8 @@ ui(bool draw, float dt, float width, float height)
// draw muted
static float muted_opacity = 0.0f;
- if (draw) {
+ if (draw)
+ {
muted_opacity = lerp(muted_opacity, muted ? 1.0f : 0.0f, 8.0f * dt);
sgp_set_color(1.0f, 1.0f, 1.0f, muted_opacity);
float size_x = 150.0f;
@@ -465,7 +670,8 @@ ui(bool draw, float dt, float width, float height)
float x = width - size_x - 40.0f;
float y = height - size_y - 40.0f;
- transform_scope{
+ transform_scope
+ {
sgp_scale_at(1.0f, -1.0f, x + size_x / 2.0f, y + size_y / 2.0f);
sgp_draw_textured_rect(x, y, size_x, size_y);
sgp_reset_image(0);
@@ -476,7 +682,7 @@ ui(bool draw, float dt, float width, float height)
{
float itemframe_width = (float)sg_query_image_info(image_itemframe).width * 2.0f;
float itemframe_height = (float)sg_query_image_info(image_itemframe).height * 2.0f;
- float total_width = itemframe_width * boxes_len;
+ float total_width = itemframe_width * ARRLEN(boxes);
float item_width = itemframe_width * 0.75f;
float item_height = itemframe_height * 0.75f;
float item_offset_x = (itemframe_width - item_width) / 2.0f;
@@ -484,27 +690,32 @@ ui(bool draw, float dt, float width, float height)
float x = width / 2.0f - total_width / 2.0f;
float y = height - itemframe_height * 1.5f;
- for (int i = 0; i < boxes_len; i++) {
+ for (int i = 0; i < ARRLEN(boxes); i++)
+ {
if (has_point(
- (AABB) {
- .x = x,
- .y = y,
- .width = itemframe_width,
- .height = itemframe_height,
- },
- mouse_pos)
- && mouse_pressed) {
+ (AABB){
+ .x = x,
+ .y = y,
+ .width = itemframe_width,
+ .height = itemframe_height,
+ },
+ mouse_pos) &&
+ mouse_pressed)
+ {
// "handle" mouse pressed
attempt_to_build(i);
mouse_pressed = false;
}
- if (draw) {
+ if (draw)
+ {
sgp_set_color(1.0f, 1.0f, 1.0f, cur_opacity);
- if (cur_editing_boxtype == i) {
+ if (cur_editing_boxtype == i)
+ {
sgp_set_image(0, image_itemframe_selected);
}
- else {
+ else
+ {
sgp_set_image(0, image_itemframe);
}
sgp_draw_textured_rect(x, y, itemframe_width, itemframe_height);
@@ -513,7 +724,8 @@ ui(bool draw, float dt, float width, float height)
{
sgp_set_image(0, info.image);
}
- else {
+ else
+ {
sgp_set_image(0, image_mystery);
}
transform_scope
@@ -521,7 +733,7 @@ ui(bool draw, float dt, float width, float height)
float item_x = x + item_offset_x;
float item_y = y + item_offset_y;
sgp_scale_at(1.0f, -1.0f, item_x + item_width / 2.0f, item_y + item_height / 2.0f);
- //sgp_scale(1.0f, -1.0f);
+ // sgp_scale(1.0f, -1.0f);
sgp_draw_textured_rect(item_x, item_y, item_width, item_height);
}
sgp_reset_image(0);
@@ -538,9 +750,11 @@ static void draw_dots(V2 camera_pos, float gap)
{
sgp_set_color(1.0f, 1.0f, 1.0f, 1.0f);
const int num = 100;
- for (int x = -num; x < num; x++) {
- for (int y = -num; y < num; y++) {
- V2 star = (V2){ (float)x * gap, (float)y * gap };
+ for (int x = -num; x < num; x++)
+ {
+ for (int y = -num; y < num; y++)
+ {
+ V2 star = (V2){(float)x * gap, (float)y * gap};
if (V2lengthsqr(V2sub(star, camera_pos)) > VISION_RADIUS * VISION_RADIUS)
continue;
@@ -561,12 +775,15 @@ frame(void)
// pressed input management
{
- for (int i = 0; i < SAPP_KEYCODE_MENU; i++) {
- if (keypressed[i].frame < sapp_frame_count()) {
+ for (int i = 0; i < SAPP_KEYCODE_MENU; i++)
+ {
+ if (keypressed[i].frame < sapp_frame_count())
+ {
keypressed[i].pressed = false;
}
}
- if (mouse_pressed_frame < sapp_frame_count()) {
+ if (mouse_pressed_frame < sapp_frame_count())
+ {
mouse_pressed = false;
}
}
@@ -574,17 +791,22 @@ frame(void)
// networking
{
ENetEvent event;
- while (true) {
+ while (true)
+ {
int enet_status = enet_host_service(client, &event, 0);
- if (enet_status > 0) {
- switch (event.type) {
- case ENET_EVENT_TYPE_CONNECT: {
+ if (enet_status > 0)
+ {
+ switch (event.type)
+ {
+ case ENET_EVENT_TYPE_CONNECT:
+ {
Log("New client from host %x\n", event.peer->address.host);
break;
}
- case ENET_EVENT_TYPE_RECEIVE: {
- char* decompressed = malloc(sizeof * decompressed * MAX_SERVER_TO_CLIENT); // @Robust no malloc
+ case ENET_EVENT_TYPE_RECEIVE:
+ {
+ char *decompressed = malloc(sizeof *decompressed * MAX_SERVER_TO_CLIENT); // @Robust no malloc
size_t decompressed_max_len = MAX_SERVER_TO_CLIENT;
assert(LZO1X_MEM_DECOMPRESS == 0);
@@ -599,7 +821,8 @@ frame(void)
server_to_client_deserialize(&msg, decompressed, decompressed_max_len, false);
myplayer = msg.your_player;
}
- else {
+ else
+ {
Log("Couldn't decompress gamestate packet, error code %d from lzo\n", return_value);
}
ma_mutex_unlock(&play_packets_mutex);
@@ -608,17 +831,20 @@ frame(void)
break;
}
- case ENET_EVENT_TYPE_DISCONNECT: {
+ case ENET_EVENT_TYPE_DISCONNECT:
+ {
fprintf(stderr, "Disconnected from server\n");
exit(-1);
break;
}
}
}
- else if (enet_status == 0) {
+ else if (enet_status == 0)
+ {
break;
}
- else if (enet_status < 0) {
+ else if (enet_status < 0)
+ {
fprintf(stderr, "Error receiving enet events: %d\n", enet_status);
break;
}
@@ -627,17 +853,17 @@ frame(void)
// gameplay
ui(false, dt, width, height); // handle events
- V2 build_target_pos = { 0 };
+ V2 build_target_pos = {0};
float build_target_rotation = 0.0f;
static V2 camera_pos = {
- 0
- }; // keeps camera at same position after player death
+ 0}; // keeps camera at same position after player death
V2 world_mouse_pos = mouse_pos; // processed later in scope
- struct BuildPreviewInfo {
+ struct BuildPreviewInfo
+ {
V2 grid_pos;
float grid_rotation;
- } build_preview = { 0 };
- V2 hand_pos = { 0 }; // in local space of grid when hovering over a grid
+ } build_preview = {0};
+ V2 hand_pos = {0}; // in local space of grid when hovering over a grid
bool hand_at_arms_length = false;
{
// interpolate zoom
@@ -645,44 +871,51 @@ frame(void)
// calculate world position and camera
{
- if (myentity() != NULL) {
+ if (myentity() != NULL)
+ {
camera_pos = entity_pos(myentity());
}
- world_mouse_pos = V2sub(world_mouse_pos, (V2) { .x = width / 2.0f, .y = height / 2.0f });
+ world_mouse_pos = V2sub(world_mouse_pos, (V2){.x = width / 2.0f, .y = height / 2.0f});
world_mouse_pos.x /= zoom;
world_mouse_pos.y /= -zoom;
- world_mouse_pos = V2add(world_mouse_pos, (V2) { .x = camera_pos.x, .y = camera_pos.y });
+ world_mouse_pos = V2add(world_mouse_pos, (V2){.x = camera_pos.x, .y = camera_pos.y});
}
// calculate build preview stuff
- EntityID grid_to_build_on = (EntityID){ 0 };
- V2 possibly_local_hand_pos = (V2){ 0 };
- if (myentity() != NULL) {
+ EntityID grid_to_build_on = (EntityID){0};
+ V2 possibly_local_hand_pos = (V2){0};
+ if (myentity() != NULL)
+ {
hand_pos = V2sub(world_mouse_pos, entity_pos(myentity()));
float hand_len = V2length(hand_pos);
- if (hand_len > MAX_HAND_REACH) {
+ if (hand_len > MAX_HAND_REACH)
+ {
hand_at_arms_length = true;
hand_len = MAX_HAND_REACH;
}
- else {
+ else
+ {
hand_at_arms_length = false;
}
hand_pos = V2scale(V2normalize(hand_pos), hand_len);
hand_pos = V2add(hand_pos, entity_pos(myentity()));
possibly_local_hand_pos = V2sub(hand_pos, entity_pos(myentity()));
- Entity* placing_grid = closest_to_point_in_radius(&gs, hand_pos, BUILD_BOX_SNAP_DIST_TO_SHIP);
- if (placing_grid == NULL) {
+ Entity *placing_grid = closest_to_point_in_radius(&gs, hand_pos, BUILD_BOX_SNAP_DIST_TO_SHIP);
+ if (placing_grid == NULL)
+ {
build_preview = (struct BuildPreviewInfo){
.grid_pos = hand_pos,
.grid_rotation = 0.0f,
};
}
- else {
+ else
+ {
grid_to_build_on = get_id(&gs, placing_grid);
hand_pos = grid_snapped_box_pos(placing_grid, hand_pos);
possibly_local_hand_pos = grid_world_to_local(placing_grid, hand_pos);
- build_preview = (struct BuildPreviewInfo){ .grid_pos = entity_pos(placing_grid),
+ build_preview = (struct BuildPreviewInfo){
+ .grid_pos = entity_pos(placing_grid),
.grid_rotation = entity_rotation(placing_grid),
};
}
@@ -692,7 +925,7 @@ frame(void)
{
static size_t last_frame_id = 0;
- InputFrame cur_input_frame = { 0 };
+ InputFrame cur_input_frame = {0};
cur_input_frame.id = last_frame_id;
V2 input = (V2){
.x = (float)keydown[SAPP_KEYCODE_D] - (float)keydown[SAPP_KEYCODE_A],
@@ -704,8 +937,10 @@ frame(void)
cur_input_frame.seat_action = keypressed[SAPP_KEYCODE_G].pressed;
cur_input_frame.grid_hand_pos_local_to = grid_to_build_on;
cur_input_frame.hand_pos = possibly_local_hand_pos;
+ cur_input_frame.take_over_squad = take_over_squad;
- if (mouse_pressed && cur_editing_boxtype != -1) {
+ if (mouse_pressed && cur_editing_boxtype != -1)
+ {
cur_input_frame.dobuild = mouse_pressed;
cur_input_frame.build_type = cur_editing_boxtype;
cur_input_frame.build_rotation = cur_editing_rotation;
@@ -726,14 +961,18 @@ frame(void)
input_differs = input_differs || cur_input_frame.build_rotation != latest.build_rotation;
input_differs = input_differs || !entityids_same(cur_input_frame.grid_hand_pos_local_to, latest.grid_hand_pos_local_to);
- if (input_differs) {
+ input_differs = input_differs || cur_input_frame.take_over_squad != latest.take_over_squad;
+
+ if (input_differs)
+ {
InputFrame last_frame = client_to_server.inputs[0];
- for (int i = 0; i < INPUT_BUFFER - 1; i++) {
+ for (int i = 0; i < INPUT_BUFFER - 1; i++)
+ {
InputFrame last_last_frame = last_frame;
last_frame = client_to_server.inputs[i + 1];
client_to_server.inputs[i + 1] = last_last_frame;
- // these references, in old input frames, may have been deleted by the time we
+ // these references, in old input frames, may have been deleted by the time we
// want to send them.
client_to_server.inputs[i + 1].seat_to_inhabit = cur_input_frame.seat_to_inhabit;
client_to_server.inputs[i + 1].grid_hand_pos_local_to = cur_input_frame.grid_hand_pos_local_to;
@@ -744,25 +983,26 @@ frame(void)
}
static int64_t last_sent_input_time = 0;
- if (stm_sec(stm_diff(stm_now(), last_sent_input_time)) > TIME_BETWEEN_INPUT_PACKETS) {
+ if (stm_sec(stm_diff(stm_now(), last_sent_input_time)) > TIME_BETWEEN_INPUT_PACKETS)
+ {
ma_mutex_lock(&send_packets_mutex);
client_to_server.mic_data = &packets_to_send;
- char serialized[MAX_CLIENT_TO_SERVER] = { 0 };
+ char serialized[MAX_CLIENT_TO_SERVER] = {0};
size_t out_len = 0;
if (client_to_server_serialize(&gs, &client_to_server, serialized, &out_len, MAX_CLIENT_TO_SERVER))
{
- ENetPacket* packet = enet_packet_create((void*)serialized,
- out_len,
- ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT);
+ ENetPacket *packet = enet_packet_create((void *)serialized,
+ out_len,
+ ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT);
enet_peer_send(peer, 0, packet); // @Robust error check this
last_sent_input_time = stm_now();
}
- else {
+ else
+ {
Log("Failed to serialize client to server!\n");
}
client_to_server.mic_data = NULL;
ma_mutex_unlock(&send_packets_mutex);
-
}
}
@@ -804,7 +1044,7 @@ frame(void)
const float stars_width = 35.0f;
float stars_height = stars_width * stars_height_over_width;
sgp_draw_textured_rect(-stars_width / 2.0f, -stars_height / 2.0f, stars_width, stars_height);
- //sgp_draw_textured_rect(0, 0, stars_width, stars_height);
+ // sgp_draw_textured_rect(0, 0, stars_width, stars_height);
sgp_reset_image(0);
}
transform_scope
@@ -817,7 +1057,7 @@ frame(void)
const float stars_width = 35.0f;
float stars_height = stars_width * stars_height_over_width;
sgp_draw_textured_rect(-stars_width / 2.0f, -stars_height / 2.0f, stars_width, stars_height);
- //sgp_draw_textured_rect(0, 0, stars_width, stars_height);
+ // sgp_draw_textured_rect(0, 0, stars_width, stars_height);
sgp_reset_image(0);
}
#endif
@@ -839,71 +1079,75 @@ frame(void)
}
#endif
- // camera go to player
- sgp_translate(-camera_pos.x, -camera_pos.y);
+ // camera go to player
+ sgp_translate(-camera_pos.x, -camera_pos.y);
- draw_dots(camera_pos, 1.5f); // in plane dots
+ draw_dots(camera_pos, 1.5f); // in plane dots
- // hand reached limit circle
- if (myentity() != NULL) {
- static float hand_reach_alpha = 1.0f;
- hand_reach_alpha = lerp(hand_reach_alpha, hand_at_arms_length ? 1.0f : 0.0f, dt * 5.0f);
- sgp_set_color(1.0f, 1.0f, 1.0f, hand_reach_alpha);
- draw_circle(entity_pos(myentity()), MAX_HAND_REACH);
- }
+ // hand reached limit circle
+ if (myentity() != NULL)
+ {
+ static float hand_reach_alpha = 1.0f;
+ hand_reach_alpha = lerp(hand_reach_alpha, hand_at_arms_length ? 1.0f : 0.0f, dt * 5.0f);
+ sgp_set_color(1.0f, 1.0f, 1.0f, hand_reach_alpha);
+ draw_circle(entity_pos(myentity()), MAX_HAND_REACH);
+ }
- // vision circle, what player can see
- if (myentity() != NULL)
- {
- set_color(colhexcode(0x4685e3));
- draw_circle(entity_pos(myentity()), VISION_RADIUS);
- }
+ // vision circle, what player can see
+ if (myentity() != NULL)
+ {
+ set_color(colhexcode(0x4685e3));
+ draw_circle(entity_pos(myentity()), VISION_RADIUS);
+ }
- float halfbox = BOX_SIZE / 2.0f;
+ float halfbox = BOX_SIZE / 2.0f;
- // mouse frozen, debugging tool
- if (mouse_frozen) {
- sgp_set_color(1.0f, 0.0f, 0.0f, 0.5f);
- sgp_draw_filled_rect(world_mouse_pos.x, world_mouse_pos.y, 0.1f, 0.1f);
- }
+ // mouse frozen, debugging tool
+ if (mouse_frozen)
+ {
+ sgp_set_color(1.0f, 0.0f, 0.0f, 0.5f);
+ sgp_draw_filled_rect(world_mouse_pos.x, world_mouse_pos.y, 0.1f, 0.1f);
+ }
- // building preview
- if (cur_editing_boxtype != -1) {
- sgp_set_color(0.5f, 0.5f, 0.5f, (sinf((float)time * 9.0f) + 1.0f) / 3.0f + 0.2f);
+ // building preview
+ if (cur_editing_boxtype != -1)
+ {
+ sgp_set_color(0.5f, 0.5f, 0.5f, (sinf((float)time * 9.0f) + 1.0f) / 3.0f + 0.2f);
- transform_scope
- {
- sgp_set_image(0, boxinfo(cur_editing_boxtype).image);
- sgp_rotate_at(build_preview.grid_rotation + rotangle(cur_editing_rotation),
- hand_pos.x,
- hand_pos.y);
- draw_texture_centered(hand_pos, BOX_SIZE);
- // drawbox(hand_pos, build_preview.grid_rotation, 0.0f,
- // cur_editing_boxtype, cur_editing_rotation);
- sgp_reset_image(0);
- }
+ transform_scope
+ {
+ sgp_set_image(0, boxinfo(cur_editing_boxtype).image);
+ sgp_rotate_at(build_preview.grid_rotation + rotangle(cur_editing_rotation),
+ hand_pos.x,
+ hand_pos.y);
+ draw_texture_centered(hand_pos, BOX_SIZE);
+ // drawbox(hand_pos, build_preview.grid_rotation, 0.0f,
+ // cur_editing_boxtype, cur_editing_rotation);
+ sgp_reset_image(0);
}
+ }
- static float player_scaling = 1.0f;
- player_scaling = lerp(player_scaling, zoom < 6.5f ? 100.0f : 1.0f, dt * 7.0f);
- for (size_t i = 0; i < gs.cur_next_entity; i++) {
- Entity* e = &gs.entities[i];
- if (!e->exists)
- continue;
- // draw grid
- if (e->is_grid)
+ static float player_scaling = 1.0f;
+ player_scaling = lerp(player_scaling, zoom < 6.5f ? 100.0f : 1.0f, dt * 7.0f);
+ for (size_t i = 0; i < gs.cur_next_entity; i++)
+ {
+ Entity *e = &gs.entities[i];
+ if (!e->exists)
+ continue;
+ // draw grid
+ if (e->is_grid)
+ {
+ Entity *g = e;
+ BOXES_ITER(&gs, b, g)
{
- Entity* g = e;
- BOXES_ITER(&gs, b, g)
+ if (b->is_explosion_unlock)
{
- if (b->is_explosion_unlock)
- {
- set_color(colhexcode(0xfcba03));
- draw_circle(entity_pos(b), GOLD_UNLOCK_RADIUS);
- }
- sgp_set_color(1.0f, 1.0f, 1.0f, 1.0f);
- // debug draw force vectors for thrusters
- #if 0
+ set_color(colhexcode(0xfcba03));
+ draw_circle(entity_pos(b), GOLD_UNLOCK_RADIUS);
+ }
+ sgp_set_color(1.0f, 1.0f, 1.0f, 1.0f);
+// debug draw force vectors for thrusters
+#if 0
{
if (b->type == BoxThruster)
{
@@ -911,131 +1155,144 @@ frame(void)
dbg_line(entity_pos(b), V2add(entity_pos(b), V2scale(thruster_force(b), -1.0f)));
}
}
- #endif
- if (b->box_type == BoxBattery) {
- float cur_alpha = sgp_get_color().a;
- Color from = WHITE;
- Color to = colhex(255, 0, 0);
- Color result = Collerp(from, to, b->energy_used / BATTERY_CAPACITY);
- sgp_set_color(result.r, result.g, result.b, cur_alpha);
- }
+#endif
+ if (b->box_type == BoxBattery)
+ {
+ float cur_alpha = sgp_get_color().a;
+ Color from = WHITE;
+ Color to = colhex(255, 0, 0);
+ Color result = Collerp(from, to, b->energy_used / BATTERY_CAPACITY);
+ sgp_set_color(result.r, result.g, result.b, cur_alpha);
+ }
+ transform_scope
+ {
+ sgp_rotate_at(entity_rotation(g) + rotangle(b->compass_rotation),
+ entity_pos(b).x,
+ entity_pos(b).y);
+
+ if (b->box_type == BoxThruster)
+ {
transform_scope
{
- sgp_rotate_at(entity_rotation(g) + rotangle(b->compass_rotation),
- entity_pos(b).x,
- entity_pos(b).y);
-
- if (b->box_type == BoxThruster) {
- transform_scope
- {
- sgp_set_color(1.0f, 1.0f, 1.0f, 1.0f);
- sgp_set_image(0, image_thrusterburn);
- // float scaling = 1.0 + (hash11(time*3.0)/2.0)*lerp(0.0,
- // 0.07, b->thrust); printf("%f\n", b->thrust);
- float scaling = 0.95f + lerp(0.0f, 0.3f, b->thrust);
- // float scaling = 1.1;
- // sgp_translate(-(scaling*BOX_SIZE - BOX_SIZE), 0.0);
- // sgp_scale(scaling, 1.0);
- sgp_scale_at(scaling, 1.0f, entity_pos(b).x, entity_pos(b).y);
- draw_texture_centered(entity_pos(b), BOX_SIZE);
- sgp_reset_image(0);
- }
- }
- sg_image img = boxinfo(b->box_type).image;
- if (b->box_type == BoxCockpit)
- {
- if (get_entity(&gs, b->player_who_is_inside_of_me) != NULL)
- img = image_cockpit_used;
- }
- if (b->box_type == BoxMedbay)
- {
- if (get_entity(&gs, b->player_who_is_inside_of_me) != NULL)
- img = image_medbay_used;
- }
- sgp_set_image(0, img);
- if (b->indestructible)
- {
- sgp_set_color(0.2f, 0.2f, 0.2f, 1.0f);
- }
+ sgp_set_color(1.0f, 1.0f, 1.0f, 1.0f);
+ sgp_set_image(0, image_thrusterburn);
+ // float scaling = 1.0 + (hash11(time*3.0)/2.0)*lerp(0.0,
+ // 0.07, b->thrust); printf("%f\n", b->thrust);
+ float scaling = 0.95f + lerp(0.0f, 0.3f, b->thrust);
+ // float scaling = 1.1;
+ // sgp_translate(-(scaling*BOX_SIZE - BOX_SIZE), 0.0);
+ // sgp_scale(scaling, 1.0);
+ sgp_scale_at(scaling, 1.0f, entity_pos(b).x, entity_pos(b).y);
draw_texture_centered(entity_pos(b), BOX_SIZE);
sgp_reset_image(0);
+ }
+ }
+ sg_image img = boxinfo(b->box_type).image;
+ if (b->box_type == BoxCockpit)
+ {
+ if (get_entity(&gs, b->player_who_is_inside_of_me) != NULL)
+ img = image_cockpit_used;
+ }
+ if (b->box_type == BoxMedbay)
+ {
+ if (get_entity(&gs, b->player_who_is_inside_of_me) != NULL)
+ img = image_medbay_used;
+ }
+ sgp_set_image(0, img);
+ if (b->indestructible)
+ {
+ sgp_set_color(0.2f, 0.2f, 0.2f, 1.0f);
+ }
+ draw_texture_centered(entity_pos(b), BOX_SIZE);
+ sgp_reset_image(0);
- if (b->box_type == BoxSolarPanel)
- {
- Color to_set = colhexcode(0xeb9834);
- to_set.a = b->sun_amount * 0.5f;
- set_color(to_set);
- draw_color_rect_centered(entity_pos(b), BOX_SIZE);
- }
+ if (b->box_type == BoxSolarPanel)
+ {
+ Color to_set = colhexcode(0xeb9834);
+ to_set.a = b->sun_amount * 0.5f;
+ set_color(to_set);
+ draw_color_rect_centered(entity_pos(b), BOX_SIZE);
+ }
- sgp_set_color(0.5f, 0.1f, 0.1f, b->damage);
- draw_color_rect_centered(entity_pos(b), BOX_SIZE);
- }
+ sgp_set_color(0.5f, 0.1f, 0.1f, b->damage);
+ draw_color_rect_centered(entity_pos(b), BOX_SIZE);
}
+ }
- // draw the velocity
+ // draw the velocity
#if 0
sgp_set_color(1.0f, 0.0f, 0.0f, 1.0f);
V2 vel = grid_vel(g);
V2 to = V2add(grid_com(g), vel);
sgp_draw_line(grid_com(g).x, grid_com(g).y, to.x, to.y);
#endif
- }
- if (e->is_player && get_entity(&gs, e->currently_inside_of_box) == NULL) {
- transform_scope
- {
- sgp_rotate_at(entity_rotation(e), entity_pos(e).x, entity_pos(e).y);
- sgp_set_color(1.0f, 1.0f, 1.0f, 1.0f);
- sgp_set_image(0, image_player);
- draw_texture_rectangle_centered(entity_pos(e), V2scale(PLAYER_SIZE, player_scaling));
- sgp_reset_image(0);
- }
- }
- if (e->is_explosion)
+ }
+
+ // draw player
+ if (e->is_player && get_entity(&gs, e->currently_inside_of_box) == NULL)
+ {
+ transform_scope
{
- sgp_set_image(0, image_explosion);
- sgp_set_color(1.0f, 1.0f, 1.0f, 1.0f - (e->explosion_progresss / EXPLOSION_TIME));
- draw_texture_centered(e->explosion_pos, EXPLOSION_RADIUS * 2.0f);
+ sgp_rotate_at(entity_rotation(e), entity_pos(e).x, entity_pos(e).y);
+ sgp_set_color(1.0f, 1.0f, 1.0f, 1.0f);
+
+ sgp_set_pipeline(pip);
+ struct SquadMeta meta = squad_meta(e->presenting_squad);
+ hueshift_uniforms_t uniform = {0};
+ uniform.is_colorless = meta.is_colorless;
+ uniform.target_hue = meta.hue;
+ sgp_set_uniform(&uniform, sizeof(hueshift_uniforms_t));
+ sgp_set_image(0, image_player);
+ draw_texture_rectangle_centered(entity_pos(e), V2scale(PLAYER_SIZE, player_scaling));
sgp_reset_image(0);
+ sgp_reset_pipeline();
}
}
-
- // gold target
- set_color(GOLD);
- sgp_draw_filled_rect(gs.goldpos.x, gs.goldpos.y, 0.1f, 0.1f);
-
- // the SUN
- transform_scope
+ if (e->is_explosion)
{
- sgp_translate(SUN_POS.x, SUN_POS.y);
- set_color(WHITE);
- sgp_set_image(0, image_sun);
- draw_texture_centered((V2) { 0 }, SUN_RADIUS * 2.0f);
+ sgp_set_image(0, image_explosion);
+ sgp_set_color(1.0f, 1.0f, 1.0f, 1.0f - (e->explosion_progresss / EXPLOSION_TIME));
+ draw_texture_centered(e->explosion_pos, EXPLOSION_RADIUS * 2.0f);
sgp_reset_image(0);
-
- // sun DEATH RADIUS
- set_color(RED);
- draw_circle((V2) { 0 }, INSTANT_DEATH_DISTANCE_FROM_SUN);
}
+ }
- sgp_set_color(1.0f, 1.0f, 1.0f, 1.0f);
- dbg_drawall();
- } // world space transform end
+ // gold target
+ set_color(GOLD);
+ sgp_draw_filled_rect(gs.goldpos.x, gs.goldpos.y, 0.1f, 0.1f);
- // low health
- if (myentity() != NULL)
+ // the SUN
+ transform_scope
{
- sgp_set_color(1.0f, 1.0f, 1.0f, myentity()->damage);
- sgp_set_image(0, image_low_health);
- draw_texture_rectangle_centered((V2) { width / 2.0f, height / 2.0f }, (V2) { width, height });
+ sgp_translate(SUN_POS.x, SUN_POS.y);
+ set_color(WHITE);
+ sgp_set_image(0, image_sun);
+ draw_texture_centered((V2){0}, SUN_RADIUS * 2.0f);
sgp_reset_image(0);
+
+ // sun DEATH RADIUS
+ set_color(RED);
+ draw_circle((V2){0}, INSTANT_DEATH_DISTANCE_FROM_SUN);
}
+ sgp_set_color(1.0f, 1.0f, 1.0f, 1.0f);
+ dbg_drawall();
+ } // world space transform end
+
+ // low health
+ if (myentity() != NULL)
+ {
+ sgp_set_color(1.0f, 1.0f, 1.0f, myentity()->damage);
+ sgp_set_image(0, image_low_health);
+ draw_texture_rectangle_centered((V2){width / 2.0f, height / 2.0f}, (V2){width, height});
+ sgp_reset_image(0);
+ }
+
// UI drawn in screen space
ui(true, dt, width, height);
}
- sg_pass_action pass_action = { 0 };
+ sg_pass_action pass_action = {0};
sg_begin_default_pass(&pass_action, (int)width, (int)height);
sgp_flush();
sgp_end();
@@ -1045,6 +1302,8 @@ frame(void)
void cleanup(void)
{
+ sg_destroy_pipeline(pip);
+
ma_mutex_lock(&server_info.info_mutex);
server_info.should_quit = true;
ma_mutex_unlock(&server_info.info_mutex);
@@ -1068,16 +1327,19 @@ void cleanup(void)
ma_mutex_uninit(&server_info.info_mutex);
}
-void event(const sapp_event* e)
+void event(const sapp_event *e)
{
- switch (e->type) {
+ switch (e->type)
+ {
case SAPP_EVENTTYPE_KEY_DOWN:
#ifdef DEBUG_TOOLS
- if (e->key_code == SAPP_KEYCODE_T) {
+ if (e->key_code == SAPP_KEYCODE_T)
+ {
mouse_frozen = !mouse_frozen;
}
#endif
- if (e->key_code == SAPP_KEYCODE_R) {
+ if (e->key_code == SAPP_KEYCODE_R)
+ {
cur_editing_rotation += 1;
cur_editing_rotation %= RotationLast;
}
@@ -1097,20 +1359,24 @@ void event(const sapp_event* e)
}
int key_num = e->key_code - SAPP_KEYCODE_0;
int target_box = key_num - 1;
- if (target_box < BoxLast && target_box >= 0) {
+ if (target_box < BoxLast && target_box >= 0)
+ {
attempt_to_build(target_box);
}
- if (!mouse_frozen) {
+ if (!mouse_frozen)
+ {
keydown[e->key_code] = true;
- if (keypressed[e->key_code].frame == 0) {
+ if (keypressed[e->key_code].frame == 0)
+ {
keypressed[e->key_code].pressed = true;
keypressed[e->key_code].frame = e->frame_count;
}
}
break;
case SAPP_EVENTTYPE_KEY_UP:
- if (!mouse_frozen) {
+ if (!mouse_frozen)
+ {
keydown[e->key_code] = false;
keypressed[e->key_code].pressed = false;
@@ -1122,28 +1388,34 @@ void event(const sapp_event* e)
zoom_target = clamp(zoom_target, 0.5f, 900.0f);
break;
case SAPP_EVENTTYPE_MOUSE_DOWN:
- if (e->mouse_button == SAPP_MOUSEBUTTON_LEFT) {
+ if (e->mouse_button == SAPP_MOUSEBUTTON_LEFT)
+ {
mouse_pressed = true;
mouse_pressed_frame = e->frame_count;
}
- if (e->mouse_button == SAPP_MOUSEBUTTON_RIGHT) {
+ if (e->mouse_button == SAPP_MOUSEBUTTON_RIGHT)
+ {
right_mouse_down = true;
}
break;
case SAPP_EVENTTYPE_MOUSE_UP:
- if (e->mouse_button == SAPP_MOUSEBUTTON_LEFT) {
+ if (e->mouse_button == SAPP_MOUSEBUTTON_LEFT)
+ {
mouse_pressed = false;
mouse_pressed_frame = 0;
}
- if (e->mouse_button == SAPP_MOUSEBUTTON_RIGHT) {
+ if (e->mouse_button == SAPP_MOUSEBUTTON_RIGHT)
+ {
right_mouse_down = false;
}
break;
case SAPP_EVENTTYPE_MOUSE_MOVE:
- if (!mouse_frozen) {
- mouse_pos = (V2){ .x = e->mouse_x, .y = e->mouse_y };
+ if (!mouse_frozen)
+ {
+ mouse_pos = (V2){.x = e->mouse_x, .y = e->mouse_y};
}
- if (right_mouse_down) {
+ if (right_mouse_down)
+ {
funval += e->mouse_dx;
Log("Funval %f\n", funval);
}
@@ -1152,28 +1424,29 @@ void event(const sapp_event* e)
}
sapp_desc
-sokol_main(int argc, char* argv[])
+sokol_main(int argc, char *argv[])
{
bool hosting = false;
stm_setup();
ma_mutex_init(&server_info.info_mutex);
server_info.world_save = "debug_world.bin";
- if (argc > 1) {
- server_thread_handle = (void*)_beginthread(server, 0, (void*)&server_info);
+ if (argc > 1)
+ {
+ server_thread_handle = (void *)_beginthread(server, 0, (void *)&server_info);
hosting = true;
}
(void)argv;
- return (sapp_desc) {
+ return (sapp_desc){
.init_cb = init,
- .frame_cb = frame,
- .cleanup_cb = cleanup,
- .width = 640,
- .height = 480,
- .gl_force_gles2 = true,
- .window_title = hosting ? "Flight Hosting" : "Flight Not Hosting",
- .icon.sokol_default = true,
- .event_cb = event,
- .win32_console_attach = true,
- .sample_count = 4, // anti aliasing
+ .frame_cb = frame,
+ .cleanup_cb = cleanup,
+ .width = 640,
+ .height = 480,
+ .gl_force_gles2 = true,
+ .window_title = hosting ? "Flight Hosting" : "Flight Not Hosting",
+ .icon.sokol_default = true,
+ .event_cb = event,
+ .win32_console_attach = true,
+ .sample_count = 4, // anti aliasing
};
}
\ No newline at end of file
diff --git a/server.c b/server.c
index 8b23257..3c3066e 100644
--- a/server.c
+++ b/server.c
@@ -253,6 +253,7 @@ void server(void* info_raw)
#ifdef UNLOCK_ALL
gs.players[player_slot].unlocked_bombs = true;
#endif
+ gs.players[player_slot].squad = SquadPurple;
}
}
break;
@@ -296,6 +297,8 @@ void server(void* info_raw)
// for these "event" inputs, only modify the current input if the event is true.
// while processing the gamestate, will mark it as false once processed. This
// prevents setting the event input to false before it's been processed.
+ if (cur_input.take_over_squad >= 0)
+ gs.players[player_slot].input.take_over_squad = cur_input.take_over_squad;
if (cur_input.seat_action)
{
gs.players[player_slot].input.seat_action = cur_input.seat_action;
diff --git a/shadergen.bat b/shadergen.bat
new file mode 100644
index 0000000..230e2bb
--- /dev/null
+++ b/shadergen.bat
@@ -0,0 +1,6 @@
+
+WHERE sokol-shdc.exe
+IF %ERRORLEVEL% NEQ 0 ECHO ERROR download sokol-shdc from https://github.com/floooh/sokol-tools-bin/blob/master/bin/win32/sokol-shdc.exe and put it in this folder
+
+@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
\ No newline at end of file
diff --git a/types.h b/types.h
index 9fc14ef..b0a522c 100644
--- a/types.h
+++ b/types.h
@@ -121,6 +121,16 @@ enum CompassRotation
RotationLast,
};
+enum Squad
+{
+ SquadNone,
+ SquadRed,
+ SquadGreen,
+ SquadBlue,
+ SquadPurple,
+ SquadLast,
+};
+
// when generation is 0, invalid ID
typedef struct EntityID
{
@@ -141,6 +151,8 @@ typedef struct InputFrame
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;
+ int take_over_squad; // -1 means not taking over any squad
+
bool seat_action;
EntityID seat_to_inhabit;
V2 hand_pos; // local to player transationally but not rotationally unless field below is not null, then it's local to that grid
@@ -172,6 +184,7 @@ typedef struct Entity
// player
bool is_player;
+ enum Squad presenting_squad;
EntityID currently_inside_of_box;
float goldness; // how much the player is a winner
@@ -206,6 +219,7 @@ typedef struct Player
{
bool connected;
bool unlocked_bombs;
+ enum Squad squad;
EntityID entity;
EntityID last_used_medbay;
InputFrame input;
@@ -236,6 +250,7 @@ typedef struct GameState
#define PLAYERS_ITER(players, cur) for(Player * cur = players; cur < players+MAX_PLAYERS; cur++) if(cur->connected)
#define PI 3.14159f
+#define TAU (PI*2.0f)
// returns in radians
static float rotangle(enum CompassRotation rot)
@@ -391,8 +406,6 @@ static OpusPacket* push_packet(OpusBuffer* buff)
return to_return;
}
-// how many unpopped packets there are, can't check for null on pop_packet because
-// could be a skipped packet. This is used in a for loop to flush a packet buffer
static int num_queued_packets(OpusBuffer* buff)
{
int to_return = 0;
@@ -558,6 +571,12 @@ static float lerp(float a, float b, float f)
return a * (1.0f - f) + (b * f);
}
+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;
+ return p_from + distance * p_weight;
+}
+
static V2 V2floor(V2 p)
{
return (V2) { floorf(p.x), floorf(p.y) };