diff --git a/src/base/serialize.c b/src/base/serialize.c index ecb304d..2e4c7ea 100644 --- a/src/base/serialize.c +++ b/src/base/serialize.c @@ -12,6 +12,17 @@ fn u64 writeU64ToBufferLE(u8* buffer, u64 value) { return 8;// number of bytes written } +fn u64 writeF32ToBufferLE(u8* buffer, f32 value) { + u32 bits; + memcpy(&bits, &value, sizeof(u32)); // reinterpret float bits as integer + + buffer[0] = (u8)(bits & 0xFF); + buffer[1] = (u8)((bits >> 8) & 0xFF); + buffer[2] = (u8)((bits >> 16) & 0xFF); + buffer[3] = (u8)((bits >> 24) & 0xFF); + return 4;// number of bytes written +} + fn u64 writeU32ToBufferLE(u8* buffer, u32 value) { buffer[0] = (u8)(value & 0xFF); buffer[1] = (u8)((value >> 8) & 0xFF); @@ -45,6 +56,18 @@ fn u64 readU64FromBufferLE(u8 *buffer) { ((u64)buffer[7] << 56); } +fn f32 readF32FromBufferLE(u8 *buf) { + u32 bits = 0; + bits |= (u32)(u8)buf[0] << 0; + bits |= (u32)(u8)buf[1] << 8; + bits |= (u32)(u8)buf[2] << 16; + bits |= (u32)(u8)buf[3] << 24; + + f32 value; + memcpy(&value, &bits, sizeof(f32)); + return value; +} + fn u32 readU32FromBufferLE(u8 *buffer) { return (u32)buffer[0] | ((u32)buffer[1] << 8) | diff --git a/src/client.c b/src/client.c index 63e99bf..81457d6 100644 --- a/src/client.c +++ b/src/client.c @@ -34,6 +34,14 @@ typedef enum Tab { Tab_Count, } Tab; +typedef enum StationTabStates { + StationTabStateTable, + StationTabStateTransact, + StationTabStateResult, + StationTabStateLoading, + StationTabStates_Count, +} StationTabStates; + typedef enum Screen { ScreenLogin, ScreenCreateCharacter, @@ -74,6 +82,12 @@ typedef struct EntityList { Entity* items; } EntityList; +typedef struct TransactionResult { + bool buying; + u32 qty; + u32 cr; +} TransactionResult; + typedef struct ParsedServerMessage { Message type; u16 port; @@ -86,6 +100,8 @@ typedef struct ParsedServerMessage { Pos2u8 positions[STAR_SYSTEM_COUNT]; PlanetType planet_types[MAX_PLANETS*STAR_SYSTEM_COUNT]; StarSystem sys; + PlayerShip ship; + TransactionResult tx_result; //Entity entities[PARSED_CLIENT_ENTITY_LEN]; //u64 ids[PARSED_IDS_LEN]; } ParsedServerMessage; @@ -133,7 +149,8 @@ typedef struct GameState { StarSystem map[STAR_SYSTEM_COUNT]; Pos2 pos; StarSystem current; - bool modal; + StationTabStates station_tab_state; + TransactionResult tx_result; } GameState; ///// GLOBALS @@ -365,15 +382,22 @@ fn void clearServerSentState() { } fn void handleIncomingMessage(u8* message, u32 len, SocketAddress sender, i32 socket) { - Message msg_type = message[0]; + u64 msg_pos = 0; + Message msg_type = message[msg_pos++]; dbg("handleIncomingMessage() of len=%d, message=%s\n", len, MESSAGE_STRINGS[msg_type]); u8List bytes = {len, len, message}; - u64 msg_pos = 1; ParsedServerMessage parsed = {0}; parsed.type = msg_type; switch (msg_type) { case MessageNewAccountCreated: case MessageBadPw: {/*nothing to parse but the type*/} break; + case MessageTransactionResult: { + parsed.tx_result.buying = message[msg_pos++]; + parsed.tx_result.qty = readU32FromBufferLE(message + msg_pos); + msg_pos += 4; + parsed.tx_result.cr = readU32FromBufferLE(message + msg_pos); + msg_pos += 4; + } break; case MessageStarPositions: { u32 star_msg_size = 2+MAX_PLANETS; for (u32 i = 0; i < STAR_SYSTEM_COUNT; i++) { @@ -393,6 +417,36 @@ fn void handleIncomingMessage(u8* message, u32 len, SocketAddress sender, i32 so } } } break; + case MessagePlayerDetails: { + parsed.ship.type = message[msg_pos++]; + parsed.ship.drive_efficiency = message[msg_pos++]; + parsed.ship.life_support_efficiency = message[msg_pos++]; + parsed.ship.vacuum_cargo_slots = readU16FromBufferLE(message + msg_pos); + msg_pos += 2; + parsed.ship.climate_cargo_slots = readU16FromBufferLE(message + msg_pos); + msg_pos += 2; + parsed.ship.passenger_berths = readU16FromBufferLE(message + msg_pos); + msg_pos += 2; + parsed.ship.passenger_amenities_flags = readU16FromBufferLE(message + msg_pos); + msg_pos += 2; + parsed.ship.smugglers_hold_cu_m = readU16FromBufferLE(message + msg_pos); + msg_pos += 2; + parsed.ship.remaining_mortgage = readU32FromBufferLE(message + msg_pos); + msg_pos += 4; + parsed.ship.interest_rate = readF32FromBufferLE(message + msg_pos); + msg_pos += 4; + parsed.ship.cu_m_fuel = readU32FromBufferLE(message + msg_pos); + msg_pos += 4; + parsed.ship.cu_m_o2 = readU32FromBufferLE(message + msg_pos); + msg_pos += 4; + parsed.ship.credits = readF32FromBufferLE(message + msg_pos); + msg_pos += 4; + parsed.ship.id = readU64FromBufferLE(message + msg_pos); + msg_pos += 8; + for (u32 i = 0; i < Commodity_Count; i++, msg_pos += 4) { + parsed.ship.commodities[i] = readU32FromBufferLE(message + msg_pos); + } + } break; case MessageCharacterId: { parsed.id = readU64FromBufferLE(message + 1); } break; @@ -446,6 +500,7 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count fflush(stdout); return should_quit; } + char sbuf[512] = {0}; // process server messages u32 msg_iters = 0; @@ -454,6 +509,30 @@ 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 MessageTransactionResult: { + state->station_tab_state = StationTabStateResult; + MemoryCopyStruct(&state->tx_result, &msg.tx_result); + } break; + case MessagePlayerDetails: { + if (msg.ship.id == state->me.id) { + state->me.type = msg.ship.type; + state->me.drive_efficiency = msg.ship.drive_efficiency; + state->me.life_support_efficiency = msg.ship.life_support_efficiency; + state->me.vacuum_cargo_slots = msg.ship.vacuum_cargo_slots; + state->me.climate_cargo_slots = msg.ship.climate_cargo_slots; + state->me.passenger_berths = msg.ship.passenger_berths; + state->me.passenger_amenities_flags = msg.ship.passenger_amenities_flags; + state->me.smugglers_hold_cu_m = msg.ship.smugglers_hold_cu_m; + state->me.remaining_mortgage = msg.ship.remaining_mortgage; + state->me.interest_rate = msg.ship.interest_rate; + state->me.cu_m_fuel = msg.ship.cu_m_fuel; + state->me.cu_m_o2 = msg.ship.cu_m_o2; + state->me.credits = msg.ship.credits; + for (u32 i = 0; i < Commodity_Count; i++) { + state->me.commodities[i] = msg.ship.commodities[i]; + } + } + } break; case MessageStarPositions: { for (u32 i = 0; i < STAR_SYSTEM_COUNT; i++) { state->map[i].x = msg.positions[i].x; @@ -557,7 +636,6 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count } //// RENDERING - char tmp_buffer[64] = {0}; u16 line = 2; // 1. draw the outline Box b = { .x = 2, .y = line, .width = screen_dimensions.width - 5, .height = screen_dimensions.height - 5 }; @@ -581,8 +659,8 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count f32 rm = r / 12.0; u32 n = 12 * 4; u32 payment = principal_borrowed * (rm*pow((1.0+rm),n))/(pow((1+rm),n) - 1); - sprintf(tmp_buffer, "Mortgage: $%d @ %f%% = $%d minimum payment per turn", selected_ship_cost - STARTING_DOWN_PAYMENT, mortgage_rate, payment); - renderStrToBuffer(tui->frame_buffer, 5, ++line, tmp_buffer, screen_dimensions); + sprintf(sbuf, "Mortgage: $%d @ %f%% = $%d minimum payment per turn", selected_ship_cost - STARTING_DOWN_PAYMENT, mortgage_rate, payment); + renderStrToBuffer(tui->frame_buffer, 5, ++line, sbuf, screen_dimensions); line++; // the table TableDrawInfo info = { @@ -701,13 +779,33 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count if (input_buffer[0] == 'q' || user_pressed_esc) { should_quit = true; } + renderStrToBuffer(tui->frame_buffer, box.x+2, box.y+1, SHIP_TYPE_STRINGS[state->me.type], screen_dimensions); + renderStrToBuffer(tui->frame_buffer, box.x+2, box.y+2, "Credits: ", screen_dimensions); + MemoryZero(sbuf, 512); + sprintf(sbuf, "%.2f", state->me.credits); + renderStrToBuffer(tui->frame_buffer, box.x+15, box.y+2, sbuf, screen_dimensions); + for (u32 i = 0; i < Commodity_Count; i++) { + MemoryZero(sbuf, 512); + sprintf(sbuf, "%-42s %d", COMMODITY_STRINGS[i], state->me.commodities[i]); + renderStrToBuffer(tui->frame_buffer, box.x+4, box.y+4+i, sbuf, screen_dimensions); + } } break; case TabStation: { if (input_buffer[0] == 'q' || user_pressed_esc) { - if (state->modal) { - state->modal = !state->modal; - } else { - should_quit = true; + switch (state->station_tab_state) { + case StationTabStateTable: + should_quit = true; + break; + case StationTabStateTransact: + state->station_tab_state = StationTabStateTable; + break; + case StationTabStateResult: + state->station_tab_state = StationTabStateTransact; + break; + case StationTabStateLoading: + state->station_tab_state = StationTabStateTable; + break; + case StationTabStates_Count: assert(false && "something has gone horribly wrong"); } } if (user_pressed_up) { @@ -744,52 +842,130 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count } renderTable(tui, info, state->row.selected_index, MARKET_COMMODITY_FIELDS, 1, rows, sizeof(MarketCommodity)); - if (user_pressed_enter) { - state->modal = !state->modal; - if (state->modal) { - state->modal_choice.len = 2; - state->modal_choice.selected_index = 0; - } - } - if (state->modal) { + if (state->station_tab_state != StationTabStateTable) { if (user_pressed_right) { state->modal_choice.selected_index = 1; } else if (user_pressed_left) { state->modal_choice.selected_index = 0; + } else if (user_pressed_a_number) { + String next_char = { 1, 2, (char*)input_buffer }; + stringChunkListAppend(&state->string_arena, &state->message_input, next_char); + } else if (user_pressed_backspace) { + stringChunkListDeleteLast(&state->string_arena, &state->message_input); } - // draw the "purchase order" modal + + // draw the modal + bool buy_selected = state->modal_choice.selected_index == 0; Box modal_outline = { .x = (tui->screen_dimensions.width - 50) / 2, .y = (tui->screen_dimensions.height - 15) / 2, .height = 15, .width = 50, }; + clearBox(tui, modal_outline); drawAnsiBox(tui->frame_buffer, modal_outline, tui->screen_dimensions, false); - str cname = COMMODITIES[state->row.selected_index].name; - renderStrToBuffer(tui->frame_buffer, modal_outline.x+(modal_outline.width/2)-strlen(cname), modal_outline.y+1, cname, screen_dimensions); u32 y_off = modal_outline.y+2; - u32 buy_x = modal_outline.x+2; - u32 sell_x = modal_outline.x+modal_outline.width-6; - renderStrToBuffer(tui->frame_buffer, buy_x, y_off, "Buy", screen_dimensions); - renderStrToBuffer(tui->frame_buffer, sell_x, y_off, "Sell", screen_dimensions); - if (state->modal_choice.selected_index == 0) { - u32 pos = XYToPos(buy_x, y_off, tui->screen_dimensions.width); - tui->frame_buffer[pos].background = ANSI_WHITE; - tui->frame_buffer[pos].foreground = ANSI_BLACK; - tui->frame_buffer[pos+1].background = ANSI_WHITE; - tui->frame_buffer[pos+1].foreground = ANSI_BLACK; - tui->frame_buffer[pos+2].background = ANSI_WHITE; - tui->frame_buffer[pos+2].foreground = ANSI_BLACK; - } else { - u32 pos = XYToPos(sell_x, y_off, tui->screen_dimensions.width); - tui->frame_buffer[pos].background = ANSI_WHITE; - tui->frame_buffer[pos].foreground = ANSI_BLACK; - tui->frame_buffer[pos+1].background = ANSI_WHITE; - tui->frame_buffer[pos+1].foreground = ANSI_BLACK; - tui->frame_buffer[pos+2].background = ANSI_WHITE; - tui->frame_buffer[pos+2].foreground = ANSI_BLACK; - tui->frame_buffer[pos+3].background = ANSI_WHITE; - tui->frame_buffer[pos+3].foreground = ANSI_BLACK; + str cname = COMMODITIES[state->row.selected_index].name; + if (state->station_tab_state == StationTabStateResult) { + if (state->tx_result.buying) { + renderStrToBuffer(tui->frame_buffer, modal_outline.x+((modal_outline.width-10)/2), y_off++, "Purchased!", screen_dimensions); + } else { + renderStrToBuffer(tui->frame_buffer, modal_outline.x+((modal_outline.width-5)/2), y_off++, "Sold!", screen_dimensions); + } + MemoryZero(sbuf, 512); + sprintf(sbuf, "%d %s", state->tx_result.qty, cname); + renderStrToBuffer(tui->frame_buffer, modal_outline.x+((modal_outline.width-strlen(sbuf))/2), y_off++, sbuf, screen_dimensions); + renderStrToBuffer(tui->frame_buffer, modal_outline.x+((modal_outline.width-3)/2), y_off++, "for", screen_dimensions); + MemoryZero(sbuf, 512); + sprintf(sbuf, "%d credits", state->tx_result.cr); + renderStrToBuffer(tui->frame_buffer, modal_outline.x+((modal_outline.width-strlen(sbuf))/2), y_off++, sbuf, screen_dimensions); + y_off++; + u32 exit_x = modal_outline.x+((modal_outline.width-4)/2); + u32 pos = XYToPos(exit_x, y_off, tui->screen_dimensions.width); + for (u32 i = 0; i < 4; i++) { + tui->frame_buffer[pos+i].background = ANSI_WHITE; + tui->frame_buffer[pos+i].foreground = ANSI_BLACK; + } + tui->cursor.x = exit_x; + tui->cursor.y = y_off; + renderStrToBuffer(tui->frame_buffer, exit_x, y_off++, "EXIT", screen_dimensions); + + if (user_pressed_enter) { + state->station_tab_state = StationTabStateTable; + } + } else if (state->station_tab_state == StationTabStateLoading) { + renderStrToBuffer(tui->frame_buffer, modal_outline.x+2, y_off, "Loading...", screen_dimensions); + } else { // StationTabStateTransact + // draw the "purchase order" modal + renderStrToBuffer(tui->frame_buffer, modal_outline.x+(modal_outline.width/2)-strlen(cname), modal_outline.y+1, cname, screen_dimensions); + u32 buy_x = modal_outline.x+2; + u32 sell_x = modal_outline.x+modal_outline.width-6; + renderStrToBuffer(tui->frame_buffer, buy_x, y_off, "Buy", screen_dimensions); + renderStrToBuffer(tui->frame_buffer, sell_x, y_off, "Sell", screen_dimensions); + if (buy_selected) { + u32 pos = XYToPos(buy_x, y_off, tui->screen_dimensions.width); + tui->frame_buffer[pos].background = ANSI_WHITE; + tui->frame_buffer[pos].foreground = ANSI_BLACK; + tui->frame_buffer[pos+1].background = ANSI_WHITE; + tui->frame_buffer[pos+1].foreground = ANSI_BLACK; + tui->frame_buffer[pos+2].background = ANSI_WHITE; + tui->frame_buffer[pos+2].foreground = ANSI_BLACK; + } else { + u32 pos = XYToPos(sell_x, y_off, tui->screen_dimensions.width); + tui->frame_buffer[pos].background = ANSI_WHITE; + tui->frame_buffer[pos].foreground = ANSI_BLACK; + tui->frame_buffer[pos+1].background = ANSI_WHITE; + tui->frame_buffer[pos+1].foreground = ANSI_BLACK; + tui->frame_buffer[pos+2].background = ANSI_WHITE; + tui->frame_buffer[pos+2].foreground = ANSI_BLACK; + tui->frame_buffer[pos+3].background = ANSI_WHITE; + tui->frame_buffer[pos+3].foreground = ANSI_BLACK; + } + y_off++; + renderStrToBuffer(tui->frame_buffer, modal_outline.x+((modal_outline.width-8)/2), y_off++, "Quantity", screen_dimensions); + renderStringChunkList(tui, &state->message_input, modal_outline.x+8, y_off); + tui->cursor.x = modal_outline.x + 8 + state->message_input.total_size; + tui->cursor.y = y_off++; + if (buy_selected) { + renderStrToBuffer(tui->frame_buffer, modal_outline.x+((modal_outline.width-4)/2), y_off++, "Cost", screen_dimensions); + } else { + renderStrToBuffer(tui->frame_buffer, modal_outline.x+((modal_outline.width-6)/2), y_off++, "Profit", screen_dimensions); + } + String input_q_str = stringChunkToString(&permanent_arena, state->message_input); + u32 input_quantity = atoi(input_q_str.bytes); + u32 credits = input_quantity * (buy_selected ? rows[state->row.selected_index].ask : rows[state->row.selected_index].bid); + arenaDealloc(&permanent_arena, input_q_str.capacity); + MemoryZero(sbuf, 512); + sprintf(sbuf, "%d", credits); + renderStrToBuffer(tui->frame_buffer, modal_outline.x+8, y_off++, sbuf, screen_dimensions); + + if (user_pressed_enter) { + // send the "purchase" or "sell" message to server + // get back new "status" of things + u32 msg_idx = 0; + UDPMessage msg = {0}; + msg.address = udp->server_address; + // 1. msg type/CommandType + msg.bytes[msg_idx++] = CommandTransact; + // 2. buy? + msg.bytes[msg_idx++] = buy_selected; + // 3. quantity + msg_idx += writeU32ToBufferLE(msg.bytes + msg_idx, input_quantity); + // 4. commodity + msg.bytes[msg_idx++] = rows[state->row.selected_index].type; + msg.bytes_len = msg_idx; + + outgoingMessageQueuePush(network_send_queue, &msg); + + state->station_tab_state = StationTabStateLoading; + } + } + } else { + // we are on the table + if (user_pressed_enter) { + state->station_tab_state = StationTabStateTransact; + state->modal_choice.len = 2; + state->modal_choice.selected_index = 0; } } } break; diff --git a/src/lib/tui.c b/src/lib/tui.c index 8a2654c..57d3d79 100644 --- a/src/lib/tui.c +++ b/src/lib/tui.c @@ -95,15 +95,6 @@ fn RGB rgbDarken(RGB color, f32 factor) { return result; } -/* -fn RGB ansiToRGB(u8 ansi) { - ansi -= 16; - u8 b = ansi % 51; - ansi -= () - u8 g = -} -*/ - fn bool rgbEq(RGB a, RGB b) { return a.r == b.r && a.g == b.g && a.b == b.b; } @@ -137,6 +128,20 @@ fn void copyStr(u8* bytes, str cstring) { } } +fn void clearBox(TuiState* tui, Box box) { + for (u32 i = 0; i < box.height; i++) { + for (u32 ii = 0; ii < box.width; ii++) { + u32 pos = XYToPos(box.x+ii, box.y+i, tui->screen_dimensions.width); + tui->frame_buffer[pos].background = 0; + tui->frame_buffer[pos].foreground = 0; + tui->frame_buffer[pos].bytes[0] = 0; + tui->frame_buffer[pos].bytes[1] = 0; + tui->frame_buffer[pos].bytes[2] = 0; + tui->frame_buffer[pos].bytes[3] = 0; + } + } +} + fn void drawAnsiBox(Pixel* buf, Box box, Dim2 sd, bool bold) { str items[] = {"┌","─","┐","│","└","┘"}; str b_items[] = {"┏","━","┓","┃","┗","┛"}; diff --git a/src/server.c b/src/server.c index 5f9a58b..8660b7e 100644 --- a/src/server.c +++ b/src/server.c @@ -42,6 +42,8 @@ typedef struct ParsedClientCommand { u16 alt_port; u32 sender_ip; u32 alt_ip; + u32 qty; + CommodityType commodity; StringChunkList name; StringChunkList pass; u64 id; @@ -369,6 +371,80 @@ fn u32 findClientHandleBySocketAddress(ClientList* clients, SocketAddress addres return 0; } +fn StarSystem* findAccountsSystem(Account* a) { + for (u32 i = 0; i < STAR_SYSTEM_COUNT; i++) { + if (state.map[i].x == a->pos.x && state.map[i].y == a->pos.y) { + return &state.map[i]; + } + } + return &state.map[0]; +} + +fn u32 starSystemPlanetCount(StarSystem* sys) { + u32 planet_count = 0; + for (u32 i = 0; i < MAX_PLANETS; i++) { + if (sys->planets[i].type != PlanetTypeNull) { + planet_count++; + } + } + return planet_count; +} + +fn UDPMessage makeMessageSystemCommodities(StarSystem* sys) { + UDPMessage outgoing_message = {0}; + u32 planet_count = starSystemPlanetCount(sys); + u32 msg_i = 0; + outgoing_message.bytes[msg_i++] = (u8)MessageSystemCommodities; + outgoing_message.bytes[msg_i++] = (u8)sys->idx; + outgoing_message.bytes[msg_i++] = (u8)planet_count; + for (u32 i = 0; i < planet_count; i++) { + for (u32 ii = 0; ii < Commodity_Count; ii++) { + outgoing_message.bytes[msg_i++] = sys->planets[i].commodities[ii]; + } + } + outgoing_message.bytes_len = msg_i; + return outgoing_message; +} + +fn void sendMessageTransactionResult(SocketAddress addr, u32 qty, bool buying, u32 credits) { + UDPMessage outgoing_message = {.address = addr}; + u32 msg_i = 0; + outgoing_message.bytes[msg_i++] = (u8)MessageTransactionResult; + outgoing_message.bytes[msg_i++] = buying; + msg_i += writeU32ToBufferLE(outgoing_message.bytes + msg_i, qty); + msg_i += writeU32ToBufferLE(outgoing_message.bytes + msg_i, credits); + outgoing_message.bytes_len = msg_i; + outgoingMessageQueuePush(state.network_send_queue, &outgoing_message); + printf("%s sent\n", MESSAGE_STRINGS[outgoing_message.bytes[0]]); +} + +fn void sendMessagePlayerDetails(PlayerShip ship, SocketAddress addr) { + UDPMessage outgoing_message = {.address = addr}; + u32 msg_i = 0; + outgoing_message.bytes[msg_i++] = (u8)MessagePlayerDetails; + outgoing_message.bytes[msg_i++] = ship.type; + outgoing_message.bytes[msg_i++] = ship.drive_efficiency; + outgoing_message.bytes[msg_i++] = ship.life_support_efficiency; + msg_i += writeU16ToBufferLE(outgoing_message.bytes + msg_i, ship.vacuum_cargo_slots); + msg_i += writeU16ToBufferLE(outgoing_message.bytes + msg_i, ship.climate_cargo_slots); + msg_i += writeU16ToBufferLE(outgoing_message.bytes + msg_i, ship.passenger_berths); + msg_i += writeU16ToBufferLE(outgoing_message.bytes + msg_i, ship.passenger_amenities_flags); + msg_i += writeU16ToBufferLE(outgoing_message.bytes + msg_i, ship.smugglers_hold_cu_m); + msg_i += writeU32ToBufferLE(outgoing_message.bytes + msg_i, ship.remaining_mortgage); + msg_i += writeF32ToBufferLE(outgoing_message.bytes + msg_i, ship.interest_rate); + msg_i += writeU32ToBufferLE(outgoing_message.bytes + msg_i, ship.cu_m_fuel); + msg_i += writeU32ToBufferLE(outgoing_message.bytes + msg_i, ship.cu_m_o2); + msg_i += writeF32ToBufferLE(outgoing_message.bytes + msg_i, ship.credits); + msg_i += writeU64ToBufferLE(outgoing_message.bytes + msg_i, ship.id); + for (u32 i = 0; i < Commodity_Count; i++) { + msg_i += writeU32ToBufferLE(outgoing_message.bytes + msg_i, ship.commodities[i]); + } + + outgoing_message.bytes_len = msg_i; + outgoingMessageQueuePush(state.network_send_queue, &outgoing_message); + printf("%s sent\n", MESSAGE_STRINGS[outgoing_message.bytes[0]]); +} + fn void handleIncomingMessage(u8* message, u32 len, SocketAddress sender, i32 socket) { dbg("%d: %s from %s:%d\n", len, command_type_strings[message[0]], inet_ntoa(sender.sin_addr), sender.sin_port); u32 msg_idx = 0; @@ -420,6 +496,12 @@ fn void handleIncomingMessage(u8* message, u32 len, SocketAddress sender, i32 so } break; case CommandKeepAlive: break; + case CommandTransact: { + printf("command transact received\n"); + parsed.byte = message[1]; // buy? + parsed.qty = readU32FromBufferLE(message + 2); + parsed.commodity = message[6]; + } break; case CommandCreateCharacter: { printf("command create character received\n"); parsed.byte = message[1]; @@ -475,9 +557,25 @@ fn void* sendNetworkUpdates(void* sock) { // continue; // they are still creating their character //} + // update allthe changed systems + for (u32 i = 0; i < STAR_SYSTEM_COUNT; i++) { + if (state.map[i].changed == true) { + UDPMessage msg_data = makeMessageSystemCommodities(&state.map[i]); + u8List msg = { + .capacity = UDP_MAX_MESSAGE_LEN, + .items = msg_data.bytes, + .length = msg_data.bytes_len, + }; + sendUDPu8List(socket, &client.address, &msg); + } + } } } unlockMutex(&state.client_mutex); + for (u32 i = 0; i < STAR_SYSTEM_COUNT; i++) { + state.map[i].changed = false; + } + u32 loop_duration = osTimeMicrosecondsNow() - loop_start; i32 remaining_time = GOAL_NETWORK_SEND_LOOP_US - loop_duration; if (remaining_time > 0) { @@ -522,6 +620,56 @@ fn void* gameLoop(void* params) { u32 client_handle = findClientHandleBySocketAddress(&state.clients, sender); Client* client = &state.clients.items[client_handle]; switch (msg.type) { + case CommandTransact: { + printf("Transact for client_handle=%d, on #%lld", client_handle, state.frame); + bool is_buying_from_system = msg.byte; + Account* account = &state.accounts[client->account_id]; + StarSystem* sys = findAccountsSystem(account); + printf("%d %lld %s\n", account->id, account->ship.id, sys->name); + u32 total_available = sys->planets[0].commodities[msg.commodity] + sys->planets[1].commodities[msg.commodity] + sys->planets[2].commodities[msg.commodity]; + u32 qty_traded = 0; + u32 credit_value = 0; + if (is_buying_from_system) { + printf("is buying from system\n"); + for (u32 amount_to_buy = Min(msg.qty, total_available); amount_to_buy > 0; amount_to_buy--, total_available--) { + u32 price = priceForCommodity(msg.commodity, total_available, false); + if (price > account->ship.credits) { + amount_to_buy = 0; + break; + } + credit_value += price; + account->ship.credits -= price; + account->ship.commodities[msg.commodity] += 1; + qty_traded += 1; + if (sys->planets[0].commodities[msg.commodity]) { + sys->planets[0].commodities[msg.commodity] -= 1; + } else if (sys->planets[1].commodities[msg.commodity]) { + sys->planets[1].commodities[msg.commodity] -= 1; + } else if (sys->planets[2].commodities[msg.commodity]) { + sys->planets[2].commodities[msg.commodity] -= 1; + } + } + } else { + // selling to system logic + qty_traded = Min(msg.qty, account->ship.commodities[msg.commodity]); + u32 planet_count = starSystemPlanetCount(sys); + for ( + u32 amount_to_sell = qty_traded; + amount_to_sell > 0; + amount_to_sell--, total_available++ + ) { + u32 price = priceForCommodity(msg.commodity, total_available, true); + credit_value += price; + account->ship.credits += price; + account->ship.commodities[msg.commodity] -= 1; + u8 planet_idx = rand() % planet_count; + sys->planets[planet_idx].commodities[msg.commodity] += 1; + } + } + sys->changed = true; + sendMessagePlayerDetails(account->ship, sender); + sendMessageTransactionResult(sender, qty_traded, is_buying_from_system, credit_value); + } break; case CommandKeepAlive: { dbg("KeepAlive for client_handle=%d, %ld", client_handle, state.frame); state.clients.items[client_handle].last_ping = state.frame; @@ -573,6 +721,7 @@ fn void* gameLoop(void* params) { if (state.accounts[i].id == 0 && state.accounts[i].name.length == 0) { existing_account = &state.accounts[i]; existing_account->id = i; + break; } } existing_account->name = name; @@ -582,7 +731,7 @@ fn void* gameLoop(void* params) { if (existing_account->ship.id != 0) { // tell the client their account id outgoing_message.bytes[0] = (u8)MessageCharacterId; - writeU64ToBufferLE(outgoing_message.bytes + 1, existing_account->id); + writeU64ToBufferLE(outgoing_message.bytes + 1, existing_account->ship.id); outgoing_message.bytes_len = 9; outgoing_message.address = sender; outgoingMessageQueuePush(state.network_send_queue, &outgoing_message); @@ -598,8 +747,8 @@ fn void* gameLoop(void* params) { printf("client_handle=%d, acct_id=%d\n", client_handle, existing_account->id); } break; case CommandCreateCharacter: { - Account account = state.accounts[client->account_id]; - if (account.ship.id == 0) { + Account* account = &state.accounts[client->account_id]; + if (account->ship.id == 0) { ShipTemplate template = SHIPS[msg.byte]; PlayerShip player_ship = { .type = msg.byte, @@ -613,26 +762,29 @@ fn void* gameLoop(void* params) { .base_cost = template.base_cost, .remaining_mortgage = template.base_cost - STARTING_DOWN_PAYMENT, .interest_rate = calcInterestRate(template.base_cost, STARTING_DOWN_PAYMENT), + .credits = 10000.0, //.cu_m_fuel; // we are just saying you can buy as much fuel as you want //.cu_m_o2; // we are just saying you can buy as much o2 as you want .id = ++state.next_eid, // start from 1 }; - account.ship = player_ship; - printf("ship_type=%s, client_handle=%d, acct_id=%d\n", SHIP_TYPE_STRINGS[msg.byte], client_handle, account.id); + account->ship = player_ship; + printf("ship_type=%s, client_handle=%d, acct_id=%d\n", SHIP_TYPE_STRINGS[msg.byte], client_handle, account->id); u32 starting_system_idx = rand() % STAR_SYSTEM_COUNT; StarSystem starting_system = state.map[starting_system_idx]; - account.pos.x = starting_system.x; - account.pos.y = starting_system.y; + account->pos.x = starting_system.x; + account->pos.y = starting_system.y; // tell the client their account id outgoing_message.address = sender; outgoing_message.bytes_len = 9; outgoing_message.bytes[0] = (u8)MessageCharacterId; - writeU64ToBufferLE(outgoing_message.bytes + 1, account.id); + writeU64ToBufferLE(outgoing_message.bytes + 1, account->ship.id); outgoingMessageQueuePush(state.network_send_queue, &outgoing_message); printf("MessageCharacterId sent\n"); - // TODO tell the client about the map + sendMessagePlayerDetails(account->ship, sender); + + // 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); @@ -647,25 +799,10 @@ fn void* gameLoop(void* params) { outgoingMessageQueuePush(state.network_send_queue, &outgoing_message); printf("%s sent\n", MESSAGE_STRINGS[outgoing_message.bytes[0]]); - u32 planet_count = 0; - for (u32 i = 0; i < MAX_PLANETS; i++) { - if (starting_system.planets[i].type != PlanetTypeNull) { - planet_count++; - } - } - outgoing_message.bytes_len = 1 + 1 + 1 + (planet_count * Commodity_Count); - u32 msg_i = 0; - outgoing_message.bytes[msg_i++] = (u8)MessageSystemCommodities; - outgoing_message.bytes[msg_i++] = (u8)starting_system_idx; - outgoing_message.bytes[msg_i++] = (u8)planet_count; - for (u32 i = 0; i < planet_count; i++) { - for (u32 ii = 0; ii < Commodity_Count; ii++) { - outgoing_message.bytes[msg_i++] = starting_system.planets[i].commodities[ii]; - } - } + outgoing_message = makeMessageSystemCommodities(&starting_system); + outgoing_message.address = sender; 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."); } @@ -738,6 +875,7 @@ i32 main(i32 argc, ptr argv[]) { state.clients.items = (Client*)arenaAllocArray(&permanent_arena, Client, SERVER_MAX_CLIENTS); // set position of all star systems for (u32 i = 0; i < STAR_SYSTEM_COUNT; i++) { + state.map[i].idx = i; state.map[i].name = STAR_NAMES[i]; state.map[i].crime = rand() % 100; state.map[i].x = rand() % MAP_WIDTH; diff --git a/src/shared.h b/src/shared.h index 26dc955..ee9ce9d 100644 --- a/src/shared.h +++ b/src/shared.h @@ -341,9 +341,11 @@ typedef struct Planet { } Planet; typedef struct StarSystem { + bool changed; u32 x; u32 y; u32 crime; + u32 idx; str name; Planet planets[MAX_PLANETS]; } StarSystem; @@ -413,6 +415,7 @@ typedef enum CommandType { CommandKeepAlive, CommandLogin, CommandCreateCharacter, + CommandTransact, CommandType_Count, } CommandType; static const char* command_type_strings[] = { @@ -431,6 +434,8 @@ typedef enum Message { MessageNewAccountCreated, MessageStarPositions, MessageSystemCommodities, + MessagePlayerDetails, + MessageTransactionResult, Message_Count, } Message; static const char* MESSAGE_STRINGS[] = { @@ -440,6 +445,8 @@ static const char* MESSAGE_STRINGS[] = { "NewAccountCreated", "StarPositions", "SystemCommodities", + "PlayerDetails", + "TransactionResult", }; #endif //GAMESHARED_H