| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,105 @@ | ||
|
|
||
| /* | ||
| * This file includes event definitions only. | ||
| * | ||
| */ | ||
|
|
||
| function eventStartLevel() { | ||
| queue("setTimers", me * 100); | ||
| if (alliancesType === ALLIANCES_TEAMS) { | ||
| // initialize subpersonality pseudo-randomly here | ||
| // to make sure teammates have the same personality | ||
| var j = 1, s = 0; | ||
| for (var i = 0; i < maxPlayers; ++i) { | ||
| if (allianceExistsBetween(me, i)) | ||
| s += j; | ||
| j *= 2; | ||
| } | ||
| // the random "s" number obtained here is the same for all players in any team | ||
| var s = s + (new Date()).getMinutes(); | ||
| j = 0; | ||
| for (var i in subpersonalities) // count the amount of subpersonalities | ||
| ++j; | ||
| s = s % j; | ||
| j = 0; | ||
| for (var i in subpersonalities) { | ||
| if (j === s) | ||
| personality = subpersonalities[i]; | ||
| ++j; | ||
| } | ||
| } else { | ||
| // if teams are not sharing research, or there are no teams at all, | ||
| // initialize the subpersonality randomly and don't care | ||
| personality = randomItem(subpersonalities); | ||
| } | ||
| // the following code is necessary to avoid some strange game bug when droids that | ||
| // are initially buried into the ground fail to move out of the way when a building | ||
| // is being placed right above them | ||
| enumTrucks().forEach(function(droid) { | ||
| orderDroidLoc(droid, DORDER_MOVE, droid.x + random(3) - 1, droid.y + random(3) - 1); | ||
| }); | ||
| } | ||
|
|
||
| function eventDroidBuilt(droid, structure) { | ||
| groupDroid(droid); | ||
| } | ||
|
|
||
| function eventStructureBuilt(structure) { | ||
| queue("checkConstruction"); | ||
| } | ||
|
|
||
| function eventAttacked(victim, attacker) { | ||
| if (attacker === null) | ||
| return; // no idea why it happens sometimes | ||
| if (isAlly(attacker.player)) | ||
| return; // don't respond to accidental friendly fire | ||
| if (victim.type === DROID) { | ||
| if (!isVTOL(victim) && defined(victim.group)) { | ||
| fallBack(victim, attacker); | ||
| setTarget(attacker, victim.group); | ||
| touchGroup(victim.group); | ||
| } else if (isVTOL(victim) && victim.player == me && !throttled(5000, victim.id)) { | ||
| orderDroidObj(victim, DORDER_ATTACK, attacker); | ||
| pushVtols(attacker); | ||
| } | ||
| } else if (victim.type === STRUCTURE) { | ||
| if (throttled(5000) && victim.player != me) | ||
| return; | ||
| if (inPanic()) | ||
| for (var i = 0; i < MAX_GROUPS; ++i) | ||
| if (groupSize(i) > 0) | ||
| setTarget(attacker, i); | ||
| setTarget(attacker, miscGroup); | ||
| setTarget(attacker); | ||
| } | ||
| } | ||
|
|
||
| function eventStructureReady(structure) { | ||
| fireLassat(structure); | ||
| } | ||
|
|
||
| function eventChat(from, to, message) { | ||
| // we are not case-sensitive | ||
| message = message.toLowerCase(); | ||
| handleChatMessage(from, to, message) | ||
| } | ||
|
|
||
| function eventObjectTransfer(object, from) { | ||
| if (object.player !== me) | ||
| return; // object was transferred from me, not to me | ||
| if (object.type === DROID) | ||
| groupDroid(object); | ||
| } | ||
|
|
||
| function eventBeacon(x, y, from, to) { | ||
| noticeBeacon(x, y, from); | ||
| } | ||
|
|
||
| function eventBeaconRemoved(from, to) { | ||
| unnoticeBeacon(from); | ||
| } | ||
|
|
||
| function eventDestroyed(object) { | ||
| if (isEnemy(object.player)) | ||
| pushVtols(object); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,107 @@ | ||
|
|
||
| /* | ||
| * This file defines generic things that should be defined | ||
| * prior to defining a ruleset. | ||
| * | ||
| * NOTE: This file is not included from main.js . | ||
| * | ||
| */ | ||
|
|
||
| // weapon path role | ||
| const ROLE = { | ||
| AT: 0, | ||
| AP: 1, | ||
| AS: 2, | ||
| AA: 3, | ||
| LENGTH: 4, // number of items in this enum | ||
| } | ||
|
|
||
| // something dual to the previous enum | ||
| const OBJTYPE = { | ||
| TANK: 0, | ||
| BORG: 1, | ||
| DEFS: 2, | ||
| VTOL: 3, | ||
| LENGTH: 4, // number of items in this enum; should be equal to ROLE.LENGTH | ||
| } | ||
|
|
||
| // this controls body and weapon compatibility. | ||
| // A little explanation: | ||
| // w \ b | L | M | H bodies can't be "ultra-", | ||
| // UL | + | - | - ultra-light weapons are for light bodies only, | ||
| // L | + | + | - light weapons are for light or medium bodies, | ||
| // M | + | + | + medium weapons are for all bodies, | ||
| // H | - | + | + heavy weapons are for medium or heavy bodies, | ||
| // UH | - | - | + ultra-heavy weapons are for heavy bodies only. | ||
| const WEIGHT = { | ||
| ULTRALIGHT: 0, | ||
| LIGHT: 1, | ||
| MEDIUM: 2, | ||
| HEAVY: 3, | ||
| ULTRAHEAVY: 4, | ||
| } | ||
|
|
||
| // what to use this defensive structure for | ||
| const DEFROLE = { | ||
| STANDALONE: 0, | ||
| GATEWAY: 1, | ||
| ARTY: 2, | ||
| ANTIAIR: 3, | ||
| FORTRESS: 4, | ||
| } | ||
|
|
||
| // return values of build order calls | ||
| const BUILDRET = { | ||
| SUCCESS: 0, | ||
| UNAVAILABLE: 1, | ||
| FAILURE: 2, | ||
| } | ||
|
|
||
| // should we execute a build call in an unsafe location? | ||
| const IMPORTANCE = { | ||
| PEACETIME: 0, | ||
| MANDATORY: 1, | ||
| } | ||
|
|
||
| // aspects of every research path | ||
| const RESASPECTS = { | ||
| WEAPONS: 0, | ||
| DEFENSES: 1, | ||
| VTOLS: 2, | ||
| EXTRAS: 3, | ||
| LENGTH: 4, // number of items in this enum | ||
| } | ||
|
|
||
| const PROPULSIONUSAGE = { | ||
| GROUND : 1, | ||
| HOVER: 2, | ||
| VTOL: 4 | ||
| } | ||
|
|
||
| // what to use this body for? (bit field) | ||
| const BODYUSAGE = { | ||
| GROUND: 1, // for tanks | ||
| AIR: 2, // for VTOLs | ||
| COMBAT: 3, // GROUND | AIR | ||
| TRUCK: 4, // trucks, sensors and repair droids | ||
| UNIVERSAL: 7, // COMBAT | TRUCK | ||
| } | ||
|
|
||
| // what sort of weapons this body is resistant to? | ||
| const BODYCLASS = { | ||
| KINETIC: 1, | ||
| THERMAL: 2, | ||
| } | ||
|
|
||
| // should this module be prioritized when not having enough power? | ||
| const MODULECOST = { | ||
| CHEAP: 0, | ||
| EXPENSIVE: 1, | ||
| } | ||
|
|
||
| // how to micromanage tanks with this sort of weapon | ||
| const MICRO = { | ||
| RANGED: 0, // normal weapons, such as cannons | ||
| MELEE: 1, // weapons that need to get very close to the enemy, such as flamers | ||
| DUMB: 2, // weapons that can't fire while moving | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
|
|
||
| /* | ||
| * This file connects all remaining pieces of AI code together. | ||
| * It shouldn't contain any code itself. | ||
| * | ||
| * NOTE: order matters! | ||
| * | ||
| */ | ||
|
|
||
| var personality; | ||
|
|
||
| include(NB_INCLUDES + "math.js"); | ||
| include(NB_INCLUDES + "intensity.js"); | ||
| include(NB_INCLUDES + "misc.js"); | ||
| include(NB_INCLUDES + "stats.js"); | ||
|
|
||
| include(NB_INCLUDES + "adapt.js"); | ||
| include(NB_INCLUDES + "build.js"); | ||
| include(NB_INCLUDES + "research.js"); | ||
| include(NB_INCLUDES + "produce.js"); | ||
| include(NB_INCLUDES + "tactics.js"); | ||
| include(NB_INCLUDES + "lassat.js"); | ||
| include(NB_INCLUDES + "chat.js"); | ||
|
|
||
| include(NB_INCLUDES + "timers.js"); | ||
| include(NB_INCLUDES + "_events.js"); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,376 @@ | ||
|
|
||
| /* | ||
| * This file describes building construction procedures. | ||
| * | ||
| */ | ||
|
|
||
| (function(_global) { | ||
| //////////////////////////////////////////////////////////////////////////////////////////// | ||
|
|
||
| function randomLocation() { | ||
| var x = baseLocation.x + random(baseScale) - baseScale / 2; | ||
| var y = baseLocation.y + random(baseScale) - baseScale / 2; | ||
| if (x < 3 || y < 3 || x > mapWidth - 4 || y > mapHeight - 4) | ||
| return baseLocation; | ||
| return {x: x, y: y}; | ||
|
|
||
| } | ||
|
|
||
| // a function for checking the presence of enemy units at the construction site | ||
| function safeSpot(x, y) { | ||
| return dangerLevel({x: x, y: y}) <= 0; | ||
| } | ||
|
|
||
| function truckFree(truck) { | ||
| if (truck.droidType !== DROID_CONSTRUCT) | ||
| return false; | ||
| if (truck.order === DORDER_BUILD) | ||
| return false; | ||
| if (truck.order === DORDER_HELPBUILD) | ||
| return false; | ||
| if (truck.order === DORDER_LINEBUILD) | ||
| return false; | ||
| if (truck.order === DORDER_DEMOLISH) | ||
| return false; | ||
| return true; | ||
| } | ||
|
|
||
| // returns one or two free trucks | ||
| function getTwoFreeTrucks() { | ||
| var trucks = enumTrucks().filter(truckFree); | ||
| if (trucks.length > 2) { | ||
| var ret = naiveFindClusters(trucks, baseScale / 2); | ||
| if (ret.maxCount >= 2) | ||
| trucks = ret.clusters[ret.maxIdx]; | ||
| } | ||
| if (trucks.length > 2) | ||
| trucks.length = 2; | ||
| return trucks; | ||
| } | ||
|
|
||
| function getFreeTruckAround(x, y) { | ||
| var list = enumTrucks().filter(truckFree).filter(function(droid) { | ||
| return droidCanReach(droid, x, y); | ||
| }).sort(function(one, two) { | ||
| return distance(one, x, y) - distance(two, x, y); | ||
| }); | ||
| if (list.length > 0) | ||
| return list[0]; | ||
| } | ||
|
|
||
| function buildModule(struct) { | ||
| trucks = getTwoFreeTrucks(); | ||
| if (trucks.length <= 0) | ||
| return BUILDRET.FAILURE; | ||
| var moduleInfo = modules.filter(function(item) { return isAvailable(item.module) && item.base === struct.stattype; }).last(); | ||
| if (!defined(moduleInfo)) | ||
| return BUILDRET.UNAVAILABLE; | ||
| if (struct.modules >= moduleInfo.count) | ||
| return BUILDRET.UNAVAILABLE; | ||
| var success = false; | ||
| for (var i = 0; i < trucks.length; ++i) | ||
| success = orderDroidBuild(trucks[i], DORDER_BUILD, moduleInfo.module, struct.x, struct.y) || success; | ||
| if (success) | ||
| return BUILDRET.SUCCESS; | ||
| return BUILDRET.FAILURE; | ||
| } | ||
|
|
||
| function buildBasicStructure(statlist, importance) { | ||
| if (throttled(5000, statlist[0])) return BUILDRET.FAILURE; | ||
| // by default, don't try building things in dangerous locations | ||
| if (!defined(importance)) | ||
| importance = IMPORTANCE.MANDATORY; | ||
| trucks = getTwoFreeTrucks(); | ||
| if (trucks.length <= 0) | ||
| return BUILDRET.FAILURE; | ||
| // choose structure type (out of the statlist), | ||
| // together with suitable location | ||
| var idx, loc, avail = false; | ||
| for (var i = 0; i < statlist.length; ++i) | ||
| if (isAvailable(statlist[i])) { | ||
| avail = true; | ||
| if (distanceToBase(trucks[0]) <= baseScale) | ||
| loc = pickStructLocation(trucks[0], statlist[i], trucks[0].x, trucks[0].y); | ||
| else { | ||
| var rndLoc = randomLocation(); | ||
| loc = pickStructLocation(trucks[0], statlist[i],rndLoc.x, rndLoc.y); | ||
| } | ||
| idx = i; | ||
| break; | ||
| } | ||
| if (!avail) | ||
| return BUILDRET.UNAVAILABLE; | ||
| if (!defined(loc)) | ||
| return BUILDRET.FAILURE; | ||
| if (importance === IMPORTANCE.PEACETIME && !safeSpot(loc.x, loc.y)) | ||
| return BUILDRET.FAILURE; | ||
| // now actually build | ||
| var success = false; | ||
| for (var i = 0; i < trucks.length; ++i) | ||
| success = orderDroidBuild(trucks[i], DORDER_BUILD, statlist[idx], loc.x, loc.y) || success; | ||
| if (success) | ||
| return BUILDRET.SUCCESS; | ||
| return BUILDRET.FAILURE; | ||
| } | ||
|
|
||
| function finishStructures() { | ||
| var success = false; | ||
| var list = enumStruct(me).filterProperty("status", BEING_BUILT); | ||
| for (var i = 0; i < list.length; ++i) { | ||
| if (success) | ||
| return; | ||
| if (throttled(10000, list[i].id)) | ||
| return; | ||
| if (list[i].stattype === RESOURCE_EXTRACTOR) | ||
| return; | ||
| var truck = getFreeTruckAround(list[i].x, list[i].y); | ||
| if (!defined(truck)) | ||
| return; | ||
| if (orderDroidObj(truck, DORDER_HELPBUILD, list[i])) | ||
| success = true; | ||
| }; | ||
| return success; | ||
| } | ||
|
|
||
| function buildStructureAround(statlist, loc, unique) { | ||
| if (!defined(statlist)) | ||
| return BUILDRET.UNAVAILABLE; | ||
| var truck = getFreeTruckAround(loc.x, loc.y); | ||
| if (!defined(truck)) | ||
| return BUILDRET.FAILURE; | ||
| var stat = statlist.filter(isAvailable).filter(function(s) { | ||
| if (unique !== true) | ||
| return true; | ||
| var list = enumStruct(me, s); | ||
| for (var i = 0; i < list.length; ++i) | ||
| if (distance(list[i], loc) < baseScale / 2) | ||
| return false; | ||
| return true; | ||
| }).last(); | ||
| if (!defined(stat)) | ||
| return BUILDRET.UNAVAILABLE; | ||
| var loc2 = pickStructLocation(truck, stat, loc.x, loc.y); | ||
| if (!defined(loc2)) | ||
| return BUILDRET.FAILURE; | ||
| // if we're not into turtling, don't build too many towers | ||
| if (personality.defensiveness < 100 && distance(loc2, loc) > baseScale / 5) | ||
| return BUILDRET.FAILURE; | ||
| if (orderDroidBuild(truck, DORDER_BUILD, stat, loc2.x, loc2.y)) | ||
| return BUILDRET.SUCCESS; | ||
| return BUILDRET.FAILURE; | ||
| } | ||
|
|
||
| function captureOil(oil) { | ||
| if (!defined(oil)) | ||
| return BUILDRET.FAILURE; | ||
| var truck = getFreeTruckAround(oil.x, oil.y); | ||
| if (!defined(truck)) | ||
| return BUILDRET.FAILURE; | ||
| var stat = structures.derricks.filter(isAvailable).last(); | ||
| if (!defined(stat)) | ||
| return BUILDRET.UNAVAILABLE; | ||
| if (throttled(90000, oil.y * mapWidth + oil.x)) | ||
| return BUILDRET.FAILURE; | ||
| if (orderDroidBuild(truck, DORDER_BUILD, stat, oil.x, oil.y)) | ||
| return BUILDRET.SUCCESS; | ||
| return BUILDRET.FAILURE; | ||
| } | ||
|
|
||
| function chooseDefense(defrole) { | ||
| return weaponStatsToDefenses(chooseAvailableWeaponPathByRoleRatings(getProductionPaths(), chooseDefendWeaponRole(), 2, defrole), defrole); | ||
| } | ||
|
|
||
| function buildTowers() { | ||
| var oils = enumStructList(structures.derricks); | ||
| if (oils.length === 0) | ||
| return false; | ||
| if (withChance(70)) | ||
| return buildStructureAround(chooseDefense(DEFROLE.STANDALONE), oils.random()) !== BUILDRET.UNAVAILABLE; | ||
| return buildStructureAround(chooseDefense(DEFROLE.FORTRESS).concat(structures.sensors), oils.random(), true) !== BUILDRET.UNAVAILABLE; | ||
| } | ||
|
|
||
| function buildGateways() { | ||
| var oils = countStructList(structures.derricks); | ||
| if (oils <= 0) | ||
| return BUILDRET.FAILURE; | ||
| var gates = enumGateways().filter(function(gate) { | ||
| var l = gate.x1 - gate.x2 + gate.y1 - gate.y2; | ||
| if (l < 0) | ||
| l = -l; | ||
| var cnt = enumRange(gate.x1, gate.y1, l, ALLIES).filterProperty("stattype", DEFENSE).length; | ||
| cnt += enumRange(gate.x2, gate.y2, l, ALLIES).filterProperty("stattype", DEFENSE).length; | ||
| cnt -= enumRange(gate.x1, gate.y1, l, ENEMIES).filterProperty("stattype", DEFENSE).length; | ||
| cnt -= enumRange(gate.x2, gate.y2, l, ENEMIES).filterProperty("stattype", DEFENSE).length; | ||
| return cnt >= 0 && (cnt < l || (personality.defensiveness === 100 && withChance(70))); // turtle AI needs to keep building towers | ||
| }).sort(function(one, two) { return distanceToBase({x: one.x1, y: one.y1}) - distanceToBase({x: two.x1, y: two.y1}); }); | ||
| if (gates.length === 0) | ||
| return; | ||
| if (withChance(50)) | ||
| return buildStructureAround(chooseDefense(DEFROLE.GATEWAY), {x: gates[0].x1, y: gates[0].y1}) !== BUILDRET.UNAVAILABLE; | ||
| else | ||
| return buildStructureAround(chooseDefense(DEFROLE.GATEWAY), {x: gates[0].x2, y: gates[0].y2}) !== BUILDRET.UNAVAILABLE; | ||
| } | ||
|
|
||
| function buildArty() { | ||
| return buildBasicStructure(chooseDefense(DEFROLE.ARTY), IMPORTANCE.PEACETIME); | ||
| } | ||
|
|
||
| _global.buildMinimum = function(statlist, count, importance) { | ||
| if (countStructList(statlist) < count) | ||
| if (buildBasicStructure(statlist, importance) !== BUILDRET.UNAVAILABLE) | ||
| return true; | ||
| return false; | ||
| } | ||
|
|
||
| _global.captureSomeOil = function() { | ||
| if (throttled(500)) | ||
| return true; | ||
| function getOilList() { | ||
| var oils = []; | ||
| oilResources.forEach(function(stat) { oils = oils.concat(enumFeature(-1, stat)); }); | ||
| oils = oils.concat(enumStructList(structures.derricks).filterProperty("status", BEING_BUILT)); | ||
| oils = oils.sort(function(one, two) { | ||
| return distanceToBase(one) - distanceToBase(two); | ||
| }); | ||
| if (oils.length > 10) | ||
| oils.length = 10; | ||
| return oils; | ||
| } | ||
| var oils = cached(getOilList, 5000); | ||
| if (countFinishedStructList(structures.derricks) >= 4 * structListLimit(structures.gens)) | ||
| return false; | ||
| for (var i = 0; i < oils.length; ++i) | ||
| if (captureOil(oils[i]) === BUILDRET.SUCCESS) | ||
| return true; | ||
| return false; | ||
| } | ||
|
|
||
| _global.buildMinimumDerricks = function(count) { | ||
| if (countFinishedStructList(structures.derricks) < count) | ||
| if (captureSomeOil()) | ||
| return true; | ||
| return false; | ||
| } | ||
|
|
||
| function buildExpand() { | ||
| if (myPower() > personality.maxPower) { | ||
| switch (chooseObjectType()) { | ||
| case 0: | ||
| if (needFastestResearch() === PROPULSIONUSAGE.GROUND) | ||
| if (buildMinimum(structures.factories, Infinity, IMPORTANCE.PEACETIME)) | ||
| return true; | ||
| // fall-through | ||
| case 1: | ||
| if (needFastestResearch() === PROPULSIONUSAGE.GROUND) | ||
| if (buildMinimum(structures.templateFactories, Infinity, IMPORTANCE.PEACETIME)) | ||
| return true; | ||
| // fall-through | ||
| case 3: | ||
| if (buildMinimum(structures.vtolFactories, Infinity, IMPORTANCE.PEACETIME)) | ||
| return true; | ||
| } | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| function buildEnergy() { | ||
| var oils = countFinishedStructList(structures.derricks); | ||
| var gens = countStructList(structures.gens); | ||
| if (oils > 4 * gens) | ||
| if (buildBasicStructure(structures.gens, IMPORTANCE.PEACETIME) !== BUILDRET.UNAVAILABLE) | ||
| return true; | ||
| if (withChance(50)) | ||
| if (captureSomeOil()) | ||
| return true; | ||
| return false; | ||
| } | ||
|
|
||
| function buildModules() { | ||
| var str = []; | ||
| for (var i = 0; i < modules.length; ++i) { | ||
| if (modules[i].base === FACTORY && needFastestResearch() !== PROPULSIONUSAGE.GROUND) | ||
| continue; | ||
| str = enumStruct(me, modules[i].base); | ||
| for (var j = 0; j < str.length; ++j) | ||
| if (buildModule(str[j]) !== BUILDRET.UNAVAILABLE) | ||
| return true; | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| _global.buildVtols = function() { | ||
| if (buildMinimum(structures.vtolPads, enumDroid(me, DROID_WEAPON).filter(isVTOL).length / 2), IMPORTANCE.PEACETIME) | ||
| return true; | ||
| return false; | ||
| } | ||
|
|
||
| function buildExtras() { | ||
| if (throttled(180000)) | ||
| return false; | ||
| if (buildBasicStructure(structures.extras, IMPORTANCE.PEACETIME) !== BUILDRET.UNAVAILABLE) | ||
| return true; | ||
| return false; | ||
| } | ||
|
|
||
| _global.buildDefenses = function() { | ||
| if (chooseObjectType() !== 2) | ||
| return false; | ||
| if (withChance(33)) { | ||
| if (buildTowers()) // includes sensor towers and forts | ||
| return true; | ||
| } else if (withChance(50)) { | ||
| if (buildGateways()) | ||
| return true; | ||
| } else | ||
| if (buildArty()) | ||
| return true; | ||
| return false; | ||
| } | ||
|
|
||
| function listOutdatedDefenses() { | ||
| for (var path in weaponStats) { | ||
| for (var role in DEFROLE) { | ||
| var list = weaponStatsToDefenses(weaponStats[path], DEFROLE[role]); | ||
| for (var i = 0; i < list.length - 2; ++i) | ||
| if (isAvailable(list[i + 2])) { | ||
| if (countStruct(list[i]) > 0) | ||
| return enumStruct(me, list[i]); | ||
| } | ||
| } | ||
| }; | ||
| return []; | ||
| } | ||
|
|
||
| function recycleDefenses() { | ||
| var trucks = enumTrucks().filter(truckFree); | ||
| if (trucks.length <= 0) | ||
| return false; | ||
| var list = listOutdatedDefenses(); | ||
| for (var i = 0; i < list.length; ++i) | ||
| for (var j = 0; j < trucks.length; ++j) | ||
| if (droidCanReach(trucks[j], list[i].x, list[i].y)) { | ||
| orderDroidObj(trucks[j], DORDER_DEMOLISH, list[i]); | ||
| return true; | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| _global.checkConstruction = function() { | ||
| if (enumTrucks().filter(truckFree).length === 0) | ||
| return; | ||
| if (functionSeries("construction", [ | ||
| finishStructures, | ||
| buildOrder, | ||
| buildExpand, | ||
| buildEnergy, | ||
| buildModules, | ||
| buildVtols, | ||
| buildExtras, | ||
| recycleDefenses, | ||
| buildDefenses, | ||
| ])) | ||
| queue("checkConstruction"); | ||
| } | ||
|
|
||
| //////////////////////////////////////////////////////////////////////////////////////////// | ||
| })(this); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,151 @@ | ||
| /* | ||
| * This file is responsible for chat listening. It contains handler functions for different chat | ||
| * messages; each such function takes message and player as a parameter and returns a reply string. | ||
| * All chat talking (such as calling for help) is coded in other places, not here. | ||
| * | ||
| * Messages are marked as translatable when necessary. | ||
| * | ||
| */ | ||
|
|
||
| (function(_global) { | ||
| //////////////////////////////////////////////////////////////////////////////////////////// | ||
|
|
||
| var prefix = '!nb'; | ||
|
|
||
| // key: name in chat, value: function that will be executed | ||
| // function gets two params: sender and argument | ||
| var commandMap = { | ||
| set: chatSet, | ||
| res: chatRes, | ||
| truck: chatTruck, | ||
| power: chatMoney, | ||
| money: chatMoney, // alias for "power" | ||
| help: chatHelp, | ||
| go: chatHelp, // alias for "help" | ||
| tx: chatUnhelp, | ||
| } | ||
|
|
||
| var beaconInfo = []; | ||
|
|
||
| _global.noticeBeacon = function(x, y, from) { | ||
| beaconInfo[from] = { | ||
| x: x, | ||
| y: y, | ||
| exists: 1, | ||
| }; | ||
| } | ||
|
|
||
| _global.unnoticeBeacon = function(from) { | ||
| beaconInfo[from].exists = 0; | ||
| } | ||
|
|
||
| _global.findBeaconPlayer = function(x, y) { | ||
| for (var i = 0; i < beaconInfo.length; ++i) | ||
| if (defined(beaconInfo[i]) && beaconInfo[i].x === x && beaconInfo[i].y === y) | ||
| return i; | ||
| } | ||
|
|
||
| _global.handleChatMessage = function(sender, receiver, message) { | ||
| // don't reply on any other message coming sender enemies | ||
| if (message === "!nb who") { | ||
| chat(sender, chatWho(sender)); | ||
| return; | ||
| } | ||
| if (!isAlly(sender)) | ||
| return; | ||
| if (message === "help me!!") { // Try to understand Nexus AI's way of calling for help | ||
| chatHelp(sender); | ||
| return; | ||
| } | ||
| var result = message.split(/ +/); | ||
| if (result[0] !== prefix) | ||
| return; | ||
| var command = result[1]; | ||
| var argument = result[2]; | ||
| if (defined(commandMap[command])) | ||
| chat(sender, commandMap[command](sender, argument)); | ||
| } | ||
|
|
||
| function chatWho(sender, argument) { | ||
| var str = "NullBot3 (" + scriptName + ") "; | ||
| switch(difficulty) { | ||
| case EASY: str += _("EASY"); break; | ||
| case MEDIUM: str=str + _("MEDIUM"); break; | ||
| case HARD: str=str + _("HARD"); break; | ||
| case INSANE: str=str + _("INSANE"); break; | ||
| } | ||
| if (isAlly(sender)) | ||
| str += (" ~" + personality.chatalias + "~"); | ||
| return str; | ||
| } | ||
|
|
||
| function chatSet(sender, argument) { | ||
| var str = ""; | ||
| for (var i in subpersonalities) { | ||
| if (subpersonalities[i].chatalias === argument) { | ||
| personality = subpersonalities[i]; | ||
| return _("Personality change successful."); | ||
| } | ||
| str = str + " " + subpersonalities[i].chatalias; | ||
| } | ||
| return _("No such personality! Try one of these:") + str; | ||
| } | ||
|
|
||
| function chatRes(sender, argument) { | ||
| if (argument === "cl") { | ||
| setForcedResearch(); // clear | ||
| return _("Forced research cleared, will research anything I want now."); | ||
| } | ||
| if (argument === "no") { | ||
| setForcedResearch(null); // clear | ||
| return _("Research blocked, will research nothing now."); | ||
| } | ||
| if (argument === "fn") { | ||
| setForcedResearch(fundamentalResearch); | ||
| return _("Researching fundamental technology."); | ||
| } | ||
| var str = " cl no fn"; | ||
| for (var i in weaponStats) { | ||
| if (weaponStats[i].chatalias === argument) { | ||
| setForcedResearch(weaponStatsToResList(weaponStats[i])); | ||
| return _("Researching ") + weaponStats[i].chatalias; | ||
| } | ||
| if (weaponStats[i].chatalias.indexOf("useless") < 0) | ||
| str = str + " " + weaponStats[i].chatalias; | ||
| } | ||
| return _("No such research path! Try one of these:") + str; | ||
| } | ||
|
|
||
| function chatTruck(sender, argument) { | ||
| var droid = enumTrucks().random(); | ||
| if (!defined(droid)) | ||
| return _("Sorry, I have no trucks."); | ||
| if (donateObject(droid, sender)) { | ||
| addBeacon(droid.x, droid.y, sender); | ||
| return _("You can use this one."); | ||
| } | ||
| return _("Sorry, droid transfer failed."); | ||
| } | ||
|
|
||
| function chatMoney(sender, argument) { | ||
| var power = Math.round(myPower()/3); | ||
| donatePower(power, sender); | ||
| return _("Power transferred."); | ||
| } | ||
|
|
||
| function chatHelp(sender, argument) { | ||
| if (!defined(beaconInfo[sender]) || !beaconInfo[sender].exists) | ||
| return _("Please put a beacon!"); | ||
| if (setTarget({ x: beaconInfo[sender].x, y: beaconInfo[sender].y, type: POSITION })) | ||
| return _("Coming!"); | ||
| else | ||
| return _("Sorry, I don't have any free forces to send for help!"); | ||
| } | ||
|
|
||
| function chatUnhelp(sender, argument) { | ||
| unsetTarget(sender); | ||
| return _("Any time, big boss!"); | ||
| } | ||
|
|
||
| //////////////////////////////////////////////////////////////////////////////////////////// | ||
| })(this); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,95 @@ | ||
|
|
||
| /* | ||
| * This file includes generic functions for improving CPU intensity, | ||
| * like caching and throttling mechanisms. | ||
| * | ||
| */ | ||
|
|
||
| (function(_global) { | ||
| //////////////////////////////////////////////////////////////////////////////////////////// | ||
|
|
||
| // to make sure some function is not called more often than every 2 seconds, | ||
| // put something like that at start the of its body: | ||
| // if (throttled(2000)) return; | ||
| // if your function requires several throttling cases, you can use the optional | ||
| // notes value: | ||
| // if (throttled(60000, player)) return; | ||
| // NOTE: it won't work if the function repeatedly dies and gets created again, eg. | ||
| // a function defined inside forEach(function(...){...}) can't be throttled | ||
| _global.throttled = function(interval, notes) { | ||
| if (!defined(arguments.callee.caller.throttleTimes)) | ||
| arguments.callee.caller.throttleTimes = {}; | ||
| if (!defined(arguments.callee.caller.throttleTimes[notes])) { | ||
| arguments.callee.caller.throttleTimes[notes] = gameTime; | ||
| return false; | ||
| } | ||
| if (gameTime - arguments.callee.caller.throttleTimes[notes] < interval) | ||
| return true; | ||
| arguments.callee.caller.throttleTimes[notes] = gameTime; | ||
| return false; | ||
| } | ||
|
|
||
| // to cache a function's output value and make sure it's not re-calculated too often, | ||
| // use the following trick: | ||
| // function calculateValue(params) { | ||
| // function uncached(params) { | ||
| // // do heavy calculations | ||
| // } | ||
| // return cached(uncached, 10000); | ||
| // } | ||
| // add necessary notes to the cached() call if necessary, similar to how you do it | ||
| // for throttled(). | ||
| // NOTE: it won't work if the function repeatedly dies and gets created again, eg. | ||
| // a function defined inside forEach(function(...){...}) can't have caching inside | ||
| _global.cached = function(whatToCall, interval, notes) { | ||
| if (!defined(arguments.callee.caller.cachedTimes)) { | ||
| arguments.callee.caller.cachedTimes = {}; | ||
| arguments.callee.caller.cachedValues = {}; | ||
| } | ||
| var t = arguments.callee.caller.cachedTimes[notes]; | ||
| if (!defined(t) || gameTime - t >= interval) { | ||
| arguments.callee.caller.cachedValues[notes] = whatToCall(); | ||
| arguments.callee.caller.cachedTimes[notes] = gameTime; | ||
| } | ||
| return arguments.callee.caller.cachedValues[notes]; | ||
| } | ||
|
|
||
| // if you actually want your script to send debug messages, consider using this function. | ||
| // it will only output each message only once, so your debug log will be readable. | ||
| _global.niceDebug = function() { | ||
| var msg = me + ": " + Array.prototype.join.call(arguments, " "); | ||
| if (throttled(Infinity, msg)) | ||
| return; | ||
| debug(msg); | ||
| } | ||
|
|
||
| // use this if you want to split a certain void function into parts. | ||
| // example: | ||
| // function first() { return random(2); } | ||
| // function second() { return random(2); } | ||
| // function third() { return random(2); } | ||
| // function doManyThings() { | ||
| // if (functionSeries("doStuff", [ first, second, third ])) | ||
| // queue("doManyThings"); | ||
| // } | ||
| // NOTE: in this example functions of the list don't need to be global, but | ||
| // doManyThings does, otherwise the queue call will fail. | ||
| _global.functionSeries = function(id, list) { | ||
| if (!defined(functionSeries.last)) | ||
| functionSeries.last = {}; | ||
| if (!defined(functionSeries.last[id])) | ||
| functionSeries.last[id] = 0; | ||
| else | ||
| ++functionSeries.last[id]; | ||
| if (functionSeries.last[id] >= list.length) { | ||
| functionSeries.last[id] = 0; | ||
| return false; // all functions in the list were called, none succeeded | ||
| } | ||
| if (!list[functionSeries.last[id]]()) | ||
| return true; // none of the function succeeded yet, telling to call us again next time | ||
| functionSeries.last[id] = undefined; | ||
| return false; // one of the functions succeeded, no need to call us anymore | ||
| } | ||
|
|
||
| //////////////////////////////////////////////////////////////////////////////////////////// | ||
| })(this); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
|
|
||
| /* | ||
| * This file is responsible for incoming laser satellite strikes. | ||
| * Relies on lassatSplash variable of the ruleset to figure out | ||
| * the radius of lassat. | ||
| * | ||
| */ | ||
|
|
||
| (function(_global) { | ||
| //////////////////////////////////////////////////////////////////////////////////////////// | ||
|
|
||
| // pick a target and fire | ||
| _global.fireLassat = function(structure) { | ||
| list = []; | ||
| enumLivingPlayers().filter(isEnemy).forEach(function(i) { | ||
| list = list.concat(enumStruct(i), enumDroid(i)); | ||
| }); | ||
| var maxIdx, maxPrice = 0; | ||
| list.forEach(function(obj, idx) { | ||
| var price = enumRange(obj.x, obj.y, lassatSplash / 2, ENEMIES, false).reduce(function(prev, curr) { return prev + curr.cost; }, 0); | ||
| if (price > maxPrice) { | ||
| maxPrice = price; | ||
| maxIdx = idx; | ||
| } | ||
| }); | ||
| activateStructure(structure, list[maxIdx]); | ||
| } | ||
|
|
||
| //////////////////////////////////////////////////////////////////////////////////////////// | ||
| })(this); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,149 @@ | ||
|
|
||
| /* | ||
| * This file includes routines and algorithms completely unrelated to Warzone 2100. | ||
| * | ||
| */ | ||
|
|
||
| (function(_global) { | ||
| //////////////////////////////////////////////////////////////////////////////////////////// | ||
|
|
||
| // return random true or false in certain chance | ||
| // 0 will return true with chance 0% | ||
| // 50 will return true with chance 50% | ||
| // 100 will return true with chance 100% | ||
| _global.withChance = function distance(chancePercent) { | ||
| return 1 + random(100) <= chancePercent; | ||
| } | ||
|
|
||
| // Get distance between two points | ||
| // acceptable arguments: | ||
| // distance(obj, obj) | ||
| // distance(x,y, obj) | ||
| // distance(obj,x,y) | ||
| _global.distance = function distance(obj1, obj2, obj3, obj4) { | ||
| var x1, x2, y1, y2; | ||
| if (defined(obj1.x)) { | ||
| x1 = obj1.x; | ||
| y1 = obj1.y; | ||
| if (defined(obj2.x)) { | ||
| x2 = obj2.x; | ||
| y2 = obj2.y; | ||
| } else { | ||
| x2 = obj2; | ||
| y2 = obj3; | ||
| } | ||
| } else { | ||
| x1 = obj1; | ||
| y1 = obj2; | ||
| if (defined(obj3.x)) { | ||
| x2 = obj3.x; | ||
| y2 = obj3.y; | ||
| } else { | ||
| x2 = obj3; | ||
| y2 = obj4; | ||
| } | ||
| } | ||
| return distBetweenTwoPoints(x1, y1, x2, y2); | ||
| } | ||
|
|
||
| _global.defined = function(variable) { | ||
| return typeof(variable) !== "undefined"; | ||
| } | ||
|
|
||
| // returns a random number between 0 and max-1 inclusively | ||
| _global.random = function(max) { | ||
| if (max > 0) | ||
| return Math.floor(Math.random() * max); | ||
| } | ||
|
|
||
| // some useful array functions | ||
| Array.prototype.random = function() { | ||
| if (this.length > 0) | ||
| return this[random(this.length)]; | ||
| } | ||
|
|
||
| Array.prototype.last = function() { | ||
| if (this.length > 0) | ||
| return this[this.length - 1]; | ||
| } | ||
|
|
||
| Array.prototype.filterProperty = function(property, value) { | ||
| return this.filter(function(element) { | ||
| return element[property] === value; | ||
| }); | ||
| } | ||
|
|
||
| Array.prototype.someProperty = function(property, value) { | ||
| return this.some(function(element) { | ||
| return element[property] === value; | ||
| }); | ||
| } | ||
|
|
||
| Array.prototype.shuffle = function() { | ||
| return this.sort(function() { return 0.5 - Math.random(); }) | ||
| } | ||
|
|
||
| _global.zeroArray = function(l) { | ||
| var ret = []; | ||
| for (var i = 0; i < l; ++i) | ||
| ret[i] = 0; | ||
| return ret; | ||
| } | ||
|
|
||
| _global.randomUnitArray = function(l) { | ||
| var ret = zeroArray(l); | ||
| ret[random(l)] = 1; | ||
| return ret; | ||
| } | ||
|
|
||
| Array.prototype.addArray = function(arr) { | ||
| for (var i = 0; i < this.length; ++i) | ||
| this[i] += arr[i]; | ||
| } | ||
|
|
||
| // returns a random property of an object | ||
| _global.randomItem = function(obj) { | ||
| var ret; | ||
| var count = 0; | ||
| for (var i in obj) | ||
| if (Math.random() < 1/++count) | ||
| ret = i; | ||
| return obj[ret]; | ||
| } | ||
|
|
||
| // cluster analysis happens here | ||
| _global.naiveFindClusters = function(list, size) { | ||
| var ret = { clusters: [], xav: [], yav: [], maxIdx: 0, maxCount: 0 }; | ||
| for (var i = list.length - 1; i >= 0; --i) { | ||
| var x = list[i].x, y = list[i].y; | ||
| var found = false; | ||
| for (var j = 0; j < ret.clusters.length; ++j) { | ||
| if (distance(ret.xav[j], ret.yav[j], x, y) < size) { | ||
| var n = ret.clusters[j].length; | ||
| ret.clusters[j][n] = list[i]; | ||
| ret.xav[j] = (n * ret.xav[j] + x) / (n + 1); | ||
| ret.yav[j] = (n * ret.yav[j] + y) / (n + 1); | ||
| if (ret.clusters[j].length > ret.maxCount) { | ||
| ret.maxIdx = j; | ||
| ret.maxCount = ret.clusters[j].length; | ||
| } | ||
| found = true; | ||
| break; | ||
| } | ||
| } | ||
| if (!found) { | ||
| var n = ret.clusters.length; | ||
| ret.clusters[n] = [list[i]]; | ||
| ret.xav[n] = x; | ||
| ret.yav[n] = y; | ||
| if (1 > ret.maxCount) { | ||
| ret.maxIdx = n; | ||
| ret.maxCount = 1 | ||
| } | ||
| } | ||
| } | ||
| return ret; | ||
| } | ||
|
|
||
| //////////////////////////////////////////////////////////////////////////////////////////// | ||
| })(this); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
|
|
||
| /* | ||
| * A few globals that didn't have a place for them in any other file | ||
| * | ||
| */ | ||
|
|
||
| var baseLocation = startPositions[me]; | ||
|
|
||
| function distanceToBase(loc) { | ||
| return distance(loc, baseLocation); | ||
| } | ||
|
|
||
| function canReachFromBase(propulsion, location) { | ||
| if (defined(propulsion)) | ||
| return propulsionCanReach(propulsion, baseLocation.x, baseLocation.y, location.x, location.y); | ||
| } | ||
|
|
||
| function myPower() { | ||
| return playerPower(me) - queuedPower(me); | ||
| } | ||
|
|
||
| function isAvailable(stat) { | ||
| return isStructureAvailable(stat, me); | ||
| } | ||
|
|
||
| function isAlly(player) { | ||
| return allianceExistsBetween(me, player); | ||
| } | ||
|
|
||
| function isEnemy(player) { | ||
| return !isAlly(player); | ||
| } | ||
|
|
||
| function enumLivingPlayers() { | ||
| function uncached() { | ||
| var ret = []; | ||
| for (var i = 0; i < maxPlayers; ++i) { | ||
| if (countStructList(targets, i) > 0) { | ||
| ret.push(i); | ||
| continue; | ||
| } | ||
| if (enumDroid(i).length > 0) | ||
| ret.push(i); | ||
| } | ||
| return ret; | ||
| }; | ||
| return cached(uncached, 30000); | ||
| } | ||
|
|
||
| function enumTrucks() { | ||
| return enumDroid(me, DROID_CONSTRUCT); | ||
| } | ||
|
|
||
| function goEasy() { | ||
| personality = randomItem(subpersonalities); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,239 @@ | ||
|
|
||
| /* | ||
| * This file is responsible for droid production. | ||
| * | ||
| */ | ||
|
|
||
| (function(_global) { | ||
| //////////////////////////////////////////////////////////////////////////////////////////// | ||
|
|
||
| function ourBuildDroid(factory, name, bodies, propulsions, weapons1, weapons2, weapons3) { | ||
| return buildDroid(factory, name, bodies, propulsions, "", "", weapons1, weapons2, weapons3); | ||
| } | ||
|
|
||
| function produceTruck(factory, turrets) { | ||
| var turret = truckTurrets.concat(); | ||
| if (defined(turrets)) | ||
| turret = turrets.concat(); | ||
| turret.reverse(); | ||
| // TODO: switch to using chooseBodyWeaponPair() here | ||
| var bodies = filterBodyStatsByUsage(BODYUSAGE.TRUCK, BODYCLASS.KINETIC).map(function(val) { return val.stat; }); | ||
| var propulsions = getPropulsionStatsComponents(PROPULSIONUSAGE.GROUND|PROPULSIONUSAGE.HOVER); | ||
| return ourBuildDroid(factory, "Fancy Truck", bodies, propulsions, turret); | ||
| } | ||
|
|
||
| function chooseWeapon(forVtol) { | ||
| if (!defined(forVtol)) | ||
| forVtol = false; | ||
| if (forVtol) { | ||
| var ret = chooseAvailableWeaponPathByRoleRatings(getProductionPaths(), chooseAttackWeaponRole(), 3); | ||
| if (defined(ret)) | ||
| return ret.vtols.concat().reverse(); | ||
| } else { | ||
| var ret = chooseAvailableWeaponPathByRoleRatings(getProductionPaths(), chooseAttackWeaponRole(), 0); | ||
| if (defined(ret)) | ||
| return ret.weapons.concat().reverse(); | ||
| } | ||
| } | ||
|
|
||
| function chooseBodyWeaponPair(bodies, weapons) { | ||
| if (!defined(bodies)) | ||
| return undefined; | ||
| if (!defined(weapons)) | ||
| return undefined; | ||
| for (var i = 0; i < weapons.length; ++i) { | ||
| var w = weapons[i].stat, ww = weapons[i].weight; | ||
| if (!componentAvailable(w)) | ||
| continue; | ||
| for (var j = 0; j < bodies.length; ++j) { | ||
| var b = bodies[j].stat, bw = bodies[j].weight; | ||
| if (!componentAvailable(b)) | ||
| continue; | ||
| switch(ww) { | ||
| case WEIGHT.ULTRALIGHT: | ||
| if (bw <= WEIGHT.LIGHT) | ||
| return {b: b, w: w}; | ||
| break; | ||
| case WEIGHT.LIGHT: | ||
| if (bw <= WEIGHT.MEDIUM) | ||
| return {b: b, w: w}; | ||
| break; | ||
| case WEIGHT.MEDIUM: | ||
| return {b: b, w: w}; | ||
| break; | ||
| case WEIGHT.HEAVY: | ||
| if (bw >= WEIGHT.MEDIUM) | ||
| return {b: b, w: w}; | ||
| break; | ||
| case WEIGHT.ULTRAHEAVY: | ||
| if (bw >= WEIGHT.HEAVY) | ||
| return {b: b, w: w}; | ||
| break; | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| function produceTank(factory) { | ||
| // TODO: needs refactoring. Make some more clever sorting. | ||
| var bodies = [] | ||
| if (chooseBodyClass() === BODYCLASS.KINETIC) { | ||
| bodies = bodies.concat( | ||
| filterBodyStatsByUsage(BODYUSAGE.GROUND, BODYCLASS.KINETIC), | ||
| filterBodyStatsByUsage(BODYUSAGE.GROUND, BODYCLASS.THERMAL) | ||
| ); | ||
| } else { | ||
| bodies = bodies.concat( | ||
| filterBodyStatsByUsage(BODYUSAGE.GROUND, BODYCLASS.THERMAL), | ||
| filterBodyStatsByUsage(BODYUSAGE.GROUND, BODYCLASS.KINETIC) | ||
| ); | ||
| } | ||
| var propulsions; | ||
| var ret = scopeRatings(); | ||
| var rnd = random(ret.land + ret.sea); | ||
| if (!defined(rnd)) // we need only vtols? | ||
| return false; | ||
| propulsions = getPropulsionStatsComponents(PROPULSIONUSAGE.GROUND); | ||
| if (iHaveHover()) { | ||
| if (rnd >= ret.land) | ||
| propulsions = getPropulsionStatsComponents(PROPULSIONUSAGE.HOVER); | ||
| } else { | ||
| if (ret.land === 0) | ||
| return false; | ||
| } | ||
| var bwPair = chooseBodyWeaponPair(bodies, chooseWeapon()); | ||
| if (!defined(bwPair)) | ||
| return false; | ||
| return ourBuildDroid(factory, "Tank", bwPair.b, propulsions, bwPair.w, bwPair.w, bwPair.w); | ||
| } | ||
|
|
||
| function produceVtol(factory) { | ||
| // TODO: consider thermal bodies | ||
| var bodies = filterBodyStatsByUsage(BODYUSAGE.AIR, BODYCLASS.KINETIC) | ||
| var propulsions = getPropulsionStatsComponents(PROPULSIONUSAGE.VTOL); | ||
| var bwPair = chooseBodyWeaponPair(bodies, chooseWeapon(true)); | ||
| if (!defined(bwPair)) | ||
| return false; | ||
| return ourBuildDroid(factory, "VTOL", bwPair.b, propulsions, bwPair.w, bwPair.w, bwPair.w); | ||
| } | ||
|
|
||
| function produceTemplateFromList(factory, list) { | ||
| var ret = scopeRatings(); | ||
| for (var i = list.length - 1; i >= 0; --i) { | ||
| if (ret.land === 0 && !isHoverPropulsion(list[i].prop) && !isVtolPropulsion(list[i].prop)) | ||
| continue; | ||
| if (ret.land === 0 && ret.sea === 0 && !isVtolPropulsion(list[i].prop)) | ||
| continue; | ||
| if (isVtolPropulsion(list[i].prop) !== (factory.stattype === VTOL_FACTORY)) | ||
| continue; | ||
| if ((!randomTemplates) || withChance(100 / (i + 1))) | ||
| if (ourBuildDroid(factory, "Template Droid", list[i].body, list[i].prop, list[i].weapons[0], list[i].weapons[1], list[i].weapons[2])) | ||
| return true; | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| function produceTemplate(factory) { | ||
| var path = chooseAvailableWeaponPathByRoleRatings(getProductionPaths(), chooseAttackWeaponRole(), 1); | ||
| if (defined(path)) | ||
| return produceTemplateFromList(factory, path.templates); | ||
| return false; | ||
| } | ||
|
|
||
| _global.checkTruckProduction = function() { | ||
| var trucks = enumTrucks(); | ||
| var hoverTrucksCount = trucks.filter(function(droid) { return isHoverPropulsion(droid.propulsion); }).length; | ||
| if (iHaveHover() && hoverTrucksCount < personality.minHoverTrucks) { | ||
| groundTrucks = trucks.filter(function(droid) { return !isHoverPropulsion(droid.propulsion); }); | ||
| if (groundTrucks.length > personality.minTrucks) { | ||
| groundTrucks.length -= personality.minTrucks; | ||
| groundTrucks.forEach(function(droid) { orderDroid(droid, DORDER_RECYCLE); }); | ||
| return false; | ||
| } | ||
| } | ||
| if (trucks.length >= getDroidLimit(me, DROID_CONSTRUCT)) | ||
| return false; | ||
| if (trucks.length < personality.minTrucks || myPower() > personality.maxPower | ||
| || (iHaveHover() && hoverTrucksCount < personality.minHoverTrucks) | ||
| ) { | ||
| var f; | ||
| f = enumFinishedStructList(structures.factories)[0]; | ||
| if (defined(f)) | ||
| if (structureIdle(f)) | ||
| if (produceTruck(f)) | ||
| return true; | ||
| if (defined(f)) | ||
| return false; | ||
| f = enumFinishedStructList(structures.templateFactories)[0]; | ||
| if (defined(f)) | ||
| if (structureIdle(f)) | ||
| if (produceTemplateFromList(f, truckTemplates)) | ||
| return true; | ||
| } | ||
| if (!iHaveArty()) | ||
| return false; | ||
| var sensors = enumDroid(me, DROID_SENSOR).length; | ||
| if (withChance(100 - 100 * sensors / personality.maxSensors)) { | ||
| f = enumFinishedStructList(structures.factories)[0]; | ||
| if (defined(f)) | ||
| if (structureIdle(f)) | ||
| if (produceTruck(f, sensorTurrets)) | ||
| return true; | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| function checkTankProduction() { | ||
| if (!iCanDesign()) | ||
| return false; // don't cheat by producing tanks before design is available (also saves money for early generators) | ||
| var success = false; | ||
| enumIdleStructList(structures.factories).forEach(function(factory) { | ||
| success = success || produceTank(factory); | ||
| }); | ||
| return success; | ||
| } | ||
|
|
||
| function checkTemplateProduction() { | ||
| var success = false; | ||
| enumIdleStructList(structures.templateFactories) | ||
| .concat(enumIdleStructList(structures.vtolFactories)) | ||
| .forEach(function(factory) | ||
| { | ||
| success = success || produceTemplate(factory); | ||
| }); | ||
| return success; | ||
| } | ||
|
|
||
| function checkVtolProduction() { | ||
| var success = false; | ||
| if (!iCanDesign()) | ||
| return false; // don't cheat by producing vtols before design is available | ||
| enumIdleStructList(structures.vtolFactories).forEach(function(factory) { | ||
| success = success || produceVtol(factory); | ||
| }); | ||
| return success; | ||
| } | ||
|
|
||
| _global.checkProduction = function() { | ||
| switch(chooseObjectType()) { | ||
| case 1: | ||
| if (checkTemplateProduction()) | ||
| return; | ||
| case 3: | ||
| if (checkVtolProduction()) | ||
| return; | ||
| default: | ||
| if (checkTankProduction()) | ||
| return; | ||
| } | ||
| // if having too much energy, don't care about what we produce | ||
| if (myPower() > personality.maxPower) { | ||
| queue("checkConstruction"); | ||
| checkTemplateProduction(); | ||
| checkTankProduction(); | ||
| checkVtolProduction(); | ||
| } | ||
| } | ||
|
|
||
| //////////////////////////////////////////////////////////////////////////////////////////// | ||
| })(this); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
|
|
||
| /* | ||
| * This file controls the AI's research choices. | ||
| * | ||
| */ | ||
|
|
||
| (function(_global) { | ||
| //////////////////////////////////////////////////////////////////////////////////////////// | ||
|
|
||
| var forcedResearch; | ||
|
|
||
| _global.setForcedResearch = function(list) { | ||
| forcedResearch = list; | ||
| } | ||
|
|
||
| _global.needFastestResearch = function() { | ||
| var ret = scopeRatings(); | ||
| if (ret.land === 0 && ret.sea === 0 && !iHaveVtol()) | ||
| return PROPULSIONUSAGE.VTOL; | ||
| if (ret.land === 0 && ret.sea !== 0 && !iHaveHover() && !iHaveVtol()) | ||
| return PROPULSIONUSAGE.HOVER; | ||
| return PROPULSIONUSAGE.GROUND; | ||
| } | ||
|
|
||
| function doResearch(lab) { | ||
| if (defined(forcedResearch)) { | ||
| if (forcedResearch === null) | ||
| return false; | ||
| if (pursueResearch(lab, forcedResearch)) | ||
| return true; | ||
| } | ||
| // if we need to quickly get a certain propulsion to reach the enemy, prioritize that. | ||
| var fastest = needFastestResearch(); | ||
| if (fastest === PROPULSIONUSAGE.VTOL) | ||
| if (pursueResearch(lab, [ | ||
| propulsionStatsToResList(PROPULSIONUSAGE.VTOL), | ||
| fastestResearch, | ||
| ].random())) | ||
| return true; | ||
| if (fastest === PROPULSIONUSAGE.HOVER) | ||
| if (pursueResearch(lab, [ | ||
| propulsionStatsToResList(PROPULSIONUSAGE.HOVER), | ||
| propulsionStatsToResList(PROPULSIONUSAGE.VTOL), | ||
| fastestResearch, | ||
| ].random())) | ||
| return true; | ||
| // otherwise, start with completing the fixed research path | ||
| if (defined(personality.earlyResearch)) | ||
| if (pursueResearch(lab, personality.earlyResearch)) | ||
| return true; | ||
| // then, see if we want to research some weapons | ||
| var objType = chooseObjectType(); | ||
| if (withChance(70)) { // TODO: make a more thoughtful decision here | ||
| var list = weaponStatsToResList(chooseAvailableWeaponPathByRoleRatings(personality.weaponPaths, chooseWeaponRole()), objType); | ||
| if (pursueResearch(lab, list)) | ||
| return true; | ||
| } | ||
| if (withChance(65)) { // TODO: make a more thoughtful decision here | ||
| if (chooseBodyClass() === BODYCLASS.KINETIC) { | ||
| if (withChance(40)) | ||
| if (pursueResearch(lab, classResearch.kinetic[objType])) | ||
| return true; | ||
| if (objType === OBJTYPE.TANK || objType === OBJTYPE.VTOL || (objType === OBJTYPE.DEFS && personality.defensiveness < 100)) | ||
| if (pursueResearch(lab, bodyStatsToResList(BODYCLASS.KINETIC))) | ||
| return true; | ||
| } else { | ||
| if (withChance(40)) | ||
| if (pursueResearch(lab, classResearch.thermal[objType])) | ||
| return true; | ||
| if (objType === OBJTYPE.TANK || objType === OBJTYPE.VTOL || (objType === OBJTYPE.DEFS && personality.defensiveness < 100)) | ||
| if (pursueResearch(lab, bodyStatsToResList(BODYCLASS.THERMAL))) | ||
| return true; | ||
| } | ||
| } | ||
| // if nothing of the above holds, do some generic research | ||
| if (pursueResearch(lab, fundamentalResearch)) | ||
| return true; | ||
| return false; | ||
|
|
||
| } | ||
|
|
||
| _global.checkResearch = function() { | ||
| enumIdleStructList(structures.labs).forEach(doResearch); | ||
| } | ||
|
|
||
| //////////////////////////////////////////////////////////////////////////////////////////// | ||
| })(this); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,233 @@ | ||
|
|
||
| /* | ||
| * This file contain functions for manipulating stats defined by rulesets. | ||
| * | ||
| */ | ||
|
|
||
| (function(_global) { | ||
| //////////////////////////////////////////////////////////////////////////////////////////// | ||
|
|
||
| _global.isHoverPropulsion = function(str) { | ||
| return propulsionStats.some(function(val) { return val.usage === PROPULSIONUSAGE.HOVER && val.stat === str; }); | ||
| } | ||
|
|
||
| _global.isVtolPropulsion = function(str) { | ||
| return propulsionStats.some(function(val) { return val.usage === PROPULSIONUSAGE.VTOL && val.stat === str; }); | ||
| } | ||
|
|
||
| _global.iHaveHover = function() { | ||
| return propulsionStats.some(function(val) { return val.usage === PROPULSIONUSAGE.HOVER && componentAvailable(val.stat);} ); | ||
| } | ||
|
|
||
| _global.iHaveVtol = function() { | ||
| return propulsionStats.some(function(val) { return val.usage === PROPULSIONUSAGE.VTOL && componentAvailable(val.stat);} ); | ||
| } | ||
|
|
||
| _global.iHaveArty = function() { | ||
| for (var stat in weaponStats) | ||
| for (var i = 0; i < weaponStats[stat].defenses.length; ++i) | ||
| if (weaponStats[stat].defenses[i].defrole === DEFROLE.ARTY) | ||
| if (countStruct(weaponStats[stat].defenses[i].stat) > 0) | ||
| return true; | ||
| return false; | ||
| } | ||
|
|
||
| // works with stored droid objects too! | ||
| _global.safeIsVtol = function(droid) { | ||
| return isVtolPropulsion(droid.propulsion); | ||
| } | ||
|
|
||
| _global.enumStructList = function(list, player) { | ||
| if (!defined(player)) | ||
| player = me; | ||
| return list.reduce(function(summ, new_value) { return summ.concat(enumStruct(player, new_value)); }, []); | ||
| } | ||
|
|
||
| _global.countStructList = function(list, player) { | ||
| if (!defined(player)) | ||
| player = me; | ||
| return list.reduce(function(summ, new_value) { return summ + countStruct(new_value, player); }, 0); | ||
| } | ||
|
|
||
| _global.enumFinishedStructList = function(list, player) { | ||
| return enumStructList(list, player).filterProperty("status", BUILT); | ||
| } | ||
|
|
||
| _global.countFinishedStructList = function(list, player) { | ||
| return enumFinishedStructList(list, player).length; | ||
| } | ||
|
|
||
| _global.enumIdleStructList = function(list, player) { | ||
| return enumFinishedStructList(list, player).filter(structureIdle); | ||
| } | ||
|
|
||
| _global.structListLimit = function(list) { | ||
| return list.reduce(function (summ, val) {return summ + getStructureLimit(val)}, 0) | ||
| } | ||
|
|
||
| _global.guessWeaponRole = function (name) { | ||
| for (var stat in weaponStats) { | ||
| if ( | ||
| weaponStats[stat].weapons.someProperty("stat", name) || | ||
| weaponStats[stat].vtols.someProperty("stat", name) || | ||
| weaponStats[stat].templates.some(function (i) {return i.weapons.indexOf(name) > -1;}) | ||
| ) | ||
| return weaponStats[stat].roles; | ||
| } | ||
| niceDebug("Ruleset warning: Couldn't guess weapon role of", name); | ||
| } | ||
|
|
||
| function guessWeaponMicro(name) { | ||
| function uncached() { | ||
| for (var stat in weaponStats) | ||
| if (weaponStats[stat].weapons.someProperty("stat", name)) | ||
| return weaponStats[stat].micro; | ||
| if (weaponStats[stat].templates.some(function(i) { return i.weapons.indexOf(name) > -1; })) | ||
| return weaponStats[stat].micro; | ||
| } | ||
| return cached(uncached, Infinity, name); | ||
| } | ||
|
|
||
| _global.guessDroidMicro = function(droid) { | ||
| for (var i = 0; i < droid.weapons.length; ++i) { | ||
| var ret = guessWeaponMicro(droid.weapons[i].name); | ||
| if (ret !== MICRO.RANGED) | ||
| return ret; | ||
| } | ||
| return MICRO.RANGED; | ||
| } | ||
|
|
||
| _global.guessBodyArmor = function(name) { | ||
| var body = bodyStats.filterProperty("stat", name).last() | ||
| if (defined(body)) | ||
| return body.armor; | ||
| else | ||
| niceDebug("Ruleset warning: Couldn't guess body class of", name); | ||
| } | ||
|
|
||
| function weaponPathIsAvailable(path, objectType, defrole) { | ||
| switch(objectType) { | ||
| case 0: | ||
| return path.weapons.some(function(val) { return componentAvailable(val.stat); }) | ||
| case 1: | ||
| return path.templates.some(function(val) { | ||
| for (var i = 0; i < val.weapons.length; ++i) | ||
| if (!componentAvailable(val.weapons[i])) | ||
| return false; | ||
| return componentAvailable(val.body) && componentAvailable(val.prop); | ||
| }); | ||
| case 2: | ||
| return path.defenses.some(function(val) { return val.defrole === defrole && isAvailable(val.stat); }); | ||
| case 3: | ||
| return path.vtols.some(function(val) { return componentAvailable(val.stat); }); | ||
| default: // research | ||
| return true; | ||
| } | ||
| } | ||
|
|
||
| _global.getProductionPaths = function() { | ||
| if (!defined(fallbackWeapon) || gameTime > 600000) | ||
| return personality.weaponPaths; | ||
| return [weaponStats[fallbackWeapon]].concat(personality.weaponPaths); | ||
| } | ||
|
|
||
| _global.chooseAvailableWeaponPathByRoleRatings = function(paths, rating, objectType, defrole) { | ||
| var minDist = Infinity, minPath; | ||
| paths.forEach(function(path) { | ||
| if (!weaponPathIsAvailable(path, objectType, defrole)) | ||
| return; | ||
| var dist = 0; | ||
| for (var i = 0; i < ROLE.LENGTH; ++i) { | ||
| var newDist = Math.abs(rating[i] - path.roles[i]) | ||
| if (newDist > dist) | ||
| dist = newDist; | ||
| } | ||
| if (dist < minDist) { | ||
| minDist = dist; | ||
| minPath = path; | ||
| } | ||
| }); | ||
| return minPath; | ||
| } | ||
|
|
||
| // | ||
| // here be functions for querying out research paths | ||
| // | ||
|
|
||
| function statsToResList(path) { | ||
| return path.map(function (val) { return val.res; }); | ||
| } | ||
|
|
||
| // todo make one StatsToResList and do filtering for path outside | ||
| _global.bodyStatsToResList = function(armor) { | ||
| return statsToResList(filterBodyStatsByUsage(armor)).reverse(); | ||
| } | ||
|
|
||
| _global.propulsionStatsToResList = function(usage) { | ||
| return statsToResList(filterDataByFlag(propulsionStats, 'usage', usage)); | ||
| } | ||
|
|
||
| _global.weaponStatsToResList = function(path, objType) { | ||
| if (!defined(path)) | ||
| return []; | ||
| var ret = []; | ||
| switch(objType) { | ||
| case 0: | ||
| ret = statsToResList(path.weapons); break; | ||
| case 1: | ||
| ret = statsToResList(path.templates); break; | ||
| case 2: | ||
| ret = statsToResList(path.defenses); break; | ||
| case 3: | ||
| ret = statsToResList(path.vtols); break; | ||
| } | ||
| if (ret.length === 0) | ||
| ret = ret.concat( | ||
| statsToResList(path.weapons), | ||
| statsToResList(path.templates), | ||
| statsToResList(path.defenses), | ||
| statsToResList(path.vtols) | ||
| ); | ||
| return ret; | ||
| } | ||
|
|
||
| // | ||
| // here be functions for querying out component lists | ||
| // | ||
|
|
||
| // TODO: move this to math. If we have same structure of data <list of objects> we can use it all around. | ||
| _global.filterDataByFlag = function(data, attr_name, flag) { | ||
| return data.filter(function(obj) { return obj[attr_name] & flag; }); | ||
| } | ||
|
|
||
| _global.filterBodyStatsByUsage = function(usage, armor) { | ||
| var data; | ||
| if (defined(armor)) | ||
| data = filterDataByFlag(bodyStats, 'armor', armor) | ||
| else | ||
| data = bodyStats; | ||
| return filterDataByFlag(data, 'usage', usage).reverse(); | ||
| } | ||
|
|
||
| _global.getPropulsionStatsComponents = function(usage) { | ||
| var data = filterDataByFlag(propulsionStats, 'usage', usage) | ||
| return data.map(function(val) { return val.stat; }).reverse() | ||
| } | ||
|
|
||
| // | ||
| // here be functions for querying out defensive structures | ||
| // | ||
|
|
||
| _global.weaponStatsToDefenses = function(stats, defrole) { | ||
| if (!defined(stats)) | ||
| return []; | ||
| var ret = []; | ||
| for (var i = 0; i < stats.defenses.length; ++i) | ||
| if (!defined(defrole) || stats.defenses[i].defrole === defrole) | ||
| ret.push(stats.defenses[i].stat); | ||
| // reverse not needed here | ||
| return ret; | ||
| } | ||
|
|
||
| //////////////////////////////////////////////////////////////////////////////////////////// | ||
| })(this); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
|
|
||
| /* | ||
| * This file lists all timers used by the AI. | ||
| * | ||
| */ | ||
|
|
||
| (function(_global) { | ||
| //////////////////////////////////////////////////////////////////////////////////////////// | ||
|
|
||
| // slightly shift all timers so that not to get too many of them on the same game frame, | ||
| // especially when multiple AI instances are running | ||
| function rnd() { | ||
| return random(201) - 100; | ||
| } | ||
|
|
||
| _global.setTimers = function() { | ||
| setTimer("spendMoney", 2000 + 3 * rnd()); | ||
| setTimer("checkConstruction", 3000 + 8 * rnd()); | ||
| setTimer("checkAttack", 100); | ||
| setTimer("adaptCycle", 100); | ||
| setTimer("rebalanceGroups", 10000 + 20 * rnd()); | ||
| if (difficulty === EASY) | ||
| setTimer("goEasy", 30000); | ||
| } | ||
|
|
||
| //////////////////////////////////////////////////////////////////////////////////////////// | ||
| })(this); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,99 @@ | ||
|
|
||
| /* | ||
| * This file defines a standard AI personality for the base game. | ||
| * | ||
| * It relies on ruleset definition in /rulesets/ to provide | ||
| * standard strategy descriptions and necessary game stat information. | ||
| * | ||
| * Then it passes control to the main code. | ||
| * | ||
| */ | ||
|
|
||
| // You can redefine these paths when you make a customized AI | ||
| // for a map or a challenge. | ||
| NB_PATH = "/multiplay/skirmish/"; | ||
| NB_INCLUDES = NB_PATH + "nb_includes/"; | ||
| NB_RULESETS = NB_PATH + "nb_rulesets/"; | ||
| NB_COMMON = NB_PATH + "nb_common/"; | ||
|
|
||
| // please don't touch this line | ||
| include(NB_INCLUDES + "_head.js"); | ||
|
|
||
| //////////////////////////////////////////////////////////////////////////////////////////// | ||
| // Start the actual personality definition | ||
|
|
||
| // the rules in which this personality plays | ||
| include(NB_RULESETS + "standard.js"); | ||
| include(NB_COMMON + "standard_build_order.js"); | ||
|
|
||
| // variables defining the personality | ||
| var subpersonalities = { | ||
| MR: { | ||
| chatalias: "mr", | ||
| weaponPaths: [ | ||
| weaponStats.rockets_AT, | ||
| weaponStats.machineguns, | ||
| weaponStats.rockets_AS, | ||
| weaponStats.rockets_AA, | ||
| weaponStats.rockets_Arty, | ||
| ], | ||
| earlyResearch: [ | ||
| "R-Defense-Tower01", | ||
| "R-Defense-Pillbox01", | ||
| "R-Defense-Tower06", | ||
| "R-Defense-MRL", | ||
| ], | ||
| minTanks: 3, becomeHarder: 3, maxTanks: 21, | ||
| minTrucks: 5, minHoverTrucks: 4, maxSensors: 6, | ||
| minMiscTanks: 1, maxMiscTanks: 6, | ||
| vtolness: 100, | ||
| defensiveness: 100, // this enables turtle AI specific code | ||
| maxPower: 300, | ||
| repairAt: 50, | ||
| }, | ||
| MC: { | ||
| chatalias: "mc", | ||
| weaponPaths: [ | ||
| weaponStats.cannons, | ||
| weaponStats.machineguns, | ||
| weaponStats.mortars, | ||
| weaponStats.fireMortars, | ||
| weaponStats.cannons_AA, | ||
| ], | ||
| earlyResearch: [ | ||
| "R-Defense-Tower01", | ||
| "R-Defense-Pillbox04", | ||
| "R-Defense-WallTower02", | ||
| "R-Defense-MortarPit", | ||
| ], | ||
| minTanks: 3, becomeHarder: 3, maxTanks: 21, | ||
| minTrucks: 5, minHoverTrucks: 4, maxSensors: 6, | ||
| minMiscTanks: 1, maxMiscTanks: 6, | ||
| vtolness: 100, defensiveness: 100, | ||
| maxPower: 300, | ||
| repairAt: 50, | ||
| }, | ||
| }; | ||
|
|
||
| // this function describes the early build order | ||
| // you can rely on personality.chatalias for choosing different build orders for | ||
| // different subpersonalities | ||
| function buildOrder() { | ||
| // Only use this build order in early game, on standard difficulty, in T1 no bases. | ||
| // Otherwise, fall back to the safe build order. | ||
| if (gameTime > 300000 || difficulty === INSANE | ||
| || isStructureAvailable("A0ComDroidControl") || baseType !== CAMP_CLEAN) | ||
| return buildOrder_StandardFallback(); | ||
| if (buildMinimum(structures.factories, 1)) return true; | ||
| if (buildMinimum(structures.labs, 1)) return true; | ||
| if (buildMinimum(structures.hqs, 1)) return true; | ||
| if (buildMinimum(structures.labs, 3)) return true; | ||
| if (buildMinimum(structures.gens, 1)) return true; | ||
| if (buildMinimumDerricks(3)) return true; | ||
| return withChance(25) ? captureSomeOil() : buildDefenses(); | ||
| } | ||
|
|
||
| //////////////////////////////////////////////////////////////////////////////////////////// | ||
| // Proceed with the main code | ||
|
|
||
| include(NB_INCLUDES + "_main.js"); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,6 @@ | ||
| { | ||
| "AI": { | ||
| "js": "nb_turtle.js", | ||
| "name": "Turtle AI", | ||
| "tip": "Tower wars AI, based on NullBot" | ||
| } | ||
|
|
||