Jump to content

R&D Dynamic UFO selection


Tycus

Recommended Posts

Recently I found myself in need of very specific modlet, that will allow to change, which UFO types are doing missions. The reason is stated in my mod thread: http://forums.nexusmods.com/index.php?/topic/1033516-combined-mod/?p=8511520 (for anyone interested). Anyway, what exactly I mean by statement above:

 

Introduction (you can skip this part, if you already know, how missions work):

Abductions (and terror) use next system: first, there is UFO created, that flies to scout target (usually Large Scout /Battleship in case of terror; that can be changed), if that UFO is shot down, then mission there is canceled in case of abduction or not in case of terror (it also can be changed for both cases to either result); and there is number of UFOs that fly around with no particular mission, and can land (depends on "require interception" chance in DGC.ini); also only way to landed UFO mission to happen. And there is satellite hunter: if player completely ignores UFO (ie no interception attempt), Battleship(Large Scout in first months) (that can be changed too) comes down and tries to take out satellite (there is chance of satellite survival, if player upgraded them to stealth satellites in Foundry; in vanilla; chances of satellite (both normal and stealth) survival can be set in DGC.ini).

 

Now I flesh out, what exactly I want to do:

- determine, which types of ships take roles of scout and abductor in abduction missions depending on in-game time passed (ie, first two month small scout scouting for abductions and large scouts doing them; next it will large scout and abductor; after upgrade to abductor and supply barge);

- likewise, for terror mission (first four month supply barge is doing scouting, after that battleship);

- same for satellite hunter ship, but with optional behavior change: vanilla mechanic on its behavior always seemed a bit counterintuitive for me - why would they send satellite hunter in case if you not interfered (regardless of reasons) with their business? much more logical would be something like this: you not bother their ships, they not bother your satellites - you take down their mission UFO and they send out in retaliation satellite hunter; this way it is up to player to escalate conflict or not, just one more of those decisions to make (obviously there should not be retaliation on hunter takedown, so taking out satellite hunter remains a valid option).

 

And one more thing: I also would like to make detection of mission UFOs and retaliation chance based (to add good bit of randomness).

 

This way a powerful and flexible tool to handle balance in strategic part of game would be created (would help greatly in resolving any strategic balance concerns in any mod with conjunction of already existing tools and methods).

 

Anyway, this is all information I have gathered on this topic so far:

All ships on missions are determined in single function: XGStrategyAI BuildObjectives (not to confuse with BuildObjective), and this function looks like:

function BuildObjectives()
{
BuildObjective(1, false);
AddUFOMission(1, 1, 4, 0, -1, 7);
BuildObjective(3, false);
AddUFOMission(3, 1, 4, 4, -1, 7);
BuildObjective(2, false);
AddUFOMission(2, 1, 4, 2, -1, 7);
BuildObjective(4, false);
AddUFOMission(4, 1, 5, 7, -1, 1);
BuildObjective(0, false);
AddUFOMission(0, 10, 4, 0, -1, 5);
BuildObjective(5, true);
AddUFOMission(5, 1, 5, 0, 500, 1);
AddUFOMission(5, 3, 6, 5, 0, 0);
BuildObjective(6, false);
AddUFOMission(6, 9, 7, 4, 100, 3);
AddUFOMission(6, 20, 8, 5, 0, 7);
BuildObjective(7, true);
AddUFOMission(7, 1, 9, 1, 100, 0);
AddUFOMission(7, 4, 9, 5, 0, 3);
//return;
}

