diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..eb018be --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,18 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Debug Client", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/build/client", + "args": [], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "environment": [], + "externalConsole": true, + "MIMode": "lldb" + } + ] +} + diff --git a/src/base/all.h b/src/base/all.h index 52a86a0..c033289 100644 --- a/src/base/all.h +++ b/src/base/all.h @@ -331,6 +331,11 @@ typedef struct Dim2 { u16 width; } Dim2; +typedef struct Pos2u8 { + u8 x; + u8 y; +} Pos2u8; + typedef struct Pos2 { u16 x; u16 y; diff --git a/src/client.c b/src/client.c index 6080b24..e0077f1 100644 --- a/src/client.c +++ b/src/client.c @@ -23,9 +23,17 @@ #define GOAL_LOOP_US 1000000/GOAL_LOOPS_PER_S #define LOGIN_NAME_BUFFER_LEN 16 #define PARSED_SERVER_MESSAGE_THREAD_QUEUE_LEN 16 -#define MAIN_GAME_TAB_COUNT (2) ///// TYPES +typedef enum Tab { + TabDebug, + TabChat, + TabMap, + TabShip, + TabStation, + Tab_Count, +} Tab; + typedef enum Screen { ScreenLogin, ScreenCreateCharacter, @@ -75,6 +83,8 @@ typedef struct ParsedServerMessage { u64 id; u64 server_frame; XYZ xyz; + Pos2u8 positions[STAR_SYSTEM_COUNT]; + PlanetType planet_types[MAX_PLANETS*STAR_SYSTEM_COUNT]; //Entity entities[PARSED_CLIENT_ENTITY_LEN]; //u64 ids[PARSED_IDS_LEN]; } ParsedServerMessage; @@ -115,10 +125,11 @@ typedef struct GameState { LoginState login_state; MenuState menu; MenuState section; // for tabbing through selected "portions" of the screen - u8 choices[4]; UDPMessage keep_alive_msg; UDPClient client; StringChunkList message_input; + StarSystem map[STAR_SYSTEM_COUNT]; + Pos2 pos; } GameState; ///// GLOBALS @@ -130,7 +141,7 @@ global u8 system_message_index = 0; global GameState state = {0}; global OutgoingMessageQueue* network_send_queue = {0}; global ParsedServerMessageThreadQueue* network_recv_queue = {0}; -global str TABS[] = {"Debug", "Speak"}; +global str TAB_STRS[Tab_Count] = {"Debug", "Chat", "Map", "Ship", "Station"}; ///// FUNCTIONS fn ParsedServerMessageThreadQueue* newPSMThreadQueue(Arena* a) { @@ -359,6 +370,16 @@ fn void handleIncomingMessage(u8* message, u32 len, SocketAddress sender, i32 so switch (msg_type) { case MessageNewAccountCreated: case MessageBadPw: {/*nothing to parse but the type*/} break; + case MessageStarPositions: { + u32 star_msg_size = 2+MAX_PLANETS; + for (u32 i = 0; i < STAR_SYSTEM_COUNT; i++) { + parsed.positions[i].x = message[1+(i*star_msg_size)]; + parsed.positions[i].y = message[2+(i*star_msg_size)]; + for (u32 ii = 0; ii < MAX_PLANETS; ii++) { + parsed.planet_types[i*MAX_PLANETS + ii] = message[(i*star_msg_size)+3+ii]; + } + } + } break; case MessageCharacterId: { parsed.id = readU64FromBufferLE(message + 1); } break; @@ -420,6 +441,15 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count while (next_net_msg != NULL) { msg_iters += 0; switch (msg.type) { + case MessageStarPositions: { + for (u32 i = 0; i < STAR_SYSTEM_COUNT; i++) { + state->map[i].x = msg.positions[i].x; + state->map[i].y = msg.positions[i].y; + for (u32 ii = 0; ii < MAX_PLANETS; ii++) { + state->map[i].planets[ii].type = msg.planet_types[i*MAX_PLANETS + ii]; + } + } + } break; case MessageNewAccountCreated: { state->screen = ScreenCreateCharacter; } break; @@ -445,6 +475,7 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count } // operate on screen-based input+state + bool user_pressed_tab = input_buffer[0] == ASCII_TAB && input_buffer[1] == 0; bool user_pressed_esc = input_buffer[0] == ASCII_ESCAPE && input_buffer[1] == 0; bool user_pressed_a_number = input_buffer[0] >= '1' && input_buffer[0] <= '9' && input_buffer[1] == 0; bool user_pressed_up = input_buffer[0] == 27 && input_buffer[1] == 91 && input_buffer[2] == 65; @@ -458,6 +489,8 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count //// SIMULATION if (state->me.id != 0) { state->screen = ScreenMainGame; + state->menu.selected_index = 0; + state->menu.len = Tab_Count; break; } if (state->section.selected_index == 0) { @@ -581,21 +614,25 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count // SIMULATION if (input_buffer[0] == 'q' || user_pressed_esc) { should_quit = true; + } else if (user_pressed_tab) { + state->menu.selected_index += 1; + if (state->menu.selected_index > state->menu.len) { + state->menu.selected_index = 0; + } } + Tab tab = state->menu.selected_index; // RENDERING - renderStrToBuffer(tui->frame_buffer, 5, 1, "Games typically would render something here...", screen_dimensions); - bool room_active = state->section.selected_index == 0; - u32 tabs_y = 1 + 2 + 2; + u32 tabs_y = 1; u32 tx = 2; - for (u32 i = 0; i < MAIN_GAME_TAB_COUNT; i++) { - u32 tab_len = strlen(TABS[i]); + for (u32 i = 0; i < Tab_Count; i++) { + u32 tab_len = strlen(TAB_STRS[i]); Box b = { .x = tx, .y = tabs_y, .width = tab_len+1, .height = 1 }; drawAnsiBox(tui->frame_buffer, b, screen_dimensions, state->menu.selected_index == i); - renderStrToBuffer(tui->frame_buffer, tx+1, tabs_y+ 1, TABS[i], screen_dimensions); + renderStrToBuffer(tui->frame_buffer, tx+1, tabs_y+ 1, TAB_STRS[i], screen_dimensions); tx += (tab_len + 3); } - // draw the system messages box + // draw the tabs box u32 box_y = tabs_y + 3; Box box = { .x = 1, @@ -603,11 +640,78 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count .width = screen_dimensions.width - 4, .height = screen_dimensions.height - box_y - 2, }; - drawAnsiBox(tui->frame_buffer, box, screen_dimensions, !room_active); - if (state->menu.selected_index == 0) { // 0 is TABS[0] is "Debug" - renderSystemMessages(tui->frame_buffer, tui->screen_dimensions, box); - } else if (state->menu.selected_index == 1) { // 1 is TABS[1] is "Speak" - renderStrToBuffer(tui->frame_buffer, box.x+2, box.y+1, "You haven't heard anything interesting lately...", screen_dimensions); + drawAnsiBox(tui->frame_buffer, box, screen_dimensions, true); + switch (tab) { + case TabDebug: { + renderSystemMessages(tui->frame_buffer, tui->screen_dimensions, box); + } break; + case TabChat: { + renderStrToBuffer(tui->frame_buffer, box.x+2, box.y+1, "You haven't heard anything interesting lately...", screen_dimensions); + } break; + case TabMap: { + if (user_pressed_up) { + state->pos.y -= 1; + } else if (user_pressed_down) { + state->pos.y += 1; + } else if (user_pressed_left) { + state->pos.x -= 1; + } else if (user_pressed_right) { + state->pos.x += 1; + } + u32 xw = 4; + u32 yh = 2; + u32 x_off = box.x+((box.width - MAP_WIDTH*xw)/2); + u32 y_off = box.y+((box.height - MAP_HEIGHT*yh)/2); + for (u32 i = 0; i < MAP_WIDTH*xw; i++) { + for (u32 ii = 0; ii < MAP_HEIGHT*yh; ii++) { + u32 sysx = i / xw; + u32 sysy = ii / yh; + u32 bufpos = XYToPos(x_off+i, y_off+ii, screen_dimensions.width); + tui->frame_buffer[bufpos].bytes[0] = ' '; + if (state->pos.x == sysx && state->pos.y == sysy) { + tui->frame_buffer[bufpos].background = ANSI_WHITE; + tui->frame_buffer[bufpos].foreground = ANSI_BLACK; + } else { + tui->frame_buffer[bufpos].foreground = ANSI_WHITE; + tui->frame_buffer[bufpos].background = ANSI_BLACK; + } + } + } + for (u32 i = 0; i < STAR_SYSTEM_COUNT; i++) { + StarSystem sys = state->map[i]; + u32 sysx = x_off + xw*sys.x; + u32 sysy = y_off + yh*sys.y; + // clear the render-grid for this "tile" position + for (u32 ii = 0; ii < xw; ii++) { + for (u32 iii = 0; iii < yh; iii++) { + u32 bufpos = XYToPos(sysx+ii, sysy+iii, screen_dimensions.width); + tui->frame_buffer[bufpos].bytes[0] = 0; + } + } + renderUtf8CodePoint(tui, sysx, sysy, "⭐"); + renderUtf8CodePoint( + tui, + sysx +2, + sysy, + strForPlanet(sys.planets[0].type) + ); + renderUtf8CodePoint( + tui, + sysx, + sysy +1, + strForPlanet(sys.planets[1].type) + ); + renderUtf8CodePoint( + tui, + sysx +2, + sysy +1, + strForPlanet(sys.planets[2].type) + ); + } + } break; + case TabShip: {} break; + case TabStation: {} break; + case Tab_Count: {} break; } } break; case ScreenLogin: { diff --git a/src/lib/tui.c b/src/lib/tui.c index 2b10d8c..86360bd 100644 --- a/src/lib/tui.c +++ b/src/lib/tui.c @@ -169,6 +169,38 @@ fn void drawAnsiBox(Pixel* buf, Box box, Dim2 sd, bool bold) { copyStr(buf[pos+1+box.width].bytes, use[5]); } +fn void renderUtf8CodePoint(TuiState* tui, u16 x, u16 y, str text) { + u32 bufpos = XYToPos(x,y,tui->screen_dimensions.width); + Pixel* buf = tui->frame_buffer; + Utf8Character first_character_class = classifyUtf8Character(text[0]); + // if it's a single ASCII character + if (first_character_class == Utf8CharacterAscii && text[1] == '\0') { + buf[bufpos].bytes[0] = text[0]; + // if it's a pair of ASCII characters + } else if (first_character_class == Utf8CharacterAscii && text[1] > 0 && text[2] == '\0') { + buf[bufpos].bytes[0] = text[0]; + buf[bufpos+1].bytes[0] = text[1]; + } else if (first_character_class == Utf8CharacterThreeByte) { + for (u32 k = 0; k < strlen(text)/3; k++) { + for (u32 l = 0; l < 3; l++) { + buf[bufpos+k].bytes[l] = text[l+(k*3)]; + } + } + } else if (first_character_class == Utf8CharacterFourByte) { + for (u32 k = 0; k < strlen(text)/UTF8_MAX_WIDTH; k++) { + for (u32 l = 0; l < UTF8_MAX_WIDTH; l++) { + buf[bufpos+k].bytes[l] = text[l+(k*UTF8_MAX_WIDTH)]; + } + } + // handle secondary bytes that didn't divide evenly + for (u32 k = 0; k < strlen(text)%UTF8_MAX_WIDTH; k++) { + buf[bufpos+1].bytes[k] = text[UTF8_MAX_WIDTH+k]; + } + } else { + assert(false && "unhandled bullshit"); + } +} + fn void renderUtf8CharToBuffer(Pixel* buf, u16 x, u16 y, str text, Dim2 screen_dimensions) { assert(strlen(text) <= UTF8_MAX_WIDTH); u32 pos = x + (screen_dimensions.width*y); diff --git a/src/render.c b/src/render.c index ce4f12b..0c56aaa 100644 --- a/src/render.c +++ b/src/render.c @@ -4,6 +4,26 @@ #define MAX_SCREEN_HEIGHT 300 #define MAX_SCREEN_WIDTH 800 +fn str strForPlanet(PlanetType type) { + switch(type) { + case PlanetTypeNull: + return " "; + case PlanetTypeAsteroid: + return "🪨"; + case PlanetTypeEarth: + return "🌎"; + case PlanetTypeGas: + return "🪐"; + case PlanetTypeMoon: + return "🌘"; + case PlanetTypeStation: + return "\xF0\x9F\x9B\xB0 "; + case PlanetType_Count: + assert(false && "this should never happen"); + return ""; + } +} + fn str charForEntity(EntityType e) { switch (e) { case EntityStarSystem: diff --git a/src/server.c b/src/server.c index a74d415..a00869c 100644 --- a/src/server.c +++ b/src/server.c @@ -626,6 +626,21 @@ fn void* gameLoop(void* params) { writeU64ToBufferLE(outgoing_message.bytes + 1, account.id); outgoingMessageQueuePush(state.network_send_queue, &outgoing_message); printf("MessageCharacterId sent\n"); + + // TODO tell the client about the map + u32 star_msg_size = 2+MAX_PLANETS; + outgoing_message.address = sender; + outgoing_message.bytes_len = 1+(star_msg_size*STAR_SYSTEM_COUNT); + outgoing_message.bytes[0] = (u8)MessageStarPositions; + for (u32 i = 0; i < STAR_SYSTEM_COUNT; i++) { + outgoing_message.bytes[1+(i*star_msg_size)] = (u8)state.map[i].x; + outgoing_message.bytes[2+(i*star_msg_size)] = (u8)state.map[i].y; + for (u32 ii = 0; ii < MAX_PLANETS; ii++) { + outgoing_message.bytes[3+ii+(i*star_msg_size)] = state.map[i].planets[ii].type; + } + } + outgoingMessageQueuePush(state.network_send_queue, &outgoing_message); + printf("%s sent\n", MESSAGE_STRINGS[outgoing_message.bytes[0]]); } else { printf("client tried to create a character when he already has one."); } @@ -715,7 +730,7 @@ i32 main(i32 argc, ptr argv[]) { for (u32 i = 0; i < STAR_SYSTEM_COUNT; i++) { u32 planet_count = rand() % MAX_PLANETS + 1; for (u32 ii = 0; ii < planet_count; ii++) { - state.map[i].planets[ii].type = (PlanetType)(rand() % PlanetType_Count); + state.map[i].planets[ii].type = 1+(PlanetType)(rand() % (PlanetType_Count -1)); switch (state.map[i].planets[ii].type) { case PlanetTypeEarth: { // TODO: roll the initial commodity counts @@ -734,7 +749,9 @@ i32 main(i32 argc, ptr argv[]) { case PlanetTypeStation: { // a lot of ??? drugs? } break; - case PlanetType_Count: {} break; + case PlanetTypeNull: + case PlanetType_Count: + break; } } } diff --git a/src/shared.h b/src/shared.h index a3d7619..e558bc2 100644 --- a/src/shared.h +++ b/src/shared.h @@ -6,10 +6,11 @@ ///// #define some game-tunable constants #define STARTING_DOWN_PAYMENT (10000) +#define SHIP_DETAIL_COUNT (8) #define STAR_SYSTEM_COUNT (40) #define MAP_WIDTH (40) #define MAP_HEIGHT (10) -#define MAX_PLANETS (16) +#define MAX_PLANETS (3) typedef enum EntityFeature { FeatureWalksAround, @@ -43,7 +44,6 @@ global str SHIP_TYPE_STRINGS[] = { "NX-400", }; -#define SHIP_DETAIL_COUNT (8) typedef struct ShipTemplate { ShipType type; u8 drive_efficiency; @@ -266,6 +266,7 @@ typedef struct Commodity { } Commodity; typedef enum PlanetType { + PlanetTypeNull, PlanetTypeEarth, PlanetTypeGas, PlanetTypeMoon, @@ -369,6 +370,7 @@ typedef enum Message { MessageCharacterId, MessageBadPw, MessageNewAccountCreated, + MessageStarPositions, Message_Count, } Message; static const char* MESSAGE_STRINGS[] = { @@ -376,6 +378,7 @@ static const char* MESSAGE_STRINGS[] = { "CharacterId", "BadPw", "NewAccountCreated", + "StarPositions", }; #endif //GAMESHARED_H