Jump to content

How Soldiers get assigned a class


Amineri

Recommended Posts

I think the loop works but but could be made even a bit smaller. Plus the negative exit condition kept turning my around while trying to think it through.

while (chosenClass == 0) //Until a class has been selected and not failed
{
  chosenClass = rand(4) + 1; //Randomly select a candidate class
  if(GetNumOfClass(chosenClass) >= rand(arrSoldiers.Length)+1) //Rand(1)+1 == 1. Rand(2)+1 == 1 or 2.
  {
    chosenClass = 0; //Selection failed, keep looping.
  }
}

Suppose there were 100 soldiers, with 1 soldier of type 1 and 99 soldiers of type 2 (ignore types 3 and 4 for simplicity). Each loop there is a 50% chance of picking type 1 or type 2. If type 1 is picked then the continue condition becomes 1 >= rand{1 ... 100}, or a 100% chance to exit. If type 2 is is picked then the continue condition becomes 99 >= rand{1 ... 100}, or a 2% chance to exit.

 

The condition could be logically reversed and the continue turned into an exit statement.

J0x00##:
{
  chosenClass = rand(4) + 1; //Randomly select a candidate class
  if(GetNumOfClass(chosenClass) < rand(arrSoldiers.Length)+1) //Rand(1)+1 == 1. Rand(2)+1 == 1 or 2.
  {
    break; // found appropriate class
  }
  goto 0x00##; // indefinite loop
}
//target of break

This has the same flaw as the original (indefinitely long loop that could in theory go on forever, but in practice will execute very quickly).

 

Same example as previous. If class 1 is picked then loop exits if 1 < rand{1 ... 100}, so a 99% chance to exit (only fails if rand is 1). If class 2 is picked the loop exits if 99 < rand{1 ... 100}, so only a 1% chance to exit.

 

This code should be a bit smaller in hex as it removes an assignment and a conditional. Can also shrink it down a bit by using the pre-increment operator 0xA3 (the code mostly uses 0xA5, which is post-increment). I think the hex code should look something like :

J0x00##:
{
  chosenClass = ++rand(4); //Randomly select a candidate class
  0F 00 #1 #1 #1 #1 A3 A4 2C 04 16 

  if(GetNumOfClass(chosenClass) < ++rand(arrSoldiers.Length)) //Rand(1)+1 == 1. Rand(2)+1 == 1 or 2.
  07 ## ## 96 1B #2 #2 #2 #2 00 00 00 00 00 #1 #1 #1 #1 16 A3 A4 36 00 #3 #3 #3 #3 16 16 16 

  {
    break; // found appropriate class
    06 ## ##
  }
  goto J0x00##; // indefinite loop
  06 ## ##
}
//target of break

My count comes out to 47 bytes required for this version. #1 is the reference for chosenClass, #2 is the reference for GetNumOfClass and #3 is the reference for arrSoldiers.

Link to comment
Share on other sites

  • Replies 45
  • Created
  • Last Reply

Top Posters In This Topic

First two mistakes I've made:

  • Assualts are class 4 and Supports are class 3 (I got them mixed up in the comments of the "original" function).
  • arrSoldiers.Length must not be used (it includes all living soldiers, including rookies which have no class)
    • I guess I focused so much on finding something short and simple that I forgot what I needed :smile:
    • There is however a GetNumOfVeterans() function which returns only soldiers of rank > 0 which was the indended value all along.

 

Also I'm not sure if the proposed modification of the code still includes the before loop check: if (no veterans) {return rand(4)+1;} ?

 

Originally I planned to leave that part out, but due to being unsure what would be the exact result of rand(0) I wanted to handle that separately, just to be on the safe side and save myself head ache in case it doesn't work the way I think it does.

 

I _think_ rand(0) would return 0 to 65535... And then I think >32768 could possibly be interpreted as negative value and then there would also be the question of what happens with 65535 +1?

 

I will most likely put a little code together later this evening and do some testing.

 

I will also probably add in an optional little weighting scheme, favouring or unfavouring different classes, as long as we don't need the extra space for something else.

 

Edit: In the end I will probably end up with something close to the code you proposed to begin with :D

Edited by Bertilsson
Link to comment
Share on other sites

My most recent proposal was only to replace the loop portion. You'd still want the check to have completely random if there were no soldiers with classes selected yet.

 

However, as you point out this type of system has the additional characteristic of being able to weight the different classes so that the expected percentage of each isn't the same.

 

All this said, just for the Long War mod -- a lot of the variance in the classes has been adjusted via the subclasses, which can already be selected amongst.

 

Snipers likely have a lower casualty rate but Scouts likely have a higher casualty rate, bringing scout/sniper to closer to average.

Gunner/Rocketeer likely has typical casualty rate since both have to be in sight of aliens

Assaults likely have higher casualty rate but Infantry have lower (since they can play with much more defensive tactics, e.g. shoot/hunker, shoot/retreat out of LOS)