However, satellite hunter is determined elsewhere (and probably don't count as "mission UFO" by game definition of it). Functions, that are responsible for satellite hunter: function ShouldHunt:

function bool ShouldHunt(XComGame.XGGameData.EShipType eUFO, XComGame.XGGameData.ECountry eTarget, XGStrategyActor.EUFOMissionResult eResult)
{
// End:0x16
if(eUFO == 9)
{
return false;
}
// End:0x2C
if(eResult != 1)
{
return false;
}
// End:0x55
if(Game().GetDifficulty() >= 3)
{
return true;
}
// End:0x6B
if(eUFO == 8 )
{
return false;
}
// End:0xBC
if((Game().GetDifficulty() <= 1) && HQ().m_arrSatellites.Length <= 2)
{
return false;
}
return true;
//return ReturnValue;
}

Also there is sort of time selection tied to difficulty level, which ship takes role of satellite hunter in function AddHuntTarget:

function AddHuntTarget(XComGame.XGGameData.ECountry eTargetCountry)
{
local XComGame.XGGameData.EShipType eHunter;
local int iMonthCutoff;

// End:0x18
if(IsCountryBeingHunted(eTargetCountry))
{
return;
}
// End:0x4D
if(Game().GetDifficulty() <= 1)
{
iMonthCutoff = 3;
}
// End:0x8E
else
{
// End:0x83
if(Game().GetDifficulty() == 2)
{
iMonthCutoff = 2;
}
// End:0x8E
else
{
iMonthCutoff = 1;
}
}
// End:0xB5
if((GetMonth()) >= iMonthCutoff)
{
eHunter = 8;
}
// End:0xC1
else
{
eHunter = 5;
}
AIAddNewObjective(4, Rand(5), HQ().m_arrSatellites[HQ().GetSatellite(eTargetCountry)].v2Loc, eTargetCountry,, eHunter);
//return;
}

 

 

And there is this one interesting function AddUFOMission (which is interesting, because it looks like this only other function, than BuildObjectives function, that can assign ship type for mission; other functions handling missions, and use BuildObjectives function to determine ships for them); also I think that this function is not used in vanilla at all; probably leftover from earlier versions/testing. What it looks like:

function AddUFOMission(XGStrategyActor.EAlienObjective eObjective, int iStartDate, XComGame.XGGameData.EShipType eUFO, XGStrategyActor.EUFOMission eMission, int iMissionRadius, int iRandomDays)
{
m_arrTObjectives[eObjective].arrStartDates.AddItem(iStartDate);
m_arrTObjectives[eObjective].arrUFOs.AddItem(eUFO);
m_arrTObjectives[eObjective].arrMissions.AddItem(eMission);
m_arrTObjectives[eObjective].arrRadii.AddItem(iMissionRadius);
// End:0xEC
if(ISCONTROLLED())
{
iRandomDays = 0;
}
m_arrTObjectives[eObjective].arrRandDays.AddItem(iRandomDays);
//return;
}

 

Also, I managed to find, that this issue was discussed earlier on this forum in thread "Storms over former XCOM members | Controlling abductions" (link to discussion page: http://forums.nexusmods.com/index.php?/topic/849122-storms-over-former-xcom-members-controlling-abductions/page-7?hl=%20armor%20%20penetration)

 

So I would be glad for anyone help on this.

Link to comment
Share on other sites

I've finally started rooting around in this area of the code, primarily while trying to find options for forcing at least one country lost so that the alien base / re-taking country thing will work.

 

My initial read is that the BuildObjectives function builds template objectives, not actual real "going to happen" objectives. The Firaxis coding standard appears to have been using an initial "T" prefix to designate templates. For example, TCharacter holds the template information about each unit type.

 

Similarly, the BuildObjective and AddUFOMission (which is kind of poorly named as it doesn't really directly have anything to do with missions) both alter the XGStrategyAI.m_arrTObjectives element.

 

This is an dynamic array of elements of type TObjective :

var array<TObjective> m_arrTObjectives;

Currently it is limited to 8 template objective types. During XGStrategyAI.Init the size is set and then initialized via :

    m_arrTObjectives.Add(8);
    BuildObjectives();

The possible alien objective types are defined in the enum XGStrategyActor.EAlienObjectives:

enum EAlienObjective
{
    eObjective_Recon,
    eObjective_Scout,
    eObjective_Harvest,
    eObjective_Flyby,
    eObjective_Hunt,
    eObjective_Abduct,
    eObjective_Terrorize,
    eObjective_Infiltrate,
    eObjective_MAX
};

So, the Hunt objective template is configured via the following in BuildObjectives:

BuildObjective(4, false);  // 4 = eObjective_Hunt
AddUFOMission(4, 1, 5, 7, -1, 1); // 4 = eObjective_Hunt, 1 = startdate, 5 = eShip_UFOLargeScout, 7 = eUM_Seek (UFO Mission), -1 = radius, 7 = random days

This doesn't appear set any actual objectives or missions -- this just defines the template for when later objectives are added.

 

There are some multi-ship objectives templated, like Abduct :

    BuildObjective(5, false); // 5 = eObjective_Abduct, was true in vanilla, which caused the abduction to abort if the first mission was shot down
    AddUFOMission(5, 1, 5, 0, 500, 1); // 5 = eObjective_Abduct, 1 = startdate, 5 = eShip_UFOLargeScout, 0 = eUM_QuickScout, 500 = radius, 1 = random days
    AddUFOMission(5, 3, 6, 5, 0, 0); // 5 = eObjective_Abduct, 3 = startdate, 6 = eShip_UFOAbductor, 5 = eUM_Direct, 0 = radius, 0 = random days

So, when an abduct mission is created (using this template), it spawns a large scout on day 1 + rand(1) that does a quick scout of the area, then on day 3 spawns an abductor that flies directly to the target site.

 

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

 

The actual current objectives (as opposed to the objective templates) appear to be stored in XGStrategyAI.m_arrObjectives, defined with :

array<XGAlienObjective> m_arrObjectives

While the TAlienObjective was a structure, XGAlienObjective is a class.

 

Current objectives appear to be defined via XGStrategyAI.AIAddNewObjective :

function XGAlienObjective AIAddNewObjective(XGStrategyActor.EAlienObjective eObjective, int iStartDate, Vector2D v2Target, int iCountry, optional int iCity, optional XComGame.XGGameData.EShipType eUFO)
{
    local XGAlienObjective kObjective;

    iCity = -1;
    eUFO = 0;
    kObjective = Spawn(class'XGAlienObjective');
    kObjective.Init(m_arrTObjectives[eObjective], iStartDate, v2Target, iCountry, iCity, eUFO);
    m_arrObjectives.AddItem(kObjective);
    return kObjective;
}

This spawns a new copy of XGAlienObjective, initializes it (using the Template objective from m_arrTObjectives), and adds the new objective to the list of current objectives stored in m_arrObjectives.

 

Objectives aren't the same as missions, though. The game clock triggers events from the GameTick function, which calls XGStrategyAI.Update every 30 minutes of in-game time (comparatively, the XCOM HQ update is called every 1 hour of in-game time).

 

This XGStrategyAI.Update calls AIUpdateMissions() and UpdateObjectives()

 

The principle piece I'm interested in new is UpdateObjectives. It updates all current objectives via the loop :

    foreach m_arrObjectives(kObjective,)
    {
        if(!kObjective.m_bComplete)
        {
            kObjective.Update(iNumUnits);
        }        
    }    

The function XGAlienObjective.Update updates the timer, and then launches missions when the timer so indicates:

    m_iTimer += iNumUnits;
    if(((iNumUnits > 1) && m_iNextMissionTimer != -1) && m_iTimer >= m_iNextMissionTimer)
    {
        m_iTimer = m_iNextMissionTimer - 1;
    }
    if(((m_iNextMissionTimer != -1) && m_iTimer >= m_iNextMissionTimer) && !GEOSCAPE().IsBusy())
    {
        LaunchNextMission();
    }

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

 

Current objectives are created with the XGStrategyAI.AIAddNewObjective function. This function has an optional parameter eUFO which can specify a UFO type.

 

This UFO type defaults to 0 if not specified. (0 = eShip_None)

 

The UFO type is passed to the XGAlienObjective.Init function via :

    kObjective.Init(m_arrTObjectives[eObjective], iStartDate, v2Target, iCountry, iCity, eUFO);

The XGAlienObjective.Init function starts off by setting the current objective to use the supplied template objective via :

    m_kTObjective = kObj;

It then uses the eUFO parameter in the following loop:

    if(eShip != 0)
    {
        iShip = 0;
        J0xE4:
        // End:0x14D [Loop If]
        if(iShip < m_kTObjective.arrUFOs.Length)
        {
            m_kTObjective.arrUFOs[iShip] = eShip;
            ++ iShip;
            // [Loop Continue]
            goto J0xE4;
        }
    }

If it is defined, then all UFOs in the new current objective are over-ridden and set to the supplied value. This would prevent this method from being used to upgrade multi-ship objectives with two upgraded, but different ships.

 

As Tycus pointed out, the XGStrategyAI.AddHuntTarget creates a new objective via AIAddObjective. The UFO passed through this call is chosen via a combination of difficulty level and month, and is either 5 = eShip_UFOLargeScout or 8 = eShip_UFOBattle. Since the hunt template objective only contains 1 ship, that ship is overridden by the shiptype defined in the AIAddObjective call.

 

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

 

My primary goal in this area right now is to try and spawn some initial 7 = eObjective_Infiltrate objectives. The objective templates are defined via :

    BuildObjective(7, true); // eObjective_Infiltrate, cancel if first UFO shot down
    AddUFOMission(7, 1, 9, 1, 100, 0); // eObjective_Infiltrate, 1 = start day, 9 = eShip_UFOEthereal, 1 = eUM_LongScout, radius = 100, rand days = 0
    AddUFOMission(7, 4, 9, 5, 0, 3); // eObjective_Infiltrate, 4 = start day, 9 = eShip_UFOEthereal, 5 = eUM_Direct, radius = 0, rand days = 3

The reason for my interest is the result if the mission is succcessful. In XGStrategyAI.OnObjectiveEnded :

            case 7:
                SignPact(kLastUFO, kObj.m_iCountryTarget);
                break;

If the infiltrate mission is successful then SignPact executes, which causes the targeted country to leave XCOM. This appears to be left-over functionality that was removed.

 

I'm not sure if the ethereal UFO would show up if it appeared in a country with a satellite (before the Hyperwave Decoder is built, that is), but I'm going to find out :smile:

 

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

 

As far as tweaking the multi-ship objectives (Abduction missions and terror missions), I really only see 2 options initially.

 

First would be to add the time-based code to BuildObjectives. BuildObjectives is called in XGStrategyAI.InitNewGame as well as XGStrategyAI.EndOfMonth, so it is refreshed every month.

 

The other alternative would be to modify the XGAlienObjective.Init function to not simply overwrite both ships with the passed ship info, but to assign different ships based upon the supplied value.

Link to comment
Share on other sites

  • 5 months later...

This is a bit of a summarize of the info on the BuildObjectives function with XGStrategyActor.EAlienObjectives.

	m_arrObjectiveNames[eObjective_Scout]="Scout Target"
	BuildObjective(1, false);
	AddUFOMission(1, 1, SmallScout, QuickScout, -1, 7 rand days);

	m_arrObjectiveNames[eObjective_Flyby]="Target Reconnaissance"
	BuildObjective(3, false);
	AddUFOMission(3, start day 1, SmallScout, 4?, -1, 7 rand days);

	m_arrObjectiveNames[eObjective_Harvest]="Harvest Live Specimens"
	BuildObjective(2, false);
	AddUFOMission(2, start day 1, SmallScout, 2, -1, 7 rand days);

	m_arrObjectiveNames[eObjective_Hunt]="Destroy Satellite"
	BuildObjective(4, false);
	AddUFOMission(4, start day 1, LargeScout, Seek, -1, 1 rand days);

	m_arrObjectiveNames[eObjective_Recon]="Reconaissance"
	BuildObjective(0, false);
	AddUFOMission(0, start day 10, SmallScout, QuickScout, -1, 5 rand days);

	m_arrObjectiveNames[eObjective_Abduct]="Abduct Specimens"
	BuildObjective(5, true);
	AddUFOMission(5, start day 1, LargeScout, QuickScout, 500, 1 rand days);
	AddUFOMission(5, start day 3, Abductor, Direct, 0, 0);

	m_arrObjectiveNames[eObjective_Terrorize]="Terrorize Populace"
	BuildObjective(6, false);
	AddUFOMission(6, start day 9, 8, 4, 100, 3 rand days);
	AddUFOMission(6, start day 20, 8, Direct, 0, 7 rand days);

	m_arrObjectiveNames[eObjective_Infiltrate]="Final Preparations"
	BuildObjective(7, true);
	AddUFOMission(7, start day 1, 9, LongScout, 100, 0 rand days);
	AddUFOMission(7, start day 4, 9, Direct, 0, 3 rand days);

One item missing is the Supply Barge, which isn't listed above. The Large Scout is also replaced with the Battleship through AddHuntTarget function.

Edited by Hobbes77
Link to comment
Share on other sites

  • Recently Browsing   0 members

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