mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-06-16 05:13:58 -07:00
Many small bugfixes for AIs. Adds support for difficulty (and along with that, some behind-the-door architecture for a handicap system).
Adds -autostart-aidiff to set the difficulty from the command line from testing. This was SVN commit r13278.
This commit is contained in:
parent
8ac9f1d8ea
commit
25293ce0cc
29 changed files with 453 additions and 303 deletions
|
|
@ -22,6 +22,10 @@ function init(settings)
|
|||
}
|
||||
}
|
||||
aiSelection.selected = selected;
|
||||
|
||||
var aiDiff = getGUIObjectByName("aiDifficulty");
|
||||
aiDiff.list = [ "Easy", "Medium", "Hard", "Very Hard" ];
|
||||
aiDiff.selected = settings.difficulty;
|
||||
}
|
||||
|
||||
function selectAI(idx)
|
||||
|
|
@ -40,9 +44,11 @@ function returnAI()
|
|||
var id = g_AIs[idx].id;
|
||||
var name = g_AIs[idx].data.name;
|
||||
|
||||
var difficulty = getGUIObjectByName("aiDifficulty").selected;
|
||||
|
||||
// Pop the page before calling the callback, so the callback runs
|
||||
// in the parent GUI page's context
|
||||
Engine.PopGuiPage();
|
||||
|
||||
g_Callback({"id": id, "name": name});
|
||||
g_Callback({"id": id, "name": name, "difficulty" : difficulty});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,23 +7,30 @@
|
|||
<!-- Add a translucent black background to fade out the menu page -->
|
||||
<object type="image" z="0" sprite="BackgroundTranslucent"/>
|
||||
|
||||
<object type="image" style="StoneDialog" size="50%-200 50%-100 50%+200 50%+140">
|
||||
<object type="image" style="StoneDialog" size="50%-200 40%-140 50%+200 40%+180">
|
||||
|
||||
<object style="TitleText" type="text" size="50%-128 -16 50%+128 16">AI Configuration</object>
|
||||
|
||||
<object size="50%-128 46 50%+128 66">
|
||||
<object type="text" style="RightLabelText" size="0 0 100 100%">
|
||||
<object size="50%-128 30 50%+128 80">
|
||||
<object type="text" style="RightLabelText" size="-10 0 90 50%">
|
||||
AI Player
|
||||
</object>
|
||||
|
||||
<object name="aiSelection" type="dropdown" style="StoneDropDown" size="50%-16 0 50%+144 28">
|
||||
<object name="aiSelection" type="dropdown" style="StoneDropDown" size="50%-24 0 50%+136 28">
|
||||
<action on="SelectionChange">selectAI(this.selected);</action>
|
||||
</object>
|
||||
|
||||
<object type="text" style="RightLabelText" size="-10 35 90 50%+35">
|
||||
AI Difficulty
|
||||
</object>
|
||||
|
||||
<object name="aiDifficulty" type="dropdown" style="StoneDropDown" size="50%-24 35 50%+136 63">
|
||||
</object>
|
||||
</object>
|
||||
|
||||
<object size="15% 50%-48 85% 50%+48">
|
||||
<object size="8% 50%-54 92% 50%+74">
|
||||
|
||||
<object name="aiDescription" type="text" style="LeftLabelText" size="0 0 100% 100%"/>
|
||||
<object name="aiDescription" type="text" style="LeftLabelText" size="0 0 100% 100%" text_align="center"/>
|
||||
|
||||
<!-- TODO: we might want to add things like difficulty controls into here -->
|
||||
|
||||
|
|
|
|||
|
|
@ -779,6 +779,10 @@ function selectMap(name)
|
|||
{
|
||||
g_GameAttributes.settings.PlayerData[i].AI = g_DefaultPlayerData[i].AI;
|
||||
}
|
||||
if (!('AIDiff' in g_GameAttributes.settings.PlayerData[i]))
|
||||
{
|
||||
g_GameAttributes.settings.PlayerData[i].AIDiff = g_DefaultPlayerData[i].AIDiff;
|
||||
}
|
||||
}
|
||||
|
||||
// Reset player assignments on map change
|
||||
|
|
@ -1238,9 +1242,11 @@ function updatePlayerList()
|
|||
Engine.PushGuiPage("page_aiconfig.xml", {
|
||||
ais: g_AIs,
|
||||
id: g_GameAttributes.settings.PlayerData[playerSlot].AI,
|
||||
difficulty: g_GameAttributes.settings.PlayerData[playerSlot].AIDiff,
|
||||
callback: function(ai) {
|
||||
g_GameAttributes.settings.PlayerData[playerSlot].AI = ai.id;
|
||||
|
||||
g_GameAttributes.settings.PlayerData[playerSlot].AIDiff = ai.difficulty;
|
||||
|
||||
if (g_IsNetworked)
|
||||
{
|
||||
Engine.SetNetworkGameAttributes(g_GameAttributes);
|
||||
|
|
|
|||
|
|
@ -325,7 +325,14 @@ var EntityTemplate = Class({
|
|||
return undefined;
|
||||
return +this._template.ResourceSupply.Amount;
|
||||
},
|
||||
|
||||
|
||||
maxGatherers: function()
|
||||
{
|
||||
if (this._template.ResourceSupply !== undefined)
|
||||
return this._template.ResourceSupply.MaxGatherers;
|
||||
return 0;
|
||||
},
|
||||
|
||||
resourceGatherRates: function() {
|
||||
if (!this._template.ResourceGatherer)
|
||||
return undefined;
|
||||
|
|
@ -520,6 +527,20 @@ var Entity = Class({
|
|||
return undefined;
|
||||
return this._entity.resourceSupplyAmount;
|
||||
},
|
||||
|
||||
resourceSupplyGatherers: function()
|
||||
{
|
||||
if (this._entity.resourceSupplyGatherers !== undefined)
|
||||
return this._entity.resourceSupplyGatherers;
|
||||
return [];
|
||||
},
|
||||
|
||||
isFull: function()
|
||||
{
|
||||
if (this._entity.resourceSupplyGatherers !== undefined)
|
||||
return (this.maxGatherers === this._entity.resourceSupplyGatherers.length);
|
||||
return undefined;
|
||||
},
|
||||
|
||||
resourceCarrying: function() {
|
||||
if(this._entity.resourceCarrying === undefined)
|
||||
|
|
|
|||
|
|
@ -216,7 +216,7 @@ aStarPath.prototype.continuePath = function(gamestate)
|
|||
}
|
||||
|
||||
if (this.widthMap[index] < this.preferredWidth)
|
||||
this.gCostArray[index] += 1000 * (this.preferredWidth-this.widthMap[index]);
|
||||
this.gCostArray[index] += 200 * (this.preferredWidth-this.widthMap[index]);
|
||||
|
||||
if (this.map[index] === 200 || (this.map[index] === 201 && this.onWater))
|
||||
this.gCostArray[index] += 1000;
|
||||
|
|
@ -243,7 +243,7 @@ aStarPath.prototype.continuePath = function(gamestate)
|
|||
addCost += 10000;
|
||||
}
|
||||
if (this.widthMap[index] < this.preferredWidth)
|
||||
addCost += 1000 * (this.preferredWidth-this.widthMap[index]);
|
||||
addCost += 200 * (this.preferredWidth-this.widthMap[index]);
|
||||
|
||||
if (this.map[index] === 200 || (this.map[index] === 201 && this.onWater))
|
||||
addCost += 1000;
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
Aegis (working name). Experimental AI for 0 A.D. ( http://play0ad.com/ ). An effort to improve over two bots: qBot (by Quantumstate, based on TestBot) and Marilyn (by Wraitii, itself based on qBot).
|
||||
Aegis. AI for 0 A.D. ( http://play0ad.com/ ). An effort to improve over two bots: qBot (by Quantumstate, based on TestBot) and Marilyn (by Wraitii, itself based on qBot).
|
||||
|
||||
Install by placing files into the data/mods/public/simulation/ai/qbot-wc folder.
|
||||
|
||||
You may want to set "debug : true" in config.js if you are developping, you will get a better understanding of what the AI does. There are also many commented debug outputs, and many commented map outputs that you may want to uncomment.
|
||||
|
||||
This bot is not yet default as it was mostly a work in progress for the past few months. It features early technological support, early naval support, better economic management, better defense and better attack management than qBot. It is generally much stronger than the former, and should hopefully be able to handle more situations properly. It is, however, not faultless.
|
||||
This bot has been made default as of Alpha 13. It features some technological support, early naval support, better economic management, better defense and better attack management (over qBot). It is generally much stronger than the former, and should hopefully be able to handle more situations properly. It is, however, not faultless.
|
||||
|
||||
Please report any error to the wildfire games forum ( http://www.wildfiregames.com/forum/index.php?act=idx ), thanks for playing!
|
||||
Please report any error to the wildfire games forum ( http://www.wildfiregames.com/forum/index.php?act=idx ), and thanks for playing!
|
||||
|
||||
Requires common-api-v3.
|
||||
|
||||
(note: no saved game support as of yet. A very basic difficulty setting can be found in config.js).
|
||||
(note: no saved game support as of yet).
|
||||
|
|
@ -40,6 +40,7 @@ function CityAttack(gameState, militaryManager, uniqueID, targetEnemy, type , ta
|
|||
this.healthRecord = [];
|
||||
|
||||
this.timeOfPlanStart = gameState.getTimeElapsed(); // we get the time at which we decided to start the attack
|
||||
|
||||
this.maxPreparationTime = 210*1000;
|
||||
// in this case we want to have the attack ready by the 13th minute. Countdown. Minimum 2 minutes.
|
||||
if (type !== "superSized" && Config.difficulty >= 2)
|
||||
|
|
@ -78,10 +79,14 @@ function CityAttack(gameState, militaryManager, uniqueID, targetEnemy, type , ta
|
|||
priority = 120;
|
||||
} else if (type === "superSized") {
|
||||
// our first attack has started worst case at the 14th minute, we want to attack another time by the 21th minute, so we rock 6.5 minutes
|
||||
this.maxPreparationTime = 450000;
|
||||
this.unitStat["RangedInfantry"] = { "priority" : 1, "minSize" : 5, "targetSize" : 15, "batchSize" : 5, "classes" : ["Infantry","Ranged", "CitizenSoldier"],
|
||||
this.maxPreparationTime = 480000;
|
||||
this.unitStat["RangedInfantry"] = { "priority" : 1, "minSize" : 5, "targetSize" : 10, "batchSize" : 5, "classes" : ["Infantry","Ranged", "CitizenSoldier"],
|
||||
"interests" : [["strength",3], ["cost",1] ], "templates" : [] };
|
||||
this.unitStat["MeleeInfantry"] = { "priority" : 1, "minSize" : 5, "targetSize" : 15, "batchSize" : 5, "classes" : ["Infantry","Melee", "CitizenSoldier" ],
|
||||
this.unitStat["MeleeInfantry"] = { "priority" : 1, "minSize" : 5, "targetSize" : 10, "batchSize" : 5, "classes" : ["Infantry","Melee", "CitizenSoldier" ],
|
||||
"interests" : [ ["strength",3], ["cost",1] ], "templates" : [] };
|
||||
this.unitStat["ChampRangedInfantry"] = { "priority" : 1, "minSize" : 5, "targetSize" : 15, "batchSize" : 5, "classes" : ["Infantry","Ranged", "Champion"],
|
||||
"interests" : [["strength",3], ["cost",1] ], "templates" : [] };
|
||||
this.unitStat["ChampMeleeInfantry"] = { "priority" : 1, "minSize" : 5, "targetSize" : 15, "batchSize" : 5, "classes" : ["Infantry","Melee", "Champion" ],
|
||||
"interests" : [ ["strength",3], ["cost",1] ], "templates" : [] };
|
||||
this.unitStat["MeleeCavalry"] = { "priority" : 1, "minSize" : 3, "targetSize" : 10, "batchSize" : 3, "classes" : ["Cavalry","Melee", "CitizenSoldier" ],
|
||||
"interests" : [ ["strength",2], ["cost",1] ], "templates" : [] };
|
||||
|
|
@ -169,31 +174,43 @@ function CityAttack(gameState, militaryManager, uniqueID, targetEnemy, type , ta
|
|||
}
|
||||
this.anyNotMinimal = true; // used for support plans
|
||||
|
||||
// taking this so that fortresses won't crash it for now. TODO: change the rally point if it becomes invalid
|
||||
if(gameState.ai.pathsToMe.length > 1)
|
||||
var position = [(gameState.ai.pathsToMe[0][0]+gameState.ai.pathsToMe[1][0])/2.0,(gameState.ai.pathsToMe[0][1]+gameState.ai.pathsToMe[1][1])/2.0];
|
||||
else if (gameState.ai.pathsToMe.length !== 0)
|
||||
var position = [gameState.ai.pathsToMe[0][0],gameState.ai.pathsToMe[0][1]];
|
||||
else
|
||||
var position = [-1,-1];
|
||||
|
||||
if (gameState.ai.accessibility.getAccessValue(position) !== gameState.ai.myIndex)
|
||||
var position = [-1,-1];
|
||||
|
||||
var nearestCCArray = CCs.filterNearest(position, 1).toEntityArray();
|
||||
var CCpos = nearestCCArray[0].position();
|
||||
this.rallyPoint = [0,0];
|
||||
if (position[0] !== -1) {
|
||||
this.rallyPoint[0] = position[0];
|
||||
this.rallyPoint[1] = position[1];
|
||||
} else {
|
||||
this.rallyPoint[0] = CCpos[0];
|
||||
this.rallyPoint[1] = CCpos[1];
|
||||
}
|
||||
if (type == 'harass_raid')
|
||||
|
||||
var myFortresses = gameState.getOwnTrainingFacilities().filter(Filters.byClass("GarrisonFortress"));
|
||||
if (myFortresses.length !== 0)
|
||||
{
|
||||
this.rallyPoint[0] = (position[0]*3.9 + 0.1 * CCpos[0]) / 4.0;
|
||||
this.rallyPoint[1] = (position[1]*3.9 + 0.1 * CCpos[1]) / 4.0;
|
||||
// make this our rallypoint
|
||||
for (i in myFortresses._entities)
|
||||
{
|
||||
this.rallyPoint = myFortresses._entities[i].position();
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
|
||||
if(gameState.ai.pathsToMe.length > 1)
|
||||
var position = [(gameState.ai.pathsToMe[0][0]+gameState.ai.pathsToMe[1][0])/2.0,(gameState.ai.pathsToMe[0][1]+gameState.ai.pathsToMe[1][1])/2.0];
|
||||
else if (gameState.ai.pathsToMe.length !== 0)
|
||||
var position = [gameState.ai.pathsToMe[0][0],gameState.ai.pathsToMe[0][1]];
|
||||
else
|
||||
var position = [-1,-1];
|
||||
|
||||
if (gameState.ai.accessibility.getAccessValue(position) !== gameState.ai.myIndex)
|
||||
var position = [-1,-1];
|
||||
|
||||
var nearestCCArray = CCs.filterNearest(position, 1).toEntityArray();
|
||||
var CCpos = nearestCCArray[0].position();
|
||||
this.rallyPoint = [0,0];
|
||||
if (position[0] !== -1) {
|
||||
this.rallyPoint[0] = position[0];
|
||||
this.rallyPoint[1] = position[1];
|
||||
} else {
|
||||
this.rallyPoint[0] = CCpos[0];
|
||||
this.rallyPoint[1] = CCpos[1];
|
||||
}
|
||||
if (type == 'harass_raid')
|
||||
{
|
||||
this.rallyPoint[0] = (position[0]*3.9 + 0.1 * CCpos[0]) / 4.0;
|
||||
this.rallyPoint[1] = (position[1]*3.9 + 0.1 * CCpos[1]) / 4.0;
|
||||
}
|
||||
}
|
||||
|
||||
// some variables for during the attack
|
||||
|
|
@ -366,6 +383,22 @@ CityAttack.prototype.updatePreparation = function(gameState, militaryManager,eve
|
|||
this.pathSampling = 3;
|
||||
this.path = this.path[0].reverse();
|
||||
delete this.pathFinder;
|
||||
// Change the rally point to something useful (should avoid rams getting stuck in houses in my territory, which is dumb.)
|
||||
for (var i = 0; i < this.path.length; ++i)
|
||||
{
|
||||
// my pathfinder returns arrays in arrays in arrays.
|
||||
var waypointPos = this.path[i][0];
|
||||
var territory = Map.createTerritoryMap(gameState);
|
||||
if (territory.getOwner(waypointPos) !== PlayerID || this.path[i][1] === true)
|
||||
{
|
||||
// if we're suddenly out of our territory or this is the point where we change transportation method.
|
||||
if (i !== 0)
|
||||
this.rallyPoint = this.path[i-1][0];
|
||||
else
|
||||
this.rallyPoint = this.path[0][0];
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (this.path[1] === true && this.pathWidth == 6) {
|
||||
// retry with a smaller pathwidth:
|
||||
this.pathWidth = 2;
|
||||
|
|
@ -373,6 +406,23 @@ CityAttack.prototype.updatePreparation = function(gameState, militaryManager,eve
|
|||
} else {
|
||||
this.path = this.path[0].reverse();
|
||||
delete this.pathFinder;
|
||||
|
||||
// Change the rally point to something useful (should avoid rams getting stuck in houses in my territory, which is dumb.)
|
||||
for (var i = 0; i < this.path.length; ++i)
|
||||
{
|
||||
// my pathfinder returns arrays in arrays in arrays.
|
||||
var waypointPos = this.path[i][0];
|
||||
var territory = Map.createTerritoryMap(gameState);
|
||||
if (territory.getOwner(waypointPos) !== PlayerID || this.path[i][1] === true)
|
||||
{
|
||||
// if we're suddenly out of our territory or this is the point where we change transportation method.
|
||||
if (i !== 0)
|
||||
this.rallyPoint = this.path[i-1][0];
|
||||
else
|
||||
this.rallyPoint = this.path[0][0];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -419,13 +469,11 @@ CityAttack.prototype.updatePreparation = function(gameState, militaryManager,eve
|
|||
return (a[0]) - (b[0]);
|
||||
});
|
||||
|
||||
if (!this.isPaused()) {
|
||||
this.assignUnits(gameState);
|
||||
this.assignUnits(gameState);
|
||||
|
||||
if (gameState.ai.playedTurn % 5 == 0) {
|
||||
this.AllToRallyPoint(gameState, false);
|
||||
this.unitCollection.setStance("standground"); // make sure units won't disperse out of control
|
||||
}
|
||||
if (gameState.ai.playedTurn % 5 == 0) {
|
||||
this.AllToRallyPoint(gameState, false);
|
||||
this.unitCollection.setStance("standground"); // make sure units won't disperse out of control
|
||||
}
|
||||
|
||||
Engine.ProfileStart("Creating units.");
|
||||
|
|
@ -620,6 +668,9 @@ CityAttack.prototype.StartAttack = function(gameState, militaryManager){
|
|||
var curPos = this.unitCollection.getCentrePosition();
|
||||
|
||||
this.unitCollection.forEach(function(ent) { ent.setMetadata(PlayerID, "subrole", "walking"); ent.setMetadata(PlayerID, "role", "attack") ;});
|
||||
|
||||
this.unitCollectionNoWarship = this.unitCollection.filter(Filters.not(Filters.byClass("Warship")));
|
||||
this.unitCollectionNoWarship.registerUpdates();
|
||||
|
||||
this.unitCollection.move(this.path[0][0][0], this.path[0][0][1]);
|
||||
this.unitCollection.setStance("aggressive"); // make sure units won't disperse out of control
|
||||
|
|
@ -754,7 +805,7 @@ CityAttack.prototype.update = function(gameState, militaryManager, events){
|
|||
}
|
||||
if (this.state === "walking"){
|
||||
|
||||
this.position = this.unitCollection.filter(Filters.not(Filters.byClass("Warship"))).getCentrePosition();
|
||||
this.position = this.unitCollectionNoWarship.getCentrePosition();
|
||||
|
||||
// probably not too good.
|
||||
if (!this.position)
|
||||
|
|
@ -781,7 +832,7 @@ CityAttack.prototype.update = function(gameState, militaryManager, events){
|
|||
this.position5TurnsAgo = this.position;
|
||||
|
||||
if (this.lastPosition && SquareVectorDistance(this.position, this.lastPosition) < 20 && this.path.length > 0) {
|
||||
this.unitCollection.filter(Filters.not(Filters.byClass("Warship"))).moveIndiv(this.path[0][0][0], this.path[0][0][1]);
|
||||
this.unitCollectionNoWarship.moveIndiv(this.path[0][0][0], this.path[0][0][1]);
|
||||
// We're stuck, presumably. Check if there are no walls just close to us. If so, we're arrived, and we're gonna tear down some serious stone.
|
||||
var walls = gameState.getEnemyEntities().filter(Filters.and(Filters.byOwner(this.targetPlayer), Filters.byClass("StoneWall")));
|
||||
var nexttoWalls = false;
|
||||
|
|
@ -802,10 +853,10 @@ CityAttack.prototype.update = function(gameState, militaryManager, events){
|
|||
}
|
||||
|
||||
// check if our land units are close enough from the next waypoint.
|
||||
if (SquareVectorDistance(this.unitCollection.filter(Filters.not(Filters.byClass("Warship"))).getCentrePosition(), this.targetPos) < 7500 ||
|
||||
SquareVectorDistance(this.unitCollection.filter(Filters.not(Filters.byClass("Warship"))).getCentrePosition(), this.path[0][0]) < 850) {
|
||||
if (SquareVectorDistance(this.unitCollectionNoWarship.getCentrePosition(), this.targetPos) < 7500 ||
|
||||
SquareVectorDistance(this.unitCollectionNoWarship.getCentrePosition(), this.path[0][0]) < 850) {
|
||||
if (this.unitCollection.filter(Filters.byClass("Siege")).length !== 0
|
||||
&& SquareVectorDistance(this.unitCollection.filter(Filters.not(Filters.byClass("Warship"))).getCentrePosition(), this.targetPos) > 7500
|
||||
&& SquareVectorDistance(this.unitCollectionNoWarship.getCentrePosition(), this.targetPos) > 7500
|
||||
&& SquareVectorDistance(this.unitCollection.filter(Filters.byClass("Siege")).getCentrePosition(), this.path[0][0]) > 850)
|
||||
{
|
||||
} else {
|
||||
|
|
@ -815,7 +866,7 @@ CityAttack.prototype.update = function(gameState, militaryManager, events){
|
|||
{
|
||||
this.path.shift();
|
||||
if (this.path.length > 0){
|
||||
this.unitCollection.filter(Filters.not(Filters.byClass("Warship"))).moveIndiv(this.path[0][0][0], this.path[0][0][1]);
|
||||
this.unitCollectionNoWarship.moveIndiv(this.path[0][0][0], this.path[0][0][1]);
|
||||
} else {
|
||||
debug ("Attack Plan " +this.type +" " +this.name +" has arrived to destination.");
|
||||
// we must assume we've arrived at the end of the trail.
|
||||
|
|
@ -861,23 +912,23 @@ CityAttack.prototype.update = function(gameState, militaryManager, events){
|
|||
}
|
||||
}
|
||||
} else if (this.state === "boarding") {
|
||||
this.position = this.unitCollection.filter(Filters.not(Filters.byClass("Warship"))).getCentrePosition();
|
||||
this.position = this.unitCollectionNoWarship.getCentrePosition();
|
||||
|
||||
var ships = this.unitCollection.filter(Filters.byClass("Warship"));
|
||||
if (ships.length === 0)
|
||||
return 0; // abort
|
||||
|
||||
var globalPos = this.unitCollection.filter(Filters.not(Filters.byClass("Warship"))).getCentrePosition();
|
||||
var globalPos = this.unitCollectionNoWarship.getCentrePosition();
|
||||
var shipPos = ships.getCentrePosition();
|
||||
|
||||
if (globalPos !== undefined && SquareVectorDistance(globalPos,shipPos) > 800)
|
||||
{ // get them closer
|
||||
ships.moveIndiv(globalPos[0],globalPos[1]);
|
||||
this.unitCollection.filter(Filters.not(Filters.byClass("Warship"))).moveIndiv(shipPos[0],shipPos[1]);
|
||||
this.unitCollectionNoWarship.moveIndiv(shipPos[0],shipPos[1]);
|
||||
} else {
|
||||
// okay try to garrison.
|
||||
var shipsArray = ships.toEntityArray();
|
||||
this.unitCollection.filter(Filters.not(Filters.byClass("Warship"))).forEach(function (ent) { //}){
|
||||
this.unitCollectionNoWarship.forEach(function (ent) { //}){
|
||||
if (ent.position()) // if we're not garrisoned
|
||||
for (var shipId = 0; shipId < shipsArray.length; shipId++) {
|
||||
if (shipsArray[shipId].garrisoned().length < shipsArray[shipId].garrisonMax())
|
||||
|
|
@ -891,7 +942,7 @@ CityAttack.prototype.update = function(gameState, militaryManager, events){
|
|||
for (var shipId = 0; shipId < shipsArray.length; shipId++)
|
||||
garrLength += shipsArray[shipId].garrisoned().length;
|
||||
|
||||
if (garrLength == this.unitCollection.filter(Filters.not(Filters.byClass("Warship"))).length) {
|
||||
if (garrLength == this.unitCollectionNoWarship.length) {
|
||||
// okay.
|
||||
this.path.shift();
|
||||
if (this.path.length > 0){
|
||||
|
|
|
|||
|
|
@ -1,17 +1,14 @@
|
|||
// Baseconfig is the highest difficulty.
|
||||
var baseConfig = {
|
||||
"Military" : {
|
||||
"fortressStartTime" : 780, // Time to wait before building one fortress.
|
||||
"fortressLapseTime" : 540, // Time to wait between building 2 fortresses
|
||||
"defenceBuildingTime" : 600, // Time to wait before building towers or fortresses
|
||||
"advancedMilitaryStartTime" : 720, // Time to wait before building advanced military buildings. Also limited by phase 2.
|
||||
"attackPlansStartTime" : 0 // time to wait before attacking. Start as soon as possible (first barracks)
|
||||
},
|
||||
"Economy" : {
|
||||
"townPhase" : 180, // time to start trying to reach town phase (might be a while after. Still need the requirements + ress )
|
||||
"cityPhase" : 540, // time to start trying to reach city phase
|
||||
"farmsteadStartTime" : 400, // Time to wait before building a farmstead.
|
||||
"marketStartTime" : 480, // Time to wait before building the market.
|
||||
"dockStartTime" : 240, // Time to wait before building the dock
|
||||
"techStartTime" : 600, // time to wait before teching.
|
||||
"targetNumBuilders" : 1.5, // Base number of builders per foundation. Later updated, but this remains a multiplier.
|
||||
|
|
@ -52,76 +49,75 @@ var baseConfig = {
|
|||
|
||||
// qbot
|
||||
"priorities" : { // Note these are dynamic, you are only setting the initial values
|
||||
"house" : 250,
|
||||
"citizenSoldier" : 60,
|
||||
"villager" : 55,
|
||||
"house" : 200,
|
||||
"citizenSoldier" : 65,
|
||||
"villager" : 60,
|
||||
"economicBuilding" : 70,
|
||||
"dropsites" : 120,
|
||||
"field" : 1000,
|
||||
"militaryBuilding" : 90,
|
||||
"defenceBuilding" : 70,
|
||||
"majorTech" : 270,
|
||||
"majorTech" : 400,
|
||||
"minorTech" : 40,
|
||||
"civilCentre" : 10000 // will hog all resources
|
||||
},
|
||||
"difficulty" : 2, // for now 2 is "hard", ie default. 1 is normal, 0 is easy.
|
||||
"difficulty" : 2, // for now 2 is "hard", ie default. 1 is normal, 0 is easy. 3 is very hard
|
||||
"debug" : false
|
||||
};
|
||||
|
||||
var Config = {
|
||||
"debug": true,
|
||||
"difficulty" : 2
|
||||
"debug": false,
|
||||
"difficulty" : 2, // overriden by the GUI
|
||||
updateDifficulty: function(difficulty)
|
||||
{
|
||||
Config.difficulty = difficulty;
|
||||
// changing settings based on difficulty.
|
||||
if (Config.difficulty === 1)
|
||||
{
|
||||
Config["Military"] = {
|
||||
"fortressLapseTime" : 900,
|
||||
"defenceBuildingTime" : 720,
|
||||
"attackPlansStartTime" : 600
|
||||
};
|
||||
Config["Economy"] = {
|
||||
"townPhase" : 360,
|
||||
"cityPhase" : 900,
|
||||
"farmsteadStartTime" : 600,
|
||||
"dockStartTime" : 240,
|
||||
"techStartTime" : 1320,
|
||||
"targetNumBuilders" : 2,
|
||||
"femaleRatio" : 0.5,
|
||||
"targetNumWorkers" : 110 // should not make more than 2 barracks.
|
||||
};
|
||||
Config["Defence"] = {
|
||||
"defenceRatio" : 4.0,
|
||||
"armyCompactSize" : 700,
|
||||
"armyBreakawaySize" : 900
|
||||
};
|
||||
} else if (Config.difficulty === 0)
|
||||
{
|
||||
Config["Military"] = {
|
||||
"fortressLapseTime" : 1000000, // never
|
||||
"defenceBuildingTime" : 900,
|
||||
"attackPlansStartTime" : 1200 // 20 minutes ought to give enough times for beginners
|
||||
};
|
||||
Config["Economy"] = {
|
||||
"townPhase" : 480,
|
||||
"cityPhase" : 1200,
|
||||
"farmsteadStartTime" : 1200,
|
||||
"dockStartTime" : 240,
|
||||
"techStartTime" : 600000, // never
|
||||
"targetNumBuilders" : 1,
|
||||
"femaleRatio" : 0.0, // makes us slower, but also less sucky at defending so it's still fun to attack it.
|
||||
"targetNumWorkers" : 80 // we will make advanced buildings and a fortress (and a market), but nothing more.
|
||||
};
|
||||
Config["Defence"] = {
|
||||
"defenceRatio" : 2.0,
|
||||
"armyCompactSize" : 700,
|
||||
"armyBreakawaySize" : 900
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Config.__proto__ = baseConfig;
|
||||
|
||||
// changing settings based on difficulty.
|
||||
|
||||
if (Config.difficulty === 1)
|
||||
{
|
||||
Config["Military"] = {
|
||||
"fortressStartTime" : 1000,
|
||||
"fortressLapseTime" : 900,
|
||||
"defenceBuildingTime" : 720,
|
||||
"advancedMilitaryStartTime" : 1000,
|
||||
"attackPlansStartTime" : 600
|
||||
};
|
||||
Config["Economy"] = {
|
||||
"townPhase" : 360,
|
||||
"cityPhase" : 900,
|
||||
"farmsteadStartTime" : 600,
|
||||
"marketStartTime" : 800,
|
||||
"techStartTime" : 1320,
|
||||
"targetNumBuilders" : 2,
|
||||
"femaleRatio" : 0.5,
|
||||
"targetNumWorkers" : 100
|
||||
};
|
||||
Config["Defence"] = {
|
||||
"armyCompactSize" : 700,
|
||||
"armyBreakawaySize" : 900
|
||||
};
|
||||
} else if (Config.difficulty === 0)
|
||||
{
|
||||
Config["Military"] = {
|
||||
"fortressStartTime" : 1500,
|
||||
"fortressLapseTime" : 1000000,
|
||||
"defenceBuildingTime" : 900,
|
||||
"advancedMilitaryStartTime" : 1300,
|
||||
"attackPlansStartTime" : 1200 // 20 minutes ought to give enough times for beginners
|
||||
};
|
||||
Config["Economy"] = {
|
||||
"townPhase" : 480,
|
||||
"cityPhase" : 1200,
|
||||
"farmsteadStartTime" : 1200,
|
||||
"marketStartTime" : 1000,
|
||||
"techStartTime" : 600000, // never
|
||||
"targetNumBuilders" : 1,
|
||||
"femaleRatio" : 0.0,
|
||||
"targetNumWorkers" : 50
|
||||
};
|
||||
Config["Defence"] = {
|
||||
"defenceRatio" : 2.0,
|
||||
"armyCompactSize" : 700,
|
||||
"armyBreakawaySize" : 900
|
||||
};
|
||||
}
|
||||
Config.__proto__ = baseConfig;
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "Aegis Bot",
|
||||
"description": "An WIP AI improving upon qBot.\nThis bot is stronger than qBot, use the former if you're a beginner.\nPlease report any problems on the Wildfire forums.",
|
||||
"description": "Wraitii's improvement of qBot.\nIt is more reliable and is generally a better player.\nNote that Aegis is still very much a work in progress, so you may run into issues or bugs.\nPlease report those to the Wildfire Games forums.",
|
||||
"constructor": "QBotAI",
|
||||
"useShared": true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -433,6 +433,9 @@ Defence.prototype.defendFromEnemies = function(gameState, events, militaryManage
|
|||
});
|
||||
}
|
||||
|
||||
if (newEnemies.length === 0)
|
||||
return;
|
||||
|
||||
var nonDefenders = this.myUnits.filter(Filters.or(Filters.not(Filters.byMetadata(PlayerID, "role","defence")),Filters.isIdle()));
|
||||
nonDefenders = nonDefenders.filter(Filters.not(Filters.byClass("Female")));
|
||||
nonDefenders = nonDefenders.filter(Filters.not(Filters.byMetadata(PlayerID, "subrole","attacking")));
|
||||
|
|
@ -441,10 +444,10 @@ Defence.prototype.defendFromEnemies = function(gameState, events, militaryManage
|
|||
//debug ("newEnemies.length "+ newEnemies.length);
|
||||
//debug ("nonDefenders.length "+ nonDefenders.length);
|
||||
|
||||
if (newEnemies.length > nonDefenders.length * 1.2 && this.nbAttackers > 5)
|
||||
if (newEnemies.length + this.nbAttackers > (this.nbDefenders + nonDefenders.length) * 0.8 && this.nbAttackers > 9)
|
||||
gameState.setDefcon(2);
|
||||
|
||||
if (nonDefenders.length * 2.0 < newEnemies.length && this.nbAttackers > 5)
|
||||
if (newEnemies.length + this.nbAttackers > (this.nbDefenders + nonDefenders.length) * 1.5 && this.nbAttackers > 5)
|
||||
gameState.setDefcon(1);
|
||||
|
||||
if (gameState.defcon() > 3)
|
||||
|
|
@ -468,6 +471,21 @@ Defence.prototype.defendFromEnemies = function(gameState, events, militaryManage
|
|||
militaryManager.ungarrisonAll(gameState);
|
||||
}*/
|
||||
|
||||
// A little sorting to target sieges first.
|
||||
newEnemies.sort (function (a,b) {
|
||||
var vala = 1;
|
||||
var valb = 1;
|
||||
if (a.hasClass("Siege"))
|
||||
vala = 10;
|
||||
else if (a.hasClass("Champion") || a.hasClass("Hero"))
|
||||
vala = 5;
|
||||
if (b.hasClass("Siege"))
|
||||
valb = 10;
|
||||
else if (b.hasClass("Champion") || b.hasClass("Hero"))
|
||||
valb = 5;
|
||||
return valb - vala;
|
||||
});
|
||||
|
||||
// For each enemy, we'll pick two units.
|
||||
for each (enemy in newEnemies) {
|
||||
if (nonDefenders.length === 0 || self.nbDefenders >= self.nbAttackers * 1.8)
|
||||
|
|
@ -480,7 +498,7 @@ Defence.prototype.defendFromEnemies = function(gameState, events, militaryManage
|
|||
|
||||
var defRatio = defenceRatio;
|
||||
if (enemy.hasClass("Siege"))
|
||||
defRatio *= 1.6;
|
||||
defRatio *= 1.2;
|
||||
|
||||
if (assigned >= defRatio)
|
||||
return;
|
||||
|
|
@ -492,16 +510,21 @@ Defence.prototype.defendFromEnemies = function(gameState, events, militaryManage
|
|||
for (var id in nonDefenders._entities)
|
||||
{
|
||||
var ent = nonDefenders._entities[id];
|
||||
if (ent.position() && !ent.hasClass("Siege"))
|
||||
if (ent.position())
|
||||
data.push([id, ent, SquareVectorDistance(enemy.position(), ent.position())]);
|
||||
}
|
||||
// refine the defenders we want.
|
||||
data.sort(function (a, b) {
|
||||
var vala = a[2];
|
||||
var valb = b[2];
|
||||
if (a[1].hasClass("Ranged") && enemy.hasClass("Siege"))
|
||||
vala *= 5;
|
||||
if (b[1].hasClass("Ranged") && enemy.hasClass("Siege"))
|
||||
valb *= 5;
|
||||
if (a[1].hasClass("Siege") && !enemy.hasClass("Siege"))
|
||||
vala *= 4;
|
||||
if (b[1].hasClass("Siege") && !enemy.hasClass("Siege"))
|
||||
valb *= 4;
|
||||
if (enemy.hasClass("Siege") && a[1].attackStrengths("Melee") !== undefined)
|
||||
vala /= (a[1].attackStrengths("Melee")["hack"] + a[1].attackStrengths("Melee")["crush"]);
|
||||
if (enemy.hasClass("Siege") && b[1].attackStrengths("Melee") !== undefined)
|
||||
valb /= (b[1].attackStrengths("Melee")["hack"] + b[1].attackStrengths("Melee")["crush"]);
|
||||
if (a[1].countersClasses(b[1].classes()))
|
||||
vala *= 0.8;
|
||||
if (b[1].countersClasses(a[1].classes()))
|
||||
|
|
@ -520,7 +543,7 @@ Defence.prototype.defendFromEnemies = function(gameState, events, militaryManage
|
|||
|
||||
// successfully sorted
|
||||
defs.forEach(function (defender) { //}){
|
||||
if (defender.getMetadata(PlayerID, "plan") != undefined && gameState.defcon() < 3)
|
||||
if (defender.getMetadata(PlayerID, "plan") != undefined && gameState.defcon() < 4)
|
||||
militaryManager.pausePlan(gameState, defender.getMetadata(PlayerID, "plan"));
|
||||
//debug ("Against " +enemy.id() + " Assigning " + defender.id());
|
||||
if (defender.getMetadata(PlayerID, "role") == "worker" || defender.getMetadata(PlayerID, "role") == "attack")
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ var EconomyManager = function() {
|
|||
// Used by the QueueManager to determine future needs.
|
||||
this.baseNeed = {};
|
||||
this.baseNeed["food"] = 150;
|
||||
this.baseNeed["wood"] = 80;
|
||||
this.baseNeed["wood"] = 100;
|
||||
this.baseNeed["stone"] = 0;
|
||||
this.baseNeed["metal"] = 0;
|
||||
|
||||
|
|
@ -17,7 +17,6 @@ var EconomyManager = function() {
|
|||
this.lastStatG = { "food" : 0, "wood" : 0, "stone" : 0, "metal" : 0}; // resource collecting stats: gathered
|
||||
this.lastStatU = { "food" : 0, "wood" : 0, "stone" : 0, "metal" : 0}; // resource collecting stats: used
|
||||
|
||||
this.marketStartTime = Config.Economy.marketStartTime * 1000;
|
||||
this.dockStartTime = Config.Economy.dockStartTime * 1000;
|
||||
this.farmsteadStartTime = Config.Economy.farmsteadStartTime * 1000;
|
||||
this.techStartTime = Config.Economy.techStartTime * 1000;
|
||||
|
|
@ -116,22 +115,28 @@ EconomyManager.prototype.trainMoreWorkers = function(gameState, queues) {
|
|||
var numTotal = numWorkers + numQueued;
|
||||
|
||||
if (gameState.currentPhase() > 1 || gameState.isResearching("phase_town"))
|
||||
this.targetNumFields = numFemales/23;
|
||||
this.targetNumFields = numWorkers/25;
|
||||
else
|
||||
this.targetNumFields = 1;
|
||||
|
||||
// ought to refine this.
|
||||
if ((gameState.ai.playedTurn+2) % 3 === 0) {
|
||||
this.dropsiteNumbers = {"wood": Math.ceil((numWorkers)/18)/2, "stone": Math.ceil((numWorkers)/30)/2, "metal": Math.ceil((numWorkers)/20)/2};
|
||||
this.dropsiteNumbers = {"wood": Math.ceil(numWorkers/25)/2, "stone": Math.ceil(numWorkers/30)/2, "metal": Math.ceil(numWorkers/20)/2};
|
||||
if (numWorkers < 30)
|
||||
{
|
||||
this.dropsiteNumbers["wood"] -= 0.5;
|
||||
this.dropsiteNumbers["metal"] -= 0.5;
|
||||
this.dropsiteNumbers["stone"] -= 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//debug (numTotal + "/" +this.targetNumWorkers + ", " +numFemales +"/" +numTotal);
|
||||
|
||||
// If we have too few, train more
|
||||
// should plan enough to always have females…
|
||||
if (numTotal < this.targetNumWorkers && numQueued < 2 && (numQueued+numInTraining) < 15) {
|
||||
if (numTotal < this.targetNumWorkers && numQueued < 15 && ((queues.villager.length() < 2 && queues.citizenSoldier.length() < 2) || gameState.currentPhase() !== 1) && (numInTraining) < 15) {
|
||||
var template = gameState.applyCiv("units/{civ}_support_female_citizen");
|
||||
var size = Math.min(Math.ceil(gameState.getTimeElapsed() / 30000),5);
|
||||
var size = Math.min(Math.ceil(gameState.getTimeElapsed() / 20000),5);
|
||||
if (numFemales/numTotal > this.femaleRatio && (gameState.getTimeElapsed() > 60*1000 || this.fastStart)) {
|
||||
if (numTotal < 100)
|
||||
template = this.findBestTrainableUnit(gameState, ["CitizenSoldier", "Infantry"], [ ["cost",1], ["speed",0.5], ["costsResource", 0.5, "stone"], ["costsResource", 0.5, "metal"]]);
|
||||
|
|
@ -140,10 +145,10 @@ EconomyManager.prototype.trainMoreWorkers = function(gameState, queues) {
|
|||
if (!template)
|
||||
template = gameState.applyCiv("units/{civ}_support_female_citizen");
|
||||
else
|
||||
size = Math.min(Math.ceil(gameState.getTimeElapsed() / 90000),5);
|
||||
size = Math.min(Math.ceil(gameState.getTimeElapsed() / 60000),5);
|
||||
}
|
||||
|
||||
if ((gameState.getTimeElapsed() < 60000 && gameState.ai.queueManager.getAvailableResources(gameState, true)["food"] > 250) || this.fastStart)
|
||||
if ((gameState.getTimeElapsed() < 60000 && gameState.ai.queueManager.getAvailableResources(gameState)["food"] > 250) || this.fastStart)
|
||||
size = 5;
|
||||
|
||||
if (template === gameState.applyCiv("units/{civ}_support_female_citizen"))
|
||||
|
|
@ -356,7 +361,7 @@ EconomyManager.prototype.assignToFoundations = function(gameState, noRepair) {
|
|||
|
||||
if (assigned < targetNB) {
|
||||
if (builderWorkers.length + addedWorkers < this.targetNumBuilders*Math.min(4.0,gameState.getTimeElapsed()/30000)) {
|
||||
var nonBuilderWorkers = workers.filter(function(ent) { return (ent.getMetadata(PlayerID, "subrole") !== "builder" && ent.getMetadata(PlayerID, "gather-type") !== "food" && ent.position() !== undefined); });
|
||||
var nonBuilderWorkers = workers.filter(function(ent) { return (ent.getMetadata(PlayerID, "subrole") !== "builder" && ent.position() !== undefined); });
|
||||
var nearestNonBuilders = nonBuilderWorkers.filterNearest(target.position(), targetNB - assigned);
|
||||
|
||||
nearestNonBuilders.forEach(function(ent) {
|
||||
|
|
@ -417,7 +422,7 @@ EconomyManager.prototype.buildMoreFields = function(gameState, queues) {
|
|||
var numFarms = gameState.countEntitiesAndQueuedByType(gameState.applyCiv("structures/{civ}_field"));
|
||||
numFarms += queues.field.countTotalQueuedUnits();
|
||||
|
||||
if (numFarms < this.targetNumFields + Math.floor(gameState.getTimeElapsed() / 750000))
|
||||
if (numFarms < this.targetNumFields)
|
||||
queues.field.addItem(new BuildingConstructionPlan(gameState, "structures/{civ}_field"));
|
||||
} else {
|
||||
var foodAmount = 0;
|
||||
|
|
@ -464,10 +469,10 @@ EconomyManager.prototype.updateResourceMaps = function(gameState, events) {
|
|||
var radius = {'wood':10.0, 'stone': 24.0, 'metal': 24.0, 'food': 24.0};
|
||||
|
||||
// smallRadius is the distance necessary to mark a resource as linked to a dropsite.
|
||||
var smallRadius = { 'food':90*90,'wood':40*40,'stone':60*60,'metal':60*60 };
|
||||
var smallRadius = { 'food':90*90,'wood':55*55,'stone':70*70,'metal':70*70 };
|
||||
// bigRadius is the distance for a weak link (resources are considered when building other dropsites)
|
||||
// and their resource amount is divided by 3 when checking for dropsite resource level.
|
||||
var bigRadius = { 'food':110*110,'wood':100*100,'stone':140*140,'metal':140*140 };
|
||||
var bigRadius = { 'food':100*100,'wood':100*100,'stone':140*140,'metal':140*140 };
|
||||
|
||||
var self = this;
|
||||
|
||||
|
|
@ -763,7 +768,7 @@ EconomyManager.prototype.updateResourceConcentrations = function(gameState, reso
|
|||
if (dropsite.getMetadata(PlayerID, "linked-resources-" + resource) == undefined)
|
||||
return;
|
||||
dropsite.getMetadata(PlayerID, "linked-resources-" + resource).forEach(function(supply){ //}){
|
||||
if (supply.getMetadata(PlayerID, "full") == true || supply.getMetadata(PlayerID, "inaccessible") == true)
|
||||
if (supply.isFull() === true || supply.getMetadata(PlayerID, "inaccessible") == true)
|
||||
return;
|
||||
|
||||
if (supply.getMetadata(PlayerID, "linked-dropsite-nearby") == true)
|
||||
|
|
@ -791,10 +796,10 @@ EconomyManager.prototype.updateNearbyResources = function(gameState,resource){
|
|||
var radius = {'wood':10.0, 'stone': 24.0, 'metal': 24.0, 'food': 24.0};
|
||||
|
||||
// smallRadius is the distance necessary to mark a resource as linked to a dropsite.
|
||||
var smallRadius = { 'food':90*90,'wood':60*60,'stone':70*70,'metal':70*70 };
|
||||
var smallRadius = { 'food':90*90,'wood':55*55,'stone':70*70,'metal':70*70 };
|
||||
// bigRadius is the distance for a weak link (resources are considered when building other dropsites)
|
||||
// and their resource amount is divided by 3 when checking for dropsite resource level.
|
||||
var bigRadius = { 'food':110*110,'wood':140*140,'stone':140*140,'metal':140*140 };
|
||||
var bigRadius = { 'food':100*100,'wood':100*100,'stone':140*140,'metal':140*140 };
|
||||
|
||||
gameState.getOwnDropsites(resource).forEach(function(ent) { //}){
|
||||
|
||||
|
|
@ -984,8 +989,8 @@ EconomyManager.prototype.buildDropsites = function(gameState, queues){
|
|||
gameState.countFoundationsWithType(gameState.applyCiv("structures/{civ}_civil_centre")) === 0){
|
||||
//only ever build one mill/CC/market at a time
|
||||
if (gameState.getTimeElapsed() > 30 * 1000){
|
||||
var built = false;
|
||||
for (var resource in this.dropsiteNumbers){
|
||||
|
||||
if (this.checkResourceConcentrations(gameState, resource) < this.dropsiteNumbers[resource]){
|
||||
var spot = this.getBestResourceBuildSpot(gameState, resource);
|
||||
if (spot[1][0] === -1)
|
||||
|
|
@ -995,9 +1000,25 @@ EconomyManager.prototype.buildDropsites = function(gameState, queues){
|
|||
} else {
|
||||
queues.dropsites.addItem(new BuildingConstructionPlan(gameState, "structures/{civ}_mill", spot[1]));
|
||||
}
|
||||
built = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!built)
|
||||
for (var resource in this.dropsiteNumbers){
|
||||
if (this.checkResourceConcentrations(gameState, resource) < Math.ceil(this.dropsiteNumbers[resource])){
|
||||
var spot = this.getBestResourceBuildSpot(gameState, resource);
|
||||
if (spot[1][0] === -1)
|
||||
break;
|
||||
if (spot[0] === true){
|
||||
queues.dropsites.addItem(new BuildingConstructionPlan(gameState, "structures/{civ}_civil_centre", spot[1]));
|
||||
} else {
|
||||
queues.dropsites.addItem(new BuildingConstructionPlan(gameState, "structures/{civ}_mill", spot[1]));
|
||||
}
|
||||
built = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -1008,20 +1029,21 @@ EconomyManager.prototype.buildMoreHouses = function(gameState, queues) {
|
|||
|
||||
// temporary 'remaining population space' based check, need to do
|
||||
// predictive in future
|
||||
if (gameState.getPopulationLimit() - gameState.getPopulation() < 15
|
||||
if (gameState.getPopulationLimit() - gameState.getPopulation() < 20
|
||||
&& gameState.getPopulationLimit() < gameState.getPopulationMax()) {
|
||||
|
||||
var numConstructing = gameState.countEntitiesByType(gameState.applyCiv("foundation|structures/{civ}_house"));
|
||||
var numPlanned = queues.house.totalLength();
|
||||
|
||||
if (gameState.getTimeElapsed() < 120000 && numConstructing + numPlanned !== 0)
|
||||
return;
|
||||
var additional = 0;
|
||||
if (gameState.civ() == "gaul" || gameState.civ() == "brit" || gameState.civ() == "iber")
|
||||
additional = Math.ceil((20 - (gameState.getPopulationLimit() - gameState.getPopulation())) / 5) - numConstructing - numPlanned;
|
||||
else
|
||||
additional = Math.ceil((20 - (gameState.getPopulationLimit() - gameState.getPopulation())) / 10) - numConstructing - numPlanned;
|
||||
|
||||
if (Config.difficulty === 3)
|
||||
additional *= 2; // we don't build enough otherwise.
|
||||
|
||||
for ( var i = 0; i < additional; i++) {
|
||||
queues.house.addItem(new BuildingConstructionPlan(gameState, "structures/{civ}_house"));
|
||||
}
|
||||
|
|
@ -1111,7 +1133,7 @@ EconomyManager.prototype.update = function(gameState, queues, events) {
|
|||
ent.getMetadata(PlayerID, "worker-object").update(gameState);
|
||||
});
|
||||
// Gatherer count updates for non-workers
|
||||
var filter = Filters.and(Filters.not(Filters.byMetadata(PlayerID, "worker-object", undefined)),
|
||||
/*var filter = Filters.and(Filters.not(Filters.byMetadata(PlayerID, "worker-object", undefined)),
|
||||
Filters.not(Filters.byMetadata(PlayerID, "role", "worker")));
|
||||
gameState.updatingCollection("reassigned-workers", filter, gameState.getOwnEntities()).forEach(function(ent){
|
||||
ent.getMetadata(PlayerID, "worker-object").updateGathererCounts(gameState);
|
||||
|
|
@ -1127,7 +1149,7 @@ EconomyManager.prototype.update = function(gameState, queues, events) {
|
|||
delete e.msg.metadata[PlayerID]["worker-object"];
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
Engine.ProfileStop();
|
||||
return;
|
||||
}
|
||||
|
|
@ -1160,11 +1182,11 @@ EconomyManager.prototype.update = function(gameState, queues, events) {
|
|||
else
|
||||
this.femaleRatio = Config.Economy.femaleRatio;
|
||||
|
||||
if (gameState.getTimeElapsed() > 600000 && this.numWorkers < 50)
|
||||
if (gameState.getTimeElapsed() > 600000 && this.numWorkers < 50 && Config.difficulty > 0)
|
||||
{
|
||||
gameState.ai.queueManager.changePriority("villager", 80);
|
||||
gameState.ai.queueManager.changePriority("citizenSoldier", 70);
|
||||
} else if (gameState.getTimeElapsed() > 600000 && this.numWorkers > 80
|
||||
} else if (gameState.getTimeElapsed() > 600000 && this.numWorkers > 80 && Config.difficulty > 0
|
||||
&& gameState.ai.queueManager.priorities["villager"] == 80)
|
||||
{
|
||||
gameState.ai.queueManager.changePriority("villager", Config.priorities.villager);
|
||||
|
|
@ -1177,16 +1199,21 @@ EconomyManager.prototype.update = function(gameState, queues, events) {
|
|||
this.baseNeed["wood"] = 100;
|
||||
this.baseNeed["stone"] = 80;
|
||||
this.baseNeed["metal"] = 50;
|
||||
if (gameState.civ() == "maur" || gameState.civ() == "brit" || gameState.civ() == "gaul")
|
||||
{
|
||||
this.baseNeed["wood"] = 120;
|
||||
this.baseNeed["stone"] = 60;
|
||||
}
|
||||
} else if (this.baseNeed["stone"] === 80 && (gameState.currentPhase() === 3 || gameState.isResearching("phase_city_generic")) )
|
||||
{
|
||||
// switch back to less stone but push metal.
|
||||
this.baseNeed["food"] = 80;
|
||||
this.baseNeed["wood"] = 80;
|
||||
this.baseNeed["stone"] = 60;
|
||||
this.baseNeed["metal"] = 70;
|
||||
this.baseNeed["stone"] = 45;
|
||||
this.baseNeed["metal"] = 65;
|
||||
}
|
||||
if (Config.difficulty === 2 && gameState.getTimeElapsed() > 900000 && gameState.ai.playedTurn % 60 === 10)
|
||||
this.rePrioritize(gameState);
|
||||
//if (Config.difficulty === 2 && gameState.getTimeElapsed() > 900000 && gameState.ai.playedTurn % 60 === 10)
|
||||
// this.rePrioritize(gameState);
|
||||
|
||||
Engine.ProfileStart("Update Resource Maps and Concentrations");
|
||||
this.updateResourceMaps(gameState, events);
|
||||
|
|
@ -1213,16 +1240,18 @@ EconomyManager.prototype.update = function(gameState, queues, events) {
|
|||
// this.buildTemple(gameState, queues);
|
||||
this.buildDock(gameState, queues); // not if not a water map.
|
||||
|
||||
if (gameState.ai.playedTurn % 20 === 0){
|
||||
if (gameState.ai.playedTurn % 10 === 0){
|
||||
this.setWorkersIdleByPriority(gameState);
|
||||
}
|
||||
|
||||
Engine.ProfileStart("Reassign Idle Workers");
|
||||
this.reassignIdleWorkers(gameState);
|
||||
Engine.ProfileStop();
|
||||
if (gameState.ai.playedTurn % 3 === 0)
|
||||
{
|
||||
Engine.ProfileStart("Reassign Idle Workers");
|
||||
this.reassignIdleWorkers(gameState);
|
||||
Engine.ProfileStop();
|
||||
}
|
||||
|
||||
// this is pretty slow, run it once in a while
|
||||
if (gameState.ai.playedTurn % 4 === 1) {
|
||||
if (gameState.ai.playedTurn % 6 === 1) {
|
||||
Engine.ProfileStart("Swap Workers");
|
||||
var gathererGroups = {};
|
||||
gameState.getOwnEntitiesByRole("worker").forEach(function(ent){
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ var MilitaryAttackManager = function() {
|
|||
this.fortressStartTime = 0;
|
||||
this.fortressLapseTime = Config.Military.fortressLapseTime * 1000;
|
||||
this.defenceBuildingTime = Config.Military.defenceBuildingTime * 1000;
|
||||
this.advancedMilitaryStartTime = Config.Military.advancedMilitaryStartTime * 1000;
|
||||
this.attackPlansStartTime = Config.Military.attackPlansStartTime * 1000;
|
||||
this.defenceManager = new Defence();
|
||||
|
||||
|
|
@ -463,7 +462,7 @@ MilitaryAttackManager.prototype.constructTrainingBuildings = function(gameState,
|
|||
}
|
||||
}
|
||||
//build advanced military buildings
|
||||
if (workersNumber > 75 && gameState.currentPhase() > 2){
|
||||
if (workersNumber >= 75 && gameState.currentPhase() > 2){
|
||||
if (queues.militaryBuilding.totalLength() === 0){
|
||||
var inConst = 0;
|
||||
for (var i in this.bAdvanced)
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ BuildingConstructionPlan.prototype.findGoodPosition = function(gameState) {
|
|||
} else if (template.hasClass("GarrisonFortress"))
|
||||
{
|
||||
friendlyTiles.addInfluence(x, z, 20, 10);
|
||||
friendlyTiles.addInfluence(x, z, 10, -40);
|
||||
friendlyTiles.addInfluence(x, z, 10, -40, 'linear');
|
||||
} else if (ent.genericName() != "House") // houses have no influence on other buildings
|
||||
{
|
||||
friendlyTiles.addInfluence(x, z, infl);
|
||||
|
|
@ -151,15 +151,13 @@ BuildingConstructionPlan.prototype.findGoodPosition = function(gameState) {
|
|||
if (template.genericName() == "Field")
|
||||
radius = Math.ceil(template.obstructionRadius() / cellSize) - 0.4;
|
||||
else if (template.hasClass("GarrisonFortress"))
|
||||
radius = Math.ceil(template.obstructionRadius() / cellSize) + 1;
|
||||
radius = Math.ceil(template.obstructionRadius() / cellSize) + 2;
|
||||
else if (template.buildCategory() === "Dock")
|
||||
radius = 1;//Math.floor(template.obstructionRadius() / cellSize);
|
||||
else if (template.genericName() != "House" && !template.hasClass("DropsiteWood") && !template.hasClass("DropsiteStone") && !template.hasClass("DropsiteMetal"))
|
||||
radius = Math.ceil(template.obstructionRadius() / cellSize + 0.5);
|
||||
else if (gameState.civ() === "iber" || gameState.civ() === "gaul" || gameState.civ() === "brit")
|
||||
radius = Math.ceil(template.obstructionRadius() / cellSize);
|
||||
else if (!template.hasClass("DropsiteWood") && !template.hasClass("DropsiteStone") && !template.hasClass("DropsiteMetal"))
|
||||
radius = Math.ceil(template.obstructionRadius() / cellSize + 1);
|
||||
else
|
||||
radius = Math.ceil(template.obstructionRadius() / cellSize + 0.2);
|
||||
radius = Math.ceil(template.obstructionRadius() / cellSize);
|
||||
|
||||
// further contract cause walls
|
||||
// Note: I'm currently destroying them so that doesn't matter.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
function QBotAI(settings) {
|
||||
BaseAI.call(this, settings);
|
||||
|
||||
Config.updateDifficulty(settings.difficulty);
|
||||
|
||||
this.turn = 0;
|
||||
|
||||
this.playedTurn = 0;
|
||||
|
|
@ -92,7 +94,7 @@ QBotAI.prototype.runInit = function(gameState, events){
|
|||
this.modules[i].init(gameState, events);
|
||||
}
|
||||
}
|
||||
debug ("inited");
|
||||
debug ("Inited, diff is " + Config.difficulty);
|
||||
this.timer = new Timer();
|
||||
|
||||
|
||||
|
|
@ -201,14 +203,18 @@ QBotAI.prototype.OnUpdate = function(sharedScript) {
|
|||
&& gameState.findResearchers("phase_town",true).length != 0 && this.queues.majorTech.totalLength() === 0) {
|
||||
this.queues.majorTech.addItem(new ResearchPlan(gameState, "phase_town",true)); // we rush the town phase.
|
||||
debug ("Trying to reach town phase");
|
||||
var nb = gameState.getOwnEntities().filter(Filters.byClass("Village")).length-1;
|
||||
if (nb < 5)
|
||||
{
|
||||
while (nb < 5 && ++nb)
|
||||
this.queues.house.addItem(new BuildingConstructionPlan(gameState, "structures/{civ}_house"));
|
||||
}
|
||||
} else if (gameState.canResearch("phase_city_generic",true) && gameState.getTimeElapsed() > (Config.Economy.cityPhase*1000)
|
||||
&& gameState.getOwnEntitiesByRole("worker").length > 85
|
||||
&& gameState.findResearchers("phase_city_generic", true).length != 0 && this.queues.majorTech.totalLength() === 0) {
|
||||
debug ("Trying to reach city phase");
|
||||
this.queues.majorTech.addItem(new ResearchPlan(gameState, "phase_city_generic"));
|
||||
}
|
||||
|
||||
|
||||
// defcon cooldown
|
||||
if (this.defcon < 5 && gameState.timeSinceDefconChange() > 20000)
|
||||
{
|
||||
|
|
@ -251,7 +257,7 @@ QBotAI.prototype.OnUpdate = function(sharedScript) {
|
|||
}*/
|
||||
|
||||
|
||||
//if (this.playedTurn % 15 === 0)
|
||||
//if (this.playedTurn % 5 === 0)
|
||||
// this.queueManager.printQueues(gameState);
|
||||
|
||||
// Generate some entropy in the random numbers (against humans) until the engine gets random initialised numbers
|
||||
|
|
|
|||
|
|
@ -36,10 +36,6 @@ var QueueManager = function(queues, priorities) {
|
|||
|
||||
QueueManager.prototype.getAvailableResources = function(gameState, noAccounts) {
|
||||
var resources = gameState.getResources();
|
||||
if (Config.difficulty == 1)
|
||||
resources.multiply(0.75);
|
||||
else if (Config.difficulty == 1)
|
||||
resources.multiply(0.5);
|
||||
if (noAccounts)
|
||||
return resources;
|
||||
for (var key in this.queues) {
|
||||
|
|
@ -49,66 +45,6 @@ QueueManager.prototype.getAvailableResources = function(gameState, noAccounts) {
|
|||
};
|
||||
|
||||
QueueManager.prototype.futureNeeds = function(gameState, EcoManager) {
|
||||
/*
|
||||
// Work out which plans will be executed next using priority and return the total cost of these plans
|
||||
var recurse = function(queues, qm, number, depth){
|
||||
var needs = new Resources();
|
||||
var totalPriority = 0;
|
||||
for (var i = 0; i < queues.length; i++){
|
||||
totalPriority += qm.priorities[queues[i]];
|
||||
}
|
||||
for (var i = 0; i < queues.length; i++){
|
||||
var num = Math.round(((qm.priorities[queues[i]]/totalPriority) * number));
|
||||
if (num < qm.queues[queues[i]].countQueuedUnits()){
|
||||
var cnt = 0;
|
||||
for ( var j = 0; cnt < num; j++) {
|
||||
cnt += qm.queues[queues[i]].queue[j].number;
|
||||
needs.add(qm.queues[queues[i]].queue[j].getCost());
|
||||
number -= qm.queues[queues[i]].queue[j].number;
|
||||
}
|
||||
}else{
|
||||
for ( var j = 0; j < qm.queues[queues[i]].length(); j++) {
|
||||
needs.add(qm.queues[queues[i]].queue[j].getCost());
|
||||
number -= qm.queues[queues[i]].queue[j].number;
|
||||
}
|
||||
queues.splice(i, 1);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
// Check that more items were selected this call and that there are plans left to be allocated
|
||||
// Also there is a fail-safe max depth
|
||||
if (queues.length > 0 && number > 0 && depth < 20){
|
||||
needs.add(recurse(queues, qm, number, depth + 1));
|
||||
}
|
||||
return needs;
|
||||
};
|
||||
|
||||
//number of plans to look at
|
||||
var current = this.getAvailableResources(gameState, true);
|
||||
|
||||
var futureNum = 20;
|
||||
var queues = [];
|
||||
for (var q in this.queues){
|
||||
queues.push(q);
|
||||
}
|
||||
var needs = recurse(queues, this, futureNum, 0);
|
||||
|
||||
if (EcoManager === false) {
|
||||
return {
|
||||
"food" : Math.max(needs.food - current.food, 0),
|
||||
"wood" : Math.max(needs.wood + 15*needs.population - current.wood, 0),
|
||||
"stone" : Math.max(needs.stone - current.stone, 0),
|
||||
"metal" : Math.max(needs.metal - current.metal, 0)
|
||||
};
|
||||
} else {
|
||||
// Return predicted values minus the current stockpiles along with a base rater for all resources
|
||||
return {
|
||||
"food" : Math.max(needs.food - current.food, 0) + EcoManager.baseNeed["food"],
|
||||
"wood" : Math.max(needs.wood + 15*needs.population - current.wood, 0) + EcoManager.baseNeed["wood"], //TODO: read the house cost in case it changes in the future
|
||||
"stone" : Math.max(needs.stone - current.stone, 0) + EcoManager.baseNeed["stone"],
|
||||
"metal" : Math.max(needs.metal - current.metal, 0) + EcoManager.baseNeed["metal"]
|
||||
};
|
||||
}*/
|
||||
var needs = new Resources();
|
||||
// get ouy current resources, not removing accounts.
|
||||
var current = this.getAvailableResources(gameState, true);
|
||||
|
|
@ -117,7 +53,7 @@ QueueManager.prototype.futureNeeds = function(gameState, EcoManager) {
|
|||
{
|
||||
var name = this.queueArrays[i][0];
|
||||
var queue = this.queueArrays[i][1];
|
||||
for (var j = 0; j < Math.min(3,queue.length()); ++j)
|
||||
for (var j = 0; j < Math.min(2,queue.length()); ++j)
|
||||
{
|
||||
needs.add(queue.queue[j].getCost());
|
||||
}
|
||||
|
|
@ -223,46 +159,56 @@ QueueManager.prototype.update = function(gameState) {
|
|||
}
|
||||
|
||||
var availableRes = this.getAvailableResources(gameState);
|
||||
// assign some accounts to queues. This is done by priority, and by need. Note that this currently only looks at the next element.
|
||||
// assign some accounts to queues. This is done by priority, and by need.
|
||||
for (ress in availableRes)
|
||||
{
|
||||
if (availableRes[ress] > 0 && ress != "population")
|
||||
{
|
||||
var totalPriority = 0;
|
||||
var tempPrio = {};
|
||||
var maxNeed = {};
|
||||
// Okay so this is where it gets complicated.
|
||||
// If a queue requires "ress" for the next element (in the queue or the outqueue)
|
||||
// And the account is not high enough for it (multiplied by queue length... Might be bad, might not be).
|
||||
// If a queue requires "ress" for the next elements (in the queue or the outqueue)
|
||||
// And the account is not high enough for it.
|
||||
// Then we add it to the total priority.
|
||||
// (sorry about readability... Those big 'ifs' basically check if there is a need in the inqueue/outqueue
|
||||
// To try and be clever, we don't want a long queue to hog all resources. So two things:
|
||||
// -if a queue has enough of resource X for the 1st element, its priority is decreased (/2).
|
||||
// -queues accounts are capped at "resources for the first + 80% of the next"
|
||||
// This avoids getting a high priority queue with many elements hogging all of one resource
|
||||
// uselessly while it awaits for other resources.
|
||||
for (j in this.queues) {
|
||||
if ((this.queues[j].length() > 0 && this.queues[j].getNext().getCost()[ress] > 0)
|
||||
|| (this.queues[j].outQueueLength() > 0 && this.queues[j].outQueueNext().getCost()[ress] > 0))
|
||||
if ( (this.queues[j].length() && this.accounts[j][ress] < this.queues[j].length() * (this.queues[j].getNext().getCost()[ress]))
|
||||
|| (this.queues[j].outQueueLength() && this.accounts[j][ress] < this.queues[j].outQueueLength() * (this.queues[j].outQueueNext().getCost()[ress])))
|
||||
totalPriority += this.priorities[j];
|
||||
}
|
||||
// Now we allow resources to the accounts. We can at most allow "priority/totalpriority*available"
|
||||
// But we'll sometimes allow less if that would overflow.
|
||||
for (j in this.queues) {
|
||||
if ((this.queues[j].length() > 0 && this.queues[j].getNext().getCost()[ress] > 0)
|
||||
|| (this.queues[j].outQueueLength() > 0 && this.queues[j].outQueueNext().getCost()[ress] > 0))
|
||||
var outQueueCost = this.queues[j].outQueueCost();
|
||||
var queueCost = this.queues[j].queueCost();
|
||||
if (this.accounts[j][ress] < queueCost[ress] + outQueueCost[ress])
|
||||
{
|
||||
// we'll add at much what can be allowed to this queue.
|
||||
var toAdd = Math.floor(this.priorities[j]/totalPriority * availableRes[ress]);
|
||||
|
||||
var maxNeed = 0;
|
||||
if (this.queues[j].length())
|
||||
for (var y = 0; y < Math.min(3,this.queues[j].length()); ++y)
|
||||
maxNeed += this.queues[j].queue[y].getCost()[ress];
|
||||
if (this.queues[j].outQueueLength())
|
||||
for (var y = 0; y < this.queues[j].outQueueLength(); ++y)
|
||||
maxNeed += this.queues[j].outQueue[y].getCost()[ress];
|
||||
if (toAdd + this.accounts[j][ress] > maxNeed)
|
||||
toAdd = maxNeed - this.accounts[j][ress]; // always inferior to the original level.
|
||||
//debug ("Adding " + toAdd + " of " + ress + " to the account of " + j);
|
||||
this.accounts[j][ress] += toAdd;
|
||||
// adding us to the list of queues that need an update.
|
||||
tempPrio[j] = this.priorities[j];
|
||||
maxNeed[j] = outQueueCost[ress] + this.queues[j].getNext().getCost()[ress];
|
||||
// if we have enough of that resource for the outqueue and our first resource in the queue, diminish our priority.
|
||||
if (this.accounts[j][ress] >= outQueueCost[ress] + this.queues[j].getNext().getCost()[ress])
|
||||
{
|
||||
tempPrio[j] /= 2;
|
||||
if (this.queues[j].length() !== 1)
|
||||
{
|
||||
var halfcost = this.queues[j].queue[1].getCost()[ress]*0.8;
|
||||
maxNeed[j] += halfcost;
|
||||
if (this.accounts[j][ress] >= outQueueCost[ress] + this.queues[j].getNext().getCost()[ress] + halfcost)
|
||||
delete tempPrio[j];
|
||||
}
|
||||
}
|
||||
if (tempPrio[j])
|
||||
totalPriority += tempPrio[j];
|
||||
}
|
||||
}
|
||||
// Now we allow resources to the accounts. We can at most allow "TempPriority/totalpriority*available"
|
||||
// But we'll sometimes allow less if that would overflow.
|
||||
for (j in tempPrio) {
|
||||
// we'll add at much what can be allowed to this queue.
|
||||
var toAdd = Math.floor(tempPrio[j]/totalPriority * availableRes[ress]);
|
||||
// let's check we're not adding too much.
|
||||
var maxAdd = Math.min(maxNeed[j] - this.accounts[j][ress], toAdd);
|
||||
this.accounts[j][ress] += maxAdd;
|
||||
}
|
||||
}
|
||||
}
|
||||
Engine.ProfileStop();
|
||||
|
|
|
|||
|
|
@ -49,6 +49,14 @@ Queue.prototype.outQueueCost = function(){
|
|||
return cost;
|
||||
};
|
||||
|
||||
Queue.prototype.queueCost = function(){
|
||||
var cost = new Resources();
|
||||
for (var key in this.queue){
|
||||
cost.add(this.queue[key].getCost());
|
||||
}
|
||||
return cost;
|
||||
};
|
||||
|
||||
Queue.prototype.nextToOutQueue = function(){
|
||||
if (this.queue.length > 0){
|
||||
this.outQueue.push(this.queue.shift());
|
||||
|
|
|
|||
|
|
@ -319,10 +319,8 @@ Worker.prototype.startGathering = function(gameState){
|
|||
//debug ("inaccessible");
|
||||
return;
|
||||
}
|
||||
|
||||
// too many workers trying to gather from this resource
|
||||
if (supply.getMetadata(PlayerID, "full") === true) {
|
||||
//debug ("full");
|
||||
|
||||
if (supply.isFull() === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -331,7 +329,7 @@ Worker.prototype.startGathering = function(gameState){
|
|||
//debug ("enemy");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// quickscope accessbility check.
|
||||
if (!gameState.ai.accessibility.pathAvailable(gameState, ent.position(), supply.position(), true)) {
|
||||
//debug ("nopath");
|
||||
|
|
@ -352,6 +350,9 @@ Worker.prototype.startGathering = function(gameState){
|
|||
// measure the distance to the resource (largely irrelevant)
|
||||
var dist = SquareVectorDistance(supply.position(), ent.position());
|
||||
|
||||
if (dist > 4900 && supply.hasClass("Animal"))
|
||||
return;
|
||||
|
||||
// Add on a factor for the nearest dropsite if one exists
|
||||
if (nearestDropsite !== undefined ){
|
||||
dist += 4*SquareVectorDistance(supply.position(), nearestDropsite.position());
|
||||
|
|
@ -486,8 +487,8 @@ Worker.prototype.startHunting = function(gameState){
|
|||
if (supply.getMetadata(PlayerID, "inaccessible") === true)
|
||||
return;
|
||||
|
||||
//if (supply.isFull === true)
|
||||
// return;
|
||||
if (supply.isFull() === true)
|
||||
return;
|
||||
|
||||
if (!supply.hasClass("Animal"))
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "qBot",
|
||||
"description": "Quantumstate's improved version of the Test Bot",
|
||||
"description": "Quantumstate's improved version of the Test Bot\n\n(Note: qBot doesn't support difficulty levels.)",
|
||||
"constructor": "QBotAI",
|
||||
"useShared" : false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -169,7 +169,7 @@ Worker.prototype.startGathering = function(gameState){
|
|||
return;
|
||||
}
|
||||
|
||||
if (supply.isFull === true) {
|
||||
if (supply.isFull() === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ Player.prototype.Init = function()
|
|||
this.startCam = undefined;
|
||||
this.controlAllUnits = false;
|
||||
this.isAI = false;
|
||||
this.gatherRateMultiplier = 1;
|
||||
this.cheatsEnabled = true;
|
||||
this.cheatTimeMultiplier = 1;
|
||||
};
|
||||
|
|
@ -109,6 +110,16 @@ Player.prototype.GetMaxPopulation = function()
|
|||
return Math.round(ApplyTechModificationsToPlayer("Player/MaxPopulation", this.maxPop, this.entity));
|
||||
};
|
||||
|
||||
Player.prototype.SetGatherRateMultiplier = function(value)
|
||||
{
|
||||
this.gatherRateMultiplier = value;
|
||||
};
|
||||
|
||||
Player.prototype.GetGatherRateMultiplier = function()
|
||||
{
|
||||
return this.gatherRateMultiplier;
|
||||
};
|
||||
|
||||
Player.prototype.IsTrainingBlocked = function()
|
||||
{
|
||||
return this.trainingBlocked;
|
||||
|
|
|
|||
|
|
@ -128,7 +128,9 @@ ResourceGatherer.prototype.GetLastCarriedType = function()
|
|||
ResourceGatherer.prototype.GetGatherRates = function()
|
||||
{
|
||||
var ret = {};
|
||||
var baseSpeed = ApplyTechModificationsToEntity("ResourceGatherer/BaseSpeed", +this.template.BaseSpeed, this.entity);
|
||||
var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
|
||||
|
||||
var baseSpeed = ApplyTechModificationsToEntity("ResourceGatherer/BaseSpeed", +this.template.BaseSpeed, this.entity) * cmpPlayer.GetGatherRateMultiplier();
|
||||
|
||||
for (var r in this.template.Rates)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -5,55 +5,64 @@
|
|||
"Name": "Gaia",
|
||||
"Civ": "gaia",
|
||||
"Colour": { "r": 255, "g": 255, "b": 255 },
|
||||
"AI": ""
|
||||
"AI": "",
|
||||
"AIDiff": 2
|
||||
},
|
||||
{
|
||||
"Name": "Player 1",
|
||||
"Civ": "athen",
|
||||
"Colour": { "r": 46, "g": 46, "b": 200 },
|
||||
"AI": ""
|
||||
"AI": "",
|
||||
"AIDiff": 2
|
||||
},
|
||||
{
|
||||
"Name": "Player 2",
|
||||
"Civ": "cart",
|
||||
"Colour": { "r": 150, "g": 20, "b": 20 },
|
||||
"AI": "qbot"
|
||||
"AI": "qbot-wc",
|
||||
"AIDiff": 2
|
||||
},
|
||||
{
|
||||
"Name": "Player 3",
|
||||
"Civ": "gaul",
|
||||
"Colour": { "r": 50, "g": 165, "b": 5 },
|
||||
"AI": "qbot"
|
||||
"AI": "qbot-wc",
|
||||
"AIDiff": 2
|
||||
},
|
||||
{
|
||||
"Name": "Player 4",
|
||||
"Civ": "iber",
|
||||
"Colour": { "r": 230, "g": 230, "b": 75 },
|
||||
"AI": "qbot"
|
||||
"AI": "qbot-wc",
|
||||
"AIDiff": 2
|
||||
},
|
||||
{
|
||||
"Name": "Player 5",
|
||||
"Civ": "mace",
|
||||
"Colour": { "r": 50, "g": 170, "b": 170 },
|
||||
"AI": "qbot"
|
||||
"AI": "qbot-wc",
|
||||
"AIDiff": 2
|
||||
},
|
||||
{
|
||||
"Name": "Player 6",
|
||||
"Civ": "pers",
|
||||
"Colour": { "r": 160, "g": 80, "b": 200 },
|
||||
"AI": "qbot"
|
||||
"AI": "qbot-wc",
|
||||
"AIDiff": 2
|
||||
},
|
||||
{
|
||||
"Name": "Player 7",
|
||||
"Civ": "rome",
|
||||
"Colour": { "r": 235, "g": 120, "b": 20 },
|
||||
"AI": "qbot"
|
||||
"AI": "qbot-wc",
|
||||
"AIDiff": 2
|
||||
},
|
||||
{
|
||||
"Name": "Player 8",
|
||||
"Civ": "spart",
|
||||
"Colour": { "r": 64, "g": 64, "b": 64 },
|
||||
"AI": "qbot"
|
||||
"AI": "qbot-wc",
|
||||
"AIDiff": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,8 +18,9 @@ function InitGame(settings)
|
|||
cmpPlayer.SetCheatEnabled(false);
|
||||
if (settings.PlayerData[i] && settings.PlayerData[i].AI && settings.PlayerData[i].AI != "")
|
||||
{
|
||||
cmpAIManager.AddPlayer(settings.PlayerData[i].AI, i+1);
|
||||
cmpAIManager.AddPlayer(settings.PlayerData[i].AI, i+1, settings.PlayerData[i].AIDiff);
|
||||
cmpPlayer.SetAI(true);
|
||||
cmpPlayer.SetGatherRateMultiplier((+settings.PlayerData[i].AIDiff+2)/3.0) // Medium is 1, easy is 66%, hard is 133%, very hard 166%
|
||||
cmpPlayer.SetCheatEnabled(true);
|
||||
}
|
||||
if (settings.PopulationCap)
|
||||
|
|
|
|||
|
|
@ -75,6 +75,11 @@ function LoadPlayerSettings(settings, newPlayers)
|
|||
var colour = getSetting(pData, pDefs, "Colour");
|
||||
cmpPlayer.SetColour(colour.r, colour.g, colour.b);
|
||||
|
||||
// Note: this is not yet implemented but I leave it commented to highlight it's easy
|
||||
// If anyone ever adds handicap.
|
||||
//if (getSetting(pData, pDefs, "GatherRateMultiplier") !== undefined)
|
||||
// cmpPlayer.SetGatherRateMultiplier(getSetting(pData, pDefs, "GatherRateMultiplier"));
|
||||
|
||||
if (getSetting(pData, pDefs, "PopulationLimit") !== undefined)
|
||||
cmpPlayer.SetMaxPopulation(getSetting(pData, pDefs, "PopulationLimit"));
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ Basic gameplay:
|
|||
Autostart:
|
||||
-autostart=NAME map NAME for scenario, or rms name for random map
|
||||
-autostart-ai=PLAYER:AI adds named AI to the given PLAYER (e.g. 2:testbot)
|
||||
-autostart-aidiff=PLAYER:level changes the difficulty setting for AI PLAYER (0: easy, 3: very hard)
|
||||
Multiplayer:
|
||||
-autostart-playername=NAME multiplayer local player NAME (default 'anonymous')
|
||||
-autostart-host multiplayer host mode
|
||||
|
|
|
|||
|
|
@ -1196,7 +1196,26 @@ bool Autostart(const CmdLineArgs& args)
|
|||
scriptInterface.SetPropertyInt(playerData.get(), playerID-1, player);
|
||||
}
|
||||
}
|
||||
|
||||
// Set AI difficulty
|
||||
if (args.Has("autostart-aidiff"))
|
||||
{
|
||||
std::vector<CStr> civArgs = args.GetMultiple("autostart-aidiff");
|
||||
for (size_t i = 0; i < civArgs.size(); ++i)
|
||||
{
|
||||
// Instead of overwriting existing player data, modify the array
|
||||
CScriptVal player;
|
||||
if (!scriptInterface.GetPropertyInt(playerData.get(), i, player) || player.undefined())
|
||||
{
|
||||
scriptInterface.Eval("({})", player);
|
||||
}
|
||||
|
||||
int playerID = civArgs[i].BeforeFirst(":").ToInt();
|
||||
int difficulty = civArgs[i].AfterFirst(":").ToInt();
|
||||
|
||||
scriptInterface.SetProperty(player.get(), "AIDiff", difficulty);
|
||||
scriptInterface.SetPropertyInt(playerData.get(), playerID-1, player);
|
||||
}
|
||||
}
|
||||
// Set player data for Civs
|
||||
if (args.Has("autostart-civ"))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -77,9 +77,9 @@ private:
|
|||
{
|
||||
NONCOPYABLE(CAIPlayer);
|
||||
public:
|
||||
CAIPlayer(CAIWorker& worker, const std::wstring& aiName, player_id_t player,
|
||||
CAIPlayer(CAIWorker& worker, const std::wstring& aiName, player_id_t player, uint8_t difficulty,
|
||||
const shared_ptr<ScriptRuntime>& runtime, boost::rand48& rng) :
|
||||
m_Worker(worker), m_AIName(aiName), m_Player(player), m_ScriptInterface("Engine", "AI", runtime)
|
||||
m_Worker(worker), m_AIName(aiName), m_Player(player), m_Difficulty(difficulty), m_ScriptInterface("Engine", "AI", runtime)
|
||||
{
|
||||
m_ScriptInterface.SetCallbackData(static_cast<void*> (this));
|
||||
|
||||
|
|
@ -231,6 +231,7 @@ private:
|
|||
CScriptVal settings;
|
||||
m_ScriptInterface.Eval(L"({})", settings);
|
||||
m_ScriptInterface.SetProperty(settings.get(), "player", m_Player, false);
|
||||
m_ScriptInterface.SetProperty(settings.get(), "difficulty", m_Difficulty, false);
|
||||
ENSURE(m_Worker.m_HasLoadedEntityTemplates);
|
||||
m_ScriptInterface.SetProperty(settings.get(), "templates", m_Worker.m_EntityTemplates, false);
|
||||
|
||||
|
|
@ -275,6 +276,7 @@ private:
|
|||
CAIWorker& m_Worker;
|
||||
std::wstring m_AIName;
|
||||
player_id_t m_Player;
|
||||
uint8_t m_Difficulty;
|
||||
bool m_UseSharedComponent;
|
||||
|
||||
ScriptInterface m_ScriptInterface;
|
||||
|
|
@ -455,9 +457,9 @@ public:
|
|||
return true;
|
||||
}
|
||||
|
||||
bool AddPlayer(const std::wstring& aiName, player_id_t player, bool callConstructor)
|
||||
bool AddPlayer(const std::wstring& aiName, player_id_t player, uint8_t difficulty, bool callConstructor)
|
||||
{
|
||||
shared_ptr<CAIPlayer> ai(new CAIPlayer(*this, aiName, player, m_ScriptRuntime, m_RNG));
|
||||
shared_ptr<CAIPlayer> ai(new CAIPlayer(*this, aiName, player, difficulty, m_ScriptRuntime, m_RNG));
|
||||
if (!ai->Initialise(callConstructor))
|
||||
return false;
|
||||
|
||||
|
|
@ -598,7 +600,8 @@ public:
|
|||
{
|
||||
serializer.String("name", m_Players[i]->m_AIName, 1, 256);
|
||||
serializer.NumberI32_Unbounded("player", m_Players[i]->m_Player);
|
||||
|
||||
serializer.NumberU8_Unbounded("difficulty", m_Players[i]->m_Difficulty);
|
||||
|
||||
serializer.NumberU32_Unbounded("num commands", (u32)m_Players[i]->m_Commands.size());
|
||||
for (size_t j = 0; j < m_Players[i]->m_Commands.size(); ++j)
|
||||
{
|
||||
|
|
@ -645,9 +648,11 @@ public:
|
|||
{
|
||||
std::wstring name;
|
||||
player_id_t player;
|
||||
uint8_t difficulty;
|
||||
deserializer.String("name", name, 1, 256);
|
||||
deserializer.NumberI32_Unbounded("player", player);
|
||||
if (!AddPlayer(name, player, true))
|
||||
deserializer.NumberU8_Unbounded("difficulty",difficulty);
|
||||
if (!AddPlayer(name, player, difficulty, true))
|
||||
throw PSERROR_Deserialize_ScriptError();
|
||||
|
||||
uint32_t numCommands;
|
||||
|
|
@ -855,9 +860,9 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
virtual void AddPlayer(std::wstring id, player_id_t player)
|
||||
virtual void AddPlayer(std::wstring id, player_id_t player, uint8_t difficulty)
|
||||
{
|
||||
m_Worker.AddPlayer(id, player, true);
|
||||
m_Worker.AddPlayer(id, player, difficulty, true);
|
||||
|
||||
// AI players can cheat and see through FoW/SoD, since that greatly simplifies
|
||||
// their implementation.
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@
|
|||
#include "ps/Filesystem.h"
|
||||
|
||||
BEGIN_INTERFACE_WRAPPER(AIManager)
|
||||
DEFINE_INTERFACE_METHOD_2("AddPlayer", void, ICmpAIManager, AddPlayer, std::wstring, player_id_t)
|
||||
DEFINE_INTERFACE_METHOD_3("AddPlayer", void, ICmpAIManager, AddPlayer, std::wstring, player_id_t, uint8_t)
|
||||
DEFINE_INTERFACE_METHOD_0("TryLoadSharedComponent", void, ICmpAIManager, TryLoadSharedComponent)
|
||||
DEFINE_INTERFACE_METHOD_0("RunGamestateInit", void, ICmpAIManager, RunGamestateInit)
|
||||
END_INTERFACE_WRAPPER(AIManager)
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ public:
|
|||
* by @p id (corresponding to a subdirectory in simulation/ai/),
|
||||
* to control player @p player.
|
||||
*/
|
||||
virtual void AddPlayer(std::wstring id, player_id_t player) = 0;
|
||||
virtual void AddPlayer(std::wstring id, player_id_t player, uint8_t difficulty) = 0;
|
||||
virtual void TryLoadSharedComponent() = 0;
|
||||
virtual void RunGamestateInit() = 0;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue