LUA Code Help - Generating Great Persons

Gleb Bazov

Warlord
Joined
Feb 13, 2017
Messages
176
Following on a recent thread about a creating a civilization trait granting a great persons upon construction of a district (after a specific technology is researched), I tried and ultimately gave up coding this in XML. The Modifier available in XML (MODIFIER_SINGLE_CITY_GRANT_GREAT_PERSON_CLASS_IN_CITY) is tied to COLLECTION_OWNER and there just isn't a way that I can see to tie all the necessary conditions (Civilization Type, District, Technology, etc.). This is particularly annoying because I can see that there is a Requirement (REQUIREMENT_CIV_TYPE_MATCHES) that would tie all this together by making it a District Modifier instead of a Trait, but this requirement is not used anywhere in the base game, and I can't figure out a way to use it with CivilizationTypes (I suspect it refers to the Major/Minor/Barbarian distinction instead). Anyways, long story short, I went the other way and coded the above process in LUA. It works and works well, apart from the following:

(1) I can't find a way to generate GreatPersonIndividuals. All I get are generic generals/admirals/engineers/etc., obviously without any abilities. I suppose I could mod generic great person units to have some useful abilities, but that wasn't the task I intended to accomplish.

Using GameInfo.GreatPersonClasses["GREAT_PERSON_CLASS_GENERAL"].Index for unitType in Create (unitType, unitX, unitY) generates a settler instead (not unexpectedly, since the index value is 0 for both).

(2) This one is silly. Instead of getting one unit, I get two of the same kind. For some reason, the code loops on itself. Using the print function, I get two sets of outputs when the district is created.

Here is the code. I would appreciate any assistance you may provide! Thank you in advance.

Code:
--====================================================================================================================
--    Function Generating a Great Person Upon Certain Conditions Being Satisfied
--====================================================================================================================

local sRequiredCiv = "CIVILIZATION_EGYPT"
local iUnitType = "UNIT_GREAT_GENERAL"
local iRequiredDistrict = "DISTRICT_ENCAMPMENT"
local pRequiredTech = "TECH_MINING"

function OnEncampmentCompleted( playerID, cityID, orderType, unitType, canceled, typeModifier )
    if orderType == 2 and GameInfo.Districts[unitType].DistrictType == iRequiredDistrict then
        print ("District Conditions Satisfied")
        local pPlayer = Players[ playerID ]
        local uCity = pPlayer:GetCities():FindID(cityID)
        local pPlayerConfig = PlayerConfigurations[ playerID ]
        local pPlayerCivName = pPlayerConfig:GetCivilizationTypeName()
        if pPlayerCivName == sRequiredCiv then
            print ("Civilization Type Conditions Satisfied")
            local pPlayerTechs = pPlayer:GetTechs()
            if pPlayerTechs:HasTech(GameInfo.Technologies[pRequiredTech].Index) then
                print ("Tech Conditions Satisfied")
                local pUnitType = GameInfo.Units[iUnitType].Index
                pPlayer:GetUnits():Create(pUnitType, uCity:GetX(), uCity:GetY())
            end
        end
    end
end
Events.CityProductionCompleted.Add(OnEncampmentCompleted);

Going a different way, is there a function to trigger a GameEffect in LUA? E.g. to trigger the effect underlying MODIFIER_SINGLE_CITY_GRANT_GREAT_PERSON_CLASS_IN_CITY through Lua?

EDIT: For completeness, I also tried the UnitManager.InitUnit(pPlayer, "UNIT_GREAT_GENERAL", uCity:GetX(), uCity:GetY()) method, and it works, but it suffers from the same two problems as the Create approach.
 
Last edited:
UPDATE: While I was typing the above, it occurred to me that I could adapt LeeS' dummy buildings code to create a dummy building instead of the unit and tie the MODIFIER_SINGLE_CITY_GRANT_GREAT_PERSON_CLASS_IN_CITY to that building. This seems to be a decent workaround. However, I would love to find the solution to the original approach.

Thanks!
 
I simply don't think there's a function available for this. Even the FireTuner can only create 'generic' GPs. Also, I think it could mess up with the new system of recruiting GPs. They are queued once the game starts, so there would be gaps. It's unnecessary programming from devs, so they didn't do it.
 
