/** * Example implementation demonstrating the proposed visibility tracking system * This shows how UIGridPoint, UIGridPointState, and libtcod maps work together */ #include #include #include #include // Forward declarations class UIGrid; class UIEntity; class TCODMap; /** * UIGridPoint - The "ground truth" of a grid cell * This represents the actual state of the world */ class UIGridPoint { public: // Core properties bool walkable = true; // Can entities move through this cell? bool transparent = true; // Does this cell block line of sight? int tilesprite = 0; // What tile to render // Visual properties sf::Color color; sf::Color color_overlay; // Grid position int grid_x, grid_y; UIGrid* parent_grid; // When these change, sync with TCOD map void setWalkable(bool value) { walkable = value; if (parent_grid) syncTCODMapCell(); } void setTransparent(bool value) { transparent = value; if (parent_grid) syncTCODMapCell(); } private: void syncTCODMapCell(); // Update TCOD map when properties change }; /** * UIGridPointState - What an entity knows about a grid cell * Each entity maintains one of these for each cell it has encountered */ class UIGridPointState { public: // Visibility state bool visible = false; // Currently in entity's FOV? bool discovered = false; // Has entity ever seen this cell? // When the entity last saw this cell (for fog of war effects) int last_seen_turn = -1; // What the entity remembers about this cell // (may be outdated if cell changed after entity saw it) bool remembered_walkable = true; bool remembered_transparent = true; int remembered_tilesprite = 0; // Update remembered state from actual grid point void updateFromTruth(const UIGridPoint& truth, int current_turn) { if (visible) { discovered = true; last_seen_turn = current_turn; remembered_walkable = truth.walkable; remembered_transparent = truth.transparent; remembered_tilesprite = truth.tilesprite; } } }; /** * EntityGridKnowledge - Manages an entity's knowledge across multiple grids * This allows entities to remember explored areas even when changing levels */ class EntityGridKnowledge { private: // Map from grid ID to the entity's knowledge of that grid std::unordered_map> grid_knowledge; public: // Get or create knowledge vector for a specific grid std::vector& getGridKnowledge(const std::string& grid_id, int grid_size) { auto& knowledge = grid_knowledge[grid_id]; if (knowledge.empty()) { knowledge.resize(grid_size); } return knowledge; } // Check if entity has visited this grid before bool hasGridKnowledge(const std::string& grid_id) const { return grid_knowledge.find(grid_id) != grid_knowledge.end(); } // Clear knowledge of a specific grid (e.g., for memory-wiping effects) void forgetGrid(const std::string& grid_id) { grid_knowledge.erase(grid_id); } // Get total number of grids this entity knows about size_t getKnownGridCount() const { return grid_knowledge.size(); } }; /** * Enhanced UIEntity with visibility tracking */ class UIEntity { private: // Entity properties float x, y; // Position UIGrid* current_grid; // Current grid entity is on EntityGridKnowledge knowledge; // Multi-grid knowledge storage int sight_radius = 10; // How far entity can see bool omniscient = false; // Does entity know everything? public: // Update entity's FOV and visibility knowledge void updateFOV(int radius = -1) { if (!current_grid) return; if (radius < 0) radius = sight_radius; // Get entity's knowledge of current grid auto& grid_knowledge = knowledge.getGridKnowledge( current_grid->getGridId(), current_grid->getGridSize() ); // Reset visibility for all cells for (auto& cell_knowledge : grid_knowledge) { cell_knowledge.visible = false; } if (omniscient) { // Omniscient entities see everything for (int i = 0; i < grid_knowledge.size(); i++) { grid_knowledge[i].visible = true; grid_knowledge[i].discovered = true; grid_knowledge[i].updateFromTruth( current_grid->getPointAt(i), current_grid->getCurrentTurn() ); } } else { // Normal FOV calculation using TCOD current_grid->computeFOVForEntity(this, (int)x, (int)y, radius); // Update visibility states based on TCOD FOV results for (int gy = 0; gy < current_grid->getHeight(); gy++) { for (int gx = 0; gx < current_grid->getWidth(); gx++) { int idx = gy * current_grid->getWidth() + gx; if (current_grid->isCellInFOV(gx, gy)) { grid_knowledge[idx].visible = true; grid_knowledge[idx].updateFromTruth( current_grid->getPointAt(idx), current_grid->getCurrentTurn() ); } } } } } // Check if entity can see a specific position bool canSeePosition(int gx, int gy) const { if (!current_grid) return false; auto& grid_knowledge = const_cast(knowledge).getGridKnowledge( current_grid->getGridId(), current_grid->getGridSize() ); int idx = gy * current_grid->getWidth() + gx; return idx >= 0 && idx < grid_knowledge.size() && grid_knowledge[idx].visible; } // Check if entity has ever discovered a position bool hasDiscoveredPosition(int gx, int gy) const { if (!current_grid) return false; auto& grid_knowledge = const_cast(knowledge).getGridKnowledge( current_grid->getGridId(), current_grid->getGridSize() ); int idx = gy * current_grid->getWidth() + gx; return idx >= 0 && idx < grid_knowledge.size() && grid_knowledge[idx].discovered; } // Find path using only discovered/remembered terrain std::vector> findKnownPath(int dest_x, int dest_y) { if (!current_grid) return {}; // Create a TCOD map based on entity's knowledge auto knowledge_map = current_grid->createKnowledgeMapForEntity(this); // Use A* on the knowledge map auto path = knowledge_map->computePath((int)x, (int)y, dest_x, dest_y); delete knowledge_map; return path; } // Move to a new grid, preserving knowledge of the old one void moveToGrid(UIGrid* new_grid) { if (current_grid) { // Knowledge is automatically preserved in the knowledge map current_grid->removeEntity(this); } current_grid = new_grid; if (new_grid) { new_grid->addEntity(this); // If we've been here before, we still remember it updateFOV(); } } }; /** * Example use cases */ // Use Case 1: Player exploring a dungeon void playerExploration() { auto player = std::make_shared(); auto dungeon_level1 = std::make_shared("dungeon_level_1", 50, 50); // Player starts with no knowledge player->moveToGrid(dungeon_level1.get()); player->updateFOV(10); // Can see 10 tiles in each direction // Only render what player can see dungeon_level1->renderWithEntityPerspective(player.get()); // Player tries to path to unexplored area auto path = player->findKnownPath(45, 45); if (path.empty()) { // "You haven't explored that area yet!" } } // Use Case 2: Entity with perfect knowledge void omniscientEntity() { auto guardian = std::make_shared(); guardian->setOmniscient(true); // Knows everything about any grid it enters auto temple = std::make_shared("temple", 30, 30); guardian->moveToGrid(temple.get()); // Guardian immediately knows entire layout auto path = guardian->findKnownPath(29, 29); // Can path anywhere } // Use Case 3: Entity returning to previously explored area void returningToArea() { auto scout = std::make_shared(); auto forest = std::make_shared("forest", 40, 40); auto cave = std::make_shared("cave", 20, 20); // Scout explores forest scout->moveToGrid(forest.get()); scout->updateFOV(15); // ... scout moves around, discovering ~50% of forest ... // Scout enters cave scout->moveToGrid(cave.get()); scout->updateFOV(8); // Darker in cave, reduced vision // Later, scout returns to forest scout->moveToGrid(forest.get()); // Scout still remembers the areas previously explored! // Can immediately path through known areas auto path = scout->findKnownPath(10, 10); // Works if area was explored before } // Use Case 4: Fog of war - remembered vs current state void fogOfWar() { auto player = std::make_shared(); auto dungeon = std::make_shared("dungeon", 50, 50); player->moveToGrid(dungeon.get()); player->setPosition(25, 25); player->updateFOV(10); // Player sees a door at (30, 25) - it's open auto& door_point = dungeon->at(30, 25); door_point.walkable = true; door_point.transparent = true; // Player moves away player->setPosition(10, 10); player->updateFOV(10); // While player is gone, door closes door_point.walkable = false; door_point.transparent = false; // Player's memory still thinks door is open auto& player_knowledge = player->getKnowledgeAt(30, 25); // player_knowledge.remembered_walkable is still true! // Player tries to path through the door based on memory auto path = player->findKnownPath(35, 25); // Path planning succeeds based on remembered state // But when player gets close enough to see it again... player->setPosition(25, 25); player->updateFOV(10); // Knowledge updates - door is actually closed! } /** * Proper use of each component: * * UIGridPoint: * - Stores the actual, current state of the world * - Used by the game logic to determine what really happens * - Syncs with TCOD map for consistent pathfinding/FOV * * UIGridPointState: * - Stores what an entity believes/remembers about a cell * - May be outdated if world changed since last seen * - Used for rendering fog of war and entity decision-making * * TCOD Map: * - Provides efficient FOV and pathfinding algorithms * - Can be created from either ground truth or entity knowledge * - Multiple maps can exist (one for truth, one per entity for knowledge-based pathfinding) */