You must be logged in to post messages.
Please login or register

Modding Discussions
Moderated by Alexastor, MosheLevi, Mister SCP

Hop to:    
Welcome! You are not logged in. Please Login or Register.376 replies, Sticky
Age of Empires III Heaven » Forums » Modding Discussions » Idea: Let's start a "Better AI project"
Bottom
Topic Subject:Idea: Let's start a "Better AI project"
« Previous Page  1 ··· 13 14 15  Next Page »
murdilator
Skirmisher
posted 09-22-12 06:52 AM EDT (US)         
EDIT:

Here are links to The Beginner's Guide to AI Modification, written by me:

Part 1:

http://aoe3.heavengames.com/cgi-bin/forums/display.cgi?action=ct&f=14,38878,,10

Part 2:

http://aoe3.heavengames.com/cgi-bin/forums/display.cgi?action=ct&f=14,38885,30,100


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

Original Post (9-22-2012, 06:52 AM)

I have an idea that we can significantly improve the AOE3 AI. This will of course build from the Drauger AI. I have made a decent AI in my mod N3O Fan Patch, but it currently uses some techs that I have added to the game, so it won't work yet for an unmodified Age of Empires III. Yet I can easily remove those techs with some testing (to make sure it works) so that it can be played on an unmodified AOE3 and furthermore be a base rubric for further AI improvement.

http://aoe3.heavengames.com/downloads/showfile.php?fileid=3363

Mandorex also has an improved version of the Drauger AI:

http://aoe3.heavengames.com/downloads/showfile.php?fileid=3222


I also noticed another Drauger AI improvement uploaded for Vanilla AOE3 which makes the computer build forward bases. This would be interesting to look into, but I do say that this is quite a job for one coder. However, what I suggest is that we make it a community project to sort of build off an already improved AI and (for those who have time) fix various issues. I myself am pressed for time otherwise.


Anyone for an idea such as this!


Feel free to discuss and share your ideas! Its best if we discuss before we actually decide anything big.



best of regards to all here,




murdilator

[This message has been edited by murdilator (edited 06-23-2013 @ 10:35 AM).]

AuthorReplies:
Panmaster2014
Skirmisher
posted 10-15-18 09:45 PM EDT (US)     351 / 376       
BTW, I have noticed that we can't place kbUnitQuerySetUnitType before kbUnitQuerySetPlayerID, kbUnitQuerySetState, or it will forever return 0.
That's because you forgot to set reset to false.
kbUnitQuerySetPlayerID(queryID,playerID, bool reset)
ylg_hke
Skirmisher
posted 10-16-18 10:15 AM EDT (US)     352 / 376       
That's because you forgot to set reset to false.
==> Thanks! I didn't know there's a bool.

EDIT:
How do you guys locate the incorrect lines? My script now wont run :/

[This message has been edited by ylg_hke (edited 10-18-2018 @ 02:54 AM).]

AlistairJah
Skirmisher
posted 10-18-18 01:19 PM EDT (US)     353 / 376       
Locate incorrect lines: super large comment, then make it smaller and smaller until you see the incorrect line.

/*
Codes
Codes
Codes
Incorrect
Codes
Codes*/

Codes
Codes
/*
Codes
Incorrect
Codes
Codes*/

Codes
Codes
Codes
Incorrect
/*Codes
Codes*/


It's extremely annoying so just avoid incorrect lines as much as you can.
Panmaster2014
Skirmisher
posted 10-18-18 03:31 PM EDT (US)     354 / 376       
Are you running the debugger +noIntroCinematics +developer +aiDebug +showAIOutput +logAIErrors and then using the AI debug files in My Documents/.../AI3 to find the last line?

[This message has been edited by Panmaster2014 (edited 10-18-2018 @ 03:56 PM).]

ylg_hke
Skirmisher
posted 10-18-18 09:06 PM EDT (US)     355 / 376       
super large comment, then make it smaller and smaller until you see the incorrect line.
==> This is exactly what I have been doing, haha.


debugger +noIntroCinematics +developer +aiDebug +showAIOutput +logAIErrors and then using the AI debug files
==> Oh cool, it shows the rule name, right? It would be best if it could show the exact line number.
Anyway it's a huge improvement already, Thanks!!
AlistairJah
Skirmisher
posted 10-19-18 03:49 AM EDT (US)     356 / 376       
Where are you murdilator XD you started the thread XD
ylg_hke
Skirmisher
posted 10-19-18 04:32 AM EDT (US)     357 / 376       
Where are you murdilator XD you started the thread XD
==> After so many years, he probably get bored with AI scripting...


