const std = @import("std"); const sdl = @import("sdl"); const gtl = @import("gtl"); const App = @import("app"); const component = @import("../component/root.zig"); const resource = @import("../resource/root.zig"); const event = @import("../event/root.zig"); /// ---------------------------------------------------- /// ---------------------------------------------------- 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, textures: *resource.Textures, renderer: *resource.Renderer, }); // // 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, }; } } { // 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( raw, 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 }); } } } /// ---------------------------------------------------- /// ---------------------------------------------------- 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; res.time.elapsed += res.time.dt; 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, textures: *resource.Textures, }); // --- 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{ .pos = .{ e.transform.pos[0] + (e.transform.size[0] / 2.0), e.transform.pos[1] + (e.transform.size[1] / 2.0), }, }, component.AABB{}, component.Velocity{ .value = .{ 600, 0 }, }, component.Material{ .style = .{ .textured = try res.textures.load("src/assets/textures/projectile.png"), } }, component.Projectile{ .owner = query.entity(), }, }); // --- SET COOLDOWN --- e.caster.current_cooldown = e.caster.cooldown; } } /// ---------------------------------------------------- /// ---------------------------------------------------- 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]; }