private void LoadBlueprint() { Debug.Log(Debug.BlueprintTransfer, "Loading blueprint at path {0}", snapshotName); string deflatedName = snapshotName; if (Path.GetExtension(snapshotName).Equals(".bp")) { deflatedName = snapshotName + ".xml"; if (!File.Exists(deflatedName)) { string data = Compressor.UnzipFile(snapshotName); File.WriteAllText(deflatedName, data); } } XmlDocument snapshot = new XmlDocument(); snapshot.Load(deflatedName); XmlNodeList elemList = snapshot.GetElementsByTagName("cell"); int blueprintWidth = int.Parse(snapshot.FirstChild.Attributes["width"].Value); int blueprintHeight = int.Parse(snapshot.FirstChild.Attributes["height"].Value); Version blueprintVersion = new Version(snapshot.FirstChild?.Attributes["version"]?.Value ?? "0.0.0.0"); blueprint = new Blueprint(blueprintWidth, blueprintHeight, blueprintVersion); //Snapshot year is an in-game year when snapshot was taken. Thus, all corpse ages, death times, art events and so on are in between of 5500 and [snapshotYear] blueprint.snapshotYear = int.Parse(snapshot.FirstChild.Attributes["inGameYear"]?.Value ?? "5600"); //To prevent artifacts from future we need to shift all dates by some number to the past by _at_least_ (snaphotYear - 5500) years int itemNodes = 0; int terrainNodes = 0; foreach (XmlNode cellNode in elemList) { int x = int.Parse(cellNode.Attributes["x"].Value); int z = int.Parse(cellNode.Attributes["z"].Value); blueprint.itemsMap[x, z] = new List <ItemTile>(); foreach (XmlNode cellElement in cellNode.ChildNodes) { try { if (cellElement.Name.Equals("terrain")) { terrainNodes++; TerrainTile terrain = new TerrainTile(cellElement); terrain.location = new IntVec3(x, 0, z); blueprint.terrainMap[x, z] = terrain; } else if (cellElement.Name.Equals("item")) { itemNodes++; ItemTile tile = new ItemTile(cellElement); //replace all collapsed rocks with walls if (tile.defName == ThingDefOf.CollapsedRocks.defName) { tile = ItemTile.WallReplacementItemTile(tile.location); } if (tile.defName == ThingDefOf.MinifiedThing.defName && (tile.innerItems?.Count() ?? 0) == 0) { continue; //skip minified things with no inner items } //Trying to load corresponding definition to check if the object is accessible ThingDef thingDef = DefDatabase <ThingDef> .GetNamed(tile.defName, false); if (thingDef != null) { if (thingDef.fillPercent == 1.0f || tile.isWall || tile.isDoor) { blueprint.wallMap[x, z] = -1; //place wall } tile.stackCount = Math.Min(thingDef.stackLimit, tile.stackCount); //limit stack to max stack size to correctly calculate weight and cost later tile.location = new IntVec3(x, 0, z); blueprint.itemsMap[x, z].Add(tile); //save item if it's def is valid. } else { if (tile.isDoor) //replacing unavailable door with abstract default door { tile.defName = ThingDefOf.Door.defName; tile.location = new IntVec3(x, 0, z); blueprint.itemsMap[x, z].Add(tile); //replacement door is ok } else if (tile.isWall || tile.defName.ToLower().Contains("wall")) //replacing unavailable impassable 100% filling block (which was likely a wall) with a wall { tile.defName = ThingDefOf.Wall.defName; tile.location = new IntVec3(x, 0, z); blueprint.itemsMap[x, z].Add(tile); //now it's a wall } else if (tile.defName == "Corpse") { tile.location = new IntVec3(x, 0, z); blueprint.itemsMap[x, z].Add(tile); // corpse is ok } } } else if (cellElement.Name.Equals("roof")) { blueprint.roofMap[x, z] = true; } } catch (Exception) { //ignore invalid or unloadable cells } } } }
public void RaidAndScavenge(Blueprint blueprint, ScatterOptions options) { //remove the most precious things. smash some other things. //word is spread, so each next raid is more destructive than the previous ones //to make calculations a bit easier we're going to calculate value per cell, not per item. this.options = options; this.blueprint = blueprint; Debug.active = false; float scavengersActivity = Rand.Value * options.scavengingMultiplier + (options.scavengingMultiplier) / 3; //slight variation for scavengers activity for this particular blueprint float elapsedTime = -blueprint.dateShift; int totalRemovedTiles = 0; int totalRemovedTerrains = 0; int totalReplacedTiles = 0; int processedTiles = 0; int processedTerrains = 0; for (int x = 0; x < blueprint.width; x++) { for (int z = 0; z < blueprint.height; z++) { if (blueprint.terrainMap[x, z] != null) { tilesByCost.Add(blueprint.terrainMap[x, z]); totalCost += blueprint.terrainMap[x, z].cost; processedTerrains++; } foreach (ItemTile item in blueprint.itemsMap[x, z]) { tilesByCost.Add(item); totalCost += item.cost; processedTiles++; } } } tilesByCost.Sort(delegate(Tile t1, Tile t2) { return((t1.cost / t1.weight).CompareTo(t2.cost / t2.weight)); }); int ruinsArea = blueprint.width * blueprint.height; //Debug.Message("Scavenging blueprint of area {0}, age {1}, scavengers activity multiplier {2}", ruinsArea, elapsedTime, scavengersActivity); //Debug.Message("Enumerated {0} items", tilesByCost.Count()); //Debug.PrintArray(tilesByCost.ToArray()); int raidsCount = (int)(Math.Log(elapsedTime / 10 + 1) * scavengersActivity); if (raidsCount > 50) { raidsCount = 50; } if (options.scavengingMultiplier > 0.9f && raidsCount <= 0) { raidsCount = 1; //at least one raid for each ruins in case of normal scavenging activity } float baseRaidCapacity = ruinsArea / 10 * scavengersActivity; Debug.Log(Debug.BlueprintTransfer, "Performing {0} raids. Base capacity: {1}", raidsCount, baseRaidCapacity); for (int i = 0; i < raidsCount; i++) { float raidCapacity = baseRaidCapacity * (float)Math.Pow(1.1, i); bool shouldStop = false; Debug.Log(Debug.BlueprintTransfer, "Performing raid {0} of capacity {1}", i, raidCapacity); while (tilesByCost.Count > 0 && raidCapacity > 0 && !shouldStop) { Tile topTile = tilesByCost.Pop(); String msg = string.Format("Inspecting tile \"{0}\" of cost {1} and weight {2}. ", topTile.defName, topTile.cost, topTile.weight); if (topTile.cost / topTile.weight < 7) { shouldStop = true; //nothing to do here, everything valueable has already gone msg += "Too cheap, stopping."; } else { if (Rand.Chance(0.999f)) //there is still chance that even the most expensive thing will be left after raid. ("Big momma said ya shouldn't touch that golden chair, it's cursed") { raidCapacity -= topTile.weight; if (topTile is TerrainTile) { blueprint.terrainMap[topTile.location.x, topTile.location.z] = null; totalRemovedTerrains++; totalCost -= topTile.cost; msg += "Terrain removed."; } else if (topTile is ItemTile) { ItemTile itemTile = topTile as ItemTile; totalCost -= itemTile.cost; blueprint.itemsMap[topTile.location.x, topTile.location.z].Remove(itemTile); if (itemTile.isDoor) //if door is removed it should be replaced with another door, raiders are very polite and always replace expensive doors with cheaper ones. { if (Rand.Chance(0.8f)) //ok, not always. { ItemTile replacementTile = ItemTile.DefaultDoorItemTile(itemTile.location); blueprint.itemsMap[topTile.location.x, topTile.location.z].Add(replacementTile); msg += "Added " + replacementTile.defName + ", original "; totalReplacedTiles++; } else { totalRemovedTiles++; blueprint.RemoveWall(itemTile.location.x, itemTile.location.z); } } else if (itemTile.isWall) //if something like a wall removed (vent or aircon) you usually want to cover the hole to keep wall integrity { ItemTile replacementTile = ItemTile.WallReplacementItemTile(itemTile.location); blueprint.itemsMap[topTile.location.x, topTile.location.z].Add(replacementTile); msg += "Added " + replacementTile.defName + ", original "; totalReplacedTiles++; } else { totalRemovedTiles++; } msg += "Tile removed."; } } } Debug.Log(Debug.BlueprintTransfer, msg); if (shouldStop) { break; } } if (shouldStop) { break; } } //Check that there are no "hanging doors" left bool HasWallsIn(List <ItemTile> list) { foreach (ItemTile tile in list) { if (tile.isWall) { return(true); } } return(false); } for (int z = 1; z < blueprint.height - 1; z++) { for (int x = 1; x < blueprint.width - 1; x++) { ItemTile tileToRemove = null; foreach (ItemTile tile in blueprint.itemsMap[x, z]) { if (tile.isDoor) //check if a particular door tile has both two vertically adjacent walls (or similar) or two horizintally adjacent walls { if (!(HasWallsIn(blueprint.itemsMap[x - 1, z]) && HasWallsIn(blueprint.itemsMap[x + 1, z])) && !(HasWallsIn(blueprint.itemsMap[x, z - 1]) && HasWallsIn(blueprint.itemsMap[x, z + 1]))) { tileToRemove = tile; break; } } } if (tileToRemove != null) { blueprint.itemsMap[x, z].Remove(tileToRemove); totalCost -= tileToRemove.cost; blueprint.RemoveWall(x, z); } } } Debug.active = true; //Debug.Message("Scavenging completed. Terrain removed: {0}/{1}, Tiles removed: {2}/{3}, tiles replaced: {4}.", totalRemovedTerrains, processedTerrains, totalRemovedTiles, processedTiles, totalReplacedTiles); if (options.costCap > 0) { LimitCostToCap(); } }