Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Glovz rewrite to c++ #355

Closed
FakelsHub opened this issue Jan 26, 2021 · 14 comments
Closed

Glovz rewrite to c++ #355

FakelsHub opened this issue Jan 26, 2021 · 14 comments
Labels

Comments

@FakelsHub
Copy link
Contributor

https://github.com/phobos2077/sfall/blob/f9157bee6d7919134312c6fcf0423afa9f202813/sfall/Modules/DamageMod.cpp#L35
If you have the desire you can rewrite it in c++
I once tried to do this for a very, very long time, but somehow I could not master this formula
here are my saved old beginnings. use this as a helper

//begin:
	    int temp = 0;                                                        // clear value
		int rawDamage = fo::func::item_w_damage(ctd.attacker, ctd.hitMode);  // get the raw damage value
		if (bonusRangedDamage > 0) {        // cmp ebx, 0x0; //jle rdJmp;    // compare the range bonus damage value to 0 
			                                                                 // if the RB value is less than or equal to 0 then goto rdJmp
			rawDamage += bonusRangedDamage; // add eax, ebx;                 // add the RB value to the RD value 
		}
//rdJmp:
		if (rawDamage <= 0) continue; // cmp eax, 0x0; jle noDamageJmp;      // compare the new damage value to 0 
		int ND = rawDamage;           // mov ebx, eax;                       // set the ND value 

		// get the armorDT value      // mov edx, dword ptr ss : [esp + 0x28]; 
		if (armorDT <= 0)             // cmp edx, 0x0; jle bJmp;             // compare the armorDT value to 0  
			goto bJmp;                                                       // if the armorDT value is less than or equal to 0 then goto bJmp
		
		// get the ammoY value
		int ammoY = getAmmoY; 
		if (ammoY < 1)           // cmp eax, 0x0; jg aJmp;                   // compare the ammoY value to 0, if the ammoY value is greater than 0 then goto aJmp
			ammoY = 1;           // mov eax, 0x1;                            // set the ammoY value to 1 
aJmp:
		int ebp = 0;             // xor ebp, ebp;                            // clear value
		if (armorDT < ammoY)     // cmp edx, eax;                            // compare the dividend with the divisor 
			goto lrThan;         // jl lrThan;                               // the dividend is less than the divisor then goto lrThan
		if (armorDT > ammoY) 
			goto grThan;         // jg grThan;                               // the dividend is greater than the divisor then goto grThan
		goto setOne;                                                         // if the two values are equal then goto setOne
////////////////////////////////////
lrThan:
		ebp = armorDT;           // mov ebp, edx;                            // store the dividend value temporarily
		armorDT *= 2;            // imul edx, 0x2;                           // multiply dividend value by 2
		armorDT -= ammoY;        // sub edx, eax;                            // subtract divisor value from the dividend value
		if (armorDT < 0)         // cmp edx, 0x0;                            // compare the result to 0
			goto setZero;        // jl setZero;                              // if the result is less than 0 then goto setZero
		if (armorDT > 0)
			goto setOne;         // jg setOne;                               // if the result is greater than 0 then goto setOne
		armorDT = ebp;           // mov edx, ebp;                            // restore dividend value
		armorDT &= 1;            // and edx, 0x1; 
		if (armorDT == 0)                                                    // if true (1) then odd if false (0) then even
			goto setZero;        // jz setZero;                              // if the result is equal to 0 then setZero
		goto setOne;                                                         // if the result is not 0 then goto setOne
////////////////////////////////////
grThan:
		ebp = ammoY;             // mov ebp, eax;                            // assign the divisor value
		int aDT = 0;             // xor eax, eax;                            // clear value
bbbJmp:
		aDT++;                   // inc eax;                                 // add 1 to the quotient value
		armorDT -= ebp;          // sub edx, ebp;                            // subtract the divisor value from the dividend value
		if (armorDT >= ebp)      // cmp edx, ebp;                            // compare the remainder value to the divisor value
			goto bbbJmp;         // jge bbbJmp;                              // if the remainder value is greater or equal to the divisor value then goto bbbJmp
		if (armorDT == 0) 
			goto endDiv;         // jz endDiv;                               // if the remainder value is equal to 0 then goto endDiv (this bug in assebler???)
		
		armorDT *= 2;            // imul edx, 0x2;                           // multiply temp remainder value by 2
		armorDT -= ebp;          // sub edx, ebp;                            // subtract the divisor value from the temp remainder
		if (armorDT < 0)         // cmp edx, 0x0;                            // compare the result to 0
			goto endDiv;         // jl endDiv;                               // if the result is less than 0 then goto endDiv
		if (armorDT > 0)
			goto addOne;         // jg addOne;                               // if the result is greater than 0 then goto addOne
		ebp = aDT;               // mov ebp, eax;                            // assign the quotient value
		ebp &= 1;                // and ebp, 0x1;
		if (ebp == 0)                                                        // if true (1) then odd if false (0) then even
			goto endDiv;         // jz endDiv;                               // if the result is equal to zero goto endDiv
