Remove broad command

This commit is contained in:
Vladislav Khorev committed 2026-11-06 21:30:17 +06:00
1 parent 1ab9f300b6
commit 3f68a8ba07
11 files changed
+120 -160

No files matched your search

+3 -1
View File
@@ -140,7 +140,9 @@ Snapshots have a monotonically increasing revision. `InternalGameServer` caches
- Script globals.
- Character transforms, attack targets, navigation destinations, stopping,
facing, replanning, health, damage, death reset, attack combos, and animation
metadata.
metadata. Character position, facing angle, home position, face target, and
enabled state use separate semantic commands; no whole-character commit
command is exposed.
- Interactive-object activation.
- Trigger-zone state.
- Quest-definition reloads.
-18
View File
@@ -144,24 +144,6 @@ CharacterUpdateEvents CharacterState::update(
CharacterUpdateEvents events;
if (!enabled) return events;
if (slowMoTimeRemaining > 0.0f) {
slowMoTimeRemaining = (std::max)(
0.0f, slowMoTimeRemaining - static_cast<float>(deltaMs) / 1000.0f);
float transitionProgress = 1.0f;
if (slowMoTimeRemaining > slowMoActiveTime - slowMoTransitionTime) {
const float timeInTransition = slowMoActiveTime - slowMoTimeRemaining;
transitionProgress = (std::min)(1.0f, timeInTransition / slowMoTransitionTime);
} else if (slowMoTimeRemaining < slowMoTransitionTime) {
transitionProgress = (std::max)(0.0f, slowMoTimeRemaining / slowMoTransitionTime);
}
const float speedMultiplier = isPlayer
? 1.0f + 0.5f * transitionProgress
: 1.0f - 0.9f * transitionProgress;
deltaMs = static_cast<std::int64_t>(
static_cast<float>(deltaMs) * speedMultiplier);
}
const CharacterObservation* attackTarget = context.attackTarget;
const CharacterObservation* faceTarget = context.faceTarget;
if (!attackTarget) {
-5
View File
@@ -97,11 +97,6 @@ public:
Eigen::Vector3f homePosition = Eigen::Vector3f::Zero();
float maxHomeDrift = 3.0f;
// --- Slow-motion effect ---
float slowMoTimeRemaining = 0.0f;
float slowMoActiveTime = 6.0f;
float slowMoTransitionTime = 0.5f;
// --- Authoritative logical animation clock ---
// Frame counts are immutable metadata copied from loaded clips during
// character construction. Simulation advances animationFrame itself.
+4 -25
View File
@@ -649,11 +649,10 @@ namespace FRG
Location* loc = currentLocation();
if (!loc) return;
if (loc->player) {
loc->updateCharacterState(*loc->player, [&](CharacterState& workingState) {
workingState.position = destPos;
workingState.facingAngle = destRotY;
workingState.targetFacingAngle = destRotY;
});
server().submit(SetCharacterPositionCommand{
destName, loc->player->entityId(), destPos});
server().submit(SetCharacterFacingAngleCommand{
destName, loc->player->entityId(), destRotY, true});
loc->stopCharacter(*loc->player);
}
loc->localState().cameraAzimuth = destRotY;
@@ -1536,26 +1535,6 @@ namespace FRG
return n;
}
void Game::activateSlowMoEffect() {
if (!currentLocation()) return;
if (currentLocation()->player) {
currentLocation()->updateCharacterState(
*currentLocation()->player, [](CharacterState& workingState) {
workingState.slowMoTimeRemaining = workingState.slowMoActiveTime;
});
}
for (auto& npc : currentLocation()->npcs) {
if (npc) {
currentLocation()->updateCharacterState(
*npc, [](CharacterState& workingState) {
workingState.slowMoTimeRemaining = workingState.slowMoActiveTime;
});
}
}
}
void Game::enterCameraDragMode(int eventX, int eventY)
{
cameraDragging = true;
-2
View File
@@ -106,8 +106,6 @@ namespace FRG {
MenuManager menuManager;
void activateSlowMoEffect();
#ifdef EMSCRIPTEN
static Game* s_instance;
#endif
+17 -42
View File
@@ -132,18 +132,6 @@ namespace FRG
locationId_, character.entityId(), target, std::move(callbackName)});
}
void Location::updateCharacterState(
Character& character,
const std::function<void(CharacterState&)>& mutation)
{
auto snapshot = character.stateSnapshot();
if (!snapshot) return;
CharacterState stateCopy = *snapshot;
mutation(stateCopy);
gameServer_.submit(CommitCharacterStateCommand{
locationId_, character.entityId(), std::move(stateCopy)});
}
void Location::clearCharacterAttackTarget(Character& character)
{
gameServer_.submit(ClearCharacterAttackTargetCommand{
@@ -1847,7 +1835,6 @@ namespace FRG
void Location::update(int64_t delta, const LocationSimulationEvents& events)
{
pathVisualization.update(delta);
synchronizeRemotePlayers();
auto locationState = stateSnapshot();
if (!locationState) return;
@@ -2022,7 +2009,7 @@ namespace FRG
Eigen::Vector3f camRight(cosAzim, 0.f, sinAzim);
Eigen::Vector3f camForward(sinAzim * cosIncl, -sinIncl, -cosAzim * cosIncl);
Eigen::Vector3f camUp(sinAzim * sinIncl, cosIncl, -cosAzim * sinIncl);
const Eigen::Vector3f playerPos = playerState->position;
const Eigen::Vector3f playerPos = player->renderPosition();
Eigen::Vector3f camPos = playerPos + Eigen::Vector3f(-sinAzim * cosIncl, sinIncl, cosAzim * cosIncl) * Environment::zoom + Eigen::Vector3f(0,1,0);
Eigen::Vector3f rayDir = (camForward + camRight * (ndcX * aspect * tanHalfFov) + camUp * (ndcY * tanHalfFov)).normalized();
@@ -2065,10 +2052,7 @@ namespace FRG
? clickedObject->state.interactionPosition
: clickedObject->state.position;
setCharacterTarget(*player, interactionTarget);
auto updatedPlayerState = player->stateSnapshot();
if (updatedPlayerState && !updatedPlayerState->pathWaypoints.empty()) {
pathVisualization.start(*player, interactionTarget);
}
pathVisualization.start(*player, interactionTarget);
clearCharacterAttackTarget(*player);
//logger() << "[CLICK] Player moving to object..." << std::endl;
}
@@ -2101,21 +2085,17 @@ namespace FRG
tryCharacterAttackCombo(*player);
}
const std::string targetId = clickedNpc->entityId();
updateCharacterState(*player, [targetId](CharacterState& workingState) {
workingState.attackTargetId = targetId;
});
gameServer_.submit(SetCharacterTargetCommand{
locationId_, player->entityId(), targetId});
gameServer_.submit(SetLocationTargetsCommand{
locationId_, std::string(), std::string(), std::string()});
if (distance >= Character::kAttackRange) {
setCharacterTarget(*player, clickedNpcState->position);
auto updatedPlayerState = player->stateSnapshot();
if (updatedPlayerState && !updatedPlayerState->pathWaypoints.empty()) {
pathVisualization.start(
*player,
clickedNpcState->position,
PathVisualization::Style::Hostile);
}
pathVisualization.start(
*player,
clickedNpcState->position,
PathVisualization::Style::Hostile);
}
if (distance <= clickedNpcState->interactionRadius) {
@@ -2131,9 +2111,9 @@ namespace FRG
// Already in talk range — fire immediately, stop, and face the player.
//logger() << "[CLICK] *** SUCCESS: Clicked on NPC index: " << npcIndex << " (in range) ***" << std::endl;
setCharacterTarget(*player, playerState->position);
updateCharacterState(*npcs[npcIndex], [this](CharacterState& workingState) {
workingState.faceTargetId = gameServer_.localPlayerId();
});
gameServer_.submit(SetCharacterFaceTargetCommand{
locationId_, npcs[npcIndex]->entityId(),
gameServer_.localPlayerId()});
scriptEngine.callNpcInteractCallback(clickedNpc->entityId());
gameServer_.submit(SetLocationTargetsCommand{
locationId_, std::string(), std::string(), std::string()});
@@ -2142,10 +2122,7 @@ namespace FRG
//logger() << "[CLICK] NPC " << npcIndex << " out of talk range (distance " << distance
// << " > " << NPC_TALK_DISTANCE << "); walking to NPC..." << std::endl;
setCharacterTarget(*player, clickedNpcState->position);
auto updatedPlayerState = player->stateSnapshot();
if (updatedPlayerState && !updatedPlayerState->pathWaypoints.empty()) {
pathVisualization.start(*player, clickedNpcState->position);
}
pathVisualization.start(*player, clickedNpcState->position);
gameServer_.submit(SetLocationTargetsCommand{
locationId_, std::string(), clickedNpc->entityId(),
std::string()});
@@ -2173,13 +2150,10 @@ namespace FRG
locationId_, std::string(), std::string(),
clickedTeleport->id});
setCharacterTarget(*player, clickedTeleport->position);
auto updatedPlayerState = player->stateSnapshot();
if (updatedPlayerState && !updatedPlayerState->pathWaypoints.empty()) {
pathVisualization.start(
*player,
clickedTeleport->position,
PathVisualization::Style::Teleport);
}
pathVisualization.start(
*player,
clickedTeleport->position,
PathVisualization::Style::Teleport);
clearCharacterAttackTarget(*player);
} else {
const Eigen::Vector3f clickedDestination(hit.x(), 0.f, hit.z());
@@ -2235,6 +2209,7 @@ namespace FRG
void Location::updatePresentation(int64_t delta)
{
dialogueSystem.update(static_cast<int>(delta));
pathVisualization.update(delta);
for (auto& tz : teleportZones)
tz.update(static_cast<float>(delta));
}
+1 -3
View File
@@ -80,6 +80,7 @@ namespace FRG
// Detaches the (shared, longer-lived) player from this location.
~Location();
const std::string& locationId() const { return locationId_; }
IGameServer& gameServer() { return gameServer_; }
std::shared_ptr<const LocationNetworkState> stateSnapshot() const;
LocationLocalState& localState() { return *localState_; }
const LocationLocalState& localState() const { return *localState_; }
@@ -175,9 +176,6 @@ namespace FRG
bool switchNavigation(int index);
void setCharacterTarget(Character& character, const Eigen::Vector3f& target,
std::string callbackName = "");
void updateCharacterState(
Character& character,
const std::function<void(CharacterState&)>& mutation);
void clearCharacterAttackTarget(Character& character);
void stopCharacter(Character& character);
void faceCharacter(Character& character, std::string targetId);
+20 -44
View File
@@ -124,9 +124,8 @@ namespace FRG {
if (cbName.empty())
std::cerr << "[script] npc_walk_to: on_arrived must be a global Lua function\n";
}
loc->updateCharacterState(*npc, [x, z](CharacterState& workingState) {
workingState.homePosition = Eigen::Vector3f(x, 0.f, z);
});
loc->gameServer().submit(SetCharacterHomePositionCommand{
loc->locationId(), npc->entityId(), Eigen::Vector3f(x, 0.f, z)});
loc->setCharacterTarget(
*npc, Eigen::Vector3f(x, y, z), std::move(cbName));
});
@@ -142,18 +141,16 @@ namespace FRG {
const float rad = angle * static_cast<float>(M_PI) / 180.f;
logger() << "[script] npc_rotate_to: " << npcId << " angle " << angle << " degrees (" << rad << " radians)" << std::endl;
loc->updateCharacterState(*npc, [rad](CharacterState& workingState) {
workingState.targetFacingAngle = rad;
});
loc->gameServer().submit(SetCharacterFacingAngleCommand{
loc->locationId(), npc->entityId(), rad, false});
});
api.set_function("player_rotate_to",
[loc](float angle) {
const float rad = angle * static_cast<float>(M_PI) / 180.f;
logger() << "[script] player_rotate_to: angle " << angle << " degrees (" << rad << " radians)" << std::endl;
loc->updateCharacterState(*loc->player, [rad](CharacterState& workingState) {
workingState.targetFacingAngle = rad;
});
loc->gameServer().submit(SetCharacterFacingAngleCommand{
loc->locationId(), loc->player->entityId(), rad, false});
});
api.set_function("player_stop_and_rotate_to_npc",
@@ -188,9 +185,9 @@ namespace FRG {
if (cbName.empty())
std::cerr << "[script] player_walk_to: on_arrived must be a global Lua function\n";
}
loc->updateCharacterState(*loc->player, [x, z](CharacterState& workingState) {
workingState.homePosition = Eigen::Vector3f(x, 0.f, z);
});
loc->gameServer().submit(SetCharacterHomePositionCommand{
loc->locationId(), loc->player->entityId(),
Eigen::Vector3f(x, 0.f, z)});
loc->setCharacterTarget(
*loc->player, Eigen::Vector3f(x, y, z), std::move(cbName));
});
@@ -440,13 +437,7 @@ namespace FRG {
if (loc->player)
{
loc->setCharacterHp(*loc->player, value);
loc->updateCharacterState(*loc->player, [](CharacterState& workingState) {
if (workingState.currentState == AnimationState::ACTION_TO_DEATH ||
workingState.currentState == AnimationState::DEATH_IDLE) {
workingState.currentState = AnimationState::STAND;
workingState.resetAnim = true;
}
});
loc->resetCharacterAfterDeath(*loc->player);
}
});
// get_player_hp() → float (0 if no player)
@@ -602,10 +593,8 @@ namespace FRG {
return;
}
Eigen::Vector3f pos(x, y, z);
loc->updateCharacterState(*npc, [&pos](CharacterState& workingState) {
workingState.position = pos;
workingState.homePosition = pos;
});
loc->gameServer().submit(SetCharacterPositionCommand{
loc->locationId(), npc->entityId(), pos, true});
loc->setCharacterTarget(*npc, pos);
});
@@ -618,10 +607,8 @@ namespace FRG {
return;
}
const float rad = angle * static_cast<float>(M_PI) / 180.f;
loc->updateCharacterState(*npc, [rad](CharacterState& workingState) {
workingState.facingAngle = rad;
workingState.targetFacingAngle = rad;
});
loc->gameServer().submit(SetCharacterFacingAngleCommand{
loc->locationId(), npc->entityId(), rad, true});
});
// set_npc_enabled(npc_id, enabled)
@@ -632,34 +619,23 @@ namespace FRG {
std::cerr << "[script] set_npc_enabled: unknown NPC id '" << npcId << "'\n";
return;
}
loc->updateCharacterState(*npc, [value](CharacterState& workingState) {
workingState.enabled = value;
workingState.attack = 0;
workingState.attack_cooldown = 0;
workingState.currentState = AnimationState::STAND;
if (workingState.canAttack)
workingState.attackTargetId = "player";
workingState.battle_state = 0;
});
loc->gameServer().submit(SetCharacterEnabledCommand{
loc->locationId(), npc->entityId(), value});
});
api.set_function("player_set_position",
[loc](float x, float y, float z) {
Eigen::Vector3f pos(x, y, z);
loc->updateCharacterState(*loc->player, [&pos](CharacterState& workingState) {
workingState.position = pos;
workingState.homePosition = pos;
});
loc->gameServer().submit(SetCharacterPositionCommand{
loc->locationId(), loc->player->entityId(), pos, true});
loc->setCharacterTarget(*loc->player, pos);
});
api.set_function("player_set_rotation",
[loc](float angle) {
const float rad = angle * static_cast<float>(M_PI) / 180.f;
loc->updateCharacterState(*loc->player, [rad](CharacterState& workingState) {
workingState.facingAngle = rad;
workingState.targetFacingAngle = rad;
});
loc->gameServer().submit(SetCharacterFacingAngleCommand{
loc->locationId(), loc->player->entityId(), rad, true});
});
// move_object(name, x, y, z, duration_sec [, on_complete])
+28 -9
View File
@@ -136,17 +136,38 @@ struct SetQuestActiveObjectiveCommand {
};
struct SetGlobalIntCommand { std::string key; int value; };
struct SetGlobalFloatCommand { std::string key; float value; };
struct SetCharacterTransformCommand {
struct SetCharacterPositionCommand {
std::string locationId;
std::string characterId;
Eigen::Vector3f position;
bool updateHomePosition = false;
};
struct SetCharacterFacingAngleCommand {
std::string locationId;
std::string characterId;
float angle;
bool setCurrentAngle = false;
};
struct SetCharacterHomePositionCommand {
std::string locationId;
std::string characterId;
Eigen::Vector3f position;
float facingAngle;
};
struct SetCharacterTargetCommand {
std::string locationId;
std::string characterId;
std::string targetId;
};
struct SetCharacterFaceTargetCommand {
std::string locationId;
std::string characterId;
std::string targetId;
};
struct SetCharacterEnabledCommand {
std::string locationId;
std::string characterId;
bool enabled;
};
struct ClearCharacterAttackTargetCommand {
std::string locationId;
std::string characterId;
@@ -196,11 +217,6 @@ struct DamageCharacterCommand {
float amount;
std::string attackerId;
};
struct CommitCharacterStateCommand {
std::string locationId;
std::string characterId;
CharacterState state;
};
struct SetInteractiveObjectActiveCommand {
std::string locationId;
std::string objectId;
@@ -241,8 +257,12 @@ using GameCommand = std::variant<
SetQuestActiveObjectiveCommand,
SetGlobalIntCommand,
SetGlobalFloatCommand,
SetCharacterTransformCommand,
SetCharacterPositionCommand,
SetCharacterFacingAngleCommand,
SetCharacterHomePositionCommand,
SetCharacterTargetCommand,
SetCharacterFaceTargetCommand,
SetCharacterEnabledCommand,
ClearCharacterAttackTargetCommand,
SetCharacterDestinationCommand,
StopCharacterCommand,
@@ -253,7 +273,6 @@ using GameCommand = std::variant<
SetCharacterHealthCommand,
SetCharacterAnimationMetadataCommand,
DamageCharacterCommand,
CommitCharacterStateCommand,
SetInteractiveObjectActiveCommand,
SetTriggerZoneEnabledCommand,
ReloadQuestDefinitionsCommand>;
+32 -7
View File
@@ -378,18 +378,47 @@ void InternalGameServer::submit(const GameCommand& command)
state_.globalInts[value.key] = value.value;
} else if constexpr (std::is_same_v<T, SetGlobalFloatCommand>) {
state_.globalFloats[value.key] = value.value;
} else if constexpr (std::is_same_v<T, SetCharacterTransformCommand>) {
} else if constexpr (std::is_same_v<T, SetCharacterPositionCommand>) {
CharacterState* character =
state_.findCharacter(value.locationId, value.characterId);
if (character) {
character->position = value.position;
character->facingAngle = value.facingAngle;
character->targetFacingAngle = value.facingAngle;
if (value.updateHomePosition)
character->homePosition = value.position;
}
} else if constexpr (std::is_same_v<T, SetCharacterFacingAngleCommand>) {
CharacterState* character =
state_.findCharacter(value.locationId, value.characterId);
if (character) {
character->targetFacingAngle = value.angle;
if (value.setCurrentAngle)
character->facingAngle = value.angle;
}
} else if constexpr (std::is_same_v<T, SetCharacterHomePositionCommand>) {
CharacterState* character =
state_.findCharacter(value.locationId, value.characterId);
if (character)
character->homePosition = value.position;
} else if constexpr (std::is_same_v<T, SetCharacterTargetCommand>) {
CharacterState* character =
state_.findCharacter(value.locationId, value.characterId);
if (character) character->attackTargetId = value.targetId;
} else if constexpr (std::is_same_v<T, SetCharacterFaceTargetCommand>) {
CharacterState* character =
state_.findCharacter(value.locationId, value.characterId);
if (character) character->faceTargetId = value.targetId;
} else if constexpr (std::is_same_v<T, SetCharacterEnabledCommand>) {
CharacterState* character =
state_.findCharacter(value.locationId, value.characterId);
if (character) {
character->enabled = value.enabled;
character->attack = 0;
character->attack_cooldown = 0;
character->currentState = AnimationState::STAND;
if (character->canAttack)
character->attackTargetId = "player";
character->battle_state = 0;
}
} else if constexpr (std::is_same_v<T, ClearCharacterAttackTargetCommand>) {
CharacterState* character =
state_.findCharacter(value.locationId, value.characterId);
@@ -445,10 +474,6 @@ void InternalGameServer::submit(const GameCommand& command)
CharacterState* character =
state_.findCharacter(value.locationId, value.characterId);
if (character) character->hp = (std::max)(0.f, character->hp - (std::max)(0.f, value.amount));
} else if constexpr (std::is_same_v<T, CommitCharacterStateCommand>) {
CharacterState* character =
state_.findCharacter(value.locationId, value.characterId);
if (character) *character = value.state;
} else if constexpr (std::is_same_v<T, SetInteractiveObjectActiveCommand>) {
auto location = state_.locations.find(value.locationId);
if (location == state_.locations.end()) return;
+15 -4
View File
@@ -104,17 +104,28 @@ namespace FRG {
if (!state) return;
std::vector<Eigen::Vector3f> route;
route.reserve(state->pathWaypoints.size() + 1);
route.push_back(Eigen::Vector3f(state->position.x(), 0.0f, state->position.z()));
const Eigen::Vector3f renderPosition = player.renderPosition();
route.push_back(Eigen::Vector3f(renderPosition.x(), 0.0f, renderPosition.z()));
const size_t firstWaypoint = (std::min)(
state->currentWaypointIndex, state->pathWaypoints.size());
for (size_t i = firstWaypoint; i < state->pathWaypoints.size(); ++i) {
const Eigen::Vector3f& waypoint = state->pathWaypoints[i];
route.push_back(Eigen::Vector3f(waypoint.x(), 0.0f, waypoint.z()));
}
const Eigen::Vector3f clickedPosition(
clickedDestination.x(), 0.0f, clickedDestination.z());
if (route.size() == 1 ||
(route.back() - clickedPosition).norm() > 0.5f) {
// A remote destination is not present in the replica yet, or the
// replica still contains the route for a previous click. Preview
// the new intent directly until authoritative waypoints catch up.
route.clear();
route.push_back(Eigen::Vector3f(
renderPosition.x(), 0.0f, renderPosition.z()));
route.push_back(clickedPosition);
}
destination = route.size() > 1
? route.back()
: Eigen::Vector3f(clickedDestination.x(), 0.0f, clickedDestination.z());
destination = clickedPosition;
float totalLength = 0.0f;
for (size_t i = 1; i < route.size(); ++i) {