Jump to content

How Soldiers get assigned a class


Amineri

Recommended Posts

I don't see why someone would use this to scam NCE as it would be faster and easier to not use NCE and to change the base stats for rookies to NCEs best.

 

My feeling is that it's a mostly psychological thing. It seems like there's a perceptual difference between exploiting a system that's in the game vs. actually editing a config file, even though the end result is the same.

Link to comment
Share on other sites

  • Replies 45
  • Created
  • Last Reply

Top Posters In This Topic

I see your point but exploiting a system that's only in the game because you added it vs. setting the game to give you what the exploded system would have seems to me to be ridiculous.

 

Fair enough :)

 

I was thinking more along the lines of what I think JL would consider publishable in Long War. In a full-up mod package like that (much like in the original game) most any exploit is considered 'legit' by the players.

 

If the person is modding it in for their own individual use then I 100% agree that's there's no difference.

Link to comment
Share on other sites

  • 2 months later...

I have now revisited this topic and redesigned PickAClass-function completely.

 

The new logic is:

  1. Check how many soldiers of each class is present in the barracks
  2. Select a random class
  3. Compare the randomly selected class representation to all other classes and if any other class is less represented then select that class instead.

This way, the least represented class will always be selected and if it is a tie then random class will be selected.

 

I have also added a very basic weight system that makes it possible to offset the balance in favor of one or more classes relative to each other.

 

The object code looks like this:

function XComGame.XGTacticalGameCoreData.ESoldierClass PickAClass()
{
  local array<XComGame.XGTacticalGameCoreData.ESoldierClass> arrClasses, candidateClasses;
  local XComGame.XGTacticalGameCoreData.ESoldierClass chosenClass;
  local int numOfChosenClass, I;

  arrClasses.Add(5);
  arrClasses[0] = 0;
  arrClasses[1] = (GetNumOfClass(1)) * 10; //Lower this number to increase Sniper representation
  arrClasses[2] = (GetNumOfClass(2)) * 10; //Lower this number to increase Heavy representation
  arrClasses[3] = (GetNumOfClass(3)) * 10; //Lower this number to increase Support representation
  arrClasses[4] = (GetNumOfClass(4)) * 10; //Lower this number to increase Assault representation
  chosenClass = Rand(4) + 1;
  numOfChosenClass = arrClasses[chosenClass];
  I = 1;
  J0xC7:
  // End:0x136 [Loop If]
  if(I < 5)
  {
    // End:0x128
    if(arrClasses[I] < numOfChosenClass)
    {
      chosenClass = I;
      numOfChosenClass = arrClasses[chosenClass];
    }
    ++ I;
    // [Loop Continue]
    goto J0xC7;
  }
  return chosenClass;
  //return ReturnValue;  
}

If you lower the weight for Snipers to 5 then you will end up with twice as many snipers as any other class (10/5=2)

If you lower it to 1 you will end up with 10 times as many snipers (10/1=10)

 

