before mutliplayer
This commit is contained in:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user