Anyway, somehow the transport plan can't ungarrison villagers on my end, any idea? (I just copied it from ZenAI to an empty .xs file.)
=======================================

rule Trans
active
minInterval 5
{

aiSetWaterMap(true);
static int gGetMyUnitQueryID = -1;
if (gGetMyUnitQueryID < 0)
{ gGetMyUnitQueryID = kbUnitQueryCreate("gGetMyUnitQueryID"); }


static int buildID = -1;
if (buildID < 0)
{
kbUnitQueryResetResults(gGetMyUnitQueryID);
kbUnitQuerySetPlayerID(gGetMyUnitQueryID, cMyID);
kbUnitQuerySetState(gGetMyUnitQueryID, cUnitStateAlive);
kbUnitQuerySetUnitType(gGetMyUnitQueryID, cUnitTypeTownCenter);
kbUnitQueryExecute(gGetMyUnitQueryID);
buildID = kbUnitQueryGetResult(gGetMyUnitQueryID, 0);
}

kbUnitQueryResetResults(gGetMyUnitQueryID);
kbUnitQuerySetPlayerID(gGetMyUnitQueryID, cMyID);
kbUnitQuerySetState(gGetMyUnitQueryID, cUnitStateAlive);
kbUnitQuerySetUnitType(gGetMyUnitQueryID, cUnitTypeAbstractVillager);
int UnitNo = kbUnitQueryExecute(gGetMyUnitQueryID);

aiChat(1, "buildID " + buildID);
aiChat(1, "kbGetUnitBaseTypeID(buildID) " + kbGetUnitBaseTypeID(buildID));
aiChat(1, "UnitNo " + UnitNo);

if(UnitNo < 1)
{return;}

vector v = kbUnitGetPosition(buildID);
int UnitID = -1;
int planID = -1;
planID = aiPlanCreate("DeGarrison"+buildID,cPlanTransport);
aiPlanAddUnitType(planID,cUnitTypeAbstractVillager,1,99,99);
aiPlanSetVariableInt(planID,cTransportPlanTransportID,0,buildID);
aiPlanSetVariableInt(planID,cTransportPlanTransportTypeID,0,kbGetUnitBaseTypeID(buildID));
aiPlanSetVariableInt(planID,cTransportPlanPathType,0,cTransportPathTypePoints);
aiPlanSetVariableBool(planID,cTransportPlanTakeMoreUnits,0,false);
aiPlanSetVariableBool(planID,cTransportPlanPathPlanned,0,true);
aiPlanSetVariableBool(planID,cTransportPlanPersistent,0,false);
aiPlanSetVariableBool(planID,cTransportPlanReturnWhenDone,0,false); //no effect
aiPlanSetVariableBool(planID,cTransportPlanMaximizeXportMovement,0,false); //no effect
aiPlanSetVariableVector(planID,cTransportPlanGatherPoint,0, v);

aiPlanSetActive(planID,true);

for(i=0;<UnitNo)
{
UnitID = kbUnitQueryGetResult(gGetMyUnitQueryID, i);
aiPlanAddUnit(planID, UnitID);
}
}


=======================================
AlistairJah
Skirmisher
posted 10-19-18 05:37 AM EDT (US)     358 / 376       
You're trying to put villagers inside villagers (buildid AbstractVillager). That's why it doesn't work.


Look at my Ceylon transport plan, in my script.

[This message has been edited by AlistairJah (edited 10-19-2018 @ 05:39 AM).]

ylg_hke
Skirmisher
posted 10-19-18 06:36 AM EDT (US)     359 / 376       
buildid AbstractVillager
==> I don't get it:

kbUnitQueryResetResults(gGetMyUnitQueryID);
kbUnitQuerySetPlayerID(gGetMyUnitQueryID, cMyID);
kbUnitQuerySetState(gGetMyUnitQueryID, cUnitStateAlive);
kbUnitQuerySetUnitType(gGetMyUnitQueryID, cUnitTypeTownCenter);
kbUnitQueryExecute(gGetMyUnitQueryID);
buildID = kbUnitQueryGetResult(gGetMyUnitQueryID, 0);

