+MadLabs Brewery Bar Arcade Game
+-------------------------------
+
+Think Overcooked meets Stardew Valley.
+
+Chaotic order-meeting tasks, with small town people with consistent orders.
+When they've been here a couple of times, they should have to ask for their usual.
+
+As this is an arcade game, the controls will be rather limited:
+ - At most 2 players
+ - 1 joystick and 2 buttons each
+ - 1 button for interacting
+ - 1 button for canelling
+
+
+package runtime.vars
+
+
+MAJOR_VERSION :: 0
+MINOR_VERSION :: 1
+
+DEBUG :: true
+
#load_path "/home/brendan/dev/onyx"
#load_path "./../lib"
use package core
use package glfw3
-Entity_Handles :: struct {
- update : (entity: ^Entity, dt: f32) -> void = null_proc;
- draw : (entity: ^Entity) -> void = null_proc;
- get_rect : (entity: ^Entity) -> Rect = null_proc;
-}
-
Entity :: struct {
id: u32;
type: type_expr;
+ flags: Entity_Flags;
pos: Vector2;
}
+Entity_Flags :: enum #flags {
+ Interactable;
+ Solid;
+}
+
IsEntity :: interface (e: $E) {
{ e } -> ^Entity;
- { e.handles } -> Entity_Handles;
+ e.init_data;
+ e.init(e, e.init_data.{}); // This constraint ensures that everything in the initialization data is given a default value.
+}
+
+Entity_Handles :: struct {
+ update : (entity: ^Entity, dt: f32) -> void = null_proc;
+ draw : (entity: ^Entity) -> void = null_proc;
+ get_rect : (entity: ^Entity) -> Rect = null_proc;
+ interact : (entity: ^Entity, interactor: ^Entity) -> void = null_proc;
}
Entity_Manager :: struct {
- // The allocator for these entity pointers is assumed to be the
- // context allocator.
+ entity_allocator: Allocator;
entities: [..] ^Entity;
entity_types: Map(type_expr, Entity_Handles);
register :: entity_manager_register;
add :: entity_manager_add;
+ make :: entity_manager_make;
update :: entity_manager_update;
draw :: entity_manager_draw;
- query_entities :: entity_manager_query_entities;
+ query :: entity_manager_query;
+ query_by_type :: entity_manager_query_by_type;
+ query_by_flags :: entity_manager_query_by_flags;
}
-entity_manager_make :: () -> Entity_Manager {
+entity_manager_create :: () -> Entity_Manager {
em: Entity_Manager;
+ em.entity_allocator = context.allocator; @TODO // Replace the allocator here.
array.init(^em.entities, 4);
map.init(^em.entity_types);
entity_manager_register :: (use this: ^Entity_Manager, $entity_type: type_expr) where IsEntity(^entity_type) {
if !entity_types->has(entity_type) {
- entity_types[entity_type] = entity_type.handles;
+ handles := Entity_Handles.{};
+
+ @CompilerFeatures // Maybe there should be data stored in the Type_Info_Struct about
+ // the functions/methods that are defined in the structs scope. I don't know if that
+ // would be worthwhile or if it would just be bulking up the type info data.
+ #if #defined(entity_type.update) { handles.update = entity_type.update; if DEBUG do printf("{} has '{}'.\n", entity_type, "update"); }
+ #if #defined(entity_type.draw) { handles.draw = entity_type.draw; if DEBUG do printf("{} has '{}'.\n", entity_type, "draw"); }
+ #if #defined(entity_type.get_rect) { handles.get_rect = entity_type.get_rect; if DEBUG do printf("{} has '{}'.\n", entity_type, "get_rect"); }
+ #if #defined(entity_type.interact) { handles.interact = entity_type.interact; if DEBUG do printf("{} has '{}'.\n", entity_type, "interact"); }
+
+ entity_types[entity_type] = handles;
}
}
return entity.id;
}
+entity_manager_make :: (use this: ^Entity_Manager, $entity_type: type_expr, data: entity_type.init_data = .{}) -> ^entity_type where IsEntity(^entity_type) {
+ entity := new(entity_type, allocator=entity_allocator);
+ entity_type.init(entity, data);
+ this->add(entity);
+ return entity;
+}
+
entity_manager_update :: (use this: ^Entity_Manager, dt: f32) {
for entities {
- vtable := ^entity_types[it.type];
- if vtable == null do continue;
- if vtable.update == null_proc do continue;
+ update := entity_types[it.type].update;
+ if update == null_proc do continue;
- vtable.update(it, dt);
+ update(it, dt);
}
}
entity_manager_draw :: (use this: ^Entity_Manager) {
// Entities should be sorted by z-order.
for entities {
- vtable := ^entity_types[it.type];
- if vtable == null do continue;
- if vtable.draw == null_proc do continue;
+ draw := entity_types[it.type].draw;
+ if draw == null_proc do continue;
+
+ draw(it);
+ }
+}
+
+entity_manager_query :: (use this: ^Entity_Manager, area: Rect) -> [] ^Entity {
+ ents: [..] ^Entity;
+ for entities {
+ get_rect := entity_types[it.type].get_rect;
+ if get_rect == null_proc do continue;
- vtable.draw(it);
+ if Rect.intersects(get_rect(it), area) {
+ ents << it;
+ }
}
+
+ return ents;
+
}
-entity_manager_query_entities :: (use this: ^Entity_Manager, area: Rect, type: type_expr = void) -> [] ^Entity {
+entity_manager_query_by_type :: (use this: ^Entity_Manager, area: Rect, type: type_expr) -> [] ^Entity {
+ #if DEBUG {
+ use type_info;
+ assert(struct_inherits(type, Entity), "Expected a type that inherits from Entity!");
+ }
+
ents: [..] ^Entity;
for entities {
if type != void && it.type != type do continue;
- vtable := ^entity_types[it.type];
- if vtable == null do continue;
- if vtable.get_rect == null_proc do continue;
+ get_rect := entity_types[it.type].get_rect;
+ if get_rect == null_proc do continue;
- if Rect.intersects(vtable.get_rect(it), area) {
+ if Rect.intersects(get_rect(it), area) {
ents << it;
}
}
return ents;
-}
\ No newline at end of file
+}
+
+entity_manager_query_by_flags :: (use this: ^Entity_Manager, area: Rect, flags: Entity_Flags) -> [] ^Entity {
+ ents: [..] ^Entity;
+
+ // Currently, enum #flags are not the greatest and & doesn't do what you want it to here.
+ expected_flags := cast(u32) flags;
+
+ for entities {
+ if (~~ it.flags & expected_flags) != expected_flags do continue;
+
+ get_rect := entity_types[it.type].get_rect;
+ if get_rect == null_proc do continue;
+
+ if Rect.intersects(get_rect(it), area) {
+ ents << it;
+ }
+ }
+
+ return ents;
+}
+
+entity_is :: macro (entity: ^Entity, type: type_expr, body: Code) {
+ if entity.type == type {
+ it := cast(^type) entity;
+ #insert body;
+ }
+}
use package glfw3
Player :: struct {
- handles :: Entity_Handles.{ update, draw, get_rect }
-
use entity: Entity;
- make :: () -> ^Player {
- player := new(Player);
- player.pos = .{0,0};
- return player;
+ health := 69.0f;
+
+ Size :: 16.0f
+
+ init_data :: struct {
+ pos := Vector2.{400, 200};
}
- get_rect :: (use this: ^Player) => Rect.{ pos.x, pos.y, 64, 64 };
+ init :: (use this: ^Player, data: init_data) {
+ this.pos = data.pos;
+ }
+
+ get_rect :: (use this: ^Player) => Rect.{ pos.x - Size, pos.y - Size, Size * 2, Size * 2 };
update :: (use this: ^Player, dt: f32) {
+ speed :: Size * 8;
+
delta: Vector2;
- if is_key_down(GLFW_KEY_W) do delta.y -= 200 * dt;
- if is_key_down(GLFW_KEY_S) do delta.y += 200 * dt;
- if is_key_down(GLFW_KEY_A) do delta.x -= 200 * dt;
- if is_key_down(GLFW_KEY_D) do delta.x += 200 * dt;
+ if is_key_down(GLFW_KEY_W) do delta.y -= speed * dt;
+ if is_key_down(GLFW_KEY_S) do delta.y += speed * dt;
+ if is_key_down(GLFW_KEY_A) do delta.x -= speed * dt;
+ if is_key_down(GLFW_KEY_D) do delta.x += speed * dt;
- walls := entity_manager->query_entities(.{pos.x - 100, pos.y - 100, 200, 200}, Wall);
+ walls := entity_manager->query_by_flags(.{pos.x - 100, pos.y - 100, 200, 200}, .Solid);
defer memory.free_slice(^walls);
try_move(this, .{delta.x, 0}, walls);
try_move(this, .{0, delta.y}, walls);
+
+ if is_key_just_up(GLFW_KEY_SPACE) {
+ area := Rect.{pos.x - Size * 2, pos.y - Size * 2, Size * 4, Size * 4};
+ objects := entity_manager->query_by_flags(area, Entity_Flags.Solid | .Interactable);
+ defer memory.free_slice(^objects);
+
+ for objects {
+ vtable := ^entity_manager.entity_types[it.type];
+ if vtable.interact == null_proc do continue;
+ vtable.interact(it, this);
+ }
+ }
}
try_move :: (use this: ^Player, delta: Vector2, obsticles: [] ^Entity) {
ent_rect := get_rect(this);
for obsticles {
- if Rect.intersects(ent_rect, Wall.get_rect(~~ it)) {
+ vtable := ^entity_manager.entity_types[it.type];
+ if Rect.intersects(ent_rect, vtable.get_rect(~~ it)) {
pos -= delta;
break;
}
draw :: (use this: ^Player) {
immediate_set_color(.{1,1,1});
- immediate_image(^player_texture, pos.x, pos.y, 64, 64);
+
+ rect := this->get_rect();
+ immediate_image(^player_texture, rect.x, rect.y, rect.w, rect.h);
}
}
Wall :: struct {
- handles :: Entity_Handles.{ get_rect = get_rect, draw = draw }
-
use entity: Entity;
size: Vector2;
- make :: (pos, size: Vector2) -> ^Wall {
- wall := new(Wall);
- wall.pos = pos;
- wall.size = size;
- return wall;
+ init_data :: struct {
+ pos := Vector2.{0, 0};
+ size := Vector2.{0, 0};
+ }
+
+ init :: (use this: ^Wall, data: init_data) {
+ this.pos = data.pos;
+ this.size = data.size;
+
+ flags |= .Solid;
}
get_rect :: (use this: ^Wall) => Rect.{ pos.x, pos.y, size.x, size.y };
immediate_set_color(.{1,1,1});
immediate_rectangle(pos.x, pos.y, size.x, size.y);
}
-}
\ No newline at end of file
+}
+
+Door :: struct {
+ use entity: Entity;
+ size: Vector2;
+
+ openness := 0.0f;
+ target_openness := 0.0f;
+
+ init_data :: struct {
+ pos := Vector2.{0, 0};
+ size := Vector2.{0, 0};
+ }
+
+ init :: (use this: ^Door, data: init_data) {
+ this.pos = data.pos;
+ this.size = data.size;
+
+ this.flags |= .Interactable;
+ this.flags |= .Solid;
+ }
+
+ get_rect :: (use this: ^Door) => Rect.{ pos.x, pos.y, size.x * (1 - openness), size.y };
+
+ update :: (use this: ^Door, dt: f32) {
+ if openness != target_openness {
+ move_towards(^openness, target_openness, dt * 2);
+ }
+ }
+
+ interact :: (use this: ^Door, interactor: ^Entity) {
+ printf("The door at {} was interacted with by a {}!\n", pos, interactor.type);
+
+ // Doors only open if interacted with by a player
+ entity_is(interactor, Player) {
+ if target_openness > 0 do target_openness = 0;
+ else do target_openness = 0.8f;
+
+ dist := Vector2.mag(this.pos - interactor.pos);
+ printf("The player is {} units away and has {.2} health.\n", dist, it.health);
+ }
+ }
+
+ draw :: (use this: ^Door) {
+ immediate_set_color(.{0.7, 0.7, 0.1});
+
+ r := this->get_rect();
+ immediate_rectangle(r.x, r.y, r.w, r.h);
+ }
+}
+
+@Relocate
+move_towards :: (v: ^$T, target: T, diff: T) {
+ if math.abs(target - *v) <= diff {
+ *v = target;
+ return;
+ }
+
+ if *v < target do *v += diff;
+ if *v > target do *v -= diff;
+}
+
Vector2 :: struct [conv.Custom_Format.{format_vector2}] {
x, y: f32;
+
+ mag :: macro (v: Vector2) => math.sqrt(v.x * v.x + v.y * v.y);
}
Vector3i :: struct [conv.Custom_Format.{format_vector3i}] {
}
#operator + macro (v1, v2: Vector2) => (typeof v1).{ v1.x + v2.x, v1.y + v2.y };
-#operator - macro (v1, v2: Vector2) => Vector2.{ v1.x - v2.x, v1.y - v2.y };
-#operator * macro (v: Vector2, s: f32) => Vector2.{ v.x * s, v.y * s };
+#operator - macro (v1, v2: Vector2) => (typeof v1).{ v1.x - v2.x, v1.y - v2.y };
+#operator * macro (v: Vector2, s: f32) => (typeof v1).{ v.x * s, v.y * s };
#operator == macro (v1, v2: Vector2) => v1.x == v2.x && v1.y == v2.y;
#operator + macro (v1, v2: Vector3) => Vector3.{ v1.x + v2.x, v1.y + v2.y, v1.z + v2.z };
&& r1.y <= r2.y + r2.h
&& r1.y + r1.h >= r2.y;
}
-}
\ No newline at end of file
+}