Fix craft parsing, sustained combat, combat cooldown

- Craft regex now captures only 1-2 words (not entire sentence)
- Mine regex same fix
- Combat is now sustained: bridge keeps attacking every 500ms until
  target dies, leaves range, or 10s timeout
- Combat has 12-second cooldown to prevent spam
- Bot chases target if too far for melee during combat

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
roberts 2026-03-30 13:34:34 -05:00
parent 3832094a5c
commit 9c7ae89dd2
3 changed files with 56 additions and 8 deletions

View file

@ -583,8 +583,50 @@ async function handleAction(action, params = {}) {
if (hostiles.length === 0) return { attacked: false, reason: 'no_hostiles' };
hostiles.sort((a, b) => a.dist - b.dist);
const target = hostiles[0].entity;
bot.attack(target);
return { attacked: true, target: target.name || target.type, distance: hostiles[0].dist };
// Sustained combat: keep attacking until target is dead or out of range
let hits = 0;
const maxHits = 20;
const combatPromise = new Promise((resolve) => {
const attackInterval = setInterval(() => {
// Check if target is still alive and in range
const ent = bot.entities[target.id];
if (!ent || !ent.position) {
clearInterval(attackInterval);
resolve({ attacked: true, hits, result: 'target_gone' });
return;
}
const d = ent.position.distanceTo(bot.entity.position);
if (d > range + 2) {
clearInterval(attackInterval);
resolve({ attacked: true, hits, result: 'out_of_range' });
return;
}
if (hits >= maxHits) {
clearInterval(attackInterval);
resolve({ attacked: true, hits, result: 'max_hits' });
return;
}
// Look at target and attack
bot.lookAt(ent.position.offset(0, ent.height * 0.8, 0)).then(() => {
try { bot.attack(ent); hits++; } catch (e) {}
});
// Move toward target if too far for melee
if (d > 3) {
bot.pathfinder.setGoal(new GoalNear(ent.position.x, ent.position.y, ent.position.z, 2));
}
}, 500); // Attack every 500ms
// Safety timeout
setTimeout(() => {
clearInterval(attackInterval);
resolve({ attacked: true, hits, result: 'timeout' });
}, 10000);
});
return await combatPromise;
}
// --- Crafting ---

View file

@ -41,6 +41,7 @@ class BehaviorEngine:
self._last_scan_time = 0.0
self._last_chat_time = 0.0
self._last_wander_time = 0.0
self._last_combat_time = 0.0
self._explored_positions: list[dict] = [] # Places we've been
self._known_containers: list[dict] = [] # Containers we've found
self._relationships: dict[str, float] = {} # Player name → fondness (-1 to 1)
@ -215,19 +216,24 @@ class BehaviorEngine:
if bravery < 30:
return None # Too scared to fight
# Cooldown — don't spam combat tasks
if time.time() - self._last_combat_time < 12:
return None
# Find attackable hostile within melee range
for hostile in self.nearby_hostiles:
dist = hostile.get("distance", 99)
if dist < 4 and self.health > 8:
if dist < 5 and self.health > 8:
# Brave Dougs attack, others might not
if bravery > 60 or (bravery > 40 and self.health > 14):
self._last_combat_time = time.time()
return Task(
name=f"attack_{hostile['type']}",
name="combat",
priority=Priority.HIGH,
action="attack_nearest_hostile",
params={"range": 5},
params={"range": 6},
description=f"Fighting a {hostile['type']}!",
timeout=10,
timeout=15,
)
return None

View file

@ -57,11 +57,11 @@ class CommandParser:
]
CRAFT_PATTERNS = [
r"(?:craft|make|build|create)\s+(?:a\s+|an\s+|some\s+)?(.+)",
r"(?:craft|make|build|create)\s+(?:a\s+|an\s+|some\s+|me\s+)?(\w+(?:\s+\w+)?)",
]
MINE_PATTERNS = [
r"(?:mine|dig|break|destroy)\s+(?:that|this|the)?\s*(.+)",
r"(?:mine|dig|break|destroy)\s+(?:that|this|the|some)?\s*(\w+(?:\s+\w+)?)",
]
GIVE_PATTERNS = [