Skip to content

Commit

Permalink
Animated pickaxe
Browse files Browse the repository at this point in the history
Signed-off-by: Ole Herman Schumacher Elgesem <[email protected]>
  • Loading branch information
olehermanse committed Aug 21, 2024
1 parent 69c51d7 commit b6863ed
Show file tree
Hide file tree
Showing 5 changed files with 229 additions and 42 deletions.
138 changes: 122 additions & 16 deletions src/frontend/painter.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Drawer } from "../todo_utils.ts";
import { Drawer, xy_copy } from "../todo_utils.ts";
import { Application } from "./application.ts"; // For access to width, height, game object
import {
Choice,
Expand All @@ -11,29 +11,121 @@ import {
import { CR, XY } from "@olehermanse/utils";
import { cr, wh, xy } from "@olehermanse/utils/funcs.js";

class SpriteLocation {
export class SpriteMetadata {
cr: CR;
frames: number;
constructor(r: number, c: number, frames?: number) {
constructor(
r: number,
c: number,
frames?: number,
public animation_data?: AnimationData,
) {
this.cr = cr(c, r);
this.frames = frames ?? 1;
}
}

export interface AnimationFrame {
index: number;
time: number;
}

export function frame(index: number, time: number): AnimationFrame {
return { index: index, time: time };
}

export class AnimationData {
constructor(
public frames: AnimationFrame[],
public loop?: boolean,
) {
}

get_animator() {
return new Animation(this.frames, this.loop);
}
}

export class Animation {
current_ms: number = 0;
current_frame_index: number = 0;
max_time: number;
done: boolean = false;
constructor(
public frames: AnimationFrame[],
public loop?: boolean,
) {
this.max_time = this.frames[this.frames.length - 1].time;
}

restart() {
this.current_frame_index = 0;
this.current_ms = 0;
this.done = false;
}

get_current_frame(): number {
return this.frames[this.current_frame_index].index;
}

get_next_time(): number {
return this.frames[this.current_frame_index].time;
}

tick(ms: number) {
if (this.done) {
return;
}
this.current_ms += ms;
const beyond_end = this.current_ms >= this.max_time;
if (beyond_end) {
if (!this.loop) {
this.done = true;
this.current_frame_index = this.frames.length - 1;
return;
}
// Go back to start if necessary
this.current_frame_index = 0;
this.current_ms -= this.max_time;
while (this.current_ms > this.max_time) {
this.current_ms -= this.max_time;
}
}
// Advance frame if necessary
while (
this.current_frame_index < this.frames.length - 1 &&
this.current_ms > this.get_next_time()
) {
this.current_frame_index += 1;
}
}
}

const SPRITESHEET = {
player: new SpriteLocation(0, 0, 2),
sword: new SpriteLocation(1, 0, 2),
pickaxe: new SpriteLocation(1, 2, 2),
axe: new SpriteLocation(1, 4, 2),
staff: new SpriteLocation(1, 4, 2),
selector: new SpriteLocation(2, 0, 2),
chest: new SpriteLocation(3, 0),
rock: new SpriteLocation(3, 1, 3),
crystal: new SpriteLocation(3, 4),
skeleton: new SpriteLocation(4, 0, 4),
fog: new SpriteLocation(5, 0, 5),
player: new SpriteMetadata(0, 0, 2),
sword: new SpriteMetadata(1, 0, 2),
pickaxe: new SpriteMetadata(
1,
2,
2,
new AnimationData([frame(0, 250), frame(1, 500)], false),
),
axe: new SpriteMetadata(1, 4, 2),
staff: new SpriteMetadata(1, 4, 2),
selector: new SpriteMetadata(2, 0, 2),
chest: new SpriteMetadata(3, 0),
rock: new SpriteMetadata(3, 1, 3),
crystal: new SpriteMetadata(3, 4),
skeleton: new SpriteMetadata(4, 0, 4),
fog: new SpriteMetadata(5, 0, 5),
};

export type SpriteName = keyof typeof SPRITESHEET;

export function get_sprite_metadata(name: SpriteName) {
return SPRITESHEET[name];
}

type SpriteCallback = {
(spritesheet: ImageBitmap[][]): void;
};
Expand All @@ -48,10 +140,10 @@ function load_sprites(
) {
const image = new Image();
const sprites: ImageBitmap[] = [];
const frames: SpriteLocation[] = [];
const frames: SpriteMetadata[] = [];
for (let r = 0; r < rows; r++) {
for (let c = 0; c < columns; c++) {
frames.push(new SpriteLocation(r, c));
frames.push(new SpriteMetadata(r, c));
}
}
image.onload = () => {
Expand Down Expand Up @@ -269,6 +361,20 @@ export class Painter {
player.xy,
player.reversed,
);
for (const item of player.inventory) {
if (item.animation === undefined || item.animation.done) {
continue;
}
const sprite =
this.sprites[item.name][item.animation.get_current_frame()];
const pos = xy_copy(player.xy);
if (player.reversed) {
pos.x -= 4;
} else {
pos.x += 4;
}
this.offscreen_drawer.sprite(sprite, pos, player.reversed);
}
}

draw_card(choice: Choice) {
Expand Down
98 changes: 78 additions & 20 deletions src/libtrpg/game.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,37 +20,58 @@ import {
import { generate_room, RoomType } from "./rooms.ts";
import { Keyboard } from "./keyboard.ts";
import { cr_4_neighbors } from "../todo_utils.ts";
import {
Animation,
get_sprite_metadata,
SpriteMetadata,
SpriteName,
} from "../frontend/painter.ts";

const DIAG = 1.42;
const BASE_SPEED = 16.0;

export class Entity {
name: string;
zone: Zone;
xy: XY;
fxy: XY | null; // can hold floating point xy values, so players can move half pixels.
cr: CR;
wh: WH;
variant: number;
reversed: boolean;
animation?: Animation;

constructor(
name: string,
public name: SpriteName,
pos: CR,
zone: Zone,
public zone: Zone,
variant?: number,
reversed?: boolean,
public reversed?: boolean,
) {
this.reversed = reversed === true;
this.name = name;
this.zone = zone;
this.cr = cr(pos.c, pos.r);
this.wh = wh(zone.cell_width, zone.cell_height);
this.xy = cr_to_xy(this.cr, zone);
this.variant = variant ?? 0;
this.fxy = null;
}

tick(ms: number) {
if (this.animation === undefined) {
return;
}
this.animation.tick(ms);
}

start_animation() {
if (this.animation !== undefined) {
this.animation.restart();
return;
}
const metadata: SpriteMetadata = get_sprite_metadata(this.name);
if (metadata.animation_data === undefined) {
console.log("No animation found for " + this.name);
return;
}
this.animation = metadata.animation_data.get_animator();
}

get center(): XY {
const r = xy(this.xy.x, this.xy.y);
r.x += this.wh.width / 2;
Expand All @@ -74,7 +95,7 @@ export class Target {
constructor(
public cr: CR,
grid: Grid,
public draw: boolean
public draw: boolean,
) {
this.xy = cr_to_xy(cr, grid);
}
Expand All @@ -89,6 +110,7 @@ export class Player extends Entity {
xp = 0;
stats: Stats;
upgrades: NamedUpgrade[];
inventory: Entity[] = [];
speed = BASE_SPEED;
walk_counter = 0;
target: Target | null = null;
Expand Down Expand Up @@ -238,6 +260,9 @@ export class Player extends Entity {
}

tick(ms: number) {
for (const item of this.inventory) {
item.tick(ms);
}
if (this.target === null) {
return;
}
Expand Down Expand Up @@ -280,6 +305,15 @@ export class Player extends Entity {
this.cr.c = new_pos.c;
this.cr.r = new_pos.r;
this.defog();
const tile = this.game.current_zone.get_tile(this.cr);
if (tile.is_interactable()) {
this.interact(tile);
}
}
interact(tile: Tile) {
const item = tile.pickup();
item.start_animation();
this.inventory.push(item);
}
}

Expand Down Expand Up @@ -332,6 +366,24 @@ export class Tile {
is_empty() {
return this.entities.length === 0;
}

is_interactable() {
if (this.is_empty() || this.is_rock()) {
return false;
}
if (this.entities[0].name === "pickaxe") {
return true;
}
return false;
}

pickup() {
console.assert(this.entities.length > 0);
console.assert(this.entities[0].name === "pickaxe");
const item = this.entities[0];
array_remove(this.entities, item);
return item;
}
}

export class Zone extends Grid {
Expand Down Expand Up @@ -604,7 +656,7 @@ export class Game {
constructor(public grid: Grid) {
this.current_zone = new Zone(grid, this, cr(0, 0));
this.put_zone(this.current_zone);
this.player = new Player(cr(1, 1), this.current_zone, this);
this.player = new Player(cr(7, 3), this.current_zone, this);
this.choices = [];
this.choices.push(new Choice("Vision", "light +1", 0, grid));
this.choices.push(new Choice("Haste", "Speed x2", 1, grid));
Expand Down Expand Up @@ -768,6 +820,12 @@ export class Game {
this.choices[2].set(upgrades[2]);
}

attempt_move_or_interact(pos: CR, mouse: boolean) {
const tile = this.current_zone.get_tile(pos);
console.assert(tile.is_empty() || tile.is_interactable());
this.player.target = new Target(pos, this.grid, mouse);
}

zone_click(position: XY) {
const pos = xy_to_cr(position, this.grid);
if (
Expand All @@ -777,11 +835,11 @@ export class Game {
) {
return;
}
const tile = this.current_zone.tiles[pos.c][pos.r];
const tile = this.current_zone.get_tile(pos);
if (tile.light !== 5 || !tile.is_empty()) {
return;
}
this.player.target = new Target(pos, this.grid, true);
this.attempt_move_or_interact(pos, true);
}

level_up_click(position: XY) {
Expand All @@ -807,8 +865,8 @@ export class Game {
let up = this.keyboard.pressed("w") || this.keyboard.pressed("ArrowUp");
let down = this.keyboard.pressed("s") || this.keyboard.pressed("ArrowDown");
let left = this.keyboard.pressed("a") || this.keyboard.pressed("ArrowLeft");
let right =
this.keyboard.pressed("d") || this.keyboard.pressed("ArrowRight");
let right = this.keyboard.pressed("d") ||
this.keyboard.pressed("ArrowRight");

if (up === down && left === right) {
return;
Expand All @@ -821,7 +879,7 @@ export class Game {
(down && this.player.cr.r === this.current_zone.rows - 1) ||
(right && this.player.cr.c === this.current_zone.columns - 1)
) {
this.player.target = new Target(this.player.cr, this.grid, false);
this.attempt_move_or_interact(this.player.cr, false);
return;
}
}
Expand Down Expand Up @@ -854,8 +912,8 @@ export class Game {
if (tile.light !== 5) {
return;
}
if (tile.is_empty()) {
this.player.target = new Target(pos, this.grid, false);
if (tile.is_empty() || tile.is_interactable()) {
this.attempt_move_or_interact(pos, false);
return;
}
const alternatives: CR[] = cr_4_neighbors(
Expand All @@ -880,15 +938,15 @@ export class Game {
neighbor.c === this.current_zone.columns - 1 ||
neighbor.r === this.current_zone.rows - 1
) {
this.player.target = new Target(neighbor, this.grid, false);
this.attempt_move_or_interact(neighbor, false);
return;
}
second_choice = neighbor;
}
if (second_choice === null) {
return;
}
this.player.target = new Target(second_choice, this.grid, false);
this.attempt_move_or_interact(second_choice, false);
}

click(position: XY) {
Expand Down
Loading

0 comments on commit b6863ed

Please sign in to comment.