From 51f4f7bf3bfb2d3cd812f9a306060defde82fc21 Mon Sep 17 00:00:00 2001 From: Tenari Date: Wed, 11 Feb 2026 07:56:04 -0600 Subject: [PATCH] you can pick a ship --- src/client.c | 67 ++++++++++------------ src/server.c | 158 ++++++++++++++++----------------------------------- src/shared.h | 9 ++- 3 files changed, 88 insertions(+), 146 deletions(-) diff --git a/src/client.c b/src/client.c index 499cf39..6080b24 100644 --- a/src/client.c +++ b/src/client.c @@ -7,6 +7,7 @@ * MY_CONSTANT * */ #include +#include #include "base/impl.c" #include "lib/network.c" #include "render.c" @@ -472,31 +473,25 @@ 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) { - if (state->section.selected_index == 0) { - state->choices[0] = state->menu.selected_index; - state->section.selected_index += 1; - state->menu.selected_index = 0; - state->menu.len = 2; - } else { - // send character color to server - UDPMessage msg = {0}; - msg.address = udp->server_address; - msg.bytes_len = 2; - // 1. msg type/CommandType - msg.bytes[0] = CommandCreateCharacter; - // 2. what color - msg.bytes[1] = ANSI_HP_RED;//WIZARD_COLORS[state->choices[0]]; + // send character ship details to server + UDPMessage msg = {0}; + msg.address = udp->server_address; + msg.bytes_len = 2; + // 1. msg type/CommandType + msg.bytes[0] = CommandCreateCharacter; + // 2. chosen ShipType + msg.bytes[1] = SHIPS[state->menu.selected_index].type; - outgoingMessageQueuePush(network_send_queue, &msg); + outgoingMessageQueuePush(network_send_queue, &msg); - state->screen = ScreenMainGame; - } + state->screen = ScreenMainGame; } if (state->menu.selected_index >= state->menu.len) { state->menu.selected_index = 0; } //// 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 }; @@ -513,7 +508,15 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count line++; str ship_desc = "You have 10,000 credits to use as a down-payment on a ship."; renderStrToBuffer(tui->frame_buffer, 6, ++line, ship_desc, screen_dimensions); - line++; + u32 selected_ship_cost = SHIPS[state->menu.selected_index].base_cost; + u32 principal_borrowed = selected_ship_cost - STARTING_DOWN_PAYMENT; + f32 mortgage_rate = calcInterestRate(selected_ship_cost, STARTING_DOWN_PAYMENT); + f32 r = mortgage_rate / 100.0; + 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); line++; // the table u32 table_x = 5; @@ -529,7 +532,6 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count line++; line++; // render the rows - char tmp_buffer[32] = {0}; for (u32 i = 0; i < ShipType_Count; line++, i++) { col_x_pos = 0; ShipTemplate ship = SHIPS[i]; @@ -545,42 +547,35 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count } FieldDescriptor details = SHIP_FIELDS[ii]; void *field_ptr = (char *)&ship + details.offset; + MemoryZero(tmp_buffer, 64); switch (details.type) { case FieldTypeEnum: { renderStrToBuffer(tui->frame_buffer, table_x+col_x_pos, line, details.enum_vals[ship.type], screen_dimensions); } break; case FieldTypeU8: { - MemoryZero(tmp_buffer, 32); sprintf(tmp_buffer, "%d", *(u8 *)field_ptr); renderStrToBuffer(tui->frame_buffer, table_x+col_x_pos, line, tmp_buffer, screen_dimensions); } break; case FieldTypeU16: { - MemoryZero(tmp_buffer, 32); sprintf(tmp_buffer, "%d", *(u16 *)field_ptr); renderStrToBuffer(tui->frame_buffer, table_x+col_x_pos, line, tmp_buffer, screen_dimensions); } break; case FieldTypeU32: { - MemoryZero(tmp_buffer, 32); sprintf(tmp_buffer, "%d", *(u32 *)field_ptr); renderStrToBuffer(tui->frame_buffer, table_x+col_x_pos, line, tmp_buffer, screen_dimensions); } break; + case FieldTypeFloat: { + sprintf(tmp_buffer, "%f", *(f32 *)field_ptr); + renderStrToBuffer(tui->frame_buffer, table_x+col_x_pos, line, tmp_buffer, screen_dimensions); + } break; + case FieldTypeString: { + renderStrToBuffer(tui->frame_buffer, table_x+col_x_pos, line, (ptr)field_ptr, screen_dimensions); + } break; + case FieldType_Count: + break; } } } - /* - renderStrToBuffer(tui->frame_buffer, 5, ++line, ship_label, screen_dimensions); - line++; - renderChoiceMenu( - tui, - 5, - line, - (ptr*)SHIP_TYPE_STRINGS, - ShipType_Count, - state->section.selected_index == 0, - state->menu.selected_index, - NULL - ); - */ } break; case ScreenMainGame: { // SIMULATION diff --git a/src/server.c b/src/server.c index 3bdef07..0076117 100644 --- a/src/server.c +++ b/src/server.c @@ -31,7 +31,7 @@ #define GOAL_GAME_LOOP_US 1000000/GOAL_GAME_LOOPS_PER_S #define CLIENT_TIMEOUT_FRAMES GOAL_GAME_LOOPS_PER_S*3 #define CHUNK_SIZE 64 -#define ACCOUNT_CHUNK_SIZE 64 +#define ACCOUNT_LEN (16) #define PARSED_CLIENT_COMMAND_THREAD_QUEUE_LEN 64 ///// TypeDefs @@ -69,19 +69,12 @@ typedef struct Entity { } Entity; typedef struct Account { - u64 id; + u32 id; // the index in the array String name; String pw; - u64 eid; + PlayerShip ship; } Account; -typedef struct AccountChunk { - u64 length; // the currently used # of accounts in this chunk - u64 capacity; // the "chunk size" / space in this chunk - struct AccountChunk* next; // the next chunk - Account* items; // the actual accounts -} AccountChunk; - typedef struct EntityList { u64 length; // the currently used length u64 capacity; @@ -105,7 +98,6 @@ typedef struct ChunkedEntityList { typedef struct Client { u16 lan_port; i32 lan_ip; - u64 character_eid; u64 account_id; SocketAddress address; CommandType commands[CLIENT_COMMAND_LIST_LEN]; @@ -123,7 +115,7 @@ typedef struct State { Mutex mutex; ClientList clients; u64 next_eid; - AccountChunk accounts; + Account accounts[ACCOUNT_LEN]; u64 frame; Arena game_scratch; StringArena string_arena; @@ -220,71 +212,15 @@ fn u64 entityFeaturesFromType(EntityType type) { return result; } -fn Account* findAccountById(u64 id) { - AccountChunk* current = &state.accounts; - while (current != NULL) { - for (u32 i = 0; i < current->length; i++) { - if (current->items[i].id == id) { - return ¤t->items[i]; - } - } - current = current->next; - } - return NULL; -} - -fn Account* findAccountByEId(u64 id) { - AccountChunk* current = &state.accounts; - while (current != NULL) { - for (u32 i = 0; i < current->length; i++) { - if (current->items[i].eid == id) { - return ¤t->items[i]; - } - } - current = current->next; - } - return NULL; -} - fn Account* findAccountByName(String name) { - AccountChunk* current = &state.accounts; - while (current != NULL) { - for (u32 i = 0; i < current->length; i++) { - if (stringsEq(¤t->items[i].name, &name)) { - return ¤t->items[i]; - } + for (u32 i = 0; i < ACCOUNT_LEN; i++) { + if (stringsEq(&state.accounts[i].name, &name)) { + return &state.accounts[i]; } - current = current->next; } return NULL; } -fn Account* newAccount(Arena* a, Account details) { - u64 id = 0; - AccountChunk* current = &state.accounts; - while (current != NULL) { - id += current->length; - if (current->length < current->capacity) { - Account* result = ¤t->items[current->length]; - *result = details; - result->id = id; - current->length += 1; - printf("new account created id=%lld\n", id); - return result; - } - if (current->next == NULL) { - // alloc next chunk of accounts - AccountChunk* next = arenaAlloc(a, sizeof(AccountChunk)); - next->capacity = ACCOUNT_CHUNK_SIZE; - next->items = arenaAllocArray(a, Account, next->capacity); - current->next = next; - } - current = current->next; - } - assert(false); // never should be reached - return NULL; -} - fn u64 pushEntity(EntityChunk* chunk, Entity e) { assert(chunk->length < chunk->capacity); chunk->items[chunk->length] = e; @@ -384,7 +320,7 @@ fn u32 pushClient(ClientList* clients, SocketAddress addr) { // first, try to overwrite an old dc'ed client for (u32 i = 1; i < clients->length; i++) { - if (clients->items[i].character_eid == 0) { + if (clients->items[i].last_ping == 0) { clients->items[i] = new_client; return i; } @@ -398,12 +334,12 @@ fn u32 pushClient(ClientList* clients, SocketAddress addr) { return result; } -fn bool deleteClientByEId(ClientList* clients, u64 id) { +fn bool deleteClientByAccountId(ClientList* clients, u64 id) { bool succeeded = false; Client blank_client = {0}; // i=1 because first client is null-client for (u32 i = 1; i < clients->length && !succeeded; i++) { - if (clients->items[i].character_eid == id) { + if (clients->items[i].account_id == id) { clients->items[i] = blank_client; succeeded = true; } @@ -411,10 +347,10 @@ fn bool deleteClientByEId(ClientList* clients, u64 id) { return succeeded; } -fn u32 findClientHandleByEId(ClientList* clients, u64 id) { +fn u32 findClientHandleByAccountId(ClientList* clients, u64 id) { for (u32 i = 0; i < clients->length; i++) { Client c = clients->items[i]; - if (c.character_eid == id) { + if (c.account_id == id) { return i; } } @@ -533,9 +469,9 @@ fn void* sendNetworkUpdates(void* sock) { memset(&state.clients.items[i], 0, sizeof(Client)); continue; } - if (client.character_eid == 0) { - continue; // they are still creating their character - } + //if (client.character_eid == 0) { + // continue; // they are still creating their character + //} } } unlockMutex(&state.client_mutex); @@ -631,20 +567,20 @@ fn void* gameLoop(void* params) { break; } } else { - Account new_account = { - .eid = 0, - .name = name, - .pw = pw, - }; - existing_account = newAccount(&permanent_arena, new_account); + for (u32 i = 0; i < ACCOUNT_LEN; i++) { + if (state.accounts[i].id == 0 && state.accounts[i].name.length == 0) { + existing_account = &state.accounts[i]; + existing_account->id = i; + } + } + existing_account->name = name; + existing_account->pw = pw; } client->account_id = existing_account->id; - if (existing_account->eid != 0) { - client->character_eid = existing_account->eid; - - // tell the client their character id + 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->eid); + writeU64ToBufferLE(outgoing_message.bytes + 1, existing_account->id); outgoing_message.bytes_len = 9; outgoing_message.address = sender; outgoingMessageQueuePush(state.network_send_queue, &outgoing_message); @@ -657,30 +593,36 @@ fn void* gameLoop(void* params) { outgoingMessageQueuePush(state.network_send_queue, &outgoing_message); printf("MessageNewAccountCreated sent\n"); } - printf("eid=%lld, client_handle=%d, acct_id=%lld\n", existing_account->eid, client_handle, existing_account->id); + printf("client_handle=%d, acct_id=%d\n", client_handle, existing_account->id); } break; case CommandCreateCharacter: { - if (client->character_eid == 0) { - // Create new character - XYZ room_xyz = {0, 0, 0 }; - Entity character = { - .type = EntityCharacter, - .id = state.next_eid++, - .changed = true, - .features = entityFeaturesFromType(EntityCharacter), - .color = msg.byte, + Account account = state.accounts[client->account_id]; + if (account.ship.id == 0) { + ShipTemplate template = SHIPS[msg.byte]; + PlayerShip player_ship = { + .type = msg.byte, + .drive_efficiency = template.drive_efficiency, + .life_support_efficiency = template.life_support_efficiency, + .vacuum_cargo_slots = template.vacuum_cargo_slots, + .climate_cargo_slots = template.climate_cargo_slots, + .passenger_berths = template.passenger_berths, + .passenger_amenities_flags = template.passenger_amenities_flags, + .smugglers_hold_cu_m = template.smugglers_hold_cu_m, + .base_cost = template.base_cost, + .remaining_mortgage = template.base_cost - STARTING_DOWN_PAYMENT, + .interest_rate = calcInterestRate(template.base_cost, STARTING_DOWN_PAYMENT), + //.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 }; - dbg("made new character id=%ld\n", character.id); - client->character_eid = character.id; - Account* account = findAccountById(client->account_id); - account->eid = character.id; - printf("character_eid=%lld, client_handle=%d, acct_id=%lld\n", account->eid, 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); - // tell the client their character id + // 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, character.id); + writeU64ToBufferLE(outgoing_message.bytes + 1, account.id); outgoingMessageQueuePush(state.network_send_queue, &outgoing_message); printf("MessageCharacterId sent\n"); } else { @@ -753,8 +695,6 @@ i32 main(i32 argc, ptr argv[]) { state.clients.capacity = SERVER_MAX_CLIENTS; state.clients.length = 1; // making entry 0 to be a "null" client state.clients.items = (Client*)arenaAllocArray(&permanent_arena, Client, SERVER_MAX_CLIENTS); - state.accounts.capacity = ACCOUNT_CHUNK_SIZE; - state.accounts.items = arenaAllocArray(&permanent_arena, Account, ACCOUNT_CHUNK_SIZE); // 2. spin off sendNetworkUpdates() infinite loop thread UDPServer listener = createUDPServer(SERVER_PORT); diff --git a/src/shared.h b/src/shared.h index deed340..8fefef4 100644 --- a/src/shared.h +++ b/src/shared.h @@ -5,7 +5,7 @@ #define GAMESHARED_H ///// #define some game-tunable constants -#define GAME_CONSTANT_ONE (1) +#define STARTING_DOWN_PAYMENT (10000) #define GAME_CONSTANT_TWO (2) typedef enum EntityFeature { @@ -79,6 +79,11 @@ global ShipTemplate SHIPS[] = { }, }; +fn f32 calcInterestRate(u32 cost, u32 down_payment) { + f32 mortgage_rate = 10.0 *(1.0 - (3.0*(f32)down_payment / ((f32)cost/3.0))); + return mortgage_rate; +} + typedef enum { FieldTypeU8, FieldTypeU16, @@ -98,7 +103,9 @@ typedef struct FieldDescriptor { } FieldDescriptor; // Define field metadata using offsetof +#ifndef offsetof #define offsetof(st, m) ((size_t)&(((st*)0)->m)) +#endif // Field descriptors for Employee global FieldDescriptor SHIP_FIELDS[SHIP_DETAIL_COUNT] = {