• Civilization 7 has been announced. For more info please check the forum here .

Deceptive Lua calls.

Inightshade

Chieftain
Joined
Sep 29, 2014
Messages
33
Location
Renis
Alright! Its possible I'm just doing something horribly wrong, but lets start anyways.

Shortened to ease people looking for possible answers. But will keep as a testament to my failures :p
Spoiler :

This is about the Lua Team:Calls() and the internal oops that is their C methods.


At face value, one would assume that a call as bland as :Meet() would work like this:
pPlayerOne:Meet(pPlayerTwo); -- And poof, player 1 meets player 2.

Short answer: No. Nowhere even close.

Step One: The code--
Spoiler :

//void meet(TeamTypes eTeam, bool bSuppressMessages);
int CvLuaTeam::lMeet(lua_State* L)
{
CvTeam* pkTeam = GetInstance(L);
TeamTypes eOtherTeam = (TeamTypes)lua_tointeger(L, 2);
const bool bSuppressMessages = lua_toboolean(L, 3);

pkTeam->meet(eOtherTeam, bSuppressMessages);
return 0;
}


Yep, straight out of the source. The lua call takes the caller (pPlayerOne:Meet(pPlayerTwo);
and the first variable (pPlayerOne:Meet(pPlayerTwo);
and a the second variable. (if its not there lua defaults null to false)

Then sends this off to the C meet method, which requires all three of these.


So? What gives?

Step B:
Why, oh gods why does this:
Spoiler :

function(pPlayerImported)
local pTeamImported = Teams[pPlayerImported:GetTeam()];

for id, pPlayerOther in pairs(Players) do -- Cycle all civs
-- remove non-major powers and dead players!
if (pPlayerOther ~= pPlayerImported and not pPlayerOther:IsMinorCiv() and not pPlayerOther:IsBarbarian() and pPlayerOther:IsEverAlive()) then
-- meet
local pTeamOther = Teams[pPlayerOther:GetTeam()];
pTeamImported:Meet(pTeamOther, true);
--pTeamOther:Meet(pTeamImported, true);
end
end
end


not work as expected?
Well, technically it DOES work.
But
Not as one would expect...


Here is the results:

Case: Imported is the player in sp; there are no others who match the restrictions for 'Imported'
Nothing. You don't meet anyone.

Case2: Imported is NOT the player in sp; player is also not passing 'imported' filtering.
Poof! You meet the AI who pass filtering.

Case3: exchange the two different :Meet() lines; same as Case1.
Poof! You meet the AI!

Case4: same but for case2.
Um? You meet all BUT the AI who meet the conditions to start the function?

So? It can be twisted to "work" for the human in sp.




So what gives? Well lets take a look at the AI's PoV.

Case1: Nothing.
Case2: You meet ONLY the human in sp
Case3: Each AI meets ONLY the human in sp.
Case4: ? The AI who passes meets no one, the AI who fail meet ONLY the Human.



Strange...
But Multiplayer is even stranger. No matter what, the second player will NEVER meet anyone. Period.


So lets look at the C code... Maybe it'll tell us what's going on...
Spoiler :

void CvTeam::meet(TeamTypes eTeam, bool bSuppressMessages)
{
if(!isHasMet(eTeam))
{
makeHasMet(eTeam, bSuppressMessages);
GET_TEAM(eTeam).makeHasMet(GetID(), bSuppressMessages);

ICvEngineScriptSystem1* pkScriptSystem = gDLL->GetScriptSystem();
if(pkScriptSystem)
{
CvLuaArgsHandle args(2);
args->Push(eTeam);
args->Push(GetID());

bool bResult;
LuaSupport::CallHook(pkScriptSystem, "TeamMeet", args.get(), bResult);
}
}
}


This is the first piece. Where the Lua goes with that pkTeam->meet.

So my MsVis can't discern where pkTeam->meet is suppose to go, it gets confused between CvTeam.cpp and CvTeam.h (though .h only defines) but looking at this one doesn't see any inherent flaws.

We are executed via a pkTeam, importing eTeam and a bool. A basic sterilization check at the start, (something else that is broken, btw) then make them meet. The rest sets the hook for the event.
So? Maybe makeHasMet?

Spoiler :

void CvTeam::makeHasMet(TeamTypes eIndex, bool bSuppressMessages)
{
int iI;

CvAssertMsg(eIndex >= 0, "eIndex is expected to be non-negative (invalid Index)");
CvAssertMsg(eIndex < MAX_TEAMS, "eIndex is expected to be within maximum bounds (invalid Index)");

if(!isHasMet(eIndex))
{
m_abHasMet[eIndex] = true;

SetTurnTeamMet(eIndex, GC.getGame().getGameTurn());

updateTechShare();

//... Removed for brevity

// Report event
gDLL->GameplayMetTeam(GetID(), eIndex);
}
}


Okay, again, nothing wrong? we check again if they have met (redundant?) and perform the expected actions. The cut is just related to ALWAYS_WAR.

So, theoretically...

Shouldn't this: pPlayerOne:Meet();
And this: pPlayerOne:Meet(Firaxis Why!);
Return different results? (or throw errors?) But they don't...
Actually...
They perform the EXACT same as: pPlayerOne:Meet(pPlayerTwo, true);


So?
What gives? (EDIT: checked vs a unmodded game (just the short lua code) and same results :/ )
(sorry you'll have to either figure out your self and tell me or wait until I find it :p)
(but mayhaps someone has some insight to this matter.)
 
Though I only just thought about this: I need to check against a unmodified DLL; if at the least for security reasons. I can't think of anything I might have muddled with that would have an effect like this...

Note: Though I use the :Meet() call in the example, I have tested this with anything from :DeclareWar() to :GetID() it's LuaTeam that seems broken, only EVER using playerSlot 0 as the main variable, no matter who's involved. (And no effects when PlayerZero is dead D: ).

Strange note: using :IsHasMet() Always return true for playerZero:IsHasMet(); ALLWAYS. :o
It's near random about returning true or false (null?) for others calling it.

Full makeHasMet()
Spoiler :

void CvTeam::makeHasMet(TeamTypes eIndex, bool bSuppressMessages)
{
int iI;

CvAssertMsg(eIndex >= 0, "eIndex is expected to be non-negative (invalid Index)");
CvAssertMsg(eIndex < MAX_TEAMS, "eIndex is expected to be within maximum bounds (invalid Index)");

if(!isHasMet(eIndex))
{
m_abHasMet[eIndex] = true;

SetTurnTeamMet(eIndex, GC.getGame().getGameTurn());

updateTechShare();

if(GC.getGame().isOption(GAMEOPTION_ALWAYS_WAR))
{
if(isHuman())
{
if(GetID() != eIndex)
{
declareWar(eIndex);
}
}
}

int iMyPlayersLoop;
PlayerTypes eMyPlayer;

int iTheirPlayersLoop;
PlayerTypes eTheirPlayer;

int iThirdPlayersLoop;
PlayerTypes eThirdPlayer;

// First Contact in Diplo AI (Civ 5)
for(iMyPlayersLoop = 0; iMyPlayersLoop < MAX_CIV_PLAYERS; iMyPlayersLoop++)
{
eMyPlayer = (PlayerTypes) iMyPlayersLoop;

if(GET_PLAYER(eMyPlayer).isAlive())
{
if(GET_PLAYER(eMyPlayer).getTeam() == GetID())
{
// Now loop through players on Their team
for(iTheirPlayersLoop = 0; iTheirPlayersLoop < MAX_CIV_PLAYERS; iTheirPlayersLoop++)
{
eTheirPlayer = (PlayerTypes) iTheirPlayersLoop;

// Don't calculate proximity to oneself!
if(eMyPlayer != eTheirPlayer)
{
if(GET_PLAYER(eTheirPlayer).isAlive())
{
if(GET_PLAYER(eTheirPlayer).getTeam() == eIndex)
{
// Begin contact stuff here

// Update Proximity between players
GET_PLAYER(eMyPlayer).DoUpdateProximityToPlayer(eTheirPlayer);
GET_PLAYER(eTheirPlayer).DoUpdateProximityToPlayer(eMyPlayer);

// First contact Diplo changes (no Minors)
if(!isMinorCiv())
{
GET_PLAYER(eMyPlayer).GetDiplomacyAI()->DoFirstContact(eTheirPlayer);
}

// THIRD party loop - let everyone else know that someone met someone!
for(iThirdPlayersLoop = 0; iThirdPlayersLoop < MAX_CIV_PLAYERS; iThirdPlayersLoop++)
{
eThirdPlayer = (PlayerTypes) iThirdPlayersLoop;

if(GET_PLAYER(eThirdPlayer).isAlive())
{
// Don't notify diplo AI if we're the one meeting or the one being met
if(eThirdPlayer != eMyPlayer && eThirdPlayer != eTheirPlayer)
{
GET_PLAYER(eThirdPlayer).GetDiplomacyAI()->DoPlayerMetSomeone(eMyPlayer, eTheirPlayer);
}
}
}
}
}
}
}
}
}
}

if(GET_TEAM(eIndex).isHuman())
{
for(iI = 0; iI < MAX_PLAYERS; iI++)
{
if(GET_PLAYER((PlayerTypes)iI).isAlive())
{
if(GET_PLAYER((PlayerTypes)iI).getTeam() == GetID())
{
if(!(GET_PLAYER((PlayerTypes)iI).isHuman()))
{
GET_PLAYER((PlayerTypes)iI).clearResearchQueue();
//GET_PLAYER((PlayerTypes)iI).AI_makeProductionDirty();
}
}
}
}
}

if((GetID() == GC.getGame().getActiveTeam()) || (eIndex == GC.getGame().getActiveTeam()))
{
DLLUI->setDirty(Score_DIRTY_BIT, true);
}

if(GET_TEAM(eIndex).isMinorCiv())
{
int iCapitalX = -1;
int iCapitalY = -1;
int iCapitalID = -1;

// Minor reveals his capital to the player so that he can click on the City to contact
CvCity* pCap = GET_PLAYER(GET_TEAM(eIndex).getLeaderID()).getCapitalCity();
if(pCap)
{
iCapitalX = pCap->getX();
iCapitalY = pCap->getY();
iCapitalID = pCap->GetID();
CvPlot* pCapPlot = pCap->plot();
if(pCapPlot)
{
pCapPlot->setRevealed(GetID(), true);
GC.getMap().updateDeferredFog();
}
}

// First contact with major stuff
if(!isMinorCiv())
{
GET_PLAYER(GET_TEAM(eIndex).getLeaderID()).GetMinorCivAI()->DoFirstContactWithMajor(GetID(), /*bSuppressMessages*/ isAtWar(eIndex));
}

if(!isAtWar(eIndex))
{
// Notify the Team that they met someone
if(!bSuppressMessages)
{
CvString strBuffer = GetLocalizedText("TXT_KEY_NOTIFICATION_MET_MINOR_CIV", GET_PLAYER(GET_TEAM(eIndex).getLeaderID()).getNameKey());
CvString strSummary = GetLocalizedText("TXT_KEY_NOTIFICATION_SUMMARY_MET_MINOR_CIV", GET_PLAYER(GET_TEAM(eIndex).getLeaderID()).getNameKey());

AddNotification(NOTIFICATION_MET_MINOR, strBuffer, strSummary, iCapitalX, iCapitalY, iCapitalID);
}
}
}

// Report event
gDLL->GameplayMetTeam(GetID(), eIndex);
}
}
 
Code:
local pTeamOther = Teams[pPlayerOther:GetTeam()];
pTeamImported:Meet(pTeamOther, true);

is wrong.

the signature is pTeam:Meet(int, bool) not pTeam:Meet(&team, bool), so the code should be

Code:
pTeamImported:Meet(pPlayerOther:GetTeam(), true);

See the examples in the New World (DLC 2/7), Wonders (DLC 6) and G&K Medieval Scenarios


Edit: This can also cause problems
Code:
if (pPlayerOther ~= pPlayerImported and not pPlayerOther:IsMinorCiv() and not pPlayerOther:IsBarbarian() and pPlayerOther:IsEverAlive()) then
as you should check for the player being alive BEFORE you call any of the other methods, so it should be
Code:
if (pPlayerOther ~= pPlayerImported and pPlayerOther:IsEverAlive() and not pPlayerOther:IsMinorCiv() and not pPlayerOther:IsBarbarian()) then
Also, comparing objects "pPlayerOther ~= pPlayerImported" is a really bad idea, you should be comparing the player ids "id ~= pPlayerImported:GetID()"
 
Gods it works. I'm such a fool.

TeamTypes eOtherTeam = (TeamTypes)lua_tointeger(L, 2);

It sat right there infront of me, and I just glossed over that _tointeger

Thanks for pointing that out. I had started looking at Scenarios but had only looked at one or two (I don't even really remember which ones...)

You are right about checking for being alive first.

And thanks for the pointer about comparing ID's.
 
Top Bottom