Damage calculator

#41
Well yeah, single thread processors run one op at a time, but the code is still just going to reflect the sequence the programmers set up. ‎  Even if they ran a subroutine like "trigger Hit animation" between damage calculation and HP reduction, you should still be able to skip past that subroutine and get the damage calculation section without much searching. Anyways, it's pointless to discuss. It takes what it takes based on your skill level and game.exe's stupidliness.

A quick look shows around 400 sub routines in game.exe; most of them quite short. Have you charted any of their functions?
Reply

#42
(2013-07-05, 10:11 PM)HwitVlf link Wrote:Well yeah, single thread processors run one op at a time, but the code is still just going to reflect the sequence the programmers set up. ‎  Even if they ran a subroutine like "trigger Hit animation" between damage calculation and HP reduction, you should still be able to skip past that subroutine and get the damage calculation section without much searching. Anyways, it's pointless to discuss. It takes what it takes based on your skill level and game.exe's stupidliness.

See you seem to be basing your conception here on having some code injected somewhere. If that's the case you may as well inject the code anywhere; edited: as in, the best place possible. If there is no code injected however, it just runs where it runs.

I wonder too if you realize that the HP loss is just the final result of attack and defense with respect to eight variables, so you can't extract all of that from the HP loss. It's like being handed a 4 and being asked whether its 1+3 or 2+2. You can't say.

Quote:A quick look shows around 400 sub routines in game.exe; most of them quite short. Have you charted any of their functions?

After this next release SomEx.dll is going to standardize around som_db.exe, specifically the final patched version. Other exe files are no longer going to be supported. So if someone wanted to they could start documenting the subroutines of that image.

I think it would be useful to have a big graph of the EXE with curved lines linking call jumps that you can zoom in and out of in order to help visualize it. But I looked for a product like that yesterday and didn't find anything. I could probably make such a graph myself with a graphing library. I don't know if there would be too much program for it to be practical or not.

I know it pretty well instinctively. It just takes a few afternoons to do whatever I want to do with it when something needs to be done.

EDITED: Also just for the record. Even in multi thread applications, or a massively parallel architecture, things are happening at the rate of nanoseconds, so you just can't presume concurrency. Everything must be run in sequence. If two execution threads must interact that's an interlock point which can bring everything to a grinding stop. It's a miracle just to keep the concurrent tasks from clobbering one another at the speed of light.
Reply

#43
Quote:To be honest I'm more interested in blurring the line between NPC and enemy. It might make sense for authors to just setup their damage formulas to not harm individuals. On the other hand, do_not_harm_defenseless_characters would be easy enough to setup.

EDITED: I did just remember that there is a box you can check to make NPCs invincible in the parameter editor. Still do_not_harm_defenseless_characters would let the player harm these NPCs while forbidding other NPCs from doing the same. It probably wouldn't hurt to add a confirmation dialogue extension to let the player confirm their decision to harm the NPC so that they can't do that on accident.

It seems like such a dialog should be the in-game variety. So you'd have to mimic that, and also there needs to be an extension to keep players from accidentally advancing / cancelling dialogs too. So such a dialog may be a ways away.
Reply

#44
Here is reproduction of the damage formula with the ASM in /*comments*/

