player into gamestate, 3d collision system, entity struct nicer

main
parent 03a0d2b5d1
commit 560e9d8db7

BIN
art/art.blend (Stored with Git LFS)

Binary file not shown.

381
main.c

@ -238,9 +238,6 @@ typedef struct Arena
size_t cur;
} Arena;
EntityRef player_ref = {0};
Entity *player = 0; // up here, used in text backend callback
typedef struct AudioSample
{
float *pcm_data; // allocated by loader, must be freed
@ -625,25 +622,25 @@ Vec2 entity_aabb_size(Entity *e)
{
if (e->is_character)
{
return V2(TILE_SIZE*0.9f, TILE_SIZE*0.5f);
return V2(1.0f*0.9f, 1.0f*0.5f);
}
else if (e->is_npc)
{
if (npc_is_knight_sprite(e))
{
return V2(TILE_SIZE*0.5f, TILE_SIZE*0.5f);
return V2(1.0f*0.5f, 1.0f*0.5f);
}
else if (e->npc_kind == NPC_Pile)
{
return V2(TILE_SIZE, TILE_SIZE);
return V2(1.0f, 1.0f);
}
else if (e->npc_kind == NPC_Door)
{
return V2(TILE_SIZE*2.0f, TILE_SIZE*2.0f);
return V2(1.0f*2.0f, 1.0f*2.0f);
}
else if (e->npc_kind == NPC_Arrow)
{
return V2(TILE_SIZE*2.0f, TILE_SIZE*2.0f);
return V2(1.0f*2.0f, 1.0f*2.0f);
}
else
{
@ -653,21 +650,21 @@ Vec2 entity_aabb_size(Entity *e)
}
else if (e->is_prop)
{
return V2(TILE_SIZE*0.5f, TILE_SIZE*0.5f);
return V2(1.0f*0.5f, 1.0f*0.5f);
}
else if (e->is_item)
{
return V2(TILE_SIZE*0.5f, TILE_SIZE*0.5f);
return V2(1.0f*0.5f, 1.0f*0.5f);
}
else if(e->is_machine)
{
if(e->machine_kind == MACH_idol_dispenser)
{
return V2(TILE_SIZE*0.5f, TILE_SIZE*0.5f);
return V2(1.0f*0.5f, 1.0f*0.5f);
}
else if(e->machine_kind == MACH_arrow_shooter)
{
return V2(TILE_SIZE*1.0f, TILE_SIZE*1.0f);
return V2(1.0f*1.0f, 1.0f*1.0f);
}
else
{
@ -844,10 +841,17 @@ Mesh load_mesh(MD_Arena *arena, MD_String8 binary_file, MD_String8 mesh_name)
return out;
}
typedef struct CollisionCube
{
struct CollisionCube *next;
AABB bounds;
} CollisionCube;
typedef struct
{
Mesh *mesh_list;
PlacedMesh *placed_mesh_list;
CollisionCube *collision_list;
} ThreeDeeLevel;
ThreeDeeLevel load_level(MD_Arena *arena, MD_String8 binary_file)
@ -926,6 +930,19 @@ ThreeDeeLevel load_level(MD_Arena *arena, MD_String8 binary_file)
}
}
MD_u64 num_collision_cubes;
ser_MD_u64(&ser, &num_collision_cubes);
for(MD_u64 i = 0; i < num_collision_cubes; i++)
{
CollisionCube *new_cube = MD_PushArray(arena, CollisionCube, 1);
Vec2 twodee_pos;
Vec2 size;
ser_Vec2(&ser, &twodee_pos);
ser_Vec2(&ser, &size);
new_cube->bounds = aabb_centered(twodee_pos, size);
MD_StackPush(out.collision_list, new_cube);
}
assert(!ser.cur_error.failed);
MD_ReleaseScratch(scratch);
@ -1376,9 +1393,9 @@ void cause_action_side_effects(Entity *from, Action a)
if(a.kind == ACT_fights_player)
{
from->standing = STANDING_FIGHTING;
player->talking_to = frome(from);
player->state = CHARACTER_TALKING;
assert(is_fighting(player));
gs.player->talking_to = frome(from);
gs.player->state = CHARACTER_TALKING;
assert(is_fighting(gs.player));
}
if(a.kind == ACT_stops_fighting_player && from->npc_kind == NPC_Arrow)
{
@ -1501,7 +1518,7 @@ bool perform_action(Entity *from, Action a)
MemoryContext context = {0};
context.author_npc_kind = from->npc_kind;
if(from == player && gete(from->talking_to))
if(from == gs.player && gete(from->talking_to))
{
context.was_talking_to_somebody = true;
context.talking_to_kind = gete(from->talking_to)->npc_kind;
@ -1571,13 +1588,13 @@ bool eq(EntityRef ref1, EntityRef ref2)
return ref1.index == ref2.index && ref1.generation == ref2.generation;
}
Entity *new_entity()
Entity *new_entity(GameState *gs)
{
for (int i = 0; i < ARRLEN(gs.entities); i++)
for (int i = 0; i < ARRLEN(gs->entities); i++)
{
if (!gs.entities[i].exists)
if (!gs->entities[i].exists)
{
Entity *to_return = &gs.entities[i];
Entity *to_return = &gs->entities[i];
int gen = to_return->generation;
*to_return = (Entity) { 0 };
to_return->exists = true;
@ -1589,22 +1606,6 @@ Entity *new_entity()
return NULL;
}
void update_player_from_entities()
{
player_ref = (EntityRef){0};
ENTITIES_ITER(gs.entities)
{
if (it->is_character)
{
assert(gete(player_ref) == 0);
player_ref = frome(it);
}
}
player = gete(player_ref);
assert(player != 0);
player->npc_kind = NPC_Player; // bad, but needed because not set by loading from level I think? investigate this.
}
typedef struct ToVisit {
struct ToVisit *next;
struct ToVisit *prev;
@ -1727,26 +1728,23 @@ int parse_enumstr_impl(MD_Arena *arena, MD_String8 enum_str, char **enumstr_arra
#define parse_enumstr(arena, enum_str, errors, string_array, enum_kind_name, prefix) parse_enumstr_impl(arena, enum_str, string_array, ARRLEN(string_array), errors, enum_kind_name, prefix)
void reset_level()
void initialize_gamestate_from_threedee_level(GameState *gs, ThreeDeeLevel *level)
{
// load level
gs.won = false;
if(!player)
{
Entity *pl = new_entity();
pl->is_character = true;
}
update_player_from_entities();
player->pos = V2(0,0);
memset(gs, 0, sizeof(GameState));
gs->player = new_entity(gs);
gs->player->is_character = true;
gs->player->npc_kind = NPC_Player;
gs->world_entity = new_entity(gs);
gs->world_entity->is_world = true;
#ifdef DEVTOOLS
if(false)
{
for (int i = 0; i < 20; i++)
BUFF_APPEND(&player->held_items, ITEM_GoldCoin);
BUFF_APPEND(&gs->player->held_items, ITEM_GoldCoin);
}
ENTITIES_ITER(gs.entities)
ENTITIES_ITER(gs->entities)
{
if(false)
if (it->npc_kind == NPC_TheBlacksmith)
@ -1854,7 +1852,7 @@ void reset_level()
if(drama_errors.node_count == 0)
{
bool found = false;
ENTITIES_ITER(gs.entities)
ENTITIES_ITER(gs->entities)
{
if(it->is_npc && it->npc_kind == want)
{
@ -1890,13 +1888,20 @@ void reset_level()
}
ENTITIES_ITER(gs.entities)
ENTITIES_ITER(gs->entities)
{
it->perceptions_dirty = false; // nobody should say anything about jester memories
}
}
}
ThreeDeeLevel level_threedee = {0};
void reset_level()
{
initialize_gamestate_from_threedee_level(&gs, &level_threedee);
}
enum
{
V0,
@ -1938,6 +1943,8 @@ void ser_entity(SerState *ser, Entity *e)
SER_BUFF(ser, ItemKind, &e->held_items);
ser_bool(ser, &e->is_world);
ser_bool(ser, &e->is_prop);
ser_PropKind(ser, &e->prop_kind);
@ -2064,6 +2071,34 @@ void ser_GameState(SerState *ser, GameState *gs)
ser_entity(ser, &(gs->entities[i]));
}
}
gs->player = 0;
gs->world_entity = 0;
if(!ser->cur_error.failed)
{
ARR_ITER(Entity, gs->entities)
{
if(it->is_character)
{
gs->player = it;
}
if(it->is_world)
{
gs->world_entity = it;
}
}
if(gs->player == 0)
{
ser->cur_error = (SerError){.failed = true, .why = MD_S8Lit("No player entity found in deserialized entities")};
}
if(gs->world_entity == 0)
{
ser->cur_error = (SerError){.failed = true, .why = MD_S8Lit("No world entity found in deserialized entities")};
}
}
}
// error_out is allocated onto arena if it fails
@ -2174,7 +2209,6 @@ void read_from_save_data(char *data, size_t length)
else
{
gs = new_gs;
update_player_from_entities();
}
MD_ReleaseScratch(scratch);
@ -2210,7 +2244,7 @@ void end_text_input(char *what_player_said_cstr)
memcpy(to_perform.speech, what_player_said.str, what_player_said.size);
to_perform.speech_length = (int)what_player_said.size;
perform_action(player, to_perform);
perform_action(gs.player, to_perform);
}
MD_ReleaseScratch(scratch);
}
@ -2436,9 +2470,12 @@ void do_serialization_tests()
Log("Testing serialization...\n");
MD_ArenaTemp scratch = MD_GetScratch(0, 0);
ThreeDeeLevel level = {0};
GameState gs = {0};
initialize_gamestate_from_threedee_level(&gs, &level);
reset_level();
player->pos = V2(50.0f, 0.0);
gs.player->pos = V2(50.0f, 0.0);
MD_String8 error = {0};
MD_String8 saved = save_to_string(scratch.arena, scratch.arena, &error, &gs);
@ -2447,10 +2484,9 @@ void do_serialization_tests()
assert(saved.size > 0);
assert(saved.str != 0);
reset_level();
initialize_gamestate_from_threedee_level(&gs, &level);
gs = load_from_string(persistent_arena, scratch.arena, saved, &error);
update_player_from_entities();
assert(player->pos.x == 50.0f);
assert(gs.player->pos.x == 50.0f);
assert(error.size == 0);
Log("Default save data size is %lld bytes\n", saved.size);
@ -2460,7 +2496,6 @@ void do_serialization_tests()
#endif
Mesh mesh_player = {0};
ThreeDeeLevel level_threedee = {0};
void stbi_flip_into_correct_direction(bool do_it)
{
@ -3142,6 +3177,7 @@ void colorquad(Quad q, Color col)
draw_quad((DrawParams) { q, image_white_square, full_region(image_white_square), col, .layer = LAYER_UI });
}
Vec2 NormV2_or_zero(Vec2 v)
{
if(v.x == 0.0f && v.y == 0.0f)
@ -3300,6 +3336,50 @@ void dbg3dline(Vec3 from, Vec3 to)
dbgline(from_screenspace, to_screenspace);
}
Vec3 plane_point(Vec2 p)
{
return V3(p.x, 0.0, p.y);
}
void colorquadplane(Quad q, Color col)
{
Quad warped = {0};
for(int i = 0; i < 4; i++)
{
q.points[i] = threedee_to_screenspace(plane_point(q.points[i]));
}
colorquad(warped, col);
}
void dbgplanesquare(Vec2 at)
{
if(!show_devtools) return;
colorquadplane(quad_centered(at, V2(3.0,3.0)), debug_color);
}
void dbgplaneline(Vec2 from, Vec2 to)
{
if(!show_devtools) return;
dbg3dline(plane_point(from), plane_point(to));
}
void dbgplanevec(Vec2 from, Vec2 vec)
{
if(!show_devtools) return;
Vec2 to = AddV2(from, vec);
dbgplaneline(from, to);
}
void dbgplanerect(AABB aabb)
{
if(!show_devtools) return;
Quad q = quad_aabb(aabb);
dbgplaneline(q.ul, q.ur);
dbgplaneline(q.ur, q.lr);
dbgplaneline(q.lr, q.ll);
dbgplaneline(q.ll, q.ul);
}
typedef struct TextParams
{
bool dry_run;
@ -3495,7 +3575,7 @@ Overlapping get_overlapping(AABB aabb)
PROFILE_SCOPE("checking the entities")
ENTITIES_ITER(gs.entities)
{
if (!(it->is_character && it->is_rolling) && overlapping(aabb, entity_aabb(it)))
if (!(it->is_character && it->is_rolling) && !it->is_world && overlapping(aabb, entity_aabb(it)))
{
BUFF_APPEND(&to_return, it);
}
@ -3539,12 +3619,18 @@ Vec2 move_and_slide(MoveSlideParams p)
} CollisionObj;
BUFF(CollisionObj, 256) to_check = { 0 };
// add world boxes
for(CollisionCube *cur = level_threedee.collision_list; cur; cur = cur->next)
{
BUFF_APPEND(&to_check, ((CollisionObj){cur->bounds, gs.world_entity}));
}
// add entity boxes
if (!p.dont_collide_with_entities && !(p.from->is_character && p.from->is_rolling))
{
ENTITIES_ITER(gs.entities)
{
if (!(it->is_character && it->is_rolling) && it != p.from && !(it->is_npc && it->dead) && !it->is_item && !(it->is_npc && it->npc_kind == NPC_Door && it->opened))
if (!(it->is_character && it->is_rolling) && it != p.from && !(it->is_npc && it->dead) && !it->is_world && !it->is_item && !(it->is_npc && it->npc_kind == NPC_Door && it->opened))
{
BUFF_APPEND(&to_check, ((CollisionObj){aabb_centered(it->pos, entity_aabb_size(it)), it}));
}
@ -3602,7 +3688,7 @@ Vec2 move_and_slide(MoveSlideParams p)
{
dbgcol(GREEN)
{
dbgrect(it->aabb);
dbgplanerect(it->aabb);
}
}
@ -3611,11 +3697,11 @@ Vec2 move_and_slide(MoveSlideParams p)
BUFF_ITER(CollisionObj, &actually_overlapping)
dbgcol(WHITE)
dbgrect(it->aabb);
dbgplanerect(it->aabb);
BUFF_ITER(CollisionObj, &overlapping_smallest_first)
dbgcol(WHITE)
dbgsquare(aabb_center(it->aabb));
dbgplanesquare(aabb_center(it->aabb));
CollisionInfo info = { 0 };
for (int col_iter_i = 0; col_iter_i < 1; col_iter_i++)
@ -3652,7 +3738,7 @@ Vec2 move_and_slide(MoveSlideParams p)
assert(closest_index != -1);
Vec2 move_dir = compass_dirs[closest_index];
info.normal = move_dir;
dbgvec(from_point, MulV2F(move_dir, 30.0f));
dbgplanevec(from_point, MulV2F(move_dir, 30.0f));
Vec2 move = MulV2F(move_dir, move_dist);
at_new.upper_left = AddV2(at_new.upper_left, move);
at_new.lower_right = AddV2(at_new.lower_right, move);
@ -3689,7 +3775,7 @@ Vec2 move_and_slide(MoveSlideParams p)
if (p.col_info_out) *p.col_info_out = info;
Vec2 result_pos = aabb_center(at_new);
dbgrect(aabb_centered(result_pos, collision_aabb_size));
dbgplanerect(aabb_centered(result_pos, collision_aabb_size));
return result_pos;
}
@ -4137,12 +4223,6 @@ void draw_item(ItemKind kind, AABB in_aabb, float alpha)
void frame(void)
{
if(gete(player_ref) == 0 || gete(player_ref)->npc_kind != NPC_Player)
{
update_player_from_entities();
player = gete(player_ref);
assert(player);
}
static float speed_factor = 1.0f;
// elapsed_time
double unwarped_dt_double = 0.0;
@ -4193,7 +4273,7 @@ void frame(void)
uint64_t time_start_frame = stm_now();
Vec3 player_pos = V3(player->pos.x, 0.0, player->pos.y);
Vec3 player_pos = V3(gs.player->pos.x, 0.0, gs.player->pos.y);
dbg3dline(player_pos, V3(0,0,0));
//dbgline(V2(0,0), V2(500, 500));
const float vertical_to_horizontal_ratio = 0.8f;
@ -4263,7 +4343,7 @@ void frame(void)
draw_placed(view, projection, cur);
}
draw_placed(view, projection, &(PlacedMesh){.draw_with = &mesh_player, .offset = V3(player->pos.x, 0.0, player->pos.y), .rotation = Make_Q(0, 0, 0, 1), .scale = V3(1, 1, 1)});
draw_placed(view, projection, &(PlacedMesh){.draw_with = &mesh_player, .offset = V3(gs.player->pos.x, 0.0, gs.player->pos.y), .rotation = Make_Q(0, 0, 0, 1), .scale = V3(1, 1, 1)});
sg_end_pass();
@ -4346,7 +4426,7 @@ void frame(void)
}
#endif
assert(player != NULL);
assert(gs.player != NULL);
// gameplay processing loop, do multiple if lagging
// these are static so that, on frames where no gameplay processing is necessary and just rendering, the rendering uses values from last frame
@ -4356,7 +4436,7 @@ void frame(void)
float speed_target;
// pausing the game
if (player->in_conversation_mode || gs.won)
if (gs.player->in_conversation_mode || gs.won)
{
speed_target = 0.0f;
}
@ -4440,7 +4520,7 @@ void frame(void)
player_in_combat = false; // in combat set by various enemies when they fight the player
PROFILE_SCOPE("entity processing")
{
if(player->knighted)
if(gs.player->knighted)
{
gs.won = true;
}
@ -4550,15 +4630,15 @@ void frame(void)
}
it->being_hovered = false;
if (player->in_conversation_mode)
if (gs.player->in_conversation_mode)
{
if (has_point(entity_aabb(it), screen_to_world(mouse_pos)))
{
it->being_hovered = true;
if (pressed.mouse_down)
{
player->talking_to = frome(it);
player->state = CHARACTER_TALKING;
gs.player->talking_to = frome(it);
gs.player->state = CHARACTER_TALKING;
}
}
}
@ -4595,7 +4675,7 @@ void frame(void)
{
it->word_anim_in = 0;
}
float dist = LenV2(SubV2(it->pos, player->pos));
float dist = LenV2(SubV2(it->pos, gs.player->pos));
float volume = Lerp(-0.6f, clamp01(dist / 70.0f), -1.0f);
AudioSample * possible_grunts[] = {
&sound_grunt_0,
@ -4624,7 +4704,7 @@ void frame(void)
}
}
Vec2 target = get_point_along_trail(BUFF_MAKEREF(&player->position_history), (float)place_in_line * TILE_SIZE);
Vec2 target = get_point_along_trail(BUFF_MAKEREF(&gs.player->position_history), (float)place_in_line * TILE_SIZE);
it->pos = LerpV2(it->pos, dt*5.0f, target);
}
@ -4634,7 +4714,7 @@ void frame(void)
if(false)
if (it->standing == STANDING_FIGHTING || it->standing == STANDING_JOINED)
{
Entity *targeting = player;
Entity *targeting = gs.player;
/*
G cost: distance from the current node to the start node
@ -4673,7 +4753,7 @@ void frame(void)
const float jump_size = TILE_SIZE / 2.0f;
BUFF_APPEND(&nodes, ((AStarNode) { .in_open_set = true, .pos = from }));
Vec2 from_hash = V2_HASH(from);
float got_there_tolerance = max_coord(entity_aabb_size(player))*1.5f;
float got_there_tolerance = max_coord(entity_aabb_size(gs.player))*1.5f;
hmput(node_cache, from_hash, &nodes.data[0]);
bool should_quit = false;
@ -4965,8 +5045,8 @@ void frame(void)
{
if (it->held_by_player)
{
Vec2 held_spot = V2(15.0f * (player->facing_left ? -1.0f : 1.0f), 7.0f);
it->pos = AddV2(player->pos, held_spot);
Vec2 held_spot = V2(15.0f * (gs.player->facing_left ? -1.0f : 1.0f), 7.0f);
it->pos = AddV2(gs.player->pos, held_spot);
}
else
{
@ -4992,13 +5072,16 @@ void frame(void)
if(it->arrow_timer >= SECONDS_PER_ARROW)
{
it->arrow_timer = 0.0;
Entity *new_arrow = new_entity();
Entity *new_arrow = new_entity(&gs);
new_arrow->is_npc = true;
new_arrow->npc_kind = NPC_Arrow;
new_arrow->pos = AddV2(it->pos, V2(entity_aabb_size(new_arrow).x + 0.01f, 0.0));
}
}
}
else if (it->is_world)
{
}
else
{
assert(false);
@ -5161,7 +5244,7 @@ void frame(void)
{
// find closest to talk to
{
AABB dialog_rect = aabb_centered(player->pos, V2(dialog_interact_size , dialog_interact_size));
AABB dialog_rect = aabb_centered(gs.player->pos, V2(dialog_interact_size , dialog_interact_size));
dbgrect(dialog_rect);
Overlapping possible_dialogs = get_overlapping(dialog_rect);
float closest_interact_with_dist = INFINITY;
@ -5181,7 +5264,7 @@ void frame(void)
if (entity_interactible)
{
float dist = LenV2(SubV2((*it)->pos, player->pos));
float dist = LenV2(SubV2((*it)->pos, gs.player->pos));
if (dist < closest_interact_with_dist)
{
closest_interact_with_dist = dist;
@ -5192,35 +5275,35 @@ void frame(void)
}
// maybe get rid of talking to
if (player->state == CHARACTER_TALKING)
if (gs.player->state == CHARACTER_TALKING)
{
if (gete(player->talking_to) == 0)
if (gete(gs.player->talking_to) == 0)
{
player->state = CHARACTER_IDLE;
gs.player->state = CHARACTER_IDLE;
}
}
else
{
player->talking_to = (EntityRef) { 0 };
gs.player->talking_to = (EntityRef) { 0 };
}
interacting_with = closest_interact_with;
if (player->state == CHARACTER_TALKING)
if (gs.player->state == CHARACTER_TALKING)
{
interacting_with = gete(player->talking_to);
interacting_with = gete(gs.player->talking_to);
assert(interacting_with);
}
}
if (interact)
{
if (player->state == CHARACTER_TALKING)
if (gs.player->state == CHARACTER_TALKING)
{
// don't add extra stuff to be done when changing state because in several
// places it's assumed to end dialog I can just do player->state = CHARACTER_IDLE
if(!is_fighting(player))
if(!is_fighting(gs.player))
{
player->state = CHARACTER_IDLE;
gs.player->state = CHARACTER_IDLE;
}
}
else if (closest_interact_with)
@ -5241,7 +5324,7 @@ void frame(void)
}
if(members_in_party >= 3)
{
BUFF_APPEND(&player->held_items, ITEM_Idol);
BUFF_APPEND(&gs.player->held_items, ITEM_Idol);
closest_interact_with->has_given_idol = true;
}
else
@ -5254,8 +5337,8 @@ void frame(void)
else if (closest_interact_with->is_npc)
{
// begin dialog with closest npc
player->state = CHARACTER_TALKING;
player->talking_to = frome(closest_interact_with);
gs.player->state = CHARACTER_TALKING;
gs.player->talking_to = frome(closest_interact_with);
}
else
{
@ -5264,33 +5347,33 @@ void frame(void)
}
}
if(is_fighting(player))
if(is_fighting(gs.player))
{
player->state = CHARACTER_TALKING;
gs.player->state = CHARACTER_TALKING;
}
float speed = 0.0f;
{
Vec2 target_vel = { 0 };
if (player->state == CHARACTER_WALKING)
if (gs.player->state == CHARACTER_WALKING)
{
speed = PLAYER_SPEED;
if (player->is_rolling) speed = PLAYER_ROLL_SPEED;
if (gs.player->is_rolling) speed = PLAYER_ROLL_SPEED;
if (LenV2(movement) == 0.0)
{
player->state = CHARACTER_IDLE;
gs.player->state = CHARACTER_IDLE;
}
else
{
}
}
else if (player->state == CHARACTER_IDLE)
else if (gs.player->state == CHARACTER_IDLE)
{
if (LenV2(movement) > 0.01) player->state = CHARACTER_WALKING;
if (LenV2(movement) > 0.01) gs.player->state = CHARACTER_WALKING;
}
else if (player->state == CHARACTER_TALKING)
else if (gs.player->state == CHARACTER_TALKING)
{
}
else
@ -5302,30 +5385,30 @@ void frame(void)
// velocity processing
{
Vec2 target_vel = MulV2F(movement, pixels_per_meter * speed);
player->vel = LerpV2(player->vel, dt * 15.0f, target_vel);
player->pos = move_and_slide((MoveSlideParams) { player, player->pos, MulV2F(player->vel, dt) });
gs.player->vel = LerpV2(gs.player->vel, dt * 15.0f, target_vel);
gs.player->pos = move_and_slide((MoveSlideParams) { gs.player, gs.player->pos, MulV2F(gs.player->vel, dt) });
bool should_append = false;
// make it so no snap when new points added
if(player->position_history.cur_index > 0)
if(gs.player->position_history.cur_index > 0)
{
player->position_history.data[player->position_history.cur_index - 1] = player->pos;
gs.player->position_history.data[gs.player->position_history.cur_index - 1] = gs.player->pos;
}
if(player->position_history.cur_index > 2)
if(gs.player->position_history.cur_index > 2)
{
should_append = LenV2(SubV2(player->position_history.data[player->position_history.cur_index - 2], player->pos)) > TILE_SIZE;
should_append = LenV2(SubV2(gs.player->position_history.data[gs.player->position_history.cur_index - 2], gs.player->pos)) > TILE_SIZE;
}
else
{
should_append = true;
}
if(should_append) BUFF_QUEUE_APPEND(&player->position_history, player->pos);
if(should_append) BUFF_QUEUE_APPEND(&gs.player->position_history, gs.player->pos);
}
// health
if (player->damage >= 1.0)
if (gs.player->damage >= 1.0)
{
reset_level();
}
@ -5357,24 +5440,24 @@ void frame(void)
{
DrawnAnimatedSprite to_draw = { 0 };
if(player->position_history.cur_index > 0)
if(gs.player->position_history.cur_index > 0)
{
float trail_len = get_total_trail_len(BUFF_MAKEREF(&player->position_history));
float trail_len = get_total_trail_len(BUFF_MAKEREF(&gs.player->position_history));
if(trail_len > 0.0f) // fmodf returns nan
{
float along = fmodf((float)elapsed_time*100.0f, 200.0f);
Vec2 at = get_point_along_trail(BUFF_MAKEREF(&player->position_history), along);
Vec2 at = get_point_along_trail(BUFF_MAKEREF(&gs.player->position_history), along);
dbgbigsquare(at);
dbgbigsquare(get_point_along_trail(BUFF_MAKEREF(&player->position_history), 50.0f));
dbgbigsquare(get_point_along_trail(BUFF_MAKEREF(&gs.player->position_history), 50.0f));
}
BUFF_ITER_I(Vec2, &player->position_history, i)
BUFF_ITER_I(Vec2, &gs.player->position_history, i)
{
if(i == player->position_history.cur_index - 1)
if(i == gs.player->position_history.cur_index - 1)
{
}
else
{
dbgline(*it, player->position_history.data[i + 1]);
dbgline(*it, gs.player->position_history.data[i + 1]);
}
}
}
@ -5386,7 +5469,7 @@ void frame(void)
if (!mobile_controls)
{
float size = 100.0f;
Vec2 midpoint = MulV2F(AddV2(interacting_with->pos, player->pos), 0.5f);
Vec2 midpoint = MulV2F(AddV2(interacting_with->pos, gs.player->pos), 0.5f);
draw_quad((DrawParams) { quad_centered(AddV2(midpoint, V2(0.0, 5.0f + sinf((float)elapsed_time*3.0f)*5.0f)), V2(size, size)), IMG(image_e_icon), blendalpha(WHITE, clamp01(1.0f - learned_e)), .layer = LAYER_UI_FG });
}
@ -5394,13 +5477,13 @@ void frame(void)
draw_quad((DrawParams) { quad_centered(interacting_with->pos, V2(TILE_SIZE, TILE_SIZE)), image_hovering_circle, full_region(image_hovering_circle), WHITE, .layer = LAYER_UI });
}
if (player->state == CHARACTER_WALKING)
if (gs.player->state == CHARACTER_WALKING)
{
}
else if (player->state == CHARACTER_IDLE)
else if (gs.player->state == CHARACTER_IDLE)
{
}
else if (player->state == CHARACTER_TALKING)
else if (gs.player->state == CHARACTER_TALKING)
{
}
else
@ -5409,18 +5492,18 @@ void frame(void)
}
// hurt vignette
if (player->damage > 0.0)
if (gs.player->damage > 0.0)
{
draw_quad((DrawParams) { (Quad) { .ul = V2(0.0f, screen_size().Y), .ur = screen_size(), .lr = V2(screen_size().X, 0.0f) }, image_hurt_vignette, full_region(image_hurt_vignette), (Color) { 1.0f, 1.0f, 1.0f, player->damage }, .layer = LAYER_SCREENSPACE_EFFECTS, });
draw_quad((DrawParams) { (Quad) { .ul = V2(0.0f, screen_size().Y), .ur = screen_size(), .lr = V2(screen_size().X, 0.0f) }, image_hurt_vignette, full_region(image_hurt_vignette), (Color) { 1.0f, 1.0f, 1.0f, gs.player->damage }, .layer = LAYER_SCREENSPACE_EFFECTS, });
}
player->anim_change_timer += dt;
if (player->anim_change_timer >= 0.05f)
gs.player->anim_change_timer += dt;
if (gs.player->anim_change_timer >= 0.05f)
{
player->anim_change_timer = 0.0f;
player->cur_animation = to_draw.anim;
gs.player->anim_change_timer = 0.0f;
gs.player->cur_animation = to_draw.anim;
}
to_draw.anim = player->cur_animation;
to_draw.anim = gs.player->cur_animation;
if (to_draw.anim)
{
@ -5441,11 +5524,11 @@ void frame(void)
if (it->is_npc)
{
float dist = LenV2(SubV2(it->pos, player->pos));
float dist = LenV2(SubV2(it->pos, gs.player->pos));
dist -= 10.0f; // radius around point where dialog is completely opaque
float max_dist = dialog_interact_size / 2.0f;
float alpha = 1.0f - (float)clamp(dist / max_dist, 0.0, 1.0);
if (gete(player->talking_to) == it && player->state == CHARACTER_TALKING) alpha = 0.0f;
if (gete(gs.player->talking_to) == it && gs.player->state == CHARACTER_TALKING) alpha = 0.0f;
if (it->being_hovered)
{
draw_quad((DrawParams) { quad_centered(it->pos, V2(TILE_SIZE, TILE_SIZE)), IMG(image_hovering_circle), WHITE });
@ -5534,9 +5617,9 @@ void frame(void)
PROFILE_SCOPE("dialog menu") // big dialog panel draw big dialog panel
{
static float on_screen = 0.0f;
Entity *talking_to = gete(player->talking_to);
Entity *talking_to = gete(gs.player->talking_to);
on_screen = Lerp(on_screen, unwarped_dt*9.0f, talking_to ? 1.0f : 0.0f);
if(is_fighting(player))
if(is_fighting(gs.player))
{
assert(talking_to);
draw_centered_text((TextParams){ false, MD_S8Fmt(frame_arena, "%s is fighting you. You can't leave until they stop fighting you", characters[talking_to->npc_kind].name), V2(screen_size().x*0.75f, screen_size().y*0.5f), WHITE, 1.0f});
@ -5548,9 +5631,9 @@ void frame(void)
if (aabb_is_valid(panel_aabb))
{
if (!item_grid_state.open && pressed.mouse_down && !has_point(panel_aabb, mouse_pos) && !is_fighting(player))
if (!item_grid_state.open && pressed.mouse_down && !has_point(panel_aabb, mouse_pos) && !is_fighting(gs.player))
{
player->state = CHARACTER_IDLE;
gs.player->state = CHARACTER_IDLE;
}
draw_quad((DrawParams) { quad_aabb(panel_aabb), IMG(image_white_square), blendalpha(BLACK, 0.7f) });
@ -5706,7 +5789,7 @@ void frame(void)
// item grid modal draw item grid choose item pick item give item
{
static float visible = 0.0f;
static float hovered_state[ARRLEN(player->held_items.data)] = { 0 };
static float hovered_state[ARRLEN(gs.player->held_items.data)] = { 0 };
float target = 0.0f;
if (item_grid_state.open) target = 1.0f;
visible = Lerp(visible, unwarped_dt*9.0f, target);
@ -5733,7 +5816,7 @@ void frame(void)
const int horizontal_item_count = 10;
const int vertical_item_count = 6;
assert(ARRLEN(player->held_items.data) < horizontal_item_count * vertical_item_count);
assert(ARRLEN(gs.player->held_items.data) < horizontal_item_count * vertical_item_count);
Vec2 space_for_items = SubV2(aabb_size(grid_aabb), V2(padding*2.0f, padding*2.0f));
float item_icon_width = (space_for_items.x - (horizontal_item_count - 1)*padding_btwn_items) / horizontal_item_count;
@ -5741,7 +5824,7 @@ void frame(void)
Vec2 cursor = AddV2(grid_aabb.upper_left, V2(padding, -padding));
int pressed_index = -1;
BUFF_ITER_I(ItemKind, &player->held_items, i)
BUFF_ITER_I(ItemKind, &gs.player->held_items, i)
{
Vec2 real_size = LerpV2(item_icon_size, hovered_state[i], MulV2F(item_icon_size, 1.25f));
Vec2 item_center = AddV2(cursor, MulV2F(V2(item_icon_size.x, -item_icon_size.y), 0.5f));
@ -5778,19 +5861,19 @@ void frame(void)
if (pressed_index > -1)
{
item_grid_state.open = false;
ItemKind selected_item = player->held_items.data[pressed_index];
ItemKind selected_item = gs.player->held_items.data[pressed_index];
if(item_grid_state.for_giving && player->state == CHARACTER_TALKING)
if(item_grid_state.for_giving && gs.player->state == CHARACTER_TALKING)
{
Entity *to = gete(player->talking_to);
Entity *to = gete(gs.player->talking_to);
assert(to);
Action give_action = {.kind = ACT_gift_item_to_targeting, .argument = { .item_to_give = selected_item }, .talking_to_somebody = true, .talking_to_kind = to->npc_kind};
perform_action(player, give_action);
perform_action(gs.player, give_action);
}
else
{
use_item(player, selected_item);
use_item(gs.player, selected_item);
}
}
}
@ -5884,7 +5967,7 @@ void frame(void)
// update camera position
{
Vec2 target = MulV2F(player->pos, -1.0f * cam.scale);
Vec2 target = MulV2F(gs.player->pos, -1.0f * cam.scale);
if (LenV2(SubV2(target, cam.pos)) <= 0.2)
{
cam.pos = target;

@ -240,6 +240,14 @@ typedef struct Entity
bool destroy;
int generation;
// the kinds are at the top so you can quickly see what kind an entity is in the debugger
bool is_world;
bool is_prop;
bool is_machine;
bool is_item;
bool is_npc;
bool is_character;
// fields for all gs.entities
Vec2 pos;
Vec2 vel; // only used sometimes, like in old man and bullet
@ -248,28 +256,28 @@ typedef struct Entity
double dead_time;
bool dead;
// the static world. An entity is always returned when you collide with something so support that here
// npcs and player
BUFF(ItemKind, 32) held_items;
// props
bool is_prop;
PropKind prop_kind;
// machines, like the machine that gives the player the idol, or the ones that
// shoot arrows
bool is_machine;
MachineKind machine_kind;
bool has_given_idol;
float idol_reminder_opacity; // fades out
float arrow_timer;
// items
bool is_item;
bool held_by_player;
ItemKind item_kind;
// npcs
bool is_npc;
bool being_hovered;
bool perceptions_dirty;
TextChunk *errorlist_first;
@ -298,7 +306,6 @@ typedef struct Entity
double swing_timer;
// character
bool is_character;
bool knighted;
bool in_conversation_mode;
Vec2 to_throw_direction;
@ -426,6 +433,10 @@ void fill_available_actions(Entity *it, AvailableActions *a)
typedef struct GameState {
uint64_t tick;
bool won;
// these must point entities in its own array.
Entity *player;
Entity *world_entity;
Entity entities[MAX_ENTITIES];
} GameState;

Loading…
Cancel
Save