using System.Collections.Generic; namespace Luneraria.Core { /// /// S3 fog of war (Dota 2 vision and wards, extended rules). It only decides what a viewer's /// team sees; the simulation never reads it, so the rules and every replay stay the same. /// In combat an enemy unit is visible within reach of an own living unit, an own living /// building or one of the team's wards (a ward watches the middle of its lane). In planning, /// enemy armies show only on scouted lanes: a ward there, or the enemy's outer tower of that /// lane has fallen. Neutral units (camp guardians, the grotto boss) are always visible. /// public static class LuneFog { public const double UnitSight = 9.0; public const double BuildingSight = 10.0; public const double WardSight = 12.0; struct Source { public Vec2 At; public double Radius; public string Zone; } /// The lane is scouted for the team in planning (ward, fallen enemy outer tower, or a ward in the own draft). public static bool LaneScouted(LuneMatch match, int team, string lane, Draft ownDraft = null) { int index = LuneMap.LaneIndex(lane); if (index < 0) return false; if (match.Boss.Wards[team * 3 + index] > 0) return true; if (ownDraft != null && ownDraft.WardLanes.Contains(lane)) return true; foreach (BuildingDef definition in match.Map.Buildings) { if (definition.Team == 1 - team && definition.Lane == lane && definition.Tier == "outer") return match.BuildingHp[definition.Id] <= 0.0; } return false; } /// /// Planning (S3): the enemy's committed army on the lanes the team has scouted, as the /// match will field it (troops and heroes, lane owner first). The planning preview shows /// these; unscouted lanes show nothing of the enemy. Empty without the extended rules. /// public static List ScoutedEnemies(LuneMatch match, int team, Draft ownDraft = null) { var result = new List(); if (match == null || !match.Extended || team < 0 || team > 1) return result; int enemy = 1 - team; foreach (string lane in LuneMap.Lanes) { if (!LaneScouted(match, team, lane, ownDraft)) continue; for (int slot = enemy * 3; slot < enemy * 3 + 3; slot++) { foreach (LuneMatch.ArmyEntry entry in match.ArmyEntries(slot)) { if (entry.Lane == lane) result.Add(entry); } } } return result; } /// Middle of a lane, where a ward watches. public static Vec2 WardPoint(LuneMatch match, string lane) { return match.Map.LanePoint(0, lane, match.Map.LanePaths[lane].Length * 0.5, 0.0); } /// /// Ids of the enemy units the team cannot see in the shown combat (the live combat, or /// the planning preview when planning). Empty without the extended rules or for team -1. /// public static HashSet Hidden(LuneMatch match, LuneCombat shown, int team, bool planning, Draft ownDraft = null) { var hidden = new HashSet(); if (match == null || shown == null || !match.Extended || team < 0 || team > 1) return hidden; if (planning) { foreach (LuneUnit unit in shown.Units) { if (unit.Team == 1 - team && unit.IsMobile() && (unit.Zone != "lane" || !LaneScouted(match, team, unit.Lane, ownDraft))) hidden.Add(unit.Id); } return hidden; } var sources = new List(); foreach (LuneUnit unit in shown.Units) { if (unit.Team != team || !unit.Alive) continue; if (unit.IsMobile()) sources.Add(new Source { At = unit.Position, Radius = UnitSight, Zone = unit.Zone }); else if (unit.IsBuilding()) sources.Add(new Source { At = unit.Position, Radius = BuildingSight, Zone = "lane" }); } foreach (string lane in LuneMap.Lanes) { if (match.Boss.Wards[team * 3 + LuneMap.LaneIndex(lane)] > 0) sources.Add(new Source { At = WardPoint(match, lane), Radius = WardSight, Zone = "lane" }); } foreach (LuneUnit unit in shown.Units) { if (unit.Team != 1 - team || !unit.IsMobile()) continue; bool seen = false; foreach (Source source in sources) { if (source.Zone == unit.Zone && unit.Position.DistanceTo(source.At) <= source.Radius) { seen = true; break; } } if (!seen) hidden.Add(unit.Id); } return hidden; } } }