Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- --[[
- Zelda Hitbox Visualizer v1.0
- (Tested in FCEUX 2.2.3 with The Legend of Zelda (U) (PRG0))
- ====Instructions====
- Using FCEUX, load your Legend of Zelda ROM.
- Select "File -> Lua -> New Lua Script Window ..."
- In the Lua script window, select "Browse...", load this script file, and select Run.
- ====Description====
- This script visualizes collision information for The Legend of Zelda as dots and regions onscreen. Collision in Zelda
- doesn't explicitly work on hitboxes; instead, it checks for X and Y proximity between two points. When colliding two
- objects, the game finds the center of each object and does a proximity check using X and Y thresholds specific to the
- type of collision. To visualize this, this script displays one of the points as a dot and the other as a proximity
- rectangle centered at the other point. If the center of the dot intersects this rectangle or its border, the two
- objects have collided.
- Because Link and his weapons use different thresholds, enemies are displayed as dots while Link and his weapons are
- rectangles. Link and some weapons have a dot in addition to rectangles, specifically for collision with special
- boundaries, which display as lines, and a few objects that use special threshold sizes or collision rules, such as push
- blocks, burnable trees, and dropped items, which display as rectangles. Dots and rectangles that are one pixel wide are
- given a colored border to increase visibility, but these borders do not represent collidable regions. The borders of
- standard rectangles, however, are part of their collidable region.
- This script is able to visualize effectively all non-background collision. It supports Link, enemies, weapons, items,
- input restriction boundaries, NPC barriers, and interactive background elements, such as docks, pushable blocks,
- burnable trees, and bombable walls on both the overworld and in dungeons. It also supports screen boundaries, used to
- prevent objects and weapons from going too far, but this is disabled by default for clutter reasons. Visualizing
- background collision, door collision, and the movement grid is beyond the scope of this project. This script was
- written based on the collision code in the ROM rather than experimentation, and has been somewhat tested using frame-
- by-frame play to verify collisions occur when expected. It's possible there may be bugs in the implementation of some
- features, or that some implementations may be based on an incomplete or incorrect understanding of the game's code.
- Individual collision elements can be disabled by commenting out the relevant function calls in the HandleDrawing
- function very near the bottom of this file. This can be done by placing -- on the line anywhere before the function
- call for the feature you want to disable. Screen boundary visualization can be enabled by removing the -- from the
- front of its two relevant functions (DrawScreenBoundaries and DrawObjectActualPositions).
- Drawing with Lua is normally delayed by 1 frame. This is intentionally left in place for this script because Zelda also
- delays its drawing for 1 frame, so the two are synced. The script's 1 frame delay can be removed by uncommenting the
- gui.register(HandleDrawing) line at the end of this file and commenting out the Main loop.
- ====FAQ====
- Q: What are those red and green lines?
- A: These are input restriction boundaries, which indicate where you're not allowed to use your weapons. The red line
- shows where your B button is disabled if you're facing that edge of the screen, and the green shows where both A and B
- are disabled, though the direction varies depending on whether your B button is already disabled by the red boundary;
- the game tests the green boundary for the direction you're facing, or the opposite boundary if your B button is
- disabled. In dungeons, the green line on the top is actually below the red line, which means you can use your sword on
- that line while facing up, but not down.
- Q: What is that yellow dot when I stab?
- A: That's your weapon's collision center for picking up items dropped by enemies. Many of these special-purpose dots
- are color-coded for what they interact with. For example, red for bombs and dodongos, green for candle fire and
- burnable trees, and blue for gohma's eye and arrows. The color palette is limited and needs to be visible against the
- backgrounds in the game, though, so these colors are hints rather than hard-and-fast rules.
- Q: Enemies have tiny hitboxes and Link and his weapons have big hitboxes, right?
- A: Not exactly. As mentioned in the Description section, Zelda doesn't use hitboxes; it instead checks if two points
- are close enough (ie within separate X and Y thresholds). Collision is really asking if a rectangle of some specified
- size can be placed such that it contains both points; the hitbox isn't tied to either point. This could also be
- visualized as rectangles around the enemies, and dots for Link and his weapons. However, this would require drawing all
- differently-sized relevant hitboxes around the enemies, because they are involved in collisions of multiple different
- sizes. Keeping the hitboxes around player-controlled objects allows the script to display more information without it
- being too cluttered.
- Q: Can this script do anything else?
- A: It can also display the screen boundaries that prevent enemies and weapons from getting too close to the edge of the
- screen. In dungeons, this is what creates the outer wall that Link can't get through. Because of the way the boundaries
- interact with objects, the information can't be cleanly displayed at the same as everything else without making things
- feel very cluttered, so it's disabled by default. The Description section explains how to enable this.
- Q: What versions of Zelda does this work on?
- A: This was created using the US PRG0 version and will definitely work there. It's likely that it also works fine on
- PRG1 and the Japanese cartridge release, and those versions don't appear to have any problems when used, but they
- haven't been thoroughly tested.
- ====Observations====
- - Enemy sizes:
- Because this isn't using typical hitboxes, most enemies are effectively the exact same size. Enemies that you might
- expect to be smaller, such as gels, boomerangs, and fireballs, are just as big as enemies like stalfos, goriyas, and
- even Ganon.
- Some enemies, such as dodongos, digdoggers, and gohmas, are actually bigger than normal. This is done by separately
- handling collision at multiple positions within the object.
- - Enemy centers:
- Most enemy graphics are 16x16 pixels, but some are 8x16. The position of an object is the top left of its graphics, and
- for most enemies, the center of the object is chosen by adding 8 to the X and Y components (putting it just down and
- right of the center). For many of these thin objects, though, a flag is used that makes the game only add 4 to X
- instead of 8. This skews the object leftward, which, for most objects, throws it off center. Gels and most projectiles
- illustrate this issue well, and this is why Link won't be hit by vertically-moving projectiles when standing a half-
- block to the right. This flag does work properly with fireballs, though, so it's not exclusively wrong.
- - Enemy hitbox skew:
- Because collision is looking for a range around a center point, all hitboxes have odd dimensions. Collision against
- Link is done on a 17x17 range, but because tiles are 8x16 pixels, object graphics tend to be 16x16. The extra pixel of
- collision range hangs off the right and bottom sides.
- - Item hitbox skew:
- Y coordinates in Zelda are considered block-aligned when they end in D instead of 0, presumably because it adds to the
- illusion of depth when drawing objects on the tiled floors of dungeons. Unfortunately, this introduces some bugs and
- odd behavior throughout the game. Room drops (referenced in this code as treasures) always spawn on a Y coordinate
- ending in 0. If Link were to try to walk over the top of the item, this would prevent him from collecting it. To fix
- this, the game treats Link like he's 3 pixels lower when colliding him with items. However, this applies to all items,
- not just room drops, so this effectively shifts all items up 3 pixels, making them harder to pick up when enemies drop
- them.
- - Item/weapon collision:
- Items collide with the sword, rod, arrows, and boomerang by replacing Link's position with each weapon and testing for
- collision as long as the weapon is active. This means all weapons are tested with the same size hitbox as Link, which
- is smaller than the hitbox normally used for all of these weapons. This also means that weapons can collide with items
- even when they aren't allowed to collide with enemies, which allows Link to pick up items with his sword that are above
- him, regardless of the direction he is facing.
- - Link hitbox skew:
- Link is drawn 2 pixels lower on the overworld and in passageways, probably because his head would otherwise poke into
- the HUD when on the top row of the screen and to prevent him from hovering above the floor. However, this effect is
- only visual; he still collides as though this weren't done.
- - Link's sword beams, rod beams, and arrows:
- The centers for Link's sword and rod are chosen based on whether he's facing vertically or horizontally. This behaves
- as expected, particularly because Link can't move when using the weapons; if he could, the centers would change when he
- turns perpendicularly, which would move the hitbox. However, this same code is reused for Link's arrows and his sword
- and rod beams. Because Link can move when these weapons are active, the direction he's facing actually moves their
- hitboxes. Choosing which way to face can move your projectile sideways 2 pixels, causing it to hit an enemy it may have
- otherwise missed.
- - The magical rod:
- The magical rod's hitbox and positioning is actually the same as the sword's because it uses most of the same code, but
- it skips the sword's check to ensure that collision only happens when fully extended, which makes it collide at each
- position along the swing. The sword and rod stabs are actually programmed as a downward sword swing from over Link's
- head, but the game doesn't have an animation for this (though the angled magical sword was likely intended to be used
- here before the idea was abandoned). This is why the extra rod extends over Link's head when he's facing left, right,
- and down, but not up.
- ====Release Notes====
- v1.0:
- Initial release.
- ====Known issues====
- - It's inconsistent from object to object whether a collision dot or region will be shown when an object is not
- handling collision. Whether an object is checking for collision on any given frame is up to its own AI, so this would
- require special casing the various objects that disable collision (such as peahats when flying, or Ganon while dead).
- Some objects have this special casing, usually because they already required some special-case code to properly
- display collision information, or because it seemed worth the effort (such as considering whether a secret is quest-
- specific or requires an item that you may not have).
- - It's possible some objects that don't collide at all will show a collision dot because they haven't been specifically
- ignored.
- - Not every hitbox edge has been 100% tested, such as the back ends of the dodongo bomb-eating hitbox, so they may be
- wrong.
- - This code isn't very clean and there is code duplication.
- If you find an issue, please report it!
- ====Acknowledgments====
- Special thanks to:
- - fcoughlin, for the small script he sent me that drew rectangles around enemies. I don't really know Lua, so that got
- me started.
- - zewt, for his Zelda disassembly, which I used while adding support for gleeok heads. They're really complicated and I
- hadn't looked into them much on my own, so this sped up development of that feature significantly.
- ]]
- -- Draws a rectangle centered at (x,y) where the edges are w and h pixels away from the center.
- function DrawRectangle(x, y, w, h, color)
- gui.line(x-w, y-h, x+w, y-h, color);
- gui.line(x-w, y-h, x-w, y+h, color);
- gui.line(x+w, y-h, x+w, y+h, color);
- gui.line(x-w, y+h, x+w, y+h, color);
- end;
- function DrawDynamicObjectCenters()
- -- Check if the current screen has a restriction on whether its secrets work in this quest.
- local current_save_slot = memory.readbyte(0x16);
- local current_quest = memory.readbyte(0x62d + current_save_slot);
- local secret_quest_flag = bit.rshift(memory.readbyte(0x4cd), 6);
- local secret_enabled = true;
- if (secret_quest_flag ~= 0) then
- local quest_table = { 0, 0, 1, 0xad };
- if (quest_table[secret_quest_flag + 1] ~= current_quest) then
- secret_enabled = false;
- end;
- end;
- for i=0,10 do
- local id = memory.readbyte(0x350 + i);
- local x = memory.readbyte(0x71 + i) + 8;
- local y = memory.readbyte(0x85 + i) + 8;
- local properties = memory.readbyte(0x4c0 + i);
- -- Thin objects' centers are 4 pixels left of normal objects, which seems like a bug.
- if (bit.band(properties, 0x40) ~= 0) then
- x = x - 4;
- end;
- -- Draw the object's collision center.
- -- Inactive object; Rock spawner; Staircase spawner; Stepladder.
- if (id == 0 or id == 0x1f or id == 0x5e or id == 0x5f) then
- -- Pond fairy.
- elseif (id == 0x2f) then
- -- Specifically checks for Link within a region.
- local fairy_status = memory.readbyte(0xad + i);
- if (fairy_status == 0) then
- local x0 = 0x70 + 8;
- local x1 = 0x80 + 8;
- local y_ = 0xad + 8;
- gui.drawbox(x0-1, y_-1, x1+1, y_+1, "#ff8040");
- gui.drawline(x0, y_, x1, y_, "#000000");
- end;
- -- Gohma.
- elseif (id == 0x33 or id == 0x34) then
- -- Handles 5 different collisions.
- x = x - 0x10;
- for j=0,4 do
- local border_color = "#ffffff";
- local eye_state = memory.readbyte(0x46c + i);
- --[[ If the eye is vulnerable, color it differently. Gohma uses special collision code that allows it
- to reject weapons it isn't immune to. This even takes into account the direction of the weapon, so
- the arrow can't come from behind.
- ]]
- if (eye_state == 3 and (j == 2 or j == 3)) then
- border_color = "#00bfff";
- end;
- gui.drawbox(x-1, y-1, x+1, y+1, "#000000", border_color);
- x = x + 8;
- end;
- -- Zelda.
- elseif (id == 0x37) then
- -- Specifically checks for Link within a region.
- local x0 = 0x70 + 8;
- local x1 = 0x80 + 8;
- local y_ = 0x95 + 8;
- gui.drawbox(x0-1, y_-1, x1+1, y_+1, "#ff8040");
- gui.drawline(x0, y_, x1, y_, "#000000");
- -- Digdogger.
- elseif (id == 0x38 or id == 0x39) then
- -- Handles 4 different collisions.
- local x_offsets = { 0x00, 0x10, 0x00, 0xf0 }
- local y_offsets = { 0x00, 0x10, 0xf0, 0x10 }
- for j=0,3 do
- x = bit.band(x + x_offsets[j+1], 0xff);
- y = bit.band(y + y_offsets[j+1], 0xff);
- gui.drawbox(x-1, y-1, x+1, y+1, "#000000", "#ffffff");
- end;
- -- Enemy-dropped item.
- elseif (id == 0x60) then
- --[[ Draw a hitbox instead of a dot because Link and his weapons collide with this. Also fix up its Y
- position because of the adjustment for treasure spawns.
- ]]
- y = y - 3;
- DrawRectangle(x, y, 8, 8, "#ffc000");
- -- Dock.
- elseif (id == 0x61) then
- local has_raft = memory.readbyte(0x660);
- if (has_raft) then
- local current_screen = memory.readbyte(0xeb);
- if (current_screen == 0x55) then
- x = 0x80;
- else
- x = 0x60;
- end;
- y = 0x3d;
- gui.drawbox(x-1, y-1, x+1, y+1, "#000000", "#ffffff");
- y = 0x7d;
- gui.drawbox(x-1, y-1, x+1, y+1, "#000000", "#ffffff");
- end;
- -- Bombable cliff; Burnable tree.
- elseif (id == 0x63 or id == 0x64) then
- if (secret_enabled == true) then
- DrawRectangle(x, y, 15, 15, "#40ff40");
- end;
- -- Pushrock; Pushgrave; Pushblock.
- elseif (id == 0x62 or id == 0x65 or id == 0x68) then
- local block_status = memory.readbyte(0xad + i);
- local is_room_clear = memory.readbyte(0x34d);
- local has_power_bracelet = memory.readbyte(0x665);
- -- Unpushed blocks.
- if (block_status == 0 and
- (id ~= 0x68 or is_room_clear ~= 0) and
- (id ~= 0x62 or has_power_bracelet ~= 0) and
- (id == 0x68 or secret_enabled == true)) then
- -- Rocks and graves can only be pushed vertically.
- if (id == 0x68) then gui.drawbox(x-17, y-4, x+17, y-2, "#ff8040") end;
- gui.drawbox(x-1, y-20, x+1, y+14, "#ff8040");
- if (id == 0x68) then gui.drawline(x-16, y-3, x+16, y-3, "#000000") end;
- gui.drawline(x, y-19, x, y+13, "#000000");
- -- Moving blocks. When Link is in this region, he is not allowed to move.
- elseif (block_status == 1) then
- DrawRectangle(x, y-3, 15, 15, "#c0c0c0");
- end;
- -- Cave NPCs.
- elseif (id >= 0x6a) then
- -- Specifically checks for collision regions for active items.
- local npc_status = memory.readbyte(0xad + i);
- local has_note = memory.readbyte(0x666);
- local rupees_to_subtract = memory.readbyte(0x67e);
- if (rupees_to_subtract == 0 and npc_status == 2 and (id ~= 0x74 or has_note == 2)) then
- local shop_table = { 0x58, 0x78, 0x98 };
- for j=0,2 do
- local item_status = memory.readbyte(0x422 + j);
- if (bit.band(item_status, 0x3f) ~= 0x3f) then
- x = shop_table[j + 1] + 8;
- y = 0x98 + 8;
- gui.drawbox(x-1, y-6, x+1, y+6, "#ff8040");
- gui.drawline(x, y-5, x, y+5, "#000000");
- end;
- end;
- end;
- else
- -- Handle any object-specific positioning.
- -- Ganon.
- if (id == 0x3e) then
- x = x + 8;
- y = y + 8;
- end;
- -- Draw the center of the object.
- gui.drawbox(x-1, y-1, x+1, y+1, "#000000", "#ffffff");
- end;
- -- Handle any objects that run multiple collision points beyond the standard one.
- -- Dodongo.
- if (id == 0x31 or id == 0x32) then
- -- Dodongos have special collision regions for bombs.
- local dodongo_status = memory.readbyte(0xad + i);
- local face_direction = memory.readbyte(0x99 + i);
- if (dodongo_status == 0) then
- local cx = x;
- local cy = y;
- if (face_direction < 4) then
- cx = cx + 8;
- else
- --[[ The vertical hitbox is shifted down by 1 because carry differs between the vertical and
- horizontal paths for y centering.
- ]]
- cy = cy + 1;
- end;
- -- Bomb smoke.
- gui.drawbox(cx - 0xb, cy - 0xb, cx + 0xc, cy + 0xc, "#00000000", "#ff8080");
- -- Bomb.
- -- Right.
- if (face_direction == 1) then
- gui.drawbox(cx + 0x10, cy + 0x04, cx + 0x01, cy - 0x03, "#00000000", "#ff0040");
- -- Left.
- elseif (face_direction == 2) then
- gui.drawbox(cx - 0x00, cy + 0x04, cx - 0x0f, cy - 0x03, "#00000000", "#ff0040");
- -- Down.
- elseif (face_direction == 4) then
- gui.drawbox(cx + 0x08, cy + 0x10, cx - 0x07, cy + 0x01, "#00000000", "#ff0040");
- -- Up.
- elseif (face_direction == 8) then
- gui.drawbox(cx + 0x08, cy + 0x00, cx - 0x07, cy - 0x0f, "#00000000", "#ff0040");
- end;
- end;
- -- When moving horizontally, dodongos have 2 collidable sections.
- if (face_direction < 4) then
- x = x + 16;
- gui.drawbox(x-1, y-1, x+1, y+1, "#000000", "#ffffff");
- end;
- -- Gleeok.
- elseif (id >= 0x42 and id <= 0x45) then
- local head_x = { 0x438, 0x452, 0x46c, 0x395 };
- local head_y = { 0x445, 0x45f, 0x479, 0x3bd };
- local heads = id - 0x42;
- local dead_heads = memory.readbyte(0x511);
- for j=0,heads do
- if (bit.band(bit.rshift(dead_heads, j), 1) == 0) then
- x = memory.readbyte(head_x[j+1] + 4) + 8;
- y = memory.readbyte(head_y[j+1] + 4) + 8;
- gui.drawbox(x-1, y-1, x+1, y+1, "#000000", "#ffffff");
- end;
- end;
- -- Bomb upgrade NPC.
- elseif (id == 0x4f) then
- -- Collision region for the bomb upgrade.
- local npc_status = memory.readbyte(0xad + i);
- if (npc_status == 2) then
- x = 0x78 + 8;
- y = 0x98 + 8;
- gui.drawbox(x-1, y-6, x+1, y+6, "#ff8040");
- gui.drawline(x, y-5, x, y+5, "#000000");
- end;
- -- Money or life NPC.
- elseif (id == 0x51) then
- -- Collision regions for the payment options.
- local npc_status = memory.readbyte(0xad + i);
- if (npc_status == 2) then
- local shop_table = { 0x58, 0x98 };
- for j=0,1 do
- x = shop_table[j + 1] + 8;
- y = 0x98 + 8;
- gui.drawbox(x-1, y-6, x+1, y+6, "#ff8040");
- gui.drawline(x, y-5, x, y+5, "#000000");
- end;
- end;
- -- Pushrock; Pushgrave; Pushblock.
- elseif (id == 0x62 or id == 0x65 or id == 0x66 or id == 0x68) then
- local block_status = memory.readbyte(0xad + i);
- if (block_status == 1) then
- DrawRectangle(x, y-3, 15, 15, "#c0c0c0");
- end;
- end;
- end;
- end;
- function DrawTreasureCenter()
- local treasure_status = memory.readbyte(0xac + 0x13);
- if (treasure_status < 0x80) then
- local x = memory.readbyte(0x83) + 8;
- -- Adjust their height because of items' Link height fixup.
- local y = memory.readbyte(0x97) - 3 + 8;
- gui.drawbox(x-1, y-1, x+1, y+1, "#000000", "#ffffff");
- end;
- end;
- function DrawSwordHitbox(link_face_direction)
- local sword_status = memory.readbyte(0xac + 0x0d);
- local x = memory.readbyte(0x70 + 0x0d);
- local y = memory.readbyte(0x84 + 0x0d);
- if (sword_status == 0x02) then
- local w;
- local h;
- if (bit.band(link_face_direction, 0x0c) ~= 0) then
- x = x + 6;
- y = y + 8;
- w = 11;
- h = 15;
- else
- x = x + 8;
- y = y + 6;
- w = 15;
- h = 11;
- end;
- DrawRectangle(x, y, w, h, "#80ff80");
- end;
- if (sword_status ~= 0) then
- -- Draw a point for item collection.
- x = memory.readbyte(0x70 + 0x0d) + 8;
- y = memory.readbyte(0x84 + 0x0d) + 8;
- gui.drawbox(x-1, y-1, x+1, y+1, "#000000", "#ffc000");
- end;
- end;
- function DrawRodOrArrowHitbox(link_face_direction)
- local rodarrow_status = memory.readbyte(0xac + 0x12);
- -- Rod.
- if (rodarrow_status >= 0x30) then
- --[[ The rod and sword share most of the same code, but the sword has a check to ensure it only collides on
- status 2, while the rod enters the sword function after that check is done. This causes the rod to collide
- at every step of its animation, including an invisible portion of the swing over his head.
- ]]
- local x = memory.readbyte(0x70 + 0x12);
- local y = memory.readbyte(0x84 + 0x12);
- local w;
- local h;
- if (bit.band(link_face_direction, 0x0c) ~= 0) then
- x = x + 6;
- y = y + 8;
- w = 11;
- h = 15;
- else
- x = x + 8;
- y = y + 6;
- w = 15;
- h = 11;
- end;
- DrawRectangle(x, y, w, h, "#00bfff");
- -- Arrow.
- elseif (rodarrow_status < 0x20 and rodarrow_status ~= 0x00) then
- local x = memory.readbyte(0x70 + 0x12);
- local y = memory.readbyte(0x84 + 0x12);
- -- An active arrow's centering depends on which direction Link is currently facing.
- if (bit.band(link_face_direction, 0x0c) ~= 0) then
- x = x + 6;
- y = y + 8;
- else
- x = x + 8;
- y = y + 6;
- end;
- DrawRectangle(x, y, 10, 10, "#00bfff");
- end;
- if (rodarrow_status ~= 0) then
- -- Draw a point for item collection.
- x = memory.readbyte(0x70 + 0x12) + 8;
- y = memory.readbyte(0x84 + 0x12) + 8;
- gui.drawbox(x-1, y-1, x+1, y+1, "#000000", "#ffc000");
- end;
- end;
- function DrawBeamHitbox(link_face_direction)
- local beam_status = memory.readbyte(0xac + 0x0e);
- if (beam_status ~= 0 and bit.band(beam_status, 1) ~= 1) then
- local x = memory.readbyte(0x70 + 0x0e);
- local y = memory.readbyte(0x84 + 0x0e);
- -- An active beam's centering depends on which direction Link is currently facing.
- if (bit.band(link_face_direction, 0x0c) ~= 0) then
- x = x + 6;
- y = y + 8;
- else
- x = x + 8;
- y = y + 6;
- end;
- DrawRectangle(x, y, 11, 11, "#80ff80");
- end;
- end;
- function DrawBoomerangHitbox()
- local boomerang_status = memory.readbyte(0xac + 0x0f);
- if (boomerang_status < 0x80 and boomerang_status ~= 0) then
- local x = memory.readbyte(0x70 + 0x0f) + 4;
- local y = memory.readbyte(0x84 + 0x0f) + 8;
- DrawRectangle(x, y, 9, 9, "#D2691E");
- end;
- if (boomerang_status ~= 0) then
- -- Draw a point for item collection.
- x = memory.readbyte(0x70 + 0x0f) + 8;
- y = memory.readbyte(0x84 + 0x0f) + 8;
- gui.drawbox(x-1, y-1, x+1, y+1, "#000000", "#ffc000");
- end;
- end;
- function DrawFireOrBombHitboxes()
- for i=0,1 do
- local firebomb_status = memory.readbyte(0xac + 0x10 + i);
- -- Fire.
- if (firebomb_status >= 0x20) then
- local x = memory.readbyte(0x70 + 0x10 + i) + 8;
- local y = memory.readbyte(0x84 + 0x10 + i) + 8;
- DrawRectangle(x, y, 13, 13, "#ff8080");
- gui.drawbox(x-1, y-1, x+1, y+1, "#000000", "#40ff40");
- -- Exploding bomb.
- elseif (firebomb_status == 0x13) then
- local x = memory.readbyte(0x70 + 0x10 + i) + 8;
- local y = memory.readbyte(0x84 + 0x10 + i) + 8;
- DrawRectangle(x, y, 23, 23, "#ff8080");
- gui.drawbox(x-1, y-1, x+1, y+1, "#000000", "#40ff40");
- -- Bomb.
- elseif (i == 0 and firebomb_status ~= 0 and firebomb_status < 0x20) then
- -- For dodongos.
- local x = memory.readbyte(0x70 + 0x10 + i) + 8;
- local y = memory.readbyte(0x84 + 0x10 + i) + 8;
- if (firebomb_status == 0x12) then
- gui.drawbox(x-1, y-1, x+1, y+1, "#000000", "#ff0040");
- else
- gui.drawbox(x-1, y-1, x+1, y+1, "#000000", "#ff8080");
- end;
- end;
- end;
- end;
- function DrawLinkHitboxAndCenter()
- local link_status = memory.readbyte(0xac);
- if (bit.band(link_status, 0xc0) ~= 0x40) then
- local x = memory.readbyte(0x70) + 8;
- local y = memory.readbyte(0x84) + 8;
- DrawRectangle(x, y, 8, 8, "#ffffff");
- gui.drawbox(x-1, y-1, x+1, y+1, "#000000", "#ffffff");
- end;
- end;
- function DrawBombableDungeonWallHitboxes()
- local level = memory.readbyte(0x10);
- local mode = memory.readbyte(0x12);
- if (level ~= 0 and mode ~= 9) then
- local current_screen = memory.readbyte(0xeb);
- local vertical_doors = memory.readbyte(0x687e + current_screen);
- local horizontal_doors = memory.readbyte(0x68fe + current_screen);
- local opened_doors = memory.readbyte(0xee);
- -- Top.
- if (bit.rshift(vertical_doors, 5) == 0x04 and bit.band(opened_doors, 8) == 0) then
- DrawRectangle(0x78+8, 0x5d+8, 23, 23, "#40ff40");
- end;
- -- Bottom.
- if (bit.band(bit.rshift(vertical_doors, 2), 7) == 0x04 and bit.band(opened_doors, 4) == 0) then
- DrawRectangle(0x78+8, 0xbd+8, 23, 23, "#40ff40");
- end;
- -- Left.
- if (bit.rshift(horizontal_doors, 5) == 0x04 and bit.band(opened_doors, 2) == 0) then
- DrawRectangle(0x20+8, 0x8d+8, 23, 23, "#40ff40");
- end;
- -- Right.
- if (bit.band(bit.rshift(horizontal_doors, 2), 7) == 0x04 and bit.band(opened_doors, 1) == 0) then
- DrawRectangle(0xd0+8, 0x8d+8, 23, 23, "#40ff40");
- end;
- end;
- end;
- function DrawScreenBoundaries()
- local left = memory.readbyte(0x346 + 0) - 1;
- local right = memory.readbyte(0x346 + 1);
- local top = memory.readbyte(0x346 + 2) - 1;
- local bottom = memory.readbyte(0x346 + 3);
- -- Link and dynamic objects with ID ~= 0x5c (boomerang).
- gui.drawline(left, 0x3d, left, 0xef, "#80c0ff");
- gui.drawline(right, 0x3d, right, 0xef, "#80c0ff");
- gui.drawline(0x00, top, 0xff, top, "#80c0ff");
- gui.drawline(0x00, bottom, 0xff, bottom, "#80c0ff");
- -- Weapons.
- gui.drawline(left-11, 0x3d, left-11, 0xef, "#40c0ff");
- gui.drawline(right+12, 0x3d, right+12, 0xef, "#40c0ff");
- gui.drawline(0x00, top-15, 0xff, top-15, "#40c0ff");
- gui.drawline(0x00, bottom+18, 0xff, bottom+18, "#40c0ff");
- end;
- function DrawInputRestrictionBoundaries()
- -- B button.
- local bottom = 0xbe + 8;
- local top = 0x54 + 8 - 1;
- local right = 0xd1 + 8;
- local left = 0x1f + 8 - 1;
- gui.drawline(left, 0x3d, left, 0xef, "#ff0080c0");
- gui.drawline(right, 0x3d, right, 0xef, "#ff0080c0");
- gui.drawline(0x00, top, 0xff, top, "#ff0080c0");
- gui.drawline(0x00, bottom, 0xff, bottom, "#ff0080c0");
- -- A button.
- local level = memory.readbyte(0x10);
- if (level == 0) then
- -- Overworld.
- bottom = 0xd6 + 8;
- top = 0x45 + 8 - 1;
- right = 0xe9 + 8;
- left = 0x07 + 8 - 1;
- else
- -- Dungeons.
- bottom = 0xc6 + 8;
- top = 0x55 + 8 - 1;
- right = 0xd9 + 8;
- left = 0x17 + 8 - 1;
- end;
- gui.drawline(left, 0x3d, left, 0xef, "#80ff00c0");
- gui.drawline(right, 0x3d, right, 0xef, "#80ff00c0");
- gui.drawline(0x00, bottom, 0xff, bottom, "#80ff00c0");
- gui.drawline(0x00, top, 0xff, top, "#80ff00c0");
- end;
- function DrawNpcBarrier()
- local mode = memory.readbyte(0x12);
- local object1 = memory.readbyte(0x350);
- if (mode == 0xb or
- mode == 0xc or
- object1 == 0x36 or
- (object1 >= 0x4b and object1 < 0x53)) then
- gui.drawline(0x00, 0x8d+8, 0xff, 0x8d+8, "#ffff00");
- end;
- end;
- function DrawObjectActualPositions()
- -- Link.
- local x = memory.readbyte(0x70);
- local y = memory.readbyte(0x84);
- gui.drawbox(x-1, y-1, x+1, y+1, "#000000", "#80c0ff");
- -- Dynamic objects.
- for i=0,10 do
- --[[ This intentionally avoids special casing most objects because almost none of that is relevant for screen
- boundaries, which are the main reason for this function.
- ]]
- local id = memory.readbyte(0x350 + i);
- x = memory.readbyte(0x71 + i);
- y = memory.readbyte(0x85 + i);
- -- Digdogger.
- if (id == 0x38 or id == 0x39) then
- -- Has 4 different positions for boundary collision.
- local x_offsets = { 0x00, 0x10, 0x00, 0xf0 }
- local y_offsets = { 0x00, 0x10, 0xf0, 0x10 }
- for j=0,3 do
- x = bit.band(x + x_offsets[j+1], 0xff);
- y = bit.band(y + y_offsets[j+1], 0xff);
- gui.drawbox(x-1, y-1, x+1, y+1, "#000000", "#80c0ff");
- end;
- -- Standard objects.
- elseif (id ~= 0) then
- gui.drawbox(x-1, y-1, x+1, y+1, "#000000", "#80c0ff");
- end;
- end;
- -- Weapons.
- for i=13,18 do
- local status = memory.readbyte(0xac + i);
- if (status ~= 0) then
- x = memory.readbyte(0x70 + i);
- y = memory.readbyte(0x84 + i);
- gui.drawbox(x-1, y-1, x+1, y+1, "#000000", "#40c0ff");
- end;
- end;
- -- Treasures.
- if (memory.readbyte(0xac + 0x13) < 0x80) then
- x = memory.readbyte(0x70 + 0x13);
- y = memory.readbyte(0x84 + 0x13);
- gui.drawbox(x-1, y-1, x+1, y+1, "#000000", "#80c0ff");
- end;
- end;
- function HandleDrawing()
- local mode = memory.readbyte(0x12);
- local task = memory.readbyte(0x11);
- local start_menu_status = memory.readbyte(0xe1);
- local link_face_direction = memory.readbyte(0x98);
- if (start_menu_status == 0 and
- task ~= 0 and
- (mode == 5 or (mode >= 0x9 and mode <= 0xc))) then
- -- Draw each collision element. These can be individually commented out to reduce screen clutter.
- DrawInputRestrictionBoundaries();
- DrawNpcBarrier();
- DrawBombableDungeonWallHitboxes();
- --[[ These functions are commented out by default because they add significantly to the amount of onscreen
- clutter and aren't as useful as the other tools. Screen boundaries operate on object positions rather than
- object centers. Other boundaries and some objects collide with Link in this same way, but because they
- only collide with Link, the locations of these collision areas have been shifted in this script to match
- up with Link's collision center rather than his real position. Because screen boundaries interact with
- more than just Link, this cannot be done here because the centers for standard objects, thin objects,
- items, and weapons all differ. As a result, it requires that actual positions be drawn in order to be
- accurate.
- ]]
- --DrawScreenBoundaries();
- --DrawObjectActualPositions();
- DrawDynamicObjectCenters();
- DrawTreasureCenter();
- DrawRodOrArrowHitbox(link_face_direction);
- DrawSwordHitbox(link_face_direction);
- DrawBeamHitbox(link_face_direction);
- DrawBoomerangHitbox();
- DrawFireOrBombHitboxes();
- DrawLinkHitboxAndCenter();
- end;
- end;
- --[[ Drawing to the screen is delayed by 1 frame, by default. This is normally fine because Zelda also delays drawing
- by 1 frame. The script's delay can be removed by uncommenting this line, and commenting out the Main loop below.
- ]]
- --gui.register(HandleDrawing);
- -- Main loop.
- while (true) do
- HandleDrawing();
- emu.frameadvance();
- end;
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement