From 0ce9ecad9d62276560e0ac64e9834b490f74ee58 Mon Sep 17 00:00:00 2001 From: Cameron Reikes Date: Sat, 19 Nov 2022 02:09:40 -0800 Subject: [PATCH] Fixes and helix fixign --- .gitignore | 2 + flight.rdbg | Bin 676 -> 972 bytes gamestate.c | 119 +++++++++++++++------ loaded/itemswitch.png | Bin 0 -> 5012 bytes main.c | 18 +++- compile_flags.txt => old_compile_flags.txt | 3 + server.c | 3 + tooling.ahk | 9 +- types.h | 19 +++- world.bin | Bin 170321 -> 0 bytes 10 files changed, 135 insertions(+), 38 deletions(-) create mode 100644 loaded/itemswitch.png rename compile_flags.txt => old_compile_flags.txt (76%) delete mode 100644 world.bin diff --git a/.gitignore b/.gitignore index cc57c07..e6a0f40 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +compile_commands.json +.cache/ enc_temp_folder/ *.spall # profiling *.bin # world files diff --git a/flight.rdbg b/flight.rdbg index cb9853c441b20b76b4b26ff9005c07327f086e57..b863b008d4281f707f0aa93404dfbb355b44f452 100644 GIT binary patch delta 221 zcmZ3&dWL;ME-NPk1B1=P1qS@7d8s8a1x5KK`N{b?ddV9L;~5#vffC%s`Pun7@#zJ6 z8M+e}s7j=yCY7eA6eX7HB{Ko_f&e2BgXPi_b5n~;5=&C`lJ7BtrQmXINOH1JJ-k2~ zXl+q?Wqe6~d}2xp$iQ-g)#e}!V)KC%=-Oo##~0)zmgMJUCQoK!GUNtHf$e5woLtDH T%muQakpn~kxhj*VG4TNa%k4Iu delta 113 zcmX@ZzJzr`E(;q21H;CR@r*`5nt=(31%bG@ATc>r*DkXpwJ5P9Ge57G7buumlmrqe v*0swk%?0uqCL1yvPHtf4U}TzH$Sebox_unlocks |= box_unlock_number(box); } bool box_unlocked(Player *player, enum BoxType box) { + assert(box < MAX_BOX_TYPES); return (player->box_unlocks & box_unlock_number(box)) > 0; } @@ -533,8 +535,11 @@ static void on_damage(cpArbiter *arb, cpSpace *space, cpDataPointer userData) // cpSpaceAddPostStepCallback(space, (cpPostStepFunc)postStepRemove, b, NULL); // cpSpaceAddPostStepCallback(space, (cpPostStepFunc)postStepRemove, a, NULL); } + +// must be called with zero initialized game state, because copies the server side computing! void initialize(GameState *gs, void *entity_arena, size_t entity_arena_size) { + bool is_server_side = gs->server_side_computing; *gs = (GameState){0}; memset(entity_arena, 0, entity_arena_size); // SUPER critical. Random vals in the entity data causes big problem gs->entities = (Entity *)entity_arena; @@ -543,6 +548,7 @@ void initialize(GameState *gs, void *entity_arena, size_t entity_arena_size) cpSpaceSetUserData(gs->space, (cpDataPointer)gs); // needed in the handler cpCollisionHandler *handler = cpSpaceAddCollisionHandler(gs->space, 0, 0); // @Robust limit collision type to just blocks that can be damaged handler->postSolveFunc = on_damage; + gs->server_side_computing = is_server_side; } void destroy(GameState *gs) { @@ -757,7 +763,7 @@ SerMaybeFailure ser_data(SerState *ser, char *data, size_t data_len, const char ser->cursor += 1; SER_ASSERT(ser->cursor <= ser->max_size); } - + // now compare! SER_ASSERT(strcmp(read_name, name) == 0); } @@ -792,6 +798,7 @@ enum GameVersion VReallyRemovedTimeFromDiskSave, // apparently last one didn't work VRemovedInsideOfMe, VSwitchedToUnlocks, + VAddedPlatonic, VMax, // this minus one will be the version used }; @@ -979,6 +986,9 @@ SerMaybeFailure ser_entity(SerState *ser, GameState *gs, Entity *e) SER_VAR(&e->box_type); SER_VAR(&e->always_visible); + if (ser->version >= VAddedPlatonic) + SER_VAR(&e->is_platonic); + if (ser->version <= VSwitchedToUnlocks) { bool throwaway; @@ -992,6 +1002,12 @@ SerMaybeFailure ser_entity(SerState *ser, GameState *gs, Entity *e) SER_VAR(&e->wanted_thrust); SER_VAR(&e->energy_used); SER_VAR(&e->sun_amount); + if (ser->version >= VAddedPlatonic) + { + SER_VAR(&e->scanner_head_rotate); + SER_VAR(&e->platonic_nearest_direction); + SER_VAR(&e->platonic_detection_strength); + } if (ser->version >= VRemovedInsideOfMe && ser->save_or_load_from_disk) { @@ -1077,9 +1093,6 @@ SerMaybeFailure ser_server_to_client(SerState *ser, ServerToClient *s) SER_VAR(&cur_next_entity); SER_ASSERT(cur_next_entity <= ser->max_entity_index); - if (!ser->save_or_load_from_disk) - SER_MAYBE_RETURN(ser_entityid(ser, &gs->cur_spacestation)); - SER_VAR(&s->your_player); if (ser->version >= VReallyRemovedTimeFromDiskSave && ser->save_or_load_from_disk) { @@ -1122,7 +1135,7 @@ SerMaybeFailure ser_server_to_client(SerState *ser, ServerToClient *s) // are loaded in the parent body is loaded in and can be referenced. BOXES_ITER(gs, cur, e) { - bool this_box_in_range = (ser->for_player == NULL || (ser->for_player != NULL && V2distsqr(entity_pos(ser->for_player), entity_pos(cur)) < VISION_RADIUS * VISION_RADIUS)); + bool this_box_in_range = (ser->save_or_load_from_disk || ser->for_player == NULL || (ser->for_player != NULL && V2distsqr(entity_pos(ser->for_player), entity_pos(cur)) < VISION_RADIUS * VISION_RADIUS)); if (cur->always_visible) this_box_in_range = true; if (this_box_in_range) @@ -1534,7 +1547,7 @@ V2 box_vel(Entity *box) return cp_to_v2(cpBodyGetVelocityAtWorldPoint(grid->body, v2_to_cp(entity_pos(box)))); } -EntityID create_spacestation(GameState *gs) +EntityID create_initial_world(GameState *gs) { #define BOX_AT_TYPE(grid, pos, type) \ { \ @@ -1542,21 +1555,18 @@ EntityID create_spacestation(GameState *gs) box_create(gs, box, grid, pos); \ box->box_type = type; \ box->indestructible = indestructible; \ - box->always_visible = true; \ - box->no_save_to_disk = true; \ } #define BOX_AT(grid, pos) BOX_AT_TYPE(grid, pos, BoxHullpiece) bool indestructible = false; Entity *grid = new_entity(gs); grid_create(gs, grid); - grid->no_save_to_disk = true; - entity_set_pos(grid, (V2){-80.0f, 0.0f}); + entity_set_pos(grid, (V2){-16.0f, 0.0f}); entity_ensure_in_orbit(grid); Entity *explosion_box = new_entity(gs); box_create(gs, explosion_box, grid, (V2){0}); - explosion_box->no_save_to_disk = true; - explosion_box->always_visible = true; + explosion_box->box_type = BoxExplosive; + explosion_box->is_platonic = true; BOX_AT_TYPE(grid, ((V2){BOX_SIZE, 0}), BoxExplosive); BOX_AT_TYPE(grid, ((V2){BOX_SIZE * 2, 0}), BoxHullpiece); BOX_AT_TYPE(grid, ((V2){BOX_SIZE * 3, 0}), BoxHullpiece); @@ -1826,11 +1836,6 @@ void process(GameState *gs, float dt) p->damage = clamp01(p->damage); } - if (get_entity(gs, gs->cur_spacestation) == NULL) - { - gs->cur_spacestation = create_spacestation(gs); - } - // process entities for (size_t i = 0; i < gs->cur_next_entity; i++) { @@ -1869,13 +1874,19 @@ void process(GameState *gs, float dt) if (e->is_box) { + if (e->is_platonic) + { + e->damage = 0.0f; + gs->platonic_positions[(int)e->box_type] = entity_pos(e); + } if (e->box_type == BoxExplosive && e->damage >= EXPLOSION_DAMAGE_THRESHOLD) { Entity *explosion = new_entity(gs); explosion->is_explosion = true; explosion->explosion_pos = entity_pos(e); explosion->explosion_vel = grid_vel(box_grid(e)); - grid_remove_box(gs, get_entity(gs, e->shape_parent_entity), e); + if (!e->is_platonic) + grid_remove_box(gs, get_entity(gs, e->shape_parent_entity), e); } if (e->damage >= 1.0f) { @@ -1884,9 +1895,10 @@ void process(GameState *gs, float dt) } if (e->is_grid) { + Entity *grid = e; // calculate how much energy solar panels provide float energy_to_add = 0.0f; - BOXES_ITER(gs, cur, e) + BOXES_ITER(gs, cur, grid) { if (cur->box_type == BoxSolarPanel) { @@ -1896,7 +1908,7 @@ void process(GameState *gs, float dt) } // apply all of the energy to all connected batteries - BOXES_ITER(gs, cur, e) + BOXES_ITER(gs, cur, grid) { if (energy_to_add <= 0.0f) break; @@ -1913,33 +1925,78 @@ void process(GameState *gs, float dt) float non_battery_energy_left_over = energy_to_add; // use the energy, stored in the batteries, in various boxes - BOXES_ITER(gs, cur, e) + BOXES_ITER(gs, cur_box, grid) { - if (cur->box_type == BoxThruster) + if (cur_box->box_type == BoxThruster) { - float energy_to_consume = cur->wanted_thrust * THRUSTER_ENERGY_USED_PER_SECOND * dt; + float energy_to_consume = cur_box->wanted_thrust * THRUSTER_ENERGY_USED_PER_SECOND * dt; if (energy_to_consume > 0.0f) { - cur->thrust = 0.0f; - float energy_unconsumed = batteries_use_energy(gs, e, &non_battery_energy_left_over, energy_to_consume); - cur->thrust = (1.0f - energy_unconsumed / energy_to_consume) * cur->wanted_thrust; - if (cur->thrust >= 0.0f) - cpBodyApplyForceAtWorldPoint(e->body, v2_to_cp(thruster_force(cur)), v2_to_cp(entity_pos(cur))); + cur_box->thrust = 0.0f; + float energy_unconsumed = batteries_use_energy(gs, grid, &non_battery_energy_left_over, energy_to_consume); + cur_box->thrust = (1.0f - energy_unconsumed / energy_to_consume) * cur_box->wanted_thrust; + if (cur_box->thrust >= 0.0f) + cpBodyApplyForceAtWorldPoint(grid->body, v2_to_cp(thruster_force(cur_box)), v2_to_cp(entity_pos(cur_box))); } } - if (cur->box_type == BoxMedbay) + if (cur_box->box_type == BoxMedbay) { - Entity *potential_meatbag_to_heal = get_entity(gs, cur->player_who_is_inside_of_me); + Entity *potential_meatbag_to_heal = get_entity(gs, cur_box->player_who_is_inside_of_me); if (potential_meatbag_to_heal != NULL) { float wanted_energy_use = fminf(potential_meatbag_to_heal->damage, PLAYER_ENERGY_RECHARGE_PER_SECOND * dt); if (wanted_energy_use > 0.0f) { - float energy_unconsumed = batteries_use_energy(gs, e, &non_battery_energy_left_over, wanted_energy_use); + float energy_unconsumed = batteries_use_energy(gs, grid, &non_battery_energy_left_over, wanted_energy_use); potential_meatbag_to_heal->damage -= (1.0f - energy_unconsumed / wanted_energy_use) * wanted_energy_use; } } } + if (cur_box->box_type == BoxScanner) + { + // set the nearest platonic solid! + if (gs->server_side_computing) + { + float energy_unconsumed = batteries_use_energy(gs, grid, &non_battery_energy_left_over, SCANNER_ENERGY_USE * dt); + if (energy_unconsumed >= SCANNER_ENERGY_USE * dt) + { + cur_box->platonic_detection_strength = 0.0f; + cur_box->platonic_nearest_direction = (V2){0}; + } + else + { + V2 from_pos = entity_pos(cur_box); + V2 nearest = {0}; + float nearest_dist = INFINITY; + for (int i = 0; i < MAX_BOX_TYPES; i++) + { + V2 cur_pos = gs->platonic_positions[i]; + if (V2length(cur_pos) > 0.0f) // zero is uninitialized, the platonic solid doesn't exist (probably) @Robust do better + { + float length_to_cur = V2dist(from_pos, cur_pos); + if (length_to_cur < nearest_dist) + { + nearest_dist = length_to_cur; + nearest = cur_pos; + } + } + } + if (nearest_dist < INFINITY) + { + cur_box->platonic_nearest_direction = V2normalize(V2sub(nearest, from_pos)); + cur_box->platonic_detection_strength = fmaxf(0.1f, 1.0f - fminf(1.0f, nearest_dist / 100.0f)); + } + else + { + cur_box->platonic_nearest_direction = (V2){0}; + cur_box->platonic_detection_strength = 0.0f; + } + } + } + cur_box->scanner_head_rotate_speed = lerp(cur_box->scanner_head_rotate_speed, cur_box->platonic_detection_strength > 0.0f ? 3.0f : 0.0f, dt * 3.0f); + cur_box->scanner_head_rotate += cur_box->scanner_head_rotate_speed * dt; + cur_box->scanner_head_rotate = fmodf(cur_box->scanner_head_rotate, 2.0f * PI); + } } } } diff --git a/loaded/itemswitch.png b/loaded/itemswitch.png new file mode 100644 index 0000000000000000000000000000000000000000..546cd6f9fc75a3c222e1a315478c329710a5d604 GIT binary patch literal 5012 zcmeHKYg7~077l7r9<{!x$U}x81*MZr9*|5H0U-fNC5RAF5Q~$^1V-~B86Y6Oz@i{k z!K$dBMJTmi!3Q8%e4yx6t|+A_E!0|6DqLHox=;{Xdrtx)uG_Wla;@8cCM(IDefIv& z-rw1K=bY^DuzBxVJ6O|bw09*T;t22ynLZON!M7nTt&c_+%U21+$+W&oJ=?7clA_&jQ2{BU! z#Wm7DpBM^UJVRaWyGydBWdtQQx?i(DDEufIj;`rgWMpD%Jb2%Y5zXrPxuDEYEZV|) zklGR4b93z+!T8TukF_?aHudDLUFF-JoIdbi{Wn5>ocM))r@gGCL?4s$`{Zxt>#jAI+wQNm$qw>5zi}6P?T0h; zG3vmhL>t<2L&KeGHNBJU$U1&HBh_(ljs5C{XAB?OxRo?tF6}8vbi6{ z=1rKTiE?mqmxv;cUf&@K8+iQ5&i1ONMG-&bsyCE}h6j5!&OCgt&hb(~O7`XZc3uM) zIjy%paqrykd}95CPn~6F$E3IQ6MDNzeO^sDW0gyriVqmm<};!{f5%Zcc(*N5wbUYT zTn}vEo0o@9aouJ;cSc1ep8?IiWyswAt9VRjXL7yIzU-4$Bj@Z%(=Oyt8ES{`84c?p?KU&3cx0ZQ+5dCL?)o zVZgNg=dX&F-5iK(+@1Y!Z!19r|FseO^F3VT{N1-C!|OeE)j`dRwby#!qs`g zkcPQV7vm!52YF3=0NvSEoOB$TL$3|7+q}HE$}T1eIoW5khdT>qY{^)8?%dXkhcY); zR!pqOfCen?^mLW>(A{4!XIE`{)LHA&`%w7smPKIYd0Az=Q+V~ByqrrHRGLM7XAigp z3`{zA_(G^K@8hQWr1T#f8osOZY}WhtciV?Q{$Q*x{gG4arw)%SAN47v-KQ2f$=xNc zejYP9JsB6a#_n=nb2qjsx^uUy+x9&k6DM!J$kl|xqA0|AWsXIB=R`~CH*Y^BfW#OK*-uON#3K?zs%F)x+{fUD##le64?AX~k$sUe) z`t_B%RduVL_lMS?lfbkq6*g%&3j0`nsW zmn(cQL=O9)Tpr9-^0=^^&G&)55pM)V*-DJZ#zsL&GzJRO;DiYZfHO#d!;&K`oQ=w1 zoR6?!uAHraF`UhX362tBv*c_aF2NfGu|Q9Pti)8Ky)r>50F<{J@*{}k~Ic%;R!4V$U45h#Y!CJi<1KUZev3P>1)5M!6OoR&p!X-jFn}NKNgsU)0 z2@F6DkQ#+H#qerMMyiQO3N!J^^6}yGx!!yfL3n&0md`7tC_--lxoARVAq)=NJY(7z z0SE_Bihy2&ZXWH~Y|1qG$RC`^n|1OP?YYypcUV4*Su z5wQ3I76K!D0WwNot00xB|4Z9cK9KLwriYLQuzsppG*nZOMB>nBXs9C1#RNg-vJhbS zPzVMrnNXPh1Xx2;cmk%0CqVxgPS}@n@-LEsr{E}fT$Bs*{0w`%t5-lQZDrhNdE!7LU$iZVKLI9R5s zW4w&km-%0O_?i_)wHRPGBm=DrbVBAxD;(wv`1*IghWFv`TmpjLF7ihFzNPCeU2nv| z8!5lduD5i(5d&|e{5HG(Z**C|x=axoZ~{sJSEW5Rx252kWhtLOPi($$(VmIw)`BN1 zT}ZTnMq{{{J{HC={F8xjJSC9^jlX9zb;1<0MQI8XbC0Ks!eBdn)#qeD4-OQ zN`OoU#l`CEPb)e2H?>K>c+$FA+%DOHo-KM M1ciw!0+(m}2bCRb6951J literal 0 HcmV?d00001 diff --git a/main.c b/main.c index 620605a..c7171a8 100644 --- a/main.c +++ b/main.c @@ -978,7 +978,7 @@ static void ui(bool draw, float dt, float width, float height) (float)sg_query_image_info(image_itemframe).width * 2.0f; float itemframe_height = (float)sg_query_image_info(image_itemframe).height * 2.0f; - float total_width = itemframe_width * (float) ARRLENF(boxes); + float total_width = itemframe_width * (float)ARRLENF(boxes); float item_width = itemframe_width * 0.75f; float item_height = itemframe_height * 0.75f; float item_offset_x = (itemframe_width - item_width) / 2.0f; @@ -1629,14 +1629,28 @@ static void frame(void) if (b->box_type == BoxScanner) { sgp_set_image(0, image_scanner_head); - sgp_rotate_at((float)gs.time * 3.0f, entity_pos(b).x, entity_pos(b).y); + sgp_rotate_at(b->scanner_head_rotate, entity_pos(b).x, entity_pos(b).y); pipeline_scope(goodpixel_pipeline) draw_texture_centered(entity_pos(b), BOX_SIZE); + set_color(WHITE); } sgp_set_color(0.5f, 0.1f, 0.1f, b->damage); draw_color_rect_centered(entity_pos(b), BOX_SIZE); } + + // outside of the transform scope + if (b->box_type == BoxScanner) + { + if (b->platonic_detection_strength > 0.0f) + { + set_color(colhexcode(0xf2d75c)); + V2 to = V2add(entity_pos(b), V2scale(b->platonic_nearest_direction, b->platonic_detection_strength)); + dbg_rect(to); + dbg_rect(entity_pos(b)); + sgp_draw_line(entity_pos(b).x, entity_pos(b).y, to.x, to.y); + } + } } } diff --git a/compile_flags.txt b/old_compile_flags.txt similarity index 76% rename from compile_flags.txt rename to old_compile_flags.txt index a7162b7..232d75a 100644 --- a/compile_flags.txt +++ b/old_compile_flags.txt @@ -1,3 +1,6 @@ +main.c +gamestate.c +server.c -Ithirdparty -Ithirdparty/minilzo -Ithirdparty/enet/include diff --git a/server.c b/server.c index 221223a..f0d77eb 100644 --- a/server.c +++ b/server.c @@ -76,8 +76,11 @@ void server(void *info_raw) size_t entities_size = (sizeof(Entity) * MAX_ENTITIES); Entity *entity_data = malloc(entities_size); initialize(&gs, entity_data, entities_size); + gs.server_side_computing = true; Log("Allocated %zu bytes for entities\n", entities_size); + create_initial_world(&gs); + // inputs Queue player_input_queues[MAX_PLAYERS] = {0}; size_t input_queue_data_size = QUEUE_SIZE_FOR_ELEMENTS(sizeof(InputFrame), INPUT_QUEUE_MAX); diff --git a/tooling.ahk b/tooling.ahk index 0332727..617678c 100644 --- a/tooling.ahk +++ b/tooling.ahk @@ -8,12 +8,17 @@ SetWorkingDir, %A_ScriptDir% ^Esc::return ^b:: -WinKill, "Flight Not Hosting" +WinKill, Flight Hosting Sleep, 20 +WinActivate flight.rdbg +Sleep 20 +Send, {Shift down}{F5}{Shift up} +Send, {F5} WinActivate, flightbuild If WinActive("flightbuild") { - Send, cd C:\Users\Cameron\Documents\flight{Enter} build_debug.bat && flight_debug.exe{Enter} + Send, {Enter} + Send, msbuild{Enter} } return diff --git a/types.h b/types.h index 86b5491..254026a 100644 --- a/types.h +++ b/types.h @@ -2,6 +2,7 @@ #include "ipsettings.h" +#define MAX_BOX_TYPES 64 #define MAX_PLAYERS 16 #define MAX_ENTITIES 1024 * 25 #define BOX_SIZE 0.25f @@ -10,6 +11,7 @@ #define PLAYER_JETPACK_FORCE 1.5f // #define PLAYER_JETPACK_FORCE 20.0f #define PLAYER_JETPACK_SPICE_PER_SECOND 0.1f +#define SCANNER_ENERGY_USE 0.05f #define MAX_HAND_REACH 1.0f #define GOLD_COLLECT_RADIUS 0.3f #define BUILD_BOX_SNAP_DIST_TO_SHIP 0.2f @@ -229,7 +231,10 @@ typedef struct Entity // boxes bool is_box; + bool is_platonic; // can't be destroyed, unaffected by physical forces bool always_visible; // always serialized to the player + + enum BoxType box_type; EntityID next_box; EntityID prev_box; // doubly linked so can remove in middle of chain @@ -240,6 +245,12 @@ typedef struct Entity float energy_used; // battery, between 0 battery capacity. You have to look through code to figure out what that is! haha sucker! float sun_amount; // solar panel, between 0 and 1 EntityID player_who_is_inside_of_me; + + // only updated when it's a scanner + float scanner_head_rotate_speed; // not serialized, cosmetic + float scanner_head_rotate; + V2 platonic_nearest_direction; // normalized + float platonic_detection_strength; // from zero to one } Entity; typedef struct Player @@ -261,8 +272,10 @@ typedef struct GameState V2 goldpos; Player players[MAX_PLAYERS]; - - EntityID cur_spacestation; + + V2 platonic_positions[MAX_BOX_TYPES]; // don't want to search over every entity to get the nearest platonic box! + + bool server_side_computing; // some things only the server should know and calculate, like platonic locations // Entity arena // ent:ity pointers can't move around because of how the physics engine handles user data. @@ -333,7 +346,7 @@ void create_player(Player *player); bool box_unlocked(Player *player, enum BoxType box); // gamestate -EntityID create_spacestation(GameState *gs); +EntityID create_initial_world(GameState *gs); void initialize(struct GameState *gs, void *entity_arena, size_t entity_arena_size); void destroy(struct GameState *gs); void process_fixed_timestep(GameState *gs); diff --git a/world.bin b/world.bin deleted file mode 100644 index 5e8cfaaf866ab67f1a996fa865958eac963539d0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 170321 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