* More items to order
+* Reputation = likelihood someone walks in
+
+* Waves / Continuous flow
+
+* Purchase bar-upgrades between days
\ No newline at end of file
+[Custom]
+id = 342
+flags = 0
+pos = 0 0
+size = 8 8
+:CollisionMaskComponent
+should_render=false
+:RenderComponent
+layer=1000
+
[Background]
id = 100
flags = 0
size.y = 32.0000
:FurnitureComponent
furniture_type = 0
-taken = true
+taken = false
:SpriteRenderComponent
sprite.sheet = "./assets/images/spritesheet.png"
sprite.pos.x = 48.0000
[Custom]
id = 334
-flags = 1
+flags = 3
pos.x = 176.0000
pos.y = 32.0000
size.x = 32.0000
--- /dev/null
+
+use package core
+
+CollisionMaskComponent :: struct {
+ use component: Component;
+
+ grid_size: i32 = 16;
+ width: i32 = 50;
+ height: i32 = 40;
+
+ #tag Entity_Store.Skip, Editor_Hidden
+ mask: [] bool;
+
+ #tag Entity_Store.Skip
+ should_render := false;
+
+ added :: (use this: ^CollisionMaskComponent, entity: ^Entity) {
+ scene->modify_component(entity, RenderComponent) {
+ comp.func = render;
+ }
+ }
+
+ update :: (use this: ^CollisionMaskComponent, entity: ^Entity, dt: f32) {
+ if mask.data == null {
+ memory.alloc_slice(^mask, width * height);
+ }
+
+ for y: height {
+ for x: width {
+ mask[y * width + x] = false;
+ for scene.entities {
+ if it.flags & .Solid {
+ area := Rect.{ ~~(x * grid_size), ~~(y * grid_size), ~~grid_size, ~~grid_size };
+ if Rect.intersects(Entity.get_rect(it), area) {
+ mask[y * width + x] = true;
+ continue continue;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ get_mask :: (use this: ^CollisionMaskComponent, pos: Vector2) -> bool {
+ x := ~~pos.x / grid_size;
+ y := ~~pos.y / grid_size;
+ if x < 0 || x >= width || y < 0 || y >= height do return false;
+ return mask[y * width + x];
+ }
+
+ get_path :: (use this: ^CollisionMaskComponent, start: Vector2, target: Vector2, buf: ^[..] Vector2) -> bool {
+ Node :: struct {
+ pos: Vector2i;
+ g: f32 = 0;
+ h: f32 = 0;
+ }
+
+ Visited_Node :: struct {
+ prev: Vector2i;
+ cost: f32;
+ }
+
+ start_pos := Vector2i.{ ~~start.x / grid_size, ~~start.y / grid_size };
+ target_pos := Vector2i.{ ~~target.x / grid_size, ~~target.y / grid_size };
+
+ stack: [..] Node;
+ stack << .{ start_pos };
+
+ visited: Map(Vector2i, Visited_Node);
+ found := false;
+
+ while stack.count != 0 {
+ top := array.pop(^stack);
+
+ if top.pos == target_pos {
+ found = true;
+ break;
+ }
+
+ for dx: -1 .. 2 {
+ for dy: -1 .. 2 {
+ if dx == 0 && dy == 0 do continue;
+
+ tx := top.pos.x + dx;
+ ty := top.pos.y + dy;
+ if tx < 0 || tx >= width || ty < 0 || ty >= height do continue;
+
+ if dx != 0 && dy != 0 {
+ if mask[top.pos.y * width + tx] || mask[ty * width + top.pos.x] do continue;
+ }
+
+ if !mask[ty * width + tx] || target_pos == .{tx, ty} {
+ if array.contains(stack, #(it.pos == .{tx, ty})) do continue;
+
+ g := cast(f32) (math.abs(dx) + math.abs(dy)) + top.g;
+ h := cast(f32) (math.abs(tx - start_pos.x) + math.abs(ty - start_pos.y));
+ f := g + h;
+
+ if visited->has(.{ tx, ty }) {
+ vn := ^visited[.{ tx, ty }];
+ if vn.cost > f {
+ *vn = .{ top.pos, f };
+ }
+
+ } else {
+ stack << .{ .{tx, ty}, g, h, };
+ visited[.{ tx, ty }] = .{ top.pos, f };
+ }
+ }
+ }
+ }
+
+ array.quicksort(stack, (n1: ^Node, n2: typeof n1) => {
+ f1 := n1.g + n1.h;
+ f2 := n2.g + n2.h;
+ if f1 < f2 do return 1;
+ if f2 < f1 do return -1;
+ return 0;
+ });
+ }
+
+ if found {
+ path_pos := target_pos;
+ while visited->has(path_pos) {
+ *buf << .{
+ ~~(path_pos.x * grid_size),
+ ~~(path_pos.y * grid_size),
+ };
+ path_pos = visited[path_pos].prev;
+ }
+
+ array.reverse(*buf);
+ }
+
+ return found;
+ }
+
+ render :: (entity: ^Entity) {
+ cmc := entity->get(CollisionMaskComponent);
+
+ if !cmc.should_render do return;
+
+ gs := cmc.grid_size;
+ immediate_set_color(.{0.3, 0.3, 0.9, 0.4});
+ for y: cmc.height {
+ for x: cmc.width {
+ if cmc.mask[y * cmc.width + x] {
+ immediate_rectangle(~~(x * gs), ~~(y * gs), ~~gs, ~~gs);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
annoy_timeout: f32;
walk_speed: f32 = 100.0f;
+ #tag Editor_Hidden, Entity_Store.Skip
+ path: [..] Vector2;
+ #tag Editor_Hidden, Entity_Store.Skip
+ path_pos: i32;
+
init :: (use this: ^PatronComponent) {
order_item = random.choice(item_store.items.entries).key;
}
}
}
- if target != Entity_Nothing {
- target_entity := scene->get(target);
- target_location := target_entity.pos;
- if Vector2.square_mag(target_location - entity.pos) < 16 {
- entity.pos = target_location;
+ {
+ if target != Entity_Nothing {
+ target_entity := scene->get(target);
+ target_location := target_entity.pos;
+ if Vector2.square_mag(target_location - entity.pos) < 4 {
+ break;
+ }
+
+ if path.count == 0 {
+ array.clear(^path);
+ collision_mask := scene->first_component(CollisionMaskComponent);
+ if !collision_mask->get_path(entity.pos, target_location, ^path) {
+ debug_log(.Warning, "No path for patron.");
+ return;
+ }
+
+ path_pos = 0;
+ }
+
+ target_position := path[path_pos];
+ if Vector2.square_mag(target_position - entity.pos) < 16 {
+ path_pos += 1;
+ }
- if state == .Walking_To_Seat do state = .Waiting_To_Place_Order;
- if state == .Leaving do entity.flags |= .Dead;
- } else {
- delta := Vector2.norm(target_location - entity.pos) * walk_speed;
+ delta := Vector2.norm(target_position - entity.pos) * walk_speed;
entity.pos += delta * dt;
+
+ if path_pos >= path.count {
+ array.clear(^path);
+
+ target_entity := scene->get(target);
+ target_location := target_entity.pos;
+ entity.pos = target_location;
+
+ if state == .Walking_To_Seat do state = .Waiting_To_Place_Order;
+ if state == .Leaving do entity.flags |= .Dead;
+ }
}
}
item_data := item_store->get_item(order_item);
item_data.sprite->render(r);
}
+
+ //
+ // Draw paths for debugging
+ #if false {
+ if path.count > 0 {
+ immediate_set_color(.{1, 1, 0, 0.4});
+ for p: path {
+ immediate_rectangle(p.x, p.y, 8, 8);
+ }
+ }
+ }
}
}
conv.format(^save_path, "./scenes/level1.scene");
custom_editors[Color] = render_color_picker;
+ custom_editors[bool] = render_bool_toggle;
}
editor_shown :: () => editor_openness != 0.0f || editor_target_openness != 0.0f;
immediate_rectangle(x, y+120, w, h - 120);
}
+#local render_bool_toggle :: (v: any, x, y, w, h: f32, field_name: str) {
+ immediate_set_color(.{.3, .3, .3});
+ immediate_rectangle(x, y, w, h);
+
+ draw_checkbox(.{x, y + 64, w, 48}, cast(^bool) v.data, field_name);
+}
+
#local {
background_color :: Color.{0.15, 0.15, 0.15, 1};
//
// 'init' is called when the component is first created, before it
// has been added to any entity.
- init : (rawptr) -> void = null_proc;
+ init : (^Component) -> void = null_proc;
//
// 'added' is called when the component is added to an entity.
// In theory, it could be called multiple times, if the component
// is shared between multiple entities.
- added : (rawptr, ^Entity) -> void = null_proc;
+ added : (^Component, ^Entity) -> void = null_proc;
//
// 'update' is called every update cycle. Currently, there is no
// specified as to the order in which components get updated.
// I think by circumstance, it is the order that you add them
// to the entity, but could break in the future.
- update : (rawptr, ^Entity, f32) -> void = null_proc;
+ update : (^Component, ^Entity, f32) -> void = null_proc;
//
// 'post_render' is called after the entity has been rendered
// normally. Entities must have a 'RenderComponent' in order
// to have 'post_render' called on their components. This
// can be used to add overlays or popups to an entity.
- post_render: (rawptr, ^Entity) -> void = null_proc;
+ post_render: (^Component, ^Entity) -> void = null_proc;
}
Component :: struct {
+#local hash :: package core.hash
+
+Vector2i :: struct {
+ x, y: i32;
+
+ #struct_tag conv.Custom_Format.{format_vector2i}
+ #struct_tag conv.Custom_Parse.{parse_vector2i}
+}
+
Vector2 :: struct {
x, y: f32;
#struct_tag conv.Custom_Format.{format_vector3}
}
+#operator + macro (v1, v2: Vector2i) => (typeof v1).{ v1.x + v2.x, v1.y + v2.y };
+#operator - macro (v1, v2: Vector2i) => (typeof v1).{ v1.x - v2.x, v1.y - v2.y };
+#operator * macro (v: Vector2i, s: i32) => (typeof v ).{ v.x * s, v.y * s };
+#operator * macro (v1, v2: Vector2i) => (typeof v1).{ v1.x * v2.x, v1.y * v2.y };
+#operator == macro (v1, v2: Vector2i) => v1.x == v2.x && v1.y == v2.y;
+#match hash.to_u32 macro (v: Vector2i) => 13 * v.x + 17 * v.y;
+
#operator + macro (v1, v2: Vector2) => (typeof v1).{ v1.x + v2.x, v1.y + v2.y };
#operator - macro (v1, v2: Vector2) => (typeof v1).{ v1.x - v2.x, v1.y - v2.y };
#operator * macro (v: Vector2, s: f32) => (typeof v ).{ v.x * s, v.y * s };
#local {
conv :: package core.conv
+ format_vector2i :: (output: ^conv.Format_Output, format: ^conv.Format, v: ^Vector2i) {
+ conv.format(output, "({}, {})", v.x, v.y);
+ }
+
format_vector2 :: (output: ^conv.Format_Output, format: ^conv.Format, v: ^Vector2) {
conv.format(output, "({}, {})", v.x, v.y);
}
conv.format(output, "({}, {}, {})", v.x, v.y, v.z);
}
+ parse_vector2i :: (output: ^Vector2i, line_: str, string_allocator: Allocator) -> bool {
+ string :: package core.string
+
+ line := line_;
+ xs := string.read_until(^line, #char " ");
+ string.advance(^line, 1);
+ ys := string.read_until(^line, #char " ");
+
+ if xs == "" || ys == "" do return false;
+
+ output.x = ~~ conv.str_to_i64(xs);
+ output.y = ~~ conv.str_to_i64(ys);
+ return true;
+ }
+
parse_vector2 :: (output: ^Vector2, line_: str, string_allocator: Allocator) -> bool {
string :: package core.string
x, y, w, h: f32;
intersects :: (r1, r2: Rect) -> bool {
- return r1.x <= r2.x + r2.w
- && r1.x + r1.w >= r2.x
- && r1.y <= r2.y + r2.h
- && r1.y + r1.h >= r2.y;
+ return r1.x < r2.x + r2.w
+ && r1.x + r1.w > r2.x
+ && r1.y < r2.y + r2.h
+ && r1.y + r1.h > r2.y;
}
contains :: (r: Rect, p: Vector2) -> bool {