Medics probably have slightly lower casualty rate (unless they have to run in to heal/revive), but Engineers likely have slightly higher casualty rates due to requirements in closer grenade range.

 

I think this is why JL decided to just stick with random base class since the subclass can be selected.

 

Even if the game only ever rolled up a single class, a mix of the two subclasses could likely still be reasonably effective.

 

As vanilla stands, the different classes have different casualty rates, for sure. However, it's not clear to me what the "proper" ratio of classes should be. It seems that it really comes down to personal preferences, so you'd really want a UI to allow the player to adjust the class ratios.

 

Also, another player's thoughts on random classes from the 2k forums:

 

 

by GreasyDave

 

I know it's an annoying feature - but I kind of got used to it, and quite liked it. I think the way it works on the ground is that it forces me to improvise and step out of my comfort zone. I need to keep a rookie in the squad for at least the first two months, until I get a broad spread of back ups. And that forces me into certain tactical decisions...the biggest one being who do I leave behind?

That's what I like about X com Eu in general, more so with the second wave options turned on. There's a bunch of design decisions that force me out of my comfort zone, regularly. So I don't get bored as quickly or as easily.

Yes, I agree, it is annoying. But that's x com babyhttp://forums.2kgames.com/images/dark/smilies/smile.gif I just had to slip that in

Link to comment
Share on other sites

As vanilla stands, the different classes have different casualty rates, for sure. However, it's not clear to me what the "proper" ratio of classes should be. It seems that it really comes down to personal preferences, so you'd really want a UI to allow the player to adjust the class ratios.

That is actually a very good idea!

 

It would be very simple to assign a fixed roll percentage for each class and make an UI that generates a meta-mod with user selected percentage for each class.

Example:

Sniper: 25%

Assault: 30%

Heavy 25%

Support 20%

 

But given that Enemy within is only a few weeks away and with absolute certainty will mess with the very definition of classes it seems like a waste to create an UI for something almost guaranteed to be obsolete before being used by anyone.

 

From that perspective combined with me likely being the only mod-user, and that my personal preference is deterministic, I think I will put this thread on ice and revisit the topic when EW has arrived :smile:

Link to comment
Share on other sites

Amineri, I'm writing a description about class assigment for the UFOPaedia Classes page using the info on this post. Could you briefly describe the factors related with ?the PickRewardSoldierClass function when it chooses the class?

 

Sure thing.

 

Just for your assurance I'm going back to an archived set of decompiled vanilla code instead of looking at the Long War code (since I think JL modded this function for Long War).

 

As you point out, the relevant function is in XGStrategyGame.upk >> XGFacility_Barracks.PickRewardSoldierClass. This is a completely different function from the previously mentioned functions that Bertillson and I have been discussing.

 

Here is the complete UE Explorer function decompile, which is a bit of a hot mess:

 

 

function XComGame.XGTacticalGameCoreData.ESoldierClass PickRewardSoldierClass()
{
	local array<int> arrClasses, arrLevels;
	local array<XComGame.XGTacticalGameCoreData.ESoldierClass> arrRewards;
	local XComGame.XGTacticalGameCoreData.ESoldierClass eClass;
	local int iSoldier, iRank, iClass, iRewardThreshhold;

	arrClasses.Add(6);
	arrLevels.Add(6);
	iSoldier = 0;
	J0x25:

	if(iSoldier < m_arrSoldiers.Length)
	{
		if(m_arrSoldiers[iSoldier].IsATank())
		{
		}
		else
		{
			eClass = m_arrSoldiers[iSoldier].GetClass();
			iRank = m_arrSoldiers[iSoldier].GetRank();
			arrClasses[eClass] += 1;
			if(iRank > arrLevels[eClass])
			{
				arrLevels[eClass] = iRank;
			}
		}
		++ iSoldier;
		// This is an implied JumpToken; Continue!
		goto J0x25;
	}
	iRewardThreshhold = 2;
	iClass = 0 + 1;
	J0x154:
	if(iClass < 5)
	{
		if(arrClasses[iClass] < iRewardThreshhold)
		{
			iSoldier = 0;
			J0x190:

			if(iSoldier < iRewardThreshhold - arrClasses[iClass])
			{
				arrRewards.AddItem(byte(iClass));
				++ iSoldier;
				// This is an implied JumpToken; Continue!
				goto J0x190;
			}
		}
		++ iClass;
		// This is an implied JumpToken; Continue!
		goto J0x154;
	}
	if(arrRewards.Length != 0 && (Roll(50)))
	{
		return arrRewards[Rand(arrRewards.Length)];
	}
	else
	{
		arrRewards.Remove(0, arrRewards.Length);
	}
	iClass = 0 + 1;
	J0x24f:

	if(iClass < 5)
	{
		if(arrRewards.Length == 0)
		{
			arrRewards.AddItem(byte(iClass));
		}
		else
		{
			if(arrLevels[iClass] < arrLevels[arrRewards[0]])
			{
				arrRewards.Remove(0, arrRewards.Length);
				arrRewards.AddItem(byte(iClass));
			}
			else
			{
				if(arrLevels[iClass] == arrLevels[arrRewards[0]])
				{
					arrRewards.AddItem(byte(iClass));
				}
			}
		}
		++ iClass;
		// This is an implied JumpToken; Continue!
		goto J0x24f;
	}
	if(arrRewards.Length != 0 && (Roll(50)))
	{
		return arrRewards[Rand(arrRewards.Length)];
	}
	else
	{
		arrRewards.Remove(0, arrRewards.Length);
	}
	eClass = byte(Rand(5 - 1) + 1);
	if(eClass != 1 && (eClass != 2) && (eClass != 3) && (eClass != 4))
	{
		eClass = 2;
	}
	return eClass;
} 

 

 

 

