Jump to content

Explosives Random Radius


anUser

Recommended Posts

After struggling against OR's, skip tokens and other nasty creatures from the world of hexes, I finally got explosive weapons with an extra random radius. The result may not be very appealing visually, but it indeed works. And this, combined with explosives random damage modlet now defenitively makes grenades something much less certain

Function: XComGame >> XGWeapon >> GetDamageRadius

Original code:

 

 

simulated function float GetDamageRadius()

{

    // End:0xF6

    if((m_eType == 7) || m_eType == 19)

    {

        // End:0xF6

        if((m_kOwner != none) && m_kOwner.GetCharacter().HasUpgrade(25))

        {

            return float(m_kTWeapon.iRadius + XComGameReplicationInfo(class'Engine'.static.GetCurrentWorldInfo().GRI).m_kGameCore.192);

        }

    }

    return float(m_kTWeapon.iRadius);

    //return ReturnValue;    

}

 

 

 

Weapon types 7 and 19 are rocket launcher and blaster launcher, respectively. HasUpgrade(25) is Danger Zone perk, and 192 is 3 tiles wide.

 

What I've done is removing the Danger zone check, and add random extra radius to either grenades and rockets

 

New Code:

 

 

simulated function float GetDamageRadius()

{

    // End:0x65

    if((m_eType == 85) || m_eType == 88)

    {

        return float(m_kTWeapon.iRadius + Rand(m_kTWeapon.iRadius / 2));

    }

    // End:0xEE

    if((m_eType == 7) || m_eType == 19)

    {

        return float(m_kTWeapon.iRadius + Rand(m_kTWeapon.iRadius / 2));                                                                                                                                                                                                                                                                                                

    }

    return float(m_kTWeapon.iRadius);

    //return ReturnValue;    

}

 

 

 

New code in hex:

 

 

27 B9 00 00 47 55 00 00 00 00 00 00 11 B9 00 00

00 00 00 00 00 00 00 00 12 B9 00 00 00 00 00 00

C0 00 00 00 56 17 00 00 18 01 00 00 C8 00 00 00

07 65 00 84 9A 01 C0 9B 00 00 2C 55 16 18 0E 00

9A 01 C0 9B 00 00 2C 58 16 16 04 38 3F 92 35 9D

0D 00 00 A8 0D 00 00 00 00 01 F1 B8 00 00 A7 91

35 9D 0D 00 00 A8 0D 00 00 00 00 01 F1 B8 00 00

2C 02 16 16 16 07 EE 00 84 9A 01 C0 9B 00 00 2C

07 16 18 0E 00 9A 01 C0 9B 00 00 2C 13 16 16 04

38 3F 92 35 9D 0D 00 00 A8 0D 00 00 00 00 01 F1

B8 00 00 A7 91 35 9D 0D 00 00 A8 0D 00 00 00 00

01 F1 B8 00 00 2C 02 16 16 16 0B 0B 0B 0B 0B 0B

0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B

0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 04 38

3F 35 9D 0D 00 00 A8 0D 00 00 00 00 01 F1 B8 00

00 04 3A 12 B9 00 00 53 00 00 00 02 01 02 00 03

32 00 00 00 00 00 00

 

 

 

Code break down:

 

 

; Function Header (notice Virtual Size changed to 0x118 <==> 18 01)
27 B9 00 00 47 55 00 00 00 00 00 00 11 B9 00 00

00 00 00 00 00 00 00 00 12 B9 00 00 00 00 00 00

C0 00 00 00 56 17 00 00 18 01 00 00 C8 00 00 00
 
;if not jump to 0x65

07 65 00
;logical OR ||
84
;logical equals ==
9A
;m_eType
01 C0 9B 00 00
;int 85
2C 55
;end equals
16
;skip token (skip next condition if previous one was true. Not sure exactly how it works, but it works)
18 0E 00
;logical equals ==
9A
01 C0 9B 00 00
01 C0 9B 00 00
; int 88
2C 58
;end equals
16
; end OR
16
 
;return
04
; explicit casting int to float
38 3F
 
;sum +
92
; m_kTWeapon.iRadius
35 9D 0D 00 00 A8 0D 00 00 00 00 01 F1 B8 00 00
; Rand()
A7
; division /
91
; m_kTWeapon.iRadius

35 9D 0D 00 00 A8 0D 00 00 00 00 01 F1 B8 00 00
; int 2

2C 02
; end division
16
; end random
16
; end sum
16
 
; rockets check, it's basically the same now
07 EE 00 84 9A 01 C0 9B 00 00 2C 07 16 18 0E 00
9A 01 C0 9B 00 00 2C 13 16 16 04 38 3F 92 35 9D
0D 00 00 A8 0D 00 00 00 00 01 F1 B8 00 00 A7 91
35 9D 0D 00 00 A8 0D 00 00 00 00 01 F1 B8 00 00
2C 02 16 16 16
 