Thank you for looking into this! Since posting this, I have successfully adapted the pCity:GetBuildQueue():CreateIncompleteBuilding() method from LeeS' Dummy Buildings mod for my purposes and the basic process is now in place, combining code mostly in LUA with a dummy GP generation trigger building in XML through a modifier. It works flawlessly, though it would have been more elegant to keep it all in one places instead. Since the process now ends on the XML side, there is no issue with duplication of units.

As for LUA, I tried a number of approaches, attempting to guess hidden functions, e.g. by mirroring:

Game:GetReligion():FoundPantheon()

with

Game:GetGreatPeople():RecruitGreatPerson or PatronizeGreatPerson

but, even if either of this functions exists I have no idea what the arguments are, and I tried, but failed to guess them.

It would seem, if there were a way to access the GreatPersonIndividuals table for the purpose of creating GPs, it would be possible also to (a) query whether the GP has already been generated and find the next one available of the given type (by checking against the game's current set of units until a free GP is found), and (b) to ensure that the Era (to prevent skipping ahead to modern GPs) , District (e.g. Harbour for GP Admirals), etc. requirements are satisfied before granting the GP.

However, you are probably right. It is very possible that no LUA function has been programmed for GP creation, and the entire mechanism is hardcoded under the hood.

If anyone is interested in the final code, here is the LUA part:

Code:
--====================================================================================================================
--    Function Generating a Great Person Upon Certain Conditions Being Satisfied
--====================================================================================================================

local sRequiredCiv = "CIVILIZATION_EGYPT"
local iUnitType = "UNIT_GREAT_GENERAL"
local iRequiredDistrict = "DISTRICT_ENCAMPMENT"
local pRequiredTech = "TECH_MINING"

function PlaceBuildingInCityCenter(pCity, iBuilding)
    local iCityPlotIndex = Map.GetPlot(pCity:GetX(), pCity:GetY()):GetIndex()
    if not pCity:GetBuildings():HasBuilding(iBuilding) then
        print ("PlaceBuildingInCity: Attempting to ADD Building ID#" .. iBuilding .. " : " .. GameInfo.Buildings[iBuilding].BuildingType .. " to the city of " .. Locale.Lookup(pCity:GetName()))
        pCity:GetBuildQueue():CreateIncompleteBuilding(iBuilding, iCityPlotIndex, 100);
    else
        print ("PlaceBuildingInCity: Building ID#" .. iBuilding .. " : " .. GameInfo.Buildings[iBuilding].BuildingType .. " already exists in the city of " .. Locale.Lookup(pCity:GetName()))
        if pCity:GetBuildings():IsPillaged(iBuilding) then
            print ("PlaceBuildingInCity: Building ID#" .. iBuilding .. " : " .. GameInfo.Buildings[iBuilding].BuildingType .. " was pillaged: attempting to remove and replace")
            pCity:GetBuildings():RemoveBuilding(iBuilding);
            pCity:GetBuildQueue():CreateIncompleteBuilding(iBuilding, iCityPlotIndex, 100);
        else
            print ("PlaceBuildingInCity: Building ID#" .. iBuilding .. " : " .. GameInfo.Buildings[iBuilding].BuildingType .. " was not pillaged: conditions are correct and no further action is required")
        end
    end
end

function RemoveBuildingFromCityCenter(pCity, iBuilding)
    local iCityPlotIndex = Map.GetPlot(pCity:GetX(), pCity:GetY()):GetIndex()
    if pCity:GetBuildings():HasBuilding(iBuilding) then
        print ("RemoveBuildingFromCityCenter: Attempting to REMOVE Building ID#" .. iBuilding .. " : " .. GameInfo.Buildings[iBuilding].BuildingType .. " from the city of " .. Locale.Lookup(pCity:GetName()))
        pCity:GetBuildings():RemoveBuilding(iBuilding);
        if pCity:GetBuildings():HasBuilding(iBuilding) then
            print ("RemoveBuildingFromCityCenter: FAILED to REMOVE Building ID#" .. iBuilding .. " : " .. GameInfo.Buildings[iBuilding].BuildingType .. " from the city of " .. Locale.Lookup(pCity:GetName()))
        else
            print ("RemoveBuildingFromCityCenter: SUCCESS in removing Building ID#" .. iBuilding .. " : " .. GameInfo.Buildings[iBuilding].BuildingType .. " from the city of " .. Locale.Lookup(pCity:GetName()))
        end
    end
end

function OnEncampmentCompleted( playerID, cityID, orderType, unitType, canceled, typeModifier )
    if orderType == 2 and GameInfo.Districts[unitType].DistrictType == iRequiredDistrict then
        print ("District Conditions Satisfied")
        local pPlayer = Players[ playerID ]
        local uCity = pPlayer:GetCities():FindID(cityID)
        local pPlayerConfig = PlayerConfigurations[ playerID ]
        local pPlayerCivName = pPlayerConfig:GetCivilizationTypeName()
        if pPlayerCivName == sRequiredCiv then
            print ("Civilization Type Conditions Satisfied")
            local pPlayerTechs = pPlayer:GetTechs()
            if pPlayerTechs:HasTech(GameInfo.Technologies[pRequiredTech].Index) then
                print ("Tech Conditions Satisfied")
                local iBuilding = GameInfo.Buildings["BUILDING_GP_TRIGGER"].Index
                PlaceBuildingInCityCenter(uCity, iBuilding)
                print ("Trigger building placed successfully, awaiting GP generation")
                GameEvents.UnitAddedToMap.Add(function ( zPlayer, zUnit )
                    local xPlayer = Players[zPlayer]
                    local xUnit = xPlayer:GetUnits():FindID(unitID)
                    if xPlayer == pPlayer and xUnit:GetType() == GameInfo.Units[iUnitType].Index then
                        print ("Success. GP generated")
                        print ("GP generated has ID# = " .. tostring(UnitId))
                    else
                        print ("Failure. Something went wrong")
                    end
                end)
            end
        end
    end
end
Events.CityProductionCompleted.Add(OnEncampmentCompleted);

and the adjunct SQL part:

Code:
INSERT INTO Types (Type, Kind) VALUES ('BUILDING_GP_TRIGGER', 'KIND_BUILDING');
INSERT INTO Buildings (BuildingType, Name, Description, PrereqDistrict, Cost, EnabledByReligion) VALUES
('BUILDING_GP_TRIGGER', 'GP Creation Building', 'Completes the LUA process for GP generation trait', 'DISTRICT_CITY_CENTER', 1, 1);
INSERT INTO BuildingModifiers (BuildingType, ModifierId) VALUES ('BUILDING_GP_TRIGGER', 'GP_TRIGGER_MODIFIER');

INSERT INTO Modifiers (ModifierId, ModifierType, RunOnce, Permanent, OwnerRequirementSetId, SubjectRequirementSetId) VALUES
('GP_TRIGGER_MODIFIER', 'MODIFIER_SINGLE_CITY_GRANT_GREAT_PERSON_CLASS_IN_CITY', 1, 1, NULL, NULL);

INSERT INTO ModifierArguments (ModifierId, Name, Type, Value, Extra, SecondExtra) VALUES
('GP_TRIGGER_MODIFIER', 'GreatPersonClassType', 'ARGTYPE_IDENTITY', 'GREAT_PERSON_CLASS_GENERAL', NULL, NULL),
('GP_TRIGGER_MODIFIER', 'Amount', 'ARGTYPE_IDENTITY', '1', NULL, NULL);

The next step is to create a LUA and XML accessible Table incorporating the settings (Civ Type, GP Type, etc.) to make the process amenable to easy customization.

I am not giving up completely on the original idea. So, if anyone has suggestions or solutions, please let me know!

Cheers.
 
As I understand OnEncampmentCompleted is still called twice, bot you only place one dummy, checking if one already exists, right?
And just curious - do you get the next individual GP in line or a generic one?
 
Yup, I get the next individual GP with this, no more generic useless units. :)
Yeah, the OnEncampmentCompleted does get called twice ... :| But the code only places one dummy after all the modifications.
 
Oh, and after giving it another go, I figured out a way to achieve the same as above in LUA through XML. Also using a dummy building, since there is literally no other way I could twist the code into ensuring that all the requirements are satisfied. It still bugs me that REQUIREMENT_CIV_TYPE_MATCHES has no functionality (or that I can't figure out how it works). Bummer. If only it worked, the XML code would need no dummy building and be quite elegant. Oh well. I still like the LUA implementation. It just seems more robust to me.

Here is the XML SQL code if anyone is interested:

Code:
INSERT INTO Types (Type, Kind) VALUES
('TRAIT_BUILDING_GP_GENERATOR', 'KIND_TRAIT');

INSERT INTO Traits (TraitType, Name, Description) VALUES
('TRAIT_BUILDING_GP_GENERATOR', 'Trait Generating GPs', 'Trait that enables GP generation upon certain conditions being reached');

INSERT INTO CivilizationTraits (CivilizationType, TraitType) VALUES
('CIVILIZATION_EGYPT', 'TRAIT_BUILDING_GP_GENERATOR');

INSERT INTO Types (Type, Kind) VALUES ('BUILDING_GP_GENERATOR', 'KIND_BUILDING');
INSERT INTO Buildings (BuildingType, Name, Description, PrereqTech, PrereqDistrict, Cost, EnabledByReligion, MustPurchase, TraitType) VALUES
('BUILDING_GP_GENERATOR', 'GP Generator Asset', 'Dummy building that generates a GP', 'TECH_MINING', 'DISTRICT_ENCAMPMENT', 1, 0, 1, "TRAIT_BUILDING_GP_GENERATOR");

INSERT INTO DistrictModifiers (DistrictType, ModifierId) VALUES
('DISTRICT_ENCAMPMENT', 'BUILDING_GP_GENERATOR_MODIFIER');

INSERT INTO Modifiers (ModifierId, ModifierType, RunOnce, Permanent, OwnerRequirementSetId, SubjectRequirementSetId) VALUES
('BUILDING_GP_GENERATOR_MODIFIER', 'MODIFIER_SINGLE_CITY_GRANT_BUILDING_IN_CITY', 0, 0, NULL, 'PLAYER_HAS_DESIGNATED_TECH_REQUIREMENTS');

INSERT INTO ModifierArguments (ModifierId, Name, Type, Value, Extra, SecondExtra) VALUES
('BUILDING_GP_GENERATOR_MODIFIER', 'BuildingType', 'ARGTYPE_IDENTITY', 'BUILDING_GP_GENERATOR', NULL, NULL);

INSERT INTO RequirementSets (RequirementSetId, RequirementSetType) VALUES
('PLAYER_HAS_DESIGNATED_TECH_REQUIREMENTS', 'REQUIREMENTSET_TEST_ALL');

INSERT INTO RequirementSetRequirements (RequirementSetId, RequirementId) VALUES
('PLAYER_HAS_DESIGNATED_TECH_REQUIREMENTS', 'REQUIRES_DESIGNATED_TECHNOLOGY');

INSERT INTO Requirements (RequirementId, RequirementType) VALUES
('REQUIRES_DESIGNATED_TECHNOLOGY', 'REQUIREMENT_PLAYER_HAS_TECHNOLOGY');

INSERT INTO RequirementArguments (RequirementId, Name, Value) VALUES
('REQUIRES_DESIGNATED_TECHNOLOGY', 'TechnologyType', 'TECH_MINING');

INSERT INTO BuildingModifiers (BuildingType, ModifierId) VALUES
('BUILDING_GP_GENERATOR', 'GP_GENERATOR_MODIFIER');

INSERT INTO Modifiers (ModifierId, ModifierType, RunOnce, Permanent, OwnerRequirementSetId, SubjectRequirementSetId) VALUES
('GP_GENERATOR_MODIFIER', 'MODIFIER_SINGLE_CITY_GRANT_GREAT_PERSON_CLASS_IN_CITY', 0, 1, NULL, NULL);

INSERT INTO ModifierArguments (ModifierId, Name, Type, Value, Extra, SecondExtra) VALUES
('GP_GENERATOR_MODIFIER', 'GreatPersonClassType', 'ARGTYPE_IDENTITY', 'GREAT_PERSON_CLASS_GENERAL', NULL, NULL),
('GP_GENERATOR_MODIFIER', 'Amount', 'ARGTYPE_IDENTITY', '1', NULL, NULL);
 
ADDITION: There is a further step required with the XML implementation, which I intended to put in, but forgot. If the code is left as is, the dummy building will be generated in the district previously built upon the player reaching the required technology. To avoid this, an dummy anti-building must be generated if the district is built prior to completing the research of the required technology. This anti-building must be made MutuallyExclusive with the active dummy building. As follows:

PART II

Code:
INSERT INTO Types (Type, Kind) VALUES ('BUILDING_ANTI_GP_GENERATOR', 'KIND_BUILDING');
INSERT INTO Buildings (BuildingType, Name, Description, PrereqDistrict, Cost, EnabledByReligion, MustPurchase) VALUES
('BUILDING_ANTI_GP_GENERATOR', 'GP Generation Limiter', 'Precludes generation of GPs for districts built prior to completing required technology', 'DISTRICT_ENCAMPMENT', 1, 0, 1);

INSERT INTO MutuallyExclusiveBuildings (Building, MutuallyExclusiveBuilding) VALUES
('BUILDING_GP_GENERATOR', 'BUILDING_ANTI_GP_GENERATOR'),
('BUILDING_ANTI_GP_GENERATOR', 'BUILDING_GP_GENERATOR');

INSERT INTO DistrictModifiers (DistrictType, ModifierId) VALUES
('DISTRICT_ENCAMPMENT', 'BUILDING_ANTI_GP_GENERATOR_MODIFIER');

INSERT INTO Modifiers (ModifierId, ModifierType, RunOnce, Permanent, OwnerRequirementSetId, SubjectRequirementSetId) VALUES
('BUILDING_ANTI_GP_GENERATOR_MODIFIER', 'MODIFIER_SINGLE_CITY_GRANT_BUILDING_IN_CITY', 0, 1, NULL, 'PLAYER_LACKS_DESIGNATED_TECH_REQUIREMENTS');

INSERT INTO ModifierArguments (ModifierId, Name, Type, Value, Extra, SecondExtra) VALUES
('BUILDING_ANTI_GP_GENERATOR_MODIFIER', 'BuildingType', 'ARGTYPE_IDENTITY', 'BUILDING_ANTI_GP_GENERATOR', NULL, NULL);

INSERT INTO RequirementSets (RequirementSetId, RequirementSetType) VALUES
('PLAYER_LACKS_DESIGNATED_TECH_REQUIREMENTS', 'REQUIREMENTSET_TEST_ALL');

INSERT INTO RequirementSetRequirements (RequirementSetId, RequirementId) VALUES
('PLAYER_LACKS_DESIGNATED_TECH_REQUIREMENTS', 'REQUIRES_LACK_OF_DESIGNATED_TECHNOLOGY');

INSERT INTO Requirements (RequirementId, RequirementType, Likeliness, Inverse, Triggered) VALUES
('REQUIRES_LACK_OF_DESIGNATED_TECHNOLOGY', 'REQUIREMENT_PLAYER_HAS_TECHNOLOGY', 0, 1, 0);

INSERT INTO RequirementArguments (RequirementId, Name, Value) VALUES
('REQUIRES_LACK_OF_DESIGNATED_TECHNOLOGY', 'TechnologyType', 'TECH_BRONZE_WORKING');

OK, now the code seems to be complete.
 
I've been trying to do something similar, although I wanted to create a specific great person when a player is the first to do something (e.g. create a building, discover a tech, etc.). I was trying to work with the following code lifted from GreatPeoplePopup.lua:
Code:
local kParameters:table = {};
kParameters[PlayerOperations.PARAM_GREAT_PERSON_INDIVIDUAL_TYPE] = individualID;
UI.RequestPlayerOperation(Game.GetLocalPlayer(), PlayerOperations.RECRUIT_GREAT_PERSON, kParameters);
Close();
This errors unless it is in the UI context (guess that should've been obvious to me from the use of 'UI'!), but even after sorting that out, I still couldn't get it to do anything. I came to the conclusion that this is probably tied into the timeline data and most likely checks that the GP Individual is currently available, and that the player has enough GP points to recruit them. Which is a shame, because I had this running with both specific GPs and just the next GP available, with no errors.

So what I've ended up with is a bit of a work around, but it's currently good enough for me.

I created a custom table to hold GreatPersonTriggers. I then check these in a LUA script based on handling various events (currently CityProductionCompleted, CivicCompleted, ResearchCompleted, NaturalWonderRevealed). If the trigger settings match, I then top-up the player's GP points (% amount is configurable in the GreatPersonTriggers table), which in turn triggers the notification and allows the player to recruit the GP the normal way. I then track which triggers have been completed so that they don't happen again - this goes in to the save file so that it persists between loads.
Code:
-- get current gp points for classID (number)
player:GetGreatPeoplePoints():GetPointsTotal(classID)

-- set gp points for classID (number)
player:GetGreatPeoplePoints():SetPointsTotal(classID, amount);
Triggers are configured using KIND and TYPE, and at the moment all the code does is make sure these match. E.g.
Code:
<Row GreatPersonTriggerType="GREATPERSON_TRIGGER_RADIO_GRANTS_MUSICIAN" PrereqKind="KIND_TECH" PrereqType="TECH_RADIO" GreatPersonClassType="GREAT_PERSON_CLASS_MUSICIAN" Boost="100" RunOnce="true" />
The code is still a first cut and needs some refining, but you can see it in action (and view the code), in my 'More Great Musicians' and 'More Great Artists' mods ... in the assets/greatpeopletriggers folder.

Next steps are:
  • Wire-up more events
  • Conditions. I want to implement something like Modifier Requirements, so that you could attach multiple conditions to a trigger(e.g. must have n Ampitheaters, and the Opera/Ballet civic)
  • Notifications (When the GP points boost is less than 100%)
 
EDIT: OOPS, NEVERMIND... :) I misread your post. Thank you for the code, it's exactly what I need! :)))

@StormingRomans,

You'll be happy to know :) that the most recent (Spring) patch creates a new LUA function for Great People that would appear to do exactly what you want it to do (unfortunately, it does not do what I want it to do, it's too limited). Here it is:

Grant Great Person function

Game.GetGreatPeople():GrantPerson(individual, class, era, cost, 0, false)

local individual = GameInfo.GreatPersonIndividuals["GREAT_PERSON_INDIVIDUAL_ALEXANDER"].Hash;
local class = GameInfo.GreatPersonClasses["GREAT_PERSON_CLASS_GENERAL"].Hash;
local era = GameInfo.Eras["ERA_CLASSICAL"].Hash;
local cost = 0;

Here is an implementation example I coded (it does not do what I want it to do, but it works within the limitations of the function as intended):

Code:
function OnEncampmentCompleted( playerID, cityID, orderType, unitType, canceled, typeModifier )
    if orderType == 2 and GameInfo.Districts[unitType].DistrictType == iRequiredDistrict then
        print (unitType)
        print ("District Conditions Satisfied")
        local pPlayer = Players[ playerID ]
        local pCity = pPlayer:GetCities():FindID(cityID)
        local pPlayerConfig = PlayerConfigurations[ playerID ]
        local sPlayerCivName = pPlayerConfig:GetCivilizationTypeName()
        if sPlayerCivName == sRequiredCiv then
            print ("Civilization Type Conditions Satisfied")
            local tPlayerTechs = pPlayer:GetTechs()
            if tPlayerTechs:HasTech(GameInfo.Technologies[sRequiredTech].Index) then
                print ("Tech Conditions Satisfied")
                local individual = GameInfo.GreatPersonIndividuals["GREAT_PERSON_INDIVIDUAL_BOUDICA"].Hash;
                local class = GameInfo.GreatPersonClasses["GREAT_PERSON_CLASS_GENERAL"].Hash;
                local era = GameInfo.Eras["ERA_CLASSICAL"].Hash;
                local cost = 0;
                Game.GetGreatPeople():GrantPerson(individual, class, era, cost, 0, false);
            end
        end
    end