addOne:
		aDT++;                   // inc eax;                                 // add 1 to the quotient value
		goto endDiv;
////////////////////////////////////
setOne:
		aDT = 1;                 // mov eax, 0x1;                            // set the quotient value to 1
		goto endDiv;
////////////////////////////////////
setZero:
		aDT = 0;                 // xor eax, eax;                            // clear value
endDiv:
		switch (temp)
		{
		case 2:             // cmp dword ptr ss : [esp + 0x30], 0x2;         // compare value to 2
			goto divTwo;    // je divTwo;                                    // goto divTwo
		case 3:	            // cmp dword ptr ss : [esp + 0x30], 0x3;         // compare value to 3
			goto divThree;	// je divThree;                                  // goto divThree
		case 4:	            // cmp dword ptr ss : [esp + 0x30], 0x4;         // compare value to 4
			goto divFour;   // je divFour;                                   // goto divFour
		case 5:             // cmp dword ptr ss : [esp + 0x30], 0x5;         // compare value to 5
			goto divFive;   // je divFive;                                   // goto divFive
		case 6:	            // cmp dword ptr ss : [esp + 0x30], 0x6;         // compare value to 6
			goto divSix;    // je divSix;                                    // goto divSix
		}
		ND -= aDT;          // sub ebx, eax;(bug in assebler use ammoY)      // subtract the new armorDT value from the RD value 
		goto cJmp;
////////////////////////////////////
bJmp:
		// get the armorDR value
		if (armorDR <= 0)              // cmp edx, 0x0;                      // compare the armorDR value to 0
			goto dJmp;                 // jle dJmp;                          // if the armorDR value is less than or equal to 0 then goto dJmp
cJmp:
		if (ND <= 0) goto noDamageJmp; // cmp ebx, 0x0; jle noDamageJmp;     // compare the new damage value to 0
		// get the armorDR value       // mov edx, dword ptr ss : [esp + 0x2c];
		if (armorDR <= 0)              // cmp edx, 0x0;                      // compare the armorDR value to 0
			goto eJmp;                 // jle eJmp;                          // if the armorDR value is less than or equal to 0 then goto eJmp
		
		// get the CD value
		if (difficulty > 100)          // cmp eax, 0x64;                     // compare the CD value to 100
			goto sdrJmp;               // jg sdrJmp;                         // if the CD value is greater than 100 then goto sdrJmp
		if (difficulty == 100)
			goto aSubCJmp;             // je aSubCJmp;                       // if the CD value is equal to 100 then goto aSubCJmp
		armorDR += 20;                 // add edx, 0x14;                     // add 20 to the armorDR value
		goto aSubCJmp;
////////////////////////////////////
sdrJmp:
		armorDR -= 20;                 // sub edx, 0x14;                     // subtract 20 from the armorDR value
aSubCJmp:
		// get the ammoDRM value
		int ammoDRM = fo::func::item_w_dr_adjust(ctd.weapon); 
		if (ammoDRM < 0)              // cmp eax, 0x0;                       // compare the ammoDRM value to 0
			goto adrJmp;              // jl adrJmp;                          // if the ammoDRM value is less than 0 then goto adrJmp
		if (ammoDRM == 0)
			goto bSubCJmp;            // je bSubCJmp;                        // if the ammoDRM value is equal to 0 then goto bSubCJmp
									  // xor ebp, ebp; // clear value
									  // sub ebp, eax; // subtract ammoDRM value from 0
									  // mov eax, ebp; // set new ammoDRM value
		ammoDRM = 0 - ammoDRM;                                               // subtract ammoDRM value from 0 and set new ammoDRM value
adrJmp:
		armorDR += ammoDRM;           // add edx, eax;                       // add the ammoDRM value to the armorDR value
bSubCJmp:
		// get the ammoX value
		int ammoX = fo::func::item_w_dam_mult(ctd.weapon); 
		if (ammoX > 0)               // cmp eax, 0x0;                        // compare the ammoX value to 0
			goto cSubCJmp;           // jg cSubCJmp;                         // if the ammoX value is greater than 0 then goto cSubCJmp;
		ammoX = 1;                   // mov eax, 0x1;                        // set the ammoX value to 1