buildID is a TC.

I had read your script, but yours is really for transport, not for ungarrision, that's why I copied from ZenAI.
AlistairJah
Skirmisher
posted 10-19-18 10:29 AM EDT (US)     360 / 376       
Are you sure?
kbUnitQueryResetResults(gGetMyUnitQueryID);
kbUnitQuerySetPlayerID(gGetMyUnitQueryID, cMyID);
kbUnitQuerySetState(gGetMyUnitQueryID, cUnitStateAlive);
kbUnitQuerySetUnitType(gGetMyUnitQueryID, cUnitTypeAbstractVillager);
int UnitNo = kbUnitQueryExecute(gGetMyUnitQueryID);
ylg_hke
Skirmisher
posted 10-19-18 11:19 AM EDT (US)     361 / 376       
Are you sure?
==>

kbUnitQuerySetUnitType(gGetMyUnitQueryID, cUnitTypeAbstractVillager);
int UnitNo = kbUnitQueryExecute(gGetMyUnitQueryID);

wrong quotation, this's UnitNo, not buildID, please re-read my script, I run the query twice, first get tc id and then get all villager count.

Anyway, please forget that rule , i moved it to the original AI and replaced the query with getUnit and now its working.

I noticed that turning off aiSetRandomMap(true) seems to be another way to make transport plan works.
AlistairJah
Skirmisher
posted 10-19-18 11:43 AM EDT (US)     362 / 376       
Oops my bad
ylg_hke
Skirmisher
posted 10-20-18 03:12 AM EDT (US)     363 / 376       
Since Town Bell plan is often too slow to start and only affect nearby units, we need manual garrison anyway.

If you have time please test it, now villagers should be able to garrison at nearby TC outpost village castle fort:

====================================================
3 extern int
===================================================
extern int gGetMyUnitQueryID = -1;
extern int gGetAllyUnitQueryID = -1;
extern int gGetEnemyUnitQueryID = -1;
gGetMyUnitQueryID = kbUnitQueryCreate("gGetMyUnitQueryID");

gGetAllyUnitQueryID = kbUnitQueryCreate("gGetAllyUnitQueryID");

gGetEnemyUnitQueryID = kbUnitQueryCreate("gGetEnemyUnitQueryID");

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




void GathererGetShelter(vector Position = cInvalidVector)
{
int GathererNo = -1;

kbUnitQueryResetResults(gGetMyUnitQueryID);
kbUnitQuerySetPlayerID(gGetMyUnitQueryID, cMyID);
kbUnitQuerySetState(gGetMyUnitQueryID, cUnitStateAlive);
kbUnitQuerySetUnitType(gGetMyUnitQueryID, cUnitTypeAbstractVillager);
kbUnitQuerySetPosition(gGetMyUnitQueryID, Position);
kbUnitQuerySetMaximumDistance(gGetMyUnitQueryID, 40);
kbUnitQuerySetAscendingSort(gGetMyUnitQueryID, true);
GathererNo = kbUnitQueryExecute(gGetMyUnitQueryID);

if(GathererNo < 1)
{
return;
}

int GathererID = -1;
int ShelterNo = -1;
int ShelterID = -1;
int ShelterType = -1;
int UngarrisonPlanID = -1;
int EffectiveCount = -1;

kbUnitQueryResetResults(gGetAllyUnitQueryID);
kbUnitQuerySetPlayerID(gGetAllyUnitQueryID, -1);
kbUnitQuerySetPlayerRelation(gGetAllyUnitQueryID, cPlayerRelationAlly);
kbUnitQuerySetState(gGetAllyUnitQueryID, cUnitStateAlive);
kbUnitQuerySetIgnoreKnockedOutUnits(gGetAllyUnitQueryID, true);
kbUnitQuerySetPosition(gGetAllyUnitQueryID, Position);
kbUnitQuerySetMaximumDistance(gGetAllyUnitQueryID, 120);
kbUnitQuerySetAscendingSort(gGetAllyUnitQueryID, true);

kbUnitQuerySetUnitType(gGetAllyUnitQueryID, cUnitTypeAbstractTownCenter);
ShelterNo = kbUnitQueryExecute(gGetAllyUnitQueryID);

kbUnitQuerySetUnitType(gGetAllyUnitQueryID, cUnitTypeAbstractFort);
ShelterNo = kbUnitQueryExecute(gGetAllyUnitQueryID);

kbUnitQuerySetUnitType(gGetAllyUnitQueryID, cUnitTypeAbstractFoundry);
ShelterNo = kbUnitQueryExecute(gGetAllyUnitQueryID);

for(x = 0; < ShelterNo)
{
ShelterID = kbUnitQueryGetResult(gGetAllyUnitQueryID, x);

if((kbUnitIsType(ShelterID, cUnitTypeVictoryPointBuilding) == true) || (kbUnitIsType(ShelterID, cUnitTypeArtilleryDepot) == true))
{
continue;
}

break;
}

UngarrisonPlanID = aiPlanCreate("Ungarrison" + ShelterID, cPlanTransport);
aiPlanSetVariableInt(UngarrisonPlanID, cTransportPlanTransportID, 0, ShelterID);
aiPlanSetVariableInt(UngarrisonPlanID, cTransportPlanTransportTypeID, 0, kbGetUnitBaseTypeID(ShelterID));
aiPlanSetVariableBool(UngarrisonPlanID, cTransportPlanPathPlanned, 0, true);
aiPlanSetVariableVector(UngarrisonPlanID, cTransportPlanGatherPoint, 0, kbUnitGetPosition(ShelterID));

EffectiveCount = 0;

for(x = 0; < GathererNo)
{
GathererID = kbUnitQueryGetResult(gGetAllyUnitQueryID, x);

if((kbUnitIsType(GathererID, cUnitTypeShip) == true) || (kbUnitGetPlanID(GathererID) >= 0))
{
continue;
}

EffectiveCount = EffectiveCount + 1;
aiTaskUnitWork(GathererID, ShelterID);
aiPlanAddUnit(UngarrisonPlanID, GathererID);
}

aiPlanAddUnitType(UngarrisonPlanID, cUnitTypeAbstractVillager, 1, EffectiveCount, EffectiveCount);

xsEnableRule("GathererUngarrison");
}


