Jump to content

R&D Modding Ammo


Amineri

Recommended Posts

I've had a few questions about this, so am starting up a new topic to collect R&D on the topic.

 

Being able to modify how much ammo is used in various situations would be useful in adjusting game balance, so here goes.

 

-------------

 

Ammo is actually stored as an integer with value ranging from 0 to 100.

 

If you use the developer console, the GiveAmmo command will adjust the ammo of the current soldier -- to refill the ammo you use GiveAmmo 100. GiveAmmo 50 results in the bar being half full.

 

This means that fire actions use the following ammo amounts:

 

Shooting, default -- 25 ammo

Shooting, eWP_Heavy -- 33 ammo

Suppression, rifle -- 50 ammo

Suppress, eWP_Heavy -- 66 ammo

 

The ammo conservation project halves these (rounding up I assume):

In theory this could allow for a lot of flexibility in figuring ammo costs in various situations.

 

-----------------------------------

 

Unfortunately, the GetAmmoCost function that the game uses to calculate ammo used in various situations is a native function, prototyped in XGTacticalGameCoreNativeBase:

 

native noexport simulated function int GetAmmoCost(int iWeapon, int iAbility, bool bHasAmmoConservation, optional out TCharacter kCharacter, optional bool bReactionFire)

Arguments include:
-- the weapon (which includes weapon properties such as eWP_Heavy)
-- the ability (so it knows if suppression is being used, for example)
-- booleans about Ammo Conservation and whether the shot is Reaction Fire
-- TCharacter out variable -- not sure what this is for, TBH
-------------------------------------
The good news is that GetAmmoCost isn't called very many places. There are only five locations in XComGame.upk it is called, which appear to satisfy all of the functionality around ammo usage.
1) XGAbilityTree.ApplyActionCost
This is where the "meat" is. Ammo is deducted as a cost of using an ability.

if(kAbility.HasProperty(1) || kAbility.GetType() == 40)
{
// End:0x1EB
if(kAbility.m_kWeapon != none)
{
iCost = XComGameReplicationInfo(class'Engine'.static.GetCurrentWorldInfo().GRI).m_kGameCore.GetAmmoCost(kAbility.m_kWeapon.GameplayType(), kAbility.GetType(), kAbility.m_kUnit.GetPlayer().HasFoundryHistory(10), kAbility.m_kUnit.GetCharacter().m_kChar, kAbility.m_bReactionFire);
}
// End:0x2B8

 

The iCost value is later applied in the same function via:

if(kAbility.m_kWeapon != none)
{
kAbility.m_kWeapon.ApplyAmmoCost(iCost);

 

This XGAbility.ApplyActionCost is where anUser has been making his changes to make items consumable, by deleting items from the unit's TInventory as an additional cost to using the action. With the space he has freed up, it could be possible to wrap the call to make modifications to the base ammo cost returned by GetAmmoCost. Alternatively, entirely new code could be built.

 

XGWeapon.ApplyAmmoCost is only ever called from here. It is pretty simple -- it simply subtracts the iCost from XGWeapon.iAmmo. If after subtraction iAmmo == 1, then it drops iAmmo to 0. (necessary because 100/3 isn't even).

----

 

2,3) UITacticalHUD_WeaponPanel.SetWeaponAndAmmo

 

Is called twice for two different cases. One for weapon overheating, one for not (Weapon Overheating was removed from the released game).

 

This is what displays the filled / flashing amount in the weapon outlines in the Weapon Panel of the Tactical HUD. Important to update this so that the player knows how much ammo she actually has.

 

----

 

4) XGUnit.SecondaryWeaponHasAmmoForShot

 

A pretty self-descriptive function that tests whether a secondary weapon has ammo. Pistols, Rocket Launchers and Arc Throwers are all currently defined as secondary weapons, but only Pistols use ammo AFAIK.

 

----

 

5) XGUnit.ReactionWeaponCheck

 

Tests to see if remaining ammo is sufficient to fire. Probably called prior to allowing Reaction Fire. Currently hardly used, since Reaction Fire was removed and replaced with Overwatch. Overwatch ability cannot be used without sufficient ammo.

 

 

---------------------------------------------------------

 

Modding ammo usage won't be easy, as workarounds to the core GetAmmoCost function being defined in native code will have to be found.

 

 

 

