342 lines
11 KiB
C++
342 lines
11 KiB
C++
/**
|
|
* Example implementation demonstrating the proposed visibility tracking system
|
|
* This shows how UIGridPoint, UIGridPointState, and libtcod maps work together
|
|
*/
|
|
|
|
#include <memory>
|
|
#include <unordered_map>
|
|
#include <vector>
|
|
#include <string>
|
|
|
|
// 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<std::string, std::vector<UIGridPointState>> grid_knowledge;
|
|
|
|
public:
|
|
// Get or create knowledge vector for a specific grid
|
|
std::vector<UIGridPointState>& 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<EntityGridKnowledge&>(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<EntityGridKnowledge&>(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<std::pair<int, int>> 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<UIEntity>();
|
|
auto dungeon_level1 = std::make_shared<UIGrid>("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<UIEntity>();
|
|
guardian->setOmniscient(true); // Knows everything about any grid it enters
|
|
|
|
auto temple = std::make_shared<UIGrid>("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<UIEntity>();
|
|
auto forest = std::make_shared<UIGrid>("forest", 40, 40);
|
|
auto cave = std::make_shared<UIGrid>("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<UIEntity>();
|
|
auto dungeon = std::make_shared<UIGrid>("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)
|
|
*/ |