; null values to fill empty space
0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B

0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B

0B 0B 0B 0B
 
; return null value and end-of-script stuff (do not touch)
04 38 3F 35 9D 0D 00 00 A8 0D 00 00 00 00 01 F1
B8 00 00 04 3A 12 B9 00 00 53 00 00 00 02 01 02
00 03 32 00 00 00 00 00 00

 

 

 

ight now once this change applied, when selecting a grenade or a rocket to throw, you'll see the blue semi-sphere marking the area of effect changing it's size constantly, as if blinking, so you can get an idea of how small or how big it can get, but there's no way to tell beforehand how it will end, no matter the size of the blue area at the moment of throwing the grenade, when it explodes it will calculate it again. Therefor I'm expecting inconsistant behaviour from AI, since they won't be able to tell 100% if a grenade will hit their target or won't, so maybe they'll hold some grenade they could throw or maybe they'll miss the grenade because of different random results. Anyway it should be of minor effect, but I'll report if I see it affecting in a relevant manner.

 

an please someone tell me how those skip token work? I assume they operate like: if previous condition evaluates to true, then skip the rest of checks (useful for conditions with a bunch of OR's, when it's either one or another or another one, so when one evals true just skip that section) but the numbers following the 18 puzzle me. I assume it's also some offset in little endian or however it's called, but the number itself I don't know what it should be. I got it working just by trial and error, but I can't see what it represents... and it's not even multiple of 4. Any clue?

 

edit: typo, copy-pasting, etc

Edited by anUser
Link to comment
Share on other sites

Very nice -- more randomness. In games like this, randomness typically hurts the player more then the AI. From a statistical point of view, the expected average result is that the player will win. The greater the randomness (i.e. the variance), the greater the likelihood that the AI will have the advantage. Plus, probabilistic decision trees are much much harder to navigate -- this is why there aren't any good Backgammon-playing computer programs like there are for chess.

 

As to the boolean operator optimization tokens: I looked at those a little while ago. The number after the token appears to be a relative offset to jump. Jump statements use absolute offsets (absolute with respect to the start of the function, anyhow). The boolean token offsets are relative to the token position -- it jumps ahead that many bytes if the optimization triggers (bytes are virtual bytes, not file-size bytes).

 

The optimization triggers are :

OR -- first boolean is true, expression is true

AND -- first boolean false, expression is false

Link to comment
Share on other sites

Yeah, as I suspected. Then I'm not counting well. I was taking the values from UE expl in view tokens, besides each item there's a number which I thought it was virtual size... but there are things I ignore if they should be counted like native functions that only take 1 byte. In this example...

 

(0x000) JumpIfNot(34) -> NativeFunction(31) -> NativeFunction(13) -> InstanceVariable(9) -> IntConstByte(2) -> EndFunctionParms(1) -> Skip(16) -> NativeFunction(13) -> InstanceVariable(9) -> IntConstByte(2) -> EndFunctionParms(1) -> EndFunctionParms(1)
    if((m_eType == 85) || m_eType == 88)

I know the offset is 14 (0E) because I've been trying, but I cannot figure this offset from this.

... and if I were to change several skips, as I pretend, if there's only OR's (A || B || C || D), the first "skip" points to "outside" the condition, so every successive skip token "skips less", right?

 

if I get it all in one sentence I'll try to bring back danger zone perk, as I've realized now this affects suppression area. btw I've been playing for a while with this and grenade random damage and I must say it's absolutely great. I'm playing a new game, got only 4 soldiers and faced a bunch of sectoids... what a difference now, when before throwing a grenade meant killing 3+ sectoids (including psi-link deaths) now it's completely different, you never know if that grenade will even be a kill. I've also reduced damage to 3 to increase the dramatizing effect, and sure it does.

 

A side note, I'm thinking if this were used on other weapons, given they had radius > 0 (I've tested changing it in the ini, it works), this random radius thing could be used to cause accidental friendly fire, or taking it further, make shotguns behave like shotguns, getting increased radius and decreased damage per distance.... if that were possible

Link to comment
Share on other sites

You should be able to save space by not using the optimization tokens. They are only optimizers, not required items. Without them, the second statement of an OR operation will be evaluated even if the first statement was true.

 

Since there aren't exactly a huge number of boolean operations being checked here, it should be fine time-wise to leave them out. That might free up some more bytes to fit in more functionality.

 

