Erweiterte Regeln: in jeder dritten Runde ein neutraler Wächter in jeder Lanemitte; wer beim Fall mehr Einheiten (dann mehr Leben) in 8 m hat, nimmt das Lager: Glutsegen (+10 % Schaden), Mondschild (Schild 10 %), Beute (50 Gold je Spieler). Fog of War (LuneFog, nur Darstellung): Gegner sichtbar in 9 m um eigene Einheiten, 10 m um Gebäude, 12 m um Späher; Späher (40 Gold, 3 Runden, Teamwirkung) zeigen in der Planung die gegnerische Armee ihrer Lane. HUD: Teampanel SPÄHER und LAGER, Lager-Sechseck, Nebel in Feld, Minimap, Namensschildern und Lane-Zählung. Kern 597/597 (s3 31), EditMode 33/33, PlayMode 18/18, Verify 718/0, Selbsttest 30/30, Golden 0, Fingerabdruck unverändert. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
129 lines
5.5 KiB
C#
129 lines
5.5 KiB
C#
using System.Collections.Generic;
|
|
|
|
namespace Luneraria.Core
|
|
{
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
|
|
/// <summary>The lane is scouted for the team in planning (ward, fallen enemy outer tower, or a ward in the own draft).</summary>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
public static List<LuneMatch.ArmyEntry> ScoutedEnemies(LuneMatch match, int team, Draft ownDraft = null)
|
|
{
|
|
var result = new List<LuneMatch.ArmyEntry>();
|
|
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;
|
|
}
|
|
|
|
/// <summary>Middle of a lane, where a ward watches.</summary>
|
|
public static Vec2 WardPoint(LuneMatch match, string lane)
|
|
{
|
|
return match.Map.LanePoint(0, lane, match.Map.LanePaths[lane].Length * 0.5, 0.0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
public static HashSet<int> Hidden(LuneMatch match, LuneCombat shown, int team, bool planning, Draft ownDraft = null)
|
|
{
|
|
var hidden = new HashSet<int>();
|
|
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<Source>();
|
|
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;
|
|
}
|
|
}
|
|
}
|