const w4 = @import("./wasm4.zig"); const sh0rk = @import("./sh0rk.zig"); const sprites = @import("./sprites.zig"); const palette = @import("./palette.zig"); const tframe = @import("./tframe.zig"); const std = @import("std"); const fmt = std.fmt; const Direction = sh0rk.Direction; const Point = sh0rk.Point; const Rect = sh0rk.Rect; const map = @import("./maps/overworld.zig"); var frame_count: u32 = 0; var mara_direction: Direction = Direction.Right; var mara_frame: bool = false; var mara_box: Rect = Rect{ .base = map.start_point, .width = 16, .height = 16 }; var mara_speed: i16 = 0; var old_speed: i16 = 0; var screen = Rect{ .base = Point{ .x = 0, .y = 0 }, .width = 160, .height = 160 }; const screen_width = 20; const screen_height = 20; const tile_size: i16 = 8; var sound_timer: u8 = 0; var textBuf: [160]u8 = undefined; var done: bool = false; var camera = Point{ .x = 0, .y = 0 }; var state: sh0rk.State = .Gameplay; fn world_to_screen(p: Point) Point { p.sub(camera); } fn screen_to_world(p: Point) Point { p.add(camera); } export fn start() void { palette.mist(); } fn bonk() void { if (sound_timer != 0) { return; } w4.tone( w4.Tone.Frequency{ .start = 220, .end = 40 }, w4.Tone.Duration{ .attack = 0, .decay = 0, .sustain = 6, .release = 6, }, w4.Tone.Volume{ .sustain = 100, .attack = 100, }, w4.Tone.Flags{ .channel = .triangle, .pulse_duty = .@"1/4", }, ); sound_timer = 12; } fn drawMap() !void { switch (map.tileset) { .Dungeon => { w4.m.colors.* = .{ ._0 = .p2, ._1 = .p0, ._2 = .p1, ._3 = .p3, }; }, .Rpg => { w4.m.colors.* = .{ ._0 = .p3, ._1 = .p2, ._2 = .p1, ._3 = .p0, }; }, } if (!done) { var buf = fmt.bufPrint(&textBuf, "camera: {},{}", .{ camera.x, camera.y }) catch unreachable; w4.trace(buf); } var startCol: i32 = @divTrunc(camera.x, tile_size); var endCol = startCol + screen_width; var startRow: i32 = @divTrunc(camera.y, tile_size); var endRow = startRow + screen_height; var offsetX = startCol * tile_size; var offsetY = startRow * tile_size; if (!done) { var buf = fmt.bufPrint(&textBuf, "{},{}: {},{}", .{ startCol, startRow, endCol, endRow }) catch unreachable; w4.trace(buf); } if (!done) { var buf = fmt.bufPrint(&textBuf, "offx: {}, offy: {}", .{ offsetX, offsetY }) catch unreachable; w4.trace(buf); } var col: i32 = startCol; while (col < endCol) { var row: i32 = startRow; defer col += 1; while (row < endRow) { defer row += 1; var tile = map.get_tile(@intCast(u32, col), @intCast(u32, row)); var x = (row - startCol) * tile_size - offsetX; var y = (col - startRow) * tile_size - offsetY; if (x < 0) { x = x * -1; } if (y < 0) { y = y * -1; } if (!done) { var buf = fmt.bufPrint(&textBuf, "{},{}: {}: {},{}", .{ col, row, tile, x, y }) catch unreachable; w4.trace(buf); } draw_tile(tile, @intCast(u32, x), @intCast(u32, y)); } } } fn draw_tile(tile: u32, x: u32, y: u32) void { if (tile == 0) { w4.rect(x, y, 8, 8); return; } var tileX = (tile - 1) % map.ts_width; var tileY = (tile - 1) / map.ts_width; switch (map.tileset) { .Rpg => w4.blitSub( &sprites.kenney_rpg, x, y, 8, 8, @intCast(u32, tileX) * 8, @intCast(u32, tileY) * 8, sprites.kenney_rpg_width, w4.BlitFlags{ .two_bits = true }, ), .Dungeon => w4.blitSub( &sprites.dungeon, x, y, 8, 8, @intCast(u32, tileX) * 8, @intCast(u32, tileY) * 8, sprites.dungeon_width, w4.BlitFlags{ .two_bits = true }, ), } } fn move_mara(gamepad: w4.GamePad) void { if (mara_speed > 0) { mara_speed -= 1; } if (gamepad.up) { mara_direction = Direction.Up; mara_speed = 1; } else if (gamepad.down) { mara_direction = Direction.Down; mara_speed = 1; } else if (gamepad.left) { mara_direction = Direction.Left; mara_speed = 1; } else if (gamepad.right) { mara_direction = Direction.Right; mara_speed = 1; } switch (mara_direction) { .Up => { mara_box.base.y -= mara_speed; }, .Down => { mara_box.base.y += mara_speed; }, .Left => { mara_box.base.x -= mara_speed; }, .Right => { mara_box.base.x += mara_speed; }, } } fn title() !void { w4.m.colors._0 = .p3; w4.m.colors._1 = .p0; w4.text("Mara 2:\nTamamo's Fury", 28, 8); w4.text("Press z or x", 32, 136); w4.text("From Within 2022", 16, 152); w4.m.colors._0 = .p0; w4.m.colors._1 = .p1; w4.m.colors._2 = .p2; w4.m.colors._3 = .p3; w4.blit(&sprites.tamamotitle, 48, 60, sprites.tamamotitle_width, sprites.tamamotitle_height, w4.BlitFlags{ .two_bits = true }); const gamepad = w4.m.gamepads[0]; if (gamepad.a or gamepad.b) { state = .StoryDump; } } const story = "After defeating\nthe evil mage,\nMalto was at\npeace.\n\nOne day Mara was\nwalking along the\nbeach when she saw\nthe killing stone\nwas split in two.\nTamamo-no-Mae was\nfree to wreak havoc\nacross the land.\n\nHelp us again Mara!\nSave Kanar from\nTamamo-no-Mae!\n\nPress x."; var story_idx: usize = 0; var story_counter: u8 = 4; fn storydump() !void { w4.m.colors._0 = .p3; w4.m.colors._1 = .p0; w4.text(story[0..story_idx], 4, 4); const gamepad = w4.m.gamepads[0]; if (gamepad.b) { state = .Gameplay; palette.mist(); } if (story_idx < story.len) { story_counter -= 1; if (gamepad.a) { story_counter = 0; } if (story_counter == 0) { story_idx += 1; story_counter = 4; w4.tone( w4.Tone.Frequency{ .start = 280, .end = 310 }, w4.Tone.Duration{ .attack = 0, .decay = 0, .sustain = 2, .release = 0, }, w4.Tone.Volume{ .sustain = 100, .attack = 100, }, w4.Tone.Flags{ .channel = .triangle, .pulse_duty = .@"1/4", }, ); } } else { if (gamepad.a) { state = .Gameplay; palette.mist(); } } } export fn update() void { defer frame_count += 1; switch (state) { .Title => title() catch unreachable, .StoryDump => storydump() catch unreachable, .Gameplay => gameplay() catch unreachable, } } fn gameplay() !void { defer done = true; if (sound_timer != 0) { sound_timer -= 1; } drawMap() catch unreachable; const gamepad = w4.m.gamepads[0]; if (!tframe.enabled) { move_mara(gamepad); } else { tframe.update(gamepad); } for (map.coll) |box| { if (mara_box.collides(box)) { switch (mara_direction) { .Up => { mara_box.base.y += mara_speed; }, .Down => { mara_box.base.y -= mara_speed; }, .Left => { mara_box.base.x += mara_speed; }, .Right => { mara_box.base.x -= mara_speed; }, } mara_speed = 0; bonk(); } } for (map.triggers) |trig| { if (mara_box.collides(trig.aura) and mara_direction == trig.direction and gamepad.b) { tframe.set_text(trig.dialogue); } } if (mara_box.base.x <= 0) { mara_box.base.x = 0; bonk(); } if (mara_box.base.y <= 0) { mara_box.base.y = 0; bonk(); } draw_mara(); } fn draw_glaceon(p: Point) void { palette.mist(); w4.m.colors.* = .{ ._0 = .p0, ._1 = .p1, ._2 = .p3, ._3 = .transparent, }; w4.blit(&sprites.glaceon, p.x, p.y, sprites.glaceon_width, sprites.glaceon_height, w4.BlitFlags{ .two_bits = true }); } fn draw_mara() void { var flags: w4.BlitFlags = w4.BlitFlags{ .two_bits = true, }; w4.m.colors.* = .{ ._0 = .transparent, ._1 = .p0, ._2 = .p1, ._3 = .p3, }; if (mara_direction == Direction.Left) { flags.flip_x = true; } if (frame_count % 32 == 0 and mara_speed > 0) { mara_frame = !mara_frame; } var frame: u32 = switch (mara_direction) { .Left => 4, .Right => 4, .Up => 2, .Down => 0, }; var step: u32 = if (mara_frame) 1 else 0; w4.blitSub(&sprites.Mara, @intCast(u32, mara_box.base.x), @intCast(u32, mara_box.base.y), 16, 16, 16 * (frame + step), 0, 96, flags); } pub fn panic(msg: []const u8, st: ?*std.builtin.StackTrace) noreturn { _ = st; w4.trace("!!! PANIC !!!"); w4.trace(msg); while (true) {} }