However, I have run into troubles trying to rewrite some boolean operations (when trying to get the weapon itemcard to show for SHIV weapons -- which still doesn't work). So, you might take this advice with a grain of salt.

Link to comment
Share on other sites

that's strange... I tried to write the condition without the skip token and with just two elements to evaluate and I couldn't get it to work until I put in the skip token... maybe I wasn't writting it properly or it was an error of endparam tokens (too much, too few) sometimes they seem to compile well but then they throw execution time errors... I'll give it another go, but I'd really appreciate if someone can point out how to count virtual bytes just in case I have to use the skip tokens, and just for knowing. I'll try to sum up what I've discovered so far, please let me know if I'm wrong or there's something I'm missing.

 

local variables take 5 physical bytes, starting with 01 (as in this example), and I've seen and used this technique of invoking a local variable just to take up virtual space so ifs and jumps keep pointing where they should and in order to preserve original virtual size, and I've noticed each invocation it takes 4 bytes. Knowing this and the original virtual size and current position of edn-of-script token I could calculate how many variable calls I have to make.

I've noticed there are things that don't make VS increase, like all of those 1 byte items, like operators, native functions, etc, is it because they take also 1 byte of "virtual size", whatever it means?

 

So, when counting towards this skip tokens, should I count 9 for local variables and 1 for native functions? Do 16 endParam tokens count as well? Anything I should exclude from counting?

 

I've managed to work with jumps/gotos, if/else, cause ue explorer tells where does each line start, so absolute offset is ok so far

 

btw, the original function made use of lots of explicit casting, even in the conditions where checked m_eType == 7 both values were converted to integer. I've tried without converting them and it works as well. I'm wondering if this is a common procedure in programming for the sake of security and consistency, or it's just code leftovers or compilation-time stuff that have nothing to do with how the original code was written...?

Link to comment
Share on other sites

I've finally got it written in one line, with added "Danger Zone" perk check. I had to use skip tokens after each "OR", fortunately I got it quickly enough.

 

Now the code reads:

 

simulated function float GetDamageRadius()
{
    // End:0xE2
    if((m_eType == 85) || (m_eType == 88) || (m_eType == 7) || (m_eType == 19) || m_kOwner.GetCharacter().HasUpgrade(25))
    {
        return float(m_kTWeapon.iRadius + Rand(m_kTWeapon.iRadius));                                                                                                                                                                                                                                                                        
    }
    return float(m_kTWeapon.iRadius);
    //return ReturnValue;    
}

 

I got the values for the skip tokens' offset from UE Explorer, in view tokens mode:

 

(0x000) JumpIfNot(130) -> NativeFunction(127) -> NativeFunction(13) -> InstanceVariable(9) -> IntConstByte(2) -> EndFunctionParms(1) -> Skip(112) -> NativeFunction(109) -> NativeFunction(13) -> InstanceVariable(9) -> IntConstByte(2) -> EndFunctionParms(1) -> Skip(94) -> NativeFunction(91) -> NativeFunction(13) -> InstanceVariable(9) -> IntConstByte(2) -> EndFunctionParms(1) -> Skip(76) -> NativeFunction(73) -> NativeFunction(13) -> InstanceVariable(9) -> IntConstByte(2) -> EndFunctionParms(1) -> Skip(58) -> Context(55) -> Context(31) -> InstanceVariable(9) -> VirtualFunction(10) -> EndFunctionParms(1) -> VirtualFunction(12) -> IntConstByte(2) -> EndFunctionParms(1) -> EndFunctionParms(1) -> EndFunctionParms(1) -> EndFunctionParms(1) -> EndFunctionParms(1)
    if((m_eType == 85) || (m_eType == 88) || (m_eType == 7) || (m_eType == 19) || m_kOwner.GetCharacter().HasUpgrade(25))

Notice the values right after Skip(112) -> NativeFunction(109), that PLUS ONE is the value for the skip tokens.

 

Now the code in hex is:

 

 

;function header
27 B9 00 00 47 55 00 00 00 00 00 00 11 B9 00 00
00 00 00 00 00 00 00 00 12 B9 00 00 00 00 00 00
C0 00 00 00 56 17 00 00 0C 01 00 00 C8 00 00 00

;function body
07 E2 00 84 9A 01 C0 9B 00 00 2C 55 16 18 6E 00
84 9A 01 C0 9B 00 00 2C 58 16 18 5C 00 84 9A 01
C0 9B 00 00 2C 07 16 18 4A 00 84 9A 01 C0 9B 00
00 2C 13 16 18 38 00 19 19 01 50 A6 00 00 0A 00
40 B4 00 00 00 1B 74 31 00 00 00 00 00 00 16 0C
00 C7 A2 00 00 00 1B B0 36 00 00 00 00 00 00 2C
19 16 16 16 16 16 04 38 3F 92 35 9D 0D 00 00 A8
0D 00 00 00 00 01 F1 B8 00 00 A7 35 9D 0D 00 00
A8 0D 00 00 00 00 01 F1 B8 00 00 16 16 0B 0B 0B
0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B
0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B

;return null + footer
04 38 3F 35 9D 0D 00 00 A8 0D 00 00 00 00 01 F1
B8 00 00 04 3A 12 B9 00 00 53 00 00 00 02 01 02
00 03 32 00 00 00 00 00 00
 
Notice the offsets and the values in the line above
6E = 110; 5C = 92; 4A = 74; 38 = 56

 

 

Now the radius may grow randomly up to double it's original radius, so I recommend setting them lower in DGC.ini. I'm currently playing with this values:

Frag grenade: 128 (from 2 to 4 tiles wide) Damage: 3

Alien grenade: 160 (from 2.5 to 5 tiles wide) Damage: 4

Rocket launcher: 192 (from 3 to 6 tiles wide) Damage: 5

Blaster launcher: 256 (from 4 to 8 tiles wide) Damage: 7

+ random +/-1 damage

 

As it is now the same check validates either for explosives OR for Danger Zone perk, because having both would have been ridiculous (I love the ability on MG but on missiles it kinda feels lame) so now machineguns (or every weapon with a radius greater than zero set in the ini, and in the hands of a soldier with the danger zone ability) will benefit from this randomly increased radius, not only on suppression, but also on regular shots.

I'm currently testing a radius of 64, which is 1 tile wide, so in theory it could end up covering 2 tiles wide, but I suspect it might affect more units at once depending on how the maths are done exactly. I'll try to find out, or most probably I'll just play it and adjust it as I see fit :p

 

Well if anyone decides to give it a try let me know what you think about it, if there's some interesting idea there's still some room left to add some small improvement.

 

Oh, and if someone figures out whether it's possible to create local variables, and how if so, I'd be very thankful. I could definitively compress that code pretty much and add a bunch of variations with a switch only that I could create a variable here. I guess I can use some other unused class variable.. and hope it isn't really used nor it gets removed next patch.

Link to comment
Share on other sites

Excellent job on getting this working! :dance:

 

Toning down the deterministic (and powerful) effects of grenades is a good thing, I think. Randomized damage + randomized radius = a lot more uncertainty. The only thing left is to have the toss location end up randomized (i.e. not perfect aim).

 

The game will probably need some rebalancing, though, as grenades were one of the principle ways to get advantage in the early game, which could already be tough on Impossible.

 

One question I had was regarding the actual radius. From earlier observations I'd noticed that it isn't a true 3D explosive sphere -- it doesn't seem to affect units above the aim point. Did you see anything in there relating to that, or is it buried deeper in?

 

I guess your experience shows why my boolean checks didn't work (crashed at run-time). That's good to know ... now I can go back and try to get the weapon item cards to work for the SHIV weapons. Thanks a bunch for the info!

Link to comment
Share on other sites

Not only it's less deterministic about output damage (whether you'll damage or kill your targets) but it also makes it you can no longer control 100% environment damage. How many times you've tossed a grenade just in the right position to kill an alien but not to damage that car one of your units is using as a cover? Try it now and you'll now risk.

 

