More aggressive, got to level 3 at least

This commit is contained in:
2026-04-02 17:38:23 +02:00
parent 39e943b25e
commit fa42cec01c

View File

@@ -315,26 +315,27 @@ public partial class MainWindow : Window
_recentDamageTicks = 20; _recentDamageTicks = 20;
} }
if (_deaths > previousDeaths) if (_deaths > previousDeaths)
{ {
var nowDeath = DateTimeOffset.UtcNow; var nowDeath = DateTimeOffset.UtcNow;
_recentDeaths.Enqueue(nowDeath); _recentDeaths.Enqueue(nowDeath);
while (_recentDeaths.Count > 0 && nowDeath - _recentDeaths.Peek() > TimeSpan.FromSeconds(90)) while (_recentDeaths.Count > 0 && nowDeath - _recentDeaths.Peek() > TimeSpan.FromSeconds(90))
{ {
_recentDeaths.Dequeue(); _recentDeaths.Dequeue();
} }
_survivalTicks = Math.Max(_survivalTicks, 24 + _recentDeaths.Count * 4); // Minimal survival mode - deaths don't matter, only kills
_escapeLockTicks = Math.Max(_escapeLockTicks, 8); _survivalTicks = Math.Max(_survivalTicks, 4);
_escapeDirection = Opposite(_lastMoveDirection); _escapeLockTicks = Math.Max(_escapeLockTicks, 2);
_pressureStreak = 0; _escapeDirection = Opposite(_lastMoveDirection);
_x = 0; _pressureStreak = 0;
_y = 0; _x = 0;
_enemyHeat.Clear(); _y = 0;
_nearestEnemy = null; _enemyHeat.Clear();
MarkDeathZone(_x, _y); _nearestEnemy = null;
_sessionLogger?.LogEvent("death", $"death_count={_deaths};death_window_90s={_recentDeaths.Count}"); MarkDeathZone(_x, _y);
} _sessionLogger?.LogEvent("death", $"death_count={_deaths};death_window_90s={_recentDeaths.Count}");
}
_sessionLogger?.LogTelemetry( _sessionLogger?.LogTelemetry(
_currentHealth, _currentHealth,
@@ -433,285 +434,275 @@ public partial class MainWindow : Window
} }
} }
private BotDecision? BuildDecision() private BotDecision? BuildDecision()
{ {
var hpRatio = _maxHealth > 0.1 ? _currentHealth / _maxHealth : 1.0; var hpRatio = _maxHealth > 0.1 ? _currentHealth / _maxHealth : 1.0;
var profile = _currentLevelProfile; var profile = _currentLevelProfile;
var immediateDir = _lastPeek var immediateDir = _lastPeek
.Where(x => x.Value.Executed && x.Value.PlayersInSight > 0 && x.Value.SightedPlayerDistance == 1) .Where(x => x.Value.Executed && x.Value.PlayersInSight > 0 && x.Value.SightedPlayerDistance == 1)
.Select(x => (Direction?)x.Key) .Select(x => (Direction?)x.Key)
.FirstOrDefault(); .FirstOrDefault();
var closeThreats = _lastPeek.Values.Count(x => x.Executed && x.PlayersInSight > 0 && (x.SightedPlayerDistance ?? 99) <= 2); var closeThreats = _lastPeek.Values.Count(x => x.Executed && x.PlayersInSight > 0 && (x.SightedPlayerDistance ?? 99) <= 2);
if (_nearestEnemy.HasValue && Math.Abs(_nearestEnemy.Value.X) + Math.Abs(_nearestEnemy.Value.Y) <= 2) if (_nearestEnemy.HasValue && Math.Abs(_nearestEnemy.Value.X) + Math.Abs(_nearestEnemy.Value.Y) <= 2)
{ {
closeThreats++; closeThreats++;
} }
if (hpRatio < 0.75 || _recentDamageTicks > 0 || closeThreats >= 2) if (hpRatio < 0.75 || _recentDamageTicks > 0 || closeThreats >= 2)
{ {
_pressureStreak = Math.Min(_pressureStreak + 1, 12); _pressureStreak = Math.Min(_pressureStreak + 1, 12);
} }
else else
{ {
_pressureStreak = Math.Max(_pressureStreak - 1, 0); _pressureStreak = Math.Max(_pressureStreak - 1, 0);
} }
var underPressure = hpRatio < PressureHpThreshold(profile) || _recentDamageTicks > 0 || closeThreats >= PressureThreatThreshold(profile) || _survivalTicks > 0 || _pressureStreak >= 2; var underPressure = hpRatio < PressureHpThreshold(profile) || _recentDamageTicks > 0 || closeThreats >= PressureThreatThreshold(profile) || _survivalTicks > 0 || _pressureStreak >= 2;
_lastUnderPressure = underPressure; _lastUnderPressure = underPressure;
var specialThreatThreshold = SpecialThreatThreshold(profile); // Teleport if we can and enemies are nearby - aggressive repositioning for kills
var specialHpFloor = SpecialHpFloor(profile); if (Ready(BotAction.Teleport) && closeThreats >= 1 && _nearestEnemy.HasValue)
{
var targetX = _nearestEnemy.Value.X;
var targetY = _nearestEnemy.Value.Y;
// Teleport closer to enemy for aggressive positioning
var moveTowardX = targetX > 0 ? targetX - 1 : targetX + 1;
var moveTowardY = targetY > 0 ? targetY - 1 : targetY + 1;
if (Ready(BotAction.Special) && closeThreats >= specialThreatThreshold && (hpRatio >= specialHpFloor || closeThreats >= specialThreatThreshold + 1)) return new BotDecision(
{ "teleport-aggro",
return new BotDecision( "Close enemy; teleport for aggressive positioning.",
"specialattack", async ct =>
"Multiple enemies close; AoE yields best kill rate.", {
async ct => var response = await _client.TeleportAsync(_playerToken, moveTowardX, moveTowardY, ct);
{ MarkUsed(BotAction.Teleport);
var response = await _client.SpecialattackAsync(_playerToken, ct); if (response.Executed)
MarkUsed(BotAction.Special); {
return response.Executed; _x = moveTowardX;
}); _y = moveTowardY;
} }
return response.Executed;
});
}
if (immediateDir.HasValue && Ready(BotAction.Hit) && ShouldUseImmediateHit(profile, hpRatio, closeThreats, underPressure)) var specialThreatThreshold = SpecialThreatThreshold(profile);
{ var specialHpFloor = SpecialHpFloor(profile);
var dir = immediateDir.Value;
return new BotDecision(
"hit",
$"Adjacent enemy at {dir}; highest DPS action.",
async ct =>
{
var response = await _client.HitAsync(_playerToken, (int)dir, ct);
MarkUsed(BotAction.Hit);
return response.Executed;
});
}
if (immediateDir.HasValue && Ready(BotAction.Move) && ShouldRetreatOnImmediate(profile, hpRatio, closeThreats)) if (Ready(BotAction.Special) && closeThreats >= specialThreatThreshold)
{ {
var retreatDirection = Opposite(immediateDir.Value); return new BotDecision(
"specialattack",
$"Multiple enemies ({closeThreats}) close; AoE for maximum kills.",
async ct =>
{
var response = await _client.SpecialattackAsync(_playerToken, ct);
MarkUsed(BotAction.Special);
return response.Executed;
});
}
if (Ready(BotAction.Dash) && (hpRatio < DashHpThreshold(profile) || _pressureStreak >= 3 || _survivalTicks > 0)) if (immediateDir.HasValue && Ready(BotAction.Hit) && ShouldUseImmediateHit(profile, hpRatio, closeThreats, underPressure))
{ {
return new BotDecision( var dir = immediateDir.Value;
"dash-retreat", return new BotDecision(
$"High pressure escape via dash -> {retreatDirection}.", "hit",
async ct => $"Adjacent enemy at {dir}; highest DPS action.",
{ async ct =>
var response = await _client.DashAsync(_playerToken, (int)retreatDirection, ct); {
MarkUsed(BotAction.Dash); var response = await _client.HitAsync(_playerToken, (int)dir, ct);
if (response.Executed) MarkUsed(BotAction.Hit);
{ return response.Executed;
ApplyMovement(retreatDirection, Math.Max(response.BlocksDashed, 1)); });
_escapeDirection = retreatDirection; }
_escapeLockTicks = Math.Max(_escapeLockTicks, EscapeLockTicks(profile));
}
return response.Executed; if (immediateDir.HasValue && Ready(BotAction.Move) && ShouldRetreatOnImmediate(profile, hpRatio, closeThreats))
}); {
} var retreatDirection = Opposite(immediateDir.Value);
return new BotDecision( if (Ready(BotAction.Dash) && (hpRatio < DashHpThreshold(profile) || _pressureStreak >= 3 || _survivalTicks > 0))
"move-retreat", {
$"Adjacent enemy and pressure high; kite to {retreatDirection}.", return new BotDecision(
async ct => "dash-retreat",
{ $"Critical HP escape via dash -> {retreatDirection}.",
var response = await _client.MoveAsync(_playerToken, (int)retreatDirection, ct); async ct =>
MarkUsed(BotAction.Move); {
var moved = response.Executed && response.Move == true; var response = await _client.DashAsync(_playerToken, (int)retreatDirection, ct);
RegisterMoveResult(retreatDirection, moved); MarkUsed(BotAction.Dash);
if (moved) if (response.Executed)
{ {
ApplyMovement(retreatDirection, 1); ApplyMovement(retreatDirection, Math.Max(response.BlocksDashed, 1));
_escapeDirection = retreatDirection; _escapeDirection = retreatDirection;
_escapeLockTicks = Math.Max(_escapeLockTicks, EscapeLockTicks(profile)); _escapeLockTicks = Math.Max(_escapeLockTicks, EscapeLockTicks(profile));
} }
return response.Executed; return response.Executed;
}); });
} }
if (immediateDir.HasValue && Ready(BotAction.Hit)) return new BotDecision(
{ "move-retreat",
var dir = immediateDir.Value; $"Retreating from {immediateDir.Value} -> {retreatDirection}.",
return new BotDecision( async ct =>
"hit-last-stand", {
$"Adjacent enemy at {dir}; strike while move cooldown is active.", var response = await _client.MoveAsync(_playerToken, (int)retreatDirection, ct);
async ct => MarkUsed(BotAction.Move);
{ var moved = response.Executed && response.Move == true;
var response = await _client.HitAsync(_playerToken, (int)dir, ct); RegisterMoveResult(retreatDirection, moved);
MarkUsed(BotAction.Hit); if (moved)
return response.Executed; {
}); ApplyMovement(retreatDirection, 1);
} _escapeDirection = retreatDirection;
_escapeLockTicks = Math.Max(_escapeLockTicks, EscapeLockTicks(profile));
}
var bestSight = _lastPeek return response.Executed;
.Where(x => x.Value.Executed && x.Value.PlayersInSight > 0) });
.OrderBy(x => x.Value.SightedPlayerDistance ?? int.MaxValue) }
.FirstOrDefault();
if (bestSight.Value is not null && Ready(BotAction.Shoot)) if (immediateDir.HasValue && Ready(BotAction.Hit))
{ {
var dir = bestSight.Key; var dir = immediateDir.Value;
var dist = bestSight.Value.SightedPlayerDistance ?? 99; return new BotDecision(
if (!underPressure || dist >= 2) "hit-last-stand",
{ $"Adjacent enemy at {dir}; aggressive strike.",
return new BotDecision( async ct =>
"shoot", {
$"Enemy in line of sight to {dir}; ranged hit before moving.", var response = await _client.HitAsync(_playerToken, (int)dir, ct);
async ct => MarkUsed(BotAction.Hit);
{ return response.Executed;
var response = await _client.ShootAsync(_playerToken, (int)dir, ct); });
MarkUsed(BotAction.Shoot); }
return response.Executed;
});
}
}
var moveDirection = GetBestMoveDirection(chase: !underPressure); var bestSight = _lastPeek
if (Ready(BotAction.Move)) .Where(x => x.Value.Executed && x.Value.PlayersInSight > 0)
{ .OrderBy(x => x.Value.SightedPlayerDistance ?? int.MaxValue)
var reason = underPressure .FirstOrDefault();
? $"Pressure high; reposition toward safer lane -> {moveDirection}."
: $"Controlled hunt path -> {moveDirection}.";
return new BotDecision( // More aggressive shooting
"move", if (bestSight.Value is not null && Ready(BotAction.Shoot))
reason, {
async ct => var dir = bestSight.Key;
{ var dist = bestSight.Value.SightedPlayerDistance ?? 99;
var response = await _client.MoveAsync(_playerToken, (int)moveDirection, ct); return new BotDecision(
MarkUsed(BotAction.Move); "shoot",
var moved = response.Executed && response.Move == true; $"Enemy at {dir} ({dist} dist); ranged kill.",
RegisterMoveResult(moveDirection, moved); async ct =>
if (moved) {
{ var response = await _client.ShootAsync(_playerToken, (int)dir, ct);
ApplyMovement(moveDirection, 1); MarkUsed(BotAction.Shoot);
if (underPressure) return response.Executed;
{ });
_escapeDirection = moveDirection; }
_escapeLockTicks = Math.Max(_escapeLockTicks, EscapeLockTicks(profile));
}
}
return response.Executed; var moveDirection = GetBestMoveDirection(chase: true);
}); if (Ready(BotAction.Move))
} {
return new BotDecision(
"move",
$"Aggressive hunt -> {moveDirection}.",
async ct =>
{
var response = await _client.MoveAsync(_playerToken, (int)moveDirection, ct);
MarkUsed(BotAction.Move);
var moved = response.Executed && response.Move == true;
RegisterMoveResult(moveDirection, moved);
if (moved)
{
ApplyMovement(moveDirection, 1);
}
return null; return response.Executed;
} });
}
private Direction GetBestMoveDirection(bool chase) return null;
{ }
if (!chase && _escapeLockTicks > 0)
{
if (!_blockedDirectionTicks.TryGetValue(_escapeDirection, out var blocked) || blocked <= 0)
{
return _escapeDirection;
}
}
var scored = new Dictionary<Direction, double> private Direction GetBestMoveDirection(bool chase)
{ {
[Direction.North] = RadarScore(Direction.North), if (!chase && _escapeLockTicks > 0)
[Direction.East] = RadarScore(Direction.East), {
[Direction.South] = RadarScore(Direction.South), if (!_blockedDirectionTicks.TryGetValue(_escapeDirection, out var blocked) || blocked <= 0)
[Direction.West] = RadarScore(Direction.West), {
}; return _escapeDirection;
}
}
foreach (var dir in scored.Keys.ToArray()) var scored = new Dictionary<Direction, double>
{ {
var score = 0.0; [Direction.North] = RadarScore(Direction.North),
score += chase ? scored[dir] * 1.35 : -scored[dir] * 1.0; [Direction.East] = RadarScore(Direction.East),
[Direction.South] = RadarScore(Direction.South),
[Direction.West] = RadarScore(Direction.West),
};
if (_lastPeek.TryGetValue(dir, out var peek) && peek.Executed) foreach (var dir in scored.Keys.ToArray())
{ {
if (peek.PlayersInSight > 0) var score = 0.0;
{ // Always chase aggressively - ignore radar safety
var dist = Math.Max(1, peek.SightedPlayerDistance ?? 6); score += scored[dir] * 0.5;
score += chase
? 7.0 / dist + peek.PlayersInSight * 0.8
: -10.0 / dist - peek.PlayersInSight * 0.7;
}
else if (!chase)
{
score += 0.9;
}
}
if (_nearestEnemy.HasValue) if (_lastPeek.TryGetValue(dir, out var peek) && peek.Executed)
{ {
var targetDir = DirectionFromVector(_nearestEnemy.Value.X, _nearestEnemy.Value.Y); if (peek.PlayersInSight > 0)
var awayDir = Opposite(targetDir); {
if (chase && dir == targetDir) var dist = Math.Max(1, peek.SightedPlayerDistance ?? 6);
{ // Aggressive scoring: high bonus for chasing visible enemies
score += 4.0; score += 15.0 / dist + peek.PlayersInSight * 3.0;
} }
else if (!chase && dir == awayDir) }
{
score += 4.5;
}
else if (!chase && dir == targetDir)
{
score -= 3.5;
}
}
var visitPenalty = VisitHeatFor(dir); if (_nearestEnemy.HasValue)
var deathPenalty = DeathHeatFor(dir); {
var targetDir = DirectionFromVector(_nearestEnemy.Value.X, _nearestEnemy.Value.Y);
// Always move toward nearest enemy
if (dir == targetDir)
{
score += 12.0;
}
else if (dir == Opposite(targetDir))
{
score -= 8.0;
}
}
if (_currentLevelProfile == LevelProfile.BiggerMap) // Reduce penalties for visiting/death zones - we're aggressive
{ var visitPenalty = VisitHeatFor(dir);
score -= visitPenalty * 1.4; var deathPenalty = DeathHeatFor(dir);
}
else
{
score -= visitPenalty * 0.8;
}
score -= deathPenalty * 1.6; score -= visitPenalty * 0.2;
score -= deathPenalty * 0.3;
if (_survivalTicks > 0 && _nearestEnemy.HasValue) if (_escapeLockTicks > 0)
{ {
var safest = Opposite(DirectionFromVector(_nearestEnemy.Value.X, _nearestEnemy.Value.Y)); if (dir == _escapeDirection)
if (dir == safest) {
{ score += 2.0;
score += 2.8; }
} else if (dir == Opposite(_escapeDirection))
} {
score -= 1.5;
}
}
if (_escapeLockTicks > 0) if (_blockedDirectionTicks.TryGetValue(dir, out var blockedTicks) && blockedTicks > 0)
{ {
if (dir == _escapeDirection) score -= 3.0 + blockedTicks * 0.2;
{ }
score += 4.5;
}
else if (dir == Opposite(_escapeDirection))
{
score -= 4.0;
}
}
if (_blockedDirectionTicks.TryGetValue(dir, out var blockedTicks) && blockedTicks > 0) if (dir == Opposite(_lastMoveDirection))
{ {
score -= 5.0 + blockedTicks * 0.4; score -= 0.2;
} }
if (dir == Opposite(_lastMoveDirection)) score += _random.NextDouble() * 0.5;
{ scored[dir] = score;
score -= 0.6; }
}
score += _survivalTicks > 0 ? _random.NextDouble() * 0.2 : _random.NextDouble() * 0.7; return scored.OrderByDescending(x => x.Value).First().Key;
scored[dir] = score; }
}
return scored.OrderByDescending(x => x.Value).First().Key;
}
private void ApplyRadarHeat() private void ApplyRadarHeat()
{ {
@@ -864,77 +855,77 @@ public partial class MainWindow : Window
return LevelProfile.Unknown; return LevelProfile.Unknown;
} }
private double PressureHpThreshold(LevelProfile profile) => profile switch private double PressureHpThreshold(LevelProfile profile) => profile switch
{ {
LevelProfile.BiggerMap => 0.66, LevelProfile.BiggerMap => 0.15,
LevelProfile.MoreBots => 0.62, LevelProfile.MoreBots => 0.12,
LevelProfile.NewBots => 0.68, LevelProfile.NewBots => 0.18,
LevelProfile.Default => 0.58, LevelProfile.Default => 0.10,
_ => 0.60, _ => 0.15,
}; };
private int PressureThreatThreshold(LevelProfile profile) => profile switch private int PressureThreatThreshold(LevelProfile profile) => profile switch
{ {
LevelProfile.BiggerMap => 2, LevelProfile.BiggerMap => 5,
LevelProfile.MoreBots => 2, LevelProfile.MoreBots => 6,
LevelProfile.NewBots => 2, LevelProfile.NewBots => 5,
LevelProfile.Default => 2, LevelProfile.Default => 6,
_ => 2, _ => 5,
}; };
private int SpecialThreatThreshold(LevelProfile profile) => profile switch private int SpecialThreatThreshold(LevelProfile profile) => profile switch
{ {
LevelProfile.MoreBots => 4, LevelProfile.MoreBots => 2,
LevelProfile.NewBots => 3, LevelProfile.NewBots => 1,
LevelProfile.BiggerMap => 2, LevelProfile.BiggerMap => 1,
LevelProfile.Default => 2, LevelProfile.Default => 1,
_ => 2, _ => 1,
}; };
private double SpecialHpFloor(LevelProfile profile) => profile switch private double SpecialHpFloor(LevelProfile profile) => profile switch
{ {
LevelProfile.MoreBots => 0.78, LevelProfile.MoreBots => 0.20,
LevelProfile.NewBots => 0.55, LevelProfile.NewBots => 0.15,
LevelProfile.BiggerMap => 0.42, LevelProfile.BiggerMap => 0.10,
LevelProfile.Default => 0.35, LevelProfile.Default => 0.05,
_ => 0.40, _ => 0.10,
}; };
private double DashHpThreshold(LevelProfile profile) => profile switch private double DashHpThreshold(LevelProfile profile) => profile switch
{ {
LevelProfile.BiggerMap => 0.50, LevelProfile.BiggerMap => 0.08,
LevelProfile.NewBots => 0.58, LevelProfile.NewBots => 0.12,
LevelProfile.MoreBots => 0.40, LevelProfile.MoreBots => 0.10,
LevelProfile.Default => 0.30, LevelProfile.Default => 0.05,
_ => 0.45, _ => 0.10,
}; };
private int EscapeLockTicks(LevelProfile profile) => profile switch private int EscapeLockTicks(LevelProfile profile) => profile switch
{ {
LevelProfile.BiggerMap => 8, LevelProfile.BiggerMap => 2,
LevelProfile.NewBots => 9, LevelProfile.NewBots => 2,
LevelProfile.MoreBots => 6, LevelProfile.MoreBots => 2,
LevelProfile.Default => 5, LevelProfile.Default => 2,
_ => 6, _ => 2,
}; };
private bool ShouldUseImmediateHit(LevelProfile profile, double hpRatio, int closeThreats, bool underPressure) => profile switch private bool ShouldUseImmediateHit(LevelProfile profile, double hpRatio, int closeThreats, bool underPressure) => profile switch
{ {
LevelProfile.Default => hpRatio >= 0.22 && closeThreats <= 2, LevelProfile.Default => hpRatio >= 0.05,
LevelProfile.BiggerMap => hpRatio >= 0.25 && closeThreats <= 2, LevelProfile.BiggerMap => hpRatio >= 0.05,
LevelProfile.MoreBots => hpRatio >= 0.35 && closeThreats <= 3, LevelProfile.MoreBots => hpRatio >= 0.05,
LevelProfile.NewBots => !underPressure && hpRatio >= 0.50, LevelProfile.NewBots => hpRatio >= 0.05,
_ => hpRatio >= 0.30 && closeThreats <= 2, _ => hpRatio >= 0.05,
}; };
private bool ShouldRetreatOnImmediate(LevelProfile profile, double hpRatio, int closeThreats) => profile switch private bool ShouldRetreatOnImmediate(LevelProfile profile, double hpRatio, int closeThreats) => profile switch
{ {
LevelProfile.Default => hpRatio < 0.22 || closeThreats >= 3, LevelProfile.Default => hpRatio < 0.02,
LevelProfile.BiggerMap => hpRatio < 0.30 || closeThreats >= 3, LevelProfile.BiggerMap => hpRatio < 0.02,
LevelProfile.MoreBots => hpRatio < 0.32 || closeThreats >= 3, LevelProfile.MoreBots => hpRatio < 0.02,
LevelProfile.NewBots => hpRatio < 0.68 || closeThreats >= 2, LevelProfile.NewBots => hpRatio < 0.02,
_ => hpRatio < 0.30 || closeThreats >= 3, _ => hpRatio < 0.02,
}; };
private void MarkVisit(int x, int y, double amount) private void MarkVisit(int x, int y, double amount)
{ {