rule GathererUngarrison
inactive
minInterval 7
{
int UngarrisonPlanNo = -1;
int UngarrisonPlanID = -1;
int ShelterID = -1;
int EnemyNo = -1;
int EnemyHP = -1;
int AllyHP = -1;

UngarrisonPlanNo = aiPlanGetNumber(cPlanTransport, -1, false);

if(UngarrisonPlanNo == 0)
{
xsDisableSelf();
return;
}

for(x = 0; < UngarrisonPlanNo)
{
UngarrisonPlanID = aiPlanGetIDByIndex(cPlanTransport, -1, false, x);
ShelterID = aiPlanGetVariableInt(UngarrisonPlanID, cTransportPlanTransportID, 0);

if(kbUnitIsType(ShelterID, cUnitTypeShip) == true)
{
continue;
}

kbUnitQueryResetResults(gGetEnemyUnitQueryID);
kbUnitQuerySetPlayerID(gGetEnemyUnitQueryID, -1);
kbUnitQuerySetPlayerRelation(gGetEnemyUnitQueryID, cPlayerRelationEnemyNotGaia);
kbUnitQuerySetState(gGetEnemyUnitQueryID, cUnitStateAlive);
kbUnitQuerySetIgnoreKnockedOutUnits(gGetEnemyUnitQueryID, true);
kbUnitQuerySetPosition(gGetEnemyUnitQueryID, kbUnitGetPosition(ShelterID));
kbUnitQuerySetMaximumDistance(gGetEnemyUnitQueryID, 40);
kbUnitQuerySetAscendingSort(gGetEnemyUnitQueryID, true);
kbUnitQuerySetUnitType(gGetEnemyUnitQueryID, cUnitTypeLogicalTypeLandMilitary);
EnemyNo = kbUnitQueryExecute(gGetEnemyUnitQueryID);
EnemyHP = kbUnitQueryGetUnitHitpoints(gGetEnemyUnitQueryID, true);

if(EnemyNo < 1)
{
aiPlanSetActive(UngarrisonPlanID);
continue;
}

if(EnemyNo >= 10)
{
continue;
}

kbUnitQueryResetResults(gGetAllyUnitQueryID);
kbUnitQuerySetPlayerID(gGetAllyUnitQueryID, -1);
kbUnitQuerySetPlayerRelation(gGetAllyUnitQueryID, cPlayerRelationAlly);
kbUnitQuerySetState(gGetAllyUnitQueryID, cUnitStateAlive);
kbUnitQuerySetIgnoreKnockedOutUnits(gGetAllyUnitQueryID, true);
kbUnitQuerySetPosition(gGetAllyUnitQueryID, kbUnitGetPosition(ShelterID));
kbUnitQuerySetMaximumDistance(gGetAllyUnitQueryID, 40);
kbUnitQuerySetAscendingSort(gGetAllyUnitQueryID, true);
kbUnitQuerySetUnitType(gGetAllyUnitQueryID, cUnitTypeLogicalTypeLandMilitary);
kbUnitQueryExecute(gGetAllyUnitQueryID);
AllyHP = kbUnitQueryGetUnitHitpoints(gGetAllyUnitQueryID, true);

if(AllyHP >= (EnemyHP*2))
{
aiPlanSetActive(UngarrisonPlanID);
}
}
}