end
Events.CityProductionCompleted.Add( OnEncampmentCompleted );
 
Last edited:
My pleasure! Glad the function is of some use :). The 'false' parameter is a boolean of unknown purpose and origin. There are plenty of them strewn about in Firaxis' LUA implementation, and, generally, I have no idea what they do (and for many of them, no one does). Sometimes they refer to things like "is a resource visible to player?" or some other kind of visibility. In this case, I don't know... maybe "has this great person already been picked?" I would experiment with it and see what it does if you are patient enough (unlike me :)).

Cheers!
 
I haven't given that specific question ("has this great person already been picked?") any thought since I found the way to implement the GP generation method in XML and left the development of the LUA code I had in abeyance. But, I'll think about it, and, if anything comes to mind, I'll certainly let you know! Cheers.
 
It looks like for the parameters for GrantPerson():

Parameter 5 = Player ID
Parameter 6 = Movement remaining this turn ( true = 0, false = max for GP unit (i.e. 4) )

If you set #6 to false you get 'Unit Command' alerts for some reason ... at least I do :confused:
 
The following method also works in the in-script context:

Game.GetGreatPeople():CreatePerson( iPlayerID, sGPName, iPlotX, iPlotY )

-- where sGPName = GreatPersonIndividuals.GreatPersonIndividualType string, as in: "GREAT_PERSON_INDIVIDUAL_EL_GRECO"

However, I still can find no way to get the next GP in line (for a given GP type) using LUA alone. If it were possible to persist data across saves in the In-Script context, I suppose I could get a table going and record every GP created during the game, and then verify the next GP available using that table. However, even then, the problem is that the available GPs of any given type vary from game to game and mapsize to mapsize, so that's only half of the solution. We'd need to identify the specific pool of GPs available in a specific game and iterate over that, but I don't know of a way to do that. Otherwise, using all of the methods above:

- Game.GetGreatPeople():GrantPerson(individual, class, era, cost, 0, false); or
- Game.GetGreatPeople():CreatePerson( iPlayerID, sGPName, iPlotX, iPlotY )

we might get a GP individual that should not be available in a given game.

Thus, the only consistent way I've found to use LUA to trigger GP creation and comply with the randomized pool of GPs available in a given game is to spawn, using LUA, an XML-based trigger, such as a dummy building, that itself spawns the next available GP. In other words, the method mixes LUA and XML to achieve the desired effect. I suppose spawning a special unit using LUA, with that unit itself spawning a GP using XML code, and then destroying that unit with LUA, would be a cleaner way of doing what I've done with dummy buildings. After all, a created/destroyed unit leaves no artifacts in-game, while traces of a dummy building are notoriously hard to get rid off completely, even if LUA is used to remove that building.