cSubCJmp:
		temp = 2;                    // mov dword ptr ss:[esp + 0x30], 0x2;  // set value to 2
		goto aJmp;
////////////////////////////////////
divTwo:
		//int ND2 = ND;             // mov edx, ebx;                         // set temp value
		armorDT = ND * armorDR;//aDT //imul edx, eax;                        // multiply the ND value by the armorDR value
		ammoY = 100;                // mov eax, 0x64;                        // set divisor value to 100
		temp = 3;                   // mov dword ptr ss : [esp + 0x30], 0x3; // set value to 3
		goto aJmp;
////////////////////////////////////
divThree:
		ND -= armorDR;//aDT         // sub ebx, eax;                         // subtract the damage resisted value from the ND value
		goto eJmp;
////////////////////////////////////
dJmp:
		// get the ammoX value
		int ammoX = fo::func::item_w_dam_mult(ctd.weapon);
		if (ammoX <= 1)            // cmp eax, 0x1;                          // compare the ammoX value to 1
			goto bSubDJmp;         // jle bSubDJmp;                          // if the ammoX value is less than or equal to 1 then goto bSubDJmp;
		// get the ammoY value
		int ammoY = fo::func::item_w_dam_div(ctd.weapon); 
		if (ammoX <= 1)            // cmp eax, 0x1;                          // compare the ammoY value to 1
			goto aSubDJmp;         // jle aSubDJmp;                          // if the ammoY value is less than or equal to 1 then goto aSubDJmp
		
		//int ND2 = ND;            // mov edx, ebx;                          // set temp value
		armorDT = ND * 15;         // imul edx, 0xf;                         // multiply the ND value by 15
		ammoY = 100;               // mov eax, 0x64;                         // set divisor value to 100
		temp = 4;                  // mov dword ptr ss : [esp + 0x30], 0x4;  // set value to 4
		goto aJmp;
////////////////////////////////////
divFour:
		ND += aDT;                 // add ebx, eax;                          // add the quotient value to the ND value
		goto eJmp;
////////////////////////////////////
aSubDJmp:
		//int ND2 = ND;            // mov edx, ebx;                          // set temp value
		armorDT = ND * 20;         // imul edx, 0x14;                        // multiply the ND value by 20
		ammoY = 100;               // mov eax, 0x64;                         // set divisor value to 100
		temp = 5;                  // mov dword ptr ss : [esp + 0x30], 0x5;  // set value to 5
		goto aJmp;
////////////////////////////////////
divFive:
		ND += aDT;                 // add ebx, eax;                          // add the quotient value to the ND value
		goto eJmp;
////////////////////////////////////
bSubDJmp:
		// get the ammoY value
		int ammoY = fo::func::item_w_dam_div(ctd.weapon);                  
		if (ammoY <= 1)            // cmp eax, 0x1;                          // compare the ammoY value to 1
			goto eJmp;             // jle eJmp;                              // goto eJmp
		
		int ND2 = ND;              // mov edx, ebx;                          // set temp value
		ND2 *= 10;                 // imul edx, 0xa;                         // multiply the ND value by 10
		ammoY = 100;               // mov eax, 0x64;                         // set divisor value to 100
		temp = 5;                  // mov dword ptr ss : [esp + 0x30], 0x6;  // set value to 6
		goto aJmp;
////////////////////////////////////
divSix:
		ND += aDT;                 // add ebx, eax;                          // add the quotient value to the ND value
eJmp:
		if (ND <= 0)               // cmp ebx, 0x0;                          // compare the new damage value to 0
			goto noDamageJmp;      // jle noDamageJmp;                       // goto noDamageJmp
		
		int CM = multiplierDamage; // mov eax, dword ptr ss : [esp + 0x24];  // get the CM(Critical Multiplier) value
		if (CM <= 2)               // cmp eax, 0x2;                          // compare the CM value to 2
			goto addNDJmp;         // jle addNDJmp;                          // if the CM value is less than or equal to 2 then goto addNDJmp
		ND *= CM;                  // imul ebx, eax;                         // multiply the ND value by the CM value
		ND /= 2;                   // sar ebx, 0x1;                          // divide the result by 2
addNDJmp:
		*accumulatedDamage += ND;  // add dword ptr ds : [edi], ebx;         // accumulate damage	
noDamageJmp:
@NovaRain
Copy link
Collaborator

Glovz implemented a banker's rounding inside the formula code for integer division (except the last (ND * CM) / 2) instead of using normal idiv. That's why you see the flow jumping back and forth or doing loops.

@burner1024
Copy link
Contributor

Why not just a global script?

@FakelsHub
Copy link
Contributor Author

Why not just a global script?

no matter where, the main thing is to rewrite it in a normal form.

@phobos2077
Copy link
Collaborator

phobos2077 commented Feb 9, 2021

In my opinion everything that can be written as a script - should be. If I remember correctly everything needed (hook and all necessary parameters) already exist. Not many people care enough to mess with sfall code directly and allowing more people to read and tweak such code is healthy for community.

@NovaRain
Copy link
Collaborator

NovaRain commented Oct 23, 2021

OK, I think I get the main function right, still gonna do more testing though.

void DamageMod::DamageGlovz(fo::ComputeAttackResult &ctd, DWORD &accumulatedDamage, long rounds, long armorDT, long armorDR, long bonusRangedDamage, long multiplyDamage, long difficulty) {
	if (rounds <= 0) return;

	long ammoY   = fo::func::item_w_dam_div(ctd.weapon);    // ammoY value (divisor)
	if (ammoY <= 0) ammoY = 1;
	long ammoX   = fo::func::item_w_dam_mult(ctd.weapon);   // ammoX value
	if (ammoX <= 0) ammoX = 1;
	long ammoDRM = fo::func::item_w_dr_adjust(ctd.weapon);  // ammoDRM value
	if (ammoDRM > 0) ammoDRM = -ammoDRM;

	// start of damage calculation loop
	for (long i = 0; i < rounds; i++) {                     // check the number of hits
		long rawDamage = fo::func::item_w_damage(ctd.attacker, ctd.hitMode); // get the raw damage value
		rawDamage += bonusRangedDamage;                     // add the bonus ranged damage value to the RD value
		if (rawDamage <= 0) continue;                       // if raw damage <= 0, skip damage calculation and go to bottom of loop

		if (armorDT > 0) {                                  // compare the armorDT value to 0
			long calcDT = DivRound(armorDT, ammoY);
			rawDamage -= calcDT;                            // subtract the new armorDT value from the RD value
			if (rawDamage <= 0) continue;                   // if raw damage <= 0, skip damage calculation and go to bottom of loop
		}

		if (armorDR > 0) {                                  // compare the armorDR value to 0
			long calcDR = armorDR;
			if (difficulty > 100) {                         // if the CD value is greater than 100
				calcDR -= 20;                               // subtract 20 from the armorDR value
			} else if (difficulty < 100) {                  // if the CD value is less than 100
				calcDR += 20;                               // add 20 to the armorDR value
			}
			calcDR += ammoDRM;                              // add the ammoDRM value to the armorDR value
			calcDR = DivRound(calcDR, ammoX);               // goto divTwo
			if (calcDR >= 100) continue;                    // if armorDR >= 100, skip damage calculation and go to bottom of loop

			long resistedDamage = DivRound(calcDR * rawDamage, 100); // goto divThree
			rawDamage -= resistedDamage;                    // subtract the damage resisted value from the RD value
			if (rawDamage <= 0) continue;                   // if raw damage <= 0, skip damage calculation and go to bottom of loop
		}

		// bonus damage to unarmored target
		if (armorDT <= 0 && armorDR <= 0) {
			if (ammoX > 1 && ammoY > 1) {                   // FMJ/high-end
				rawDamage += DivRound(rawDamage * 15, 100); // goto divFour
			} else if (ammoX > 1) {                         // JHP
				rawDamage += DivRound(rawDamage * 20, 100); // goto divFive
			} else if (ammoY > 1) {                         // AP
				rawDamage += DivRound(rawDamage * 10, 100); // goto divSix
			}
		}

		if (formula == 2) { // v5.1 tweak
			rawDamage += DivRound(rawDamage * multiplyDamage * 25, 100); // goto divSeven
		} else {
			rawDamage = (rawDamage * multiplyDamage) >> 1;  // divide the result by 2
		}
		if (rawDamage > 0) accumulatedDamage += rawDamage;  // accumulate damage (make sure the final raw damage > 0)
	}
}

@FakelsHub
Copy link
Contributor Author

rawDamage = (rawDamage * multiplyDamage) >> 1; // divide the result by 2

Why not ... /2 ?

@NovaRain
Copy link
Collaborator

NovaRain commented Oct 23, 2021

rawDamage = (rawDamage * multiplyDamage) >> 1; // divide the result by 2

Why not ... /2 ?