SideNotes:

 

 

Ideally Ammo would be an integer number from 0 to 120 (instead of 100).

 

2, 4, 5, and 10 divide 100 evenly, but trying to set it up to handle 3 or 6 shots results in awkward remainders.

 

2, 3, 4, 5, 6, 8, and 10 all divide 120 evenly, making it simpler to set up these number of shots before ammo is emptied.

 

To add in 7 and 9 as divisors, would require a max ammo count of 2520. This is the smallest integer for which any value from 2 to 10 will divide evenly.

 

2520 / 2 = 1260

2520 / 3 = 840

2520 / 4 = 630

2520 / 5 = 504

2520 / 6 = 420

2520 / 7 = 360

2520 / 8 = 315

2520 / 9 = 280

2520 / 10 = 252

 

2520 = 2^3 * 3^2 * 5 * 7

 

 

Link to comment
Share on other sites

  • Replies 71
  • Created
  • Last Reply

Top Posters In This Topic

Nice finding. I did once some testing with this but that was inconclusive, thanks for the clarifications.

 

A bit off topic, but I just want to point out that ammo re-balancing can be approached in a different manner, editting the dgc.ini and re-assigning classes. Giving the eWP_Heavy to rifles and pistols makes them have to reload more often that MG, snipers and shotguns. I *think* the code that sets which class uses which weapon property is in SetClass,

m_kSoldier.kClass.eWeaponType = X;
but I haven't tested that, I'm fine moving the mg to another class other than Heavy, and let the heavy keeps the rocket launcher + assault rifle.

 

That could be used to make each ability of the Arc Thrower consistently takes an appropiate amount of ammo, thus enabling it for different abilities. It could also make the iArcThrowerCharges variable obsolete because that could depend on ammo, thus freeing it for any other purpouse

Link to comment
Share on other sites

as you have the proper arguments for calling the function allready, i would suggest starting a framework for hooking the nativ functions.

 

As it sounds it shouldnt be terrible hard to just replace the function with what you wanted, or mod the return values based on the input values.

Link to comment
Share on other sites

One of the side effects of getting the new items in was that I had to fiddle with the ammo (because I had to take over the ApplyAmmoCost function), so I went ahead and hooked the GetAmmoCost function.

 

Instead of making a long call to XGTacticalGameCore, the function fits very naturally into XGAbility_Targeted, as all of the arguments can be accessed from there.

 

