added path finding and collision mask
authorBrendan Hansen <brendan.f.hansen@gmail.com>
Thu, 17 Mar 2022 18:54:21 +0000 (13:54 -0500)
committerBrendan Hansen <brendan.f.hansen@gmail.com>
Thu, 17 Mar 2022 18:54:21 +0000 (13:54 -0500)
docs/todo.txt
run_tree/scenes/level1.scene
src/entity/components/collision_mask.onyx [new file with mode: 0644]
src/entity/components/patron.onyx
src/entity/editor.onyx
src/entity/scene.onyx
src/utils/vecmath.onyx

index 02a65ad7ab771f69e239505672acabf20ce0ba3b..fccef98e25b0ae13111c62f9c6313eb64d53a139 100644 (file)
@@ -11,3 +11,8 @@ customizable behavior, without having to change the code.
 
 * More items to order
 
+* Reputation = likelihood someone walks in
+
+* Waves / Continuous flow
+
+* Purchase bar-upgrades between days
\ No newline at end of file
index 98bb91857c6076c48325b29973dc26db67c452f0..15f8b371615b94b539b5396f55be4706ea8d388f 100644 (file)
@@ -1,3 +1,13 @@
+[Custom]
+id = 342
+flags = 0
+pos = 0 0
+size = 8 8
+:CollisionMaskComponent
+should_render=false
+:RenderComponent
+layer=1000
+
 [Background]
 id = 100
 flags = 0
@@ -1420,7 +1430,7 @@ size.x = 16.0000
 size.y = 32.0000
 :FurnitureComponent
 furniture_type = 0
-taken = true
+taken = false
 :SpriteRenderComponent
 sprite.sheet = "./assets/images/spritesheet.png"
 sprite.pos.x = 48.0000
@@ -1603,7 +1613,7 @@ draw_item = true
 
 [Custom]
 id = 334
-flags = 1
+flags = 3
 pos.x = 176.0000
 pos.y = 32.0000
 size.x = 32.0000
diff --git a/src/entity/components/collision_mask.onyx b/src/entity/components/collision_mask.onyx
new file mode 100644 (file)
index 0000000..2b4c7da
--- /dev/null
@@ -0,0 +1,153 @@
+
+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
index 7ed1ea8c4eb49e5745c37ea6df0756b7d55c77e6..eff116a24b3433fcd05e3a13feca51991c40c4ad 100644 (file)
@@ -21,6 +21,11 @@ PatronComponent :: struct {
     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;
     }
@@ -120,17 +125,43 @@ PatronComponent :: struct {
             }
         }
 
-        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;
+                }
             }
         }
 
@@ -211,5 +242,16 @@ PatronComponent :: struct {
             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);
+                }
+            }
+        }
     }
 }
index da6d3a592270c437a9fddb65878113982999a9b2..ac860f9d8058e345e6ac1480d40a9130db6c9090 100644 (file)
@@ -25,6 +25,7 @@ editor_init :: () {
     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;
@@ -591,6 +592,13 @@ editor_draw :: () {
     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};
 
index b8f87ae6ab799827980d2e584ffd04a0e224b430..55dc5f3a5c45defb6bf795a2413118ef212b971b 100644 (file)
@@ -19,27 +19,27 @@ Entity_ID :: #distinct u32
     //
     // '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 {
index 1ba7aa80c67b9b1a1478c44b5fda73721e5efe30..c3e02e31d80e3d8094b2d6bdbbaab63620cd0463 100644 (file)
@@ -1,3 +1,12 @@
+#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;
 
@@ -57,6 +66,13 @@ Vector3 :: struct {
     #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     };
@@ -78,6 +94,10 @@ Vector3 :: struct {
 #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);
     }
@@ -90,6 +110,21 @@ Vector3 :: struct {
         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
 
@@ -112,10 +147,10 @@ Rect :: struct {
     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 {