From d208d145200cc3b2d8def69385d8ec8bb5da34a5 Mon Sep 17 00:00:00 2001 From: Tenari Date: Wed, 18 Feb 2026 07:33:18 -0600 Subject: [PATCH] basic ui for the trading tab --- src/client.c | 96 +++++++++++++++++++++------ src/shared.h | 182 +++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 215 insertions(+), 63 deletions(-) diff --git a/src/client.c b/src/client.c index 4459e23..2acb407 100644 --- a/src/client.c +++ b/src/client.c @@ -118,14 +118,14 @@ typedef struct GameState { Screen screen; Screen old_screen; EntityList entities; - Entity me; + PlayerShip me; u64 server_frame; u64 loop_count; Arena entity_arena; StringArena string_arena; LoginState login_state; MenuState menu; - MenuState section; // for tabbing through selected "portions" of the screen + MenuState row; // for tracking which row of a table the user has selected UDPMessage keep_alive_msg; UDPClient client; StringChunkList message_input; @@ -483,7 +483,7 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count addSystemMessage((u8*)bytes); state->screen = ScreenMainGame; state->menu.selected_index = 0; - state->section.selected_index = 0; + state->row.selected_index = 0; } break; case Message_Count: case MessageInvalid: @@ -513,9 +513,7 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count state->menu.len = Tab_Count; break; } - if (state->section.selected_index == 0) { - state->menu.len = ShipType_Count; - } + state->menu.len = ShipType_Count; if (input_buffer[0] == 'q' || user_pressed_esc) { should_quit = true; @@ -526,6 +524,19 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count } else if (user_pressed_a_number) { state->menu.selected_index = input_buffer[0] - '1'; } else if (input_buffer[0] == ASCII_RETURN || input_buffer[0] == ASCII_LINE_FEED) { + // save ship choice to our gamestate + ShipTemplate template = SHIPS[state->menu.selected_index]; + state->me.type = template.type; + state->me.drive_efficiency = template.drive_efficiency; + state->me.life_support_efficiency = template.life_support_efficiency; + state->me.vacuum_cargo_slots = template.vacuum_cargo_slots; + state->me.climate_cargo_slots = template.climate_cargo_slots; + state->me.passenger_berths = template.passenger_berths; + state->me.passenger_amenities_flags = template.passenger_amenities_flags; + state->me.smugglers_hold_cu_m = template.smugglers_hold_cu_m; + state->me.base_cost = template.base_cost; + state->me.remaining_mortgage = template.base_cost - STARTING_DOWN_PAYMENT; + state->me.interest_rate = calcInterestRate(template.base_cost, STARTING_DOWN_PAYMENT); // send character ship details to server UDPMessage msg = {0}; msg.address = udp->server_address; @@ -631,18 +642,19 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count } } break; case ScreenMainGame: { - // SIMULATION - if (input_buffer[0] == 'q' || user_pressed_esc) { - should_quit = true; - } else if (user_pressed_tab) { + if (user_pressed_tab) { state->menu.selected_index += 1; if (state->menu.selected_index > state->menu.len) { state->menu.selected_index = 0; } + if (state->menu.selected_index == TabStation) { + state->row.len = Commodity_Count; + state->row.selected_index = 0; + } } Tab tab = state->menu.selected_index; - // RENDERING + // per-tab simulation+rendering u32 tabs_y = 1; u32 tx = 2; for (u32 i = 0; i < Tab_Count; i++) { @@ -663,12 +675,18 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count drawAnsiBox(tui->frame_buffer, box, screen_dimensions, true); switch (tab) { case TabDebug: { + if (input_buffer[0] == 'q' || user_pressed_esc) { + should_quit = true; + } 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 (input_buffer[0] == 'q' || user_pressed_esc) { + should_quit = true; + } if (user_pressed_up) { state->pos.y -= 1; } else if (user_pressed_down) { @@ -729,19 +747,41 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count ); } } break; - case TabShip: {} break; + case TabShip: { + if (input_buffer[0] == 'q' || user_pressed_esc) { + should_quit = true; + } + } break; case TabStation: { + if (input_buffer[0] == 'q' || user_pressed_esc) { + should_quit = true; + } + if (user_pressed_up) { + if (state->row.selected_index == 0) { + state->row.selected_index = state->row.len - 1; + } else { + state->row.selected_index -= 1; + } + } else if (user_pressed_down) { + if (state->row.selected_index == state->row.len - 1) { + state->row.selected_index = 0; + } else { + state->row.selected_index += 1; + } + } else if (user_pressed_enter) { + // TODO they are trying to trade a commodity + } renderStrToBuffer(tui->frame_buffer, box.x+2, box.y+1, "Welcome to ", screen_dimensions); renderStrToBuffer(tui->frame_buffer, box.x+2+11, box.y+1, state->current.name, screen_dimensions); char tmp_buffer[64] = {0}; u32 table_x = box.x+2; u32 line = box.y+3; - str cols[4] = {"Commodity", "#", "Bid", "Ask"}; - u32 lens[4] = { 24, 6, 6, 6}; + str cols[6] = {"Commodity", "Unit", "#", "Bid", "Ask", "Owned"}; + u32 lens[6] = { 44, 6, 6, 6, 6, 6}; u32 col_x_pos = 0; // render headers - for (u32 i = 0; i < 4; col_x_pos += lens[i++]) { + for (u32 i = 0; i < 6; col_x_pos += lens[i++]) { renderStrToBuffer(tui->frame_buffer, table_x+col_x_pos, line, cols[i], screen_dimensions); for (u32 ii = 0; ii < lens[i]; ii++) { renderUtf8CharToBuffer(tui->frame_buffer, table_x+col_x_pos+ii, line+1, "━", screen_dimensions); @@ -751,14 +791,29 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count // render rows for (u32 i = 0; i < Commodity_Count; i++) { col_x_pos = 0; - for (u32 ii = 0; ii < 4; col_x_pos += lens[ii++]) { + u32 qty = state->current.planets[0].commodities[i] + state->current.planets[1].commodities[i] + state->current.planets[2].commodities[i]; + for (u32 ii = 0; ii < 6; col_x_pos += lens[ii++]) { + if (i == state->row.selected_index) { + for (u32 iii = 0; iii < lens[ii]; iii++) { + u32 pos = XYToPos(table_x+col_x_pos+iii, line+i, screen_dimensions.width); + tui->frame_buffer[pos].background = ANSI_WHITE; + tui->frame_buffer[pos].foreground = ANSI_BLACK; + tui->frame_buffer[pos].bytes[0] = ' '; + } + } MemoryZero(tmp_buffer, 64); if (ii == 0) { - sprintf(tmp_buffer, "%s", COMMODITY_STRINGS[i]); + sprintf(tmp_buffer, "%s", COMMODITIES[i].name); } else if (ii == 1) { - sprintf(tmp_buffer, "%d", state->current.planets[0].commodities[i]); - } else { - tmp_buffer[0] = '-'; + sprintf(tmp_buffer, "%s", COMMODITIES[i].unit == StorageUnitKg ? "kg" : "cont"); + } else if (ii == 2) { + sprintf(tmp_buffer, "%d", qty); + } else if (ii == 3) { + sprintf(tmp_buffer, "%d", (u32)priceForCommodity(i, qty, true)); + } else if (ii == 4) { + sprintf(tmp_buffer, "%d", (u32)priceForCommodity(i, qty, false)); + } else if (ii == 5) { + sprintf(tmp_buffer, "%d", state->me.commodities[i]); } renderStrToBuffer(tui->frame_buffer, table_x+col_x_pos, line+i, tmp_buffer, screen_dimensions); } @@ -884,7 +939,6 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count if (user_pressed_esc || input_buffer[0] == ASCII_RETURN || input_buffer[0] == ASCII_LINE_FEED) { state->me.id = 0; state->menu.selected_index = 0; - state->section.selected_index = 0; clearServerSentState(); state->screen = ScreenCreateCharacter; } diff --git a/src/shared.h b/src/shared.h index df7b574..f2d4d03 100644 --- a/src/shared.h +++ b/src/shared.h @@ -123,23 +123,6 @@ global FieldDescriptor SHIP_FIELDS[SHIP_DETAIL_COUNT] = { { "SmugglersHold", FieldTypeU16, offsetof(ShipTemplate, smugglers_hold_cu_m), 13 }, }; -typedef struct PlayerShip { - ShipType type; - u8 drive_efficiency; - u8 life_support_efficiency; - u16 vacuum_cargo_slots; - u16 climate_cargo_slots; - u16 passenger_berths; - u16 passenger_amenities_flags; - u16 smugglers_hold_cu_m; - u32 base_cost; - u32 remaining_mortgage; - f32 interest_rate; - u32 cu_m_fuel; // we are just saying you can buy as much fuel as you want - u32 cu_m_o2; // we are just saying you can buy as much o2 as you want - u64 id; -} PlayerShip; - typedef enum EquipmentType { EquipmentTypeAquaponicsSystem, EquipmentType3DPrinter, @@ -165,43 +148,158 @@ typedef enum CommodityType { CommoditySemiConductors, CommodityCommonMetals, CommodityRareMetals, + CommodityPreciousMetals, CommodityAlcohol, CommodityClothes, CommodityPersonalSundries, Commodity_Count, } CommodityType; -static const char* COMMODITY_STRINGS[] = { - "Hydrogen Fuel", - "Oxygen", - "Water", - "Fertilizer", - "Raw Textiles", - "Low grade Ore", - "High grade Ore", - "Plastics", - "Grain", - "Meat", - "Spices", - "Electronics", - "Glass", - "Hand Tools", - "Semi-conductors (Silicon, Arsenic, Boron)", - "Common Metals (Iron, Nickel, Zinc)", - "Rare Metals (Titanium, Chromium)", - "Alcohol", - "Clothes", - "Personal Sundries (Cutlery, Storage, Buckles, Toys, Furnishings)", -}; +typedef enum StorageUnit { + StorageUnitKg, + StorageUnitContainer, + StorageUnitAtmoContainer, + StorageUnit_Count, +} StorageUnit; typedef struct Commodity { CommodityType type; + StorageUnit unit; str name; - u32 base_price_per_kg; - u32 kg_per_container; - u32 m3_per_kg; + u32 price; + u32 qty; + u32 consumption; } Commodity; +global Commodity COMMODITIES[Commodity_Count] = { + { .type = CommodityHydrogenFuel, .unit = StorageUnitKg, + .name = "Hydrogen Fuel", + .price = 10, .qty = 1000, .consumption = 200, + }, + { .type = CommodityOxygen, .unit = StorageUnitKg, + .name = "Oxygen", + .price = 5, .qty = 10000, .consumption = 500, + }, + { .type = CommodityWater, .unit = StorageUnitContainer, + .name = "Water", + .price = 700, .qty = 100, .consumption = 10, + }, + { .type = CommodityFertilizer, .unit = StorageUnitContainer, + .name = "Fertilizer", + .price = 1200, .qty = 70, .consumption = 10, + }, + { .type = CommodityRawTextiles, .unit = StorageUnitContainer, + .name = "Raw Textiles", + .price = 1800, .qty = 40, .consumption = 3, + }, + { .type = CommodityLowGradeOre, .unit = StorageUnitContainer, + .name = "Low Grade Ore", + .price = 1800, .qty = 40, .consumption = 3, + }, + { .type = CommodityHighGradeOre, .unit = StorageUnitContainer, + .name = "High Grade Ore", + .price = 1800, .qty = 40, .consumption = 3, + }, + { .type = CommodityPlastics, .unit = StorageUnitContainer, + .name = "Plastics", + .price = 1800, .qty = 40, .consumption = 3, + }, + { .type = CommodityGrain, .unit = StorageUnitContainer, + .name = "Grain", + .price = 1800, .qty = 40, .consumption = 3, + }, + { .type = CommodityMeat, .unit = StorageUnitContainer, + .name = "Meat", + .price = 1800, .qty = 40, .consumption = 3, + }, + { .type = CommoditySpices, .unit = StorageUnitContainer, + .name = "Spices", + .price = 1800, .qty = 40, .consumption = 3, + }, + { .type = CommodityElectronics, .unit = StorageUnitContainer, + .name = "Electronics", + .price = 1800, .qty = 40, .consumption = 3, + }, + { .type = CommodityGlass, .unit = StorageUnitContainer, + .name = "Glass", + .price = 1800, .qty = 40, .consumption = 3, + }, + { .type = CommodityHandTools, .unit = StorageUnitContainer, + .name = "Hand Tools", + .price = 1800, .qty = 40, .consumption = 3, + }, + { .type = CommoditySemiConductors, .unit = StorageUnitContainer, + .name = "Semi-conductors (Silicon, Arsenic, Boron)", + .price = 1800, .qty = 40, .consumption = 3, + }, + { .type = CommodityCommonMetals, .unit = StorageUnitContainer, + .name = "Common Metals (Iron, Nickel, Zinc)", + .price = 1800, .qty = 40, .consumption = 3, + }, + { .type = CommodityRareMetals, .unit = StorageUnitContainer, + .name = "Rare Metals (Titanium, Chromium)", + .price = 1800, .qty = 40, .consumption = 3, + }, + { .type = CommodityPreciousMetals, .unit = StorageUnitKg, + .name = "Precious Metals (Silver, Gold, Platinum)", + .price = 1800, .qty = 40, .consumption = 3, + }, + { .type = CommodityAlcohol, .unit = StorageUnitContainer, + .name = "Alcohol", + .price = 1800, .qty = 40, .consumption = 3, + }, + { .type = CommodityClothes, .unit = StorageUnitContainer, + .name = "Clothes", + .price = 1800, .qty = 40, .consumption = 3, + }, + { .type = CommodityPersonalSundries, .unit = StorageUnitContainer, + .name = "Personal Sundries (Cutlery, Toys, Misc)", + .price = 1800, .qty = 40, .consumption = 3, + }, +}; +fn f32 priceForCommodity(CommodityType type, u32 quantity, bool bid) { + f32 result = 0.0; + Commodity details = COMMODITIES[type]; + f32 max = details.price * 5; + f32 min = (f32)details.price / 5.0; + if (quantity == 0) { + return max; + } + result = ((f32)details.price) * ((f32)details.qty / (f32)quantity); // the bid/ask spread + if (max < result) { + result = max; + } + if (min > result) { + result = min; + } + // the bid/ask spread + if (bid) { + result = result * 0.98; + } else { + result = result * 1.02; + } + return result; +} + +typedef struct PlayerShip { + ShipType type; + u8 drive_efficiency; + u8 life_support_efficiency; + u16 vacuum_cargo_slots; + u16 climate_cargo_slots; + u16 passenger_berths; + u16 passenger_amenities_flags; + u16 smugglers_hold_cu_m; + u32 base_cost; + u32 remaining_mortgage; + f32 interest_rate; + u32 cu_m_fuel; // we are just saying you can buy as much fuel as you want + u32 cu_m_o2; // we are just saying you can buy as much o2 as you want + f32 credits; + u64 id; + u32 commodities[Commodity_Count]; +} PlayerShip; + typedef enum PlanetType { PlanetTypeNull, PlanetTypeEarth,