Code:
    /*
    00404470 83 EC 14         sub         esp,14h
    00404473 53               push        ebx 
    00404474 8B 5C 24 1C      mov         ebx,dword ptr [esp+1Ch]
    00404478 81 7B 14 18 1A 9C 01 cmp         dword ptr [ebx+14h],19C1A18h
    0040447F 56               push        esi 
    00404480 C7 44 24 0C 00 00 00 00 mov         dword ptr [esp+0Ch],0
    00404488 C7 44 24 10 00 00 00 00 mov         dword ptr [esp+10h],0
    00404490 C7 44 24 14 00 00 00 00 mov         dword ptr [esp+14h],0
    00404498 C7 44 24 18 00 00 00 00 mov         dword ptr [esp+18h],0
    004044A0 C7 44 24 08 00 00 00 00 mov         dword ptr [esp+8],0
    004044A8 75 2F            jne         004044D9
    */
    float srcBase[2] = {0,0};
    float dstBase[2] = {0,0};
    if(*(DWORD*)(ebx+0x14)==0x19C1A18)
    {
        //always player, but src must be a weapon
        //with the holder's stats in front instead???
        //assert(src==player_src);    Â 

        /*
        004044AA 33 C0            xor         eax,eax
        004044AC 66 8B 03         mov         ax,word ptr [ebx]
        004044AF 33 C9            xor         ecx,ecx
        004044B1 66 8B 4B 02      mov         cx,word ptr [ebx+2]
        004044B5 89 44 24 20      mov         dword ptr [esp+20h],eax
        004044B9 DB 44 24 20      fild        dword ptr [esp+20h]
        004044BD 89 4C 24 20      mov         dword ptr [esp+20h],ecx
        004044C1 D8 0D BC 82 45 00 fmul        dword ptr ds:[4582BCh]
        004044C7 D9 5C 24 0C      fstp        dword ptr [esp+0Ch]
        004044CB DB 44 24 20      fild        dword ptr [esp+20h]
        004044CF D8 0D BC 82 45 00 fmul        dword ptr ds:[4582BCh]
        004044D5 D9 5C 24 10      fstp        dword ptr [esp+10h]
        */
        srcBase[0] = float(*(WORD*)(ebx))/5;
        srcBase[1] = float(*(WORD*)(ebx+2))/5;
    }
    /*
    004044D9 8B 73 40         mov         esi,dword ptr [ebx+40h]
    004044DC 81 FE 18 1A 9C 01 cmp         esi,19C1A18h
    004044E2 75 3A            jne         0040451E
    */
    DWORD esi = *(DWORD*)(ebx+0x40);
    if(esi==0x19C1A18)
    {
        assert(dst==player_dst); //19C1C24 is Strength. 19C1C28 Magic
        /*
        004044E4 8B 15 24 1C 9C 01 mov         edx,dword ptr ds:[19C1C24h]
        004044EA A1 28 1C 9C 01   mov         eax,dword ptr ds:[019C1C28h]
        004044EF 81 E2 FF FF 00 00 and         edx,0FFFFh
        004044F5 89 54 24 20      mov         dword ptr [esp+20h],edx
        004044F9 DB 44 24 20      fild        dword ptr [esp+20h]
        004044FD 25 FF FF 00 00   and         eax,0FFFFh
        00404502 89 44 24 20      mov         dword ptr [esp+20h],eax
        00404506 D8 0D BC 82 45 00 fmul        dword ptr ds:[4582BCh]
        0040450C D9 5C 24 14      fstp        dword ptr [esp+14h]
        00404510 DB 44 24 20      fild        dword ptr [esp+20h]
        00404514 D8 0D BC 82 45 00 fmul        dword ptr ds:[4582BCh]
        0040451A D9 5C 24 18      fstp        dword ptr [esp+18h]
        */        
        dstBase[0] = float(SOM::L.pcstatus[SOM::PC::str])/5;
        dstBase[1] = float(SOM::L.pcstatus[SOM::PC::mag])/5;
    }
    /*
    0040451E 83 7B 3C 02      cmp         dword ptr [ebx+3Ch],2
    00404522 55               push        ebp 
    00404523 57               push        edi 
    00404524 75 6A            jne         00404590
    */    Â      
    float mystery = 0; //???
    if(*(DWORD*)(ebx+0x3C)==2)
    {
        /*
        00404526 33 C0            xor         eax,eax
        00404528 66 8B 46 20      mov         ax,word ptr [esi+20h]
        0040452C 8D 0C 40         lea         ecx,[eax+eax*2]
        0040452F 8D 0C 89         lea         ecx,[ecx+ecx*4]
        00404532 8D 14 88         lea         edx,[eax+ecx*4]
        00404535 8B 4E 68         mov         ecx,dword ptr [esi+68h]
        00404538 33 C0            xor         eax,eax
        0040453A 66 8B 04 D5 C8 A1 4D 00 mov         ax,word ptr [edx*8+4DA1C8h]
        00404542 8D 3C D5 C8 A1 4D 00 lea         edi,[edx*8+4DA1C8h]
        00404549 51               push        ecx 
        0040454A 8B 2C 85 C8 67 4C 00 mov         ebp,dword ptr [eax*4+4C67C8h]
        00404551 E8 3A CF 03 00   call        00441490
        00404556 8B 8E 40 02 00 00 mov         ecx,dword ptr [esi+240h]
        0040455C 83 C4 04         add         esp,4
        0040455F 83 F9 0C         cmp         ecx,0Ch
        00404562 75 2C            jne         00404590
        */
        DWORD eax = *(WORD*)(esi+0x20), edx = eax+(eax*3*5*4);
        DWORD ecx = *(DWORD*)(esi+0x68), edi = edx*8+0x4DA1C8;

        eax = *(WORD*)(edi);

        DWORD ebp = *(DWORD*)(eax*4+0x4C67C8);
        
        DWORD before = *(DWORD*)(esi+0x240);

        ((VOID (__cdecl*)(DWORD))0x441490)(ecx);

        DWORD after = *(DWORD*)(esi+0x240);

        if(after==0) //cmp ecx,0Ch
        {
            /*
            00404564 33 D2            xor         edx,edx
            00404566 8A 95 89 00 00 00 mov         dl,byte ptr [ebp+89h]
            0040456C 3B D0            cmp         edx,eax
            0040456E 7F 20            jg          00404590
            00404570 33 C9            xor         ecx,ecx
            00404572 8A 8D 8A 00 00 00 mov         cl,byte ptr [ebp+8Ah]
            00404578 3B C1            cmp         eax,ecx
            0040457A 7F 14            jg          00404590
            0040457C 33 D2            xor         edx,edx
            0040457E 8A 97 46 01 00 00 mov         dl,byte ptr [edi+146h]
            00404584 89 54 24 28      mov         dword ptr [esp+28h],edx
            00404588 DB 44 24 28      fild        dword ptr [esp+28h]
            0040458C D9 5C 24 10      fstp        dword ptr [esp+10h]
            */

            edx = *(BYTE*)(ebp+0x89);

            if(edx<=eax) //cmp edx,eax
            {
                ecx = *(BYTE*)(ebp+0x8A);

                if(eax<=ecx) //cmp eax,ecx
                {
                    edx = *(BYTE*)(edi+0x146);

                    mystery = edx; assert(0); //???                    
                }
            }
        }
    }
    /*
    00404590 8B 44 24 30      mov         eax,dword ptr [esp+30h]
    00404594 8B 6C 24 2C      mov         ebp,dword ptr [esp+2Ch]
    00404598 C7 00 00 00 00 00 mov         dword ptr [eax],0
    */
    DWORD ebp = dst; *out = 0;
    for(esi=0;esi<8;esi++,ebp+=2)
    {
        /*
        0040459E 33 F6            xor         esi,esi
        004045A0 33 C0            xor         eax,eax
        004045A2 8A 44 33 04      mov         al,byte ptr [ebx+esi+4]
        004045A6 84 C0            test        al,al
        004045A8 74 77            je          00404621
        004045AA 0F BF 4D 00      movsx       ecx,word ptr [ebp]
        004045AE 25 FF 00 00 00   and         eax,0FFh
        004045B3 83 FE 03         cmp         esi,3
        004045B6 89 44 24 28      mov         dword ptr [esp+28h],eax
        004045BA DB 44 24 28      fild        dword ptr [esp+28h]
        004045BE 89 4C 24 28      mov         dword ptr [esp+28h],ecx
        004045C2 DB 44 24 28      fild        dword ptr [esp+28h]
        004045C6 D8 44 24 10      fadd        dword ptr [esp+10h]
        004045CA D9 5C 24 28      fstp        dword ptr [esp+28h]
        */
        float rating = *(BYTE*)(ebx+esi+4); if(!rating) continue;

        //mystery: unsure what else fadd could be doing
        float offset = *(SHORT*)(ebp); offset+=mystery;
        /*        
        004045CE 7D 0E            jge         004045DE
        004045D0 D8 44 24 14      fadd        dword ptr [esp+14h]
        004045D4 D9 44 24 28      fld         dword ptr [esp+28h]
        004045D8 D8 44 24 1C      fadd        dword ptr [esp+1Ch]
        004045DC EB 0C            jmp         004045EA
        004045DE D8 44 24 18      fadd        dword ptr [esp+18h]
        004045E2 D9 44 24 28      fld         dword ptr [esp+28h]
        004045E6 D8 44 24 20      fadd        dword ptr [esp+20h]
        */        
        rating+=srcBase[esi>=3]; offset+=dstBase[esi>=3];
        /*
        004045EA D9 C1            fld         st(1)
        004045EC D8 D9            fcomp       st(1)
        004045EE DF E0            fnstsw      ax   
        004045F0 F6 C4 41         test        ah,41h
        004045F3 75 0D            jne         00404602
        004045F5 D9 C1            fld         st(1)
        004045F7 D8 E1            fsub        st,st(1)         
        004045F9 E8 3A B3 04 00   call        0044F938
        004045FE 8B F8            mov         edi,eax
        00404600 EB 02            jmp         00404604
        00404602 33 FF            xor         edi,edi
        */                        Â   
        LONG edi = rating-offset;
        /*
        00404604 D9 C1            fld         st(1)
        00404606 D8 CA            fmul        st,st(2)
        00404608 D9 C9            fxch        st(1)
        0040460A DC C0            fadd        st(0),st
        0040460C DE F9            fdivp       st(1),st
        //not sure what this does but INF becomes 0//
        0040460E E8 25 B3 04 00   call        0044F938
        00404613 DD D8            fstp        st(0)
        00404615 03 F8            add         edi,eax
        */

        rating*=rating; offset+=offset;
        
        if(offset) edi+=rating/offset;

        /*
        00404617 85 FF            test        edi,edi
        00404619 7E 06            jle         00404621
        0040461B 8B 44 24 30      mov         eax,dword ptr [esp+30h]
        0040461F 01 38            add         dword ptr [eax],edi
        00404621 46               inc         esi
        00404622 83 C5 02         add         ebp,2
        00404625 83 FE 08         cmp         esi,8
        00404628 0F 8C 72 FF FF FF jl          004045A0
        */

        *out+=edi;
    }
    /*
    0040462E 8B 43 14         mov         eax,dword ptr [ebx+14h]
    00404631 BF 18 1A 9C 01   mov         edi,19C1A18h
    00404636 3B C7            cmp         eax,edi
    00404638 75 1B            jne         00404655
    */
    DWORD edi = 0x19C1A18;
    if(*(DWORD*)(ebx+0x14)==edi) //0x19C1A18
    {
        //player power gauge
        /*
        0040463A 8B 4C 24 30      mov         ecx,dword ptr [esp+30h]
        0040463E 33 D2            xor         edx,edx
        00404640 66 8B 53 0E      mov         dx,word ptr [ebx+0Eh]
        00404644 B8 59 17 B7 D1   mov         eax,0D1B71759h
        00404649 0F AF 11         imul        edx,dword ptr [ecx]
        0040464C F7 E2            mul         eax,edx
        0040464E C1 EA 0C         shr         edx,0Ch
        00404651 89 11            mov         dword ptr [ecx],edx
        00404653 EB 04            jmp         00404659
        */

        LONG edx = *out**(WORD*)(ebx+0xE);

        edx = (0xD1B71759ULL*edx)>>32; //mul edx
        
        *out = edx>>0xC;
    }
    /*
    00404655 8B 4C 24 30      mov         ecx,dword ptr [esp+30h]
    00404659 83 39 00         cmp         dword ptr [ecx],0
    0040465C 75 06            jne         00404664
    0040465E C7 01 01 00 00 00 mov         dword ptr [ecx],1
    */
    if(*out==0) *out = 1;
