402 lines
9.6 KiB
Zig
402 lines
9.6 KiB
Zig
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) {}
|
|
}
|