EDIT:
How can i change the scenario game speed to fast ?

[This message has been edited by ylg_hke (edited 10-20-2018 @ 09:41 PM).]

AlistairJah
Skirmisher
posted 10-21-18 01:59 AM EDT (US)     364 / 376       
You can't change scenario speed.
ylg_hke
Skirmisher
posted 10-21-18 09:39 PM EDT (US)     365 / 376       
I found this post:
http://aoe3.heavengames.com/cgi-bin/forums/display.cgi?action=ct&f=12,37493,,10
But sadly, map ("shift-g", "developer", "gameSpeedMult(2.0)") doesn't work, I think its another hidden function.


Anyway, suddenly many questions came to mind:
-what does kbEscrowSetCap do?

-Is "rule selectCaptain" useless? i dont seem to find anything else related to it.

-Any info about AttackPlanAttackRoutePattern, AttackPlanBaseAttackMode, AttackPlanRetreatMode ?

-Any progress on making unitPicker to respect the ratio?
Or may be we should just use our own train plans?



Regarding updateForecasts, now i think we should set a fixed amount of resource for calculating military budgets, consider the following facts:

train time:
villager = 25 (100f = 100)
musket = 30 (75f25g = 100)
xbow = 27 (40f40w = 80)
hussar = 40 (120f80g = 200)

in early age2, within 1 min, on average we will have (ABQ):
villager = 2.4 (1 tc) = 2.4*100 = 240
musket = 14-17 (2 barracks) = ~15*100 = 1500
xbow = 14-18 (2 barracks) = ~15*80 = 1200
hussar = 5-8 (1 stable) = ~6.5*200 = 1300

these ratios should be rather static and expend accordingly, so, may be something like this:
==============================
//1st land unit
CostFood = kbUnitCostPerResource(gPrimaryArmyUnit, cResourceFood);
CostWood = kbUnitCostPerResource(gPrimaryArmyUnit, cResourceWood);
CostGold = kbUnitCostPerResource(gPrimaryArmyUnit, cResourceGold);

CostTotal = CostFood + CostWood + CostGold;

EffectiveNo = (1350 * gPrimaryArmyUnitRatio) / CostTotal;

CostFood = CostFood * EffectiveNo;
CostWood = CostWood * EffectiveNo;
CostGold = CostGold * EffectiveNo;

PercentageFood = PercentageFood + CostFood;
PercentageWood = PercentageWood + CostWood;
PercentageGold = PercentageGold + CostGold;
==============================


BTW, have i ever mentioned that kbUnitQuery seems to be player based? mother-nature cant access player 1's queries, vice versa.
Panmaster2014
Skirmisher
posted 10-21-18 11:13 PM EDT (US)     366 / 376       
what does kbEscrowSetCap do?
It should limit the amount in escrow. It's still unknown if escrows can cause resource leakages.
Any info about AttackPlanAttackRoutePattern, AttackPlanBaseAttackMode, AttackPlanRetreatMode ?
I've run enough tests in the scenario editor to be certain the first and second do nothing whatsoever.
BTW, have i ever mentioned that kbUnitQuery seems to be player based? mother-nature cant access player 1's queries, vice versa.
You need to set the player context before creating the query. That would make sense given queries have the see-able option.

[This message has been edited by Panmaster2014 (edited 10-21-2018 @ 11:17 PM).]

