private static void InitializeMapAndAddBorderWalls(EncounterState state, int width, int height) { // Initialize the map with empty tiles state.MapWidth = width; state.MapHeight = height; state._encounterTiles = new EncounterTile[width, height]; for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { state._encounterTiles[x, y] = new EncounterTile(); } } // Create border walls to prevent objects running off the map for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { if (x == 0 || x == width - 1 || y == 0 || y == height - 1) { state.PlaceEntity(EntityBuilder.CreateEdgeBlockerEntity(), new EncounterPosition(x, y)); } } } }
private bool ShouldHighlightEntity(EncounterState state, Entity entity, EncounterPosition entityPos, int nextPlayerTick) { var aiComponent = entity.GetComponent <AIComponent>(); return(aiComponent != null && !(aiComponent is PathAIComponent) && state.FoVCache.IsVisible(entityPos) && entity.GetComponent <ActionTimeComponent>().NextTurnAtTick < nextPlayerTick); }
/** * Displays the danger map on the "DangerMap" TileMap. Sums the probable (get to that in a moment) damage the player would * take from being on the given square on their next turn. * * "Danger" is kind of tricky, because the possible danger can be influenced by enemy movement. For example, a railgun shot * can be intercepted by a scout, rendering all tiles in its path 'safe', but we have no idea if the scout will or won't move * onto that square. Our current rule is "You can never get hit on a safe square, but can sometimes avoid getting hit on a * dangerous square." So, we stop a projectile path if it would hit a satellite, but don't stop if it would hit a cruiser, * even if the cruiser wouldn't have time to move, because it might be destroyed by another projectile and then let the * projectile pass. * * While you could theoretically simulate to the maximum possible fidelity, such that you don't paint it if the cruiser has * HP than it could possibly take in a round and do if it doesn't, that doesn't seem worth it. */ public void UpdateAllTiles(EncounterState state) { var pathEntities = GetTree().GetNodesInGroup(PathAIComponent.ENTITY_GROUP); var timeToNextPlayerMove = state.Player.GetComponent <SpeedComponent>().Speed; var positionsToPotentialDamage = new Dictionary <EncounterPosition, int>(); // Fill the TileMap as appropriate & tally positionsToPotentialDamage this.Clear(); // TODO: We don't actually need to update every entity, every time, since we only need to set the cell when the projectile itself moves foreach (Entity pathEntity in pathEntities) { int attackerPower = GetAttackerPower(pathEntity); var dangerPositions = CalcDangerPositions(pathEntity, timeToNextPlayerMove); RotateSpriteTowardsDestination(pathEntity, dangerPositions); foreach (EncounterPosition dangerPosition in dangerPositions.Where(p => state.FoVCache.IsVisible(p))) { // If we have a fully immobile, invincible entity at the position we stop the path - otherwise we still draw it. var blockingEntity = state.BlockingEntityAtPosition(dangerPosition.X, dangerPosition.Y); if (blockingEntity != null && blockingEntity.GetComponent <ActionTimeComponent>() == null && blockingEntity.GetComponent <DefenderComponent>() != null && blockingEntity.GetComponent <DefenderComponent>().IsInvincible) { break; } if (positionsToPotentialDamage.ContainsKey(dangerPosition)) { positionsToPotentialDamage[dangerPosition] += attackerPower; } else { positionsToPotentialDamage[dangerPosition] = attackerPower; } this.SetCell(dangerPosition.X, dangerPosition.Y, 0); } } // Draw the damage numbers // If all this creation/deletion is a significant source of slowdown you can make an object pool, max size FoW area tiles foreach (var damageLabel in this._damageLabels) { this.RemoveChild(damageLabel); damageLabel.QueueFree(); } _damageLabels.Clear(); foreach (var pair in positionsToPotentialDamage) { var label = CreateLabel(pair.Value, pair.Key); this._damageLabels.Add(label); this.AddChild(label); } this.HighlightEnemies(state, timeToNextPlayerMove); }
public static FoVCache ComputeFoV(EncounterState state, EncounterPosition center, int radius) { int _x = center.X - radius; int _y = center.Y - radius; bool[,] visibleCells = new bool[radius * 2 + 1, radius * 2 + 1]; var visible = rpasCalc.CalcVisibleCellsFrom(center.X, center.Y, radius, state.IsPositionVisible); foreach ((int, int)pos in visible) { if (state.IsInBounds(pos.Item1, pos.Item2)) { visibleCells[pos.Item1 - _x, pos.Item2 - _y] = true; } } return(new FoVCache(_x, _y, visibleCells)); }
private void HighlightEnemies(EncounterState state, int timeToNextPlayerMove) { var nextPlayerTick = state.Player.GetComponent <ActionTimeComponent>().NextTurnAtTick + timeToNextPlayerMove; var actionEntities = state.ActionEntities(); foreach (Entity actionEntity in actionEntities) { var actionEntityPos = actionEntity.GetComponent <PositionComponent>().EncounterPosition; if (ShouldHighlightEntity(state, actionEntity, actionEntityPos, nextPlayerTick)) { if (this.GetCell(actionEntityPos.X, actionEntityPos.Y) == 0) { this.SetCell(actionEntityPos.X, actionEntityPos.Y, 2); } else { this.SetCell(actionEntityPos.X, actionEntityPos.Y, 1); } } } }
public static void PopulateStateForLevel(Entity player, int dungeonLevel, EncounterState state, Random seededRand, int width = 300, int height = 300, int maxZoneGenAttempts = 100) { InitializeMapAndAddBorderWalls(state, width, height); // Place each empty zone onto the map int zoneGenAttemps = 0; List <EncounterZone> zones = new List <EncounterZone>(); while (zoneGenAttemps < maxZoneGenAttempts && zones.Count < LevelData.GetNumberOfZones(dungeonLevel)) { int zoneWidth = seededRand.Next(ZONE_MIN_SIZE, ZONE_MAX_SIZE + 1); int zoneHeight = seededRand.Next(ZONE_MIN_SIZE, ZONE_MAX_SIZE + 1); int zoneX = seededRand.Next(1, state.MapWidth - zoneWidth); int zoneY = seededRand.Next(1, state.MapHeight - zoneHeight); var newZone = new EncounterZone(Guid.NewGuid().ToString(), new EncounterPosition(zoneX, zoneY), zoneWidth, zoneHeight, "Zone " + zones.Count.ToString()); bool overlaps = zones.Any(existing => existing.Intersects(newZone)); if (!overlaps) { zones.Add(newZone); } } state._zones = zones; // Add the player to the map var playerZoneIdx = seededRand.Next(0, zones.Count); state.PlaceEntity(player, zones[playerZoneIdx].Center); /* * var nextToPlayer = new EncounterPosition(zones[playerZoneIdx].Center.X + 2, zones[playerZoneIdx].Center.Y + 1); * state.PlaceEntity(EntityBuilder.CreateItemByEntityDefId(EntityDefId.ITEM_RED_PAINT), nextToPlayer); * nextToPlayer = new EncounterPosition(zones[playerZoneIdx].Center.X + 1, zones[playerZoneIdx].Center.Y + 1); * state.PlaceEntity(EntityBuilder.CreateItemByEntityDefId(EntityDefId.ITEM_EMP), nextToPlayer); * for (int i = 0; i < 26; i++) { * nextToPlayer = new EncounterPosition(zones[playerZoneIdx].Center.X + i, zones[playerZoneIdx].Center.Y + 3); * state.PlaceEntity(EntityBuilder.CreateItemByEntityDefId(EntityDefId.ITEM_EXTRA_BATTERY), nextToPlayer); * } */ var nextToPlayer = new EncounterPosition(zones[playerZoneIdx].Center.X + 2, zones[playerZoneIdx].Center.Y + 2); //string[] defs = new string[] { EntityDefId.SCOUT, EntityDefId.FIGHTER, EntityDefId.GUNSHIP, EntityDefId.FRIGATE, EntityDefId.DESTROYER, EntityDefId.CRUISER, EntityDefId.CARRIER, EntityDefId.DIPLOMAT }; string[] defs = new string[] { EntityDefId.ITEM_DUCT_TAPE, EntityDefId.ITEM_EMP, EntityDefId.ITEM_EXTRA_BATTERY, EntityDefId.ITEM_RED_PAINT }; for (int x = 0; x < defs.Length; x++) { for (int y = 0; y < 3; y++) { var pos = new EncounterPosition(nextToPlayer.X + x, nextToPlayer.Y + y); //state.PlaceEntity(EntityBuilder.CreateEnemyByEntityDefId(defs[x], "12345", 0), pos, ignoreCollision: true); state.PlaceEntity(EntityBuilder.CreateItemByEntityDefId(defs[x]), pos, ignoreCollision: true); } } // Add all the various zone features to the map // TODO: Draw this from LevelData instead of literally special-casing level 10 here if (dungeonLevel != 10) { // Generate the stairs (maybe we should refer interally as something more themetically appropriate?) // You can get stairs in your starting zone, but you probably shouldn't take them... // var stairsZone = zones[playerZoneIdx]; // For testing var stairsZone = zones[seededRand.Next(0, zones.Count)]; var stairsPosition = stairsZone.RandomEmptyPosition(seededRand, state); var stairs = EntityBuilder.CreateStairsEntity(); state.PlaceEntity(stairs, stairsPosition); stairsZone.AddFeatureToReadout(stairs); // Generate intel // var intelZone = zones[playerZoneIdx]; // For testing var intelZone = zones[seededRand.Next(0, zones.Count)]; var intelPosition = intelZone.RandomEmptyPosition(seededRand, state); var intel = EntityBuilder.CreateIntelEntity(dungeonLevel + 1); state.PlaceEntity(intel, intelPosition); intelZone.AddFeatureToReadout(intel); } // Populate each zone with an encounter foreach (EncounterZone zone in zones) { if (zone == zones[playerZoneIdx]) { PopulateZone(zone, dungeonLevel, seededRand, state, safe: true); } else { PopulateZone(zone, dungeonLevel, seededRand, state); } } }
private static void PopulateZone(EncounterZone zone, int dungeonLevel, Random seededRand, EncounterState state, bool safe = false) { // Add satellites int numSatellites = LevelData.GetNumberOfSatellites(dungeonLevel); for (int i = 0; i < numSatellites; i++) { var unblockedPosition = zone.RandomEmptyPosition(seededRand, state); var satellite = EntityBuilder.CreateSatelliteEntity(); state.PlaceEntity(satellite, unblockedPosition); } EncounterDef encounterDef; if (safe) { encounterDef = LevelData.GetEncounterDefById(EncounterDefId.EMPTY_ENCOUNTER); } else { encounterDef = LevelData.ChooseEncounter(dungeonLevel, seededRand); } zone.ReadoutEncounterName = encounterDef.Name; if (encounterDef.EntityDefIds.Count > 0) { string activationGroupId = Guid.NewGuid().ToString(); foreach (string entityDefId in encounterDef.EntityDefIds) { var unblockedPosition = zone.RandomEmptyPosition(seededRand, state); var newEntity = EntityBuilder.CreateEnemyByEntityDefId(entityDefId, activationGroupId, state.CurrentTick); state.PlaceEntity(newEntity, unblockedPosition); } } var chosenItemDefs = LevelData.ChooseItemDefs(dungeonLevel, seededRand); foreach (string chosenItemDefId in chosenItemDefs) { var unblockedPosition = zone.RandomEmptyPosition(seededRand, state); var newEntity = EntityBuilder.CreateItemByEntityDefId(chosenItemDefId); state.PlaceEntity(newEntity, unblockedPosition); zone.AddItemToReadout(newEntity); } }