Advertisement
Guest User

Zelda Hitbox Visualizer v1.1.lua

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