Fix zone selection and add bot diagnostics

- Filter planet grid to only use 18x18 outdoor zones (exclude 9x9 rooms)
- Add room exit detection in MissionSolver
- Auto-start bot for debugging
- Add zone grid visualization
- Improve zone connection logging
- Fix HandleBotAction direction handling

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Ned Heller
2026-01-29 16:37:02 -08:00
co-authored by Claude Opus 4.5
parent 1d10d368a3
commit 1d962c20ce
5 changed files with 182 additions and 66 deletions
@@ -612,6 +612,55 @@ public class MissionBot
Console.WriteLine($"[BOT] Puzzle steps: {mission.PuzzleChain.Count}");
Console.WriteLine($"[BOT] Current step: {mission.CurrentStep + 1}");
}
// Log current zone connections
LogCurrentZoneConnections();
}
private void LogCurrentZoneConnections()
{
var world = _worldGenerator.CurrentWorld;
if (world == null)
{
Console.WriteLine("[BOT] No world loaded");
return;
}
var zoneId = _state.CurrentZoneId;
Console.WriteLine($"[BOT] Current zone: {zoneId} at position ({_state.PlayerX}, {_state.PlayerY})");
if (world.Connections.TryGetValue(zoneId, out var conn))
{
Console.WriteLine($"[BOT] Zone connections: N={conn.North?.ToString() ?? "none"}, S={conn.South?.ToString() ?? "none"}, E={conn.East?.ToString() ?? "none"}, W={conn.West?.ToString() ?? "none"}");
}
else
{
Console.WriteLine($"[BOT] WARNING: Zone {zoneId} has no connection entry!");
Console.WriteLine($"[BOT] Total connections in world: {world.Connections.Count}");
Console.WriteLine($"[BOT] Connection zone IDs: {string.Join(", ", world.Connections.Keys.OrderBy(k => k).Take(20))}...");
}
// Check if this zone is in the grid
bool foundInGrid = false;
if (world.Grid != null)
{
for (int y = 0; y < 10 && !foundInGrid; y++)
{
for (int x = 0; x < 10 && !foundInGrid; x++)
{
if (world.Grid[y, x] == zoneId)
{
Console.WriteLine($"[BOT] Zone {zoneId} is at grid position ({x}, {y})");
foundInGrid = true;
}
}
}
}
if (!foundInGrid)
{
Console.WriteLine($"[BOT] WARNING: Zone {zoneId} is NOT in the world grid (may be Dagobah or room)");
}
}
}
@@ -65,6 +65,13 @@ public class MissionSolver
var phase = GetCurrentPhase();
var world = _worldGenerator.CurrentWorld;
// First priority: If we're in a room (small indoor zone), find the exit door
var doorExit = CheckForRoomExit();
if (doorExit != null)
{
return doorExit;
}
switch (phase)
{
case MissionPhase.TalkToYoda:
@@ -385,6 +392,42 @@ public class MissionSolver
};
}
/// <summary>
/// Checks if we're in a small room zone that needs an exit door.
/// Rooms are typically 9x9 zones with DoorExit objects.
/// </summary>
private BotObjective? CheckForRoomExit()
{
var zone = _state.CurrentZone;
if (zone == null) return null;
// Rooms are typically 9x9 (or smaller than 18x18 outdoor zones)
bool isRoom = zone.Width < 18 || zone.Height < 18;
if (!isRoom) return null;
// Find exit door in this room
var exitDoor = zone.Objects.FirstOrDefault(o =>
o.Type == ZoneObjectType.DoorExit ||
o.Type == ZoneObjectType.Teleporter);
if (exitDoor != null)
{
Console.WriteLine($"[BOT] In room zone {_state.CurrentZoneId} ({zone.Width}x{zone.Height}), found exit at ({exitDoor.X},{exitDoor.Y})");
return new BotObjective
{
Type = ObjectiveType.EnterDoor,
Description = $"Exit room via door at ({exitDoor.X},{exitDoor.Y})",
TargetX = exitDoor.X,
TargetY = exitDoor.Y,
TargetZoneId = exitDoor.Argument != 0xFFFF ? exitDoor.Argument : null
};
}
// No exit door found in room - this shouldn't happen
Console.WriteLine($"[BOT] WARNING: In room zone {_state.CurrentZoneId} but no exit door found!");
return null;
}
/// <summary>
/// Finds Yoda NPC in current zone.
/// </summary>
+4 -1
View File
@@ -193,11 +193,14 @@ public unsafe class GameEngine : IDisposable
_actionExecutor.OnDialogue += (speaker, text) => _messages.ShowDialogue(speaker, text);
}
// Initialize bot (but don't start it)
// Initialize bot and auto-start for debugging
if (_worldGenerator != null)
{
_bot = new MissionBot(_state, _gameData!, _worldGenerator);
_bot.OnActionRequested += HandleBotAction;
// Auto-start bot for debugging zone transitions
_bot.Start();
_messages.ShowMessage("Bot AUTO-STARTED - Press B to disable", MessageType.System);
}
}
@@ -686,14 +686,21 @@ public class WorldGenerator
// Initialize grid
CurrentWorld.Grid = new int?[GridSize, GridSize];
// Find zones matching the mission's planet type
// Find outdoor zones matching the mission's planet type (18x18 only, not 9x9 rooms)
var planetZones = _gameData.Zones
.Where(z => z.Planet == CurrentMission.Planet && z.Width > 0)
.Where(z => z.Planet == CurrentMission.Planet && z.Width == 18 && z.Height == 18)
.ToList();
// Also find indoor rooms for this planet (for door connections later)
var roomZones = _gameData.Zones
.Where(z => z.Planet == CurrentMission.Planet && z.Width > 0 && z.Width < 18)
.ToList();
Console.WriteLine($"Planet {CurrentMission.Planet}: found {planetZones.Count} outdoor zones, {roomZones.Count} room zones");
if (planetZones.Count == 0)
{
Console.WriteLine($"Warning: No zones found for planet {CurrentMission.Planet}");
Console.WriteLine($"Warning: No outdoor zones found for planet {CurrentMission.Planet}");
return;
}
@@ -702,9 +709,8 @@ public class WorldGenerator
var townZones = planetZones.Where(z => z.Type == ZoneType.Town).ToList();
var goalZones = planetZones.Where(z => z.Type == ZoneType.Goal).ToList();
var puzzleZones = planetZones.Where(z => z.Type == ZoneType.Trade || z.Type == ZoneType.Use || z.Type == ZoneType.Find).ToList();
var roomZones = planetZones.Where(z => z.Type == ZoneType.Room).ToList();
// If no categorization, use all zones
// If no categorization, use all outdoor zones
if (emptyZones.Count == 0) emptyZones = planetZones;
// 1. Place Landing Cell near center (one of 4 central squares)
@@ -795,6 +801,9 @@ public class WorldGenerator
{
if (CurrentWorld == null) return;
Console.WriteLine($"[WORLD] Setting up zone connections for {GridSize}x{GridSize} grid...");
int connectionCount = 0;
for (int y = 0; y < GridSize; y++)
{
for (int x = 0; x < GridSize; x++)
@@ -815,8 +824,29 @@ public class WorldGenerator
connections.South = CurrentWorld.Grid[y + 1, x];
CurrentWorld.Connections[zoneId.Value] = connections;
// Count actual connections
if (connections.North.HasValue) connectionCount++;
if (connections.South.HasValue) connectionCount++;
if (connections.East.HasValue) connectionCount++;
if (connections.West.HasValue) connectionCount++;
}
}
Console.WriteLine($"[WORLD] Created {CurrentWorld.Connections.Count} zone entries with {connectionCount} total connections");
// Print grid visualization
Console.WriteLine("[WORLD] Zone grid (10x10):");
for (int y = 0; y < GridSize; y++)
{
var row = "";
for (int x = 0; x < GridSize; x++)
{
var zoneId = CurrentWorld.Grid[y, x];
row += zoneId.HasValue ? $"{zoneId.Value,4}" : " .";
}
Console.WriteLine($" {row}");
}
}
/// <summary>
+51 -60
View File
@@ -698,75 +698,66 @@ public class DtaParser
{
var startPos = _reader.BaseStream.Position;
var endPos = startPos + length;
// Debug: dump first 32 bytes to understand structure
var debugBytes = _reader.ReadBytes(Math.Min(32, (int)length));
_reader.BaseStream.Seek(startPos, SeekOrigin.Begin);
Console.WriteLine($"PUZ2 first 32 bytes: {BitConverter.ToString(debugBytes)}");
// Try to find IPUZ markers in the section
int puzzleId = 0;
// First, read the puzzle count (2 bytes)
var puzzleCount = _reader.ReadUInt16();
Console.WriteLine($"PUZ2 section: expecting {puzzleCount} puzzles");
while (_reader.BaseStream.Position < endPos - 4 && puzzleId < puzzleCount)
while (_reader.BaseStream.Position < endPos - 8)
{
// Look for IPUZ marker (similar to IZON in zones)
var markerPos = _reader.BaseStream.Position;
var markerBytes = _reader.ReadBytes(4);
var marker = System.Text.Encoding.ASCII.GetString(markerBytes);
if (marker != "IPUZ")
if (marker == "IPUZ")
{
// Not at IPUZ marker, try to find it
// Found IPUZ marker - read puzzle size
var puzzleSize = _reader.ReadUInt32();
var puzzleDataStart = _reader.BaseStream.Position;
var puzzle = new Puzzle { Id = puzzleId++ };
// IPUZ format: type(2), item1(2), item2(2), unknown(2), unknown(2), then 5 strings
if (puzzleSize >= 10)
{
var puzzleType = _reader.ReadUInt16();
puzzle.Type = (PuzzleType)puzzleType;
puzzle.Item1 = _reader.ReadUInt16();
puzzle.Item2 = _reader.ReadUInt16();
_reader.ReadUInt16(); // unknown
_reader.ReadUInt16(); // unknown
// Read strings
for (int i = 0; i < 5 && _reader.BaseStream.Position < puzzleDataStart + puzzleSize; i++)
{
var strLen = _reader.ReadUInt16();
if (strLen > 0 && strLen < 500)
{
var strBytes = _reader.ReadBytes(strLen);
puzzle.Strings.Add(System.Text.Encoding.ASCII.GetString(strBytes).TrimEnd('\0'));
}
else if (strLen == 0)
{
puzzle.Strings.Add("");
}
}
}
// Skip to end of this puzzle
_reader.BaseStream.Seek(puzzleDataStart + puzzleSize, SeekOrigin.Begin);
_data.Puzzles.Add(puzzle);
if (puzzleId <= 5)
Console.WriteLine($" Puzzle {puzzle.Id}: Type={puzzle.Type}, Item1={puzzle.Item1}, Item2={puzzle.Item2}, Str0={puzzle.Strings.FirstOrDefault() ?? ""}");
}
else
{
// Not at IPUZ, advance by 1 byte and try again
_reader.BaseStream.Seek(markerPos + 1, SeekOrigin.Begin);
continue;
}
// Found IPUZ marker - read puzzle size
var puzzleSize = _reader.ReadUInt32();
var puzzleDataStart = _reader.BaseStream.Position;
var puzzle = new Puzzle { Id = puzzleId++ };
// IPUZ format (after size):
// - 2 bytes: puzzle type (0=Quest, 1=Transport, 2=Trade, 3=Use, 4=Goal)
// - 2 bytes: item1 (item to bring/find)
// - 2 bytes: item2 (reward item or NPC tile)
// - 2 bytes: unknown flags
// - 2 bytes: unknown (possibly planet)
// - 5 strings (length-prefixed)
var puzzleType = _reader.ReadUInt16();
puzzle.Type = (PuzzleType)puzzleType;
puzzle.Item1 = _reader.ReadUInt16();
puzzle.Item2 = _reader.ReadUInt16();
var unknown2 = _reader.ReadUInt16();
var unknown3 = _reader.ReadUInt16();
// Read puzzle strings (5 strings typically)
// String 0: Puzzle name/description (e.g., "Give MUSHROOM to YODA")
// String 1: Hint text
// String 2-4: Additional dialogue
for (int i = 0; i < 5; i++)
{
if (_reader.BaseStream.Position >= puzzleDataStart + puzzleSize)
break;
var strLen = _reader.ReadUInt16();
if (strLen > 0 && strLen < 1000)
{
var strBytes = _reader.ReadBytes(strLen);
puzzle.Strings.Add(System.Text.Encoding.ASCII.GetString(strBytes).TrimEnd('\0'));
}
else if (strLen == 0)
{
puzzle.Strings.Add("");
}
else
{
// Invalid length, skip rest
break;
}
}
// Skip to end of this puzzle's data
_reader.BaseStream.Seek(puzzleDataStart + puzzleSize, SeekOrigin.Begin);
_data.Puzzles.Add(puzzle);
}
// Ensure we're at the end