Working on hostile NPCs, fixing darklands mode for battle zone

This commit is contained in:
Vladislav Khorev committed 2026-11-07 00:03:03 +06:00
1 parent 2e6b93f3ac
commit f4bd3b6bed
24 files changed
+23930 -1457

No files matched your search

+9 -5
View File
@@ -66,7 +66,9 @@ void CharacterState::setTarget(
requestedWalkTarget = normalizedTarget;
onArrivedCallbackName = std::move(callbackName);
pathWaypoints = location.planPathExceptMyself(position, normalizedTarget, entityId);
pathWaypoints = location.navigationMaps.empty()
? std::vector<Eigen::Vector3f>{}
: location.planPathExceptMyself(position, normalizedTarget, entityId);
currentWaypointIndex = 0;
if (!pathWaypoints.empty()) {
for (Eigen::Vector3f& waypoint : pathWaypoints) waypoint.y() = 0.f;
@@ -83,7 +85,9 @@ void CharacterState::forceReplan(
{
const Eigen::Vector3f normalizedTarget(
requestedWalkTarget.x(), 0.f, requestedWalkTarget.z());
pathWaypoints = location.planPathExceptMyself(position, normalizedTarget, entityId);
pathWaypoints = location.navigationMaps.empty()
? std::vector<Eigen::Vector3f>{}
: location.planPathExceptMyself(position, normalizedTarget, entityId);
currentWaypointIndex = 0;
if (!pathWaypoints.empty()) {
for (Eigen::Vector3f& waypoint : pathWaypoints) waypoint.y() = 0.f;
@@ -146,15 +150,15 @@ CharacterUpdateEvents CharacterState::update(
const CharacterObservation* attackTarget = context.attackTarget;
const CharacterObservation* faceTarget = context.faceTarget;
if (!attackTarget) {
if (!attackTarget || attackTarget->hp <= 0.f) {
battle_state = 0;
attack = 0;
attackTargetId.clear();
} else {
const float distance = (attackTarget->position - position).norm();
if (distance >= visibleRange) {
if (isPlayer &&
(attackTarget->position - requestedWalkTarget).norm() > TARGET_REPLAN_THRESHOLD) {
if ((attackTarget->position - requestedWalkTarget).norm() >
TARGET_REPLAN_THRESHOLD) {
events.navigationRequested = true;
events.navigationTarget = attackTarget->position;
}
+2 -2
View File
@@ -21,8 +21,8 @@ int Environment::width = CONST_DEFAULT_WIDTH;
int Environment::height = CONST_DEFAULT_HEIGHT;
float Environment::zoom = DEFAULT_ZOOM;
bool Environment::enableLogging = false;
//bool Environment::enableLogging = true;
//bool Environment::enableLogging = false;
bool Environment::enableLogging = true;
SDL_Window* Environment::window = nullptr;
+73 -11
View File
@@ -263,6 +263,78 @@ LocationSimulationEvents LocationGameState::update(
observations.emplace(
id, CharacterObservation{npc->position, npc->hp});
std::vector<std::string> playerIds;
playerIds.reserve(players.size());
for (const auto& [id, player] : players)
if (player) playerIds.push_back(id);
std::sort(playerIds.begin(), playerIds.end());
std::vector<std::string> eligiblePlayerIds;
for (const std::string& id : playerIds) {
const auto& player = players.at(id);
if (player->enabled && player->hp > 0.f)
eligiblePlayerIds.push_back(id);
}
std::vector<std::string> npcIds;
npcIds.reserve(npcs.size());
for (const auto& [id, npc] : npcs)
if (npc) npcIds.push_back(id);
std::sort(npcIds.begin(), npcIds.end());
// Target selection is authoritative and runs before all characters observe
// the tick. A valid target is a lock: it is retained until the player dies
// or becomes disabled, even when the player leaves visibleRange.
for (const std::string& id : npcIds) {
CharacterState& npc = *npcs.at(id);
if (!npc.enabled || !npc.canAttack || npc.hp <= 0.f)
continue;
const std::string previousTarget = npc.attackTargetId;
const auto locked = players.find(previousTarget);
const bool lockValid =
locked != players.end() && locked->second &&
locked->second->enabled && locked->second->hp > 0.f;
std::string selectedTarget = lockValid ? previousTarget : std::string();
if (!lockValid) {
float nearestDistanceSquared = npc.visibleRange * npc.visibleRange;
for (const std::string& playerId : eligiblePlayerIds) {
const Eigen::Vector3f delta =
players.at(playerId)->position - npc.position;
const float distanceSquared =
delta.x() * delta.x() + delta.z() * delta.z();
if (distanceSquared <= nearestDistanceSquared) {
if (selectedTarget.empty() ||
distanceSquared < nearestDistanceSquared ||
(distanceSquared == nearestDistanceSquared &&
playerId < selectedTarget)) {
selectedTarget = playerId;
nearestDistanceSquared = distanceSquared;
}
}
}
}
if (selectedTarget != previousTarget) {
npc.stopInPlace();
npc.attackTargetId = selectedTarget;
}
if (selectedTarget.empty()) {
npc.battle_state = 0;
npc.attack = 0;
const Eigen::Vector3f homeTarget(
npc.homePosition.x(), 0.f, npc.homePosition.z());
const Eigen::Vector3f toHome = homeTarget - npc.position;
if (toHome.norm() > CharacterState::kWalkThreshold &&
(npc.requestedWalkTarget - homeTarget).norm() >
CharacterState::kWalkThreshold) {
npc.setTarget(homeTarget, {}, *location, id);
}
}
}
auto advance = [&](const std::string& id, CharacterState& state) {
CharacterUpdateContext context;
auto attack = observations.find(state.attackTargetId);
@@ -276,19 +348,9 @@ LocationSimulationEvents LocationGameState::update(
}
};
std::vector<std::string> playerIds;
playerIds.reserve(players.size());
for (const auto& [id, player] : players)
if (player) playerIds.push_back(id);
std::sort(playerIds.begin(), playerIds.end());
for (const std::string& id : playerIds)
if (players.at(id)->enabled)
advance(id, *players.at(id));
std::vector<std::string> npcIds;
npcIds.reserve(npcs.size());
for (const auto& [id, npc] : npcs)
if (npc) npcIds.push_back(id);
std::sort(npcIds.begin(), npcIds.end());
for (const std::string& id : npcIds)
if (npcs.at(id)->enabled)
advance(id, *npcs.at(id));
@@ -296,7 +358,7 @@ LocationSimulationEvents LocationGameState::update(
for (const CharacterSimulationEvent& source : result.characters) {
if (!source.events.attackImpact) continue;
CharacterState* target = findCharacter(source.events.attackTargetId, players);
if (!target || !target->enabled) continue;
if (!target || !target->enabled || target->hp <= 0.f) continue;
constexpr float damage = 10.f;
target->setHp(target->hp - damage);
result.damage.push_back({
-1
View File
@@ -182,7 +182,6 @@ CharacterState NpcData::toCharacterState() const
state.initialHp = hp;
state.visibleRange = visibleRange;
state.canAttack = canAttack;
if (canAttack) state.attackTargetId = "player";
state.enabled = enabled;
state.interactionRadius = interactionRadius;
+1 -2
View File
@@ -86,6 +86,7 @@ void InternalGameServer::initializeCampaign(
campaign.id = campaignId;
campaign.questsPath = questsPath;
startCampaign(campaign, zipFile, true, true);
//startCampaign(campaign, zipFile, true, false);
}
void InternalGameServer::startCampaign(
@@ -420,8 +421,6 @@ void InternalGameServer::submit(const GameCommand& command)
character->stopInPlace();
character->faceTargetId.clear();
character->attackTargetId.clear();
if (value.enabled && !character->isPlayer && character->canAttack)
character->attackTargetId = "player";
character->battle_state = 0;
}
} else if constexpr (std::is_same_v<T, ClearCharacterAttackTargetCommand>) {