-- TRL_TikTakGeneral
-- Author: Troller0001
--Custom Missions (as part of the VMC DLL) by Whoward69!
-- DateCreated: 9/19/2016 10:08:17 AM
--------------------------------------------------------------
--This file uses the VMC DLL! It will NOT function without it!
print("TRL_TikiTakGeneral is loaded");
-- The custom mission we're interested in
local iMissionHypnotize = GameInfoTypes.MISSION_TRL_HYPNOTIZE
local iTiki = GameInfoTypes.UNIT_TRL_TIKI
local iDomainSea = GameInfoTypes.DOMAIN_SEA
local iCaravan = GameInfoTypes.UNIT_CARAVAN
-- Return value constants needed by CustomMissionStart, do not change these, just use them
local CUSTOM_MISSION_NO_ACTION = 0
local CUSTOM_MISSION_ACTION = 1
local CUSTOM_MISSION_DONE = 2
local CUSTOM_MISSION_ACTION_AND_DONE = 3
--======Where all the stuff actually happens (NOTE: The AI doesn't use this stuff)====(DLL functions)=================================================================
function OnCustomMissionPossible(iPlayer, iUnit, iMission, iData1, iData2, _, _, iPlotX, iPlotY, bTestVisible)
if (iMission == iMissionHypnotize) then
local pPlayer = Players[iPlayer]
local pUnit = pPlayer:GetUnitByID(iUnit)
-- Can this particular unit type for this player perform this mission? In our case only Clan Magicians can
if (pUnit:GetUnitType() == iTiki) then
-- So we know the unit could perform the mission, but does it meet all the circumstantial conditions, ie, is the unit within our territory?
print("Tiki mission possible; This unit is a Tiki");
if (not AnyAdjacentUnitsAtWar(iPlayer,iUnit)) then
print("Tiki mission possible; Adjacent units that are at war");
-- Circumstantial conditions NOT met (not adjacent to any units), return the value of bTestVisible
return bTestVisible
end
-- All conditions met, so we can perform the action
return true
end
end
-- The unit can't perform the mission - return false
return false
end
GameEvents.CustomMissionPossible.Add(OnCustomMissionPossible)
function OnCustomMissionStart(iPlayer, iUnit, iMission, iData1, iData2, iFlags, iTurn)
print("OnCustomMissionStart fires");
-- Is this the Morindim player and the mission we're interested in?
if (iMission == iMissionHypnotize) then
-- We don't need to check that the unit can actually perform the mission now, as we couldn't have clicked the mission button if it can't!
print("Tiki Mission Start");
ConvertAdjacentUnits(iPlayer,iUnit,true,GetTikiHypnotizeCount(iPlayer));
-- Instantaneous missions MUST ALWAYS return the CUSTOM_MISSION_NO_ACTION outcome after executing the mission code
return CUSTOM_MISSION_NO_ACTION
end
-- The mission wasn't for us, return the default outcome
return CUSTOM_MISSION_NO_ACTION
end
GameEvents.CustomMissionStart.Add(OnCustomMissionStart)
function OnCustomMissionCompleted(iPlayer, iUnit, iMission, iData1, iData2, iFlags, iTurn)
-- Is this the Morindim player and the mission we're interested in?
print("OnCustomMissionCompleted fires");
if (iMission == iMissionHypnotize) then
-- We don't need to check that the unit can actually perform the mission, as we couldn't have clicked the mission button if it can't!
-- Instantaneous missions will never do anything here (as we did all the work in CustomMissionStart)
local pUnit = Players[iPlayer]:GetUnitByID(iUnit);
pUnit:Kill(true); --this should be an exception to that rule(?), since this event probably expects parameters from the previous gameEvent!
print("Tiki mission ended; the Tiki is dead");
-- Return true as this is our mission
return true
end
-- The mission wasn't for us, return false
return false
end
GameEvents.CustomMissionCompleted.Add(OnCustomMissionCompleted)
--========Non-VMC DLL functions====================================================================
--function that checks adjacent tiles for units from civilizations that you are at war with
function AnyAdjacentUnitsAtWar(iPlayer,iUnit)
print("----------------------Checking Adjacent Tiki Plots-----------------------------------");
local pPlayer = Players[iPlayer];
local iTikiTeam = pPlayer:GetTeam();
local pUnit = pPlayer:GetUnitByID(iUnit);
local pPlot = pUnit:GetPlot();
for i = 0, 5 do
local pPlotAdj = Map.PlotDirection(pPlot:GetX(),pPlot:GetY(),i);
if pPlotAdj == nil then
print("Plot was off the map")
elseif pPlotAdj:IsCity() then
print("This plot was a city!");
else
print("Checking plot: X="..pPlotAdj:GetX().." Y="..pPlotAdj:GetY())
for i=0,pPlotAdj:GetNumLayerUnits()-1,1 do --loop through all units on an adjacent tile
local pUnitAdj = pPlotAdj:GetLayerUnit(i);
local pUnitAdjOwner = Players[pUnitAdj:GetOwner()];
local pUnitAdjOwnerTeam = Teams[pUnitAdjOwner:GetTeam()];
if pUnitAdjOwnerTeam:IsAtWar(iTikiTeam) then
print("The TikiTeam was at war with an adjacent unit!");
print("-----------------------/end Checking Adjacent Tiki Plots------------------------------");
return true;
end
end
end
end
print("The TikiTeam was NOT at war with an adjacent unit!");
print("-----------------------/end Checking Adjacent Tiki Plots------------------------------");
return false;
end
function GetTikiHypnotizeCount(iPlayer)
return 2;
end
function ConvertAdjacentUnits(iPlayer,iUnit,bWar,iNumUnits) --bWar: Must units be at war in order to be converted?
local pPlayer = Players[iPlayer];
local iTikiTeam = pPlayer:GetTeam();
local pUnit = pPlayer:GetUnitByID(iUnit);
local pPlot = pUnit:GetPlot();
local tAdjValidPlots= {};
for i = 0, 5 do --loop through all adjacent plots to add all units on those plots to a table
local pPlotAdj = Map.PlotDirection(pPlot:GetX(),pPlot:GetY(),i);
if pPlotAdj == nil then
print("Plot was off the map")
elseif pPlotAdj:IsCity() then
print("This plot was a city!");
else
print("Checking plot: X="..pPlotAdj:GetX().." Y="..pPlotAdj:GetY())
for i=0,pPlotAdj:GetNumLayerUnits()-1,1 do --loop through all units on an adjacent tile; units from different players may be on the same tile! e.g. caravans and combat units
local pUnitAdj = pPlotAdj:GetLayerUnit(i);
local pUnitAdjOwner = Players[pUnitAdj:GetOwner()];
local pUnitAdjOwnerTeam = Teams[pUnitAdjOwner:GetTeam()];
if pUnitAdjOwnerTeam:IsAtWar(iTikiTeam) or (not bWar) then
print("Found a unit to convert!");
local bPlotIsInTable = false;
for i=1,#tAdjValidPlots,1 do
if tAdjValidPlots[i] == pPlotAdj then
bPlotIsInTable = true;
end
end
if #tAdjValidPlots == 0 or not bPlotIsInTable then
tAdjValidPlots[#tAdjValidPlots+1] = pPlotAdj; --it is generally not a good idea to store Pointers, but since this pointer is literally used 3 lines below it should be fine
print("This plot was not yet present in the table! It has now been added to it!");
else
print("This plot was already present in the table; It was not added again!");
end
end
end
end
end
for iIndex, pDebugPlot in pairs(tAdjValidPlots) do --a for loop for debugging; ignore
print("tAdjValidPlots["..iIndex.."] = "..pDebugPlot:GetPlotIndex().." (=PlotIndex)");
end
print(iNumUnits.." plots will have its unit(s) converted!");
for i=0,iNumUnits-1,1 do --repeat an X amount of times (depending on technology, i.e. iNumUnits)
--Game.Rand(value,log) -> generate a random integer from 0 to value-1
local iRandomTableIndex = 1 + Game.Rand(#tAdjValidPlots,"Rolling a random plot to Hypnotize");
local pRandomPlot = tAdjValidPlots[iRandomTableIndex];
if(pRandomPlot ~= nil) then --i.e. There are no other valid random plots left!
print("A random plot to hypnotize was chosen: "..pRandomPlot:GetPlotIndex().." (=PlotIndex)");
print("This random plot has "..pRandomPlot:GetNumLayerUnits().." unit(s) on it");
local tNewUnits = {}; --store the units to convert in a table; (when units are converted GetLayerUnit(i) changes order!)
for j=0,pRandomPlot:GetNumLayerUnits()-1,1 do --loop through all units on the randomly chosen tile;
print("Index j = "..j);
local pUnitToHypnotize = pRandomPlot:GetLayerUnit(j);
local iUnitType = pUnitToHypnotize:GetUnitType();
local pUnitPlayer = Players[pUnitToHypnotize:GetOwner()];
print("Checking "..GameInfo.Units[iUnitType].Type.."(ID = "..pUnitToHypnotize:GetID()..")");
if Teams[pUnitPlayer:GetTeam()]:IsAtWar(iTikiTeam) then
if pUnitToHypnotize:IsTrade() then --it is a trade unit; it probably needs to be converted first too! Better safe than sorry!
table.insert(tNewUnits,1,pUnitToHypnotize);
print("Unit to convert is a trade unit!");
elseif not pUnitToHypnotize:IsCombatUnit() then --it is a civilian unit; thus, it needs to be converted first!
table.insert(tNewUnits,1,pUnitToHypnotize);
print("Unit to convert is a Civilian unit!");
else
table.insert(tNewUnits,pUnitToHypnotize); --insert at the end of the unit 'que'
print("Unit to convert is a combat unit!");
end
print("This unit will be converted after looping through the plot-units has been finished ("..GameInfo.Units[iUnitType].Type..")");
else
print("This unit ("..GameInfo.Units[iUnitType].Type..") is not at war with the TikiTakTribe but was still on this (random) tile!");
end
end
for j=1,#tNewUnits,1 do --a debug loop; ignore
print("tNewUnits["..j.."] = "..tNewUnits[j]:GetID());
end
for j=1,#tNewUnits,1 do --(When converting combat units first, civilian units would get captured upon conversion, and a CTD would take place since the game can't find the Civilian unit-pointer anymore!)
local pUnitToHypnotize = tNewUnits[j];
local pNewUnit = pPlayer:InitUnit(pUnitToHypnotize:GetUnitType(), pUnitToHypnotize:GetX(), pUnitToHypnotize:GetY())
pNewUnit:Convert(pUnitToHypnotize);
print("pNewUnit was converted to "..GameInfo.Units[pNewUnit:GetUnitType()].Type);
if pNewUnit:CanEmbark(pNewUnit:GetPlot()) then --if the converted unit was embarked, our new unit should be embarked as well!
print("The converted unit is now embarked");
pNewUnit:SetEmbarked(true);
end
if pNewUnit:IsTrade() then --move a converted caravan/cargo ship to the nearest (coastal) city; else they can't move!
print("The converted unit was a trade unit!");
local pNearestCity;
if pNewUnit:GetDomainType() == iDomainSea then --check if the unit is a naval trade unit
print("The converted trade unit was DOMAIN_SEA!");
pNearestCity = GetClosestCity(pPlayer, pNewUnit:GetPlot(),true); --if so, specifiy in FindNearestCity so that it returns the nearest coastal city
if pNearestCity == nil then --i.e. the Player has no coastal cities
print("The player had no coastal cities!");
local pNewCaravan = pPlayer:InitUnit(iCaravan, pNewUnit:GetX(), pNewUnit:GetY()) --convert the cargo ship to a caravan
pNewUnit:Kill(); --kill the cargo ship
pNewUnit = pNewCaravan --update our pointer so it now references our new Caravan
pNearestCity = GetClosestCity(pPlayer,pNewUnit:GetPlot(),false);
end
else
print("The converted trade unit was NOT DOMAIN_SEA!");
pNearestCity = GetClosestCity(pPlayer, pNewUnit:GetPlot(),false);
end
if not (pNearestCity == nil) then --only do this if the player has cities!
print("The trade unit was moved to the nearest (coastal) city!");
pNewUnit:SetXY(pNearestCity:GetX(),pNearestCity:GetY());
else
pNewUnit:Kill(); --if not, well, deal with it
end
end
end
print("PlotIndex "..pRandomPlot:GetPlotIndex().." was removed from tAdjValidPlots["..iRandomTableIndex.."]");
table.remove(tAdjValidPlots,iRandomTableIndex); --remove the plot from the table and automatically move all values so that no empty spaces occur in that table
end
end
local pTikiTeam = Teams[iTikiTeam]; --updates the embark graphics in case embarked units get hypnotized
pTikiTeam:UpdateEmbarkGraphics();
end
--IGE's (In Game Editor) GetClosestCity; Some edits were made to allow to specifiy between coastal and non-coastal cities
function GetClosestCity(player, plot,bCoastal)
local newCity = nil;
local newCityDist = 100000000;
for city in player:Cities() do
if city:IsCoastal() or not bCoastal then --edit was made here: only continue of the city is a coastal city OR if the city does not need to be coastal
local dist = Map.PlotDistance(city:GetX(), city:GetY(), plot:GetX(), plot:GetY());
if dist < newCityDist then
newCity = city;
newCityDist = dist
end
end
end
return newCity;
end