Advertisement
Guest User

Zelda Hitbox Visualizer v1.0.lua

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