Reply

#45
I think the "mystery" bit (in the code above) is probably the monsters' evade maneuver defense bonus come to think of it just now.

The Strength and Magic bonuses are added to the player's defense as well as attack.

The formula is simpler than ((1+(ATK+DEF)/DEF-DEF*2/ATK)*ATK/2).

It's simply ATK-DEF+ATK*ATK/(DEF*2) where ATK and DEF are positive numbers.

EDITED: Actually with further testing it turns out it is max(0,ATK-DEF)+ATK*ATK/(DEF*2). In other words if DEF is greater than ATK the first term is 0. Why? Who can say.

If DEF*2 is 0 then the second additive term becomes infinity, which is undefined behavior when converted to an integer. But the 0044F938 subroutine produces 0 for infinity.

I think the subroutine just does floating point to integer conversion. There are at least 3 separate subroutines that do that in this chunk of code. And they are inline subroutines. So the 400 subroutine figure quoted above probably includes a lot of junk subroutines like these.

Anyway, since the ASM loads the result of the undefined operation into a register, it gets added to the total as 0.


To my mind I don't see the logic in this method of calculating damage. Why two different damage terms, one linear, one exponential, the exponential one collapsing to 0 on infinity damage, none of it makes sense to me, but I wouldn't be surprised if its the same formula used in the King's Field games, however the undefined bits might have played out differently on the PS1.

