can see the star-map

This commit is contained in:
Tenari
2026-02-14 14:17:16 -06:00
parent 7e5aa71273
commit f0a8de1228
7 changed files with 218 additions and 19 deletions
+18
View File
@@ -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"
}
]
}
+5
View File
@@ -331,6 +331,11 @@ typedef struct Dim2 {
u16 width; u16 width;
} Dim2; } Dim2;
typedef struct Pos2u8 {
u8 x;
u8 y;
} Pos2u8;
typedef struct Pos2 { typedef struct Pos2 {
u16 x; u16 x;
u16 y; u16 y;
+117 -13
View File
@@ -23,9 +23,17 @@
#define GOAL_LOOP_US 1000000/GOAL_LOOPS_PER_S #define GOAL_LOOP_US 1000000/GOAL_LOOPS_PER_S
#define LOGIN_NAME_BUFFER_LEN 16 #define LOGIN_NAME_BUFFER_LEN 16
#define PARSED_SERVER_MESSAGE_THREAD_QUEUE_LEN 16 #define PARSED_SERVER_MESSAGE_THREAD_QUEUE_LEN 16
#define MAIN_GAME_TAB_COUNT (2)
///// TYPES ///// TYPES
typedef enum Tab {
TabDebug,
TabChat,
TabMap,
TabShip,
TabStation,
Tab_Count,
} Tab;
typedef enum Screen { typedef enum Screen {
ScreenLogin, ScreenLogin,
ScreenCreateCharacter, ScreenCreateCharacter,
@@ -75,6 +83,8 @@ typedef struct ParsedServerMessage {
u64 id; u64 id;
u64 server_frame; u64 server_frame;
XYZ xyz; XYZ xyz;
Pos2u8 positions[STAR_SYSTEM_COUNT];
PlanetType planet_types[MAX_PLANETS*STAR_SYSTEM_COUNT];
//Entity entities[PARSED_CLIENT_ENTITY_LEN]; //Entity entities[PARSED_CLIENT_ENTITY_LEN];
//u64 ids[PARSED_IDS_LEN]; //u64 ids[PARSED_IDS_LEN];
} ParsedServerMessage; } ParsedServerMessage;
@@ -115,10 +125,11 @@ typedef struct GameState {
LoginState login_state; LoginState login_state;
MenuState menu; MenuState menu;
MenuState section; // for tabbing through selected "portions" of the screen MenuState section; // for tabbing through selected "portions" of the screen
u8 choices[4];
UDPMessage keep_alive_msg; UDPMessage keep_alive_msg;
UDPClient client; UDPClient client;
StringChunkList message_input; StringChunkList message_input;
StarSystem map[STAR_SYSTEM_COUNT];
Pos2 pos;
} GameState; } GameState;
///// GLOBALS ///// GLOBALS
@@ -130,7 +141,7 @@ global u8 system_message_index = 0;
global GameState state = {0}; global GameState state = {0};
global OutgoingMessageQueue* network_send_queue = {0}; global OutgoingMessageQueue* network_send_queue = {0};
global ParsedServerMessageThreadQueue* network_recv_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 ///// FUNCTIONS
fn ParsedServerMessageThreadQueue* newPSMThreadQueue(Arena* a) { fn ParsedServerMessageThreadQueue* newPSMThreadQueue(Arena* a) {
@@ -359,6 +370,16 @@ fn void handleIncomingMessage(u8* message, u32 len, SocketAddress sender, i32 so
switch (msg_type) { switch (msg_type) {
case MessageNewAccountCreated: case MessageNewAccountCreated:
case MessageBadPw: {/*nothing to parse but the type*/} break; 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: { case MessageCharacterId: {
parsed.id = readU64FromBufferLE(message + 1); parsed.id = readU64FromBufferLE(message + 1);
} break; } break;
@@ -420,6 +441,15 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count
while (next_net_msg != NULL) { while (next_net_msg != NULL) {
msg_iters += 0; msg_iters += 0;
switch (msg.type) { 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: { case MessageNewAccountCreated: {
state->screen = ScreenCreateCharacter; state->screen = ScreenCreateCharacter;
} break; } break;
@@ -445,6 +475,7 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count
} }
// operate on screen-based input+state // 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_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_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; 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 //// SIMULATION
if (state->me.id != 0) { if (state->me.id != 0) {
state->screen = ScreenMainGame; state->screen = ScreenMainGame;
state->menu.selected_index = 0;
state->menu.len = Tab_Count;
break; break;
} }
if (state->section.selected_index == 0) { if (state->section.selected_index == 0) {
@@ -581,21 +614,25 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count
// SIMULATION // SIMULATION
if (input_buffer[0] == 'q' || user_pressed_esc) { if (input_buffer[0] == 'q' || user_pressed_esc) {
should_quit = true; 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 // RENDERING
renderStrToBuffer(tui->frame_buffer, 5, 1, "Games typically would render something here...", screen_dimensions); u32 tabs_y = 1;
bool room_active = state->section.selected_index == 0;
u32 tabs_y = 1 + 2 + 2;
u32 tx = 2; u32 tx = 2;
for (u32 i = 0; i < MAIN_GAME_TAB_COUNT; i++) { for (u32 i = 0; i < Tab_Count; i++) {
u32 tab_len = strlen(TABS[i]); u32 tab_len = strlen(TAB_STRS[i]);
Box b = { .x = tx, .y = tabs_y, .width = tab_len+1, .height = 1 }; 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); 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); tx += (tab_len + 3);
} }
// draw the system messages box // draw the tabs box
u32 box_y = tabs_y + 3; u32 box_y = tabs_y + 3;
Box box = { Box box = {
.x = 1, .x = 1,
@@ -603,11 +640,78 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count
.width = screen_dimensions.width - 4, .width = screen_dimensions.width - 4,
.height = screen_dimensions.height - box_y - 2, .height = screen_dimensions.height - box_y - 2,
}; };
drawAnsiBox(tui->frame_buffer, box, screen_dimensions, !room_active); drawAnsiBox(tui->frame_buffer, box, screen_dimensions, true);
if (state->menu.selected_index == 0) { // 0 is TABS[0] is "Debug" switch (tab) {
case TabDebug: {
renderSystemMessages(tui->frame_buffer, tui->screen_dimensions, box); renderSystemMessages(tui->frame_buffer, tui->screen_dimensions, box);
} else if (state->menu.selected_index == 1) { // 1 is TABS[1] is "Speak" } break;
case TabChat: {
renderStrToBuffer(tui->frame_buffer, box.x+2, box.y+1, "You haven't heard anything interesting lately...", screen_dimensions); 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; } break;
case ScreenLogin: { case ScreenLogin: {
+32
View File
@@ -169,6 +169,38 @@ fn void drawAnsiBox(Pixel* buf, Box box, Dim2 sd, bool bold) {
copyStr(buf[pos+1+box.width].bytes, use[5]); 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) { fn void renderUtf8CharToBuffer(Pixel* buf, u16 x, u16 y, str text, Dim2 screen_dimensions) {
assert(strlen(text) <= UTF8_MAX_WIDTH); assert(strlen(text) <= UTF8_MAX_WIDTH);
u32 pos = x + (screen_dimensions.width*y); u32 pos = x + (screen_dimensions.width*y);
+20
View File
@@ -4,6 +4,26 @@
#define MAX_SCREEN_HEIGHT 300 #define MAX_SCREEN_HEIGHT 300
#define MAX_SCREEN_WIDTH 800 #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) { fn str charForEntity(EntityType e) {
switch (e) { switch (e) {
case EntityStarSystem: case EntityStarSystem:
+19 -2
View File
@@ -626,6 +626,21 @@ fn void* gameLoop(void* params) {
writeU64ToBufferLE(outgoing_message.bytes + 1, account.id); writeU64ToBufferLE(outgoing_message.bytes + 1, account.id);
outgoingMessageQueuePush(state.network_send_queue, &outgoing_message); outgoingMessageQueuePush(state.network_send_queue, &outgoing_message);
printf("MessageCharacterId sent\n"); 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 { } else {
printf("client tried to create a character when he already has one."); 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++) { for (u32 i = 0; i < STAR_SYSTEM_COUNT; i++) {
u32 planet_count = rand() % MAX_PLANETS + 1; u32 planet_count = rand() % MAX_PLANETS + 1;
for (u32 ii = 0; ii < planet_count; ii++) { 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) { switch (state.map[i].planets[ii].type) {
case PlanetTypeEarth: { case PlanetTypeEarth: {
// TODO: roll the initial commodity counts // TODO: roll the initial commodity counts
@@ -734,7 +749,9 @@ i32 main(i32 argc, ptr argv[]) {
case PlanetTypeStation: { case PlanetTypeStation: {
// a lot of ??? drugs? // a lot of ??? drugs?
} break; } break;
case PlanetType_Count: {} break; case PlanetTypeNull:
case PlanetType_Count:
break;
} }
} }
} }
+5 -2
View File
@@ -6,10 +6,11 @@
///// #define some game-tunable constants ///// #define some game-tunable constants
#define STARTING_DOWN_PAYMENT (10000) #define STARTING_DOWN_PAYMENT (10000)
#define SHIP_DETAIL_COUNT (8)
#define STAR_SYSTEM_COUNT (40) #define STAR_SYSTEM_COUNT (40)
#define MAP_WIDTH (40) #define MAP_WIDTH (40)
#define MAP_HEIGHT (10) #define MAP_HEIGHT (10)
#define MAX_PLANETS (16) #define MAX_PLANETS (3)
typedef enum EntityFeature { typedef enum EntityFeature {
FeatureWalksAround, FeatureWalksAround,
@@ -43,7 +44,6 @@ global str SHIP_TYPE_STRINGS[] = {
"NX-400", "NX-400",
}; };
#define SHIP_DETAIL_COUNT (8)
typedef struct ShipTemplate { typedef struct ShipTemplate {
ShipType type; ShipType type;
u8 drive_efficiency; u8 drive_efficiency;
@@ -266,6 +266,7 @@ typedef struct Commodity {
} Commodity; } Commodity;
typedef enum PlanetType { typedef enum PlanetType {
PlanetTypeNull,
PlanetTypeEarth, PlanetTypeEarth,
PlanetTypeGas, PlanetTypeGas,
PlanetTypeMoon, PlanetTypeMoon,
@@ -369,6 +370,7 @@ typedef enum Message {
MessageCharacterId, MessageCharacterId,
MessageBadPw, MessageBadPw,
MessageNewAccountCreated, MessageNewAccountCreated,
MessageStarPositions,
Message_Count, Message_Count,
} Message; } Message;
static const char* MESSAGE_STRINGS[] = { static const char* MESSAGE_STRINGS[] = {
@@ -376,6 +378,7 @@ static const char* MESSAGE_STRINGS[] = {
"CharacterId", "CharacterId",
"BadPw", "BadPw",
"NewAccountCreated", "NewAccountCreated",
"StarPositions",
}; };
#endif //GAMESHARED_H #endif //GAMESHARED_H