Ticket #3586: petra_headquarters_template.patch
File petra_headquarters_template.patch, 14.0 KB (added by , 8 years ago) |
---|
-
binaries/data/mods/public/simulation/ai/petra/headquarters.js
17 17 m.HQ = function(Config) 18 18 { 19 19 this.Config = Config; 20 20 21 21 this.econState = "growth"; // existing values: growth, townPhasing. 22 22 this.currentPhase = undefined; 23 23 24 24 // cache the rates. 25 this.turnCache = {}; 25 this.turnCache = {}; 26 26 this.wantedRates = { "food": 0, "wood": 0, "stone":0, "metal": 0 }; 27 27 this.currentRates = { "food": 0, "wood": 0, "stone":0, "metal": 0 }; 28 this.lastFailedGather = { "wood": undefined, "stone": undefined, "metal": undefined }; 28 this.lastFailedGather = { "wood": undefined, "stone": undefined, "metal": undefined }; 29 29 30 30 // workers configuration 31 31 this.targetNumWorkers = this.Config.Economy.targetNumWorkers; … … 123 123 let ent = gameState.getEntityById(evt.entity); 124 124 if (!ent || !ent.isOwn(PlayerID)) 125 125 continue; 126 126 127 127 if (ent.getMetadata(PlayerID, "base") == -1) 128 128 { 129 129 // Okay so let's try to create a new base around this. … … 211 211 ent.setMetadata(PlayerID, "role", undefined); 212 212 ent.setMetadata(PlayerID, "subrole", undefined); 213 213 ent.setMetadata(PlayerID, "plan", undefined); 214 ent.setMetadata(PlayerID, "PartOfArmy", undefined); 214 ent.setMetadata(PlayerID, "PartOfArmy", undefined); 215 215 if (ent.hasClass("Trader")) 216 216 { 217 217 ent.setMetadata(PlayerID, "role", "trader"); 218 218 ent.setMetadata(PlayerID, "route", undefined); 219 219 } 220 if (ent.hasClass(" Female") || ent.hasClass("CitizenSoldier"))220 if (ent.hasClass("Support") || ent.hasClass("CitizenSoldier")) 221 221 { 222 222 ent.setMetadata(PlayerID, "role", "worker"); 223 223 ent.setMetadata(PlayerID, "subrole", "idle"); … … 335 335 this.decayingStructures.add(evt.entity); 336 336 } 337 337 else if (ent.isGarrisonHolder()) 338 this.garrisonManager.removeDecayingStructure(evt.entity); 338 this.garrisonManager.removeDecayingStructure(evt.entity); 339 339 } 340 340 341 341 // then deals with decaying structures … … 381 381 { 382 382 if (this.Config.difficulty > 2 && this.femaleRatio > 0.4) 383 383 this.femaleRatio = 0.4; 384 385 var phaseName = gameState.getTemplate(gameState.townPhase()).name(); 386 m.chatNewPhase(gameState, phaseName, true); 384 385 var phaseName = gameState.getTemplate(gameState.townPhase()).name(); 386 m.chatNewPhase(gameState, phaseName, true); 387 387 }; 388 388 389 389 // Called by the "city phase" research plan once it's started … … 394 394 395 395 // increase the priority of defense buildings to free this queue for our first fortress 396 396 gameState.ai.queueManager.changePriority("defenseBuilding", 2*this.Config.priorities.defenseBuilding); 397 398 var phaseName = gameState.getTemplate(gameState.cityPhase()).name(); 399 m.chatNewPhase(gameState, phaseName, true); 397 398 var phaseName = gameState.getTemplate(gameState.cityPhase()).name(); 399 m.chatNewPhase(gameState, phaseName, true); 400 400 }; 401 401 402 402 // This code trains females and citizen workers, trying to keep close to a ratio of females/CS 403 // TODO: this should choose a base depending on which base need workers404 // TODO: also there are several things that could be greatly improved here.405 403 m.HQ.prototype.trainMoreWorkers = function(gameState, queues) 406 404 { 407 // Count the workers in the world and in progress 408 var numFemales = gameState.countEntitiesAndQueuedByType(gameState.applyCiv("units/{civ}_support_female_citizen"), true); 405 // default template 406 var requirementsDef = [["cost", 1], ["costsResource", 1, "food"]]; 407 var classesDef = ["Support"]; 408 var templateDef = this.findBestTrainableUnit(gameState, classesDef, requirementsDef); 409 409 410 // Count the workers in the world and in progress 411 var numFemales = gameState.countEntitiesAndQueuedByType(templateDef); 412 410 413 // counting the workers that aren't part of a plan 411 414 var numWorkers = 0; 412 415 gameState.getOwnUnits().forEach (function (ent) { … … 453 456 if (numQueued > 50 || (numQueuedF > 20 && numQueuedS > 20) || numInTraining > 15) 454 457 return; 455 458 456 // default template457 var templateDef = gameState.applyCiv("units/{civ}_support_female_citizen");458 459 // Choose whether we want soldiers instead. 459 460 let femaleRatio = (gameState.isDisabledTemplates(gameState.applyCiv("structures/{civ}_field")) ? Math.min(this.femaleRatio, 0.2) : this.femaleRatio); 460 461 let template; 461 if ( !gameState.templates[templateDef] || ((numFemales+numQueuedF) > 8 && (numFemales+numQueuedF)/numTotal > femaleRatio))462 if ((numFemales+numQueuedF) > 8 && (numFemales+numQueuedF)/numTotal > femaleRatio) 462 463 { 463 464 let requirements; 464 465 if (numTotal < 45) … … 466 467 else 467 468 requirements = [ ["strength", 1] ]; 468 469 469 let proba = Math.random(); 470 if (proba < 0.6) 471 { // we require at least 30% ranged and 30% melee 472 let classes = proba < 0.3 ? ["CitizenSoldier", "Infantry", "Ranged"] : ["CitizenSoldier", "Infantry", "Melee"]; 473 template = this.findBestTrainableUnit(gameState, classes, requirements); 474 } 475 if (!template) 476 template = this.findBestTrainableUnit(gameState, ["CitizenSoldier", "Infantry"], requirements); 470 var classes = ["CitizenSoldier", "Infantry"]; 471 let proba = Math.random(); 472 // we require at least 30% ranged and 30% melee 473 if ( proba < 0.3 ) 474 { 475 classes.push("Ranged") 476 } 477 else if ( proba < 0.6 ) 478 { 479 classes.push("Melee"); 480 } 481 template = this.findBestTrainableUnit(gameState, classes, requirements); 477 482 } 478 if (!template && gameState.templates[templateDef])479 template = templateDef;480 483 481 // base "0" means "auto" 482 if (template === gameState.applyCiv("units/{civ}_support_female_citizen")) 483 queues.villager.addPlan(new m.TrainingPlan(gameState, template, { "role": "worker", "base": 0 }, size, size)); 484 else if (template) 485 queues.citizenSoldier.addPlan(new m.TrainingPlan(gameState, template, { "role": "worker", "base": 0 }, size, size)); 484 // If the template variable is empty, the default unit (Support unit) 485 // would be used in the next turn 486 // 487 // base "0" means "auto" 488 if (!template && gameState.templates[templateDef]) 489 { 490 queues.villager.addPlan(new m.TrainingPlan(gameState, templateDef, { "role": "worker", "base": 0 }, size, size)); 491 } 492 else if (template) 493 { 494 queues.citizenSoldier.addPlan(new m.TrainingPlan(gameState, template, { "role": "worker", "base": 0 }, size, size)); 495 } 486 496 }; 487 497 488 498 // picks the best template based on parameters and classes … … 495 505 units = gameState.findTrainableUnits(classes, ["SiegeTower"]); 496 506 else // We do not want hero when not explicitely specified 497 507 units = gameState.findTrainableUnits(classes, ["Hero"]); 498 508 499 509 if (units.length == 0) 500 510 return undefined; 501 511 … … 504 514 var availableResources = gameState.ai.queueManager.getAvailableResources(gameState); // available (gathered) resources 505 515 for (let type in remainingResources) 506 516 { 507 if (type === "food")508 continue;509 517 if (availableResources[type] > 800) 510 518 continue; 511 519 if (remainingResources[type] > 800) … … 515 523 for (let param of parameters) 516 524 { 517 525 if (param[0] !== "costsResource" || param[2] !== type) 518 continue; 526 continue; 519 527 param[1] = Math.min( param[1], costsResource ); 520 528 toAdd = false; 521 529 break; … … 547 555 aTopParam += a[1].walkSpeed() * param[1]; 548 556 bTopParam += b[1].walkSpeed() * param[1]; 549 557 } 550 558 551 559 if (param[0] == "cost") { 552 560 aDivParam += a[1].costSum() * param[1]; 553 561 bDivParam += b[1].costSum() * param[1]; … … 638 646 } 639 647 this.turnCache.gatherRates = true; 640 648 } 641 649 642 650 return this.currentRates; 643 651 }; 644 652 … … 662 670 needed.sort(function(a, b) { 663 671 var va = (Math.max(0, a.wanted - a.current))/ (a.current+1); 664 672 var vb = (Math.max(0, b.wanted - b.current))/ (b.current+1); 665 673 666 674 // If they happen to be equal (generally this means "0" aka no need), make it fair. 667 675 if (va === vb) 668 676 return (a.current - b.current); … … 674 682 // Returns the best position to build a new Civil Centre 675 683 // Whose primary function would be to reach new resources of type "resource". 676 684 m.HQ.prototype.findEconomicCCLocation = function(gameState, template, resource, proximity, fromStrategic) 677 { 685 { 678 686 // This builds a map. The procedure is fairly simple. It adds the resource maps 679 687 // (which are dynamically updated and are made so that they will facilitate DP placement) 680 688 // Then checks for a good spot in the territory. If none, and town/city phase, checks outside … … 721 729 var cellSize = this.territoryMap.cellSize; 722 730 723 731 for (let j = 0; j < this.territoryMap.length; ++j) 724 { 732 { 725 733 if (this.territoryMap.getOwnerIndex(j) != 0) 726 734 continue; 727 735 // with enough room around to build the cc … … 739 747 // checking distance to other cc 740 748 let pos = [cellSize * (j%width+0.5), cellSize * (Math.floor(j/width)+0.5)]; 741 749 742 if (proximity) // this is our first cc, let's do it near our units 750 if (proximity) // this is our first cc, let's do it near our units 743 751 { 744 752 let dist = API3.SquareVectorDistance(proximity, pos); 745 753 norm /= (1 + dist/scale); … … 806 814 807 815 if (this.borderMap.map[j] > 0) // disfavor the borders of the map 808 816 norm *= 0.5; 809 817 810 818 let val = 2*gameState.sharedScript.CCResourceMaps[resource].map[j] + 811 819 gameState.sharedScript.CCResourceMaps.wood.map[j] + 812 820 gameState.sharedScript.CCResourceMaps.stone.map[j] + … … 852 860 // Returns the best position to build a new Civil Centre 853 861 // Whose primary function would be to assure territorial continuity with our allies 854 862 m.HQ.prototype.findStrategicCCLocation = function(gameState, template) 855 { 863 { 856 864 // This builds a map. The procedure is fairly simple. 857 865 // We minimize the Sum((dist-300)**2) where the sum is on the three nearest allied CC 858 866 // with the constraints that all CC have dist > 200 and at least one have dist < 400 … … 954 962 } 955 963 // disfavor border of the map 956 964 if (this.borderMap.map[j] > 0) 957 currentVal += 10000; 965 currentVal += 10000; 958 966 959 967 if (bestVal !== undefined && currentVal > bestVal) 960 968 continue; … … 965 973 } 966 974 967 975 if (this.Config.debug > 1) 968 API3.warn("We've found a strategic base with bestVal = " + bestVal); 976 API3.warn("We've found a strategic base with bestVal = " + bestVal); 969 977 970 978 Engine.ProfileStop(); 971 979 … … 1061 1069 } 1062 1070 1063 1071 if (this.Config.debug > 1) 1064 API3.warn("We found a market position with bestVal = " + bestVal); 1072 API3.warn("We found a market position with bestVal = " + bestVal); 1065 1073 1066 1074 if (bestVal === undefined) // no constraints. For the time being, place it arbitrarily by the ConstructionPlan 1067 1075 return [-1, -1, -1, 0]; … … 1069 1077 var expectedGain = Math.round(bestVal / this.Config.distUnitGain); 1070 1078 if (this.Config.debug > 1) 1071 1079 API3.warn("this would give a trading gain of " + expectedGain); 1072 // do not keep it if gain is too small, except if this is our first BarterMarket 1080 // do not keep it if gain is too small, except if this is our first BarterMarket 1073 1081 if (expectedGain < this.tradeManager.minimalGain || 1074 1082 (expectedGain < 8 && (!template.hasClass("BarterMarket") || gameState.getOwnEntitiesByClass("BarterMarket", true).length > 0))) 1075 1083 return false; … … 1082 1090 // Returns the best position to build defensive buildings (fortress and towers) 1083 1091 // Whose primary function is to defend our borders 1084 1092 m.HQ.prototype.findDefensiveLocation = function(gameState, template) 1085 { 1093 { 1086 1094 // We take the point in our territory which is the nearest to any enemy cc 1087 1095 // but requiring a minimal distance with our other defensive structures 1088 1096 // and not in range of any enemy defensive structure to avoid building under fire. … … 1160 1168 if (!strPos) 1161 1169 continue; 1162 1170 let dist = API3.SquareVectorDistance(strPos, pos); 1163 if (dist < 6400) // TODO check on true attack range instead of this 80*80 1171 if (dist < 6400) // TODO check on true attack range instead of this 80*80 1164 1172 { 1165 1173 minDist = -1; 1166 1174 break; … … 1332 1340 if (gameState.getOwnStructures().filter(API3.Filters.byClass(requirements["class"])).length >= requirements.number) 1333 1341 this.requireHouses = undefined; 1334 1342 } 1335 1343 1336 1344 // When population limit too tight 1337 1345 // - if no room to build, try to improve with technology 1338 1346 // - otherwise increase temporarily the priority of houses … … 1451 1459 } 1452 1460 1453 1461 if (gameState.currentPhase() < 2 || !this.canBuild(gameState, "structures/{civ}_defense_tower")) 1454 return; 1462 return; 1455 1463 1456 1464 let numTowers = gameState.getOwnEntitiesByClass("DefenseTower", true).filter(API3.Filters.byClass("Town")).length; 1457 1465 if ((!numTowers || gameState.ai.elapsedTime > (1 + 0.10*numTowers)*this.towerLapseTime + this.towerStartTime) && … … 1578 1586 /** 1579 1587 * train with highest priority ranged infantry in the nearest civil centre from a given set of positions 1580 1588 * and garrison them there for defense 1581 */ 1589 */ 1582 1590 m.HQ.prototype.trainEmergencyUnits = function(gameState, positions) 1583 1591 { 1584 1592 if (gameState.ai.queues.emergency.hasQueuedUnits()) … … 1685 1693 1686 1694 m.HQ.prototype.canBuild = function(gameState, structure) 1687 1695 { 1688 var type = gameState.applyCiv(structure); 1696 var type = gameState.applyCiv(structure); 1689 1697 // available room to build it 1690 1698 if (this.stopBuilding.has(type)) 1691 1699 { … … 1913 1921 if (this.turnCache.resourceGatherer && this.turnCache.resourceGatherer[supplyID] !== undefined) 1914 1922 ++this.turnCache.resourceGatherer[supplyID]; 1915 1923 else 1916 { 1924 { 1917 1925 if (!this.turnCache.resourceGatherer) 1918 1926 this.turnCache.resourceGatherer = {}; 1919 1927 this.turnCache.resourceGatherer[supplyID] = 1; … … 1986 1994 this.currentPhase = gameState.currentPhase(); 1987 1995 let phaseName = "Unknown Phase"; 1988 1996 if (this.currentPhase == 2) 1989 phaseName = gameState.getTemplate(gameState.townPhase()).name(); 1997 phaseName = gameState.getTemplate(gameState.townPhase()).name(); 1990 1998 else if (this.currentPhase == 3) 1991 1999 phaseName = gameState.getTemplate(gameState.cityPhase()).name(); 1992 1993 m.chatNewPhase(gameState, phaseName, false); 2000 2001 m.chatNewPhase(gameState, phaseName, false); 1994 2002 this.updateTerritories(gameState); 1995 2003 } 1996 2004 else if (gameState.ai.playedTurn - this.lastTerritoryUpdate > 100)