Index: ps/trunk/binaries/data/mods/public/simulation/ai/common-api/shared.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/ai/common-api/shared.js (revision 25510) +++ ps/trunk/binaries/data/mods/public/simulation/ai/common-api/shared.js (revision 25511) @@ -1,395 +1,465 @@ var API3 = function(m) { /** Shared script handling templates and basic terrain analysis */ m.SharedScript = function(settings) { if (!settings) return; this._players = Object.keys(settings.players).map(key => settings.players[key]); // TODO SM55 Object.values(settings.players) this._templates = settings.templates; this._entityMetadata = {}; for (let player of this._players) this._entityMetadata[player] = {}; // array of entity collections this._entityCollections = new Map(); this._entitiesModifications = new Map(); // entities modifications this._templatesModifications = {}; // template modifications // each name is a reference to the actual one. this._entityCollectionsName = new Map(); this._entityCollectionsByDynProp = {}; this._entityCollectionsUID = 0; }; /** Return a simple object (using no classes etc) that will be serialized into saved games */ m.SharedScript.prototype.Serialize = function() { return { "players": this._players, "templatesModifications": this._templatesModifications, "entitiesModifications": this._entitiesModifications, "metadata": this._entityMetadata }; }; /** * Called after the constructor when loading a saved game, with 'data' being * whatever Serialize() returned */ m.SharedScript.prototype.Deserialize = function(data) { this._players = data.players; this._templatesModifications = data.templatesModifications; this._entitiesModifications = data.entitiesModifications; this._entityMetadata = data.metadata; this.isDeserialized = true; }; m.SharedScript.prototype.GetTemplate = function(name) { if (this._templates[name] === undefined) this._templates[name] = Engine.GetTemplate(name) || null; return this._templates[name]; }; /** * Initialize the shared component. * We need to know the initial state of the game for this, as we will use it. * This is called right at the end of the map generation. */ m.SharedScript.prototype.init = function(state, deserialization) { if (!deserialization) this._entitiesModifications = new Map(); this.ApplyTemplatesDelta(state); this.passabilityClasses = state.passabilityClasses; this.playersData = state.players; this.timeElapsed = state.timeElapsed; this.circularMap = state.circularMap; this.mapSize = state.mapSize; this.victoryConditions = new Set(state.victoryConditions); this.alliedVictory = state.alliedVictory; this.ceasefireActive = state.ceasefireActive; this.ceasefireTimeRemaining = state.ceasefireTimeRemaining / 1000; this.passabilityMap = state.passabilityMap; if (this.mapSize % this.passabilityMap.width !== 0) error("AI shared component inconsistent sizes: map=" + this.mapSize + " while passability=" + this.passabilityMap.width); this.passabilityMap.cellSize = this.mapSize / this.passabilityMap.width; this.territoryMap = state.territoryMap; if (this.mapSize % this.territoryMap.width !== 0) error("AI shared component inconsistent sizes: map=" + this.mapSize + " while territory=" + this.territoryMap.width); this.territoryMap.cellSize = this.mapSize / this.territoryMap.width; /* let landPassMap = new Uint8Array(this.passabilityMap.data.length); let waterPassMap = new Uint8Array(this.passabilityMap.data.length); let obstructionMaskLand = this.passabilityClasses["default-terrain-only"]; let obstructionMaskWater = this.passabilityClasses["ship-terrain-only"]; for (let i = 0; i < this.passabilityMap.data.length; ++i) { landPassMap[i] = (this.passabilityMap.data[i] & obstructionMaskLand) ? 0 : 255; waterPassMap[i] = (this.passabilityMap.data[i] & obstructionMaskWater) ? 0 : 255; } Engine.DumpImage("LandPassMap.png", landPassMap, this.passabilityMap.width, this.passabilityMap.height, 255); Engine.DumpImage("WaterPassMap.png", waterPassMap, this.passabilityMap.width, this.passabilityMap.height, 255); */ this._entities = new Map(); if (state.entities) for (let id in state.entities) this._entities.set(+id, new m.Entity(this, state.entities[id])); // entity collection updated on create/destroy event. this.entities = new m.EntityCollection(this, this._entities); // create the terrain analyzer this.terrainAnalyzer = new m.TerrainAnalysis(); this.terrainAnalyzer.init(this, state); this.accessibility = new m.Accessibility(); this.accessibility.init(state, this.terrainAnalyzer); // Resource types: ignore = not used for resource maps // abundant = abundant resource with small amount each // sparse = sparse resource, but huge amount each // The following maps are defined in TerrainAnalysis.js and are used for some building placement (cc, dropsites) // They are updated by checking for create and destroy events for all resources this.normalizationFactor = { "abundant": 50, "sparse": 90 }; this.influenceRadius = { "abundant": 36, "sparse": 48 }; this.ccInfluenceRadius = { "abundant": 60, "sparse": 120 }; this.resourceMaps = {}; // Contains maps showing the density of resources this.ccResourceMaps = {}; // Contains maps showing the density of resources, optimized for CC placement. this.createResourceMaps(); this.gameState = {}; for (let player of this._players) { this.gameState[player] = new m.GameState(); this.gameState[player].init(this, state, player); } }; /** * General update of the shared script, before each AI's update * applies entity deltas, and each gamestate. */ m.SharedScript.prototype.onUpdate = function(state) { if (this.isDeserialized) { this.init(state, true); this.isDeserialized = false; } // deals with updating based on create and destroy messages. this.ApplyEntitiesDelta(state); this.ApplyTemplatesDelta(state); Engine.ProfileStart("onUpdate"); // those are dynamic and need to be reset as the "state" object moves in memory. this.events = state.events; this.passabilityClasses = state.passabilityClasses; this.playersData = state.players; this.timeElapsed = state.timeElapsed; this.barterPrices = state.barterPrices; this.ceasefireActive = state.ceasefireActive; this.ceasefireTimeRemaining = state.ceasefireTimeRemaining / 1000; this.passabilityMap = state.passabilityMap; this.passabilityMap.cellSize = this.mapSize / this.passabilityMap.width; this.territoryMap = state.territoryMap; this.territoryMap.cellSize = this.mapSize / this.territoryMap.width; for (let i in this.gameState) this.gameState[i].update(this); // TODO: merge this with "ApplyEntitiesDelta" since after all they do the same. this.updateResourceMaps(this.events); Engine.ProfileStop(); }; m.SharedScript.prototype.ApplyEntitiesDelta = function(state) { Engine.ProfileStart("Shared ApplyEntitiesDelta"); let foundationFinished = {}; // by order of updating: // we "Destroy" last because we want to be able to switch Metadata first. for (let evt of state.events.Create) { if (!state.entities[evt.entity]) continue; // Sometimes there are things like foundations which get destroyed too fast let entity = new m.Entity(this, state.entities[evt.entity]); this._entities.set(evt.entity, entity); this.entities.addEnt(entity); // Update all the entity collections since the create operation affects static properties as well as dynamic for (let entCol of this._entityCollections.values()) entCol.updateEnt(entity); } for (let evt of state.events.EntityRenamed) { // Switch the metadata: TODO entityCollections are updated only because of the owner change. Should be done properly for (let player of this._players) { this._entityMetadata[player][evt.newentity] = this._entityMetadata[player][evt.entity]; this._entityMetadata[player][evt.entity] = {}; } } for (let evt of state.events.TrainingFinished) { // Apply metadata stored in training queues for (let entId of evt.entities) if (this._entities.has(entId)) for (let key in evt.metadata) this.setMetadata(evt.owner, this._entities.get(entId), key, evt.metadata[key]); } for (let evt of state.events.ConstructionFinished) { // metada are already moved by EntityRenamed when needed (i.e. construction, not repair) if (evt.entity != evt.newentity) foundationFinished[evt.entity] = true; } for (let evt of state.events.AIMetadata) { if (!this._entities.has(evt.id)) continue; // might happen in some rare cases of foundations getting destroyed, perhaps. // Apply metadata (here for buildings for example) for (let key in evt.metadata) this.setMetadata(evt.owner, this._entities.get(evt.id), key, evt.metadata[key]); } for (let evt of state.events.Destroy) { if (!this._entities.has(evt.entity)) continue;// probably should remove the event. if (foundationFinished[evt.entity]) evt.SuccessfulFoundation = true; // The entity was destroyed but its data may still be useful, so // remember the entity and this AI's metadata concerning it evt.metadata = {}; evt.entityObj = this._entities.get(evt.entity); for (let player of this._players) evt.metadata[player] = this._entityMetadata[player][evt.entity]; let entity = this._entities.get(evt.entity); for (let entCol of this._entityCollections.values()) entCol.removeEnt(entity); this.entities.removeEnt(entity); this._entities.delete(evt.entity); this._entitiesModifications.delete(evt.entity); for (let player of this._players) delete this._entityMetadata[player][evt.entity]; } for (let id in state.entities) { let changes = state.entities[id]; let entity = this._entities.get(+id); for (let prop in changes) { entity._entity[prop] = changes[prop]; this.updateEntityCollections(prop, entity); } } // apply per-entity aura-related changes. // this supersedes tech-related changes. for (let id in state.changedEntityTemplateInfo) { if (!this._entities.has(+id)) continue; // dead, presumably. let changes = state.changedEntityTemplateInfo[id]; if (!this._entitiesModifications.has(+id)) this._entitiesModifications.set(+id, new Map()); let modif = this._entitiesModifications.get(+id); for (let change of changes) modif.set(change.variable, change.value); } Engine.ProfileStop(); }; m.SharedScript.prototype.ApplyTemplatesDelta = function(state) { Engine.ProfileStart("Shared ApplyTemplatesDelta"); for (let player in state.changedTemplateInfo) { let playerDiff = state.changedTemplateInfo[player]; for (let template in playerDiff) { let changes = playerDiff[template]; if (!this._templatesModifications[template]) this._templatesModifications[template] = {}; if (!this._templatesModifications[template][player]) this._templatesModifications[template][player] = new Map(); let modif = this._templatesModifications[template][player]; for (let change of changes) modif.set(change.variable, change.value); } } Engine.ProfileStop(); }; m.SharedScript.prototype.registerUpdatingEntityCollection = function(entCollection) { entCollection.setUID(this._entityCollectionsUID); this._entityCollections.set(this._entityCollectionsUID, entCollection); for (let prop of entCollection.dynamicProperties()) { if (!this._entityCollectionsByDynProp[prop]) this._entityCollectionsByDynProp[prop] = new Map(); this._entityCollectionsByDynProp[prop].set(this._entityCollectionsUID, entCollection); } this._entityCollectionsUID++; }; m.SharedScript.prototype.removeUpdatingEntityCollection = function(entCollection) { let uid = entCollection.getUID(); if (this._entityCollections.has(uid)) this._entityCollections.delete(uid); for (let prop of entCollection.dynamicProperties()) if (this._entityCollectionsByDynProp[prop].has(uid)) this._entityCollectionsByDynProp[prop].delete(uid); }; m.SharedScript.prototype.updateEntityCollections = function(property, ent) { if (this._entityCollectionsByDynProp[property] === undefined) return; for (let entCol of this._entityCollectionsByDynProp[property].values()) entCol.updateEnt(ent); }; m.SharedScript.prototype.setMetadata = function(player, ent, key, value) { let metadata = this._entityMetadata[player][ent.id()]; if (!metadata) { this._entityMetadata[player][ent.id()] = {}; metadata = this._entityMetadata[player][ent.id()]; } metadata[key] = value; this.updateEntityCollections('metadata', ent); this.updateEntityCollections('metadata.' + key, ent); }; m.SharedScript.prototype.getMetadata = function(player, ent, key) { let metadata = this._entityMetadata[player][ent.id()]; if (!metadata || !(key in metadata)) return undefined; return metadata[key]; }; m.SharedScript.prototype.deleteMetadata = function(player, ent, key) { let metadata = this._entityMetadata[player][ent.id()]; if (!metadata || !(key in metadata)) return true; metadata[key] = undefined; delete metadata[key]; this.updateEntityCollections('metadata', ent); this.updateEntityCollections('metadata.' + key, ent); return true; }; m.copyPrototype = function(descendant, parent) { let sConstructor = parent.toString(); let aMatch = sConstructor.match(/\s*function (.*)\(/); if (aMatch != null) descendant.prototype[aMatch[1]] = parent; for (let p in parent.prototype) descendant.prototype[p] = parent.prototype[p]; }; +/** creates a map of resource density */ +m.SharedScript.prototype.createResourceMaps = function() +{ + for (const resource of Resources.GetCodes()) + { + if (this.resourceMaps[resource] || + !(Resources.GetResource(resource).aiAnalysisInfluenceGroup in this.normalizationFactor)) + continue; + // We're creating them 8-bit. Things could go above 255 if there are really tons of resources + // But at that point the precision is not really important anyway. And it saves memory. + this.resourceMaps[resource] = new m.Map(this, "resource"); + this.ccResourceMaps[resource] = new m.Map(this, "resource"); + } + for (const ent of this._entities.values()) + this.addEntityToResourceMap(ent); +}; + +/** + * @param {Object} events - The events from a turn. + */ +m.SharedScript.prototype.updateResourceMaps = function(events) +{ + for (const e of events.Destroy) + if (e.entityObj) + this.removeEntityFromResourceMap(e.entityObj); + + for (const e of events.Create) + if (e.entity && this._entities.has(e.entity)) + this.addEntityToResourceMap(this._entities.get(e.entity)); +}; + +/** + * @param {entity} entity - The entity to add to the resource map. + */ +m.SharedScript.prototype.addEntityToResourceMap = function(entity) +{ + this.changeEntityInResourceMapHelper(entity, 1); +}; + +/** + * @param {entity} entity - The entity to remove from the resource map. + */ +m.SharedScript.prototype.removeEntityFromResourceMap = function(entity) +{ + this.changeEntityInResourceMapHelper(entity, -1); +}; + +/** + * @param {entity} ent - The entity to add to the resource map. + */ +m.SharedScript.prototype.changeEntityInResourceMapHelper = function(ent, multiplication = 1) +{ + if (!ent) + return; + const entPos = ent.position(); + if (!entPos) + return; + const resource = ent.resourceSupplyType()?.generic; + if (!resource || !this.resourceMaps[resource]) + return; + const cellSize = this.resourceMaps[resource].cellSize; + const x = Math.floor(entPos[0] / cellSize); + const y = Math.floor(entPos[1] / cellSize); + const grp = Resources.GetResource(resource).aiAnalysisInfluenceGroup; + const strength = multiplication * ent.resourceSupplyMax() / this.normalizationFactor[grp]; + this.resourceMaps[resource].addInfluence(x, y, this.influenceRadius[grp] / cellSize, strength / 2, "constant"); + this.resourceMaps[resource].addInfluence(x, y, this.influenceRadius[grp] / cellSize, strength / 2); + this.ccResourceMaps[resource].addInfluence(x, y, this.ccInfluenceRadius[grp] / cellSize, strength, "constant"); +}; + return m; }(API3); Index: ps/trunk/binaries/data/mods/public/simulation/ai/common-api/terrain-analysis.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/ai/common-api/terrain-analysis.js (revision 25510) +++ ps/trunk/binaries/data/mods/public/simulation/ai/common-api/terrain-analysis.js (revision 25511) @@ -1,479 +1,385 @@ var API3 = function(m) { /** * TerrainAnalysis, inheriting from the Map Component. * * This creates a suitable passability map. * This is part of the Shared Script, and thus should only be used for things that are non-player specific. * This.map is a map of the world, where particular stuffs are pointed with a value * For example, impassable land is 0, water is 200, areas near tree (ie forest grounds) are 41… * This is intended for use with 8 bit maps for reduced memory usage. * Upgraded from QuantumState's original TerrainAnalysis for qBot. */ m.TerrainAnalysis = function() { }; m.copyPrototype(m.TerrainAnalysis, m.Map); m.TerrainAnalysis.prototype.init = function(sharedScript, rawState) { let passabilityMap = rawState.passabilityMap; this.width = passabilityMap.width; this.height = passabilityMap.height; this.cellSize = passabilityMap.cellSize; let obstructionMaskLand = rawState.passabilityClasses["default-terrain-only"]; let obstructionMaskWater = rawState.passabilityClasses["ship-terrain-only"]; let obstructionTiles = new Uint8Array(passabilityMap.data.length); /* Generated map legend: 0 is impassable 200 is deep water (ie non-passable by land units) 201 is shallow water (passable by land units and water units) 255 is land (or extremely shallow water where ships can't go). */ for (let i = 0; i < passabilityMap.data.length; ++i) { // If impassable for land units, set to 0, else to 255. obstructionTiles[i] = (passabilityMap.data[i] & obstructionMaskLand) ? 0 : 255; if (!(passabilityMap.data[i] & obstructionMaskWater) && obstructionTiles[i] === 0) obstructionTiles[i] = 200; // if navigable and not walkable (ie basic water), set to 200. else if (!(passabilityMap.data[i] & obstructionMaskWater) && obstructionTiles[i] === 255) obstructionTiles[i] = 201; // navigable and walkable. } this.Map(rawState, "passability", obstructionTiles); }; /** * Accessibility inherits from TerrainAnalysis * * This can easily and efficiently determine if any two points are connected. * it can also determine if any point is "probably" reachable, assuming the unit can get close enough * for optimizations it's called after the TerrainAnalyser has finished initializing his map * so this can use the land regions already. */ m.Accessibility = function() { }; m.copyPrototype(m.Accessibility, m.TerrainAnalysis); m.Accessibility.prototype.init = function(rawState, terrainAnalyser) { this.Map(rawState, "passability", terrainAnalyser.map); this.landPassMap = new Uint16Array(terrainAnalyser.length); this.navalPassMap = new Uint16Array(terrainAnalyser.length); this.maxRegions = 65535; this.regionSize = []; this.regionType = []; // "inaccessible", "land" or "water"; // ID of the region associated with an array of region IDs. this.regionLinks = []; // initialized to 0, it's more optimized to start at 1 (I'm checking that if it's not 0, then it's already aprt of a region, don't touch); // However I actually store all unpassable as region 1 (because if I don't, on some maps the toal nb of region is over 256, and it crashes as the mapis 8bit.) // So start at 2. this.regionID = 2; for (let i = 0; i < this.landPassMap.length; ++i) { if (this.map[i] !== 0) { // any non-painted, non-inacessible area. if (this.landPassMap[i] == 0 && this.floodFill(i, this.regionID, false)) this.regionType[this.regionID++] = "land"; if (this.navalPassMap[i] == 0 && this.floodFill(i, this.regionID, true)) this.regionType[this.regionID++] = "water"; } else if (this.landPassMap[i] == 0) { // any non-painted, inacessible area. this.floodFill(i, 1, false); this.floodFill(i, 1, true); } } // calculating region links. Regions only touching diagonaly are not linked. // since we're checking all of them, we'll check from the top left to the bottom right let w = this.width; for (let x = 0; x < this.width-1; ++x) { for (let y = 0; y < this.height-1; ++y) { // checking right. let thisLID = this.landPassMap[x+y*w]; let thisNID = this.navalPassMap[x+y*w]; let rightLID = this.landPassMap[x+1+y*w]; let rightNID = this.navalPassMap[x+1+y*w]; let bottomLID = this.landPassMap[x+y*w+w]; let bottomNID = this.navalPassMap[x+y*w+w]; if (thisLID > 1) { if (rightNID > 1) if (this.regionLinks[thisLID].indexOf(rightNID) === -1) this.regionLinks[thisLID].push(rightNID); if (bottomNID > 1) if (this.regionLinks[thisLID].indexOf(bottomNID) === -1) this.regionLinks[thisLID].push(bottomNID); } if (thisNID > 1) { if (rightLID > 1) if (this.regionLinks[thisNID].indexOf(rightLID) === -1) this.regionLinks[thisNID].push(rightLID); if (bottomLID > 1) if (this.regionLinks[thisNID].indexOf(bottomLID) === -1) this.regionLinks[thisNID].push(bottomLID); if (thisLID > 1) if (this.regionLinks[thisNID].indexOf(thisLID) === -1) this.regionLinks[thisNID].push(thisLID); } } } // Engine.DumpImage("LandPassMap.png", this.landPassMap, this.width, this.height, 255); // Engine.DumpImage("NavalPassMap.png", this.navalPassMap, this.width, this.height, 255); }; m.Accessibility.prototype.getAccessValue = function(position, onWater) { let gamePos = this.gamePosToMapPos(position); if (onWater) return this.navalPassMap[gamePos[0] + this.width*gamePos[1]]; let ret = this.landPassMap[gamePos[0] + this.width*gamePos[1]]; if (ret === 1) { // quick spiral search. let indx = [ [-1, -1], [-1, 0], [-1, 1], [0, 1], [1, 1], [1, 0], [1, -1], [0, -1]]; for (let i of indx) { let id0 = gamePos[0] + i[0]; let id1 = gamePos[1] + i[1]; if (id0 < 0 || id0 >= this.width || id1 < 0 || id1 >= this.width) continue; ret = this.landPassMap[id0 + this.width*id1]; if (ret !== 1) return ret; } } return ret; }; m.Accessibility.prototype.getTrajectTo = function(start, end) { let pstart = this.gamePosToMapPos(start); let istart = pstart[0] + pstart[1]*this.width; let pend = this.gamePosToMapPos(end); let iend = pend[0] + pend[1]*this.width; let onLand = true; if (this.landPassMap[istart] <= 1 && this.navalPassMap[istart] > 1) onLand = false; if (this.landPassMap[istart] <= 1 && this.navalPassMap[istart] <= 1) return false; let endRegion = this.landPassMap[iend]; if (endRegion <= 1 && this.navalPassMap[iend] > 1) endRegion = this.navalPassMap[iend]; else if (endRegion <= 1) return false; let startRegion = onLand ? this.landPassMap[istart] : this.navalPassMap[istart]; return this.getTrajectToIndex(startRegion, endRegion); }; /** * Return a "path" of accessibility indexes from one point to another, including the start and the end indexes * this can tell you what sea zone you need to have a dock on, for example. * assumes a land unit unless start point is over deep water. */ m.Accessibility.prototype.getTrajectToIndex = function(istart, iend) { if (istart === iend) return [istart]; let trajects = new Set(); let explored = new Set(); trajects.add([istart]); explored.add(istart); while (trajects.size) { for (let traj of trajects) { let ilast = traj[traj.length-1]; for (let inew of this.regionLinks[ilast]) { if (inew === iend) return traj.concat(iend); if (explored.has(inew)) continue; trajects.add(traj.concat(inew)); explored.add(inew); } trajects.delete(traj); } } return undefined; }; m.Accessibility.prototype.getRegionSize = function(position, onWater) { let pos = this.gamePosToMapPos(position); let index = pos[0] + pos[1]*this.width; let ID = onWater === true ? this.navalPassMap[index] : this.landPassMap[index]; if (this.regionSize[ID] === undefined) return 0; return this.regionSize[ID]; }; m.Accessibility.prototype.getRegionSizei = function(index, onWater) { if (this.regionSize[this.landPassMap[index]] === undefined && (!onWater || this.regionSize[this.navalPassMap[index]] === undefined)) return 0; if (onWater && this.regionSize[this.navalPassMap[index]] > this.regionSize[this.landPassMap[index]]) return this.regionSize[this.navalPassMap[index]]; return this.regionSize[this.landPassMap[index]]; }; /** Implementation of a fast flood fill. Reasonably good performances for JS. */ m.Accessibility.prototype.floodFill = function(startIndex, value, onWater) { if (value > this.maxRegions) { error("AI accessibility map: too many regions."); this.landPassMap[startIndex] = 1; this.navalPassMap[startIndex] = 1; return false; } if (!onWater && this.landPassMap[startIndex] != 0 || onWater && this.navalPassMap[startIndex] != 0) return false; // already painted. let floodFor = "land"; if (this.map[startIndex] === 0) { this.landPassMap[startIndex] = 1; this.navalPassMap[startIndex] = 1; return false; } if (onWater === true) { if (this.map[startIndex] !== 200 && this.map[startIndex] !== 201) { this.navalPassMap[startIndex] = 1; // impassable for naval return false; // do nothing } floodFor = "water"; } else if (this.map[startIndex] === 200) { this.landPassMap[startIndex] = 1; // impassable for land return false; } // here we'll be able to start. for (let i = this.regionSize.length; i <= value; ++i) { this.regionLinks.push([]); this.regionSize.push(0); this.regionType.push("inaccessible"); } let w = this.width; let h = this.height; let y = 0; // Get x and y from index let IndexArray = [startIndex]; let newIndex; while(IndexArray.length) { newIndex = IndexArray.pop(); y = 0; let loop = false; // vertical iteration do { --y; loop = false; let index = newIndex + w*y; if (index < 0) break; if (floodFor === "land" && this.landPassMap[index] === 0 && this.map[index] !== 0 && this.map[index] !== 200) loop = true; else if (floodFor === "water" && this.navalPassMap[index] === 0 && (this.map[index] === 200 || this.map[index] === 201)) loop = true; else break; } while (loop === true); // should actually break ++y; let reachLeft = false; let reachRight = false; let index; do { index = newIndex + w*y; if (floodFor === "land" && this.landPassMap[index] === 0 && this.map[index] !== 0 && this.map[index] !== 200) { this.landPassMap[index] = value; this.regionSize[value]++; } else if (floodFor === "water" && this.navalPassMap[index] === 0 && (this.map[index] === 200 || this.map[index] === 201)) { this.navalPassMap[index] = value; this.regionSize[value]++; } else break; if (index%w > 0) { if (floodFor === "land" && this.landPassMap[index -1] === 0 && this.map[index -1] !== 0 && this.map[index -1] !== 200) { if (!reachLeft) { IndexArray.push(index -1); reachLeft = true; } } else if (floodFor === "water" && this.navalPassMap[index -1] === 0 && (this.map[index -1] === 200 || this.map[index -1] === 201)) { if (!reachLeft) { IndexArray.push(index -1); reachLeft = true; } } else if (reachLeft) reachLeft = false; } if (index%w < w - 1) { if (floodFor === "land" && this.landPassMap[index +1] === 0 && this.map[index +1] !== 0 && this.map[index +1] !== 200) { if (!reachRight) { IndexArray.push(index +1); reachRight = true; } } else if (floodFor === "water" && this.navalPassMap[index +1] === 0 && (this.map[index +1] === 200 || this.map[index +1] === 201)) { if (!reachRight) { IndexArray.push(index +1); reachRight = true; } } else if (reachRight) reachRight = false; } ++y; } while (index/w < h-1); // should actually break } return true; }; -/** creates a map of resource density */ -m.SharedScript.prototype.createResourceMaps = function() -{ - for (let resource of Resources.GetCodes()) - { - if (!(Resources.GetResource(resource).aiAnalysisInfluenceGroup in this.normalizationFactor)) - continue; - // if there is no resourceMap create one with an influence for everything with that resource - if (this.resourceMaps[resource]) - continue; - // We're creating them 8-bit. Things could go above 255 if there are really tons of resources - // But at that point the precision is not really important anyway. And it saves memory. - this.resourceMaps[resource] = new m.Map(this, "resource"); - this.ccResourceMaps[resource] = new m.Map(this, "resource"); - } - for (let ent of this._entities.values()) - { - if (!ent || !ent.position() || !ent.resourceSupplyType()) - continue; - let resource = ent.resourceSupplyType().generic; - if (!this.resourceMaps[resource]) - continue; - let cellSize = this.resourceMaps[resource].cellSize; - let x = Math.floor(ent.position()[0] / cellSize); - let z = Math.floor(ent.position()[1] / cellSize); - let grp = Resources.GetResource(resource).aiAnalysisInfluenceGroup; - let strength = ent.resourceSupplyMax() / this.normalizationFactor[grp]; - this.resourceMaps[resource].addInfluence(x, z, this.influenceRadius[grp] / cellSize, strength/2, "constant"); - this.resourceMaps[resource].addInfluence(x, z, this.influenceRadius[grp] / cellSize, strength/2); - this.ccResourceMaps[resource].addInfluence(x, z, this.ccInfluenceRadius[grp] / cellSize, strength, "constant"); - } -}; - -/** - * creates and maintains a map of unused resource density - * this also takes dropsites into account. - * resources that are "part" of a dropsite are not counted. - */ -m.SharedScript.prototype.updateResourceMaps = function(events) -{ - for (let resource of Resources.GetCodes()) - { - if (!(Resources.GetResource(resource).aiAnalysisInfluenceGroup in this.normalizationFactor)) - continue; - // if there is no resourceMap create one with an influence for everything with that resource - if (this.resourceMaps[resource]) - continue; - // We're creating them 8-bit. Things could go above 255 if there are really tons of resources - // But at that point the precision is not really important anyway. And it saves memory. - this.resourceMaps[resource] = new m.Map(this, "resource"); - this.ccResourceMaps[resource] = new m.Map(this, "resource"); - } - - // Look for destroy (or create) events and subtract (or add) the entities original influence from the resourceMap - for (let e of events.Destroy) - { - if (!e.entityObj) - continue; - let ent = e.entityObj; - if (!ent || !ent.position() || !ent.resourceSupplyType()) - continue; - let resource = ent.resourceSupplyType().generic; - if (!this.resourceMaps[resource]) - continue; - let cellSize = this.resourceMaps[resource].cellSize; - let x = Math.floor(ent.position()[0] / cellSize); - let z = Math.floor(ent.position()[1] / cellSize); - let grp = Resources.GetResource(resource).aiAnalysisInfluenceGroup; - let strength = -(ent.resourceSupplyMax() / this.normalizationFactor[grp]); - this.resourceMaps[resource].addInfluence(x, z, this.influenceRadius[grp] / cellSize, strength/2, "constant"); - this.resourceMaps[resource].addInfluence(x, z, this.influenceRadius[grp] / cellSize, strength/2); - this.ccResourceMaps[resource].addInfluence(x, z, this.ccInfluenceRadius[grp] / cellSize, strength, "constant"); - } - for (let e of events.Create) - { - if (!e.entity || !this._entities.has(e.entity)) - continue; - let ent = this._entities.get(e.entity); - if (!ent || !ent.position() || !ent.resourceSupplyType()) - continue; - let resource = ent.resourceSupplyType().generic; - if (!this.resourceMaps[resource]) - continue; - let cellSize = this.resourceMaps[resource].cellSize; - let x = Math.floor(ent.position()[0] / cellSize); - let z = Math.floor(ent.position()[1] / cellSize); - let grp = Resources.GetResource(resource).aiAnalysisInfluenceGroup; - let strength = ent.resourceSupplyMax() / this.normalizationFactor[grp]; - this.resourceMaps[resource].addInfluence(x, z, this.influenceRadius[grp] / cellSize, strength/2, "constant"); - this.resourceMaps[resource].addInfluence(x, z, this.influenceRadius[grp] / cellSize, strength/2); - this.ccResourceMaps[resource].addInfluence(x, z, this.ccInfluenceRadius[grp] / cellSize, strength, "constant"); - } -}; - return m; }(API3);