AlistairJah
Skirmisher
posted 10-22-18 01:10 AM EDT (US)     367 / 376       
BTW, have i ever mentioned that kbUnitQuery seems to be player based? mother-nature cant access player 1's queries, vice versa.
Well, nearly all of knowledge base functions, if not all of them, are "player based" like you say. Query that's been executed as player 1 can't be seen by player 2.
ylg_hke
Skirmisher
posted 10-22-18 11:13 AM EDT (US)     368 / 376       
I've run enough tests in the scenario editor to be certain the first and second do nothing whatsoever.
==>
What about AttackPlanRetreatMode?

I remember making troops not to take the straightforward path, but to take the near-map-edge path. It was like 2-3 months ago, and now I'm not sure how I did it....


Anyway, the bool of kbUnitQuerySetPlayerID is insanely important.
I know that I mentioned it and kbUnitQuerySetUnitType before, but seriously, we either have to set it to false
or have to place kbUnitQuerySetPlayerID right after kbUnitQueryResetResults, otherwise it will mess things up.

Even ES scripters overlooked it, "getUnit" from the original AI is flawed, it can get fallen hero units, even with kbUnitQuerySetIgnoreKnockedOutUnits = true.


BTW, alive, non-shrined huntables have at least 3 action types: 7 8 47

fallen hero action type = 43
Panmaster2014
Skirmisher
posted 10-22-18 08:18 PM EDT (US)     369 / 376       
What about AttackPlanRetreatMode?
I only use cAttackPlanRetreatModeNone. Given how stupid it is already retreating would do more harm than good.
I remember making troops not to take the straightforward path, but to take the near-map-edge path. It was like 2-3 months ago, and now I'm not sure how I did it....
The path is always the same. It always goes for the weakest point or nearest. At least it can group-move and path through walls.
BTW, alive, non-shrined huntables have at least 3 action types: 7 8 47
Actiontype 3 when shrined or penned.
ylg_hke
Skirmisher
posted 10-23-18 06:25 AM EDT (US)     370 / 376       
Given how stupid it is already retreating would do more harm than good.
==> Inf are too slow but Cav should be able to get away (?)

Anyway, have anyone tried "No TrainPlan, forever aiTaskUnitTrain" for military units?


EDIT:
kbUnitGetMovementType is probably broken, it always returns 1.

[This message has been edited by ylg_hke (edited 10-25-2018 @ 11:06 AM).]

Panmaster2014
Skirmisher
posted 10-26-18 01:07 AM EDT (US)     371 / 376       
Anyway, have anyone tried "No TrainPlan, forever aiTaskUnitTrain" for military units?
Quicker and easier to use than plans but you lose out on aiGetCurrentResourceNeed() and escrows.
kbUnitGetMovementType is probably broken, it always returns 1.
Make sure you use the unitTypeID, not unitID.
AlistairJah
Skirmisher
posted 10-26-18 11:30 AM EDT (US)     372 / 376       
There is resource leakage in escrows, confirmed. I can't seem to fix it, no matter what I try. Any idea?

[This message has been edited by AlistairJah (edited 10-26-2018 @ 11:30 AM).]

Panmaster2014
Skirmisher
posted 10-26-18 01:25 PM EDT (US)     373 / 376       
I still don't fully understand how kbResourceGet() can be more than all escrow amounts combined. kbEscrowAllocateResources resets it at least. If there's a leak somewhere it's not obvious, maybe wood seems low at times but it does get 60+ upgrades... hmm
ylg_hke
Skirmisher
posted 10-30-18 03:54 AM EDT (US)     374 / 376       
Make sure you use the unitTypeID, not unitID.
<==Nailed it!

How did you guys identify the resource leakage in escrows?
AlistairJah
Skirmisher
posted 10-30-18 10:49 AM EDT (US)     375 / 376       
Compare values returned by kbResourceGet() and kbEscrowGetAmount()
ylg_hke
Skirmisher
posted 11-14-18 10:19 AM EDT (US)     376 / 376       
Any progress on escrows?

anyway, i just wanted to say that kbUnitQuerySetAreaGroupID is very important when it comes to maps like ceylon, Yucatan.
« Previous Page  1 ··· 13 14 15  Next Page »
You must be logged in to post messages.
Please login or register

Hop to:    

Age of Empires III Heaven | HeavenGames