Important! You must not increase any of the weight numbers above 10! (you would risk ending up with calculations resulting in a higher number than 255 and break the function). (Edit: or well.... you could get away with it if the other weights are very low... But I still don't recommend it...) Edit: On third thought, It would probably no be a problem unless ending up with a weighted number larger than 32000 something... Which means you can toy around pretty much any way you want with the weights.

 

Code:

XGFacility_Barracks.PickAClass: XComStrategyGame.upk Offset: 2864023 (0x2BB397)
9A 28 00 00 AB 1F 00 00 00 00 00 00 92 28 00 00 00 00 00 00 00 00 00 00 9A 28 00 00 00 00 00 00 3A 05 00 00 41 92 00 00 4B 01 00 00 22 01 00 00 54 00 99 28 00 00 2C 05 16 0F 10 25 00 99 28 00 00 25 0F 10 2C 01 00 99 28 00 00 90 1B D2 0F 00 00 00 00 00 00 2C 01 16 2C 0A 16 0F 10 2C 02 00 99 28 00 00 90 1B D2 0F 00 00 00 00 00 00 2C 02 16 2C 0A 16 0F 10 2C 03 00 99 28 00 00 90 1B D2 0F 00 00 00 00 00 00 2C 03 16 2C 0A 16 0F 10 2C 04 00 99 28 00 00 90 1B D2 0F 00 00 00 00 00 00 2C 04 16 2C 0A 16 0F 00 97 28 00 00 92 A7 2C 04 16 26 16 0F 00 96 28 00 00 10 00 97 28 00 00 00 99 28 00 00 0F 00 95 28 00 00 26 07 36 01 96 00 95 28 00 00 2C 05 16 07 28 01 96 10 00 95 28 00 00 00 99 28 00 00 00 96 28 00 00 16 0F 00 97 28 00 00 00 95 28 00 00 0F 00 96 28 00 00 10 00 97 28 00 00 00 99 28 00 00 A5 00 95 28 00 00 16 06 C7 00 04 00 97 28 00 00 04 3A 9A 28 00 00 53 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 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 00 00 00 02 00 02 00 B6 21 00 00 00 00 00 00

Edit:

If 100% deterministic except for tie is unwanted it would be relatively simple to substract a random factor on top of the weights... arrClasses[1] = ((GetNumOfClass(1)) * 10) - rand(20);

This would allow any class to be randomly under-represented by up to 2 soldiers at any given class-selection. Sometimes allowing a slightly over-represented class become even more over represented.

 

Toolboks mod:

 

 

MOD_NAME=Auto-Balanced Squad Composition
AUTHOR=Bertilsson
DESCRIPTION=Always receive the least represented class on rookie promotion. Reward soldiers are not affected.

Version: 1.0

Compatible with XCOM Enemy Unknown versions:
 - Patch 4 ( Changelist: 356266 )

UPK_FILE=XComStrategyGame.upk
OFFSET=2864023
[MODDED_HEX]
9A 28 00 00 AB 1F 00 00 00 00 00 00 92 28 00 00 00 00 00 00 00 00 00 00 9A 28 00 00 00 00 00 00 3A 05 00 00 41 92 00 00 4B 01 00 00 22 01 00 00 54 00 99 28 00 00 2C 05 16 0F 10 25 00 99 28 00 00 25 0F 10 2C 01 00 99 28 00 00 90 1B D2 0F 00 00 00 00 00 00 2C 01 16 2C 0A 16 0F 10 2C 02 00 99 28 00 00 90 1B D2 0F 00 00 00 00 00 00 2C 02 16 2C 0A 16 0F 10 2C 03 00 99 28 00 00 90 1B D2 0F 00 00 00 00 00 00 2C 03 16 2C 0A 16 0F 10 2C 04 00 99 28 00 00 90 1B D2 0F 00 00 00 00 00 00 2C 04 16 2C 0A 16 0F 00 97 28 00 00 92 A7 2C 04 16 26 16 0F 00 96 28 00 00 10 00 97 28 00 00 00 99 28 00 00 0F 00 95 28 00 00 26 07 36 01 96 00 95 28 00 00 2C 05 16 07 28 01 96 10 00 95 28 00 00 00 99 28 00 00 00 96 28 00 00 16 0F 00 97 28 00 00 00 95 28 00 00 0F 00 96 28 00 00 10 00 97 28 00 00 00 99 28 00 00 A5 00 95 28 00 00 16 06 C7 00 04 00 97 28 00 00 04 3A 9A 28 00 00 53 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 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 00 00 00 02 00 02 00 B6 21 00 00 00 00 00 00 

 

 

Edited by Bertilsson
Link to comment
Share on other sites

Hmmmm...

 

Seeing your implementation has gotten my creative juices flowing. Instead of your deterministic method or the buggy deterministic method in the vanilla game, it might be possible to randomize but still have things weighted so that they attempt to correct balance.

 

Also, for Long War this doesn't take into account the subclasses -- a player might have 8 Infantry and 0 Assault. But maybe that's okay.

 

Anyhow, there are several examples of the developers using a weight randomized selection in the game code. The two that pop to mind are the selection of alien type for each pod, and the selection of ability used for each alien each turn.

 

Essentially you assign a weight to each possible outcome (as you've done), and then roll a die equal to the total weight. You then loop through the possible outcomes, tallying the sum as you go, until the roll is less than the current partial sum, at which point you select the current outcome.

 

In this case you want to use two factors:

1) Lower number of soldiers of a particular class generates a higher weight for selecting the soldier

2) Each soldier class can be manually weighted so that the target ratios are not necessarily equal

