2026-07-11 18:45:05 +01:00
|
|
|
const std = @import("std");
|
|
|
|
|
const sdl = @import("sdl");
|
2026-07-13 01:33:26 +01:00
|
|
|
const gtl = @import("gtl");
|
2026-07-11 18:45:05 +01:00
|
|
|
const App = @import("app");
|
|
|
|
|
|
|
|
|
|
const component = @import("../component/root.zig");
|
|
|
|
|
const resource = @import("../resource/root.zig");
|
2026-07-13 19:37:50 +01:00
|
|
|
const event = @import("../event/root.zig");
|
2026-07-11 18:45:05 +01:00
|
|
|
|
|
|
|
|
/// ----------------------------------------------------
|
|
|
|
|
/// ----------------------------------------------------
|
|
|
|
|
pub fn beginFrame(ctx: App.scheduler.Context) !void {
|
|
|
|
|
const res = try ctx.app.resources.getMany(struct {
|
|
|
|
|
window: *resource.Window,
|
|
|
|
|
frame: *resource.Frame,
|
|
|
|
|
time: *resource.Time,
|
2026-07-13 01:33:26 +01:00
|
|
|
|
|
|
|
|
textures: *resource.Textures,
|
|
|
|
|
renderer: *resource.Renderer,
|
2026-07-11 18:45:05 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// TIME
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
res.time.last = res.time.now;
|
|
|
|
|
res.time.now = sdl.SDL_GetPerformanceCounter();
|
|
|
|
|
|
|
|
|
|
res.time.dt =
|
|
|
|
|
@as(f32, @floatFromInt(res.time.now - res.time.last)) /
|
|
|
|
|
@as(f32, @floatFromInt(res.time.frequency));
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// CAMERA
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
var target_transform: ?*const component.Transform = null;
|
|
|
|
|
|
|
|
|
|
{ // GATHER CAMERAS TARGET
|
|
|
|
|
var query = ctx.app.world.query(struct {
|
|
|
|
|
transform: *component.Transform,
|
|
|
|
|
camera_target: ?*component.CameraTarget,
|
|
|
|
|
}, .{});
|
|
|
|
|
while (query.next()) |e| {
|
|
|
|
|
if (e.camera_target) |_| {
|
|
|
|
|
target_transform = e.transform;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
{ // GATHER CAMERAS
|
|
|
|
|
var query = ctx.app.world.query(struct {
|
|
|
|
|
camera: *component.Camera,
|
|
|
|
|
transform: *component.Transform,
|
|
|
|
|
}, .{});
|
|
|
|
|
while (query.next()) |e| {
|
|
|
|
|
const window_width: f32 = @floatFromInt(res.window.width);
|
|
|
|
|
const window_height: f32 = @floatFromInt(res.window.height);
|
|
|
|
|
const target = if (target_transform) |v| v else continue;
|
|
|
|
|
|
|
|
|
|
e.transform.pos[0] += ((window_width / 2.0) - (target.pos[0] + target.size[0] / 2.0) - e.transform.pos[0]) / 20.0;
|
|
|
|
|
e.transform.pos[1] += ((window_height / 2.0) - (target.pos[1] + target.size[1] / 2.0) - e.transform.pos[1]) / 20.0;
|
|
|
|
|
|
|
|
|
|
// --- SET CURRENT FRAME CAMERA ---
|
|
|
|
|
res.frame.camera = .{
|
|
|
|
|
.value = e.camera,
|
|
|
|
|
.transform = e.transform,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-07-13 01:33:26 +01:00
|
|
|
|
|
|
|
|
{ // BUILD TEXTURES
|
|
|
|
|
for (res.textures.data.items()) |*entry| {
|
|
|
|
|
// --- SKIP IF ALREADY BUILD ---
|
|
|
|
|
if (entry.value.raw != null) continue;
|
|
|
|
|
|
|
|
|
|
// --- SURFACE ---
|
|
|
|
|
const surface = sdl.IMG_Load(entry.value.path.ptr) orelse {
|
|
|
|
|
gtl.log.err("Texture: {s}", .{sdl.SDL_GetError()});
|
|
|
|
|
continue;
|
|
|
|
|
};
|
|
|
|
|
defer sdl.SDL_DestroySurface(surface);
|
|
|
|
|
|
|
|
|
|
// --- TEXTURE ---
|
|
|
|
|
const raw = sdl.SDL_CreateTextureFromSurface(
|
|
|
|
|
res.renderer.raw,
|
|
|
|
|
surface,
|
|
|
|
|
) orelse return error.Texture;
|
|
|
|
|
|
|
|
|
|
// --- SET TEXTURE FILTER ---
|
|
|
|
|
_ = sdl.SDL_SetTextureScaleMode(
|
2026-07-13 13:13:17 +01:00
|
|
|
raw,
|
2026-07-13 01:33:26 +01:00
|
|
|
sdl.SDL_SCALEMODE_PIXELART,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// --- ASSIGN ---
|
|
|
|
|
entry.value.raw = raw;
|
|
|
|
|
entry.value.size = .{ raw.*.w, raw.*.h };
|
|
|
|
|
|
|
|
|
|
// --- DEBUG ---
|
|
|
|
|
std.debug.print("{s}┏━ TEXTURE{s}\n", .{ gtl.ansi.purple, gtl.ansi.reset });
|
|
|
|
|
std.debug.print("{s}┃{s} Path: \"{s}\"\n", .{ gtl.ansi.purple, gtl.ansi.reset, entry.value.path });
|
|
|
|
|
std.debug.print("{s}┃{s} Size: {}x{}\n", .{ gtl.ansi.purple, gtl.ansi.reset, raw.*.w, raw.*.h });
|
|
|
|
|
std.debug.print("{s}┃{s} Filter: PixelArt\n", .{ gtl.ansi.purple, gtl.ansi.reset });
|
|
|
|
|
std.debug.print("{s}┗━{s}\n", .{ gtl.ansi.purple, gtl.ansi.reset });
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-07-11 18:45:05 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// ----------------------------------------------------
|
|
|
|
|
/// ----------------------------------------------------
|
|
|
|
|
pub fn endFrame(ctx: App.scheduler.Context) !void {
|
|
|
|
|
const res = try ctx.app.resources.getMany(struct {
|
|
|
|
|
frame: *resource.Frame,
|
|
|
|
|
time: *resource.Time,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
res.frame.camera = null;
|
2026-07-13 13:13:17 +01:00
|
|
|
res.time.elapsed += res.time.dt;
|
2026-07-11 18:45:05 +01:00
|
|
|
|
|
|
|
|
const frame_time =
|
|
|
|
|
@as(f32, @floatFromInt(sdl.SDL_GetPerformanceCounter() - res.time.now)) /
|
|
|
|
|
@as(f32, @floatFromInt(res.time.frequency));
|
|
|
|
|
|
|
|
|
|
if (frame_time < res.time.target_dt) {
|
|
|
|
|
const delay_ms = (res.time.target_dt - frame_time) * 1000.0;
|
|
|
|
|
sdl.SDL_Delay(@intFromFloat(delay_ms));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// ----------------------------------------------------
|
|
|
|
|
/// ----------------------------------------------------
|
|
|
|
|
pub fn updateTransforms(ctx: App.scheduler.Context) !void {
|
|
|
|
|
const res = try ctx.app.resources.getMany(struct {
|
|
|
|
|
time: *resource.Time,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// --- QUERY ---
|
|
|
|
|
var query = ctx.app.world.query(struct {
|
|
|
|
|
transform: *component.Transform,
|
|
|
|
|
velocity: *component.Velocity,
|
|
|
|
|
friction: ?*component.Friction,
|
|
|
|
|
}, .{});
|
|
|
|
|
while (query.next()) |e| {
|
|
|
|
|
// --- APPLY VELOCITY ---
|
|
|
|
|
e.transform.pos[0] += e.velocity.value[0] * res.time.dt;
|
|
|
|
|
e.transform.pos[1] += e.velocity.value[1] * res.time.dt;
|
|
|
|
|
|
|
|
|
|
// --- APPLY FRICTION ---
|
|
|
|
|
if (e.friction) |friction| {
|
|
|
|
|
const damping = std.math.exp(-friction.value * res.time.dt);
|
|
|
|
|
e.velocity.value[0] *= damping;
|
|
|
|
|
e.velocity.value[1] *= damping;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// ----------------------------------------------------
|
|
|
|
|
/// ----------------------------------------------------
|
|
|
|
|
pub fn updateTimers(ctx: App.scheduler.Context) !void {
|
|
|
|
|
const res = try ctx.app.resources.getMany(struct {
|
|
|
|
|
time: *resource.Time,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
var query = ctx.app.world.query(struct {
|
|
|
|
|
timer: *component.Timer,
|
|
|
|
|
}, .{});
|
|
|
|
|
while (query.next()) |e| {
|
|
|
|
|
// --- # ---
|
|
|
|
|
if (e.timer.finished and !e.timer.repeat)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
// --- PROGRESS ---
|
|
|
|
|
e.timer.remaining -=
|
|
|
|
|
res.time.dt;
|
|
|
|
|
|
|
|
|
|
if (e.timer.remaining <= 0.0) {
|
|
|
|
|
e.timer.finished = true;
|
|
|
|
|
|
|
|
|
|
if (e.timer.repeat) {
|
|
|
|
|
e.timer.remaining = e.timer.duration;
|
|
|
|
|
e.timer.finished = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// ----------------------------------------------------
|
|
|
|
|
/// ----------------------------------------------------
|
|
|
|
|
pub fn cast(ctx: App.scheduler.Context) !void {
|
|
|
|
|
const res = try ctx.app.resources.getMany(struct {
|
|
|
|
|
time: *resource.Time,
|
2026-07-13 01:33:26 +01:00
|
|
|
textures: *resource.Textures,
|
2026-07-11 18:45:05 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// --- QUERY ---
|
|
|
|
|
var query = ctx.app.world.query(struct {
|
|
|
|
|
caster: *component.Caster,
|
|
|
|
|
transform: *component.Transform,
|
|
|
|
|
}, .{});
|
|
|
|
|
while (query.next()) |e| {
|
|
|
|
|
// --- SUBTRACT COOLDOWN ---
|
|
|
|
|
e.caster.current_cooldown -=
|
|
|
|
|
@max(0, res.time.dt);
|
|
|
|
|
|
|
|
|
|
// --- SKIP IF ON COOLDOWN ---
|
|
|
|
|
if (e.caster.current_cooldown >= 0)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
// --- SPAWN PROJECTILE ---
|
|
|
|
|
try ctx.app.world.spawn(.{
|
|
|
|
|
component.Transform{
|
2026-07-13 13:13:17 +01:00
|
|
|
.pos = .{
|
|
|
|
|
e.transform.pos[0] + (e.transform.size[0] / 2.0),
|
|
|
|
|
e.transform.pos[1] + (e.transform.size[1] / 2.0),
|
|
|
|
|
},
|
2026-07-11 18:45:05 +01:00
|
|
|
},
|
2026-07-13 19:37:50 +01:00
|
|
|
component.AABB{},
|
2026-07-11 18:45:05 +01:00
|
|
|
component.Velocity{
|
2026-07-13 19:37:50 +01:00
|
|
|
.value = .{ 600, 0 },
|
2026-07-11 18:45:05 +01:00
|
|
|
},
|
2026-07-13 13:13:17 +01:00
|
|
|
component.Material{ .style = .{
|
|
|
|
|
.textured = try res.textures.load("src/assets/textures/projectile.png"),
|
|
|
|
|
} },
|
2026-07-13 19:37:50 +01:00
|
|
|
component.Projectile{
|
|
|
|
|
.owner = query.entity(),
|
|
|
|
|
},
|
2026-07-11 18:45:05 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// --- SET COOLDOWN ---
|
|
|
|
|
e.caster.current_cooldown =
|
|
|
|
|
e.caster.cooldown;
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-07-13 19:37:50 +01:00
|
|
|
|
|
|
|
|
/// ----------------------------------------------------
|
|
|
|
|
/// ----------------------------------------------------
|
|
|
|
|
pub fn resolveHealth(ctx: App.scheduler.Context) !void {
|
|
|
|
|
if (ctx.app.events.get(event.TakeDamage)) |queue| {
|
|
|
|
|
for (queue.items()) |take_dmg| {
|
|
|
|
|
const health = ctx.app.world.getComponent(component.Health, take_dmg.target) orelse continue;
|
|
|
|
|
if (health.current <= 0) continue;
|
|
|
|
|
health.current -= take_dmg.amount;
|
|
|
|
|
|
|
|
|
|
std.debug.print("{s}[TAKE_DAMAGE]{s} source({}) target({}) amount({})\n", .{
|
|
|
|
|
gtl.ansi.red,
|
|
|
|
|
gtl.ansi.reset,
|
|
|
|
|
take_dmg.source,
|
|
|
|
|
take_dmg.target,
|
|
|
|
|
take_dmg.amount,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
queue.clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var query = ctx.app.world.query(struct {
|
|
|
|
|
health: *component.Health,
|
|
|
|
|
}, .{});
|
|
|
|
|
while (query.next()) |e| {
|
|
|
|
|
if (e.health.current <= 0) {
|
|
|
|
|
ctx.app.world.despawn(query.entity());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// ----------------------------------------------------
|
|
|
|
|
/// ----------------------------------------------------
|
|
|
|
|
pub fn resolveCollisions(ctx: App.scheduler.Context) !void {
|
|
|
|
|
if (ctx.app.events.get(event.Collision)) |queue| {
|
|
|
|
|
for (queue.items()) |coll| {
|
|
|
|
|
const proj_a = ctx.app.world.getComponent(component.Projectile, coll.a);
|
|
|
|
|
const proj_b = ctx.app.world.getComponent(component.Projectile, coll.b);
|
|
|
|
|
|
|
|
|
|
if (proj_a) |projectile| {
|
|
|
|
|
if (projectile.owner != coll.b and
|
|
|
|
|
ctx.app.world.hasComponent(component.Health, coll.b))
|
|
|
|
|
{
|
|
|
|
|
try ctx.app.events.send(event.TakeDamage{
|
|
|
|
|
.target = coll.b,
|
|
|
|
|
.source = coll.a,
|
|
|
|
|
.amount = projectile.damage,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
ctx.app.world.despawn(coll.a);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (proj_b) |projectile| {
|
|
|
|
|
if (projectile.owner != coll.a and
|
|
|
|
|
ctx.app.world.hasComponent(component.Health, coll.a))
|
|
|
|
|
{
|
|
|
|
|
try ctx.app.events.send(event.TakeDamage{
|
|
|
|
|
.target = coll.a,
|
|
|
|
|
.source = coll.b,
|
|
|
|
|
.amount = projectile.damage,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
ctx.app.world.despawn(coll.b);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
queue.clear();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// ----------------------------------------------------
|
|
|
|
|
/// ----------------------------------------------------
|
|
|
|
|
pub fn checkAABB(ctx: App.scheduler.Context) !void {
|
|
|
|
|
var outer = ctx.app.world.query(struct {
|
|
|
|
|
aabb: *component.AABB,
|
|
|
|
|
transform: *component.Transform,
|
|
|
|
|
}, .{});
|
|
|
|
|
|
|
|
|
|
while (outer.next()) |a| {
|
|
|
|
|
const entity_a = outer.entity();
|
|
|
|
|
|
|
|
|
|
var inner = ctx.app.world.query(struct {
|
|
|
|
|
aabb: *component.AABB,
|
|
|
|
|
transform: *component.Transform,
|
|
|
|
|
}, .{});
|
|
|
|
|
|
|
|
|
|
while (inner.next()) |b| {
|
|
|
|
|
const entity_b = inner.entity();
|
|
|
|
|
|
|
|
|
|
// --- PREVENT SAME ENTITY ---
|
|
|
|
|
if (entity_a == entity_b)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
// --- CHECK IF OVERLAPS ---
|
|
|
|
|
if (overlaps(
|
|
|
|
|
a.transform.pos,
|
|
|
|
|
a.aabb,
|
|
|
|
|
b.transform.pos,
|
|
|
|
|
b.aabb,
|
|
|
|
|
)) {
|
|
|
|
|
try ctx.app.events.send(event.Collision{
|
|
|
|
|
.a = entity_a,
|
|
|
|
|
.b = entity_b,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn overlaps(
|
|
|
|
|
a_pos: @Vector(2, f32),
|
|
|
|
|
a: *component.AABB,
|
|
|
|
|
b_pos: @Vector(2, f32),
|
|
|
|
|
b: *component.AABB,
|
|
|
|
|
) bool {
|
|
|
|
|
return a_pos[0] + a.offset[0] < b_pos[0] + b.offset[0] + b.size[0] and
|
|
|
|
|
a_pos[0] + a.offset[0] + a.size[0] > b_pos[0] + b.offset[0] and
|
|
|
|
|
a_pos[1] + a.offset[1] < b_pos[1] + b.offset[1] + b.size[1] and
|
|
|
|
|
a_pos[1] + a.offset[1] + a.size[1] > b_pos[1] + b.offset[1];
|
|
|
|
|
}
|