Advertisement
Guest User

Zelda Hitbox Visualizer v1.2.lua

a guest
Mar 20th, 2018
503
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 41.32 KB | None | 0 0
  1. --[[
  2. Zelda Hitbox Visualizer v1.2
  3.  
  4. (Tested in FCEUX 2.2.3 with The Legend of Zelda (U) (PRG0))
  5.  
  6.  
  7. ====Instructions====
  8. Using FCEUX, load your Legend of Zelda ROM.
  9. Select "File -> Lua -> New Lua Script Window ..."
  10. In the Lua script window, select "Browse...", load this script file, and select Run.
  11.  
  12.  
  13. ====Description====
  14. This script visualizes collision information for The Legend of Zelda as dots and regions onscreen. Collision in Zelda
  15. doesn't explicitly work on hitboxes; instead, it checks for X and Y proximity between two points. When colliding two
  16. objects, the game finds the center of each object and does a proximity check using X and Y thresholds specific to the
  17. type of collision. To visualize this, this script displays one of the points as a dot and the other as a proximity
  18. rectangle centered at the other point. If the center of the dot intersects this rectangle or its border, the two
  19. objects have collided.
  20.  
  21. Because Link and his weapons use different thresholds, enemies are displayed as dots while Link and his weapons are
  22. rectangles. Link and some weapons have a dot in addition to rectangles, specifically for collision with special
  23. boundaries, which display as lines, and a few objects that use special threshold sizes or collision rules, such as push
  24. blocks, burnable trees, and dropped items, which display as rectangles. Dots and rectangles that are one pixel wide are
  25. given a colored border to increase visibility, but these borders do not represent collidable regions. The borders of
  26. standard rectangles, however, are part of their collidable region.
  27.  
  28. This script is able to visualize effectively all non-background collision. It supports Link, enemies, weapons, items,
  29. input restriction boundaries, NPC barriers, and interactive background elements, such as docks, pushable blocks,
  30. burnable trees, and bombable walls on both the overworld and in dungeons. It also supports screen boundaries, used to
  31. prevent objects and weapons from going too far, but this is disabled by default for clutter reasons. Visualizing
  32. background collision, door collision, and the movement grid is beyond the scope of this project. This script was
  33. written based on the collision code in the ROM rather than experimentation, and has been somewhat tested using frame-
  34. by-frame play to verify collisions occur when expected. It's possible there may be bugs in the implementation of some
  35. features, or that some implementations may be based on an incomplete or incorrect understanding of the game's code.
  36.  
  37. Individual collision elements can be disabled by commenting out the relevant function calls in the HandleDrawing
  38. function very near the bottom of this file. This can be done by placing -- on the line anywhere before the function
  39. call for the feature you want to disable. Screen boundary visualization can be enabled by removing the -- from the
  40. front of its two relevant functions (DrawScreenBoundaries and DrawObjectActualPositions).
  41.  
  42. Drawing with Lua is normally delayed by 1 frame. This is intentionally left in place for this script because Zelda also
  43. delays its drawing for 1 frame, so the two are synced. The script's 1 frame delay can be removed by uncommenting the
  44. gui.register(HandleDrawing) line at the end of this file and commenting out the Main loop.
  45.  
  46.  
  47. ====FAQ====
  48. Q: What are those red and green lines?
  49. A: These are input restriction boundaries, which indicate where you're not allowed to use your weapons. The red line
  50. 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
  51. are disabled, though the direction varies depending on whether your B button is already disabled by the red boundary;
  52. the game tests the green boundary for the direction you're facing, or the opposite boundary if your B button is
  53. disabled. In dungeons, the green line on the top is actually below the red line, which means you can use your sword on
  54. that line while facing up, but not down. The green line also completely disables perpendicular movement in dungeons;
  55. this differs from doors, which convert perpendicular movement into forward movement.
  56.  
  57. Q: What is that yellow dot when I stab?
  58. A: That's your weapon's collision center for picking up items dropped by enemies. Many of these special-purpose dots
  59. are color-coded for what they interact with. For example, red for bombs and dodongos, green for candle fire and
  60. burnable trees, and blue for gohma's eye and arrows. The color palette is limited and needs to be visible against the
  61. backgrounds in the game, though, so these colors are hints rather than hard-and-fast rules.
  62.  
  63. Q: Enemies have tiny hitboxes and Link and his weapons have big hitboxes, right?
  64. A: Not exactly. As mentioned in the Description section, Zelda doesn't use hitboxes; it instead checks if two points
  65. are close enough (ie within separate X and Y thresholds). Collision is really asking if a rectangle of some specified
  66. size can be placed such that it contains both points; the hitbox isn't tied to either point. This could also be
  67. visualized as rectangles around the enemies, and dots for Link and his weapons. However, this would require drawing all
  68. differently-sized relevant hitboxes around the enemies, because they are involved in collisions of multiple different
  69. sizes. Keeping the hitboxes around player-controlled objects allows the script to display more information without it
  70. being too cluttered.
  71.  
  72. Q: Can this script do anything else?
  73. A: It can also display the screen boundaries that prevent enemies and weapons from getting too close to the edge of the
  74. screen. In dungeons, this is what creates the outer wall that Link can't get through. Because of the way the boundaries
  75. interact with objects, the information can't be cleanly displayed at the same as everything else without making things
  76. feel very cluttered, so it's disabled by default. The Description section explains how to enable this.
  77.  
  78. Q: What versions of Zelda does this work on?
  79. A: This was created using the US PRG0 version and will definitely work there. It's likely that it also works fine on
  80. PRG1 and the Japanese cartridge release, and those versions don't appear to have any problems when used, but they
  81. haven't been thoroughly tested. Similarly, hacks of these ROMs are likely compatible, so long as they don't reorder
  82. variables in RAM or change hitbox sizes or positions. Hacks that don't substantially modify code, such as Randomizer,
  83. will likely meet these criteria.
  84.  
  85.  
  86. ====Observations====
  87. - Enemy sizes:
  88. Because this isn't using typical hitboxes, most enemies are effectively the exact same size. Enemies that you might
  89. expect to be smaller, such as gels, boomerangs, and fireballs, are just as big as enemies like stalfos, goriyas, and
  90. even Ganon.
  91.  
  92. Some enemies, such as dodongos, digdoggers, and gohmas, are actually bigger than normal. This is done by separately
  93. handling collision at multiple positions within the object.
  94.  
  95. - Enemy centers:
  96. Most enemy graphics are 16x16 pixels, but some are 8x16. The position of an object is the top left of its graphics, and
  97. for most enemies, the center of the object is chosen by adding 8 to the X and Y components (putting it just down and
  98. right of the center). For many of these thin objects, though, a flag is used that makes the game only add 4 to X
  99. instead of 8. This skews the object leftward, which, for most objects, throws it off center. Gels and most projectiles
  100. illustrate this issue well, and this is why Link won't be hit by vertically-moving projectiles when standing a half-
  101. block to the right. This flag does work properly with fireballs, though, so it's not exclusively wrong.
  102.  
  103. - Enemy hitbox skew:
  104. Because collision is looking for a range around a center point, all hitboxes have odd dimensions. Collision against
  105. Link is done on a 17x17 range, but because tiles are 8x16 pixels, object graphics tend to be 16x16. The extra pixel of
  106. collision range hangs off the right and bottom sides. This allows Link and enemies to graze each other when passing on
  107. neighboring parallel gridlines.
  108.  
  109. - Item hitbox skew:
  110. Y coordinates in Zelda are considered block-aligned when they end in D instead of 0, presumably because it adds to the
  111. illusion of depth when drawing objects on the tiled floors of dungeons. Unfortunately, this introduces some bugs and
  112. odd behavior throughout the game. Room drops (referenced in this code as treasures) always spawn on a Y coordinate
  113. 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
  114. this, the game treats Link like he's 3 pixels lower when colliding him with items. However, this applies to all items,
  115. not just room drops, so this effectively shifts all items up 3 pixels, making them harder to pick up when enemies drop
  116. them.
  117.  
  118. - Item/weapon collision:
  119. Items collide with the sword, rod, arrows, and boomerang by replacing Link's position with each weapon and testing for
  120. collision as long as the weapon is active. This means all weapons are tested with the same size hitbox as Link, which
  121. is smaller than the hitbox normally used for all of these weapons. This also means that weapons can collide with items
  122. even when they aren't allowed to collide with enemies, which allows Link to pick up items with his sword that are above
  123. him, regardless of the direction he is facing.
  124.  
  125. - Link hitbox skew:
  126. Link is drawn 2 pixels lower on the overworld and in passageways, probably because his head would otherwise poke into
  127. the HUD when on the top row of the screen and to prevent him from hovering above the floor. However, this effect is
  128. only visual; he still collides as though this weren't done.
  129.  
  130. - Link's sword beams, rod beams, and arrows:
  131. The centers for Link's sword and rod are chosen based on whether he's facing vertically or horizontally. This behaves
  132. as expected, particularly because Link can't move when using the weapons; if he could, the centers would change when he
  133. turns perpendicularly, which would move the hitbox. However, this same code is reused for Link's arrows and his sword
  134. and rod beams. Because Link can move when these weapons are active, the direction he's facing actually moves their
  135. hitboxes. Choosing which way to face can move your projectile sideways 2 pixels, causing it to hit an enemy it may have
  136. otherwise missed.
  137.  
  138. - The magical rod:
  139. The magical rod's hitbox and positioning is actually the same as the sword's because it uses most of the same code, but
  140. it skips the sword's check to ensure that collision only happens when fully extended, which makes it collide at each
  141. position along the swing. The sword and rod stabs are actually programmed as a downward sword swing from over Link's
  142. head, but the game doesn't have an animation for this (though the angled magical sword was likely intended to be used
  143. here before the idea was abandoned). This is why the extra rod extends over Link's head when he's facing left, right,
  144. and down, but not up.
  145.  
  146. - Fire and bomb slots:
  147. Fire and bomb weapons share two object slots rather than just one. When Link uses one of these, it goes into the first
  148. unused slot. There are some collision bugs depending on which slot these are in:
  149.  
  150. Dodongos only check collision against bombs and smoke in the first slot; the second slot is completely ignored. This
  151. means that if you miss with your bomb, you should wait for it to fully clear before placing another or it will be
  152. wasted.
  153.  
  154. Burnable trees check collision against the first active slot containing fire. If you have fire in the second slot, it
  155. will be ignored if there is fire in the first. This is normally fine because the fire in the first slot is usually
  156. older, so it will despawn before the second fire's timer hits the value necessary for the tree to burn. However, if the
  157. first flame is younger because it was created after the second flame, the second flame will be ignored and can't burn
  158. the tree. Trees also won't burn if you pause on the frame before the timer hits the right value because the timers run
  159. on the frame that you pause, but object AI does not.
  160.  
  161.  
  162. ====Release Notes====
  163. v1.2:
  164. - The hitbox for the bomb upgrade has been limited to levels 1, 2, 5, and 7.
  165. - Bombable dungeon walls now flicker red on the one frame they actually test for collision. Since both bombs can't be
  166. exploding at once, this isn't color-coded to match the relevant bomb (unlike burnable trees and flames).
  167. - Bombable dungeon wall hitboxes now disappear when any door is moving, since a moving door prevents the walls from
  168. being blown open. This is particularly relevant when the bomb kills the last enemy in a room with an opening shutter
  169. door, as the bomb would have collided with the wall, but the shutter prevents it.
  170. - Armos and ghini ghosts now correctly show collision only on every other frame when spawning.
  171. - Improved the stepladder hitbox to show a center point that Link can't walk past if the destination tile is solid.
  172. - Updated the observation on enemy hitbox skew.
  173.  
  174. v1.1:
  175. - Added a "Fire and bomb slots" section to Observations.
  176. - Updated the FAQ to explain that most hacks (Randomizer, in particular) should be compatible with this script.
  177. - Updated the FAQ to explain that the green input restriction line also disables perpendicular movement in dungeons.
  178. - Changed the color of moving blocks' hitbox to be more visible.
  179. - Removed duplicate code that was drawing moving blocks' hitbox a second time.
  180. - Burnable trees now flicker red when checking collision with the first flame object, and blue with the second. This
  181. makes it possible to tell when they skip collision checks (if a second flame is ignored in favor of a third, or if
  182. the game is paused on the frame before collision would be checked).
  183. - Updated the collision dot for the second flame object so it changes color when it will be ignored by a burnable tree.
  184. - Updated enemy-dropped items to only show their hitbox only when they're actually collectable.
  185. - Added a stepladder hitbox.
  186.  
  187. v1.0:
  188. Initial release.
  189.  
  190.  
  191. ====Known issues====
  192. - It's inconsistent from object to object whether a collision dot or region will be shown when an object is not
  193. handling collision. Whether an object is checking for collision on any given frame is up to its own AI, so this would
  194. require special casing the various objects that disable collision (such as peahats when flying, or Ganon while dead).
  195. Some objects have this special casing, usually because they already required some special-case code to properly
  196. display collision information, or because it seemed worth the effort (such as considering whether a secret is quest-
  197. specific or requires an item that you may not have).
  198. - It's possible some objects that don't collide at all will show a collision dot because they haven't been specifically
  199. ignored.
  200. - Not every hitbox edge has been 100% tested, such as the back ends of the dodongo bomb-eating hitbox, so they may be
  201. wrong.
  202. - This code isn't very clean and there is code duplication.
  203.  
  204. If you find an issue, please report it!
  205.  
  206.  
  207. ====Acknowledgments====
  208. Special thanks to:
  209. - fcoughlin, for the small script he sent me that drew rectangles around enemies. I don't really know Lua, so that got
  210. me started.
  211. - zewt, for his Zelda disassembly, which I used while adding support for gleeok heads. They're really complicated and I
  212. hadn't looked into them much on my own, so this sped up development of that feature significantly.
  213.  
  214. ]]
  215.  
  216.  
  217. -- Draws a rectangle centered at (x,y) where the edges are w and h pixels away from the center.
  218. function DrawRectangle(x, y, w, h, color)
  219. gui.line(x-w, y-h, x+w, y-h, color);
  220. gui.line(x-w, y-h, x-w, y+h, color);
  221. gui.line(x+w, y-h, x+w, y+h, color);
  222. gui.line(x-w, y+h, x+w, y+h, color);
  223. end;
  224.  
  225.  
  226. function DrawDynamicObjectCenters()
  227. -- Check if the current screen has a restriction on whether its secrets work in this quest.
  228. local current_save_slot = memory.readbyte(0x16);
  229. local current_quest = memory.readbyte(0x62d + current_save_slot);
  230. local secret_quest_flag = bit.rshift(memory.readbyte(0x4cd), 6);
  231. local secret_enabled = true;
  232. if (secret_quest_flag ~= 0) then
  233. local quest_table = { 0, 0, 1, 0xad };
  234. if (quest_table[secret_quest_flag + 1] ~= current_quest) then
  235. secret_enabled = false;
  236. end;
  237. end;
  238.  
  239. for i=0,10 do
  240. local id = memory.readbyte(0x350 + i);
  241. local x = memory.readbyte(0x71 + i) + 8;
  242. local y = memory.readbyte(0x85 + i) + 8;
  243. local properties = memory.readbyte(0x4c0 + i);
  244.  
  245. -- Thin objects' centers are 4 pixels left of normal objects, which seems like a bug.
  246. if (bit.band(properties, 0x40) ~= 0) then
  247. x = x - 4;
  248. end;
  249.  
  250. -- Draw the object's collision center.
  251. -- Inactive object; Rock spawner; Staircase spawner.
  252. if (id == 0 or id == 0x1f or id == 0x5e) then
  253.  
  254. -- Armos; Ghini ghost.
  255. elseif (id == 0x1e or id == 0x22) then
  256. local object_timer = memory.readbyte(0x29 + i);
  257. local needs_init = memory.readbyte(0x493 + i);
  258. if (needs_init == 0 or bit.band(object_timer, 1) == 0) then
  259. gui.drawbox(x-1, y-1, x+1, y+1, "#000000", "#ffffff");
  260. end;
  261.  
  262.  
  263. -- Pond fairy.
  264. elseif (id == 0x2f) then
  265. -- Specifically checks for Link within a region.
  266. local fairy_status = memory.readbyte(0xad + i);
  267. if (fairy_status == 0) then
  268. local x0 = 0x70 + 8;
  269. local x1 = 0x80 + 8;
  270. local y_ = 0xad + 8;
  271. gui.drawbox(x0-1, y_-1, x1+1, y_+1, "#ff8040");
  272. gui.drawline(x0, y_, x1, y_, "#000000");
  273. end;
  274.  
  275. -- Gohma.
  276. elseif (id == 0x33 or id == 0x34) then
  277. -- Handles 5 different collisions.
  278. x = x - 0x10;
  279. for j=0,4 do
  280. local border_color = "#ffffff";
  281. local eye_state = memory.readbyte(0x46c + i);
  282. --[[ If the eye is vulnerable, color it differently. Gohma uses special collision code that allows it
  283. to reject weapons it isn't immune to. This even takes into account the direction of the weapon, so
  284. the arrow can't come from behind.
  285. ]]
  286. if (eye_state == 3 and (j == 2 or j == 3)) then
  287. border_color = "#00bfff";
  288. end;
  289. gui.drawbox(x-1, y-1, x+1, y+1, "#000000", border_color);
  290.  
  291. x = x + 8;
  292. end;
  293.  
  294. -- Zelda.
  295. elseif (id == 0x37) then
  296. -- Specifically checks for Link within a region.
  297. local x0 = 0x70 + 8;
  298. local x1 = 0x80 + 8;
  299. local y_ = 0x95 + 8;
  300. gui.drawbox(x0-1, y_-1, x1+1, y_+1, "#ff8040");
  301. gui.drawline(x0, y_, x1, y_, "#000000");
  302.  
  303. -- Digdogger.
  304. elseif (id == 0x38 or id == 0x39) then
  305. -- Handles 4 different collisions.
  306. local x_offsets = { 0x00, 0x10, 0x00, 0xf0 }
  307. local y_offsets = { 0x00, 0x10, 0xf0, 0x10 }
  308. for j=0,3 do
  309. x = bit.band(x + x_offsets[j+1], 0xff);
  310. y = bit.band(y + y_offsets[j+1], 0xff);
  311.  
  312. gui.drawbox(x-1, y-1, x+1, y+1, "#000000", "#ffffff");
  313. end;
  314.  
  315. -- Stepladder.
  316. elseif (id == 0x5f) then
  317. -- Specifically checks for Link within a region.
  318. DrawRectangle(x, y-3, 15, 15, "#ff8040");
  319.  
  320. -- Shows the center point that Link can't walk past if he's not walking onto a nonsolid tile.
  321. gui.drawbox(x-1, y-4, x+1, y-2, "#ff8040", "#000000");
  322.  
  323. -- Enemy-dropped item.
  324. elseif (id == 0x60) then
  325. --[[ Draw a hitbox instead of a dot because Link and his weapons collide with this. Also fix up its Y
  326. position because of the adjustment for treasure spawns.
  327. ]]
  328. local object_subpos = memory.readbyte(0x3a9 + i);
  329. if (object_subpos < 0xf0) then
  330. y = y - 3;
  331. DrawRectangle(x, y, 8, 8, "#ffc000");
  332. end;
  333.  
  334. -- Dock.
  335. elseif (id == 0x61) then
  336. local has_raft = memory.readbyte(0x660);
  337. if (has_raft) then
  338. local current_screen = memory.readbyte(0xeb);
  339. if (current_screen == 0x55) then
  340. x = 0x80;
  341. else
  342. x = 0x60;
  343. end;
  344.  
  345. y = 0x3d;
  346. gui.drawbox(x-1, y-1, x+1, y+1, "#000000", "#ffffff");
  347.  
  348. y = 0x7d;
  349. gui.drawbox(x-1, y-1, x+1, y+1, "#000000", "#ffffff");
  350. end;
  351.  
  352. -- Bombable cliff.
  353. elseif (id == 0x63) then
  354. if (secret_enabled == true) then
  355. DrawRectangle(x, y, 15, 15, "#40ff40");
  356. end;
  357.  
  358. -- Burnable tree.
  359. elseif (id == 0x64) then
  360. if (secret_enabled == true) then
  361. local color = "#40ff40";
  362. local fire1_status = memory.readbyte(0xac + 0x10);
  363. local fire2_status = memory.readbyte(0xac + 0x11);
  364.  
  365. if (fire1_status == 0x22) then
  366. local fire1_timer = memory.readbyte(0x28 + 0x10);
  367. if (fire1_timer == 1) then
  368. color = "#ff4040";
  369. end;
  370. elseif (fire2_status == 0x22) then
  371. local fire2_timer = memory.readbyte(0x28 + 0x11);
  372. if (fire2_timer == 1) then
  373. color = "#4040ff";
  374. end;
  375. end;
  376.  
  377. DrawRectangle(x, y, 15, 15, color);
  378. end;
  379.  
  380. -- Pushrock; Pushgrave; Pushblock.
  381. elseif (id == 0x62 or id == 0x65 or id == 0x68) then
  382. local block_status = memory.readbyte(0xad + i);
  383. local is_room_clear = memory.readbyte(0x34d);
  384. local has_power_bracelet = memory.readbyte(0x665);
  385.  
  386. -- Unpushed blocks.
  387. if (block_status == 0 and
  388. (id ~= 0x68 or is_room_clear ~= 0) and
  389. (id ~= 0x62 or has_power_bracelet ~= 0) and
  390. (id == 0x68 or secret_enabled == true)) then
  391. -- Rocks and graves can only be pushed vertically.
  392. if (id == 0x68) then gui.drawbox(x-17, y-4, x+17, y-2, "#ff8040") end;
  393. gui.drawbox(x-1, y-20, x+1, y+14, "#ff8040");
  394. if (id == 0x68) then gui.drawline(x-16, y-3, x+16, y-3, "#000000") end;
  395. gui.drawline(x, y-19, x, y+13, "#000000");
  396.  
  397. -- Moving blocks. When Link is in this region, he is not allowed to move.
  398. elseif (block_status == 1) then
  399. DrawRectangle(x, y-3, 15, 15, "#ff8040");
  400. end;
  401.  
  402. -- Cave NPCs.
  403. elseif (id >= 0x6a) then
  404. -- Specifically checks for collision regions for active items.
  405. local npc_status = memory.readbyte(0xad + i);
  406. local has_note = memory.readbyte(0x666);
  407. local rupees_to_subtract = memory.readbyte(0x67e);
  408. if (rupees_to_subtract == 0 and npc_status == 2 and (id ~= 0x74 or has_note == 2)) then
  409. local shop_table = { 0x58, 0x78, 0x98 };
  410. for j=0,2 do
  411. local item_status = memory.readbyte(0x422 + j);
  412. if (bit.band(item_status, 0x3f) ~= 0x3f) then
  413. x = shop_table[j + 1] + 8;
  414. y = 0x98 + 8;
  415. gui.drawbox(x-1, y-6, x+1, y+6, "#ff8040");
  416. gui.drawline(x, y-5, x, y+5, "#000000");
  417. end;
  418. end;
  419. end;
  420.  
  421. else
  422. -- Handle any object-specific positioning.
  423. -- Ganon.
  424. if (id == 0x3e) then
  425. x = x + 8;
  426. y = y + 8;
  427. end;
  428.  
  429. -- Draw the center of the object.
  430. gui.drawbox(x-1, y-1, x+1, y+1, "#000000", "#ffffff");
  431. end;
  432.  
  433.  
  434. -- Handle any objects that run multiple collision points beyond the standard one.
  435. -- Dodongo.
  436. if (id == 0x31 or id == 0x32) then
  437. -- Dodongos have special collision regions for bombs.
  438. local dodongo_status = memory.readbyte(0xad + i);
  439. local face_direction = memory.readbyte(0x99 + i);
  440. if (dodongo_status == 0) then
  441. local cx = x;
  442. local cy = y;
  443. if (face_direction < 4) then
  444. cx = cx + 8;
  445. else
  446. --[[ The vertical hitbox is shifted down by 1 because carry differs between the vertical and
  447. horizontal paths for y centering.
  448. ]]
  449. cy = cy + 1;
  450. end;
  451.  
  452. -- Bomb smoke.
  453. gui.drawbox(cx - 0xb, cy - 0xb, cx + 0xc, cy + 0xc, "#00000000", "#ff8080");
  454.  
  455. -- Bomb.
  456. -- Right.
  457. if (face_direction == 1) then
  458. gui.drawbox(cx + 0x10, cy + 0x04, cx + 0x01, cy - 0x03, "#00000000", "#ff0040");
  459. -- Left.
  460. elseif (face_direction == 2) then
  461. gui.drawbox(cx - 0x00, cy + 0x04, cx - 0x0f, cy - 0x03, "#00000000", "#ff0040");
  462. -- Down.
  463. elseif (face_direction == 4) then
  464. gui.drawbox(cx + 0x08, cy + 0x10, cx - 0x07, cy + 0x01, "#00000000", "#ff0040");
  465. -- Up.
  466. elseif (face_direction == 8) then
  467. gui.drawbox(cx + 0x08, cy + 0x00, cx - 0x07, cy - 0x0f, "#00000000", "#ff0040");
  468. end;
  469. end;
  470.  
  471. -- When moving horizontally, dodongos have 2 collidable sections.
  472. if (face_direction < 4) then
  473. x = x + 16;
  474. gui.drawbox(x-1, y-1, x+1, y+1, "#000000", "#ffffff");
  475. end;
  476.  
  477. -- Gleeok.
  478. elseif (id >= 0x42 and id <= 0x45) then
  479. local head_x = { 0x438, 0x452, 0x46c, 0x395 };
  480. local head_y = { 0x445, 0x45f, 0x479, 0x3bd };
  481. local heads = id - 0x42;
  482. local dead_heads = memory.readbyte(0x511);
  483.  
  484. for j=0,heads do
  485. if (bit.band(bit.rshift(dead_heads, j), 1) == 0) then
  486. x = memory.readbyte(head_x[j+1] + 4) + 8;
  487. y = memory.readbyte(head_y[j+1] + 4) + 8;
  488.  
  489. gui.drawbox(x-1, y-1, x+1, y+1, "#000000", "#ffffff");
  490. end;
  491. end;
  492.  
  493. -- Bomb upgrade NPC.
  494. elseif (id == 0x4f and (level == 1 or level == 2 or level == 5 or level == 7)) then
  495. -- Collision region for the bomb upgrade.
  496. local npc_status = memory.readbyte(0xad + i);
  497. if (npc_status == 2) then
  498. x = 0x78 + 8;
  499. y = 0x98 + 8;
  500. gui.drawbox(x-1, y-6, x+1, y+6, "#ff8040");
  501. gui.drawline(x, y-5, x, y+5, "#000000");
  502. end;
  503.  
  504. -- Money or life NPC.
  505. elseif (id == 0x51) then
  506. -- Collision regions for the payment options.
  507. local npc_status = memory.readbyte(0xad + i);
  508. if (npc_status == 2) then
  509. local shop_table = { 0x58, 0x98 };
  510. for j=0,1 do
  511. x = shop_table[j + 1] + 8;
  512. y = 0x98 + 8;
  513. gui.drawbox(x-1, y-6, x+1, y+6, "#ff8040");
  514. gui.drawline(x, y-5, x, y+5, "#000000");
  515. end;
  516. end;
  517. end;
  518. end;
  519. end;
  520.  
  521.  
  522. function DrawTreasureCenter()
  523. local treasure_status = memory.readbyte(0xac + 0x13);
  524. if (treasure_status < 0x80) then
  525. local x = memory.readbyte(0x83) + 8;
  526. -- Adjust their height because of items' Link height fixup.
  527. local y = memory.readbyte(0x97) - 3 + 8;
  528. gui.drawbox(x-1, y-1, x+1, y+1, "#000000", "#ffffff");
  529. end;
  530. end;
  531.  
  532.  
  533. function DrawSwordHitbox(link_face_direction)
  534. local sword_status = memory.readbyte(0xac + 0x0d);
  535. local x = memory.readbyte(0x70 + 0x0d);
  536. local y = memory.readbyte(0x84 + 0x0d);
  537.  
  538. if (sword_status == 0x02) then
  539. local w;
  540. local h;
  541.  
  542. if (bit.band(link_face_direction, 0x0c) ~= 0) then
  543. x = x + 6;
  544. y = y + 8;
  545. w = 11;
  546. h = 15;
  547. else
  548. x = x + 8;
  549. y = y + 6;
  550. w = 15;
  551. h = 11;
  552. end;
  553.  
  554. DrawRectangle(x, y, w, h, "#80ff80");
  555. end;
  556.  
  557. if (sword_status ~= 0) then
  558. -- Draw a point for item collection.
  559. x = memory.readbyte(0x70 + 0x0d) + 8;
  560. y = memory.readbyte(0x84 + 0x0d) + 8;
  561. gui.drawbox(x-1, y-1, x+1, y+1, "#000000", "#ffc000");
  562. end;
  563. end;
  564.  
  565.  
  566. function DrawRodOrArrowHitbox(link_face_direction)
  567. local rodarrow_status = memory.readbyte(0xac + 0x12);
  568.  
  569. -- Rod.
  570. if (rodarrow_status >= 0x30) then
  571. --[[ The rod and sword share most of the same code, but the sword has a check to ensure it only collides on
  572. status 2, while the rod enters the sword function after that check is done. This causes the rod to collide
  573. at every step of its animation, including an invisible portion of the swing over his head.
  574. ]]
  575. local x = memory.readbyte(0x70 + 0x12);
  576. local y = memory.readbyte(0x84 + 0x12);
  577. local w;
  578. local h;
  579.  
  580. if (bit.band(link_face_direction, 0x0c) ~= 0) then
  581. x = x + 6;
  582. y = y + 8;
  583. w = 11;
  584. h = 15;
  585. else
  586. x = x + 8;
  587. y = y + 6;
  588. w = 15;
  589. h = 11;
  590. end;
  591.  
  592. DrawRectangle(x, y, w, h, "#00bfff");
  593.  
  594. -- Arrow.
  595. elseif (rodarrow_status < 0x20 and rodarrow_status ~= 0x00) then
  596. local x = memory.readbyte(0x70 + 0x12);
  597. local y = memory.readbyte(0x84 + 0x12);
  598.  
  599. -- An active arrow's centering depends on which direction Link is currently facing.
  600. if (bit.band(link_face_direction, 0x0c) ~= 0) then
  601. x = x + 6;
  602. y = y + 8;
  603. else
  604. x = x + 8;
  605. y = y + 6;
  606. end;
  607.  
  608. DrawRectangle(x, y, 10, 10, "#00bfff");
  609. end;
  610.  
  611. if (rodarrow_status ~= 0) then
  612. -- Draw a point for item collection.
  613. x = memory.readbyte(0x70 + 0x12) + 8;
  614. y = memory.readbyte(0x84 + 0x12) + 8;
  615. gui.drawbox(x-1, y-1, x+1, y+1, "#000000", "#ffc000");
  616. end;
  617. end;
  618.  
  619.  
  620. function DrawBeamHitbox(link_face_direction)
  621. local beam_status = memory.readbyte(0xac + 0x0e);
  622.  
  623. if (beam_status ~= 0 and bit.band(beam_status, 1) ~= 1) then
  624. local x = memory.readbyte(0x70 + 0x0e);
  625. local y = memory.readbyte(0x84 + 0x0e);
  626.  
  627. -- An active beam's centering depends on which direction Link is currently facing.
  628. if (bit.band(link_face_direction, 0x0c) ~= 0) then
  629. x = x + 6;
  630. y = y + 8;
  631. else
  632. x = x + 8;
  633. y = y + 6;
  634. end;
  635.  
  636. DrawRectangle(x, y, 11, 11, "#80ff80");
  637. end;
  638. end;
  639.  
  640.  
  641. function DrawBoomerangHitbox()
  642. local boomerang_status = memory.readbyte(0xac + 0x0f);
  643.  
  644. if (boomerang_status < 0x80 and boomerang_status ~= 0) then
  645. local x = memory.readbyte(0x70 + 0x0f) + 4;
  646. local y = memory.readbyte(0x84 + 0x0f) + 8;
  647. DrawRectangle(x, y, 9, 9, "#D2691E");
  648. end;
  649.  
  650. if (boomerang_status ~= 0) then
  651. -- Draw a point for item collection.
  652. x = memory.readbyte(0x70 + 0x0f) + 8;
  653. y = memory.readbyte(0x84 + 0x0f) + 8;
  654. gui.drawbox(x-1, y-1, x+1, y+1, "#000000", "#ffc000");
  655. end;
  656. end;
  657.  
  658.  
  659. function DrawFireOrBombHitboxes()
  660. for i=0,1 do
  661. local firebomb_status = memory.readbyte(0xac + 0x10 + i);
  662.  
  663. -- Fire.
  664. if (firebomb_status >= 0x20) then
  665. local x = memory.readbyte(0x70 + 0x10 + i) + 8;
  666. local y = memory.readbyte(0x84 + 0x10 + i) + 8;
  667. DrawRectangle(x, y, 13, 13, "#ff8080");
  668.  
  669. local color = "#40ff40";
  670. if (i == 1) then
  671. -- Flame 2 is ignored if flame 1 is in its standing state.
  672. local fire1_status = memory.readbyte(0xac + 0x10);
  673. local fire1_timer = memory.readbyte(0x28 + 0x10);
  674. local fire2_timer = memory.readbyte(0x28 + 0x11);
  675. if (firebomb_status == 0x22 and fire1_status == 0x22 and fire1_timer >= fire2_timer) then
  676. color = "#ff8080";
  677. end;
  678. end;
  679.  
  680. gui.drawbox(x-1, y-1, x+1, y+1, "#000000", color);
  681.  
  682. -- Exploding bomb.
  683. elseif (firebomb_status == 0x13) then
  684. local x = memory.readbyte(0x70 + 0x10 + i) + 8;
  685. local y = memory.readbyte(0x84 + 0x10 + i) + 8;
  686. DrawRectangle(x, y, 23, 23, "#ff8080");
  687. gui.drawbox(x-1, y-1, x+1, y+1, "#000000", "#40ff40");
  688.  
  689. -- Bomb.
  690. elseif (i == 0 and firebomb_status ~= 0 and firebomb_status < 0x20) then
  691. -- For dodongos.
  692. local x = memory.readbyte(0x70 + 0x10 + i) + 8;
  693. local y = memory.readbyte(0x84 + 0x10 + i) + 8;
  694. if (firebomb_status == 0x12) then
  695. gui.drawbox(x-1, y-1, x+1, y+1, "#000000", "#ff0040");
  696. else
  697. gui.drawbox(x-1, y-1, x+1, y+1, "#000000", "#ff8080");
  698. end;
  699. end;
  700. end;
  701. end;
  702.  
  703.  
  704. function DrawLinkHitboxAndCenter()
  705. local link_status = memory.readbyte(0xac);
  706. if (bit.band(link_status, 0xc0) ~= 0x40) then
  707. local x = memory.readbyte(0x70) + 8;
  708. local y = memory.readbyte(0x84) + 8;
  709. DrawRectangle(x, y, 8, 8, "#ffffff");
  710. gui.drawbox(x-1, y-1, x+1, y+1, "#000000", "#ffffff");
  711. end;
  712. end;
  713.  
  714.  
  715. function DrawBombableDungeonWallHitboxes()
  716. local level = memory.readbyte(0x10);
  717. local mode = memory.readbyte(0x12);
  718. local moving_door_status = memory.readbyte(0x54);
  719.  
  720. -- Only draw the hitboxes in dungeons, outside of passageways, and when other doors aren't currently moving.
  721. if (level ~= 0 and mode ~= 9 and moving_door_status == 0) then
  722. local current_screen = memory.readbyte(0xeb);
  723. local vertical_doors = memory.readbyte(0x687e + current_screen);
  724. local horizontal_doors = memory.readbyte(0x68fe + current_screen);
  725. local opened_doors = memory.readbyte(0xee);
  726.  
  727. -- Indicate the exact frame on which collision is tested.
  728. local color;
  729. if ((memory.readbyte(0xbc) == 0x14 and memory.readbyte(0x38) == 0xc) or
  730. (memory.readbyte(0xbd) == 0x14 and memory.readbyte(0x39) == 0xc)) then
  731. color = "#ff0000";
  732. else
  733. color = "#40ff40";
  734. end;
  735.  
  736. -- Top.
  737. if (bit.rshift(vertical_doors, 5) == 0x04 and bit.band(opened_doors, 8) == 0) then
  738. DrawRectangle(0x78+8, 0x5d+8, 23, 23, color);
  739. end;
  740.  
  741. -- Bottom.
  742. if (bit.band(bit.rshift(vertical_doors, 2), 7) == 0x04 and bit.band(opened_doors, 4) == 0) then
  743. DrawRectangle(0x78+8, 0xbd+8, 23, 23, color);
  744. end;
  745.  
  746. -- Left.
  747. if (bit.rshift(horizontal_doors, 5) == 0x04 and bit.band(opened_doors, 2) == 0) then
  748. DrawRectangle(0x20+8, 0x8d+8, 23, 23, color);
  749. end;
  750.  
  751. -- Right.
  752. if (bit.band(bit.rshift(horizontal_doors, 2), 7) == 0x04 and bit.band(opened_doors, 1) == 0) then
  753. DrawRectangle(0xd0+8, 0x8d+8, 23, 23, color);
  754. end;
  755. end;
  756. end;
  757.  
  758.  
  759. function DrawScreenBoundaries()
  760. local left = memory.readbyte(0x346 + 0) - 1;
  761. local right = memory.readbyte(0x346 + 1);
  762. local top = memory.readbyte(0x346 + 2) - 1;
  763. local bottom = memory.readbyte(0x346 + 3);
  764.  
  765. -- Link and dynamic objects with ID ~= 0x5c (boomerang).
  766. gui.drawline(left, 0x3d, left, 0xef, "#80c0ff");
  767. gui.drawline(right, 0x3d, right, 0xef, "#80c0ff");
  768. gui.drawline(0x00, top, 0xff, top, "#80c0ff");
  769. gui.drawline(0x00, bottom, 0xff, bottom, "#80c0ff");
  770.  
  771. -- Weapons.
  772. gui.drawline(left-11, 0x3d, left-11, 0xef, "#40c0ff");
  773. gui.drawline(right+12, 0x3d, right+12, 0xef, "#40c0ff");
  774. gui.drawline(0x00, top-15, 0xff, top-15, "#40c0ff");
  775. gui.drawline(0x00, bottom+18, 0xff, bottom+18, "#40c0ff");
  776. end;
  777.  
  778.  
  779. function DrawInputRestrictionBoundaries()
  780. -- B button.
  781. local bottom = 0xbe + 8;
  782. local top = 0x54 + 8 - 1;
  783. local right = 0xd1 + 8;
  784. local left = 0x1f + 8 - 1;
  785.  
  786. gui.drawline(left, 0x3d, left, 0xef, "#ff0080c0");
  787. gui.drawline(right, 0x3d, right, 0xef, "#ff0080c0");
  788. gui.drawline(0x00, top, 0xff, top, "#ff0080c0");
  789. gui.drawline(0x00, bottom, 0xff, bottom, "#ff0080c0");
  790.  
  791. -- A button.
  792. local level = memory.readbyte(0x10);
  793. if (level == 0) then
  794. -- Overworld.
  795. bottom = 0xd6 + 8;
  796. top = 0x45 + 8 - 1;
  797. right = 0xe9 + 8;
  798. left = 0x07 + 8 - 1;
  799. else
  800. -- Dungeons.
  801. bottom = 0xc6 + 8;
  802. top = 0x55 + 8 - 1;
  803. right = 0xd9 + 8;
  804. left = 0x17 + 8 - 1;
  805. end;
  806.  
  807. gui.drawline(left, 0x3d, left, 0xef, "#80ff00c0");
  808. gui.drawline(right, 0x3d, right, 0xef, "#80ff00c0");
  809. gui.drawline(0x00, bottom, 0xff, bottom, "#80ff00c0");
  810. gui.drawline(0x00, top, 0xff, top, "#80ff00c0");
  811. end;
  812.  
  813.  
  814. function DrawNpcBarrier()
  815. local mode = memory.readbyte(0x12);
  816. local object1 = memory.readbyte(0x350);
  817. if (mode == 0xb or
  818. mode == 0xc or
  819. object1 == 0x36 or
  820. (object1 >= 0x4b and object1 < 0x53)) then
  821. gui.drawline(0x00, 0x8d+8, 0xff, 0x8d+8, "#ffff00");
  822. end;
  823. end;
  824.  
  825.  
  826. function DrawObjectActualPositions()
  827. -- Link.
  828. local x = memory.readbyte(0x70);
  829. local y = memory.readbyte(0x84);
  830. gui.drawbox(x-1, y-1, x+1, y+1, "#000000", "#80c0ff");
  831.  
  832. -- Dynamic objects.
  833. for i=0,10 do
  834. --[[ This intentionally avoids special casing most objects because almost none of that is relevant for screen
  835. boundaries, which are the main reason for this function.
  836. ]]
  837. local id = memory.readbyte(0x350 + i);
  838. x = memory.readbyte(0x71 + i);
  839. y = memory.readbyte(0x85 + i);
  840.  
  841. -- Digdogger.
  842. if (id == 0x38 or id == 0x39) then
  843. -- Has 4 different positions for boundary collision.
  844. local x_offsets = { 0x00, 0x10, 0x00, 0xf0 }
  845. local y_offsets = { 0x00, 0x10, 0xf0, 0x10 }
  846. for j=0,3 do
  847. x = bit.band(x + x_offsets[j+1], 0xff);
  848. y = bit.band(y + y_offsets[j+1], 0xff);
  849.  
  850. gui.drawbox(x-1, y-1, x+1, y+1, "#000000", "#80c0ff");
  851. end;
  852.  
  853. -- Standard objects.
  854. elseif (id ~= 0) then
  855. gui.drawbox(x-1, y-1, x+1, y+1, "#000000", "#80c0ff");
  856. end;
  857. end;
  858.  
  859. -- Weapons.
  860. for i=13,18 do
  861. local status = memory.readbyte(0xac + i);
  862. if (status ~= 0) then
  863. x = memory.readbyte(0x70 + i);
  864. y = memory.readbyte(0x84 + i);
  865.  
  866. gui.drawbox(x-1, y-1, x+1, y+1, "#000000", "#40c0ff");
  867. end;
  868. end;
  869.  
  870. -- Treasures.
  871. if (memory.readbyte(0xac + 0x13) < 0x80) then
  872. x = memory.readbyte(0x70 + 0x13);
  873. y = memory.readbyte(0x84 + 0x13);
  874.  
  875. gui.drawbox(x-1, y-1, x+1, y+1, "#000000", "#80c0ff");
  876. end;
  877. end;
  878.  
  879.  
  880. function HandleDrawing()
  881. local mode = memory.readbyte(0x12);
  882. local task = memory.readbyte(0x11);
  883. local start_menu_status = memory.readbyte(0xe1);
  884. local link_face_direction = memory.readbyte(0x98);
  885.  
  886. if (start_menu_status == 0 and
  887. task ~= 0 and
  888. (mode == 5 or (mode >= 0x9 and mode <= 0xc))) then
  889. -- Draw each collision element. These can be individually commented out to reduce screen clutter.
  890. DrawInputRestrictionBoundaries();
  891. DrawNpcBarrier();
  892. DrawBombableDungeonWallHitboxes();
  893.  
  894. --[[ These functions are commented out by default because they add significantly to the amount of onscreen
  895. clutter and aren't as useful as the other tools. Screen boundaries operate on object positions rather than
  896. object centers. Other boundaries and some objects collide with Link in this same way, but because they
  897. only collide with Link, the locations of these collision areas have been shifted in this script to match
  898. up with Link's collision center rather than his real position. Because screen boundaries interact with
  899. more than just Link, this cannot be done here because the centers for standard objects, thin objects,
  900. items, and weapons all differ. As a result, it requires that actual positions be drawn in order to be
  901. accurate.
  902. ]]
  903. --DrawScreenBoundaries();
  904. --DrawObjectActualPositions();
  905.  
  906. DrawDynamicObjectCenters();
  907. DrawTreasureCenter();
  908.  
  909. DrawRodOrArrowHitbox(link_face_direction);
  910. DrawSwordHitbox(link_face_direction);
  911. DrawBeamHitbox(link_face_direction);
  912. DrawBoomerangHitbox();
  913. DrawFireOrBombHitboxes();
  914.  
  915. DrawLinkHitboxAndCenter();
  916. end;
  917. end;
  918.  
  919.  
  920. --[[ Drawing to the screen is delayed by 1 frame, by default. This is normally fine because Zelda also delays drawing
  921. by 1 frame. The script's delay can be removed by uncommenting this line, and commenting out the Main loop below.
  922. ]]
  923. --gui.register(HandleDrawing);
  924.  
  925. -- Main loop.
  926. while (true) do
  927. HandleDrawing();
  928. emu.frameadvance();
  929. end;
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement