diff --git a/core/src/Villager.cpp b/core/src/Villager.cpp index d9bbef1..078dd77 100644 --- a/core/src/Villager.cpp +++ b/core/src/Villager.cpp @@ -155,3 +155,157 @@ bool isWorkstationBlock(Block block) { return professionForWorkstation(block) != VillagerProfession::Unemployed; } +int villagerExperienceForLevel(int level) { + if (level <= 1) return 0; + if (level >= villagerMaxLevel) return levelThresholds[villagerMaxLevel - 1]; + return levelThresholds[level - 1]; +} + +int villagerLevelForExperience(int experience) { + int level = 1; + for (int candidate = 2; candidate <= villagerMaxLevel; ++candidate) + if (experience >= levelThresholds[candidate - 1]) level = candidate; + return level; +} + +const char* villagerActivityName(VillagerActivity activity) { + switch (activity) { + case VillagerActivity::Idle: return "idle"; + case VillagerActivity::SeekWorkstation: return "seekWorkstation"; + case VillagerActivity::Work: return "work"; + case VillagerActivity::Meet: return "meet"; + case VillagerActivity::Sleep: return "sleep"; + case VillagerActivity::Panic: return "panic"; + case VillagerActivity::Count: break; + } + return "unknown"; +} + +void PoiRegistry::scan(const World& world, const Vec3i& center, int radius, int verticalRadius, + const PathConfig& config) { + clear(); + for (int x = center.x - radius; x <= center.x + radius; ++x) { + for (int y = center.y - verticalRadius; y <= center.y + verticalRadius; ++y) { + for (int z = center.z - radius; z <= center.z + radius; ++z) { + Block block = world.blockAt(x, y, z); + if (!poiBlock(block)) continue; + WorkstationPoi poi; + poi.pos = {x, y, z}; + poi.block = block; + poi.profession = professionForWorkstation(block); + poi.valid = true; + poi.reachable = resolveStandCell(world, poi.pos, block, config, poi.standCell); + if (block == Block::Bell && bell < 0) bell = static_cast(pois.size()); + pois.push_back(poi); + } + } + } +} + +int PoiRegistry::indexAt(const Vec3i& pos) const { + for (std::size_t i = 0; i < pois.size(); ++i) + if (pois[i].valid && pois[i].pos == pos) return static_cast(i); + return -1; +} + +int PoiRegistry::workstationCount() const { return countPois(false, false); } + +int PoiRegistry::bedCount() const { return countPois(true, false); } + +int PoiRegistry::freeWorkstationCount() const { return countPois(false, true); } + +int PoiRegistry::freeBedCount() const { return countPois(true, true); } + +int PoiRegistry::countPois(bool beds, bool onlyFree) const { + int count = 0; + for (const WorkstationPoi& poi : pois) { + if (beds ? !poi.isBed() : !poi.isWorkstation()) continue; + if (onlyFree ? !poi.free() : !poi.valid) continue; + ++count; + } + return count; +} + +int PoiRegistry::nearestFree(const Vec3i& from, bool beds, VillagerProfession wanted) const { + int best = -1; + int bestDistance = 0; + for (std::size_t i = 0; i < pois.size(); ++i) { + const WorkstationPoi& poi = pois[i]; + if (!poi.free() || !poi.reachable) continue; + if (beds ? !poi.isBed() : !poi.isWorkstation()) continue; + if (!beds && wanted != VillagerProfession::Unemployed && poi.profession != wanted) continue; + int distance = squaredDistance(poi.standCell, from); + if (best < 0 || distance < bestDistance) { + best = static_cast(i); + bestDistance = distance; + } + } + return best; +} + +int PoiRegistry::nearestFreeWorkstation(const Vec3i& from, VillagerProfession wanted) const { + return nearestFree(from, false, wanted); +} + +int PoiRegistry::nearestFreeBed(const Vec3i& from) const { + return nearestFree(from, true, VillagerProfession::Unemployed); +} + +bool PoiRegistry::claim(int index, int villagerId) { + if (index < 0 || index >= size()) return false; + WorkstationPoi& poi = pois[index]; + if (!poi.valid || poi.ownerId >= 0) return false; + poi.ownerId = villagerId; + return true; +} + +void PoiRegistry::release(int index) { + if (index < 0 || index >= size()) return; + pois[index].ownerId = -1; +} + +void PoiRegistry::releaseOwner(int villagerId) { + for (WorkstationPoi& poi : pois) + if (poi.ownerId == villagerId) poi.ownerId = -1; +} + +int PoiRegistry::revalidate(const World& world, const PathConfig& config) { + int lost = 0; + for (std::size_t i = 0; i < pois.size(); ++i) { + WorkstationPoi& poi = pois[i]; + if (!poi.valid) continue; + if (world.blockAt(poi.pos.x, poi.pos.y, poi.pos.z) != poi.block) { + poi.valid = false; + poi.ownerId = -1; + if (bell == static_cast(i)) bell = -1; + ++lost; + continue; + } + poi.reachable = resolveStandCell(world, poi.pos, poi.block, config, poi.standCell); + } + return lost; +} + +void PoiRegistry::clear() { + pois.clear(); + bell = -1; +} + +Vec3i Villager::cell() const { + return {static_cast(std::floor(body.position.x)), + static_cast(std::floor(body.position.y)), + static_cast(std::floor(body.position.z))}; +} + +Villager makeVillager(int id, const Vec3& position, uint32_t seed) { + Villager villager; + villager.id = id; + villager.body.position = position; + villager.body.width = 0.6f; + villager.body.height = 1.95f; + villager.rng = seed ^ (static_cast(id) * 0x27d4eb2du); + uint32_t draw = nextRandom(villager.rng); + if (draw % 8u == 0u) villager.profession = VillagerProfession::Nitwit; + return villager; +} +