Anyways, if anyone has any suggestions (I might have misread your implementation @StormingRomans, but it sounds as if you are using the UI context, which I am not using), I am all ears! :)
 
I still can find no way to get the next GP in line (for a given GP type) using LUA alone
I'm guessing you mean the GP after the one currently displayed in the GP Screen?
If it were possible to persist data across saves in the In-Script context
Hmmm, that's what I'm doing. My main code runs as a gameplay script - I've not set the context to in-game (actually when I did, my main code failed). I have another script which runs in the UI/in-game context, that I call out to when I do the save(s). I now have another one in the UI/in-game context that checks to see if a GP has already been recruited, as the call to GetPastTimeline() errors otherwise:
Code:
function GreatPersonHasBeenRecruited(greatPersonIndividualType)
    local greatPeople    :table  = Game.GetGreatPeople();
    if greatPeople == nil then
        dprint("WARNING: Received NIL great people object.");
        return;
    end

    local timeline:table = greatPeople:GetPastTimeline();

    for i,entry in ipairs(timeline) do
        local entryType = "";
        if  GameInfo.GreatPersonIndividuals[entry.Individual] ~= nil then
            entryType = GameInfo.GreatPersonIndividuals[entry.Individual].GreatPersonIndividualType;
        end
     
        if entryType == greatPersonIndividualType then
            return true;
        end
    end

    return false;
end
Now, is this whole cross-context thing that I'm doing good practice and a good thing to do? That's a whole different question, and one that my software development background is currently wrestling with.

So far, I've seen no problems with using GrantPerson(). I've tried several scenarios:
  1. Grant the GP currently available in the GP pop-up. OK, pop-up transitions immediately to the next GP
  2. Grant the GP that 'might' be the next one (i.e. the one that came up next in test #1). OK, pop-up skips them and goes to another one
  3. Grant GP from future ERA. OK, GP never comes up.
All the GP's end up in the 'previously recruited' screen, so that's good too. In fact, the only 'issue' I've found so far is the annoying generic 'unit command' alert that comes up - I'd prefer to be able to display an alert specific to the GP, but haven't figure out a way to do that yet.

I'm actually in the process of re-writing and expanding everything to try and make it cleaner, and implement some more ideas I have, so my implementation will change quite considerably. Will be interesting to see how much real LUA I can actually use.
 
Last edited:
I must confess, I haven't looked at your code yet, as I haven't had time. :( Would you be so kind as to post the relevant sections here? I would be very much obliged! Much thanks.
 
Top Bottom