-
Notifications
You must be signed in to change notification settings - Fork 1k
All Trees Permanently Get Cut
By devolov
Goal: Make it so cutting a tree once will keep it cut for the rest of the game.
Removing cut trees permanently is much harder in Red than Emerald. In Red, the trees are not overworld sprites, but rather tiles. Whenever you cut a tree, the game finds the exact tile in front of you and replaces it with a cut version of that tile via a look-up-table.
In order to permanently cut, after loading a map but before drawing it, all of the trees on must be checked if they've been cut before, and if so, they should be replaced with their non-cut tile.
Create the file remove_cut_trees.asm
in engine/overworld
and find a place to put it in a bank that's not overstuffed in main.
I'm working off Red++ V3, so I placed it in field moves
, but the vanilla ROM won't have that, so you're on your own to find a place for it.
----------------------------------- main.asm ---------------------------------
SECTION "field moves", ROMX,BANK[$38]
INCLUDE "engine/overworld/field_moves.asm"
INCLUDE "engine/wonder_trade.asm"
INCLUDE "engine/overworld/automatic_repel.asm"
INCLUDE "scripts/DayCareManScript.asm"
INCLUDE "engine/overworld/ferry_script.asm"
INCLUDE "engine/battle/physical_special_split.asm"
INCLUDE "scripts/move_deleter.asm"
INCLUDE "scripts/move_relearner.asm"
INCLUDE "scripts/move_tutor.asm"
+INCLUDE "engine/overworld/remove_cut_trees.asm"
Fill remove_cut_trees
with this content. For any tree that exists in the game, its location is checked to CutTreeLocations
, so if you have more trees, add them to that array and make the wCutTrees larger if you surpass 24 trees (the game has 23 trees by default).
------------------- engine/overworld/remove_cut_trees.asm -------------------
CutTreeLocations:
; first byte = The map the tree is on
; second byte = The Y coordinate of the block
; third byte = The X coordinate of the block
db VIRIDIAN_CITY, 2, 7
db VIRIDIAN_CITY, 11, 4
db ROUTE_2, 11, 7
db ROUTE_2, 26, 6
db ROUTE_2, 30, 6
db ROUTE_2, 34, 6
db PEWTER_CITY, 2, 13
db CERULEAN_CITY, 14, 9
db ROUTE_8, 5, 20
db ROUTE_8, 6, 14
db ROUTE_9, 4, 2
db VERMILION_CITY, 9, 7
db ROUTE_10, 9, 4
db ROUTE_10, 10, 4
db ROUTE_10, 11, 4
db ROUTE_10, 12, 4
db CELADON_CITY, 10, 23
db ROUTE_13, 2, 17
db ROUTE_14, 16, 5
db ROUTE_14, 13, 2
db ROUTE_14, 21, 1
db ROUTE_16, 4, 17
db ROUTE_25, 1, 13
db $FF ; list terminator
SetCutTreeFlags::
ld a, [wYCoord]
sra a
ld d, a ; d holds the Y block loc
ld a, [wXCoord]
sra a
ld e, a ; e holds the X block loc
ld a, [wSpriteStateData1 + 9] ; player sprite's facing direction
and a
jr z, .down
cp SPRITE_FACING_UP
jr z, .up
cp SPRITE_FACING_LEFT
jr z, .left
; right
ld a, [wXBlockCoord]
and a
jr z, .findMapLoc
inc e
jr .findMapLoc
.down
ld a, [wYBlockCoord]
and a
jr z, .findMapLoc
inc d
jr .findMapLoc
.up
ld a, [wYBlockCoord]
and a
jr nz, .findMapLoc
dec d
jr .findMapLoc
.left
ld a, [wXBlockCoord]
and a
jr nz, .findMapLoc
dec e
.findMapLoc
ld a,[wCurMap]
ld b, a
ld hl, CutTreeLocations ; d = Y loc ; e = X loc ; b = map loc
ld c, 0
jr .loopfirst
.loopinctwo
inc hl
.loopincone
inc hl
inc c
.loopfirst ; find the matching tile block in the array
ld a, [hl]
inc hl
cp $ff
ret z ; Not in list; return with a cleared carry flag
cp b ; Compare map
jr nz, .loopinctwo
ld a, [hl] ; hl +1 (Y loc)
inc hl
cp d
jr nz, .loopincone
ld a, [hl] ; hl +2 (X loc)
cp e
jr nz, .loopincone
ld b, 1
ld hl, wCutTrees
ld a, c
cp 8
jr c, .setByte
; second byte of wCutTrees
sub 8
inc hl
ld c, a
.setByte
predef FlagActionPredef
ret
RemoveAlreadyCutTrees::
ld hl, CutTreeLocations
ld c, 0
jr .loopfirst
.loopinctwo
inc hl
.loopincone
inc hl
inc c
.loopfirst ; find the matching tile block in the array
ld a,[wCurMap]
ld b, a
ld a, [hl]
inc hl
cp $ff
ret z ; Not in list; return with a cleared carry flag
cp b ; Compare map
jr nz, .loopinctwo
ld d, [hl] ; hl +1 (Y loc)
inc hl
ld e, [hl] ; hl +2 (X loc)
push hl
ld hl, wCutTrees
ld a, c
ld [wTempCoins1], a ; temporarily store the current iteration
.iterByte
cp 8
jr c, .checkByte
sub 8
inc hl
ld c, a
jr .iterByte
.checkByte
ld b, 2
predef FlagActionPredef
ld a, c
and a
ld a, [wTempCoins1]
ld c, a
pop hl
jr z, .loopincone
ld b, d
ld c, e
push hl
predef FindTileBlock ; hl holds block ID at X,Y coord on the map
ld a, [hl]
ld [wNewTileBlockID], a
push hl
farcall FindTileBlockReplacementCut
pop hl
ld a, [wNewTileBlockID]
ld [hl], a
ld a, [wTempCoins1]
ld c, a
pop hl
jr .loopincone
Change cut.asm
so that every time cut is called, the flag for the tree being cut is set. Also, some logic is modified to allow for remove_cut_trees
to read the CutTreeBlockSwaps
look-up-table.
--------------------------- engine/overworld/cut.asm ---------------------------
index b4c8112d..6dacb7d8 100755
@@ -58,8 +58,9 @@ Cut2:: ; added for Field Move hack
ld [wUpdateSpritesEnabled], a
call InitCutAnimOAM
ld de, CutTreeBlockSwaps
call ReplaceTreeTileBlock
+ farcall SetCutTreeFlags
call RedrawMapView
callba AnimCut
ld a, $1
ld [wUpdateSpritesEnabled], a
@@ -243,19 +244,30 @@ ReplaceTreeTileBlock:
.next
pop de
ld a, [hl]
ld c, a
+ call LoopForTileReplacement
+ ld [hl], a
+ ret
+
+LoopForTileReplacement: ; find the matching tile block in the array
.loop ; find the matching tile block in the array
ld a, [de]
inc de
inc de
cp $ff
ret z
cp c
jr nz, .loop
dec de
ld a, [de] ; replacement tile block from matching array entry
- ld [hl], a
+ ret
+
+FindTileBlockReplacementCut::
+ ld de, CutTreeBlockSwaps
+ ld a, [wNewTileBlockID]
+ ld c, a
+ call LoopForTileReplacement
+ ld [wNewTileBlockID], a
ret
CutTreeBlockSwaps:
; first byte = tileset block containing the cut tree
The modifications to update_map
are done to make it so we can now find the X,Y tile coordinates of our current location. Be careful here, as this is located in a Bank that's fairly filled.
----------------------- engine/overworld/update_map.asm -----------------------
index 8577b9e7..f5d84bc9 100644
@@ -2,30 +2,9 @@
; and redraws the map view if necessary
; b = Y
; c = X
ReplaceTileBlock:
- call GetPredefRegisters
- ld hl, wOverworldMap
- ld a, [wCurMapWidth]
- add $6
- ld e, a
- ld d, $0
- add hl, de
- add hl, de
- add hl, de
- ld e, $3
- add hl, de
- ld e, a
- ld a, b
- and a
- jr z, .addX
-; add width * Y
-.addWidthYTimesLoop
- add hl, de
- dec b
- jr nz, .addWidthYTimesLoop
-.addX
- add hl, bc ; add X
+ call FindTileBlock
ld a, [wNewTileBlockID]
ld [hl], a
ld a, [wCurrentTileBlockMapViewPointer]
ld c, a
@@ -123,4 +102,33 @@ CompareHLWithBC:
ret nz
ld a, l
sub c
ret
+
+; loads the current tile block into in [wNewTileBlockID]
+; b = Y
+; c = X
+; ret = hl = the ID of the currently loaded tile
+FindTileBlock:
+ call GetPredefRegisters
+ ld hl, wOverworldMap
+ ld a, [wCurMapWidth]
+ add $6
+ ld e, a
+ ld d, $0
+ add hl, de
+ add hl, de
+ add hl, de
+ ld e, $3
+ add hl, de
+ ld e, a
+ ld a, b
+ and a
+ jr z, .addX
+; add width * Y
+.addWidthYTimesLoop
+ add hl, de
+ dec b
+ jr nz, .addWidthYTimesLoop
+.addX
+ add hl, bc ; add X
+ ret
Add FindTileBlock
into predefs.asm
so your registers won't reset when calling it.
------------------------------ engine/predefs.asm ------------------------------
index abfaffd6..9751e99c 100755
@@ -151,4 +151,5 @@ PredefPointers::
add_predef DrawHP2
add_predef DisplayElevatorFloorMenu
add_predef OaksAideScript
add_predef TryFieldMove
+ add_predef FindTileBlock
In overworld
, after the LoadTileBlockMap
calls, add in RemoveAlreadyCutTrees
.
------------------------------ home/overworld.asm ------------------------------
index 69da9692..50f53b62 100755
@@ -727,8 +727,9 @@ CheckMapConnections::
; Since the sprite set shouldn't change, this will just update VRAM slots at
; $C2XE without loading any tile patterns.
callba InitMapSprites
call LoadTileBlockMap
+ farcall RemoveAlreadyCutTrees
jp OverworldLoopLessDelay
.didNotEnterConnectedMap
jp OverworldLoop
@@ -2191,8 +2192,9 @@ LoadMapData::
call LoadTextBoxTilePatterns
call LoadMapHeader
callba InitMapSprites ; load tile pattern data for sprites
call LoadTileBlockMap
+ farcall RemoveAlreadyCutTrees
call LoadTilesetTilePatternData
call LoadCurrentMapView
ld b, SET_PAL_OVERWORLD
Finally, add the wCutTrees
flags into a part of wram that saves into sram. The amount of bytes needs to be at least as large as the amount of the trees in your hack divided by 8, rounded up.
----------------------------------- wram.asm -----------------------------------
index 88f0fc7e..2b88ebe2 100755
@@ -3363,10 +3367,13 @@ ENDU
wTrainerHeaderPtr:: ; da30
ds 2
+wCutTrees::
+; Check CutTreeLocations for the indexes
+ ds 3
; unused?
- ds 6
+ ds 3
wOpponentAfterWrongAnswer:: ; da38
; the trainer the player must face after getting a wrong answer in the Cinnabar
; gym quiz
If all went well, you should be done. The only issue I found was that the tree to the right of Cerulean City in Route 9 is so close, that it can be seen when you're in Cerulean City and it'll pop out of existence when you walk to it. That's because the first three tiles of connected maps get loaded when you're on a map. An easy fix for this is to move the tree by one tile in Polished Map.
Here's what the issue looks like without a fix:
Before:
After:
In CutTreeLocations
, change the location accordingly:
CutTreeLocations:
; first byte = The map the tree is on
; second byte = The Y coordinate of the block
; third byte = The X coordinate of the block
db VIRIDIAN_CITY, 2, 7
db VIRIDIAN_CITY, 11, 4
db ROUTE_2, 11, 7
db ROUTE_2, 26, 6
db ROUTE_2, 30, 6
db ROUTE_2, 34, 6
db PEWTER_CITY, 2, 13
db CERULEAN_CITY, 14, 9
db ROUTE_8, 5, 20
db ROUTE_8, 6, 14
- db ROUTE_9, 4, 2
+ db ROUTE_9, 4, 3
db VERMILION_CITY, 9, 7
db ROUTE_10, 9, 4
db ROUTE_10, 10, 4
db ROUTE_10, 11, 4
db ROUTE_10, 12, 4
db CELADON_CITY, 10, 23
db ROUTE_13, 2, 17
db ROUTE_14, 16, 5
db ROUTE_14, 13, 2
db ROUTE_14, 21, 1
db ROUTE_16, 4, 17
db ROUTE_25, 1, 13
db $FF ; list terminator