before mutliplayer

This commit is contained in:
2026-04-02 18:02:15 +02:00
parent fa42cec01c
commit f004205069
2 changed files with 449 additions and 31 deletions

View File

@@ -66,6 +66,8 @@ public partial class MainWindow : Window
private DateTimeOffset _lastStatsAt = DateTimeOffset.MinValue;
private DateTimeOffset _lastStatusAt = DateTimeOffset.MinValue;
private (int X, int Y)? _nearestEnemy;
private (int X, int Y)? _lastObservedEnemyRelative;
private (int X, int Y)? _enemyVelocityEstimate;
private Direction _lastMoveDirection = Direction.North;
private Direction _escapeDirection = Direction.North;
private int _recentDamageTicks;
@@ -402,11 +404,20 @@ public partial class MainWindow : Window
{
var scan = await _client.ScanAsync(_playerToken, ct);
MarkUsed(BotAction.Scan);
var previousEnemy = _nearestEnemy;
var sx = scan.DifferenceToNearestPlayer?.X;
var sy = scan.DifferenceToNearestPlayer?.Y;
_nearestEnemy = sx.HasValue && sy.HasValue ? (sx.Value, sy.Value) : null;
if (_nearestEnemy.HasValue)
{
if (previousEnemy.HasValue)
{
_enemyVelocityEstimate = (
_nearestEnemy.Value.X - previousEnemy.Value.X,
_nearestEnemy.Value.Y - previousEnemy.Value.Y);
}
_lastObservedEnemyRelative = _nearestEnemy;
AddHeat(_x + _nearestEnemy.Value.X, _y + _nearestEnemy.Value.Y, 5.5);
}
}
@@ -459,11 +470,31 @@ public partial class MainWindow : Window
_pressureStreak = Math.Max(_pressureStreak - 1, 0);
}
var underPressure = hpRatio < PressureHpThreshold(profile) || _recentDamageTicks > 0 || closeThreats >= PressureThreatThreshold(profile) || _survivalTicks > 0 || _pressureStreak >= 2;
_lastUnderPressure = underPressure;
var underPressure = hpRatio < PressureHpThreshold(profile) || _recentDamageTicks > 0 || closeThreats >= PressureThreatThreshold(profile) || _survivalTicks > 0 || _pressureStreak >= 2;
_lastUnderPressure = underPressure;
var predictedEnemy = PredictEnemyRelative();
// Teleport if we can and enemies are nearby - aggressive repositioning for kills
if (Ready(BotAction.Teleport) && closeThreats >= 1 && _nearestEnemy.HasValue)
var bestSight = _lastPeek
.Where(x => x.Value.Executed && x.Value.PlayersInSight > 0)
.OrderBy(x => x.Value.SightedPlayerDistance ?? int.MaxValue)
.FirstOrDefault();
if (Ready(BotAction.Hit) && immediateDir.HasValue && closeThreats >= 1)
{
var dir = immediateDir.Value;
return new BotDecision(
"hit-lock",
$"Enemy committed in close range at {dir}; preemptive melee pressure.",
async ct =>
{
var response = await _client.HitAsync(_playerToken, (int)dir, ct);
MarkUsed(BotAction.Hit);
return response.Executed;
});
}
// Teleport if we can and enemies are nearby - aggressive repositioning for kills
if (Ready(BotAction.Teleport) && closeThreats >= 1 && _nearestEnemy.HasValue)
{
var targetX = _nearestEnemy.Value.X;
var targetY = _nearestEnemy.Value.Y;
@@ -487,10 +518,28 @@ public partial class MainWindow : Window
});
}
var specialThreatThreshold = SpecialThreatThreshold(profile);
var specialHpFloor = SpecialHpFloor(profile);
if (Ready(BotAction.Dash) && TryGetAggressiveDashDirection(predictedEnemy, bestSight, out var dashDirection, out var dashReason))
{
return new BotDecision(
"dash-aggro",
dashReason,
async ct =>
{
var response = await _client.DashAsync(_playerToken, (int)dashDirection, ct);
MarkUsed(BotAction.Dash);
if (response.Executed)
{
ApplyMovement(dashDirection, Math.Max(response.BlocksDashed, 1));
}
if (Ready(BotAction.Special) && closeThreats >= specialThreatThreshold)
return response.Executed;
});
}
var specialThreatThreshold = SpecialThreatThreshold(profile);
var specialHpFloor = SpecialHpFloor(profile);
if (Ready(BotAction.Special) && closeThreats >= specialThreatThreshold && (hpRatio >= specialHpFloor || closeThreats > specialThreatThreshold))
{
return new BotDecision(
"specialattack",
@@ -575,26 +624,18 @@ public partial class MainWindow : Window
});
}
var bestSight = _lastPeek
.Where(x => x.Value.Executed && x.Value.PlayersInSight > 0)
.OrderBy(x => x.Value.SightedPlayerDistance ?? int.MaxValue)
.FirstOrDefault();
// More aggressive shooting
if (bestSight.Value is not null && Ready(BotAction.Shoot))
{
var dir = bestSight.Key;
var dist = bestSight.Value.SightedPlayerDistance ?? 99;
return new BotDecision(
"shoot",
$"Enemy at {dir} ({dist} dist); ranged kill.",
async ct =>
{
var response = await _client.ShootAsync(_playerToken, (int)dir, ct);
MarkUsed(BotAction.Shoot);
return response.Executed;
});
}
if (Ready(BotAction.Shoot) && TryGetPredictiveShotDirection(bestSight.Key, bestSight.Value, predictedEnemy, out var shotDirection, out var shotReason))
{
return new BotDecision(
"shoot",
shotReason,
async ct =>
{
var response = await _client.ShootAsync(_playerToken, (int)shotDirection, ct);
MarkUsed(BotAction.Shoot);
return response.Executed;
});
}
var moveDirection = GetBestMoveDirection(chase: true);
if (Ready(BotAction.Move))
@@ -641,8 +682,7 @@ public partial class MainWindow : Window
foreach (var dir in scored.Keys.ToArray())
{
var score = 0.0;
// Always chase aggressively - ignore radar safety
score += scored[dir] * 0.5;
score += scored[dir] * 0.5;
if (_lastPeek.TryGetValue(dir, out var peek) && peek.Executed)
{
@@ -656,8 +696,8 @@ public partial class MainWindow : Window
if (_nearestEnemy.HasValue)
{
var targetDir = DirectionFromVector(_nearestEnemy.Value.X, _nearestEnemy.Value.Y);
// Always move toward nearest enemy
var targetVector = PredictEnemyRelative() ?? _nearestEnemy.Value;
var targetDir = DirectionFromVector(targetVector.X, targetVector.Y);
if (dir == targetDir)
{
score += 12.0;
@@ -927,6 +967,96 @@ public partial class MainWindow : Window
_ => hpRatio < 0.02,
};
private (int X, int Y)? PredictEnemyRelative()
{
if (!_nearestEnemy.HasValue)
{
return _lastObservedEnemyRelative;
}
if (_enemyVelocityEstimate is null)
{
return _nearestEnemy;
}
return (
_nearestEnemy.Value.X + _enemyVelocityEstimate.Value.X,
_nearestEnemy.Value.Y + _enemyVelocityEstimate.Value.Y);
}
private bool TryGetPredictiveShotDirection(
Direction? sightDirection,
PeekEntitiesAsyncResponse? sightResponse,
(int X, int Y)? predictedEnemy,
out Direction direction,
out string reason)
{
if (sightDirection is null || sightResponse is null)
{
if (predictedEnemy.HasValue)
{
direction = DirectionFromVector(predictedEnemy.Value.X, predictedEnemy.Value.Y);
reason = $"Predicted enemy lane -> {direction}; taking lead shot.";
return true;
}
direction = Direction.North;
reason = string.Empty;
return false;
}
var currentDir = sightDirection.Value;
var dist = sightResponse.SightedPlayerDistance ?? 99;
if (predictedEnemy.HasValue && dist >= 2)
{
var predictedDir = DirectionFromVector(predictedEnemy.Value.X, predictedEnemy.Value.Y);
if (predictedDir != currentDir)
{
direction = predictedDir;
reason = $"Enemy moving toward {predictedDir}; lead shot instead of static line {currentDir}.";
return true;
}
}
direction = currentDir;
reason = $"Enemy at {currentDir} ({dist} dist); ranged kill.";
return true;
}
private bool TryGetAggressiveDashDirection(
(int X, int Y)? predictedEnemy,
KeyValuePair<Direction, PeekEntitiesAsyncResponse>? bestSight,
out Direction direction,
out string reason)
{
if (_nearestEnemy.HasValue)
{
var target = predictedEnemy ?? _nearestEnemy.Value;
var dist = Math.Abs(target.X) + Math.Abs(target.Y);
if (dist >= 2 && dist <= 6)
{
direction = DirectionFromVector(target.X, target.Y);
reason = $"Aggressive gap-close toward enemy at {direction} (predicted distance {dist}).";
return true;
}
}
if (bestSight.HasValue && bestSight.Value.Value.Executed && bestSight.Value.Value.PlayersInSight > 0)
{
var dist = bestSight.Value.Value.SightedPlayerDistance ?? 99;
if (dist >= 2 && dist <= 5)
{
direction = bestSight.Value.Key;
reason = $"Enemy visible at {direction} ({dist} dist); dash to force close-range combat.";
return true;
}
}
direction = Direction.North;
reason = string.Empty;
return false;
}
private void MarkVisit(int x, int y, double amount)
{
var key = (x, y);