The game will probably need some rebalancing, though, as grenades were one of the principle ways to get advantage in the early game, which could already be tough on Impossible.

I've choosen to boost MG and sniper by giving rifles and pistols long range penalty, including aliens', so you get the advantadge against sectoids and such at mid/long range combat, but close combat is always dangerous. I'm currently using explosives more as a mean to blow up covers rather than killing enemies, though.

 

One question I had was regarding the actual radius. From earlier observations I'd noticed that it isn't a true 3D explosive sphere -- it doesn't seem to affect units above the aim point. Did you see anything in there relating to that, or is it buried deeper in?

I haven't noticed that. At least for environment damage it seems to work as a sphere, I even recall seeing some wall being broken by the half down, and many times I've thrown a grenade above a car to hit the aliens around, and both aliens and the car got damaged all the times... but I'll keep an eye of it, although now it gets harder to confirm. Btw I haven't seen neither I've looked for the code responsible for the actual maths, I just happened to find that function.

Link to comment
Share on other sites

From what I've read (and partially experienced), the explosive damage only appears to affect the level of the explosion and the level below.

 

I have experienced exploding a rocket BELOW a aliens (in one of the construction yard missions where the aliens can start on floors 2 or 3) and the alien was unaffected.

 

However, it would not surprise me in the least if this were buried somewhere deep down in the Unreal Engine code.

Link to comment
Share on other sites

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...