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

-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
@@ -105,8 +105,6 @@ namespace FRG {
const IGameServer& server() const { return gameServer; }
MenuManager menuManager;
void activateSlowMoEffect();
#ifdef EMSCRIPTEN
static Game* s_instance;
+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) {