Jump to content
NHL'94 Forums

chaos

Admin
  • Posts

    2,102
  • Joined

  • Last visited

  • Days Won

    80

Posts posted by chaos

  1. 4 hours ago, AdamCatalyst said:

    @chaos I'm working on the shot accuracy variance system right now, and I just wanted to verify something.

    - Take the final shot speed, divide by 16…

    When calculating accuracy variance, what is the "final shot speed'? If I understand correctly, it is the last value produced before the *68 velocity multiplication. OR is it the value before being scaled up, the 31 for one-timers, etc.?

    BTW, I did some tests setting the breaking the glass threshold with my velocity calculations. It plays out exactly as expected now. Thanks for your help!

    The *68 is done after adjustments are made, so this happens before (during the non-perfect shot calc)

    The *68 is just basically converting the shot speed to a pixel calculation

     

    This is in the end of HOCKEY1.ASM source code
    1024/15 (which is commented on that *68 line) = 68 decimal
    
    ;Vel*((16*60)/$10000)=pix/sec
    ;Vel*(15/1024)=pix/sec

     

  2. 5 hours ago, AdamCatalyst said:

    Paging Mr. @chaos. When I calculated Perfect Shot probabilities, I used the following maths: 

    Therefore, a player with a 6/15 shot accuracy will have a ~71% (16+30-14 / 16+30) chance of making a perfect shot.
    
    A player with 0 Shot Accuracy will have a ~12.5% (16+0-14 / 16+0) chance of making a perfect shot.

    Your numbers are noticeably different.

    I can't figure out how you arrived at yours. Can you see what I am missing?

    EDIT: I also noticed that you listed #16 as $14, which also breaks my brain. In my notes it was ShA + $10.

    Yeah, I messed up here, I'll fix the original post. Starting value is $10 or 16 decimal. Updated post and table.

    Your math in the first example is wrong, it would be 69.6% chance with a 6 ShA

    • Thanks 1
  3. 3 hours ago, DavidG said:

    Hi, I have a very small role in this story. I worked on the SNES version of NHL 94 - in a supporting role - It's all very foggy now but as I recall being a young programmer, I was is awe of Jim Simmons - who quietly set down the foundations for Madden and NHL.  No offense to Mark Lesser but I didn't even know who he was until much later. It's weird that there are two versions of NHL 94 - one for the Genesis and one for the SNES but two completely different development stories. Mark gets a lot of limelight today and I think he's good and all but kind of only because Jim Simmons is reclusive and no one from EA Canada seems to talk much either.  In any case you can see from 
    https://www.mobygames.com/game/12399/nhl-94/credits/snes/
    that Mark Lesser isn't anywhere in the credits for the SNES version.  The way it worked is that features were fed to both teams and they implemented them. I may be wrong but I don't think that Amory Wong who was the lead of the SNES version talked much to Mark at all!   I'm pretty sure that Jim made the NHLPA (93) version for the Genesis and the SNES was ported at Park Place by William V. Robinson according to MobyGames and then we at EA Canada got the source code to do the NHL 94 version where we replaced the 'engine' with our design but kept the secret sauce of the gameplay but then went on to extend it. Amory played hockey and was a huge fan as you can imagine a Canadian team working on a hockey game was really cool.  But in the end I have endless admiration for the mysterious Jim Simmons who also did audio. That being said Jim didn't work in a vacuum and as posted earlier, the other staff around and producers all had their mark on the original product - still in those days Jim was kind of a one man band as far as getting the code down. 

    And all that being said, it is somewhat intriguing that a young man who was more of a Audio and sound effects guy at Cinemaware could suddenly burst onto the scene as a lead programmer and set down the foundation of two massive franchises with no prior sports game experience. But he did work as a Audio Tech and Sound Effects guy on Cinemaware's TV Sports Football and Basketball. Is it possible that he was inspired by those products? We may never know. 

    Thanks for posting!

    Since the original NHL Hockey source code was released last year, I've been comparing the code to the disassembled 94 Genesis code. A vast majority of it is the same, and I'm sure most stuff that is not, were part of the NHLPA93 code. I can tell what was added by Mark Lesser, but this is Jim Simmons' and team's game for sure. 

    I do have to give a lot of credit to Lesser and to the SNES 94 team though! It must be difficult having to work off code that was not previously written by them, and especially with a deadline. Source code or not, you still have to understand what is going on and where to put your changes. And the changes made the game much better than the previous entry! (Especially SNES)

  4. 13 minutes ago, AdamCatalyst said:

    oh!!!! OK, ALL makes more sense now. No, I don't even know how to test this, I've been working on my own mental model of the code, from the stuff that you have shared. I'm proud to say I got most of it right, but most of it right was still wrong. Thanks again.

    Thank you! Made me look at it again, and I figured out a mistake!

    • Like 1
  5. 11 hours ago, AdamCatalyst said:

    OK, I see what's happening here. I was seeing this as wind-up maxing out at 16, unless a player has a ShP rating below 20, in which case they are limited to 8. I couldn't find the bonus wind-up frames for ShP≥20, because I was perceiving it as a limiting of wind-up frames for ShP<20. I was getting confused by language usage, but it is exactly the same thing, except for one small detail…

    …you mentioned a 6 decimal difference, yet I'm seeing an 8 decimal difference. Any more ideas on what I'm missing?

    Did you test this?

    I tested again, and I found I am off a little. This is because of the frame counter. You usually expect to add 1 each frame. But sometimes, 2 frames elapse before it gets back to this function. Depending on where those 2 frames fall, sometimes you will get an extra frame added (if it is the same frame when the player's frame counter is reset).

    Let's do the math:

    Each shot animation has a 5 frame timer. Once the frame timer is reset, the frame animation index changes by 4. 

    A ShP value of  >= 20 will get 5 frame animation index changes before to stops adding to the passspeed. (0, 4, 8, C, 10) before it ends at 0x10. 

    ROM:0000C23E 0C6B 0010 005A                      cmpi.w  #$10,$5A(a3)
    ROM:0000C244 6C00 002C                           bge.w   _end            ; past full windup so no more passspeed

    So that's 5 + 5 + 5 + 5 = 20 frames added to passspeed (shot speed whatever you want to call it).

    A ShP value of < 20 gets 4 frame animation index changes (0, 4, 8, C) before it ends at 0xC. 

    So that's 5 + 5 + 5 = 15 frames added to passspeed (shot speed). But, since the check for ShP is not until AFTER the frame is added, they get an extra frame counted. 

    5 + 5 + 5 + 1 = 16 frames added.

    Again, these are normal conditions. 

     

    ROM:0000C23E cmpi.w  #$10,$5A(a3)
    ROM:0000C244 bge.w   _end                            ; past full windup so no more passspeed
    ROM:0000C248 add.w   d7,(passspeed).w
    ROM:0000C24C cmpi.b  #$14,$6C(a3)                    ; 6C = shot speed
    ROM:0000C252 bge.w   _checkcbut
    ROM:0000C256 cmpi.w  #8,$5A(a3)                      ; SPANum
    ROM:0000C25C bgt.w   _chganim

    So in a normal situation, the difference is not 6, but it's 4. This will vary +/-1 sometimes because of the frame counter and the player's frame timer.

    You can see, the compare to 0x10 of the frame animation index, and the 0x14 of the ShP are greater than/equal, and the compare for 8 of the frame animation index is a greater than (which will let it get to 0xC before it would branch).

    I will update the original post.

    • Thanks 1
  6. 1 hour ago, AdamCatalyst said:

    FYI, @smozoma did the 0-15 by just altering the multipliers, so rather than *5 preprocessing a 0-6 values it does *2 for a 0-15 value, so it scales with the same effective minimum and maximum. Every time I get deeper in the code and think I found a circumstance where I think he may have gotten it wrong, I marvel at how beautiful perfect it works.

    Yeah, I know there aren't actually any decimals used, it's just easer for me to think of it this way when I am modifying things. There is still a gap between when you wrote and what I understand. I'll go bang my head against the wall and take a look again in the morning.

    Thanks for sharing all this as always!

    EDIT: ASASAAHAHAHA! I see what I did. I forget to pre-multiply 0-15 x2. If I do that my numbers are the same as yours if I do that. OK, now I need to go find this full wind up bonus pass speed thing.

    Yeah, he really only had to do it in one spot, where the attributes are loaded in. So that makes sense.

    ROM:0000C224                     ; shot input
    ROM:0000C224
    ROM:0000C224                     ShotMode:                               ; CODE XREF: ROM:0000B754↑j
    ROM:0000C224                                                             ; assshoot+28↓j ...
    ROM:0000C224 0C6B 001C 005A                      cmpi.w  #$1C,$5A(a3)	 ; check animation index
    ROM:0000C22A 6C00 0048                           bge.w   prepshot        ; end of animation so shoot
    ROM:0000C22E 0800 0003                           btst    #3,d0           ; checks dpad for direction
    ROM:0000C232 6600 000A                           bne.w   _ss0
    ROM:0000C236 0240 0007                           andi.w  #7,d0           ; pass the first 3 bits of d0
    ROM:0000C23A 31C0 BEDC                           move.w  d0,(passdir).w  ; set shot direction
    ROM:0000C23E
    ROM:0000C23E                     _ss0:                                   ; CODE XREF: ShotMode+E↑j
    ROM:0000C23E 0C6B 0010 005A                      cmpi.w  #$10,$5A(a3)
    ROM:0000C244 6C00 002C                           bge.w   _end            ; past full windup so no more passspeed
    ROM:0000C248 DF78 BEDE                           add.w   d7,(passspeed).w
    ROM:0000C24C 0C2B 0014 006C                      cmpi.b  #$14,$6C(a3)    ; 6C = shot speed
    ROM:0000C252 6C00 000C                           bge.w   _checkcbut
    ROM:0000C256 0C6B 0008 005A                      cmpi.w  #8,$5A(a3)      ; SPANum - animation index
    ROM:0000C25C 6E00 000A                           bgt.w   _chganim
    ROM:0000C260
    ROM:0000C260                     _checkcbut:                             ; CODE XREF: ShotMode+2E↑j
    ROM:0000C260 0802 0005                           btst    #5,d2           ; 5 = #cbut
    ROM:0000C264 6700 000C                           beq.w   _end            ; button hasnt changed so continue windup
    ROM:0000C268
    ROM:0000C268                     _chganim:                               ; CODE XREF: ShotMode+38↑j
    ROM:0000C268 446B 005A                           neg.w   $5A(a3)         ; end windup and swing through
    ROM:0000C26C 066B 001C 005A                      addi.w  #$1C,$5A(a3)    ; Add to SPANum
    ROM:0000C272
    ROM:0000C272                     _end:                                   ; CODE XREF: ShotMode+20↑j
    ROM:0000C272                                                             ; ShotMode+40↑j
    ROM:0000C272 4E75                                rts
    ROM:0000C272                     ; End of function ShotMode

     

    • Thanks 1
  7. 7 hours ago, AdamCatalyst said:

    Interesting! I've been working on revising the shooting logic for a bit, but I completely missed the bit about extra windup power being added on to some slap-shots! Where is that in the code?

    FWIW, here was how I understood it below. I know this sounds pedantic, but in your table listed above, you labeled it "Shot Speed", but I believe the calculated value is still technically "Shot Power" until it is multiplied by 68 to get the base "Shot Speed" of the puck, before any changes are made to compensate for shooting range.

    ---

    Forehand Shot, Even ShP Number
    ShotPower = (ShP/2 + 20) × PassSpeed × 21065 ÷ 65536 × 68

    Forehand Shot, Odd ShP Number
    ShotPower = [(ShP/2 + 20) × PassSpeed × 21065 ÷ 65536 + PassSpeed ÷ 16] × 68

    Where PassSpeed =
    15 (minimum/no windup)
    23 (medium/partial windup)
    31 (maximum/full windup)

    ---

    Backhand Shot, Even ShP Number
    ShotPower = (ShP/2 + 20) × (PassSpeed × 0.75) × 21065 ÷ 65536 × 68

    Backhand Shot, Odd ShP Number
    ShotPower = [(ShP/2 + 20) × (PassSpeed × 0.75) × 21065 ÷ 65536 + (PassSpeed × 0.75) ÷ 16] × 68

    Where PassSpeed × 0.75 =
    11.25 (minimum/no windup)
    17.25 (medium/partial windup)
    23.25 (maximum/full windup)
    ---

    One-Timer, Even ShP Number
    ShotPower = (ShP/2 + 20) × 31 × 21065 ÷ 65536 × 68

    One-Timer, Odd ShP Number
    ShotPower = [(ShP/2 + 20) × 31 × 21065 ÷ 65536 + 31 ÷ 16] × 68

    ---

    Base Factor = (ShP/2 + 20)
    Backhand passspeed = regular passspeed * 0.75 (except for one-timers)
    One-timers always use passspeed 31
    ```
    
    | ShP | Min B. | Min F. | Mid B. | Mid F. | One-T. B. | One-T. F. | Max B. | Max F. |
    |-----|--------|--------|--------|--------|-----------|-----------|--------|--------|
    | 0   | 4,918  | 6,557  | 7,541  | 10,053 | 13,552    | 13,552    | 10,164 | 13,552 |
    | 1   | 5,037  | 6,715  | 7,723  | 10,296 | 13,850    | 13,850    | 10,351 | 13,850 |
    | 2   | 5,155  | 6,874  | 7,905  | 10,539 | 14,149    | 14,149    | 10,540 | 14,149 |
    | 3   | 5,274  | 7,032  | 8,086  | 10,782 | 14,447    | 14,447    | 10,728 | 14,447 |
    | 4   | 5,392  | 7,190  | 8,268  | 11,025 | 14,745    | 14,745    | 10,917 | 14,745 |
    | 5   | 5,511  | 7,348  | 8,449  | 11,267 | 15,044    | 15,044    | 11,105 | 15,044 |
    | 6   | 5,629  | 7,506  | 8,631  | 11,510 | 15,342    | 15,342    | 11,293 | 15,342 |
    | 7   | 5,748  | 7,664  | 8,812  | 11,753 | 15,641    | 15,641    | 11,481 | 15,641 |
    | 8   | 5,901  | 7,868  | 9,046  | 12,063 | 16,039    | 16,039    | 11,764 | 16,039 |
    | 9   | 6,020  | 8,026  | 9,228  | 12,306 | 16,338    | 16,338    | 11,952 | 16,338 |
    | 10  | 6,138  | 8,184  | 9,409  | 12,549 | 16,636    | 16,636    | 12,140 | 16,636 |
    | 11  | 6,257  | 8,342  | 9,591  | 12,792 | 16,935    | 16,935    | 12,329 | 16,935 |
    | 12  | 6,375  | 8,500  | 9,772  | 13,035 | 17,233    | 17,233    | 12,517 | 17,233 |
    | 13  | 6,494  | 8,658  | 9,954  | 13,278 | 17,531    | 17,531    | 12,705 | 17,531 |
    | 14  | 6,612  | 8,816  | 10,135 | 13,521 | 17,830    | 17,830    | 12,893 | 17,830 |
    | 15  | 6,765  | 9,020  | 10,370 | 13,831 | 18,228    | 18,228    | 13,176 | 18,228 |
    
    **Column Details:**
    - Min B.: Minimum Backhand (passspeed = 11.25)
    - Min F.: Minimum Forehand (passspeed = 15)
    - Mid B.: Medium Backhand (passspeed = 17.25)
    - Mid F.: Medium Forehand (passspeed = 23)
    - One-T. B.: One-Timer Backhand (passspeed = 31)
    - One-T. F.: One-Timer Forehand (passspeed = 31)
    - Max B.: Maximum Backhand (passspeed = 23.25)
    - Max F.: Maximum Forehand (passspeed = 31)

     

    Ok, so you're missing a few things. 

    I'm referring it as "Shot Speed" to make it easier for people to understand. In reality, it's using the variable called passspeed, and if you look in the 92 source code, it refers to the value stored in passspeed as "shot speed" on a comment before being multiplied by 68 (which is 1024/15). I'm just trying to keep the naming conventions the same as the source code, so if someone wanted to look it up in the 92 source code, they can.

     

    
    ShotMode subroutine - 0xC224
    
    0xC24C is the comparison for ShP:  
    cmpi.b #$14, $6C(a3) (which is where ShP value is stored for player struct)

    There are no decimals up in here. To get 25%, it divides by 4 and subtracts that from the value (quotient only, no remainder is used). I should have been more clear about that. I will edit.

    Ex: 25/4 = 6, 25-6 = 19

    Also, I'm not sure about this 0-15 scale you are using, and how it is adjusted to the original 0-6 scale. In 92, which has a 0-15 scale, the divide by 2 is not there.

     

    If I were to take a one-timer with a 6 ShP player:

    6 ShP = 30 ShP value
    
    30 / 2 = 15 
    15 + 20 = 35
    
    35 * 31 (one-timer fixed value) = 1,085
    1,085 * 21,065 = 22,855,525
    22,855,525 / 65536 = 348
    
    348 * 68 = 23,664

     

  8. 4 hours ago, smozoma said:

    wtf

    Is that some kind of randomizer trick, so shot speed isn't the same every time even if the hot/cold and windup is the same?

    Hahaha. No, it's actually some kind of calculation. The source code lists it as $4000 * 35/45. I don't know exactly where it's pulling those numbers from. Then it's just using the upper part of the result for some reason.

  9. The Art of Shooting in NHL94

     

    "How the hell did that shot not go on net?" is one of the common phrases to say when playing this game. Well, the 94 RNG gods have screwed with you more times than you can count. Is there a way to always make sure your shot goes where you want it to? Well, no. But the higher the Shot Accuracy (ShA) of the player the more likely you will get a "perfect shot".

     

    Aiming

    There are 9 places you can aim your shot at (this is looking at the top goal, when shooting up):

    Passdir.png

    0 is up on the Dpad, 1 is top right on the Dpad, 2 is right on the Dpad, and go clockwise to get the rest. When you are not pressing a direction, 8 is the default direction (middle of the net). This direction is set while holding the C button to shoot. The game will remember the LAST direction you pressed and use that as your shot direction. So you can start your windup pressing right on the Dpad, and let go of the Dpad before the shot, and it will still shoot to position 8. You can press right on the Dpad, hold C to windup, then at the last second press left on the Dpad, and it will shoot at position 6. As long as you are still in windup, the direction can be changed.

    There is no aiming for one-timers (in Genesis at least). The CPU player and the one timer players go through a separate routine for aiming. It will use the current position of the goalie and aim to where the goalie is not (the game actually calculates the angle of where the puck is and the goalie's angle relative to the goal posts, and will aim away from that). There are 4 spots that will be used for aiming in this situation: Position 0, 2, 6 and 8 (if there's no goalie).

     

    Windup

    When you hold the shot button down, the player will go into a windup animation. If the player has a >= 4 Shot Power (ShP), they will have EXTRA frames to their windup animation. Why is this significant? Every frame, there is 1 added to a variable called passspeed when the shot button is held(or shot speed; passspeed is what the source code calls it, as it uses the same variables for passing and shooting).

    The base shot speed is 15 decimal. For players with < 20 ShP value (< 4 ShP attribute), holding down the C button for the duration of the shot windup, their max starting shot speed will be 31 decimal. For players with >= 20 ShP value (4 ShP attribute or higher), holding down the C button for the duration, their max starting shot speed will be 35 decimal. This may vary +/- 1 due to frame discrepencies.

    I list the ShP value, because this is the value after Hot/Cold is applied. So, for example, if you have a cold player who normally is a 4 ShP, he will not get the extra windup (because his total ShP will be 4 * 5 - Hot/Cold, and if cold, it will be less than 20). The only bonus that ShP gets is Hot/Cold. The Hot/Cold bonus is the regular ROM is between -4 and +3.

    Here's 2 screenshots showing the difference in max windup between ShP. Notice how high the stick goes:

    3ShP.png                                          5ShP.png

     

    Note, because of the extra windup animation, a player with 4+ ShP will take longer to shoot a full power slapper than other players. Once the windup is completed, it's time to shoot the puck. 

    One-timers are different. Since there is no windup involved, there is no extra addition to the base shot speed. Because of this, the base shot speed of all one-timers is 31 decimal. 

     

    Shot Speed

    Once the stick hits the puck, it's time to shoot. 

    If you are shooting a backhand, your base shot speed will be decreased by 25% (one-timers do not get this decrease). It does this by taking the value, dividing it by 4 (quotient only, remainder is dropped), and subtracting that result from the base shot speed.

    Next, it will determine the final shot speed. This is a complex calculation. If you are playing with line changes on, the shot speed will be scaled depending on the player's energy level. I'll list the calculation here:

    - Take ShP value (ShP attribute * 5 + Hot/Cold, a value between 0-30 dec)
    - Divide ShP value by 2
    - Scale the result based on energy level of player
    - Add $14 (20 dec)
    - Multiply result by shot speed (from the windup, or 31 dec if one-timer)
    - Multiply result by $5249 (21,065 dec)
    - Swap the upper and lower words of the result (swap the upper and lower 2 bytes)
    - It then does a minor correction if the ShP was an odd value (because when dividing, it drops the remainder)

     

    To make it easier, here is a table with the shot speeds for full slapshots and for one-timers:

    Shot Speeds.png

    Notice the difference between a base 3 ShP and a base 4 ShP for Shot Speed. This is due to the extra windup and extra frames added to the initial value. Notice how one-timers do not get this boost, and the Shot Speed for a one-timer by a player with a 6 ShP is close to a 4 ShP slapshot. Also, ShP of 3 and below have the same speed regardless if its a one-timer or a full slapper.

    Once the Shot Speed is determined, it's time to direct the puck in the right direction.

     

    Perfect Shot, or Not

    A "perfect shot", as named by the 92 source code, is a shot that goes EXACTLY to the spot on the net you were aiming at. This does not mean an instant goal, it just means where you aimed, is where it's going. It can be blocked along the way, tipped, saved, etc. It's just traveling along a straight line to the spot on the net you aimed at.

    There are some situations where the calculation is ignored, and you will either guaranteed a perfect shot, or you are guaranteed not to get one:

    • Highlights always get a perfect shot
    • Shootouts always get a perfect shot
    • In order to be able to have  a chance at a perfect shot, the puck needs to be within 200 pixels of the spot you are aiming at
      • If you are within that 200 pixel distance, and your goalie is pulled, you get a perfect shot
      • Outside of 200 pixel distance, no chance at a perfect shot

    Here's a photo showing the range you need to be in for a chance at a perfect shot:

    Perfect Shot.png

     

     

    OK, so the shot passes the 200 pixel test, now what?

    The calculation for a "perfect shot" uses the players ShA and some RNG:

    - Start with 16 decimal ($10 hex)
    - Add the ShA value (ShA attribute * 5 + Bonuses (Hot/Cold, PP, PK, Team Bonus), within 0-30 dec range)
    - RNG the result
    - If the result is greater than 14 decimal ($E hex), you got a perfect shot!

    Here's a table showing base attribute values, and the probability of getting a perfect shot:

     

    Perfect Shot.png

    So what if you missed out on the perfect shot?

    No worries! There's still a chance that your shot will go on net, more than likely not to the spot you were hoping for. "Adjustments" are made to the shot speed, based on the player's ShA, a starting value, and some RNG. Adjustments are made to the X, Y, and Z velocities of the puck.

    First, an adjustment value is calculated:

    - Take the final shot speed, divide by 16
    - Divide ShA (0-30 value) by 2 and subtract it from the shot speed result
    - Add 16 dec to the result
    - Multiply the result by the straight line distance the puck will travel to the net
    - Divide that result by 64
    - Check if the straight line distance was more than 250 pixels, if so, divide the result by 2 (extra shot accuracy adjust)

     

    Once we have the adjustment value, it will be used to modify the aiming spot of the puck. Earlier, the game saved X, Y, and Z locations of where you are aiming at. The adjustment value will be +/- RNGed for X and Y (so -value < RNG result < +value) and will be added to the aiming location. 

    There are limits to how much it can adjust:

    - Max adjustment limit in X - +/- 136 pixels

    - Max adjustment limit in Y - +/- 60 pixels

    Z is slightly different, as it will take the adjustment value, divide by 2, and RNG the result (but only a positive RNG result, obviously the puck isn't going through the ice)

    If you shot behind the net (Y distance to goal line would be negative) or on the goal line, the adjustment value is halved before making the RNG calc for Y.

    After all this, perfect shot or not, the puck velocity is adjusted based on the shot speed and distance to the spot. There are checks for maximum velocity in X, Y and Z, and these are set if needed.

     

    There is a neat little adjustment made to the Z velocity based on a special circumstance. The game will check if you are in close with the puck (below the face-off dots). If you are aiming for top shelf (locations 0, 1 or 7), and the goalie is in a pad stack save, or if the goalie has been pulled (open net), it will give a little boost to the Z velocity. Watch the slow mo GIF:

     

    Top Shelf.gif

     

    Without the extra boost to Z velocity, the puck would have enough time to gain height from that close.

    • Love 4
    • Thanks 3
  10. Centers, wingers, and defensemen have specific offensive and defensive assignments, depending on where the puck is, and what team is in possession of it. In normal play, the skaters will cycle through these two assignments. But, there are special assignments for certain cases (scoring a goal, winning the finals and holding the cup, receiving a pass, in a faceoff, on a breakaway, etc.). These are temporary assignments, and once complete, will switch the player back to the offense/defense cycle.

    It should be noted that a joypad controlled skater does not get assignments. 

    assnearest - Player closest to the puck

    One such assignment is labeled "assnearest", which in the 92 source code is described as " a special assignment used for the player who is nearest the puck but doesn't have it".

    - Team playing defense will always have a player in the assnearest assignment.

    - Team playing offense (puck carrier's team) will not have one. (Technically, the puck carrier gets this assignment for breakaway testing and exits it before doing anything).

    - When the puck is loose (including during a pass or shot), both teams will have a player with the assignment.

     

    What's special about this assignment?

    - The only AI controlled players that can check in the game are defensemen in their defense assignment (assdefd) and players in the assnearest assignment

    - The assignment uses Offensive Awareness as a timer. And since part of the assignment is determining who is the closest to the puck, the Awareness timer will affect other players on their team (Low awareness = more time before switching the assignment to a player who is closer to the puck)

    - When the puck is located in the "slot", there are special effects: the OfA timer is divided in half, the player will have a chance to get a +6 or +8 to their Spd value (which is equivalent to a +1.2 or +1.6 to their Spd attribute, not to exceed max 6 attribute), and the player may also get their Chk value doubled (not exceeding the max)

    - There is another timer that is set based on certain conditions (player location to the puck, PP/PK, Chk rating and RNG)

    - The assignment is also used to jump off to others (breakaway, puck carrier). The player will stay in this assignment until it exits it by jumping out, or if it switches it to another player who is closer to the puck.

     

    Reminder, the game considers the slot the shaded area below:

    Slot.png

     

    The assignment routine will take a few different paths depending on conditions. The beginning is always the same. This mostly is for making changes if the assnearest player is currently the puck carrier. It will check conditions for a breakaway, and will change the assignment to assbreakaway if needed (which is just used to check if the breakaway is still good). It will make the changes needed for breakaway (set the flags, add 1 to the team's breakaway attempts, boost the crowd level).

    Side note - There is no longer a breakaway if the breakaway player loses the puck, their Y velocity is 0 (they've stopped skating), or their Y velocity is less than their Y position (meaning they are not moving forward towards the net). Shoutout to @kingraph for finding this.

    If this is a new assignment (player was just assigned this, first run through), there are 2 timers that are set to 0 (Awareness timer and a temporary timer).

    If the player is the puck carrier:

    The player will switch to the puck carrier assignment (asspuckc). The puck carrier will actually switch between the asspuckc and assnearest assignments until something else changes, as assnearest tests for breakaways.

     

    If the player is not the puck carrier:

    The number of frames that passed since last time through (usually 1 frame) is subtracted from the OfA timer (if this is a "new" assignment, the timer is set to 0 before this step).

    If the OfA timer is 0 or less:

    • Reset the timer: 
      Awareness attributes are between 0-6
      
      Assuming no bonuses:
      
      0 OfA = 15 frame timer
      1 OfA = 14 frame timer
      2 OfA = 12 frame timer
      3 OfA = 11 frame timer
      4 OfA = 10 frame timer
      5 OfA = 9 frame timer
      6 OfA = 7 frame timer
    • If the goalie on the player's team is pulled, or if the crowd meter is currently broken, 1 is subtracted from the timer
    • Check if the puckc slot flag is set. This is set when the puck carrier is in the offensive slot area. If they are not, or if there is no puck carrier, the flag is not set 
      • If the flag is set, divide the timer by 2
    • Check if the puck carrier is on the same team as the player. If so, it will assign assnearest to the puck carrier, and end assnearest for the current player. If the puck is loose, or opposite team possession, continue
    • Check if the current player is still the closest to the puck. If not, change the assignment of the player who is to assnearest, and end this assignment for the current player. Note, it will ignore a player who is assigned to receive a pass
    • After this, there is a timer that is set that will be used later (this timer is initially set to 0 if a new assignment). Let's call it the decision timer. This gets a little complicated:
      • - There's a multiplier used for this calc. It starts with a value of 2.
        
        - First, it checks if the player is within a 20 pixel radius of the puck.
        	- If inside the radius, subtract 2 from the multiplier (would be 0 now). If the player is a D, and is inside the 20 pixel 
        		radius, it skips the rest of the timer calc.
        	- If outside the radius, the multiplier stays at 2, regardless if F or D. 
        
        - Check if there is currently a PP (doesn't matter which team)
        	- If not, move to next step (Timer Calculation).
        	- If there is, check if the player is on a PK. 
        		- If so, subtract 2 from the multiplier.
        		- If they are on the PP, add 2 to the multiplier.
        
        Timer Calculation:
        	- 40 decmial to start
        	- Subtract player's Chk value (0-30)
        	- Use the multiplier as a shift of the result (2 would be mult. by 4, 4 would be mult. by 16, -2 would be divide by 4)
        	- RNG the result
        	- Compare it to 2. If higher than 2, do not change the timer. If 2 or less, set the timer to 240.

         

    The decision timer works like this - If the timer is not zero, or if there is no puck carrier, the player will skate to the puck, and along the way, look for checking opportunities (high Chk = more likely to check players). If the timer is zero, they will skate to a spot and get some attribute boosts depending on where the puck is. I'll give a brief explanation down below.

     

    If the OfA timer is not zero (or if it was just reset): 

    • Puck is loose (no puck carrier):  Skate to the puck, look for checking opportunities along the way
    • There is a puck carrier (player is playing defense)Subtract # of frames passed since last time checked (usually 1 frame) from the decision timer.
      • If the Decision Timer is not 0the player will just skate to the puck carrier and look for checking opportunities. 
      • Decision timer is 0: The player will skate to a location keeping themselves halfway between their crease and the puck in Y, and halfway between the puck and center ice in X.
        • If the puck carrier is in the slot area: 
          • The player will get a boost in Spd (a +6 to their Spd value, equal to +1.2 on their Spd attribute), and can also get an extra +2 to their Spd value (+0.4 to their Spd attribute) if the crowd meter is currently broken. This is checked to make sure it doesn't exceed 30 Spd value (6 attribute), if it does, set to max. Boost is only if puck carrier is in the slot. It's a way to "catch up" to the play if they are farther away.
          • If the player is out of the play (the puck carrier is between them and the back of their defensive zone in Y, or they are in between the puck carrier and the back of the zone in Y, but farther than 15 pixels away in X), they will just continue to skate to their spot
          • If they are "in the play" (between the puck carrier and back of zone in Y, within 15 pixels in X), the player gets a double Chk value boost and will look to check 
        • If the puck carrier is not in the slot: The player doesn't get the Spd and Chk boosts, but they will still look for checking opportunities while skating to their spot.

     

    Recap

    • The only AI-controlled players who can check are those players who have the assdefd or the assnearest assignment
    • The team that has puck possession will not have a player with the assnearest assignment (other than when checking if there is a breakaway, and switching to the asspuckc assignment)
    • If the puck is loose, both teams will have a player with the assnearest assignment
    • Offensive Awareness is used as the timer
    • The assignment will check and see if it needs to switch to a different player, depending on who is closest to the puck
    • Higher Chk value = more likely to initiate a check
    • When the puck is loose, both players will skate to the puck, looking for checking opportunities along the way
    • Decision timer is used by a player playing defense (other team has puck)
      • When the timer is reset, it is set for 240 frames (4 real-time seconds), and it decrements every frame
      • If the timer is not 0, the player will skate to the puck
      • If the timer is 0, the player will skate to a spot, halfway between the crease and the puck carrier in Y, and halfway between the puck and center ice in X
        • If the puck carrier is in the slot:
          • The player will get a huge boost in Spd (a +6 bonus, equivalent to a +1.2 to their Spd attribute, not to exceed max)
          • If the player is "out of the play" (the puck carrier is closer to the net than they are), they will just continue skating to their spot, not looking to check
          • If the player is "in the play" (between net and the puck carrier), the player will get a double Chk bonus (not to exceed the max) and look to check
        • If the puck carrier is outside the slot:
          • They will skate to their spot, and look to check along the way
      • The timer is checked to see if it will reset every time the OfA timer is up. The odds of a reset:
        • If the player has high Chk rating, they have a greater chance of a reset
        • If the player is on the PK, they have a greater chance of a reset
        • If the player is a defensemen, and close to the puck carrier (within 20 pixels), they will not reset
        • The timer never resets by itself. So it can count down to 0 and stay there until it is reset, unlike the other timers like the awareness ones

     

    Examples

    The puck is deep in the offensive player's zone here. The 2 wingers playing defense (for Ottawa), are close to the puck carrier. 

     

    assnearest-1.gif

    Watch the RW of Ottawa. He gets switched to assnearest as the LW skates back into the neutral zone. At first, he does a stutter. When he originally switches to assnearest, his Decision timer is 0. , So he acts as if he is going to skate back to a halfway spot between the puck carrier and the crease. Then, he turns around, and skates to the puck to throw a check. His Decision timer got reset, so now he is getting aggressive with the puck carrier. (no bonuses since puck is outside the slot). The LW is doing his asswingd assignment, where he is skating to the same Y location as the puck, but on his side of the rink. Bob Kudelski's Chk rating is low (2 Chk), but he must have gotten a bad (or good, however you want to look at it) RNG roll on his Decision timer.

     

    assnearest-2.gif

     

    In this second example, Jamie Baker is considered the closest to the puck. The puck carrier (Cam Neely) is in the slot area, and Baker is between Neely and the back of the zone. Baker is at his position, which is why he isn't skating (halfway between puck carrier and top of crease). So here, if his Decision timer is zero, he would get double Chk rating and the Spd boost. But, notice how long it takes for him to react, and he's not trying to check, he's skating to the puck carrier and poking at the puck (which is what a player will do when they are "skating to the puck"). It looks like although he had the extra Chk boost (which would give him 4 Chk attribute!), he got some bad RNG rolls, and would not try to check, then the Decision timer got reset, and he decided to skate to the puck.

    Also, there's a rare moment here. Watch Juneau (LW) move from his zone spot to a new spot. This usually doesn't happen, unless the puck passed over a line (blue line, goal line). There's a small chance of this happening, where the first 7 bits of the frame counter have to equal zero, at the same time the player's awareness timer hits zero (mentioned in the asswingo assignment post).

    assnearest-3.gif

    In this one, both Neely and Turgeon are skating towards the loose puck, both with assnearest assignment. You'll notice how Turgeon tries to check Neely a few times (but fails). Neely does not try to check because there is no good checking opportunity (he is not facing anyone to check).

     

    assnearest-4.gif

     

    Here, let's look at Brad Shaw (#4). He is a 2 Spd skater. When Juneau enters the zone on a breakaway, he enters in the slot. Shaw is the nearest defending player. With the puck in the slot, and his Decision timer at 0, he gets a +1.2 to his 2 Spd, useful to catch up to Juneau on this play (he still has 3 Agl which doesn't help him get to speed that quick).

    • Love 1
    • Thanks 1
    • Like 1
  11. All assignments have been found now. There was one I didn't know what it did (highlighted in photo). That one ended up being the assignment for the goalie when the puck goes into the corner (to determine if it's safe enough to skate out to the corner to collect the puck)

    • Like 1
  12. 2 hours ago, AdamCatalyst said:

    Woah, ok, please help me try to get this right.

    The higher the rating, the longer between position checks. BUT, the value used is the opposite of what you would expect… 

    Defensive Awareness = [30 - ((DfA attribute value * 5)+ hot/cold bonus +  Team bonus) / 2] / 2  
    

    0 DfA = [30 - ((0 * 5) + 0 + 0 / 2] / 2 = 15

    6 DfA = [30 - ((6 * 5) + 0 + 0 / 2] / 2 = 7.5

    12 DfA = [30 - ((12 * 5) + 0 + 0 / 2] / 2 = 0

    15 DfA = [30 - ((15 * 5) + 0 + 0 / 2] / 2 = -3.75

    Therefore, Ratings of 12 or higher would produce the same minimum delay in the calculation?

     

     

    Awareness attributes are between 0-6

    Assuming no bonuses:

    0 DfA = 15 frame timer

    1 DfA = 14 frame timer

    2 DfA = 12 frame timer

    3 DfA = 11 frame timer

    4 DfA = 10 frame timer

    5 DfA = 9 frame timer

    6 DfA = 7 frame timer 

    Also, there is a -1 to the timer if the crowd meter record is broken (which is a bug, so it always is broken).

     

    This is specific to 94. If you are using a 0-15 hack, I'm not sure how that would work, you'd have to look up how it converts them. 

    • Thanks 1
  13. 3 hours ago, AdamCatalyst said:

    Thanks! When I looked at the Timer code, I interpreted it as taking the players DfA, setting that as the timer, and decrementing with the frame counter. But that would mean that players with a higher DfA were more laggy in updating their defensive positioning. What am I misunderstanding?

     

    That's what it does, but OfA and DfA are stored differently than the other attributes.

    While most of the other attributes are stored as 0-6 value * 5 + Bonuses, Awareness is different:

    AWARENESS CALCULATION
    
    Offensive and Defensive Awareness in-game values are calculated differently, and thus the in-game bonuses have a different effect on them. 
    
    The other attributes that get bonuses are calculated in game as so - Default attribute value (0-6) * 5 + in-game bonuses (ranging from -3 to +4 in some cases)
    
    Offensive Awareness = [30 - ((OfA attribute value * 5) + hot/cold bonus + PP bonus + PK bonus + Team bonus + Comeback bonus) / 2] / 2     (yes, it's confusing, remember PEMDAS)
    
    Defensive Awareness = [30 - ((DfA attribute value * 5)+ hot/cold bonus +  Team bonus) / 2] / 2  
    
     
    
    All you have to know here, is that the bonuses have a larger effect on players with lower awareness. The lower the awareness, the larger the bonuses make a difference. Players with 5 or 6 Awareness attribute value will not benefit as much from the bonuses as players with 2-4. But, if the bonuses are all positive, a player with a 5 rating may benefit with a small bump. Also, the LOWER the total value here, the higher the actual Awareness of the player (unlike the other attributes, where higher value is better).
    
    For example, a player with 6 Awareness attribute will react twice as fast as a player with 0 Awareness attribute. The timer for a 0 Awareness player is 15 frames (disregarding bonuses), and a player with 6 Awareness will have a timer of 7 frames (disregarding bonuses).

     

  14. On 3/2/2025 at 1:16 PM, AdamCatalyst said:

    Wow. This is interesting. I recently updated the formula for how a player decides to make a pass, but this is an order of magnitude more complex. I think I’m going to need this. Actually, question: do the D-men react to the actual blue lines in the code, or flags, or are manual co-ordinates set?

    They are reacting to where the puck is relative to specific Y values, which are the Y positions of the blue lines

    • Thanks 1
  15. There are 2 assignments where a CPU player will look to check - assnearest (player nearest to the puck but doesn't have it), and assdefd (defensive assignment for defensemen).

    Once it is determined where they will skate to, the player will go into the check4check routine. In this routine, a calculation is made, using the player's Chk rating, to determine whether or not to look to check. If the calculation is successful, it will do a check to see if there are any players in the vicinity to check. Finally, it will choose a type of check (either a hold check, or a burst check).

    Formula to determine if there will be a check attempt (for a hold or burst check):

    Start with $28 (40 decimal):
    
    - Subtract the checking player's Chk rating (in game rating - Chk attribute * 5 + bonuses, max is $1E or 30 decimal)
    - Check if penalties are on
    - If penalties are on, the result is multiplied by 2
    - RNG result
    - Compare to 6. If the RNG result is higher than 6, exit the routine (no check will occur)

     

    If the result is lower than or equal to 6, the next step is to look and see if someone is around to check! 

    The code loops through the opponent's players on the ice. First, it will check if they are within +/- 30 pixels in X. As a gauge of distance, 30 pixels is the radius of a face-off circle. If a player is found, the next step is to check if they are within +/- 30 pixels in the Y direction. Next, it will check if the checking player is facing the player. If none of these pass, it will loop to the next player. If a player is found and all the checks pass, we get to the next step.

    Let's look at the area the game considers the slot (shaded):

    Slot.png

    The next step is to check if the puck carrier is in the slot. If there's a puck carrier, and they are in the shaded area in the photo, the checking player will do a hold check. If no puck carrier, or if the puck carrier is not in the slot, the frame counter is used as a random bit. It will use the last 3 bits of the frame counter, which would give us a value from 0-7. If the value is 0, the player will do a hold check. If it is not 0, the player will do a burst check, and will enter the checking routine (see "How Checking Works in Genesis") - 

     

     

    A sweep check (B check, stick check whatever you want to call it) is called from the assnearest assignment. The player with the assnearest assignment is the only one who can do a sweep check. Once I write up the assnearest assignment (which is the most complicated one!), I'll talk about the sweep check.

     

     

    • Love 5
  16. Defensemen have 2 unique assignments - assdefd and assdefo. One for defense, one for offense. These are assigned to CPU defensemen who do not have the puck. One is used when their team is on offense, and the other when their team is playing defense.

    Defensemen are a little different compared to the center and wing positions. On offense, the center and the wingers will skate to imaginary boxed areas, with a position inside randomized, which is reset when the puck crosses some markers (blue lines, offensive goal line). On offense, the defensemen will actually react to the puck location and move inside their imaginary box in a specific fashion.

    On defense, the wingers and centers also skate to specific areas, and react to the puck location accordingly. Defensemen will do the same, but they have some added functions depending on certain situations.

    The main difference in these assignments is the use of Offensive Awareness. The center and wingers do not use offensive awareness in their normal offense and defense routines. The defensemen will use Offensive Awareness as the offensive assignment timer, and use Defensive Awareness as their defensive timer. When the timer is up, the code will reload the timer, and do some checks to see if the assignment needs to be changed.

     

    assdefo - Defenseman on offense

     

    When another player on their team has the puck in the offensive zone, the defensemen will be in the assdefo assignment (switching to this assignment depends on the DfA of the defensemen, since this is used as the timer for the assdefd assignment).

    DefO.png

    When the puck is in the offensive zone, the LD and RD will skate to the yellow line and will move back and forth across that line depending on the X position of the puck. When the puck is on their side of the offensive zone, the defenseman will match their X position with the X position of the puck. When the puck is on the opposite side of the zone, the defenseman will skate between the black mark on their side the center of the rink in X (if the puck is on the right side board in this photo, the LD will be close to the center of the rink in X).

    GIF included:
    DefO.gif

    Notice how when the puck first crosses the center of the ice, Bourque (RD here) goes to skate back towards where the black square would be. This has to do with the skateto routine, which will be covered in another post. To make the game more realistic in a way, players can only change where they skate to every 12 frames or so. At first, when the puck crosses the center, he's told to skate to where the black square is. He won't change his destination until 12 frames or so later. Also, you'll see Sweeney (LD) do the same as he starts skating away from the puck, then when it crosses the center, he initially will skate to where the puck was when it first crossed.

    Also, on offense, there is a secondary skating routine which is used to avoid the puck carrier on your team. So if you skate towards a defenseman while carrying the puck, he will skate away from his positioning to try and avoid you.

    It's important to note that if the puck is not in the offensive zone, the defensemen will go back to their defensive assignment.

     

    assdefd - Defenseman on defense

     This assignment is used whenever the puck is outside the offensive zone, regardless if the team has the puck or not. It's a lot more complicated than assdefo.

    DfA is used for the timer here. When the timer is up, the assignment code will determine the new location for the defenseman to skate to.

    The code will check for the closest opposing player to the defensive zone in the Y direction. It uses their position and accounts for the player's velocity (so the defenseman isn't lagging behind). If it determines that the player will end up inside the area between the top face-off circle hash and the back boards, they will position themselves in the low slot. If it determines they are outside that area, the defenseman will skate to their Y position. So a player outside of that area, who is not skating or skating very slow (low velocity), the defensman will skate to a spot 50 pixels behind them in Y. Again, this is reacting to skaters who are skating to a spot closest to their defensive zone, not technically the puck carrier.

    They will position themselves 65 pixels away from center ice in X. If the puck is opposite side of center in X, they will move to center X (middle of slot). This is regardless if the puck is in their team's possession or not.

    Here's a few GIFs, and there are important notes to the FLA defensemen's movements here:

    DefD-1.gif

    Now, the closest player to the zone, is the LW. The puck is currently on the left side of the rink. The RD of FLA is staying near center ice, while the LD positions themselves 65 pixels away from center (outside zone face-off dots). They only stay put for a short time, and you'll notice them skating back and forth. Why? Right now, BOS LW, C, and RW have no Y velocity (they are not skating). But let's look at the BOS defensemen, who right now are reacting to the player posing the most "threat" to their zone:

    DefD-2.gif

    Ray and Sweeney are reacting to the FLA LW and C, who are in turn reacting to the puck carrier (switching off to assnearest, the opposite team's player closest to the puck carrier). With the BOS D skating towards center ice, the FLA defensemen are reacting to their skating over the BOS LW, since they are skating towards their defensive zone. So, you get this kind of feedback on defense.

     

    Defenseman low in the slot, guarding a player below the face-off hashes:

    Def-Low.png

     

    When the DfA timer is up, it sets the position to skate to (described above). When the timer is running, the defenseman will continue to skate to that position, while also looking for a "good checking opportunity", where it uses their Chk rating and some RNG to determine if it should look for a check. This is specific to assdefd and assnearest assignments. I'll describe this in another post.

     

    • Love 6
  17. 8 minutes ago, AdamCatalyst said:

    YOU HAVE THIS DISASSEMBLED ON GITHUB!?!?!?!?! Good lord, how long have I been oblivious?

    Haha, I just only posted it in the Discord like a week ago or so. Like I said, it's not all done, or nowhere near close. But a bunch of important stuff I've gone through and made comments to help better understand.

    • Love 1
  18. 4 hours ago, von Ozbourne said:

    Had a few minutes to try something else out and realized that This might not work in my context.

    While as previously mentioned, changing the value at 0x14410 from 84 to 34 will make every check of the puck carrier into an injury inducing hit, even after the whistle,
    Changing it to 74 apparently means that most hits will result in a penalty, even after the whistle.

    Weird, I thought I responded to your post earlier. The change from 84 to 34 is for a conditional branch. It's skipping over the other checks of code, and going directly to the "Add Penalty" part. So what you are changing there is how much of a jump ahead the branch is. Changing the branch 0084 to 0034, makes the branch go to 0x14444 ,which is the instruction before "setInjuryType".

     

    image.png

     

  19. If you set 0x14410 to from 84 to 34, most checks to the puck carrier will cause injuries.

    If you set 0x14404 from 66 00 00 8E to 67 00 00 3E, all checks to the puck carrier will cause injuries, even after the whistle. 

     

    NOTE: The player getting checked has to be the puck carrier in order to have a shot at getting injured.

    • Thanks 1
  20. 21 hours ago, AdamCatalyst said:

    Thanks for this! I was trying to force injuries, for testing purposes, but nothing I've done seems to work as I would expect so far. Will keep trying.

    Set the value at 0x1442D from A0 to something much less (I think anything between 00 and 20 will always be an injury).

  21. 20 minutes ago, AdamCatalyst said:

    @chaos Where is this in the code?

    The code to determine if there will be an injury is in the FallDown subroutine at 0x14428 (move.l #$A0, d0).

     

    The subroutine to determine injury type starts at 0x144AC. I updated the post above. 

  22. Here's a GIF with the value at 0xAC1D set to 03 instead of 09. You are right! The puck slows down. Upon further investigation, it seems that the puck always has the flag pfdoff set to ON. So it will always be using this value on deceleration (when it is on the ice. If there is any Z height to the puck it won't decelerate like this). Players will also use this when decelerating for a pass though.

    Puck_Slow.gif

     

    Screenshot 2025-01-28 at 1.39.13 PM.png

    • Like 1
×
×
  • Create New...