The first loop determines the highest rank in each class as well as the number of each class. Since it is using the XGFacility_Barracks.m_arrSoldiers list this includes soldiers that are injured (But not soldiers that are dead. Dead soldiers are removed from m_arrSoldiers and stored in m_arrFallen in the functions UnloadSoldier and RemoveSoldier.)

 

arrClasses stores the number of soldiers of each class, while arrLevels stores the highest current rank of each class.

 

The first test checks to see if any soldier class has fewer than 2 soldiers (i.e. 0 or 1 soldiers). This is alive soldiers (but possibly wounded). If there are any classes with 0 or 1 alive soldiers, then there is a 50% roll that one of those classes (chosen at random if there are multiple classes) will be picked.

 

If no soldier is picked from the first test, then the function proceeds to the second test.

 

The second test looks for the class(es) with the lowest current maximum rank. If there are multiple classes in which the highest rank soldier are the same (and are less then the other class(es)), it builds an array of all such classes. There is a 50% roll to pick one of these classes.

 

If the second test does not return a class then the function uniformly randomly selects a class.

 

If that random function were somehow to fail (it never should), then the function as default returns the Heavy class.

 

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

 

In summary, PickRewardSoldierClass :

0) Considers all alive soldiers (including wounded soldiers) when determining what class to pick

1) If any classes have 0 or 1 soldiers alive, there is a 50% chance that one of those classes will be picked (equal chance for each)

2) If the class is not picked in step 1, there is a 50% chance of picking the class with the smallest max rank. If multiple classes are tied for the smallest max rank then randomly pick one of them.

3) If the class is not picked in step 2, then randomly select a class, with each class having equal chance

Link to comment
Share on other sites

@Bertilsson

 

One other thing to consider is that XGFacility_Barracks.m_arrSoldiers also includes SHIVs, which have to be excluded from the list. I think that your GetNumVeterans() function would exclude SHIVs, since their rank should always be zero, but it's something to test for.

Link to comment
Share on other sites

PickRewardSoldierClass does exactly that with the following loop:

	J0x25:
	if(iSoldier < m_arrSoldiers.Length)
	{
		if(m_arrSoldiers[iSoldier].IsATank())
		{
		}
		else
		{
			eClass = m_arrSoldiers[iSoldier].GetClass();
			iRank = m_arrSoldiers[iSoldier].GetRank();
			arrClasses[eClass] += 1;
			if(iRank > arrLevels[eClass])
			{
				arrLevels[eClass] = iRank;
			}
		}
		++ iSoldier;
		// This is an implied JumpToken; Continue!
		goto J0x25;
	}

The loop counts both the number of soldiers of each class (arrClasses) as well as recording the highest rank achieved for each class (arrLevels).

Edited by Amineri
Link to comment
Share on other sites

 

In summary, PickRewardSoldierClass :

0) Considers all alive soldiers (including wounded soldiers) when determining what class to pick

1) If any classes have 0 or 1 soldiers alive, there is a 50% chance that one of those classes will be picked (equal chance for each)

2) If the class is not picked in step 1, there is a 50% chance of picking the class with the smallest max rank. If multiple classes are tied for the smallest max rank then randomly pick one of them.

3) If the class is not picked in step 2, then randomly select a class, with each class having equal chance

 

 

This is great, thank you.

 

I also recall reading something on these forums about how the soldier's rank for rewards is chosen but I can't find it. From my experience playing the game it seems that it is related to the soldiers present in the Barracks, i.e. if the maximum rank present is Sergeant than you can that one or the rank immediately above it (a Lieutenant), with the maximum rank possible being Major.

 

Do you know anything about that? Sorry for being off topic but you know a lot about those subjects :smile:

Edited by Hobbes77
Link to comment
Share on other sites

  • Recently Browsing   0 members

    • No registered users viewing this page.

×
×
  • Create New...