implement burn multiplier

This commit is contained in:
Rokas Puzonas 2026-07-17 02:29:51 +03:00
parent 300c8c47e2
commit b4173ec95b
2 changed files with 223 additions and 108 deletions

View File

@ -163,7 +163,8 @@ const Entity = struct {
minion_state: MinionState = .wander,
minion_chop_cooldown: Nanoseconds = 0,
minion_throw_cooldown: Nanoseconds = 0,
minion_carried_wood: ?EntityId = null,
minion_carried_wood_head: ?EntityId = null,
minion_carried_wood_len: u32 = 0,
tree_health: u32 = 0,
tree_max_health: u32 = 0,
@ -175,6 +176,7 @@ const Entity = struct {
wood_burning: bool = false,
wood_health: f32 = 0,
wood_max_health: f32 = 0,
wood_next_carried: ?EntityId = null,
pos: Vec2 = .init(0, 0),
vel: Vec2 = .init(0, 0),
@ -214,9 +216,12 @@ const Entity = struct {
};
const Upgrades = struct {
minion_count: u32 = 0,
minion_chop_damage: u32 = 2,
minion_chop_rate: f32 = 1,
minion_throw_cooldown: Nanoseconds = std.time.ns_per_s,
minion_throw_strength: u32 = 1,
minion_carry_capacity: u32 = 1,
minion_walk: MinionWalkStats = .{},
tree_health: u32 = 10,
@ -227,12 +232,21 @@ const Upgrades = struct {
burn_speed: f32 = 10,
burn_wood_health: f32 = 20,
burn_heat_per_wood: u32 = 1,
burn_levels: [2]BurnLevel = .{
.{ .threshold = 10, .multiplier = 2 },
.{ .threshold = 20, .multiplier = 3 },
},
const MinionWalkStats = struct {
friction: f32 = 0.998,
max_velocity: f32 = 50,
max_speed: f32 = 300,
};
const BurnLevel = struct {
threshold: u32,
multiplier: u32,
};
};
fn initImageDataFromSTB(img: STBImage) Gfx.ImageData {
@ -295,9 +309,6 @@ pub fn init(self: *App, plt: Platform.Init) !?u8 {
.upgrades = .{}
};
self.spawnMinion(.init(0, 0));
// try self.spawnMinion(.init(32, 0));
self.spawnWoodStation(-100);
self.spawnWoodStation(-150);
self.spawnBurnStation(100);
@ -556,6 +567,35 @@ fn countEternalFlameStackSize(self: *App) u32 {
return result;
}
fn minionPushWood(self: *App, minion_id: EntityId, wood_id: EntityId) void {
const minion = self.entities.get(minion_id) orelse return;
const wood = self.entities.get(wood_id) orelse return;
wood.wood_carried_by = minion_id;
wood.wood_next_carried = minion.minion_carried_wood_head;
wood.flags.has_kinematics = false;
minion.minion_carried_wood_head = wood_id;
minion.minion_carried_wood_len += 1;
}
fn minionPopWood(self: *App, minion_id: EntityId) ?EntityId {
const minion = self.entities.get(minion_id) orelse return null;
if (minion.minion_carried_wood_head) |wood_id| {
minion.minion_carried_wood_len -= 1;
if (self.entities.get(wood_id)) |wood| {
minion.minion_carried_wood_head = wood.wood_next_carried;
wood.wood_carried_by = null;
wood.wood_next_carried = null;
return wood_id;
} else {
minion.minion_carried_wood_head = null;
}
}
return null;
}
fn minionLogic(self: *App, plt: Platform.Frame, minion_id: EntityId) !void {
const minion = self.entities.getAssumeExists(minion_id);
assert(minion.type == .minion);
@ -563,7 +603,6 @@ fn minionLogic(self: *App, plt: Platform.Frame, minion_id: EntityId) !void {
const goal_distance = 10;
var walk_goal: ?Vec2 = null;
self.ensureEntityExists(&minion.minion_carried_wood);
minion.minion_chop_cooldown = @max(minion.minion_chop_cooldown - plt.dt, 0);
switch (minion.minion_state) {
@ -610,7 +649,7 @@ fn minionLogic(self: *App, plt: Platform.Frame, minion_id: EntityId) !void {
if (minion.pos.distance(tree.pos) < goal_distance) {
self.unsetEntityTarget(minion_id);
self.minionPickupWood(minion_id, wood_id);
self.minionPushWood(minion_id, wood_id);
minion.minion_state = .wander;
} else {
walk_goal = tree.pos;
@ -620,7 +659,7 @@ fn minionLogic(self: *App, plt: Platform.Frame, minion_id: EntityId) !void {
}
},
.goto_burn_station => {
if (minion.minion_carried_wood == null) {
if (minion.minion_carried_wood_len == 0) {
self.unsetEntityTarget(minion_id);
minion.minion_state = .wander;
}
@ -637,19 +676,24 @@ fn minionLogic(self: *App, plt: Platform.Frame, minion_id: EntityId) !void {
}
},
.burn_wood => {
if (minion.minion_carried_wood) |wood_id| {
minion.minion_throw_cooldown = @max(minion.minion_throw_cooldown - plt.dt, 0);
if (self.countEternalFlameStackSize() < self.upgrades.burn_max_stack_size and minion.minion_throw_cooldown == 0) {
var threw_anything = false;
for (0..self.upgrades.minion_throw_strength) |_| {
if (self.minionPopWood(minion_id)) |wood_id| {
const wood = self.entities.getAssumeExists(wood_id);
const eternal_flame = self.entities.getAssumeExists(self.eternal_flame_id);
if (self.countEternalFlameStackSize() < self.upgrades.burn_max_stack_size) {
wood.pos = eternal_flame.pos;
wood.wood_burning = true;
wood.wood_carried_by = null;
minion.minion_carried_wood = null;
minion.minion_throw_cooldown = self.upgrades.minion_throw_cooldown;
threw_anything = true;
}
}
if (threw_anything) {
minion.minion_throw_cooldown = self.upgrades.minion_throw_cooldown;
} else {
minion.minion_throw_cooldown = @max(minion.minion_throw_cooldown - plt.dt, 0);
if (minion.minion_throw_cooldown == 0) {
minion.minion_state = .wander;
}
}
@ -667,23 +711,37 @@ fn minionLogic(self: *App, plt: Platform.Frame, minion_id: EntityId) !void {
}
}
fn minionPickupWood(self: *App, minion_id: EntityId, wood_id: EntityId) void {
const minion = self.entities.get(minion_id) orelse return;
assert(minion.type == .minion);
const wood = self.entities.get(wood_id) orelse return;
assert(wood.type == .wood);
wood.wood_carried_by = minion_id;
minion.minion_carried_wood = wood_id;
}
fn ensureEntityExists(self: *App, maybe_entity_id: *?EntityId) void {
if (maybe_entity_id.* != null and !self.entities.exists(maybe_entity_id.*.?)) {
maybe_entity_id.* = null;
}
}
fn removeMinion(self: *App, minion_id: EntityId) void {
const minion = self.entities.get(minion_id) orelse return;
assert(minion.type == .minion);
while (self.minionPopWood(minion_id)) |wood_id| {
const wood = self.entities.getAssumeExists(wood_id);
wood.flags.has_kinematics = true;
}
self.entities.removeAssumeExists(minion_id);
}
fn getBurnMultiplier(self: *App) u32 {
const stack_size = self.countEternalFlameStackSize();
for (0..self.upgrades.burn_levels.len) |i| {
const level = self.upgrades.burn_levels[self.upgrades.burn_levels.len - i - 1];
if (stack_size >= level.threshold) {
return level.multiplier;
}
}
return 1;
}
pub fn frame(self: *App, plt: Platform.Frame) !void {
const input = plt.input;
_ = input; // autofix
@ -707,6 +765,27 @@ pub fn frame(self: *App, plt: Platform.Frame) !void {
Gfx.setClearColor(.rgb(40, 40, 40));
Gfx.drawLine(.init(-800, 0), .init(800, 0), .white, 5);
{ // Spawn in or delete minions based on self.upgrades.minion_count
var minion_count: u32 = 0;
{
var minion_iter = self.initEntityIteratorByType(.minion);
while (minion_iter.next()) |_| {
minion_count += 1;
}
}
if (minion_count > self.upgrades.minion_count) {
var minion_iter = self.initEntityIteratorByType(.minion);
for (0..(minion_count-self.upgrades.minion_count)) |_| {
self.removeMinion(minion_iter.next().?);
}
} else if (minion_count < self.upgrades.minion_count) {
for (0..(self.upgrades.minion_count-minion_count)) |_| {
self.spawnMinion(.init(32, 0));
}
}
}
{ // Spawn trees at wood station on a cooldown
var wood_station_iter = self.initEntityIteratorByType(.wood_station);
while (wood_station_iter.next()) |wood_station_id| {
@ -762,7 +841,7 @@ pub fn frame(self: *App, plt: Platform.Frame) !void {
wood.wood_health = @max(wood.wood_health - self.upgrades.burn_speed * dt, 0);
if (wood.wood_health == 0) {
self.heat += self.upgrades.burn_heat_per_wood;
self.heat += self.upgrades.burn_heat_per_wood * self.getBurnMultiplier();
self.entities.removeAssumeExists(wood_id);
}
@ -770,11 +849,11 @@ pub fn frame(self: *App, plt: Platform.Frame) !void {
}
// Each wood entity will assign itself to the nearest minion that isn't busy
if (wood.wood_carried_by == null and (wood.targeted_by_entity == null or wood.wood_carried_by == null)) {
if (wood.wood_carried_by == null and wood.targeted_by_entity == null) {
var minion_iter = self.initNearestEntityIter(.minion, wood.pos, plt.arena);
while (try minion_iter.next()) |minion_id| {
const minion = self.entities.getAssumeExists(minion_id);
if (minion.minion_carried_wood != null) {
if (minion.minion_carried_wood_len == self.upgrades.minion_carry_capacity) {
continue;
}
@ -786,6 +865,10 @@ pub fn frame(self: *App, plt: Platform.Frame) !void {
}
}
}
if (wood.flags.has_kinematics) {
wood.vel = .init(0, 100);
}
}
}
@ -793,26 +876,37 @@ pub fn frame(self: *App, plt: Platform.Frame) !void {
var tree_iter = self.initEntityIteratorByType(.tree);
while (tree_iter.next()) |tree_id| {
const tree = self.entities.getAssumeExists(tree_id);
if (tree.targeted_by_entity != null) {
continue;
}
var minion_iter = self.initNearestEntityIter(.minion, tree.pos, plt.arena);
while (try minion_iter.next()) |minion_id| {
const minion = self.entities.getAssumeExists(minion_id);
if (minion.minion_carried_wood != null) {
if (minion.minion_carried_wood_len == self.upgrades.minion_carry_capacity) {
continue;
}
const state = minion.minion_state;
if (state == .wander or state == .goto_tree_station) {
if (!(state == .wander or state == .goto_tree_station)) {
continue;
}
if (tree.targeted_by_entity) |targeted_by_minion_id| {
const targeted_by_minion = self.entities.getAssumeExists(targeted_by_minion_id);
if (minion.pos.distanceSqr(tree.pos) > targeted_by_minion.pos.distanceSqr(tree.pos)) {
continue;
}
// Steal tree if this minion is closer to it.
// This is useful for sitation when a minion far away minion as reserved a tree.
self.unsetEntityTarget(targeted_by_minion_id);
tree.targeted_by_entity = null;
targeted_by_minion.minion_state = .wander;
}
self.setEntityTarget(minion_id, tree_id);
minion.minion_state = .chop_tree;
break;
}
}
}
}
{ // Each wood station will assign itself to the nearest minion that isn't busy
var wood_station_iter = self.initEntityIteratorByType(.wood_station);
@ -821,11 +915,14 @@ pub fn frame(self: *App, plt: Platform.Frame) !void {
if (wood_station.targeted_by_entity != null) {
continue;
}
if (wood_station.station_owned_tree != null) {
continue;
}
var minion_iter = self.initNearestEntityIter(.minion, wood_station.pos, plt.arena);
while (try minion_iter.next()) |minion_id| {
const minion = self.entities.getAssumeExists(minion_id);
if (minion.minion_carried_wood != null) {
if (minion.minion_carried_wood_len == self.upgrades.minion_carry_capacity) {
continue;
}
@ -850,12 +947,8 @@ pub fn frame(self: *App, plt: Platform.Frame) !void {
var minion_iter = self.initNearestEntityIter(.minion, burn_station.pos, plt.arena);
while (try minion_iter.next()) |minion_id| {
const minion = self.entities.getAssumeExists(minion_id);
if (minion.minion_carried_wood == null) {
continue;
}
const state = minion.minion_state;
if (state == .wander) {
if (state == .wander and minion.minion_carried_wood_len == self.upgrades.minion_carry_capacity) {
self.setEntityTarget(minion_id, burn_station_id);
minion.minion_state = .goto_burn_station;
break;
@ -922,6 +1015,8 @@ pub fn frame(self: *App, plt: Platform.Frame) !void {
e.vel = e.vel.add(e.acc.multiplyScalar(dt));
e.pos = e.pos.add(e.vel.multiplyScalar(dt));
e.pos.y = @min(e.pos.y, 0);
e.acc = .init(0, 0);
}
@ -1008,6 +1103,11 @@ pub fn frame(self: *App, plt: Platform.Frame) !void {
.pos = eternal_flame.pos.sub(.init(10, 80)),
.height = 24
});
Gfx.drawText(self.font, .{
.text = try std.fmt.allocPrint(plt.arena, "Mult: {}", .{self.getBurnMultiplier()}),
.pos = eternal_flame.pos.sub(.init(10, 110)),
.height = 24
});
}
}
@ -1029,10 +1129,6 @@ pub fn frame(self: *App, plt: Platform.Frame) !void {
if (ImGUI.beginWindow(.{ .name = "Debug", .alpha = 0.8, .size = .init(500, 200) })) {
defer ImGUI.endWindow();
if (ImGUI.button("spawn minion")) {
self.spawnMinion(.init(32, 0));
}
var heat: u32 = @intCast(self.heat);
if (ImGUI.inputU32("heat", &heat)) {
self.heat = @intCast(heat);
@ -1040,12 +1136,18 @@ pub fn frame(self: *App, plt: Platform.Frame) !void {
ImGUI.separator();
_ = ImGUI.text("Minion upgrades", .{});
_ = ImGUI.inputU32("count", &self.upgrades.minion_count);
_ = ImGUI.inputU32("carry_capacity", &self.upgrades.minion_carry_capacity);
_ = ImGUI.inputU32("throw_strength", &self.upgrades.minion_throw_strength);
_ = ImGUI.inputDuration("throw_cooldown", &self.upgrades.minion_throw_cooldown);
_ = ImGUI.inputU32("chop_damage", &self.upgrades.minion_chop_damage);
_ = ImGUI.inputF32Drag(.{
.label = "chop_rate",
.value = &self.upgrades.minion_chop_rate
});
_ = ImGUI.inputDuration("throw_cooldown", &self.upgrades.minion_throw_cooldown);
_ = ImGUI.inputF32Drag(.{
.label = "walk_max_speed",
.value = &self.upgrades.minion_walk.max_speed
@ -1078,6 +1180,18 @@ pub fn frame(self: *App, plt: Platform.Frame) !void {
});
_ = ImGUI.inputU32("heat_per_wood", &self.upgrades.burn_heat_per_wood);
_ = ImGUI.inputU32("max_stack_size", &self.upgrades.burn_max_stack_size);
for (1.., &self.upgrades.burn_levels) |i, *level| {
ImGUI.pushID(.{ .int = @intCast(i) });
defer ImGUI.popID();
_ = ImGUI.text("Level {}", .{i});
ImGUI.sameLine();
ImGUI.setNextItemWidth(150);
_ = ImGUI.inputU32("threshold", &level.threshold);
ImGUI.sameLine();
ImGUI.setNextItemWidth(100);
_ = ImGUI.inputU32("multiplier", &level.multiplier);
}
}
}

View File

@ -155,12 +155,13 @@ pub const WindowOptions = struct {
alpha: f32 = 1
};
pub fn beginWindow(opts: WindowOptions) bool {
if (!build_options.has_imgui) {
return false;
}
inline fn isDisabled() bool {
const self = &g_state;
if (!self.enabled) {
return !(build_options.has_imgui and self.enabled);
}
pub fn beginWindow(opts: WindowOptions) bool {
if (isDisabled()) {
return false;
}
@ -200,11 +201,7 @@ pub fn beginWindow(opts: WindowOptions) bool {
}
pub fn endWindow() void {
if (!build_options.has_imgui) {
return;
}
const self = &g_state;
if (!self.enabled) {
if (isDisabled()) {
return;
}
@ -220,11 +217,7 @@ fn formatString(comptime fmt: []const u8, args: anytype) [:0]const u8 {
}
pub fn text(comptime fmt: []const u8, args: anytype) void {
if (!build_options.has_imgui) {
return;
}
const self = &g_state;
if (!self.enabled) {
if (isDisabled()) {
return;
}
@ -232,11 +225,7 @@ pub fn text(comptime fmt: []const u8, args: anytype) void {
}
pub fn button(label: []const u8) bool {
if (!build_options.has_imgui) {
return false;
}
const self = &g_state;
if (!self.enabled) {
if (isDisabled()) {
return false;
}
@ -251,11 +240,7 @@ pub const SliderOptions = struct {
};
pub fn slider(opts: SliderOptions) bool {
if (!build_options.has_imgui) {
return false;
}
const self = &g_state;
if (!self.enabled) {
if (isDisabled()) {
return false;
}
@ -278,11 +263,7 @@ const InputF32Options = struct {
};
pub fn inputF32(opts: InputF32Options) bool {
if (!build_options.has_imgui) {
return false;
}
const self = &g_state;
if (!self.enabled) {
if (isDisabled()) {
return false;
}
@ -314,11 +295,7 @@ const InputF32DragOptions = struct {
};
pub fn inputF32Drag(opts: InputF32DragOptions) bool {
if (!build_options.has_imgui) {
return false;
}
const self = &g_state;
if (!self.enabled) {
if (isDisabled()) {
return false;
}
@ -360,11 +337,7 @@ pub fn inputDuration(label: []const u8, duration_ns: *Nanoseconds) bool {
}
pub fn inputI32(label: []const u8, value: *i32) bool {
if (!build_options.has_imgui) {
return false;
}
const self = &g_state;
if (!self.enabled) {
if (isDisabled()) {
return false;
}
@ -387,11 +360,7 @@ pub fn inputU32(label: []const u8, value: *u32) bool {
}
pub fn checkbox(label: []const u8, value: *bool) bool {
if (!build_options.has_imgui) {
return false;
}
const self = &g_state;
if (!self.enabled) {
if (isDisabled()) {
return false;
}
@ -402,11 +371,7 @@ pub fn checkbox(label: []const u8, value: *bool) bool {
}
pub fn separator() void {
if (!build_options.has_imgui) {
return;
}
const self = &g_state;
if (!self.enabled) {
if (isDisabled()) {
return;
}
@ -420,11 +385,7 @@ const ColorEditOptions = struct {
};
pub fn colorEdit(opts: ColorEditOptions) bool {
if (!build_options.has_imgui) {
return false;
}
const self = &g_state;
if (!self.enabled) {
if (isDisabled()) {
return false;
}
@ -459,3 +420,43 @@ fn toImVec2(vec2: Vec2) ig.ImVec2 {
.y = vec2.y,
};
}
pub fn sameLine() void {
if (isDisabled()) {
return;
}
ig.igSameLine();
}
pub const ID = union(enum) {
string: []const u8,
int: i32
};
pub fn pushID(id: ID) void {
if (isDisabled()) {
return;
}
switch (id) {
.string => |str| ig.igPushIDStr(str.ptr, str.ptr + str.len),
.int => |int| ig.igPushIDInt(int)
}
}
pub fn popID() void {
if (isDisabled()) {
return;
}
ig.igPopID();
}
pub fn setNextItemWidth(width: f32) void {
if (isDisabled()) {
return;
}
ig.igSetNextItemWidth(width);
}