AOE Monk

Koare

Chieftain
Joined
Sep 15, 2016
Messages
7
Location
Sweden
This is the best idea I've ever had.

Anyone who's ever thought of replacing missionaries with monks from Age of Empires?

No? well they'd have the best line to say aswell:
WOLOLO

Is there an easy enough way of doing this, without phasing too many problems?
 

Attachments

  • 1c10gp.jpg
    1c10gp.jpg
    43.9 KB · Views: 56
I'm in the process of making a custom civ and have already coded the UU that uses this 'WOLOLO"-ability. It requires VMC, but I could share the lua-code if you want :)
(I'm 80% sure that it will not cause crashes, but I could be wrong on that since I don't think I've tested enough scenarios yet... The game really hates converting embarked units for some reason...)
 
I'm in the process of making a custom civ and have already coded the UU that uses this 'WOLOLO"-ability. It requires VMC, but I could share the lua-code if you want :)
(I'm 80% sure that it will not cause crashes, but I could be wrong on that since I don't think I've tested enough scenarios yet... The game really hates converting embarked units for some reason...)

Dude, that would be awesome. If you'd just tell me how to use the code properly too, it'd be like the best thing ever! =)
 
DISCLAIMER: DON'T MIND THE SPAGHETTI-CODE
NOTE: This requires VMC!!! (the events I use are also present in the CP IIRC, though I haven't tested it with that yet and can therefore not guarantee that it works with it)

Now, what my code currently does (as in, I noticed I wasn't completely finished yet), is check adjacent tiles for units from civs that the player is at war with. If that is the case, then all units that are at war with the player on that plot become 'qualified' for the random selection. The random selection then takes place, where 2 'qualified' plots will be chosen. All units that are at war with the player that are on that plot will be converted to your side. If there is also a trade-unit on that plot, it will be moved to your nearest city. If that trade unit is a cargo ship and you have no coastal cities, it will become a Caravan. (converting trade units allows you to go over the trade limit, which was intended on my end). If you have no cities, then the trade unit will be destroyed.

Anyhow, here is the code:
Spoiler :

---------------------------------
The lua file (set to an InGame UI addin in the content-tab of modbuddy)
Code:
-- 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
Add this in an SQL-file (and use OnModActivate -> UpdateDatabase in the Actions-tab); It enables the use of the DLL Lua-events above
Code:
--Allows VMC's 'CustomMissions'-GameEvents to function!
UPDATE CustomModOptions SET Value=1 WHERE Name='EVENTS_CUSTOM_MISSIONS';

Use "ConvertAdjacentUnits(iPlayer,iUnit,true,GetTikiHypnotizeCount(iPlayer));" if you only want to convert units from players that you are at war with. Change the 'true' to 'false' if you don't care about war. (EDIT: It seems that I only implemented that 'feature' for half of the code; Right now, setting that 'true' to 'false' acts the following: If you are adjacent to units at war, then you can convert any (warring or non-warring) adjacent unit.
Replace the 2 in "return 2;" to allow more plots to be converted. (If the unit runs out of plots to convert, it will simply convert the maximum amount of plots; the extra 'converts' will be wasted)

EDIT: I wish I could highlight things in the code but that feature has not yet been implemented on the new forums :cry:
EDIT2: Neither is the :cry:-smiley :(

EDIT3: I was also going to add a custom sound effect when converting units, but I haven't looked into that yet. (and it'll probably take a while before I actually have time to check)
 
Last edited:
Top Bottom