I think probably the thing to do in terms of looking at this as a bug, would be to take the linear term out. And treat 0 defense against nonzero attack as infinity damage. Whatever is done, clamping to 0 seems like a good place to begin.


PS: @HwitVlf: neither am I convinced that just doing ATK-DEF would work so well in terms of game play. I don't think players would like it so much unless it was coupled with a way to chip away at the opponents' defenses (that doesn't involve leveling up.)


EDITED: Also, the last bit is if there was no damage, then the damage is 1. This subroutine also does bad status infliction as a result of an attack, but I've not included that part above.
Reply

#46
EDITED: I think something that could capture the spirit of this formula would be ATK-DEF where DEF can be chipped away at by ATK*ATK/(DEF*2). In other words, the DEF stat would be brought down with every swing according to an exponential function. The remainder could then be applied to ATK-DEF.

And then instead of 0 DEF being infinity damage, it would just be no defense.
Reply

#47
(2013-07-05, 02:05 PM)Holy Diver link Wrote:For the physical affinities using grades it would still appear powerless. No Slash/Smash/Stab or whatever, but the Raw power would be the sum of the magical or material affinities, so you could conclude that the power does not take the form of one of the physical affinities, which would actually be the case. In other words it would be a "vorpal" sword of some kind, or more likely a magic spell...

I think one approach to dealing with this could be to just not award mastery for the Void physical affinity. Since it would be an innate power in the weapon. No amount of swinging practice would make a difference. And since how you apply the weapon would have no relation to the stat then it wouldn't be clear how each swing relates to the effectiveness of the damage output. Could make these kinds of weapons popular with wizard players because they would be just as effective with or without mastery.

I reckon magic spells could work the same way too. So that if they don't have any physical affinities, then there is no benefit from mastery. Actually the power of the spell should probably just be a function of the Magic stat. Where mastery only awards modest benefits. Probably you would want to go up to 150%, so that anything over 100% is S. That way if the weapon/swing is a 50% F with complete mastery of the swing you could get a 100% A hit.

Reply

#48
I expected the actual damage formula would look different than what I used in the excel, but it worked, and no one ever used the Excel anyways so it didn't really matter. Still, comparing the two, I can't easily see how they came to the same conclusion. Not worth the brain power to figure out.

I believe the KF damage formula was designed to be a limiter to exploration. If an enemies stats are significantly unbalanced from the player's, it results in giving/receiving massive damage. ‎  In an open world game, that works to keep the player out of advanced areas until they're at the proper level. The down side to the KF formula is: it's not user friendly when making up a game, and it completely takes the challenge out of defeating enemies who are at a lower level.

People should be able to customize their damage formula with your plugin, but I like "damage=atk-def" for the default setting because it's simple for new developers to understand and use. As long as they have the option to change it, starting with the simplest formula makes sense to me.
Reply

#49
(2013-07-06, 09:50 PM)HwitVlf link Wrote:People should be able to customize their damage formula with your plugin, but I like "damage=atk-def" for the default setting because it's simple for new developers to understand and use. As long as they have the option to change it, starting with the simplest formula makes sense to me.

When it comes to defaults I don't deviate from SOM if there is not a technical reason to do so. I don't want to usurp its identity. Besides _1-_2 is easy enough to write into a text file.

Still I think that formula could present problems. I wonder if there is a website that documents popular damage formulas. If not we should make one. I reckon once the people have a tool to experiment with a standard formula will emerge. I for one wish game play was more standardized. Every game working differently, detracts from the story telling potential in games I think. It's hard to consume games like books and movies when every one is different. That's not to say that experimentation should not occur. It should just occur behind the scenes IMO. After 30yrs of gaming some standards should have emerged by now.

EDITED: IMO the game mechanics should not detract or add to the experience. They should only exist to make the experience more visceral. Since that is the strength of the medium.

EDITED: I think one formula that might work as a default could be ATK-max(0,DEF-ATK*ATK/(DEF*2))

This says that the exponential formula negates the DEF and then what remains goes through the defenses. It's as if the armor regenerates after each hit. I think armor could be an interesting concept. It would certainly take the all powerfulness away from healing magic in video games, since the healing would heal the body but not the armor. So you might be full HP but standing naked. A game could have living armor that could be healed, lessening the effect of the heal on HP in the process, but I doubt that would fit into many games. Though it might become a popular trope. Like in King's Field probably only the Dark Armor would have that property.

Anyway, the damage formula is pretty buggy. So changing it to some degree is a necessity. Using that formula would at least look similar to the original formula. I want to keep the exponential curve regardless. And I'd like for the results to be similar. I think that formula would basically reduce down to the linear component. But the exponential curve means that it is more forgiving than atk-def would be.
Reply

#50
I've taken a look at ATK-max(0,DEF-ATK*ATK/(DEF*2)). I am a little hesitant about using a completely different damage formula by default. But it has some nice properties.

For one. You can obviously never do more damage than the weapon+bonus itself. Which I think is very intuitive. It isn't as unforgiving as ATK-DEF would be.

Specifically I think you can start doing damage if your rating is within square root of 0.5 or 70.71% of the defense rating. That's a fairly auspicious number. It shows up in circles. For instance it's how far you can move diagonally when pressing the movement buttons.

Whereas with ATK-DEF you would have to be at 100% of the defense rating before you can deal damage. Which seems unrealistic, though I've no doubt games use this. It's reasonable to suspect that a weapon with a rating just below the armor can do more than 0 damage.

When the weapon and armor are equally matched you deal 55.90% of the weapon's damage. I am not sure if this number is significant (it almost definitely is) but it is the same number that SOM's classical formula yields. So that seems good (edited: I have a feeling this may be 50%. Could be rounding and stat bonuses were making the difference. Still seems like an awful big difference. May be worth revisiting sometime)

I expect when the weapon is 141.42% over the armor you will deal the weapon's maximum damage. This is square root of 2. Or the length of the diagonal of a square enclosing a unit circle. Whereas square root of 0.5 is the width of the square formed by that diagonal clipped at the edge of the circle. ‎  Anyway at this point the armor is no longer effective (edited: for the record, this hunch seems to checkout)


It would be easy to draw a diagram of these relationships. It would be a circle in a square with another square just inside one of the quadrants of the circle.

I mean. Considering the limited range of the stats. I think this is about as good as anything you could come up with. It's intuitive. And based on literally round numbers. And uses the two terms of the original formula exclusively.


EDITED: I like this formula I think. If this is what the bugfix does. Because there must be a fix. Then to get the original formula minus its bugs I guess you'd just have to manually use that formula itself. It seems unlikely that anyone would want to. And I kind of feel like weapons doing more damage than their ratings is a bug in and of itself. Plus I don't have the reservations for this formula that I have for ATK-DEF.
Reply





Users browsing this thread:
11 Guest(s)