The plan is to take over the function GraduatedOdds(), which is some "cheat" code that helps the player on Easy difficulty. Graduate odds returns an int (the correct type) and can accept a boolean with the Foundry Ammo Conservation project info (since it's already built right there).

 

Here's my current 'guts' for the new ammo function:

	iAdjustment = 0;
	if(!m_kWeapon.IsMelee())
	{
		if(!m_kWeapon.HasProperty(eWP_UnlimitedAmmo)) ((10))
		{
			if(!bCantLose) // doesn't have bHasAmmoConservation
			{
				if(m_kWeapon.HasProperty(eWP_Heavy)  ((8))
				{
					iAdjustment = 25; // four shots
				}
				if(m_kWeapon.HasProperty(eWP_Support)  ((4))
				{
					iAdjustment = 25; // four shots
				}
				if(m_kWeapon.HasProperty(eWP_Pistol)  ((2))
				{
					iAdjustment = 50; // two shots
				}
				if(m_kWeapon.HasProperty(eWP_Rifle)  ((5))
				{
					iAdjustment = 33; // three shots
				}
				if(m_kWeapon.HasProperty(eWP_Assault)  ((6)) 
				{
					iAdjustment = 33; // three shots
				}
				if(m_kWeapon.HasProperty(eWP_Sniper)  ((7))
				{
					iAdjustment = 33; // three shots
				}
			}
			else // has ammo conservation
			{
				if(m_kWeapon.HasProperty(eWP_Heavy) ((8))
				{
					iAdjustment = 16; // six shots
				}
				if(m_kWeapon.HasProperty(eWP_Support) ((4))
				{
					iAdjustment = 16; // six shots
				}
				if(m_kWeapon.HasProperty(eWP_Pistol) ((2))
				{
					iAdjustment = 33; // three shots
				}
				if(m_kWeapon.HasProperty(eWP_Rifle) ((5))
				{
					iAdjustment = 20; // five shots
				}
				if(m_kWeapon.HasProperty(eWP_Assault) ((6))
				{
					iAdjustment = 20; // five shots
				}
				if(m_kWeapon.HasProperty(eWP_Sniper) ((7))
				{
					iAdjustment = 20; // five shots
				}
			}
			if (GetType() == 17) // check for suppression
			{
				iAdjustment = iAdjustment * 2;
			}
		}
	}
	return iAdjustment;

I've added separate checks for each of the six weapon types: Pistol, Rifle, Heavy, Sniper, Support, and Assault so that they can be configured separately.

 

This still leaves 243 bytes in the function to add new functionality.

 

The values themselves should be adjusted based on the final mod balance requirements. I'm more concerned about whether I'm missing any necessary functionality.

 

Are there any abilities that should be requiring more/less ammo? Specific weapon types?

 

Any other nifty features people would like to see added?

Link to comment
Share on other sites

Flush? and didn't you say you were going to be changing it to 120 total ammo?

 

I was going to change it to 120 ammo (actually calculated the amounts and put them in), and then I realized that it would require reworking the TacticalHUD_Weapon UI display code that shows ammo consumption, and decided that for the first pass it wasn't worth it.

 

Does Flush really take double ammo? Or are you asking that it be allowed to use a different amount of ammo?

Link to comment
Share on other sites

It takes double ammo atleast, usually it wipes out a huge amount of your ammo I don't know the exact amount, should probably be the same as Supression.

 

Wow.... just .... wow. Flush already seems like such a weak ability that to make it require double ammo seems kind of crazy.

 

I'll add the conditional to allow Flush to consume additional ammo, but am going to leave the default value to be normal ammo usage.

Link to comment
Share on other sites

Amineri, on 28 May 2013 - 17:26, said:

 

Yzaxtol, on 28 May 2013 - 17:19, said:

It takes double ammo atleast, usually it wipes out a huge amount of your ammo I don't know the exact amount, should probably be the same as Supression.

Wow.... just .... wow. Flush already seems like such a weak ability that to make it require double ammo seems kind of crazy.

 

I'll add the conditional to allow Flush to consume additional ammo, but am going to leave the default value to be normal ammo usage.

I think Flush uses 3 ammo charges, since it's a guaranteed hit (it has +30% hit chance and forces the target to move to another cover, although at reduced damage).

 

It is a good way of triggering "Close Combat Specialist" on a closer Assault or trigger Overwatch attacks for Opportunist Snipers who are not in direct (or Squadsight) LOS, and need the target to move somewhere else.

 

About that: What's the chance of hitting an alien who's on the run, with overwatch, apart from the -20% penalty? Does it get "exposed" (hence, OW has critical damage buffed), or it considers the older cover? half cover? full cover? range constraints?

Edited by osito2dancer
Link to comment
Share on other sites

I'm not entirely sure what the to-hit chances are for overwatch, as the "real" hit chance code is native, buried in xcomgame.exe.

 

All that we can glean about hit chances is from the separate code that displays the broken down hit chances, and empirically from the game effects. The "real" hit chance is displayed in the UI, though -- it is not calculated from the sum of the displayed modifiers (discovered this when trying to fix the UI for the Squadsight aim penalty).

 

From the XGAbility_Targeted.GetCritSummary:

    if(m_bReactionFire && m_kUnit.GetCharacter().m_kChar.aUpgrades[ePerk_Opportunist] == 0)
    {
        return;
    }

which basically says that you can't crit with reaction fire unless you have Opportunist. If you DO have Opportunist, all other crit-chance values appear unchanged, meaning that I'd think that the exposed crit bonus would come into play.

 

This potentially makes Opportunist an extremely powerful defensive perk (I personally think it goes best with Supports and Assault Rifles).

 

-------------------------------------------

 

I'm setting up Flush to, by default, use normal ammo:

 

	if (GetType() == eAbility_ShotFlush) ((12))
	{
		iAdjustment = 1 * iAdjustment;
	}

The 1 value will be represented via a "2C 01" hex code so that it can be easily modified to double or triple ammo as desired.

Link to comment
Share on other sites

  • Recently Browsing   0 members

    • No registered users viewing this page.

×
×
  • Create New...