Line data Source code
1 : /** 2 : * @param {Array} templates - Each Item is an Object that contains the properties "templateName" and optionally "margin," "constraints" and "painters". 3 : */ 4 : function CityPainter(templates, angle, playerID) 5 : { 6 0 : this.angle = angle; 7 0 : this.playerID = playerID; 8 0 : this.templates = templates.map(template => { 9 : 10 0 : let obstructionSize = getObstructionSize(template.templateName, template.margin || 0); 11 0 : return { 12 : "templateName": template.templateName, 13 : "maxCount": template.maxCount !== undefined ? template.maxCount : Infinity, 14 : "constraint": template.constraints && new AndConstraint(template.constraints), 15 : "painter": template.painters && new MultiPainter(template.painters), 16 : "obstructionCorners": [ 17 : new Vector2D(0, 0), 18 : new Vector2D(obstructionSize.x, 0), 19 : new Vector2D(0, obstructionSize.y), 20 : obstructionSize 21 : ] 22 : }; 23 : }); 24 : } 25 : 26 6 : CityPainter.prototype.paint = function(area) 27 : { 28 0 : let templates = this.templates; 29 : 30 0 : let templateCounts = {}; 31 0 : for (let template of this.templates) 32 0 : templateCounts[template.templateName] = 0; 33 : 34 0 : let mapCenter = g_Map.getCenter(); 35 0 : let mapSize = g_Map.getSize(); 36 : 37 : // TODO: Due to the rounding, this is wasting a lot of space. 38 : // The city would be much denser if it would test for actual shape intersection or 39 : // if it would use a custom, more fine-grained obstruction grid 40 0 : let tileClass = g_Map.createTileClass(); 41 : 42 0 : let processed = new Array(mapSize).fill(0).map(() => new Uint8Array(mapSize)); 43 : 44 0 : for (let x = 0; x < mapSize; x += 0.5) 45 0 : for (let y = 0; y < mapSize; y += 0.5) 46 : { 47 0 : let point = new Vector2D(x, y).rotateAround(this.angle, mapCenter).round(); 48 0 : if (!area.contains(point) || processed[point.x][point.y] || !g_Map.validTilePassable(point)) 49 0 : continue; 50 : 51 0 : processed[point.x][point.y] = 1; 52 : 53 0 : for (let template of shuffleArray(templates)) 54 : { 55 0 : if (template.constraint && !template.constraint.allows(point)) 56 0 : continue; 57 : 58 : // Randomize building angle while keeping it aligned 59 0 : let buildingAngle = this.angle + randIntInclusive(0, 3) * Math.PI / 2; 60 : 61 : // Place the entity if all points are within the boundaries and don't collide with the other entities 62 0 : let obstructionCorners = template.obstructionCorners.map(obstructionCorner => 63 0 : Vector2D.add(point, obstructionCorner.clone().rotate(buildingAngle))); 64 : 65 0 : let obstructionPoints = new ConvexPolygonPlacer(obstructionCorners, 0).place( 66 : new AndConstraint([new StayAreasConstraint([area]), avoidClasses(tileClass, 0), new PassableMapAreaConstraint()])); 67 : 68 0 : if (!obstructionPoints) 69 0 : continue; 70 : 71 0 : g_Map.placeEntityPassable(template.templateName, this.playerID, Vector2D.average(obstructionCorners), -buildingAngle); 72 : 73 0 : if (template.painter) 74 0 : template.painter.paint(new Area(obstructionPoints)); 75 : 76 0 : for (let obstructionPoint of obstructionPoints) 77 0 : tileClass.add(obstructionPoint); 78 : 79 0 : ++templateCounts[template.templateName]; 80 0 : templates = templates.filter(template => templateCounts[template.templateName] < template.maxCount); 81 0 : break; 82 : } 83 : } 84 : };