From c9f33d9b0e41d111f71cc1560979cb9207b68596 Mon Sep 17 00:00:00 2001 From: Tenari Date: Thu, 26 Mar 2026 07:28:10 -0500 Subject: [PATCH] basic auction process works, but I still need to restrict the purchase/sale of these commodities on the Market tab --- src/client.c | 93 ++++++++++++++++++++++++++++++++++++++++++---------- src/server.c | 38 +++++++++++++++++++-- src/shared.h | 10 ++++++ 3 files changed, 121 insertions(+), 20 deletions(-) diff --git a/src/client.c b/src/client.c index 26edd07..fee8b53 100644 --- a/src/client.c +++ b/src/client.c @@ -65,6 +65,9 @@ typedef enum PassengersTabStates { typedef enum AuctionTabStates { AuctionTabStateMain, AuctionTabStateLoading, + AuctionTabStateNotEnoughMoney, + AuctionTabStateNotEnoughCargoSpace, + AuctionTabStateAlreadyFinished, AuctionTabStateResult, AuctionTabStates_Count, } AuctionTabStates; @@ -348,6 +351,7 @@ fn void handleIncomingMessage(u8* message, u32 len, SocketAddress sender, i32 so case MessageNewAccountCreated: case MessageNotAlive: case MessageBadPw: {/*nothing to parse but the type*/} break; + case MessageAuctionBidResult: // same as GameOver case MessageGameOver: { parsed.byte = message[msg_pos++]; } break; @@ -565,6 +569,26 @@ 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 MessageAuctionBidResult: { + AuctionBidResult result = msg.byte; + switch (result) { + case AuctionBidResultNotEnoughMoney: + state->auction_tab_state = AuctionTabStateNotEnoughMoney; + break; + case AuctionBidResultNotEnoughCargoSpace: + state->auction_tab_state = AuctionTabStateNotEnoughCargoSpace; + break; + case AuctionBidResultPurchased: + state->auction_tab_state = AuctionTabStateResult; + break; + case AuctionBidResultAuctionAlreadyFinished: + state->auction_tab_state = AuctionTabStateAlreadyFinished; + break; + case AuctionBidResult_Count: + assert(false && "unhandled message result"); + break; + } + } break; case MessageNotAlive: { // auto-re-login if (state->me.base_cost > 0) { @@ -1272,10 +1296,20 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count } break; case TabAuction: { u32 yoff = box.y+1; + drawStatusLine(tui, sbuf, &state->me, box.x+2, yoff); + yoff++; + renderStrToBuffer(tui->frame_buffer, box.x+((box.width - 13)/2), yoff++, "Auction House", screen_dimensions); yoff++; if (curr.auction.finished_at > curr.auction.started_at) { + if (state->auction_tab_state == AuctionTabStateResult) { + renderStrToBuffer(tui->frame_buffer, box.x+2, yoff++, "Your bid was accepted", screen_dimensions); + yoff++; + } else if (state->auction_tab_state == AuctionTabStateAlreadyFinished) { + renderStrToBuffer(tui->frame_buffer, box.x+2, yoff++, "Your bid was too late..", screen_dimensions); + yoff++; + } MemoryZero(sbuf, SBUFLEN); sprintf(sbuf, "Sold! %d containers of %s were sold for $%d", curr.auction.qty, COMMODITY_STRINGS[curr.auction.type], curr.auction.price); renderStrToBuffer(tui->frame_buffer, box.x+2, yoff++, sbuf, screen_dimensions); @@ -1294,25 +1328,48 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count renderStrToBuffer(tui->frame_buffer, box.x+2, yoff++, sbuf, screen_dimensions); yoff++; - const char* label = "ACCEPT OFFER [ENTER]"; - Range1u32 range = { - .min = XYToPos(box.x+6, yoff, screen_dimensions.width), - .max = XYToPos(box.x+6+strlen(label), yoff, screen_dimensions.width), - }; - tui->cursor.x = box.x+6; - tui->cursor.y = yoff; - colorizeRange(tui, range, ANSI_WHITE, ANSI_BLACK); - renderStrToBuffer(tui->frame_buffer, box.x+6, yoff++, label, screen_dimensions); - if (user_pressed_enter) { - // send the "buy this auction" message to the server - u32 msg_idx = 0; - UDPMessage msg = {0}; - msg.address = udp->server_address; - msg.bytes[msg_idx++] = CommandBuyAuction; - msg.bytes_len = msg_idx; - outgoingMessageQueuePush(network_send_queue, &msg); + bool player_has_money_for_purchase = state->me.credits > curr.auction.price; + bool player_has_cargo_space_for_purchase = (state->me.vacuum_cargo_slots - usedVacuumCargoSlots(state->me)) > curr.auction.qty; + if (player_has_money_for_purchase && player_has_cargo_space_for_purchase) { + const char* label = "ACCEPT OFFER [ENTER]"; + Range1u32 range = { + .min = XYToPos(box.x+6, yoff, screen_dimensions.width), + .max = XYToPos(box.x+6+strlen(label), yoff, screen_dimensions.width), + }; + tui->cursor.x = box.x+6; + tui->cursor.y = yoff; + colorizeRange(tui, range, ANSI_WHITE, ANSI_BLACK); + renderStrToBuffer(tui->frame_buffer, box.x+6, yoff++, label, screen_dimensions); + if (user_pressed_enter) { + // send the "buy this auction" message to the server + u32 msg_idx = 0; + UDPMessage msg = {0}; + msg.address = udp->server_address; + msg.bytes[msg_idx++] = CommandBuyAuction; + msg.bytes_len = msg_idx; + outgoingMessageQueuePush(network_send_queue, &msg); - state->auction_tab_state = AuctionTabStateLoading; + state->auction_tab_state = AuctionTabStateLoading; + } + } else { + if (!player_has_money_for_purchase) { + const char* no_money_label = "You do not have enough money"; + Range1u32 range = { + .min = XYToPos(box.x+6, yoff, screen_dimensions.width), + .max = XYToPos(box.x+6+strlen(no_money_label), yoff, screen_dimensions.width), + }; + colorizeRange(tui, range, ANSI_DULL_RED, ANSI_WHITE); + renderStrToBuffer(tui->frame_buffer, box.x+6, yoff++, no_money_label, screen_dimensions); + } + if (!player_has_cargo_space_for_purchase) { + const char* no_cargo_label = "You do not have enough cargo space"; + Range1u32 range = { + .min = XYToPos(box.x+6, yoff, screen_dimensions.width), + .max = XYToPos(box.x+6+strlen(no_cargo_label), yoff, screen_dimensions.width), + }; + colorizeRange(tui, range, ANSI_DULL_RED, ANSI_WHITE); + renderStrToBuffer(tui->frame_buffer, box.x+6, yoff++, no_cargo_label, screen_dimensions); + } } } else if (state->auction_tab_state == AuctionTabStateLoading) { renderStrToBuffer(tui->frame_buffer, box.x+((box.width - 13)/2), yoff++, "Bid submitted...", screen_dimensions); diff --git a/src/server.c b/src/server.c index 08460de..59946db 100644 --- a/src/server.c +++ b/src/server.c @@ -419,6 +419,16 @@ fn void sendMessagePlayerDetails(PlayerShip ship, SocketAddress addr) { addSystemMessage((u8*)"MessagePlayerDetails sent"); } +fn void sendMessageAuctionBidResult(SocketAddress addr, AuctionBidResult result) { + UDPMessage outgoing_message = {.address = addr}; + u32 msg_i = 0; + outgoing_message.bytes[msg_i++] = (u8)MessageAuctionBidResult; + outgoing_message.bytes[msg_i++] = result; + outgoing_message.bytes_len = msg_i; + outgoingMessageQueuePush(state.network_send_queue, &outgoing_message); + addSystemMessage((u8*)"Message AuctionBidResult sent"); +} + 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); char sbuf[SBUFLEN] = {0}; @@ -435,6 +445,7 @@ fn void handleIncomingMessage(u8* message, u32 len, SocketAddress sender, i32 so .capacity = 0, }; switch (parsed.type) { + case CommandBuyAuction: break; // nothing to parse but the type case CommandLogin: { // parse the login command parsed.alt_port = ~readU16FromBufferLE(message + msg_idx); @@ -690,6 +701,29 @@ fn void* gameLoop(void* params) { u32 client_handle = findClientHandleBySocketAddress(&state.clients, sender); Client* client = &state.clients.items[client_handle]; switch (msg.type) { + case CommandBuyAuction: { + if (client_handle == 0) break; + Account* account = &state.accounts[client->account_id]; + StarSystem* player_sys = &state.map[account->ship.system_idx]; + bool is_auction_still_running = player_sys->auction.finished_at == 0; + if (is_auction_still_running) { + bool player_has_money_for_purchase = account->ship.credits >= player_sys->auction.price; + bool player_has_cargo_space_for_purchase = (account->ship.vacuum_cargo_slots - usedVacuumCargoSlots(account->ship)) >= player_sys->auction.qty; + if (player_has_cargo_space_for_purchase && player_has_money_for_purchase) { + player_sys->auction.finished_at = state.frame; + account->ship.credits -= player_sys->auction.price; + account->ship.commodities[player_sys->auction.type] += player_sys->auction.qty; + sendMessageAuctionBidResult(sender, AuctionBidResultPurchased); + sendMessagePlayerDetails(account->ship, sender); + } else if (!player_has_money_for_purchase) { + sendMessageAuctionBidResult(sender, AuctionBidResultNotEnoughMoney); + } else { + sendMessageAuctionBidResult(sender, AuctionBidResultNotEnoughCargoSpace); + } + } else { + sendMessageAuctionBidResult(sender, AuctionBidResultAuctionAlreadyFinished); + } + } break; case CommandAcceptPassengerJob: { if (client_handle == 0) break; // move the offer out of the system and INTO the PlayerShip @@ -937,7 +971,7 @@ 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 = 2000.0, + .credits = 20000.0, .cu_m_fuel = template.cu_m_fuel, .cu_m_o2 = template.cu_m_o2, .id = account->id, @@ -1039,7 +1073,7 @@ fn void* gameLoop(void* params) { f32 t_sec = ((f32)state.frame - (f32)grace_period_ends_at) / (f32)GOAL_GAME_LOOPS_PER_S; // t is in minutes f32 t = t_sec / 60; - f32 decay = -0.30 * t; + f32 decay = -0.50 * t; f32 floor_price = COMMODITIES[sys->auction.type].price * 0.9; sys->auction.price = Max((initial_price * pow(EULERS_E, decay)), floor_price); } diff --git a/src/shared.h b/src/shared.h index a05e6e6..d1c2392 100644 --- a/src/shared.h +++ b/src/shared.h @@ -342,6 +342,14 @@ global str STAR_NAMES[STAR_SYSTEM_COUNT] = { */ }; +typedef enum AuctionBidResult { + AuctionBidResultPurchased, + AuctionBidResultNotEnoughMoney, + AuctionBidResultNotEnoughCargoSpace, + AuctionBidResultAuctionAlreadyFinished, + AuctionBidResult_Count +} AuctionBidResult; + typedef enum Direction { DirectionInvalid, North, @@ -396,6 +404,7 @@ typedef enum Message { MessageJobComplete, MessageNotAlive, MessageAuctionDetails, + MessageAuctionBidResult, Message_Count, } Message; @@ -415,6 +424,7 @@ static const char* MESSAGE_STRINGS[] = { "JobAcceptResult", "NotAlive", "AuctionDetails", + "AuctionBidResult", }; ///// shared helper functions