function XComGame.XGTacticalGameCoreData.ESoldierClass PickAClass()
{
  local array<XComGame.XGTacticalGameCoreData.ESoldierClass> arrClasses, candidateClasses;
  local XComGame.XGTacticalGameCoreData.ESoldierClass chosenClass;
  local int numOfChosenClass, I;

  arrClasses.Add(5);
  arrClasses[0] = 0;
  sumClass = 0;
  arrClasses[1] = 100 + (100 * 2) / (2 + GetNumOfClass(1))); 
  sumClass += arrClasses[1];
  arrClasses[2] = 100 + (100 * 2) / (2 + GetNumOfClass(2)));
  sumClass += arrClasses[2];
  arrClasses[3] = 100 + (100 * 2) / (2 + GetNumOfClass(3))); 
  sumClass += arrClasses[3];
  arrClasses[4] = 100 + (100 * 2) / (2 + GetNumOfClass(4)));
  sumClass += arrClasses[4];
  iRoll = Rand(sumClass);
  numOfChosenClass = arrClasses[chosenClass];
  I = 1;
  sumClass = 0;
  J0xC7:
  // End:0x136 [Loop If]
  if(I < 5)
  {
    sumClass += arrClasses[I];
    if(iRoll <= sumClass)
    {
      return I
    }
    ++ I;
    // [Loop Continue]
    goto J0xC7;
  }
  return 4;
  //return ReturnValue;  
}

What this does is assign an initial weight to each class -- in the example each class is equally weighted at 2 * 100. 100 was picked to minimize underflow with a large barracks. Each class weight is reduced proportionally based on the number of current members in the class.

 

The weights are designed so that there is a minimum weight for each class regardless of how many soldiers of that class there are. The 2's represent a scale factor that controls how quickly the weight reduces as the number of soldiers increases.

 

After the weights are all set, the roll is made against the total weight for all classes. Then the classes are looped through to select the appropriate class based on the roll.

 

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

 

I'm going to dissect the weight assignment to try and clarify what the different values are doing:

100 + (100 * 2) / (2 + GetNumOfClass(1)));

