From 09a7eb507a04a2d2ae60a5918e13a34d4bdadc60 Mon Sep 17 00:00:00 2001 From: Tenari Date: Sat, 14 Mar 2026 15:30:20 -0500 Subject: [PATCH] can display a list of passenger job offers --- src/base/memory.c | 6 ++ src/client.c | 169 +++++++++++++++++++++++++++++++++------------- src/server.c | 96 +++++++++++++++++++++++++- src/shared.h | 91 ++++++++++++++++++++----- 4 files changed, 297 insertions(+), 65 deletions(-) diff --git a/src/base/memory.c b/src/base/memory.c index 3d63540..b0655ac 100644 --- a/src/base/memory.c +++ b/src/base/memory.c @@ -40,6 +40,12 @@ fn void* arenaAlloc(Arena* arena, u64 size) { return memory; } +fn void* arenaAllocZero(Arena* a, u64 size) { + void* result = arenaAlloc(a, size); + MemoryZero(result, size); + return result; +} + fn void* arenaAllocArraySized(Arena* arena, u64 elem_size, u64 count) { return arenaAlloc(arena, elem_size * count); } diff --git a/src/client.c b/src/client.c index 8b52ae7..56be93f 100644 --- a/src/client.c +++ b/src/client.c @@ -33,7 +33,8 @@ typedef enum Tab { TabChat, TabMap, TabShip, - TabStation, + TabMarket, + TabPassengers, Tab_Count, } Tab; @@ -44,14 +45,22 @@ typedef enum ShipTabStates { ShipTabStates_Count, } ShipTabStates; -typedef enum StationTabStates { - StationTabStateTable, - StationTabStateComparisonTable, - StationTabStateTransact, - StationTabStateResult, - StationTabStateLoading, - StationTabStates_Count, -} StationTabStates; +typedef enum MarketTabStates { + MarketTabStateTable, + MarketTabStateComparisonTable, + MarketTabStateTransact, + MarketTabStateResult, + MarketTabStateLoading, + MarketTabStates_Count, +} MarketTabStates; + +typedef enum PassengersTabStates { + PassengersTabStateTable, + PassengersTabStateBookModal, + PassengersTabStateLoading, + PassengersTabStateResult, + PassengersTabStates_Count, +} PassengersTabStates; typedef enum Screen { ScreenLogin, @@ -98,7 +107,7 @@ typedef struct ParsedServerMessage { StarSystem sys; PlayerShip ship; TransactionResult tx_result; - //u64 ids[PARSED_IDS_LEN]; + PassengerJobOffer offers[MAX_PASSENGER_JOB_OFFERS]; } ParsedServerMessage; typedef struct ParsedServerMessageThreadQueue { @@ -149,7 +158,7 @@ typedef struct GameState { StarSystem map[STAR_SYSTEM_COUNT]; Pos2 pos; u8 destination_sys_idx; - StationTabStates station_tab_state; + MarketTabStates market_tab_state; ShipTabStates ship_tab_states; TransactionResult tx_result; u64 turn_tick_started_on; @@ -164,7 +173,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 TAB_STRS[Tab_Count] = {"Debug", "Chat", "Map", "Ship", "Station"}; +global str TAB_STRS[Tab_Count] = {"Debug", "Chat", "Map", "Ship", "Market", "Passengers"}; global FieldDescriptor COMPARE_COMMODITY_FIELDS[STAR_SYSTEM_COUNT] = { { "System", FieldTypeString, offsetof(CompareCommodity, system_name), 16 }, { "Bid", FieldTypeU32, offsetof(CompareCommodity, bid), 6 }, @@ -310,13 +319,16 @@ fn void renderStaticAssetToPixelBuffer(TuiState* tui, u8* asset, u32 len, u16 x, } fn void resetTabRow(Tab tab) { - if (state.menu.selected_index == TabStation) { + if (state.menu.selected_index == TabMarket) { state.row.len = Commodity_Count; state.row.selected_index = 0; } else if (state.menu.selected_index == TabShip) { state.ship_tab_states = ShipTabStateMain; state.row.len = 2; state.row.selected_index = 0; + } else if (state.menu.selected_index == TabPassengers) { + state.row.len = 0;// TODO the number of available passenger missions + state.row.selected_index = 0; } } @@ -351,6 +363,17 @@ fn void handleIncomingMessage(u8* message, u32 len, SocketAddress sender, i32 so case MessageGameOver: { parsed.byte = message[msg_pos++]; } break; + case MessageSystemPassengers: { + parsed.byte = message[msg_pos++]; + u32 i = 0; + while (msg_pos < len) { + parsed.offers[i].goal_system_idx = message[msg_pos++]; + parsed.offers[i].people = message[msg_pos++]; + parsed.offers[i].time_limit = message[msg_pos++]; + parsed.offers[i].offer = readU32FromBufferLE(message + msg_pos); + msg_pos += 4; + } + } break; case MessageTransactionResult: { parsed.tx_result.buying = message[msg_pos++]; parsed.tx_result.qty = readU32FromBufferLE(message + msg_pos); @@ -454,6 +477,23 @@ fn i32 compareCompareCommodity(const void *a, const void *b) { return ((CompareCommodity*)b)->bid - ((CompareCommodity*)a)->bid; } +fn void drawStatusLine(TuiState* tui, ptr sbuf, PlayerShip* me, u32 x, u32 y) { + MemoryZero(sbuf, SBUFLEN); + sprintf(sbuf, " $%d ", me->credits); + Range1u32 range = { + .min = XYToPos(x, y, tui->screen_dimensions.width), + .max = XYToPos(x+strlen(sbuf), y, tui->screen_dimensions.width), + }; + colorizeRange(tui, range, ANSI_HIGHLIGHT_YELLOW, ANSI_BLACK); + renderStrToBuffer(tui->frame_buffer, x, y, sbuf, tui->screen_dimensions); + MemoryZero(sbuf, SBUFLEN); + sprintf(sbuf, " %d / %d cargo ", usedVacuumCargoSlots(*me), me->vacuum_cargo_slots); + range.min = range.max; + range.max = range.min+strlen(sbuf); + colorizeRange(tui, range, ANSI_DULL_BLUE, ANSI_WHITE); + renderStrToBuffer(tui->frame_buffer, range.min % tui->screen_dimensions.width, y, sbuf, tui->screen_dimensions); +} + fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count) { GameState* state = (GameState*) s; state->loop_count = loop_count; @@ -476,6 +516,10 @@ 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 MessageSystemPassengers: { + // put the stuff from msg into the correct state.map[i] + MemoryCopy(&state->map[msg.byte].offers, &msg.offers, sizeof(PassengerJobOffer) * MAX_PASSENGER_JOB_OFFERS); + } break; case MessageGameOver: { state->screen = state->me.id == msg.byte ? ScreenVictory : ScreenDefeat; sprintf(sbuf,"got winner id: %d", msg.byte); @@ -492,7 +536,7 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count state->turn_tick_started_on = loop_count; } break; case MessageTransactionResult: { - state->station_tab_state = StationTabStateResult; + state->market_tab_state = MarketTabStateResult; MemoryCopyStruct(&state->tx_result, &msg.tx_result); } break; case MessagePlayerDetails: { @@ -1026,32 +1070,20 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count renderStrToBuffer(tui->frame_buffer, modal_outline.x+2, y_off, "Loading...", screen_dimensions); } } break; - case TabStation: { + case TabMarket: { if (input_buffer[0] == 'q' || user_pressed_esc) { - if (state->station_tab_state == StationTabStateTable) { + if (state->market_tab_state == MarketTabStateTable) { should_quit = true; } else { - state->station_tab_state = StationTabStateTable; + state->market_tab_state = MarketTabStateTable; } } u32 line = box.y + 1; // draw a nifty status-line - MemoryZero(sbuf, SBUFLEN); - sprintf(sbuf, " $: %d ", state->me.credits); - Range1u32 range = { - .min = XYToPos(box.x+2, line, screen_dimensions.width), - .max = XYToPos(box.x+2+strlen(sbuf), line, screen_dimensions.width), - }; - colorizeRange(tui, range, ANSI_HIGHLIGHT_YELLOW, ANSI_BLACK); - renderStrToBuffer(tui->frame_buffer, box.x+2, line, sbuf, screen_dimensions); - MemoryZero(sbuf, SBUFLEN); - sprintf(sbuf, " %d / %d cargo ", usedVacuumCargoSlots(state->me), state->me.vacuum_cargo_slots); - range.min = range.max; - range.max = range.min+strlen(sbuf); - colorizeRange(tui, range, ANSI_DULL_BLUE, ANSI_WHITE); - renderStrToBuffer(tui->frame_buffer, range.min % screen_dimensions.width, line++, sbuf, screen_dimensions); + drawStatusLine(tui, sbuf, &state->me, box.x+2, line); + line++; MemoryZero(sbuf, SBUFLEN); sprintf(sbuf, "Welcome to %s", curr.name); @@ -1072,7 +1104,7 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count .qty = selected_qty, .owned = state->me.commodities[c.type], }; - if (state->station_tab_state == StationTabStateComparisonTable) { + if (state->market_tab_state == MarketTabStateComparisonTable) { info.rows = STAR_SYSTEM_COUNT; info.cols = 4; CompareCommodity rows[STAR_SYSTEM_COUNT] = { 0 }; @@ -1117,7 +1149,7 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count .height = 15, .width = 50, }; - if (state->station_tab_state != StationTabStateTable && state->station_tab_state != StationTabStateComparisonTable) { + if (state->market_tab_state != MarketTabStateTable && state->market_tab_state != MarketTabStateComparisonTable) { // draw the modal outline clearBox(tui, modal_outline); drawAnsiBox(tui->frame_buffer, modal_outline, tui->screen_dimensions, false); @@ -1127,24 +1159,24 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count u32 y_off = modal_outline.y+2; str cname = COMMODITIES[state->row.selected_index].name; - switch (state->station_tab_state) { - case StationTabStateTable: { + switch (state->market_tab_state) { + case MarketTabStateTable: { moveRowUpDown(user_pressed_up, user_pressed_down); if (user_pressed_enter) { - state->station_tab_state = StationTabStateTransact; + state->market_tab_state = MarketTabStateTransact; state->modal_choice.len = 2; state->modal_choice.selected_index = 0; } else if (user_pressed_space) { - state->station_tab_state = StationTabStateComparisonTable; + state->market_tab_state = MarketTabStateComparisonTable; } } break; - case StationTabStateComparisonTable: { + case MarketTabStateComparisonTable: { if (user_pressed_backspace) { - state->station_tab_state = StationTabStateTable; + state->market_tab_state = MarketTabStateTable; } // TODO } break; - case StationTabStateTransact: { + case MarketTabStateTransact: { if (user_pressed_right) { state->modal_choice.selected_index = 1; } else if (user_pressed_left) { @@ -1217,10 +1249,10 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count outgoingMessageQueuePush(network_send_queue, &msg); - state->station_tab_state = StationTabStateLoading; + state->market_tab_state = MarketTabStateLoading; } } break; - case StationTabStateResult: { + case MarketTabStateResult: { if (state->tx_result.buying) { renderStrToBuffer(tui->frame_buffer, modal_outline.x+((modal_outline.width-10)/2), y_off++, "Purchased!", screen_dimensions); } else { @@ -1245,16 +1277,49 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count renderStrToBuffer(tui->frame_buffer, exit_x, y_off++, "EXIT", screen_dimensions); if (user_pressed_enter) { - state->station_tab_state = StationTabStateTable; + state->market_tab_state = MarketTabStateTable; } } break; - case StationTabStateLoading: { + case MarketTabStateLoading: { renderStrToBuffer(tui->frame_buffer, modal_outline.x+2, y_off, "Loading...", screen_dimensions); } break; - case StationTabStates_Count: assert(false && "invalid station_tab_state"); break; + case MarketTabStates_Count: assert(false && "invalid market_tab_state"); break; } } break; + case TabPassengers: { + u32 line = box.y + 1; + + // draw a nifty status-line + drawStatusLine(tui, sbuf, &state->me, box.x+2, line); + line++; + + // the table + u32 offer_count = 0; + DisplayPassengerJobOffer rows[MAX_PASSENGER_JOB_OFFERS] = { 0 }; + for (u32 i = 0; i < MAX_PASSENGER_JOB_OFFERS; i++) { + if (curr.offers[i].people > 0) { + offer_count += 1; + rows[i].destination = state->map[curr.offers[i].goal_system_idx].name; + rows[i].people = curr.offers[i].people; + rows[i].time_limit = curr.offers[i].time_limit; + rows[i].offer = curr.offers[i].offer; + } + } + TableDrawInfo info = { + .x_offset = box.x+2, .y_offset = ++line, + .rows = offer_count, .cols = 4, + }; + renderTable( + tui, + info, + state->row.selected_index, + PASSENGER_JOB_OFFER_FIELDS, + 1, + rows, + sizeof(DisplayPassengerJobOffer) + ); + } break; case Tab_Count: {} break; } } break; @@ -1431,7 +1496,19 @@ i32 main(i32 argc, ptr argv[]) { printf("bad network startup"); exit(1); } - state.client = createUDPClient(7777, argc > 1 ? argv[1] : NULL); + ptr server_address = NULL; + if (argc > 1) { + server_address = argv[1]; + } else { + printf("Server IP Address: "); + char input_string[16] = { 0 }; + scanf("%15s", input_string); + printf("%s", input_string); + if (input_string[0] && input_string[1] && input_string[2] && input_string[3]) { + server_address = input_string; + } + } + state.client = createUDPClient(7777, server_address); // "hardcoded" keep alive message to periodically send to server state.keep_alive_msg.address = state.client.server_address; diff --git a/src/server.c b/src/server.c index 2f01b17..fee0f25 100644 --- a/src/server.c +++ b/src/server.c @@ -37,6 +37,8 @@ typedef struct ParsedClientCommand { CommandType type; u8 byte; + u8 people; + u8 time_limit; u16 sender_port; u16 alt_port; u32 sender_ip; @@ -235,6 +237,23 @@ fn u32 starSystemPlanetCount(StarSystem* sys) { return planet_count; } +fn UDPMessage makeMessageSystemPassengers(StarSystem* sys) { + UDPMessage outgoing_message = {0}; + u32 msg_i = 0; + outgoing_message.bytes[msg_i++] = (u8)MessageSystemPassengers; + outgoing_message.bytes[msg_i++] = (u8)sys->idx; + for (u32 i = 0; i < MAX_PASSENGER_JOB_OFFERS; i++) { + if (sys->offers[i].people) { + outgoing_message.bytes[msg_i++] = sys->offers[i].goal_system_idx; + outgoing_message.bytes[msg_i++] = sys->offers[i].people; + outgoing_message.bytes[msg_i++] = sys->offers[i].time_limit; + msg_i += writeU32ToBufferLE(outgoing_message.bytes + msg_i, sys->offers[i].offer); + } + } + outgoing_message.bytes_len = msg_i; + return outgoing_message; +} + fn UDPMessage makeMessageSystemCommodities(StarSystem* sys) { UDPMessage outgoing_message = {0}; u32 planet_count = starSystemPlanetCount(sys); @@ -395,6 +414,13 @@ fn void handleIncomingMessage(u8* message, u32 len, SocketAddress sender, i32 so printf("command create character received\n"); parsed.byte = message[1]; } break; + case CommandAcceptPassengerJob: { + printf("command accept passenger job received\n"); + parsed.byte = message[1]; + parsed.people = message[2]; + parsed.time_limit = message[3]; + parsed.qty = readU32FromBufferLE(message + 4); + } break; case CommandInvalid: case CommandType_Count: { dbg("invalid command type"); @@ -443,6 +469,13 @@ fn void* sendNetworkUpdates(void* sock) { .items = sys_udp_msg.bytes, .length = sys_udp_msg.bytes_len, }; + UDPMessage sys_pass_udp_msg = { 0 }; + sys_pass_udp_msg = makeMessageSystemPassengers(&state.map[send_loop % STAR_SYSTEM_COUNT]); + u8List sys_pass_msg = { + .capacity = UDP_MAX_MESSAGE_LEN, + .items = sys_pass_udp_msg.bytes, + .length = sys_pass_udp_msg.bytes_len, + }; lockMutex(&state.client_mutex); { // WARNING the `i` starts at 1 here because state.clients.items[0] is a "null" Client for (u32 i = 1; i < state.clients.length; i++) { @@ -455,6 +488,8 @@ fn void* sendNetworkUpdates(void* sock) { // every "send-frame" we send each connnected client the current prices for a different system // so that the prices mostly stay up to date pretty quickly without having to track changes sendUDPu8List(socket, &client.address, &sys_msg); + // and the passenger offers + sendUDPu8List(socket, &client.address, &sys_pass_msg); // update all the changed systems UDPMessage msg_data; @@ -549,6 +584,37 @@ fn void* gameLoop(void* params) { u32 client_handle = findClientHandleBySocketAddress(&state.clients, sender); Client* client = &state.clients.items[client_handle]; switch (msg.type) { + case CommandAcceptPassengerJob: { + // move the offer out of the system and INTO the PlayerShip + Account* account = &state.accounts[client->account_id]; + StarSystem* player_sys = &state.map[account->ship.system_idx]; + PassengerJobOffer pjo = { + .goal_system_idx = msg.byte, + .people = msg.people, + .time_limit = msg.time_limit, + .offer = msg.qty, + }; + // find the matching job, and IF the ship has room, + // "accept" it by clearing it from the system list and adding it to the ship + for (u32 i = 0; i < MAX_PASSENGER_JOB_OFFERS; i++) { + if (passengerJobEq(player_sys->offers[i], pjo)) { + if (shipAvailablePassengerBerths(account->ship) > 0) { + for (u32 ii = 0; ii < MAX_PASSENGER_BERTHS; ii++) { + if (account->ship.passengers[ii].people == 0) { + account->ship.passengers[ii].people = pjo.people; + account->ship.passengers[ii].goal_system_idx = pjo.goal_system_idx; + account->ship.passengers[ii].turns_remaining = pjo.time_limit; + account->ship.passengers[ii].reward = pjo.offer; + MemoryZero(&player_sys->offers[i], sizeof(PassengerJobOffer)); + ii = MAX_PASSENGER_BERTHS; + i = MAX_PASSENGER_JOB_OFFERS; + break; + } + } + } + } + } + } break; case CommandPayMortgage: { Account* account = &state.accounts[client->account_id]; u32 amount_to_pay = msg.id; @@ -819,6 +885,7 @@ fn void* gameLoop(void* params) { for (u32 i = sys_range.min; i < sys_range.max; i++) { sys = &state.map[i]; sys->changed = true; + // consume and produce commodities for (u32 ii = 0; ii < Commodity_Count; ii++) { for (u32 iii = 0; iii < MAX_PLANETS; iii++) { if (sys->planets[iii].type != PlanetTypeNull) { @@ -831,6 +898,22 @@ fn void* gameLoop(void* params) { } } } + // potentially create a new passenger job + u32 planet_divisor = ((MAX_PLANETS - starSystemPlanetCount(sys))+1); + u32 max_passenger_job_count = 1 + ((MAX_PASSENGER_JOB_OFFERS-1) / planet_divisor); + for (u32 ii = 0; ii < max_passenger_job_count; ii++) { + if (sys->offers[ii].people == 0) { + // TODO choose a system further away + sys->offers[ii].goal_system_idx = (i+(rand() % 6)) % STAR_SYSTEM_COUNT; + sys->offers[ii].people = 1 + (rand() % (MAX_PASSENGER_JOB_PEOPLE - 1)); + sys->offers[ii].time_limit = 3 + (rand() % 5); + // TODO compute based on distance between people and time_limit + sys->offers[ii].offer = 1000 + (rand() % MAX_PASSENGER_JOB_PRICE); + break; + } else { //increase the offer amount each turn + sys->offers[ii].offer = (u32)((f32)sys->offers[ii].offer * 1.05); + } + } } Range1u64 ship_range = LaneRange(player_count); @@ -953,7 +1036,7 @@ i32 main(i32 argc, ptr argv[]) { } } - // now, build out the commodity info for the systems + // now, build out the commodity info and passenger jobs for the systems for (u32 i = 0; i < STAR_SYSTEM_COUNT; i++) { u32 planet_count = rand() % MAX_PLANETS + 1; for (u32 ii = 0; ii < planet_count; ii++) { @@ -1055,6 +1138,17 @@ i32 main(i32 argc, ptr argv[]) { break; } } + + u32 planet_divisor = ((MAX_PLANETS - planet_count)+1); + u32 offer_count = 1 + (rand() % (MAX_PASSENGER_JOB_OFFERS / planet_divisor)); + for (u32 ii = 0; ii < offer_count; ii++) { + // TODO choose a system further away + state.map[i].offers[ii].goal_system_idx = (i+(rand() % 6)) % STAR_SYSTEM_COUNT; + state.map[i].offers[ii].people = 1 + (rand() % (MAX_PASSENGER_JOB_PEOPLE - 1)); + state.map[i].offers[ii].time_limit = 3 + (rand() % 5); + // TODO compute based on distance between people and time_limit + state.map[i].offers[ii].offer = 1000 + (rand() % MAX_PASSENGER_JOB_PRICE); + } } // 2. spin off sendNetworkUpdates() infinite loop thread diff --git a/src/shared.h b/src/shared.h index c96540f..c974698 100644 --- a/src/shared.h +++ b/src/shared.h @@ -11,12 +11,16 @@ #define MAP_WIDTH (36) #define MAP_HEIGHT (12) #define MAX_PLANETS (3) +#define MAX_PASSENGER_JOB_OFFERS (16) +#define MAX_PASSENGER_JOB_PEOPLE (4) +#define MAX_PASSENGER_JOB_PRICE (10000) +#define MAX_PASSENGER_BERTHS (8) typedef enum ShipType { ShipSparrow, ShipDart, ShipHaulerPrimeZ1, -// ShipNX400, + ShipNX400, ShipType_Count, } ShipType; @@ -24,7 +28,7 @@ global str SHIP_TYPE_STRINGS[] = { "Sparrow", "Dart", "Hauler-Prime Z-1", -// "NX-400", + "NX-400", }; typedef struct ShipTemplate { @@ -43,33 +47,33 @@ typedef struct ShipTemplate { global ShipTemplate SHIPS[] = { { - .type = ShipSparrow, .drive_efficiency = 10, .life_support_efficiency = 2, + .type = ShipSparrow, .drive_efficiency = 10, .life_support_efficiency = 1, .vacuum_cargo_slots = 5, .climate_cargo_slots = 0, .passenger_berths = 0, .passenger_amenities_flags = 0, .smugglers_hold_cu_m = 1, .base_cost = 100000, - .cu_m_fuel = 2000, .cu_m_o2 = 1000, + .cu_m_fuel = 2500, .cu_m_o2 = 1000, }, { - .type = ShipDart, .drive_efficiency = 70, .life_support_efficiency = 4, - .vacuum_cargo_slots = 10, .climate_cargo_slots = 1, .passenger_berths = 1, + .type = ShipDart, .drive_efficiency = 70, .life_support_efficiency = 3, + .vacuum_cargo_slots = 6, .climate_cargo_slots = 0, .passenger_berths = 1, .passenger_amenities_flags = 0, .smugglers_hold_cu_m = 0, .base_cost = 150000, - .cu_m_fuel = 2000, .cu_m_o2 = 1000, + .cu_m_fuel = 2000, .cu_m_o2 = 2000, }, { - .type = ShipHaulerPrimeZ1, .drive_efficiency = 50, .life_support_efficiency = 3, - .vacuum_cargo_slots = 100, .climate_cargo_slots = 5, .passenger_berths = 0, + .type = ShipHaulerPrimeZ1, .drive_efficiency = 50, .life_support_efficiency = 2, + .vacuum_cargo_slots = 30, .climate_cargo_slots = 0, .passenger_berths = 0, .passenger_amenities_flags = 0, .smugglers_hold_cu_m = 0, .base_cost = 250000, - .cu_m_fuel = 2000, .cu_m_o2 = 1000, + .cu_m_fuel = 2500, .cu_m_o2 = 1000, }, -/* { - .type = ShipNX400, .drive_efficiency = 6, .life_support_efficiency = 8, - .vacuum_cargo_slots = 5, .climate_cargo_slots = 5, .passenger_berths = 5, + { + .type = ShipNX400, .drive_efficiency = 60, .life_support_efficiency = 5, + .vacuum_cargo_slots = 3, .climate_cargo_slots = 0, .passenger_berths = 3, .passenger_amenities_flags = 0, - .smugglers_hold_cu_m = 0, .base_cost = 350000, - .cu_m_fuel = 2000, .cu_m_o2 = 1000, - },*/ + .smugglers_hold_cu_m = 0, .base_cost = 250000, + .cu_m_fuel = 2000, .cu_m_o2 = 3000, + }, }; global FieldDescriptor SHIP_FIELDS[SHIP_DETAIL_COUNT] = { @@ -272,6 +276,35 @@ global FieldDescriptor MARKET_COMMODITY_FIELDS[Commodity_Count] = { { "Owned", FieldTypeU32, offsetof(MarketCommodity, owned), 6 }, }; +typedef struct Passenger { + u8 people; + u8 goal_system_idx; + u8 turns_remaining; + u32 reward; +} Passenger; + +typedef struct PassengerJobOffer { + u8 goal_system_idx; + u8 people; // 1 - 4, affects how much oxygen they consume + u8 time_limit; // # of turns until they leave your ship without paying + u32 offer; +} PassengerJobOffer; + +typedef struct DisplayPassengerJobOffer { + str destination; + u8 people; + u8 time_limit; + u32 offer; +} DisplayPassengerJobOffer; + + +global FieldDescriptor PASSENGER_JOB_OFFER_FIELDS[] = { + { "Destination", FieldTypeString, offsetof(DisplayPassengerJobOffer, destination), 44 }, + { "People", FieldTypeU8, offsetof(DisplayPassengerJobOffer, people), 6 }, + { "Turns", FieldTypeU8, offsetof(DisplayPassengerJobOffer, time_limit), 6 }, + { "Offer", FieldTypeU32, offsetof(DisplayPassengerJobOffer, offer), 6 }, +}; + typedef struct PlayerShip { ShipType type; bool ready_to_depart; @@ -291,6 +324,7 @@ typedef struct PlayerShip { u32 credits; u64 id; u32 commodities[Commodity_Count]; + Passenger passengers[MAX_PASSENGER_BERTHS]; } PlayerShip; typedef enum PlanetType { @@ -317,6 +351,7 @@ typedef struct StarSystem { u32 idx; str name; Planet planets[MAX_PLANETS]; + PassengerJobOffer offers[MAX_PASSENGER_JOB_OFFERS]; } StarSystem; global str STAR_NAMES[STAR_SYSTEM_COUNT] = { @@ -382,6 +417,7 @@ typedef enum CommandType { CommandReadyStatus, CommandSetDestination, CommandPayMortgage, + CommandAcceptPassengerJob, CommandType_Count, } CommandType; @@ -393,10 +429,9 @@ static const char* command_type_strings[CommandType_Count] = { "ReadyStatus", "SetDestination", "PayMortgage", + "AcceptPassengerJob", }; -#define ENTITY_HEADER_MESSAGE_SIZE (8+8+1+1+1) -#define ENTITY_MESSAGE_SIZE (ENTITY_HEADER_MESSAGE_SIZE+2+2+2+2+8+1+1) typedef enum Message { MessageInvalid, MessageCharacterId, @@ -409,6 +444,7 @@ typedef enum Message { MessageTurnTick, MessageGameOver, MessagePayoffResult, + MessageSystemPassengers, Message_Count, } Message; @@ -424,6 +460,7 @@ static const char* MESSAGE_STRINGS[] = { "TurnTick", "GameOver", "PayoffResult", + "SystemPassengers", }; ///// shared helper functions @@ -473,4 +510,22 @@ fn f32 calcInterestRate(u32 cost, u32 down_payment) { return mortgage_rate; } +fn bool passengerJobEq(PassengerJobOffer a, PassengerJobOffer b) { + return a.goal_system_idx == b.goal_system_idx && + a.offer == b.offer && + a.people == b.people && + a.time_limit == b.time_limit; +} + +fn bool shipAvailablePassengerBerths(PlayerShip ship) { + u32 used = 0; + for (u32 i = 0; i < MAX_PASSENGER_BERTHS; i++) { + if (ship.passengers[i].people > 0) { + used += 1; + } + } + assert(used <= ship.passenger_berths); + return ship.passenger_berths - used; +} + #endif //GAMESHARED_H