private void SetNightMode(TR2CombinedLevel level) { foreach (TR2Room room in level.Data.Rooms) { room.Darken(); } // Replace any entities that don't "make sense" at night List <TR2Entity> entities = level.Data.Entities.ToList(); // A list of item locations to choose from List <TR2Entity> items = entities.Where ( e => TR2EntityUtilities.IsAmmoType((TR2Entities)e.TypeID) || TR2EntityUtilities.IsGunType((TR2Entities)e.TypeID) || TR2EntityUtilities.IsUtilityType((TR2Entities)e.TypeID) ).ToList(); foreach (TR2Entities entityToReplace in _entitiesToReplace.Keys) { IEnumerable <TR2Entity> ents = entities.Where(e => e.TypeID == (short)entityToReplace); foreach (TR2Entity entity in ents) { TR2Entity item = items[_generator.Next(0, items.Count)]; entity.TypeID = (short)_entitiesToReplace[entityToReplace]; entity.Room = item.Room; entity.X = item.X; entity.Y = item.Y; entity.Z = item.Z; entity.Intensity1 = item.Intensity1; entity.Intensity2 = item.Intensity2; } } // Hide any static meshes if (_staticMeshesToHide.ContainsKey(level.Name)) { List <TRStaticMesh> staticMeshes = level.Data.StaticMeshes.ToList(); foreach (uint meshID in _staticMeshesToHide[level.Name]) { TRStaticMesh mesh = staticMeshes.Find(m => m.ID == meshID); if (mesh != null) { mesh.NonCollidable = true; mesh.Visible = false; } } } }
public static bool IsDroppableEnemyRequired(TR2CombinedLevel level) { foreach (TR2Entity entityInstance in level.Data.Entities) { List <TR2Entity> sharedItems = new List <TR2Entity>(Array.FindAll ( level.Data.Entities, e => ( e.X == entityInstance.X && e.Y == entityInstance.Y && e.Z == entityInstance.Z ) )); if (sharedItems.Count > 1) { // Are any entities that are sharing a location a droppable pickup? foreach (TR2Entity ent in sharedItems) { TR2Entities EntType = (TR2Entities)ent.TypeID; if ( TR2EntityUtilities.IsUtilityType(EntType) || TR2EntityUtilities.IsGunType(EntType) || TR2EntityUtilities.IsKeyItemType(EntType) ) { return(true); } } } } return(false); }
private void RandomizeEnemyTypes(string lvl) { List <TR2Entities> EnemyTypes = TR2EntityUtilities.GetEnemyTypeDictionary()[lvl]; for (int i = 0; i < _levelInstance.Entities.Count(); i++) { if (lvl == LevelNames.CHICKEN && _levelInstance.Entities[i].Room == 143 && _levelInstance.Entities[i].TypeID == (int)TR2Entities.BirdMonster) { //#60 - Ice Palace - Room 143 chicken man must remain to avoid softlock. continue; } else if (lvl == LevelNames.HOME && _levelInstance.Entities[i].TypeID == (int)TR2Entities.ShotgunGoon) { //#62 - Avoid randomizing shotgun goon in HSH continue; } //#45 - Check to see if any items are at the same location as the enemy. //If there are we need to ensure that the new random enemy type is one that can drop items. List <TR2Entity> SharedItems = new List <TR2Entity>(Array.FindAll(_levelInstance.Entities, e => ((e.X == _levelInstance.Entities[i].X) && (e.Y == _levelInstance.Entities[i].Y) && (e.Z == _levelInstance.Entities[i].Z)))); List <TR2Entities> DroppableEnemies = TR2EntityUtilities.DroppableEnemyTypes()[lvl]; //Is it an entity we are keen on replacing? if (EnemyTypes.Contains((TR2Entities)_levelInstance.Entities[i].TypeID)) { //Do multiple entities share one location? if ((SharedItems.Count > 1) && (DroppableEnemies.Count != 0)) { //Are any entities sharing a location a droppable pickup? bool IsPickupItem = false; foreach (TR2Entity ent in SharedItems) { TR2Entities EntType = (TR2Entities)ent.TypeID; IsPickupItem = (TR2EntityUtilities.IsUtilityType(EntType)) || (TR2EntityUtilities.IsGunType(EntType)) || (TR2EntityUtilities.IsKeyItemType(EntType)); if (IsPickupItem) { break; } } //Generate a location _levelInstance.Entities[i].TypeID = (short)EnemyTypes[_generator.Next(0, EnemyTypes.Count)]; //Do we need to ensure the enemy can drop the item on the same tile? if (!TR2EntityUtilities.CanDropPickups((TR2Entities)_levelInstance.Entities[i].TypeID, false) && IsPickupItem) { //Ensure the new random entity can drop pickups _levelInstance.Entities[i].TypeID = (short)DroppableEnemies[_generator.Next(0, DroppableEnemies.Count)]; } } else { _levelInstance.Entities[i].TypeID = (short)EnemyTypes[_generator.Next(0, EnemyTypes.Count)]; } short room = _levelInstance.Entities[i].Room; if (!TR2EntityUtilities.IsWaterCreature((TR2Entities)_levelInstance.Entities[i].TypeID) && _levelInstance.Rooms[room].ContainsWater) { if (!PerformDraining(lvl, room)) { //Draining cannot be performed so make the entity a water creature. TR2Entities ent; //Make sure water creature can appear on level do { ent = TR2EntityUtilities.WaterCreatures()[_generator.Next(0, TR2EntityUtilities.WaterCreatures().Count)]; } while (!EnemyTypes.Contains(ent)); _levelInstance.Entities[i].TypeID = (short)ent; } } } } }
private void RandomizeEnemies(TR2CombinedLevel level, EnemyRandomizationCollection enemies) { bool shotgunGoonSeen = level.Is(LevelNames.HOME); // 1 ShotgunGoon in HSH only bool dragonSeen = level.Is(LevelNames.LAIR); // 1 Marco in DL only // Get a list of current enemy entities List <TR2Entity> enemyEntities = level.GetEnemyEntities(); // Keep track of any new entities added (e.g. Skidoo) List <TR2Entity> newEntities = new List <TR2Entity>(); // #148 If it's HSH and we have been able to import cross-level, we will add 15 // dogs outside the gate to ensure the kill counter works. Dogs, Goon1 and // StickGoons will have been excluded from the cross-level pool for simplicity // Their textures will have been removed but they won't spawn anyway as we aren't // defining triggers - the game only needs them to be present in the entity list. if (level.Is(LevelNames.HOME) && !enemies.Available.Contains(TR2Entities.Doberman)) { for (int i = 0; i < 15; i++) { newEntities.Add(new TR2Entity { TypeID = (short)TR2Entities.Doberman, Room = 85, X = 61919, Y = 2560, Z = 74222, Angle = 16384, Flags = 0, Intensity1 = -1, Intensity2 = -1 }); } } // First iterate through any enemies that are restricted by room Dictionary <TR2Entities, List <int> > enemyRooms = EnemyUtilities.GetRestrictedEnemyRooms(level.Name); if (enemyRooms != null) { foreach (TR2Entities entity in enemyRooms.Keys) { if (!enemies.Available.Contains(entity)) { continue; } List <int> rooms = enemyRooms[entity]; int maxEntityCount = EnemyUtilities.GetRestrictedEnemyLevelCount(entity); if (maxEntityCount == -1) { // We are allowed any number, but this can't be more than the number of unique rooms, // so we will assume 1 per room as these restricted enemies are likely to be tanky. maxEntityCount = rooms.Count; } else { maxEntityCount = Math.Min(maxEntityCount, rooms.Count); } // Pick an actual count int enemyCount = _generator.Next(1, maxEntityCount + 1); for (int i = 0; i < enemyCount; i++) { // Find an entity in one of the rooms that the new enemy is restricted to TR2Entity targetEntity = null; do { int room = enemyRooms[entity][_generator.Next(0, enemyRooms[entity].Count)]; targetEntity = enemyEntities.Find(e => e.Room == room); }while (targetEntity == null); targetEntity.TypeID = (short)TR2EntityUtilities.TranslateEntityAlias(entity); // #146 Ensure OneShot triggers are set for this enemy if needed EnemyUtilities.SetEntityTriggers(level.Data, targetEntity); // Remove the target entity so it doesn't get replaced enemyEntities.Remove(targetEntity); } // Remove this entity type from the available rando pool enemies.Available.Remove(entity); } } foreach (TR2Entity currentEntity in enemyEntities) { TR2Entities currentEntityType = (TR2Entities)currentEntity.TypeID; TR2Entities newEntityType = currentEntityType; // If it's an existing enemy that has to remain in the same spot, skip it if (EnemyUtilities.IsEnemyRequired(level.Name, currentEntityType)) { continue; } //#45 - Check to see if any items are at the same location as the enemy. //If there are we need to ensure that the new random enemy type is one that can drop items. List <TR2Entity> sharedItems = new List <TR2Entity>(Array.FindAll ( level.Data.Entities, e => ( e.X == currentEntity.X && e.Y == currentEntity.Y && e.Z == currentEntity.Z ) )); //Do multiple entities share one location? bool isPickupItem = false; if (sharedItems.Count > 1 && enemies.Droppable.Count != 0) { //Are any entities sharing a location a droppable pickup? foreach (TR2Entity ent in sharedItems) { TR2Entities entType = (TR2Entities)ent.TypeID; isPickupItem = TR2EntityUtilities.IsUtilityType(entType) || TR2EntityUtilities.IsGunType(entType) || TR2EntityUtilities.IsKeyItemType(entType); if (isPickupItem) { break; } } //Generate a new type newEntityType = enemies.Available[_generator.Next(0, enemies.Available.Count)]; //Do we need to ensure the enemy can drop the item on the same tile? if (!TR2EntityUtilities.CanDropPickups(newEntityType, !ProtectMonks) && isPickupItem) { //Ensure the new random entity can drop pickups newEntityType = enemies.Droppable[_generator.Next(0, enemies.Droppable.Count)]; } } else { //Generate a new type newEntityType = enemies.Available[_generator.Next(0, enemies.Available.Count)]; } short roomIndex = currentEntity.Room; TR2Room room = level.Data.Rooms[roomIndex]; if (level.Is(LevelNames.DA) && roomIndex == 77) { // Make sure the end level trigger isn't blocked by an unkillable enemy while (TR2EntityUtilities.IsHazardCreature(newEntityType) || (ProtectMonks && TR2EntityUtilities.IsMonk(newEntityType))) { newEntityType = enemies.Available[_generator.Next(0, enemies.Available.Count)]; } } if (TR2EntityUtilities.IsWaterCreature(currentEntityType) && !TR2EntityUtilities.IsWaterCreature(newEntityType)) { // Check alternate rooms too - e.g. rooms 74/48 in 40 Fathoms short roomDrainIndex = -1; if (room.ContainsWater) { roomDrainIndex = roomIndex; } else if (room.AlternateRoom != -1 && level.Data.Rooms[room.AlternateRoom].ContainsWater) { roomDrainIndex = room.AlternateRoom; } if (roomDrainIndex != -1 && !level.PerformDraining(roomDrainIndex)) { // Draining cannot be performed so make the entity a water creature. // The list of provided water creatures will either be those native // to this level, or if randomizing cross-level, a pre-check will // have already been performed on draining so if it's not possible, // at least one water creature will be available. newEntityType = enemies.Water[_generator.Next(0, enemies.Water.Count)]; } } // Ensure that if we have to pick a different enemy at this point that we still // honour any pickups in the same spot. List <TR2Entities> enemyPool = isPickupItem ? enemies.Droppable : enemies.Available; if (newEntityType == TR2Entities.ShotgunGoon && shotgunGoonSeen) // HSH only { while (newEntityType == TR2Entities.ShotgunGoon) { newEntityType = enemyPool[_generator.Next(0, enemyPool.Count)]; } } if (newEntityType == TR2Entities.MarcoBartoli && dragonSeen) // DL only, other levels use quasi-zoning for the dragon { while (newEntityType == TR2Entities.MarcoBartoli) { newEntityType = enemyPool[_generator.Next(0, enemyPool.Count)]; } } // If we are restricting count per level for this enemy and have reached that count, pick // someting else. This applies when we are restricting by in-level count, but not by room // (e.g. Winston). int maxEntityCount = EnemyUtilities.GetRestrictedEnemyLevelCount(newEntityType); if (maxEntityCount != -1) { if (level.Data.Entities.ToList().FindAll(e => e.TypeID == (short)newEntityType).Count == maxEntityCount) { TR2Entities tmp = newEntityType; while (newEntityType == tmp) { newEntityType = enemyPool[_generator.Next(0, enemyPool.Count)]; } } } // #158 Several entity freezing issues have been found with various enemy // combinations in Barkhang, so for now all Mercenary1 and MonkWithLongStick // entities must remain in place, and no additional ones should be added. if (level.Is(LevelNames.MONASTERY)) { while (EnemyUtilities.IsEnemyRequired(level.Name, newEntityType)) { newEntityType = enemyPool[_generator.Next(0, enemyPool.Count)]; } } // #144 Disguise something as the Chicken. Pre-checks will have been done to ensure // the guiser is suitable for the level. if (DocileBirdMonsters && newEntityType == TR2Entities.BirdMonster) { newEntityType = enemies.BirdMonsterGuiser; } // Make sure to convert BengalTiger, StickWieldingGoonBandana etc back to their actual types currentEntity.TypeID = (short)TR2EntityUtilities.TranslateEntityAlias(newEntityType); // #146 Ensure OneShot triggers are set for this enemy if needed. This currently only applies // to the dragon, which will be handled above in defined rooms, but the check should be made // here in case this needs to be extended later. EnemyUtilities.SetEntityTriggers(level.Data, currentEntity); } // MercSnowMobDriver relies on RedSnowmobile so it will be available in the model list if (!level.Is(LevelNames.TIBET)) { TR2Entity mercDriver = level.Data.Entities.ToList().Find(e => e.TypeID == (short)TR2Entities.MercSnowmobDriver); if (mercDriver != null) { short room, angle; int x, y, z; // we will only spawn one skidoo, so only need one random location Location randomLocation = VehicleUtilities.GetRandomLocation(level.Name, TR2Entities.RedSnowmobile, _generator); if (randomLocation != null) { room = (short)randomLocation.Room; x = randomLocation.X; y = randomLocation.Y; z = randomLocation.Z; angle = randomLocation.Angle; } else { // if the level does not have skidoo locations for some reason, just spawn it on the MercSnowMobDriver room = mercDriver.Room; x = mercDriver.X; y = mercDriver.Y; z = mercDriver.Z; angle = mercDriver.Angle; } newEntities.Add(new TR2Entity { TypeID = (short)TR2Entities.RedSnowmobile, Room = room, X = x, Y = y, Z = z, Angle = angle, Flags = 0, Intensity1 = -1, Intensity2 = -1 }); } } // Did we add any new entities? if (newEntities.Count > 0) { List <TR2Entity> levelEntities = level.Data.Entities.ToList(); levelEntities.AddRange(newEntities); level.Data.Entities = levelEntities.ToArray(); level.Data.NumEntities = (uint)levelEntities.Count; } }