The first 100 value is the minimum weight for that class. No matter how many soldiers in a class there is at least some chance of still picking that class (for those 'That's XCOM, baby!' moments ^_^).

 

The second 100 is the used to scale the weight based on number of current members of the class. If there are no class members then the weight would be 200, which is the maximum possible weight.

 

The 2 values of 2, in terms of understanding, should be arranged as (2 / (2 + GetNumOfClass(1))). The order of operations was changed to a mathematically equivalent form in order to make the integer division work properly.

 

If NumClass = 0, then the scale factor is 1, resulting in weight 200

If NumClass = 1, then the scale factor is 2/3, resulting in weight 166

If NumClass = 2, then the scale factor is 2/4, resulting in weight 150

 

For comparison, if the two 2 values were both changed to 4, then the weight would scale down more slowly as the number in the class increased.

 

With 4:

If NumClass = 0, then the scale factor is 1, resulting in weight 200

If NumClass = 1, then the scale factor is 4/5, resulting in weight 180

If NumClass = 2, then the scale factor is 4/6, resulting in weight 166

etc

 

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

 

I'm not quite sure that this is the best weighting formula, but it's one idea.

 

The core concept is to preserve some randomness in class selection but move aware from a pure uniform randomness of class selection, and instead use a weighted randomization with the weights based upon the number of each class.

Link to comment
Share on other sites

I like the model. But I would personally describe it a little bit different (not necessarily better).

 

Basically what you do is:

Assign a slice of the total probability cake to each class and use the rand function on the sum of the total cake to select winner.

 

The individual slices are defined by a fixed base value (100) + a non-fixed value (200 / (numberOfClassSoldiers + 2)) resulting in each slice ranging in size of 100 to 200.

 

Resulting example:

1 Sniper (100+200/3=166)

2 Heavies (100+200/4=150)

3 Assaults (100+200/5=140)

4 Supports (100+200/6=133)

Total cake size: 166+150+140+133=589

 

Sniper probability 166 / 589 = 28.2%

Support probability: 133 / 589 = 22.6%

 

In my tent at camp deterministic that feels like a very large chunk of random regardless of the number of soldiers of each class present in the barracks.

 

In my tent we would probably go for something closer to 10 as fixed value, still giving all classes a piece of the cake.

 

As a final note I suspect the real cake size is more correctly described in the example as 590 due to the sniper slice having value 0 as part of the slice and thereby a very small advantage against the others :smile:

Edited by Bertilsson
Link to comment
Share on other sites

Oh definitely the weighting function would need some tuning. I just wanted to toss out an at least semi-workable weighting function as an example.

 

The primary question in my mind is whether it is worthwhile to implement such a hybrid system that falls in-between (1) a full on deterministic system (always choose the class that is least represented) and a uniform and (2) a uniformly randomly distributed system.

 

If I'm comprehending it correctly the code you implemented very rarely randomly selects soldiers (if it happens to randomly select one of the classes that is with the minimum soldiers). If it randomly selects a soldier NOT with the minimum number, then it expresses a preference in the order (1) Sniper, (2) Heavy, (3) Support, (4) Assault. This is because if Sniper and Heavy have the same (minimum) number, then Sniper is selected first. Because Heavy and Sniper have the same number Sniper will be selected first.

 

Example:

1 Sniper, 1 Heavy, 5 Support, 5 Assault.

 

If Sniper, Support, or Assault is randomly selected then the class will end up as Sniper. 75% odds

If Heavy is randomly selected then the class will end up as Heavy. 25% odds

 

Not a big deal in the long run, as the next class (barring more casualties) is guaranteed to be a Heavy.

Link to comment
Share on other sites

How about doing it like this instead?

if(rand(2) //50% chance

{

return rand(4)+1; //Return a random class

}

else

{

least represented class is selected

}

 

This would be very simple to implement and there would be be a 50% chance each for random or need based selection.

 

I would expect the in-game result to be that you never have perfectly balanced barracks but no extreme offsets either.

Edited by Bertilsson
Link to comment
Share on other sites

I guess this could also be a possibility:

if(arrSoldiers.Length < 1) //No soldiers in barracks
{
  return rand(4) + 1; //Return a random soldier
}

chosenClass = 0; //No class selected
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.
  }
}
return chosenClass;
rand(arrSoldiers.Length)+1 will always be in the range of 1 to total number of soldiers (0 will never happen since the function would have returned on line 3)
If total number of soldiers is 0 then the randomly selected will always succeed since 0 is not bigger than or equal to 1.

If my discrete mathematics is not completely off this should give the following probabilities:

If a class is representing 25% (or 0 of 0) of population it will have 25% chance of being selected.

If a class is representing 33.33% of population it will have 16,67% chance of being selected.

If a class is representing 50% of population it will have 12.5% chance of being selected.

If a class is representing 66,67% of population it will have 8.33% chance of being selected.

If a class is representing 75% of population it will have 6.75% chance of being selected.

If a class is representing 90% of population it will have 2.5% chance of being selected.

If a class is representing 100% of population it will have 0% chance of being selected.

If a class is representing less than 25% of population it will have 100% chance to be selected minus the sum of the other classes chances to be selected :smile:

 

If a class has 0 soldiers of total 3 soldiers it should give between 33.3% and 50% chance to be selected depending on how the 3 soldiers are distributed on the other classes.

 

Pros

  • Logically unbiased in favor of any given class
  • Self weighting bias to any under or over represented class
  • Very short and thereby simple to integrate as a module in a bigger function.

Cons

  • Could in theory loop forever
    • In reality it will succeed within a very small number of loops and it will not be humanly possible to detect any delay.
  • No simple way to add weights in favor of any class
  • My discrete mathematics could be completely off...
Edited by Bertilsson
Link to comment
Share on other sites

  • Recently Browsing   0 members

    • No registered users viewing this page.

×
×
  • Create New...