diff --git a/src/app.zig b/src/app.zig index 5ea43e1..5a456af 100644 --- a/src/app.zig +++ b/src/app.zig @@ -160,6 +160,7 @@ const Entity = struct { minion_chop_cooldown: Nanoseconds = 0, minion_chop_damage: u32 = 2, minion_burn_speed: f32 = 1, + minion_carried_wood: ?EntityId = null, tree_health: u32 = 0, tree_max_health: u32 = 0, @@ -168,6 +169,8 @@ const Entity = struct { station_spawn_tree_cooldown: Nanoseconds = 0, station_owned_tree: ?EntityId = null, + wood_carried_by: ?EntityId = null, + pos: Vec2 = .init(0, 0), vel: Vec2 = .init(0, 0), acc: Vec2 = .init(0, 0), @@ -264,19 +267,19 @@ pub fn init(self: *App, plt: Platform.Init) !?u8 { .font = font }; - try self.spawnMinion(.init(0, 0)); + self.spawnMinion(.init(0, 0)); // try self.spawnMinion(.init(32, 0)); - try self.spawnStation(-100, .chop); - try self.spawnStation(-150, .chop); - try self.spawnStation(100, .burn); - try self.spawnStation(150, .burn); + self.spawnStation(-100, .chop); + self.spawnStation(-150, .chop); + self.spawnStation(100, .burn); + self.spawnStation(150, .burn); return null; } -pub fn spawnMinion(self: *App, pos: Vec2) !void { - _ = try self.entities.insert(Entity{ +pub fn spawnMinion(self: *App, pos: Vec2) void { + _ = self.entities.insert(Entity{ .type = .minion, .flags = .{ .has_animation = true, @@ -289,19 +292,25 @@ pub fn spawnMinion(self: *App, pos: Vec2) !void { .max_walk_speed = 300, .animation = .idle, - }); + }) catch { + log.warn("Failed to spawn minion, entity limit reached", .{}); + return; + }; } -pub fn spawnStation(self: *App, pos: f32, subtype: Entity.StationSubtype) !void { - _ = try self.entities.insert(Entity{ +pub fn spawnStation(self: *App, pos: f32, subtype: Entity.StationSubtype) void { + _ = self.entities.insert(Entity{ .type = .station, .station_subtype = subtype, .pos = .init(pos, 0), - }); + }) catch { + log.warn("Failed to spawn station, entity limit reached", .{}); + return; + }; } -fn spawnTree(self: *App, station_id: EntityId) !void { +fn spawnTree(self: *App, station_id: EntityId) void { const station = self.entities.get(station_id) orelse return; assert(station.type == .station); if (station.station_owned_tree != null) { @@ -309,16 +318,38 @@ fn spawnTree(self: *App, station_id: EntityId) !void { } const health = 10; - const id = try self.entities.insert(Entity{ + const id = self.entities.insert(Entity{ .type = .tree, .tree_health = health, .tree_max_health = health, .pos = station.pos - }); + }) catch { + log.warn("Failed to spawn tree, entity limit reached", .{}); + return; + }; station.station_owned_tree = id; } +fn destroyTree(self: *App, tree_id: EntityId) void { + const tree = self.entities.get(tree_id) orelse return; + assert(tree.type == .tree); + + self.spawnWood(tree.pos); + + self.entities.removeAssumeExists(tree_id); +} + +fn spawnWood(self: *App, pos: Vec2) void { + _ = self.entities.insert(Entity{ + .type = .wood, + .pos = pos + }) catch { + log.warn("Failed to spawn wood, entity limit reached", .{}); + return; + }; +} + fn getNearestFreeStation(self: *App, from: Vec2, subtype: Entity.StationSubtype) ?EntityId { var result: ?EntityId = null; var result_distance: f32 = 0; @@ -347,14 +378,14 @@ fn getNearestFreeStation(self: *App, from: Vec2, subtype: Entity.StationSubtype) return result; } -fn getNearestFreeTree(self: *App, from: Vec2) ?EntityId { +fn getNearestFreeEntity(self: *App, from: Vec2, entity_type: Entity.Type) ?EntityId { var result: ?EntityId = null; var result_distance: f32 = 0; var station_iter = self.entities.iterator(); while (station_iter.next()) |id| { const station = self.entities.getAssumeExists(id); - if (station.type != .tree) { + if (station.type != entity_type) { continue; } if (station.targeted_by_entity != null) { @@ -400,6 +431,141 @@ fn drawProgressBar(progress: f32, center: Vec2, size: Vec2, color: Color) void { Gfx.drawLine(from, from.add(.init(size.x * progress, 0)), color, size.y); } +fn minionLogic(self: *App, plt: Platform.Frame, minion_id: EntityId) !void { + const minion = self.entities.getAssumeExists(minion_id); + assert(minion.type == .minion); + + const goal_distance_threshold = 10; + var walk_goal: ?Vec2 = null; + var station_goal: ?Entity.StationSubtype = null; + + if (minion.minion_state == .goto_tree_station) { + // if (minion.minion_carried_wood == null) { + // if (self.getNearestFreeEntity(minion.pos, .tree)) |tree_id| { + // _ = tree_id; // autofix + // } + // } + } + + switch (minion.minion_state) { + .goto_tree_station => { + // TODO: Steal tree if it is nearer to you compared to the current minion that has it reserved. + if (self.getNearestFreeEntity(minion.pos, .tree)) |tree_id| { + self.setEntityTarget(minion_id, tree_id); + minion.minion_state = .chop_tree; + } else { + station_goal = .chop; + + if (minion.target_entity) |station_id| { + const station = self.entities.getAssumeExists(station_id); + if (station.type != .station) { + self.unsetEntityTarget(minion_id); + } + } + } + }, + .chop_tree => { + if (minion.target_entity) |tree_id| { + if (self.entities.getAssumeExists(tree_id).type != .tree) { + self.unsetEntityTarget(minion_id); + minion.minion_state = .goto_tree_station; + } + } + + var is_at_tree = false; + if (minion.target_entity) |tree_id| { + const tree = self.entities.getAssumeExists(tree_id); + + is_at_tree = minion.pos.distance(tree.pos) < goal_distance_threshold; + if (is_at_tree) { + minion.minion_chop_cooldown = @max(minion.minion_chop_cooldown - plt.dt, 0); + if (minion.minion_chop_cooldown == 0) { + minion.minion_chop_cooldown = std.time.ns_per_ms * 500; + tree.tree_health -= minion.minion_chop_damage; + } + + if (tree.tree_health <= 0) { + minion.minion_state = .burn; + + self.unsetEntityTarget(minion_id); + self.destroyTree(tree_id); + } + } else { + walk_goal = tree.pos; + } + } + + if (!is_at_tree) { + minion.minion_work_progress = 0; + } + }, + .pickup_wood => { + }, + .burn => { + station_goal = .burn; + + var is_at_station = false; + if (minion.target_entity) |station_id| { + const station = self.entities.getAssumeExists(station_id); + if (station.pos.distance(minion.pos) < goal_distance_threshold) { + is_at_station = true; + } + } + + if (!is_at_station) { + minion.minion_work_progress = 0; + } + + if (is_at_station) { + minion.minion_work_progress += minion.minion_burn_speed * plt.deltaTime(); + } + + minion.minion_work_progress = std.math.clamp(minion.minion_work_progress, 0, 1); + if (minion.minion_work_progress == 1) { + minion.minion_state = .goto_tree_station; + minion.minion_work_progress = 0; + self.heat += 1; + } + }, + } + + if (station_goal) |station_type| { + if (minion.target_entity) |entity_id| { + if (self.entities.get(entity_id)) |station| { + if (station.type != .station) { + } + } + } + + if (minion.target_entity) |station_id| { + const station = self.entities.getAssumeExists(station_id); + if (station.station_subtype != station_type) { + self.unsetEntityTarget(minion_id); + } + } + + if (minion.target_entity == null) { + if (self.getNearestFreeStation(minion.pos, station_type)) |station_id| { + self.setEntityTarget(minion_id, station_id); + } + } + + if (minion.target_entity) |station_id| { + const station = self.entities.getAssumeExists(station_id); + walk_goal = station.pos; + } + } + + if (walk_goal) |pos| { + const diff = minion.pos.x - pos.x; + if (diff < -goal_distance_threshold) { + minion.acc = .init(minion.max_walk_speed, 0); + } else if (diff > goal_distance_threshold) { + minion.acc = .init(-minion.max_walk_speed, 0); + } + } +} + pub fn frame(self: *App, plt: Platform.Frame) !void { const input = plt.input; _ = input; // autofix @@ -440,7 +606,7 @@ pub fn frame(self: *App, plt: Platform.Frame) !void { if (station.station_subtype == .chop) { station.station_spawn_tree_cooldown = @max(station.station_spawn_tree_cooldown - plt.dt, 0); if (station.station_spawn_tree_cooldown == 0 and station.station_owned_tree == null) { - try self.spawnTree(station_id); + self.spawnTree(station_id); } } } @@ -481,127 +647,7 @@ pub fn frame(self: *App, plt: Platform.Frame) !void { continue; } - const goal_distance_threshold = 10; - var walk_goal: ?Vec2 = null; - var station_goal: ?Entity.StationSubtype = null; - - switch (minion.minion_state) { - .goto_tree_station => { - // TODO: Steal tree is it is nearer to you compared to the current minion that has it reserved. - if (self.getNearestFreeTree(minion.pos)) |tree_id| { - self.setEntityTarget(minion_id, tree_id); - minion.minion_state = .chop_tree; - } else { - station_goal = .chop; - - if (minion.target_entity) |station_id| { - const station = self.entities.getAssumeExists(station_id); - if (station.type != .station) { - self.unsetEntityTarget(minion_id); - } - } - } - }, - .chop_tree => { - if (minion.target_entity) |tree_id| { - if (self.entities.getAssumeExists(tree_id).type != .tree) { - self.unsetEntityTarget(minion_id); - minion.minion_state = .goto_tree_station; - } - } - - var is_at_tree = false; - if (minion.target_entity) |tree_id| { - const tree = self.entities.getAssumeExists(tree_id); - - is_at_tree = minion.pos.distance(tree.pos) < goal_distance_threshold; - if (is_at_tree) { - minion.minion_chop_cooldown = @max(minion.minion_chop_cooldown - plt.dt, 0); - if (minion.minion_chop_cooldown == 0) { - minion.minion_chop_cooldown = std.time.ns_per_ms * 500; - tree.tree_health -= minion.minion_chop_damage; - } - - if (tree.tree_health <= 0) { - minion.minion_state = .burn; - - self.unsetEntityTarget(minion_id); - self.entities.removeAssumeExists(tree_id); - } - } else { - walk_goal = tree.pos; - } - } - - if (!is_at_tree) { - minion.minion_work_progress = 0; - } - }, - .pickup_wood => { - }, - .burn => { - station_goal = .burn; - - var is_at_station = false; - if (minion.target_entity) |station_id| { - const station = self.entities.getAssumeExists(station_id); - if (station.pos.distance(minion.pos) < goal_distance_threshold) { - is_at_station = true; - } - } - - if (!is_at_station) { - minion.minion_work_progress = 0; - } - - if (is_at_station) { - minion.minion_work_progress += minion.minion_burn_speed * dt; - } - - minion.minion_work_progress = std.math.clamp(minion.minion_work_progress, 0, 1); - if (minion.minion_work_progress == 1) { - minion.minion_state = .goto_tree_station; - minion.minion_work_progress = 0; - self.heat += 1; - } - }, - } - - if (station_goal) |station_type| { - if (minion.target_entity) |entity_id| { - if (self.entities.get(entity_id)) |station| { - if (station.type != .station) { - } - } - } - - if (minion.target_entity) |station_id| { - const station = self.entities.getAssumeExists(station_id); - if (station.station_subtype != station_type) { - self.unsetEntityTarget(minion_id); - } - } - - if (minion.target_entity == null) { - if (self.getNearestFreeStation(minion.pos, station_type)) |station_id| { - self.setEntityTarget(minion_id, station_id); - } - } - - if (minion.target_entity) |station_id| { - const station = self.entities.getAssumeExists(station_id); - walk_goal = station.pos; - } - } - - if (walk_goal) |pos| { - const diff = minion.pos.x - pos.x; - if (diff < -goal_distance_threshold) { - minion.acc = .init(minion.max_walk_speed, 0); - } else if (diff > goal_distance_threshold) { - minion.acc = .init(-minion.max_walk_speed, 0); - } - } + try self.minionLogic(plt, minion_id); if (minion.acc.x > 0) { minion.animation = .walk; @@ -630,82 +676,96 @@ pub fn frame(self: *App, plt: Platform.Frame) !void { } } - var entity_iter = self.entities.iterator(); - while (entity_iter.next()) |id| { - const e = self.entities.getAssumeExists(id); + { + var entity_iter = self.entities.iterator(); + while (entity_iter.next()) |id| { + const e = self.entities.getAssumeExists(id); - if (e.flags.has_kinematics) { - e.vel = e.vel.add(e.acc.multiplyScalar(dt)); - e.vel = e.vel.limitLength(e.max_velocity); - e.pos = e.pos.add(e.vel.multiplyScalar(dt)); + if (e.flags.has_kinematics) { + e.vel = e.vel.add(e.acc.multiplyScalar(dt)); + e.vel = e.vel.limitLength(e.max_velocity); + e.pos = e.pos.add(e.vel.multiplyScalar(dt)); - const friction_force = std.math.pow(f32, 1 - e.friction, dt); - e.vel = e.vel.multiplyScalar(friction_force); + const friction_force = std.math.pow(f32, 1 - e.friction, dt); + e.vel = e.vel.multiplyScalar(friction_force); - e.acc = .init(0, 0); - } + e.acc = .init(0, 0); + } - if (e.flags.has_animation) { - const animations = switch (e.animation) { - .idle => self.idle_animations, - .walk => self.walk_animations, - .death => self.death_animations - }; - var flip_animation = false; - const animation = switch (e.animation_dir) { - .right => animations.right, - .up => animations.up, - .down => animations.down, - .left => blk: { - flip_animation = true; - break :blk animations.right; + if (e.flags.has_animation) { + const animations = switch (e.animation) { + .idle => self.idle_animations, + .walk => self.walk_animations, + .death => self.death_animations + }; + var flip_animation = false; + const animation = switch (e.animation_dir) { + .right => animations.right, + .up => animations.up, + .down => animations.down, + .left => blk: { + flip_animation = true; + break :blk animations.right; + } + }; + + e.animation_timer += plt.dt; + while (e.animation_timer >= animation.frame_duration) { + e.animation_timer -= animation.frame_duration; + e.animation_index += 1; } - }; - e.animation_timer += plt.dt; - while (e.animation_timer >= animation.frame_duration) { - e.animation_timer -= animation.frame_duration; - e.animation_index += 1; + e.animation_index = e.animation_index % @as(u32, @intCast(animation.frames.len)); + const sprite = animation.frames[e.animation_index]; + + var size = character_size; + var pos = e.pos; + pos.y -= character_size.y*0.7; + if (flip_animation) { + size.x *= -1; + pos.x += character_size.x/2; + } else { + pos.x -= character_size.x/2; + } + + Gfx.drawRectangle( + e.pos.sub(.init(5, 5)), + .init(10, 10), + .white + ); + Gfx.drawSprite(sprite, pos, size, .white); + + const is_working = e.minion_state == .chop_tree or e.minion_state == .burn; + if (e.type == .minion and is_working and e.minion_work_progress > 0) { + drawProgressBar(e.minion_work_progress, e.pos.add(.init(0, 20)), .init(100, 10), .rgb(0, 200, 20)); + } } - e.animation_index = e.animation_index % @as(u32, @intCast(animation.frames.len)); - const sprite = animation.frames[e.animation_index]; - - var size = character_size; - var pos = e.pos; - pos.y -= character_size.y*0.7; - if (flip_animation) { - size.x *= -1; - pos.x += character_size.x/2; - } else { - pos.x -= character_size.x/2; - } - - Gfx.drawRectangle( - e.pos.sub(.init(5, 5)), - .init(10, 10), - .white - ); - Gfx.drawSprite(sprite, pos, size, .white); - - const is_working = e.minion_state == .chop_tree or e.minion_state == .burn; - if (e.type == .minion and is_working and e.minion_work_progress > 0) { - drawProgressBar(e.minion_work_progress, e.pos.add(.init(0, 20)), .init(100, 10), .rgb(0, 200, 20)); + if (e.type == .station) { + if (e.station_subtype == .chop) { + Gfx.drawRectangle(e.pos.sub(.init(5, 5)), .init(10, 10), .rgb(20, 200, 20)); + } else { + Gfx.drawRectangle(e.pos.sub(.init(5, 5)), .init(10, 10), .rgb(200, 20, 20)); + } + } else if (e.type == .minion) { + Gfx.drawText(self.font, .{ + .pos = e.pos.sub(.init(20, 50)), + .height = 16, + .text = try std.fmt.allocPrint(plt.arena, "{}", .{e.minion_state}) + }); } } + } - if (e.type == .station) { - if (e.station_subtype == .chop) { - Gfx.drawRectangle(e.pos.sub(.init(5, 5)), .init(10, 10), .rgb(20, 200, 20)); - } else { - Gfx.drawRectangle(e.pos.sub(.init(5, 5)), .init(10, 10), .rgb(200, 20, 20)); + { + var wood_iter = self.entities.iterator(); + while (wood_iter.next()) |id| { + const wood = self.entities.getAssumeExists(id); + if (wood.type != .wood) { + continue; } - } else if (e.type == .minion) { - Gfx.drawText(self.font, .{ - .pos = e.pos.sub(.init(20, 50)), - .height = 16, - .text = try std.fmt.allocPrint(plt.arena, "{}", .{e.minion_state}) - }); + + Gfx.drawRectangle(wood.pos.sub(.init(10, 10)), .init(20, 20), .rgb(100, 100, 20)); } } @@ -719,7 +779,7 @@ pub fn frame(self: *App, plt: Platform.Frame) !void { defer ImGUI.endWindow(); if (ImGUI.button("spawn")) { - try self.spawnMinion(.init(32, 0)); + self.spawnMinion(.init(32, 0)); } } }