Port Headbutt and Rock Smash encounters
Facing a Headbutt tree asks to HEADBUTT it, and HEADBUTT from the party menu skips the question. ShakeHeadbuttTree shakes the tree as a sprite over grass, then TreeMonEncounter scores the facing tile against the trainer ID and draws from the map's common or rare tree table. A Pokémon that falls fights as BATTLETYPE_TREE: "fell out of the tree!", and the time of day's AsleepTreeMons species start asleep. Smashable rocks run SmashRockScript, and ROCK SMASH from the party menu acts on the facing rock; after the rock breaks, RockMonEncounter gives the map's rock set a 40% chance of a wild battle. Closes #26 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ee55d344af
commit
f259163da9
18 files changed
+789
-36
No files matched your search
@@ -10,6 +10,11 @@ import {
|
||||
type RenderViewport,
|
||||
} from "./viewport";
|
||||
|
||||
/** Video frames before LoadOverworldTilemapAndAttrmapPals puts the
|
||||
* Headbutt tree back: HideHeadbuttTree's WaitBGMap and the 32 shaking
|
||||
* frames. */
|
||||
const HEADBUTT_HIDDEN_FRAMES = 36;
|
||||
|
||||
export class WorldRenderer {
|
||||
width: number = BASE_VIEWPORT.width;
|
||||
height: number = BASE_VIEWPORT.height;
|
||||
@@ -91,6 +96,10 @@ export class WorldRenderer {
|
||||
const cameraX = Math.round(player.x - (this.width / 2 - 8)),
|
||||
cameraY = Math.round(player.y - (this.height / 2 - 8)) + world.screenShake;
|
||||
this.image.data.fill(255);
|
||||
// HideHeadbuttTree puts tile $05 (grass) in the tree's four tilemap
|
||||
// places, keeping their attributes, until the shaking ends.
|
||||
const tree = world.headbuttTree;
|
||||
const hidden = tree && tree.frames < HEADBUTT_HIDDEN_FRAMES ? tree : undefined;
|
||||
for (
|
||||
let tileY = Math.floor(cameraY / 8);
|
||||
tileY < Math.ceil((cameraY + this.height) / 8);
|
||||
@@ -111,7 +120,12 @@ export class WorldRenderer {
|
||||
const encoded = tileset.metatiles[block * 16 + localY * 4 + localX];
|
||||
const packed = tileset.paletteMap[encoded >> 1],
|
||||
attribute = encoded & 1 ? packed >> 4 : packed & 15;
|
||||
const tile = (encoded & 0x7f) + (attribute & 8 ? 0x60 : 0);
|
||||
const covered =
|
||||
hidden &&
|
||||
tileX >> 1 === hidden.x &&
|
||||
tileY >> 1 === hidden.y;
|
||||
const tile =
|
||||
(covered ? 0x05 : encoded & 0x7f) + (attribute & 8 ? 0x60 : 0);
|
||||
drawTile(
|
||||
this.image.data,
|
||||
this.width,
|
||||
@@ -200,6 +214,31 @@ export class WorldRenderer {
|
||||
);
|
||||
}
|
||||
}
|
||||
if (tree) {
|
||||
// SPRITE_ANIM_OBJ_HEADBUTT over the tree's square, in PAL_OW_TREE:
|
||||
// the two frames alternate every three animation steps, the second
|
||||
// mirrored on every other pass. The first step lasts through
|
||||
// HideHeadbuttTree's WaitBGMap, the last until the sprite is cleared.
|
||||
const graphics = this.assets.headbuttTree(),
|
||||
step = Math.min(32, Math.max(0, tree.frames - 4)),
|
||||
phase = Math.floor(step / 3) % 4,
|
||||
flip = phase === 3,
|
||||
palette = this.assets.objectPalette(6, time);
|
||||
for (let tile = 0; tile < 4; tile++) {
|
||||
const column = tile & 1;
|
||||
drawTile(
|
||||
this.image.data,
|
||||
this.width,
|
||||
this.height,
|
||||
graphics,
|
||||
(phase & 1) * 4 + tile,
|
||||
tree.x * 16 - cameraX + (flip ? 8 - column * 8 : column * 8),
|
||||
tree.y * 16 - cameraY + (tile >> 1) * 8,
|
||||
palette,
|
||||
{ transparent: true, flipX: flip },
|
||||
);
|
||||
}
|
||||
}
|
||||
this.context.putImageData(this.image, 0, 0);
|
||||
}
|
||||
private actor(
|
||||
|
||||
Reference in new issue
Block a user