Jump to content
NHL'94 Forums

chaos

Admin
  • Posts

    2,145
  • Joined

  • Last visited

  • Days Won

    98

chaos last won the day on August 2

chaos had the most liked content!

7 Followers

About chaos

  • Birthday 10/15/1981

Contact Methods

  • Discord
    chaos
  • Website URL
    http://

Profile Information

  • Location
    New Jersey

Previous Fields

  • Preferred System
    SNES
  • Favorite Way To Score
    I Don't

Recent Profile Visitors

30,990 profile views

chaos's Achievements

Community Regular

Community Regular (8/14)

  • Reacting Well Rare
  • Very Popular Rare
  • First Post
  • Collaborator Rare
  • Posting Machine Rare

Recent Badges

464

Reputation

  1. Read the linked post, it will tell you how to find the tiles. Frame 405 and 406 are the nets.
  2. Frames 405 and 406 are the net frames (405 is the top net). Look at the Frame Data (NHL93-94) section of this post. This gets complicated though, since you are adding more sprite tiles than were originally there. So you would have to change the data for that frame, then update the Frame pointer list for all the frames after that. The better solution would be to move the Frame list and all the Frame Data and Hot Spots to a new location, add your frames to the end of the list, then update the location where it chooses the frame for the nets in the code. You would also have to update the locations that point to the frame list and the frame data.
  3. Are you trying to copy over the net sprite tiles, or are you trying to have the code point to your new ones?
  4. If anyone is willing to beta test the patch, DM me. I'll added some testing variables to mess around with how to start a fight. I believe with the default values, there will rarely be fights, so it will need some messing around with. Also I will note that this patch will be compatible with 30 and 32 team ROMs, once I check for open space in them so I'm not overwriting anything.
  5. No, you will need to update them manually in NOSE so they are in order.
  6. OK, let me be a little bit clearer. The only math I'm changing here is the display for the fighting attribute. All the display math was changed from 93 to 94, and the fighting attribute display math was not fixed. This math has nothing to do with what the game uses for logic, it's just used to display the attribute on a 25-99 scale. Since 94 added hot/cold, team, and PP/PK bonuses, they had to change the attributes so that the bonuses would not affect as much as whole points. So they made a 0-6 scale, where the logic will multiply that value by 5, then add the bonuses, for a max of 30 decimal. There's a lot of spots in the player logic that use the same math as 93, but they will double the starting value of the math to work with the 0-30 scale instead of 0-15 scale. Other spots are completely changed to work with the new scale. There's a patch to use 0-15 scale attribute ratings, though I don't know exactly how smoz changes things to work with 0-15. The bonuses would need to be revamped, or he's multiplying by 2 instead of 5 in the logic. The display math and the logic math for attributes are 2 different things.
  7. OK, got the display to be correct now. 93 takes the attribute, multiplies by 100, and divides by $F (15 decimal). In 94, instead of $F, it sets the divisor to $64 (100 decimal). It will multiply the attribute by 16, add hot/cold, then multiply by 100 before dividing. As I described in post above, it ends up subtracting 1 from the divisor when calculating the Fighting attribute display (I think this code was never changed from 93). So in 93 it divides fighting by $E (14 decimal) (subtracts 1 because removal of Hand bit, $E is the highest Fighting can be). It still subtracts 1 in 94, but this ends up dividing by 99 decimal instead. All 93 attributes are on the 0-F scale. In 94, they are on 0-6 scale, except for Fighting (and Weight, but that's done the same in both games). So I can't just change where it sets $64 as the divisor. But instead, I can change the 2 bytes used for the subtracting of 1, and use $E for the fighting divisor. That's located at $8E3E: 0x8E3E: ***d1 is the register used as the divisor, set to $64 (100 dec) earlier in 94 code, or $F in the 93 code. Original code - 53 41 (subq #1,d1) Change to - 72 0E (moveq #$E,d1) OK, so now all the stuff involved with fighting is in the ROM. Now the last thing to do is make some changes to determine how to start a fight.
  8. So I got fighting to show up on the edit lines/team rosters screens. It was actually much easier than I thought, once I figured out how the display function worked. The data containing the strings for the headers (Status, Overall, Agl, etc) actually contains other data pertaining to what attribute to look at in the ROM data. The function will find the team's data in the ROM, move to the player data, shuffle past the name, and will find the attribute linked to that string. It will then take it, do some math depending on the type of data (Status, Energy, Weight, Fighting, Handedness, Overall, or a regular attribute), and display it. This is also where the check for "if it's 0 make it 25" is. If the calculated value is below 50, it will take half the calculated value, and add 25 to it before it displays it. So note how the Fight numbers are low here compared to 93. That's the next thing to figure out, but according to how 94 is doing the math for Fighting, it works out here. It will take the value in the ROM (0-15, though no one is above a 13), knock off the Handedness (round down if its an odd number) then it multiplies by 100, and divides by 99, getting rid of the remainder (so in Ledyard's case, we have 12 * 100 / 99 = 12). Since 12 is lower than 50, it will take half of that and add it to 25. 25 + 6=31. The string data in the original ROM starts at 0x193F8. To show you an example on how the attribute pointer is stored in the string data, I'll look at Agility. dc.w $12 dc.b $5B ; [ dc.b $20 dc.b $20 dc.b $20 dc.b $41 ; A dc.b $67 ; g dc.b $69 ; i dc.b $6C ; l dc.b $69 ; i dc.b $74 ; t dc.b $79 ; y dc.b $20 dc.b $20 dc.b $20 dc.b $20 dc.b $5D ; ] dc.l $1000000A The word size of data before the string (0012 in this case) tell the code how long the string is following this (it includes the string length bytes as well). So we have $12 = 18 bytes. Including the 00 12, the string will end with dc.b $5D. The string is stored as ASCII code, and you'll notice the brackets here, which the code will change into left and right arrows when it displays on the screen. After the string, there is a long word size of data (4 bytes) - $10 00 00 0A. This is what we are interested in. We want to look at the upper word, as this is what is used ($1000). The bottom word ($000A) will be set differently depending on the type of attribute (regular attribute like Agl here will have $000A). When the code scrolls through to the player to be displayed, and it gets to the player needed, the pointer will be 1 byte after the end of the attribute list. Let's look at how the attributes are stored in ROM: Player Data Structure in ROM: 00 XX PLAYER NAME YY 12 34 56 78 9A BC DE 00 XX = Player name length + 2 (the two bytes containing the length of the name) in hex Note: The 2 bytes above are swapped in SNES version (because of endianness) Player Name = Straight-forward YY = Jersey # (in decimal) 1 = Weight 2 = Agility 3 = Speed 4 = Off Awareness 5 = Def Awareness 6 = Shot Power 7 = Checking 8 = Handedness/Fighting (Odd = Right, Even = Left) 9 = Stick Handling A = Shot Accuracy B = Endurance C = Pass/Shot Bias D = Passing E = Aggression So right now we are 1 byte after the Passing/Aggression byte (which would be the first byte in the next player's name length) after looping through to the player to display. The code will compensate by adding -1 to the pointer offset. If we look at our pointer in the Agility data ($1000) we need to find what bit is set, as this is used to compare. Data - 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 Bit - 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 Bit 12 is set for Agility. Now if you look above at the ROM data for the player, you will see each byte contains 2 attributes. Wgt/Agility is byte 6 from the end (since we add -1 to the offset, Pass/Agr is byte 0). We take our bit 12, divide by 2 (leave the remainder if applicable) to get 6. We add a -6 offset to grab the whole byte from the attribute data. Then, since we are using the lower nibble, the code will mask off the Weight, leaving the Agility nibble (if we were looking at weight, it checks if the bit is odd, and will shift accordingly). In the case of Overall: There is a table used that has weights for the attribute list. This is located at $FAB1C for players, and $FAB2C for goalies. If you want to add the Fighting display into a ROM: Copy the attribute header strings from NHLPA93 (starting at $156C0, ending at $15837) to a free location in ROM. Update the pointers at $84DD and $8BD9 (original pointer is 01 93 F8) to the new starting location. If you want to show Fighting attribute on the Shootout mode edit screens, there are 2 more pointers that need to be updated, but you will need to calculate them. It will be new starting location + $16. Update the pointers at $FC85F and $FC891 (original pointer 01 94 0E) Once I figure out how to display the Fighting attribute correctly, I'll update this post.
  9. Just to give an update, I stepped away from this for a bit just to take a break. I will be back at it this week. Looking into adding the Fighting attribute to the Team Roster and Edit Lines screens.
  10. I attached a ZIP file containing the frames for 92-94 in BMP form (this is the output from the EA-NHL-Tools posted above). NHL92-94_Frames.zip
  11. While this example was all done manually, @bcrt2000 has made a tool to extract the frames automatically and store them as BMPs. Check it out here - https://github.com/abdulahmad/EA-NHL-Tools
  12. NHL Hockey (NHL92), NHLPA Hockey 93, and NHL94 all share a similar proprietary format when it comes to sprites, frames, and animations. While 92 is a little different in the way the frame data is laid out, 93 and 94 are the same. The format used might actually be similar to other EA Sports games at the time (early Madden games). Note, the labels used in the NHL92 source code will be used in this post. What's a sprite? A sprite is a special object that can be moved around and placed on top of, or below the background tiles and other sprites. The amount of sprites that can be displayed on the screen is limited, depending on the console. Sprites are usually small compared to the background they are on. A tile is a group of 8x8 pixels. The sprites in these games vary in tile size. The sprites for the on-ice objects vary from 1x1 to 4x4 tiles. Sprites are used for on-ice objects, crowd aminations, faceoff window, and the zamboni. What's a frame? A frame is a group of sprites. Each frame also has a "hot spot", which is used as a second "anchor point" (i.e. a player's stick blade can be a hot spot in a frame, where the puck will "glue" to when the player has possession). For on-ice objects, there are 549 frames in 92, 649 frames in 93, and 845 frames in 94. What's an animation? An animation is a group of frames that are assigned based on a specific action (i.e. skating with the puck, making a pad stack save, shooting). There are different on-ice objects that the game keeps track of: Fixed Frame Objects (FFO): These objects are tied to the rink scrolling. These vary from game to game. 92 - Player Stars (2), possession star, gloves/sticks for fighting, center ice logo 93 - Player Stars (2), possession star, replay cursor, gloves/sticks for fighting 94 - Player Stars (4), possession star, replay cursor, gloves/sticks for fighting (yes, this is still there, though it points to a blank frame) These are fixed frame objects (there are no animations). SSO (no idea what this stands for): These objects are not tied to rink scrolling. These vary from game to game. 92 - Off-screen player arrows (2), logos (2) on the score bug 93 - Off-screen player arrows (2) 94 - Off-screen player arrows (4) These are also fixed frame objects (there are no animations). Sort Graphics Objects: These are objects that are involved in the action, and are "sorted" on a list depending on their location in the Y, where lower objects on the screen have a higher priority. There are 16 of these objects - 12 skaters (6 per team), 2 nets, the puck, and the puck shadow (which converts to the goal light when a goal is scored). These objects have many different frames and animations that are assigned to them based on game conditions (except for the nets, they have 1 frame each). The Sort Objects are the ones we will concentrate on. These objects each have a data structure that has variables for X/Y/Z positions and velocities, current frame number, current animation list and animation index, player attributes, conditional flags, etc. Not every object uses all the data structure variables (there is no Chk rating for a goal net for example). The data structure size is $80 hex or 128 decimal. They start at RAM location $FFB04A (6 Home Team objects, 6 Away Team objects, 2 goal nets, puck, puck shadow). Sprite Animation List (SPAList) SPAList locations in ROM: 92 - $35B6 93 - $4D8E 94 - $5B1C First, lets look at directions. The object can face 8 different directions (0-7). Direction 0 is facing upward, and we go clockwise from there. Now, lets say the player is skating with the puck. The code assigns the player the SPA (SPrite Animation) of skating with the puck. But, depending on the direction the player is facing, the frames for the animation should be different. So, for each SPA, there is a table containing offsets based on direction. This offset will be used to get the correct animation frame list based on that direction. Also included for each SPA is a flag for repeating the animation. Let's use skating with the puck as an example (SPAskatewp). The player is skating in direction 1. Let's find the direction table, and get the offset to the animation frame list. We will use NHL94 in this example. SPAskatewp = $53E direction = 1 SPAList starts at $5B1C in 94 To get to the table for SPAskatewp, we add SPAskatewp to SPAList - $5B1C+$53E = $605A. Now, each SPA table in the SPAList is 18 bytes long (2 bytes per direction), and the last 2 bytes are for the SPA attribute (which only the first bit is used in the NHL games, for repeating the animation). The frame animation lists for this animation follow immediately after this table. Here is the table at $605A from the ROM: Dir 0 - 00 12 Dir 1 - 00 22 Dir 2 - 00 32 Dir 3 - 00 42 Dir 4 - 00 52 Dir 5 - 00 62 Dir 6 - 00 72 Dir 7 - 00 82 Attrib - 00 00 To get the offset, we take the start of the table ($605A). We add direction*2 to get the offset (in the case of direction 1, 00 22). We take this offset, and add it to the start of the table to get to the animation frame list ($605A + $0022 = $607C). So for SPAskatewp, player skating in direction 1, the animation frame list is at ROM location $607C. The animation frame list will have values in pairs. The pair is Frame # and the time to show the frame (value in video frames). The last frame in the animation will show a negative time (this is how the code knows it's the last frame in the animation). Let's look at our example: Animation frame list at $607C: 00 07 00 0A (frame 7, time 10) 00 08 00 0A (frame 8, time 10) 00 09 00 0A (frame 9, time 10) 00 0A FF F6 (frame 10, time 10) - last frame in animation Each animation frame is displayed for 10 video frames. There are 4 frames total in this animation. Since our repeat flag was 0, the animation does not repeat. The code will take care of changing or restarting this animation if needed. Let's look at our animation: The SPAList contains the table for direction offsets, attribute flags (only repeat is used), and animation frame lists for each animation in the game. OK, so we know how to get the frame for each animation. But how is that frame put together? Remember earlier, I described a frame as a group of sprites with data associated with it. Our SPAskatewp animation is not a single sprite in each frame. Now this is where 92 is vastly different from 93 and 94. In 92, the Frame data and the sprite data bytes are together in one structure. In 93 and 94, they are separated, and the sprite data bytes are a little different. I will go over how they are laid out in 92 then show how they are laid out in 93/94. Frame Data (NHL92) If you look at the 92 source code, the Sprites.anim file starts in the base ROM at address $3D5EE. The first frame data starts at offset 6 ($3D5F4). Here is the layout of each Frame data: Frame data structure (First frame at $3DF54): SprStratt - offset $A: attribute flags (2 bytes) - not used in NHL SprStrhot - offset $C: hotspot data (24 bytes) - though this is 24 bytes, only 4 bytes are used. 2 for Hot Spot X (byte 12-13), 2 for Hot Spot Y (byte 14-15) SprStrnum - offset $24: # of sprites in this frame - 1 SprStrdat - offset $26: start of sprite data bytes (8 bytes per sprite) The Sprite data bytes contain data for each specific sprite in the frame: Sprite Data Bytes: Byte 0-1: Y Global position Byte 2-3: sizetab and top 4 bits of tile data offset Byte 4-5: bottom 11 (0-10) bits of tile data offset, H/V flip priority (bits 11+12), palette (13-15) Byte 6-7: X Global position Let's go over the easy ones: Y Global and X Global: Each Sort Object has an X and Y position stored in its structure. The X and Y Globals of the Sprite Data are added to that X and Y position to determine the upper left corner of the sprite. Sizetab: This is an index for the sizetab table. The sizetab table contains values related to the number of tiles in the sprite. Sizetab table: Lists # of tiles in the sprite, and is linked to their layout: Index | Value | Tile Layout (XY) 0 1 1x1 1 2 1x2 2 3 1x3 3 4 1x4 4 2 2x1 5 4 2x2 6 6 2x3 7 8 2x4 8 3 3x1 9 6 3x2 A 9 3x3 B C 3x4 C 4 4x1 D 8 4x2 E C 4x3 F 10 4x4 To get the index, you need to look at bytes 2-3. Bytes 2-3: Sizetab bytes Byte 2: Upper nibble (top 4 bits) used for the top 4 bits of tile data pointer (more later). Bottom nibble (bottom 4 bits) used as an index to the sizetab table. Byte 3: Not used, always 00 in 92. Tile Data Offset, H/V Priority, Palette: This part gets a little complicated. The tile data offset is 15 bits. The lower 11 bits are in byte 4-5, and the upper 4 bits are in byte 2. Bytes 4-5: Data Offset, Flip and Palette Data Offset: This is used to find the 1st starting tile for the sprite. First 11 bits (0-10) are used for the data pointer. Then the top 4 bits are taken from byte 2 to get the full 15 bits of the offset (these are the upper 4 bits of the offset). The result is then sign-extended long word, multiplied by $20 (32 decimal), and used as an offset to the Spritetiles. Flip: Bits 11 and 12 are used for H and V flip of the sprite. If the lower nibble of byte 4 is 8 or higher, the sprite will be H flipped by default. If the upper nibble of byte 4 is odd, it will be V flipped by default. Later on, depending on the handedness of the player, the whole frame can be flipped before stored in memory. Palette: The last 3 bits (13-15) are used for palette and priority. Bits 13 and 14 will decide which palette to use (Home, Visitor, 1st or 2nd Rink Palette). These bits can be manipulated before stored in memory, depending on if it's a player and what team they are playing for. Bit 15 is priority. If it is set, the sprite will appear in front of the background. If you want a better description of flip and palettes, please check out @AdamCatalyst's awesome post here - Let's look at our SPAskatewp example. We will choose the first frame from the animation, frame 7. But in order to find the data for frame 7, we do not know how long the frame data for 1-6 are (length will vary depending on the # of sprites in each frame). If we open the sprites.anim file in the 92 source, each start frame data structure starts with 53 53. If we were to automate this, we would look at the # of sprites in each frame data (offset $24), multiply that by 8, and add it to $26 (the length of the frame data struct before the sprite data starts) to get our frame data size, and continue through the frame data that way. 41 41 02 24 00 02 Frame 1: 53 53 00 CD 00 C2 00 20 00 40 00 00 00 00 00 00 00 FF 00 00 00 00 00 FF FF FC FF EC 00 00 00 00 00 00 00 FF 00 00 FF E7 07 00 46 A0 FF F9 Frame 2: 53 53 00 CD 00 C2 00 30 00 40 00 00 00 00 00 00 00 FF 00 00 00 00 00 FF FF FF FF E8 00 00 00 00 00 00 00 FF 00 01 FF E5 07 00 46 98 FF F9 FF FD 20 00 46 0D FF F1 Frame 3: 53 53 00 CD 00 C3 00 20 00 40 00 00 00 00 00 00 00 FF 00 00 00 00 00 FF FF FE FF E4 00 00 00 00 00 00 00 FF 00 00 FF E1 07 00 46 90 FF F8 Frame 4: 53 53 00 CD 00 C2 00 20 00 40 00 00 00 00 00 00 00 FF 00 00 00 00 00 FF FF FF FF E7 00 00 00 00 00 00 00 FF 00 00 FF E4 07 00 46 88 FF FB Frame 5: 53 53 00 CD 00 C2 00 20 00 40 00 00 00 00 00 00 00 FF 00 00 00 00 00 FF 00 01 FF E7 00 00 00 00 00 00 00 FF 00 00 FF E3 07 00 46 80 FF FB Frame 6: 53 53 00 DB 00 C9 00 40 00 40 00 00 00 00 00 00 00 FF 00 00 00 00 00 FF 00 10 FF F8 00 01 00 00 00 00 00 FF 00 01 FF E8 07 00 46 78 FF F9 FF F6 24 00 43 4E 00 09 Frame 7: 53 53 00 D8 00 CD 00 40 00 40 00 00 00 00 00 00 00 FF 00 00 00 00 00 FF 00 17 FF F6 00 C8 00 00 00 00 00 FF 00 03 FF F2 19 00 46 DE FF FA FF F2 20 00 46 0C 00 12 FF EA 24 00 43 4C FF FC FF E2 20 00 46 0B 00 04 Frame 7, byte $24 and $25 = 00 03, which means 4 sprites in the frame (3+1 =4) Sprite 1: FF F2 19 00 46 DE FF FA Sprite 2: FF F2 20 00 46 0C 00 12 Sprite 3: FF EA 24 00 43 4C FF FC Sprite 4: FF E2 20 00 46 0B 00 04 Lets get the size in tiles and the tile data offsets for each sprite: Sprite 1: FF F2 19 00 46 DE FF FA Sizetab index - lower nibble of Byte 2 = 9, which is a 3x2 tile sprite. Tile data offset: Byte 4+5 = $46DE. To get the first 11 bits, we AND with $7FF = $6DE Byte 2+3 = $1900. To get the top nibble, we need to AND with $F000 = $1000 Since we are 1 bit off (only took 11 of the 12 bits from 4+5), we need to shift right 1 bit (divide by 2) $1000 >> 1 = $800 Now, we OR (or add) the 2 results together - $6DE OR $800 = $EDE. We take our $EDE and mult. by $20 (32 decimal) = $1DBC0 -> this is our offset to the first tile in this sprite. Sprite 2: FF F2 20 00 46 0C 00 12 Sizetab = 0 = 1x1 sprite Tile data offset: $460C AND $7FF = $60C $60C + $1000 ($2000 >> 1) = $160C $160C * $20 = $2C180 Sprite 3: FF EA 24 00 43 4C FF FC Sizetab = 4 = 2x1 sprite Tile data offset: $434C AND $7FF = $34C $34C + $1000 ($2000 >> 1) = $134C $134C * $20 = $26980 Sprite 4: FF E2 20 00 46 0B 00 04 Sizetab = 0 = 1x1 sprite Tile data offset: $460B AND $7FF = $60B $60B + $1000 ($2000 >> 1) = $160B $160B * $20 = $2C160 The sprite tiles in the NHL92 ROM start at $45F16. Each tile is 32 bytes of data (4bpp linear = 4 bits per pixel. 64 pixels are in a single tile, so this = 64 * 4 = 256, divide by 8 bits (8 bits = 1 byte), we get 32 bytes). So our tile data for each sprite starts at: $45F16 + $1DBC0 = $63AD6 - 3x2 sprite. 6 tiles = 192 bytes $45F16 + $2C180 = $72096 - 1x1. 1 tile = 32 bytes $45F16 + $26980 = $6C896 - 2x1. 2 tiles = 64 bytes $45F16 + $2C160 = $72076 - 1x1. 1 tile = 32 bytes Using tile molester (used HFD palette, $19C8): Now, we need to assemble them using the global X and Y positions. Since the player is not located on the ice, we don't have an actual start position to work off of. So I will make a canvas big enough for the sprite, and designate a point near the bottom as the position. Usually the position of the player on the ice is where their feet are in the sprite. Let's get the global X,Y for the sprites: Sprite 1: FF F2 19 00 46 DE FF FA Global Y (Byte 0+1) = FF F2 = -14 decimal Global X (Bytes 6+7) = FF FA = -6 Sprite 2: FF F2 20 00 46 0C 00 12 Y = FF F2 = -14 X = 00 12 = 18 Sprite 3: FF EA 24 00 43 4C FF FC Y = FF EA = -22 X = FF FC = -4 Sprite 4: FF E2 20 00 46 0B 00 04 Y = FF E2 = -30 X = 00 04 = 4 Now we can assemble the frame. The black dot in the bottom shadow is the point used for 0,0. What about the Hot Spot of the frame? Let's look at the frame data again: Frame 7: 53 53 00 D8 00 CD 00 40 00 40 00 00 00 00 00 00 00 FF 00 00 00 00 00 FF 00 17 FF F6 00 C8 00 00 00 00 00 FF 00 03 FF F2 19 00 46 DE FF FA FF F2 20 00 46 0C 00 12 FF EA 24 00 43 4C FF FC FF E2 20 00 46 0B 00 04 Hotspot data starts at offset $C, and is 24 bytes long: 00 00 00 00 00 FF 00 00 00 00 00 FF 00 17 FF F6 00 C8 00 00 00 00 00 FF X Hotspot is bytes 12-13 - 00 17 - 23 decimal Y Hotspot is bytes 13-14 - FF F6 - -10 decimal These are offset from the Sort Cord's XY position, just like the sprites. Since we were able to derive the XY position in the frame (the black dot in bottom shadow), we can find the Hot Spot in the frame as well, 23 decimal to the right, and 10 decimal up: The black dot near the end of the stick blade is the Hot Spot. This is where the puck will "stick" to the blade in this frame. Frame Data (NHL93-94) The frame data in 93 and 94 is different than 92. In 92, everything was combined into one Frame Data structure for each frame (Hot spots, # of sprites, sprite data bytes). Here, it is all split up into different lists (probably to save space). Hotlist table - Contains the X and Y Hot Spots for each frame. 1 byte for each Hot Spot, so 2 bytes for each frame. This is indexed using the current frame # of the Sort Cord : X Hot Spot - Hotlist + frame*2 Y Hot Spot - Hotlist + frame*2 + 1 These are used in word size, so after the byte is retreived, it will sign extend it to word size (i.e $17 = 0017, $F6 = FFF6) Hotlist table locations: NHL93 (v1.1) - $743FE ($512 long) NHL94 - $A44CA ($68A long) # of Sprites - There is no data stored for this. Instead, there's a table of offsets to the sprite data bytes. The code will take the current frame's offset, and the next frame's offset, find the difference, divide by 8 (8 bytes per sprite), and find the # of sprites that way. Sprite Data Bytes - The layout for the sprite data bytes is a little different. Sprite data bytes: Byte 0-1: X Global Byte 2-3: Y Global Byte 4-5: Tile offset Byte 6: Palette/HV Flip byte Byte 7: Sizetab byte Note the differences from 92. We have a full byte for Sizetab, 2 bytes for Tile offset, and a full byte for palette and sprite orientation. This makes the data much easier to deal with compared to 92. So there is no need for extra tricky math here. This layout is just like the layout given in Adam's post below. You can look at that post for a better description. The sprite data bytes are accessed by adding the sprite data offset to the start of the list. Let's look at our example again, the SPAskatewp frame 7. Luckily, there are many frames shared across all 3 games, and this is one of them. So the goal in this example is to get the same data that we got in 92, but from the 94 ROM. (Trivia: the animation for SPAskatewp has an extra frame in 92 than in 93 and 94. It's the still frame of the faced direction from the Directions GIF in this post). Ok, let's get the Hot Spot data for frame 7 first, since that is the easiest: Hotlist table starts at $A44C8 in 94. Hot Spots for frame 7: X Hot Spot - $A44C8 + (7 * 2) = $A44D6 address, which is $17. Sign extend, we get 00 17. Y Hot Spot - $A44C8 + (7 * 2 + 1) = $A44D7 address, which is $F6. Sign extend, we get FF F6. OK, Hot Spot is the same as 92. Now, lets find the # of sprites in frame 7: Frame offset list in 94 starts at $9E724. Frame 7 offset: $9E724 + (7*2) = $9E732 address -> 06 DE We also need frame 8 offset: $9E724 + (8*2) = $9E734 address -> 06 FE $06FE - $06DE = $20 (32 decimal). $20 / 8 (8 bytes per sprite) = 4 sprites in frame Same # of frames as 92. Let's get the sprite data bytes for each sprite in the frame: Sprite data offset list in 94 starts at $9E724. Frame 7 offset = $06DE $9E724 + $6DE = $9EE02 Since we found that there are 4 sprites, we need 32 bytes of data, split into 8 bytes per sprite: Data: FF FA FF F2 00 33 40 09 00 12 FF F2 00 39 40 00 FF FC FF EA 00 3A 40 04 00 04 FF E2 00 3C 40 00 Let's break it up: Sprite 1: FF FA FF F2 00 33 40 09 X Global - FF FA - -6 decimal Y Global - FF F2 - -14 decimal Tile data offset - 00 33 - $33 x $20 = $660 Palette/Flip - 40 - no flip, home team palette (can be changed later) Sizetab - 09 - 3x2 sprite Sprite 2: 00 12 FF F2 00 39 40 00 X - 00 12 - 18 decimal Y - FF F2 - -14 decimal Tile data offset - 00 39 - $39 x $20 = $720 Palette/Flip - 40 - no flip, home team palette (can be changed later) Sizetab - 00 - 1x1 sprite Sprite 3: FF FC FF EA 00 3A 40 04 X - FF FC - -4 decimal Y - FF EA - -22 decimal Tile data offset - 00 3A - $3A x $20 = $740 Palette/Flip - 40 - no flip, home team palette (can be changed later) Sizetab - 04 - 2x1 sprite Sprite 4: 00 04 FF E2 00 3C 40 00 X - 00 04 - 4 decimal Y - FF E2 - -30 decimal Tile data offset - 00 3C - $3C x $20 = $780 Palette/Flip - 40 - no flip, home team palette (can be changed later) Sizetab - 00 - 1x1 sprite Let's use the tile data offsets to find the starting tile data address in the ROM for each sprite: Sprite tiles start at $5DE84 in 94. Offsets: 1 - $660 -> $5DE84 + $660 = $5E4E4 (need 6 tiles, so 192 decimal bytes) 2 - $720 -> $5DE84 + $720 = $5E5A4 (need 1 tile, so 32 bytes) 3 - $740 -> $5DE84 + $740 = $5E5C4 (need 2 tiles, so 64 bytes) 4 - $780 -> $5DE84 + $780 = $5E604 (need 1 tile, so 32 bytes) Let's grab the tiles (using VAN home palette, $4828): And assemble them using the X and Y globals, with a reference point and the Hot Spot: Success! Frame 7 from 92 and 94 both look the same here. Adding sprites? Is it possible? Yes, it is! I added the fighting sprites from 93 into 94 here: This is for another topic though. Because all the frame and sprite related info are right next to each other in the ROM, the tables need to be moved, the offsets updated, and some hard coded pointers need changed. A topic for another day. Table and Data Locations: NHL92: SPAList starts at $35B6 (start of frames.asm in ROM) Frame data starts at $3D5F4 (first frame) Sprite tiles start at $45F16 NHL93 (v1.1 ROM): SPAList - $4DBE-$6446 ($16B8 long) Sprite tiles - $3A3B0-$6FAF0 ($35740 long) Frame sprite data offsets - $6FAF0-$70006 ($516 long, first 2 and last 2 bytes are 00 00, so $512 bytes are used for 649 frames) Sprite data bytes - $70006-$743FE ($43F8 long, 2175 total sprites) Hotlist table - $743FE-$74910 ($512 long) NHL94: SPAList - $5B1C-$76B2 ($1B96 long) Sprite tiles - $5DE84-$9E724 ($408A0 long) Frame sprite data offsets - $9E724-$9EDC2 ($69E long, first 2 and last 2 bytes are just used for start and end, so $69A bytes are used for 845 frames) Sprite data bytes - $9EDC2-$A44CA ($5708 long, 2785 total sprites) Hotlist table - $A44CA-$A4B54 ($68A long, which is 16 bytes shorter, 8 frames worth of XY Hot Spots, than the number of frames. The last 8 frames are the extra arrows and stars for the 3rd and 4th player, and they do not have hotspots.) Big time credit and thanks go to @McMarkis and @bcrt2000. The 3 of us worked hard figuring this all out!
  13. I looked into this more, I'll prob post some info on how acceleration and top speed works soon. In 94, where we have a 0-30 range for Speed, there are only 15 entries in the MaxSpeed table. So, when the value is divided by 2 (shifted right by 1 bit), it will check if the Speed value was odd. Since there's no way to handle remainders, it needs to do extra math. So if it finds the starting Speed value was odd, it will take the difference between the 2 steps, divide that by 2, and add it to the lower MaxSpeed entry (So that it is halfway between the 2), and use that for MaxSpeed. Why are the MaxSpeed values squared? Because it is adding the squares of the new X and Y velocities and using that number to compare to MaxSpeed. Also, should be noted, if the player has the puck and is joypad controlled, their velocity adjustment will be halved. So they will be slower to get up to speed than without the puck (does not affect CPU players carrying puck though)
  14. Nice experiment! Just for reference, in the 92 source code (92 uses a 0-15 value for attributes), max speed table is listed like this: MaxSpeed ;max speed values for each rating level 0-f dc.l ((0+20)*250)^2 dc.l ((1+20)*250)^2 dc.l ((2+20)*250)^2 dc.l ((3+20)*250)^2 dc.l ((4+20)*250)^2 dc.l ((5+20)*250)^2 dc.l ((6+20)*250)^2 dc.l ((7+20)*250)^2 dc.l ((8+20)*250)^2 dc.l ((9+20)*250)^2 dc.l ((10+20)*250)^2 dc.l ((11+20)*250)^2 dc.l ((12+20)*250)^2 dc.l ((13+20)*250)^2 dc.l ((14+20)*250)^2 dc.l ((15+20)*250)^2 Just to give you an idea on how it calculated the values. 94 values are a little different (they are the same as 93, which also uses a 0-15 value for attributes). The calculation is ((X + 20) *275)^2 where X = In game Spd value / 2. (Max is 30 for the in game values) In game Spd value = Spd attribute (0-6) * 5 + Hot/Cold Bonus (which could be anywhere from +3 to -4). For example, a 5 Spd rated player 5 * 5 = 25. Let's say they have a -3 Hot/Cold, so 25 + -3 = 22. 22 / 2 = 11. So the MaxSpeed would be ((11+20) * 275)^2 = 8525^2 =72,675,625 or $0454F129. But, there is a bug in the game where the game thinks the crowd meter is broken. So the in game Spd value gets a +2 bonus, which would change the 11 to 12. So now we have ((12+20) * 275)^2 = 8800^2 =77,440,000 or $049DA400. The player can go above this speed temporarily (for example when speed bursting, or even during normal skating a little bit), but they will not get any acceleration if they are above it, so they will decelerate until they are below the max speed and continue that cycle. So, my question for your tests is did you make the ROM a static ROM (no hot/cold)? Because that would affect your numbers as well
  15. A League Champs: S1: Uncle Seth S2: angryjay93 S3: Uncle Seth S4: kingraph S5: angryjay93 S6: Uncle Seth S7: Corbettkb S8: angryjay93 S9: angryjay93 S10: Schmidt B League Champs: S1: sonoffett87 S2: hokkeefan2 S3: Tickenest S4: szpakman S5: kazelegend S6: Wittgenstein S7: INDIO * (replaced skankhunt42) S8: jv S9: kidswasted S10: Stantonator C League Champs: S2: INDIO S3: TecmoDPS S4: Charlesworth S5: Big Valboski S6: skankhunt42 S7: SOH S8: NewJerseyKiller S9: Stantonator S10: niuhuskie224 Draft Cameos: S2: Jeremy Roenick S3: Luc Robitaille (no show) S4: Russ Courtnall S5: Teemu Selanne CDL Season 11 I plan on starting this league in a few weeks. I will give it till 7/3/25 for S10 players from A, B, and C Leagues to let me know if they are returning. Once the time is done (or I've heard from everyone), there may be some open spots. Open spots in A will be filled by previous A league caliber players (ones who have played in A and still qualified to stay in A), or I will do a play in of sorts from top B teams to determine replacements (though I doubt we will need this). Open spots in B will more than likely be filled by C league players from S10, with a play in as well. This all depends on who signs up. There may be exceptions to the rule (where someone of B quality returns, they would participate in the play-in as well). Note, the only thing holding up the draft will be A and B signups. Once this is figured out, I will announce the start of the draft (along with the draft order). Draft show TBD. C league signups will be open up to the day of the A/B League Draft, and are open to anyone. Current league cap is 26. C league signups will be limited to the number of A/B league teams. Players who completed their seasons in CDL10 get priority in spots. Any open spots will be discussed once I hear from the returning coaches. Relegation and Promotion The criteria is still the same. Last season we had 13 teams in A, and 13 in B. The champion from C is guaranteed to move up to B, and the champion from B is guaranteed to move up to A. The coach with the worst record in A will be moved to B and the coach with the worst record in B will be moved to C. Depending on signups, the top performing regular season teams from B and C will have a chance to be promoted as well, in which case the 2nd worst teams in A and B will be moved down to B and C respectively. If the winner of the league was also the top performing reg season team, the next best reg season team will be chosen in this scenario. This is not a guarantee like winning the league is. As of right now: Moving up to A - Stantonator (B Champ) and whalers (best reg season record) Moving down to B - kidswasted (0.104 win Pct) and Chris O (0.250 win Pct) Moving up to B - niuhuskie (C Champ) and TecmoJon (.750 win Pct) Moving down to C - LeifErikson (.354 win Pct) and skills324 (.375 win Pct). If there is a play-in for B, both will be allowed to play. Again, this could change, depending on who is returning, signing up, etc. There could be more or less movement. Sign-ups C League is more of like a "farm" league, where coaches will choose (via a Team Selection Draft) one of the drafted A/B teams to play a season with, continued with a playoff. The C Champ and the team with the best reg. season record will be promoted to B league the following season, and allowed to partake in the A/B draft. This league is geared more towards the guys who are more novices at the game, especially draft leagues. They can choose a team during the Team Selection Draft that fits their abilities. C is open to any player whose setup has been CONFIRMED to work (they will have a role assigned to them in Discord). Sign-up deadline will be the day of the A/B League Draft, and cannot exceed the total amount of A/B teams (currently 26). League spots are for CDL10 returnees first. Empty spots in A and B will be filled by my choice; see first paragraph. Draft News Once A/B returnees all report, and I fill out the remaining spots, I will post the draft order. We are starting a new set of seasons in CDL with CDL11. I decided to make changes to the draft process. CDL11 will have an entirely random draft order, just like how CDL1 was. Starting with CDL12, the draft will be done differently. I haven't completely decided on what to do, but there will be a lottery for the top few picks, instead of going strictly by record. As far as the draft show, we will do a post A/B draft show, analyzing the teams and hopefully giving some insight that can help the C league players before their Team Selection Draft. League Info Stuff not changing from S10: - 5 min periods, 10 min OT, 1 min Penalties - Quicker control and longer range of Goalie - 0 Team Bonuses and PP/PK bonus - Warm/Cool Bonus (very slight hot/cold, attributes in the Edit Lines screen will be accurate) - Small Home Team Bonus in the Playoff ROM (+1), to make home ice a little more meaningful. - Reduced Penalty Shot timer - Goalie Boosts - +1 to Agl, +1 to DfA, +1 to PkC (previously +1 Agl, and +1 to all Stk/Glv in CDL1-9). All Stk/Glv will be normal classic values. Rules Warning! There are glitches in the game. A majority of them are unavoidable. Some of them are able to be triggered. In this league, anything goes. If a glitch causes a goal against you, you are free to talk about it with your opponent and decide an outcome, and come to an agreement. But there is no rule stating they have to reciprocate the goal. Double penalty shot? Play to the whistle. Next time blow your opponent out if he does something to piss you off. One rule regarding pulling the goalie - It must be done while having possession, or before a faceoff in the offensive or neutral zone. Let me know if you are returning or not, by posting below, or in this Discord thread - CDL11 Signups and Returnees. If I don't hear from you, I will assume you are not returning. List of returnees:
×
×
  • Create New...