Oh, the original ASM uses sar reg, 1 so I just use the same bitshift operation instead of division. Because the value of (rawDamage * multiplyDamage) is pretty much guaranteed to be a positive integer (unless someone tries to cause excessively high damage with damage multiplier to break 0x7FFFFFFF?), using right shift here should be fine I guess?

Using dumpbin to check the difference between the two:

// (rawDamage * multiplyDamage) >> 1
0F AF 75 18        imul        esi,dword ptr [ebp+18h]
D1 FE              sar         esi,1

//  (rawDamage * multiplyDamage) / 2
0F AF 75 18        imul        esi,dword ptr [ebp+18h]
8B C6              mov         eax,esi
99                 cdq
2B C2              sub         eax,edx
8B F0              mov         esi,eax
D1 FE              sar         esi,1

Maybe I should do a last check on rawDamage value before adding it to accumulatedDamage, as in YAAM?
if (rawDamage > 0) accumulatedDamage += rawDamage;

EDIT: updated the code with a couple more checks.

@FakelsHub
Copy link
Contributor Author

Maybe I should do a last check

I think there is no need.

DivRound - what is the function?

@NovaRain
Copy link
Collaborator

NovaRain commented Oct 23, 2021

Maybe I should do a last check

I think there is no need.

Well, just try to make sure the "overflow" case won't happen and negative value being added to accumulatedDamage.

DivRound - what is the function?

Currently it's this one, basically the same logic as in the original ASM code:

static long DivRound(long dividend, long divisor) {
	if (dividend == divisor) return 1;

	if (dividend < divisor) {
		dividend <<= 1; // multiply by 2
		return (dividend <= divisor) ? 0 : 1;
	} else {
		long quotient = 0;
		while (dividend >= divisor) {
			quotient++;
			dividend -= divisor;
		}
		// check the remainder
		if (dividend) {
			dividend <<= 1; // multiply by 2
			if (dividend > divisor) {
				quotient++;
			} else if (dividend == divisor) {
				if ((quotient & 1) != 0) quotient++; // round half to even
			}
		}
		return quotient;
	}
}

I run some numbers through it and the result is OK.
One interesting thing is, the compiler will inline this for some calls in the main function (not all of them), don't know if it should be set to noinline.

@FakelsHub
Copy link
Contributor Author

FakelsHub commented Oct 23, 2021

Some terrible function :)

long quotient = 0;
while (dividend >= divisor) {
	quotient++;
	dividend -= divisor;
}

It seems mathematically it should be like this

long quotient = dividend / divisor
dividend -= divisor * quotient

@NovaRain
Copy link
Collaborator

NovaRain commented Oct 24, 2021

Some terrible function :)

Because of the while loop? The original idea was to use subtraction instead of division operator to do the math, and I simply replicate it in the div func. It does look a bit silly for when the dividend is much bigger than the divisor (e.g. do 100 / 2 -> loop 50 times).
I guess the original ASM can be rewritten like this:

; original with subtraction loop
grThan:
        mov  ebp, eax; // assign the divisor value
        xor  eax, eax; // clear value
bbbJmp:
        inc  eax;      // add 1 to the quotient value
        sub  edx, ebp; // subtract the divisor value from the dividend value
        cmp  edx, ebp; // compare the remainder value to the divisor value
        jge  bbbJmp;   // if the remainder value is greater or equal to the divisor value then goto bbbJmp (loop)
        jz   endDiv;   // if the remainder value is equal to 0 then goto endDiv
        ...

; no loop
grThan:
        ;test eax, eax; // compare the divisor value to 0 (redundant?)
        ;jz   endDiv;   // avoid division by zero error
        mov  ebp, eax; // assign the divisor value
        mov  eax, edx; // assign the dividend value
        cdq;           // prepare for division
        idiv ebp;      // perform signed integer division (eax = quotient, edx = remainder)
        test edx, edx; // compare the remainder value to 0
        jz   endDiv;   // if the remainder value is equal to 0 then goto endDiv
        ...

It seems mathematically it should be like this

long quotient = dividend / divisor
dividend -= divisor * quotient

I think it can be simpler:

long quotient = dividend / divisor;
dividend %= divisor; // now it's the remainder

@FakelsHub
Copy link
Contributor Author

Because of the while loop?

not just because of the loop.
function can easily rebuild.

@NovaRain
Copy link
Collaborator

NovaRain commented Oct 24, 2021

Because of the while loop?

not just because of the loop. function can easily rebuild.

Any suggestion?
EDIT: I committed the current version, at least my tests checked out.

@NovaRain
Copy link
Collaborator

The new div function is much slimmer, thanks for the help.
Closing the issue as now the formula function is in C++.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants