Minor fixing
This commit is contained in:
1 parent
6cfd47e80d
commit
24c963c0a5
11 files changed
+123
-65
No files matched your search
+34
-3
@@ -5,17 +5,28 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
|
||||||
<title>Shadow Over Bishkek</title>
|
<title>Shadow Over Bishkek</title>
|
||||||
<style>
|
<style>
|
||||||
|
:root {
|
||||||
|
/* Visible viewport size, kept in sync by syncViewport() below.
|
||||||
|
100vh on mobile is the *largest* viewport (URL bar hidden) and
|
||||||
|
would push the bottom of the canvas off-screen. */
|
||||||
|
--app-w: 100%;
|
||||||
|
--app-h: 100dvh;
|
||||||
|
}
|
||||||
body, html {
|
body, html {
|
||||||
margin: 0; padding: 0; width: 100%; height: 100%;
|
margin: 0; padding: 0; width: 100%; height: 100%;
|
||||||
overflow: hidden; background-color: #000;
|
overflow: hidden; background-color: #000;
|
||||||
position: fixed;
|
position: fixed; top: 0; left: 0;
|
||||||
}
|
}
|
||||||
#canvas {
|
#canvas {
|
||||||
display: block;
|
display: block;
|
||||||
position: absolute;
|
position: fixed;
|
||||||
top: 0; left: 0;
|
top: 0; left: 0;
|
||||||
width: 100vw; height: 100vh;
|
width: var(--app-w); height: var(--app-h);
|
||||||
border: none;
|
border: none;
|
||||||
|
touch-action: none;
|
||||||
|
-webkit-touch-callout: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#status { color: white; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); }
|
#status { color: white; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); }
|
||||||
@@ -49,8 +60,28 @@
|
|||||||
loadGameScript();
|
loadGameScript();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function syncViewport() {
|
||||||
|
var vv = window.visualViewport;
|
||||||
|
var w = (vv && vv.width) || window.innerWidth;
|
||||||
|
var h = (vv && vv.height) || window.innerHeight;
|
||||||
|
var root = document.documentElement;
|
||||||
|
root.style.setProperty('--app-w', Math.round(w) + 'px');
|
||||||
|
root.style.setProperty('--app-h', Math.round(h) + 'px');
|
||||||
|
}
|
||||||
|
syncViewport();
|
||||||
|
// Registered before the game script loads, so the CSS size is updated
|
||||||
|
// before the engine's own resize handler reads it.
|
||||||
|
window.addEventListener('resize', syncViewport);
|
||||||
|
if (window.visualViewport) {
|
||||||
|
window.visualViewport.addEventListener('resize', function () {
|
||||||
|
syncViewport();
|
||||||
|
window.dispatchEvent(new Event('resize'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
window.addEventListener("orientationchange", function() {
|
window.addEventListener("orientationchange", function() {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
syncViewport();
|
||||||
window.dispatchEvent(new Event('resize'));
|
window.dispatchEvent(new Event('resize'));
|
||||||
}, 200);
|
}, 200);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -171,7 +171,6 @@ if(BUILD_TESTING)
|
|||||||
target_include_directories(
|
target_include_directories(
|
||||||
GameServerWebsocketLockstepTests PRIVATE "${BOOST_BUNDLED_DIR}")
|
GameServerWebsocketLockstepTests PRIVATE "${BOOST_BUNDLED_DIR}")
|
||||||
target_compile_definitions(
|
target_compile_definitions(
|
||||||
GameServerWebsocketLockstepTests PRIVATE NETWORK)
|
|
||||||
GameServerWebsocketLockstepTests PRIVATE
|
GameServerWebsocketLockstepTests PRIVATE
|
||||||
NETWORK WIN32_LEAN_AND_MEAN _WIN32_WINNT=0x0A00)
|
NETWORK WIN32_LEAN_AND_MEAN _WIN32_WINNT=0x0A00)
|
||||||
if(MSVC)
|
if(MSVC)
|
||||||
@@ -197,7 +196,7 @@ if(BUILD_TESTING)
|
|||||||
COMMAND GameServer --ticks 2 --no-sleep)
|
COMMAND GameServer --ticks 2 --no-sleep)
|
||||||
add_test(
|
add_test(
|
||||||
NAME GameServer.BattleZoneSharedPlayer
|
NAME GameServer.BattleZoneSharedPlayer
|
||||||
COMMAND GameServer --campaign battle_zone --location uni_exterior
|
COMMAND GameServer --campaign battle_zone --location park_korea
|
||||||
--ticks 2 --no-sleep)
|
--ticks 2 --no-sleep)
|
||||||
set_tests_properties(
|
set_tests_properties(
|
||||||
GameServer.BattleZoneSharedPlayer
|
GameServer.BattleZoneSharedPlayer
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ int main()
|
|||||||
return fail("Battle Zone modes do not share their initial state");
|
return fail("Battle Zone modes do not share their initial state");
|
||||||
}
|
}
|
||||||
if (multiplayerBattleZoneConfig->networkPlayerSlots.size() != 2 ||
|
if (multiplayerBattleZoneConfig->networkPlayerSlots.size() != 2 ||
|
||||||
multiplayerBattleZoneConfig->networkPlayerSlots[0] != "player" ||
|
multiplayerBattleZoneConfig->networkPlayerSlots[0] != "player1" ||
|
||||||
multiplayerBattleZoneConfig->networkPlayerSlots[1] !=
|
multiplayerBattleZoneConfig->networkPlayerSlots[1] !=
|
||||||
"player2" ||
|
"player2" ||
|
||||||
multiplayerBattleZoneConfig->networkCompatibility.empty()) {
|
multiplayerBattleZoneConfig->networkCompatibility.empty()) {
|
||||||
@@ -140,7 +140,7 @@ int main()
|
|||||||
disabledPlayer->enabled = false;
|
disabledPlayer->enabled = false;
|
||||||
state.location->targetInteractiveObjectId = "object";
|
state.location->targetInteractiveObjectId = "object";
|
||||||
state.location->targetInteractNpcId = "npc";
|
state.location->targetInteractNpcId = "npc";
|
||||||
FRG::PlayerStateMap disabledPlayers{{"player", disabledPlayer}};
|
FRG::PlayerStateMap disabledPlayers{{"player1", disabledPlayer}};
|
||||||
const FRG::LocationSimulationEvents disabledEvents =
|
const FRG::LocationSimulationEvents disabledEvents =
|
||||||
state.update(50, disabledPlayers, false);
|
state.update(50, disabledPlayers, false);
|
||||||
if (!disabledEvents.interactiveObjects.empty() ||
|
if (!disabledEvents.interactiveObjects.empty() ||
|
||||||
@@ -165,7 +165,7 @@ int main()
|
|||||||
playerTwo->enabled = true;
|
playerTwo->enabled = true;
|
||||||
playerTwo->position = {2.f, 0.f, 0.f};
|
playerTwo->position = {2.f, 0.f, 0.f};
|
||||||
FRG::PlayerStateMap combatPlayers{
|
FRG::PlayerStateMap combatPlayers{
|
||||||
{"player", playerOne}, {"player2", playerTwo}};
|
{"player1", playerOne}, {"player2", playerTwo}};
|
||||||
|
|
||||||
state.update(100, combatPlayers, false);
|
state.update(100, combatPlayers, false);
|
||||||
if (hostileNpc.attackTargetId != "player2") {
|
if (hostileNpc.attackTargetId != "player2") {
|
||||||
@@ -181,7 +181,7 @@ int main()
|
|||||||
|
|
||||||
playerTwo->hp = 0.f;
|
playerTwo->hp = 0.f;
|
||||||
state.update(100, combatPlayers, false);
|
state.update(100, combatPlayers, false);
|
||||||
if (hostileNpc.attackTargetId != "player") {
|
if (hostileNpc.attackTargetId != "player1") {
|
||||||
return fail("Hostile NPC did not acquire a replacement after target death");
|
return fail("Hostile NPC did not acquire a replacement after target death");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,7 +201,7 @@ int main()
|
|||||||
state.npcs["blocker"] = blocker;
|
state.npcs["blocker"] = blocker;
|
||||||
hostileNpc.position = Eigen::Vector3f::Zero();
|
hostileNpc.position = Eigen::Vector3f::Zero();
|
||||||
hostileNpc.homePosition = {5.f, 0.f, 0.f};
|
hostileNpc.homePosition = {5.f, 0.f, 0.f};
|
||||||
hostileNpc.attackTargetId = "player";
|
hostileNpc.attackTargetId = "player1";
|
||||||
hostileNpc.requestedWalkTarget = playerOne->position;
|
hostileNpc.requestedWalkTarget = playerOne->position;
|
||||||
hostileNpc.walkTarget = playerOne->position;
|
hostileNpc.walkTarget = playerOne->position;
|
||||||
hostileNpc.pathWaypoints = {playerOne->position};
|
hostileNpc.pathWaypoints = {playerOne->position};
|
||||||
@@ -267,22 +267,22 @@ int main()
|
|||||||
FRG::CampaignConfig multiplayerBattleZone = battleZone;
|
FRG::CampaignConfig multiplayerBattleZone = battleZone;
|
||||||
multiplayerBattleZone.id = "battle_zone_multiplayer";
|
multiplayerBattleZone.id = "battle_zone_multiplayer";
|
||||||
multiplayerBattleZone.networkPlayerSlots = {
|
multiplayerBattleZone.networkPlayerSlots = {
|
||||||
"player", "player2"};
|
"player1", "player2"};
|
||||||
networkServer.startCampaign(multiplayerBattleZone, {}, true, false);
|
networkServer.startCampaign(multiplayerBattleZone, {}, true, false);
|
||||||
const auto networkSnapshot = networkServer.snapshot();
|
const auto networkSnapshot = networkServer.snapshot();
|
||||||
if (networkSnapshot->campaignId != "battle_zone_multiplayer")
|
if (networkSnapshot->campaignId != "battle_zone_multiplayer")
|
||||||
return fail("Network campaign ID was not retained");
|
return fail("Network campaign ID was not retained");
|
||||||
if (networkSnapshot->players.count("player") != 1)
|
if (networkSnapshot->players.count("player1") != 1)
|
||||||
return fail("Network campaign shared player was not created");
|
return fail("Network campaign shared player was not created");
|
||||||
if (networkSnapshot->players.count("player2") != 0)
|
if (networkSnapshot->players.count("player2") != 0)
|
||||||
return fail("Network campaign unexpectedly created the test bot");
|
return fail("Network campaign unexpectedly created the test bot");
|
||||||
if (networkSnapshot->players.at("player").state.enabled)
|
if (networkSnapshot->players.at("player1").state.enabled)
|
||||||
return fail("Network campaign player slot was enabled before connection");
|
return fail("Network campaign player slot was enabled before connection");
|
||||||
|
|
||||||
nlohmann::json loadedNetworkState;
|
nlohmann::json loadedNetworkState;
|
||||||
networkServer.save(loadedNetworkState);
|
networkServer.save(loadedNetworkState);
|
||||||
loadedNetworkState["players"]["player2"] =
|
loadedNetworkState["players"]["player2"] =
|
||||||
loadedNetworkState["players"]["player"];
|
loadedNetworkState["players"]["player1"];
|
||||||
loadedNetworkState["players"]["player2"]["position"] =
|
loadedNetworkState["players"]["player2"]["position"] =
|
||||||
{4.f, 0.f, 5.f};
|
{4.f, 0.f, 5.f};
|
||||||
networkServer.load(loadedNetworkState);
|
networkServer.load(loadedNetworkState);
|
||||||
@@ -308,7 +308,7 @@ int main()
|
|||||||
networkServer.submit(FRG::SetCharacterFacingAngleCommand{
|
networkServer.submit(FRG::SetCharacterFacingAngleCommand{
|
||||||
locationId, "player2", 1.5f, true});
|
locationId, "player2", 1.5f, true});
|
||||||
networkServer.submit(FRG::SetCharacterFaceTargetCommand{
|
networkServer.submit(FRG::SetCharacterFaceTargetCommand{
|
||||||
locationId, "player2", "player"});
|
locationId, "player2", "player1"});
|
||||||
networkServer.submit(FRG::SetCharacterEnabledCommand{
|
networkServer.submit(FRG::SetCharacterEnabledCommand{
|
||||||
locationId, "player2", false});
|
locationId, "player2", false});
|
||||||
const auto commandSnapshot = networkServer.snapshot();
|
const auto commandSnapshot = networkServer.snapshot();
|
||||||
@@ -371,6 +371,7 @@ int main()
|
|||||||
moving->hp = 10.f;
|
moving->hp = 10.f;
|
||||||
moving->pathWaypoints = {Eigen::Vector3f(8.f, 0.f, 0.f)};
|
moving->pathWaypoints = {Eigen::Vector3f(8.f, 0.f, 0.f)};
|
||||||
moving->movedLastTick = true;
|
moving->movedLastTick = true;
|
||||||
|
moving->attackTargetId = "stationary";
|
||||||
FRG::PlayerStateMap occupancyPlayers{
|
FRG::PlayerStateMap occupancyPlayers{
|
||||||
{"stationary", stationary}, {"moving", moving}};
|
{"stationary", stationary}, {"moving", moving}};
|
||||||
if (occupancyState.collectPathObstacles(
|
if (occupancyState.collectPathObstacles(
|
||||||
@@ -378,7 +379,11 @@ int main()
|
|||||||
occupancyState.collectPathObstacles(
|
occupancyState.collectPathObstacles(
|
||||||
"requester", occupancyPlayers, "moving").size() != 2 ||
|
"requester", occupancyPlayers, "moving").size() != 2 ||
|
||||||
!occupancyState.collectPathObstacles(
|
!occupancyState.collectPathObstacles(
|
||||||
"stationary", occupancyPlayers).empty()) {
|
"stationary", occupancyPlayers).empty() ||
|
||||||
|
!occupancyState.collectPathObstacles(
|
||||||
|
"moving", occupancyPlayers).empty() ||
|
||||||
|
occupancyState.collectPathObstacles(
|
||||||
|
"moving", occupancyPlayers, "stationary").size() != 1) {
|
||||||
return fail("Stationary obstacle collection policy is incorrect");
|
return fail("Stationary obstacle collection policy is incorrect");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,12 +23,12 @@ int main()
|
|||||||
const nlohmann::json original{
|
const nlohmann::json original{
|
||||||
{"schemaVersion", 2},
|
{"schemaVersion", 2},
|
||||||
{"campaignId", "battle_zone_multiplayer"},
|
{"campaignId", "battle_zone_multiplayer"},
|
||||||
{"players", {{"player", {{"hp", 200.0}}}}},
|
{"players", {{"player1", {{"hp", 200.0}}}}},
|
||||||
{"locations", {{"uni_exterior", {{"state", savedLocationState}}}}}
|
{"locations", {{"park_korea", {{"state", savedLocationState}}}}}
|
||||||
};
|
};
|
||||||
FRG::InitialStateMessage initial;
|
FRG::InitialStateMessage initial;
|
||||||
initial.state = original;
|
initial.state = original;
|
||||||
initial.assignedPlayerId = "player";
|
initial.assignedPlayerId = "player1";
|
||||||
initial.snapshotTick = 42;
|
initial.snapshotTick = 42;
|
||||||
initial.compatibilityFingerprint = "lockstep-v2:battle_zone_multiplayer";
|
initial.compatibilityFingerprint = "lockstep-v2:battle_zone_multiplayer";
|
||||||
const std::string message = FRG::makeInitialStateMessage(initial);
|
const std::string message = FRG::makeInitialStateMessage(initial);
|
||||||
@@ -40,11 +40,11 @@ int main()
|
|||||||
require(parsed.state == original, "state payload did not round trip");
|
require(parsed.state == original, "state payload did not round trip");
|
||||||
require(parsed.snapshotTick == 42, "snapshot tick did not round trip");
|
require(parsed.snapshotTick == 42, "snapshot tick did not round trip");
|
||||||
require(
|
require(
|
||||||
parsed.assignedPlayerId == "player",
|
parsed.assignedPlayerId == "player1",
|
||||||
"assigned player did not round trip");
|
"assigned player did not round trip");
|
||||||
require(
|
require(
|
||||||
!parsed.state["locations"]["uni_exterior"]["state"].contains("cameraAzimuth") &&
|
!parsed.state["locations"]["park_korea"]["state"].contains("cameraAzimuth") &&
|
||||||
!parsed.state["locations"]["uni_exterior"]["state"].contains("cameraInclination"),
|
!parsed.state["locations"]["park_korea"]["state"].contains("cameraInclination"),
|
||||||
"local camera state leaked into the network payload");
|
"local camera state leaked into the network payload");
|
||||||
|
|
||||||
FRG::DestinationInputMessage input;
|
FRG::DestinationInputMessage input;
|
||||||
@@ -87,11 +87,11 @@ int main()
|
|||||||
FRG::TickMessage tick;
|
FRG::TickMessage tick;
|
||||||
tick.tickId = 43;
|
tick.tickId = 43;
|
||||||
tick.commands.push_back(FRG::ScheduledDestinationCommand{
|
tick.commands.push_back(FRG::ScheduledDestinationCommand{
|
||||||
1, "player", parsedInput.target});
|
1, "player1", parsedInput.target});
|
||||||
tick.commands.push_back(FRG::ScheduledCharacterEnabledCommand{
|
tick.commands.push_back(FRG::ScheduledCharacterEnabledCommand{
|
||||||
2, "uni_exterior", "player2", true});
|
2, "park_korea", "player2", true});
|
||||||
tick.commands.push_back(FRG::ScheduledCharacterActionCommand{
|
tick.commands.push_back(FRG::ScheduledCharacterActionCommand{
|
||||||
3, "player", "set_target", "hostile_npc"});
|
3, "player1", "set_target", "hostile_npc"});
|
||||||
tick.stateHash = "0123456789abcdef";
|
tick.stateHash = "0123456789abcdef";
|
||||||
FRG::TickMessage parsedTick;
|
FRG::TickMessage parsedTick;
|
||||||
require(
|
require(
|
||||||
@@ -147,9 +147,9 @@ int main()
|
|||||||
|
|
||||||
FRG::LockstepTimeline timeline(2);
|
FRG::LockstepTimeline timeline(2);
|
||||||
const std::uint64_t firstSequence =
|
const std::uint64_t firstSequence =
|
||||||
timeline.schedule("player", Eigen::Vector3f(1.f, 0.f, 1.f));
|
timeline.schedule("player1", Eigen::Vector3f(1.f, 0.f, 1.f));
|
||||||
const std::uint64_t secondSequence =
|
const std::uint64_t secondSequence =
|
||||||
timeline.schedule("player_2", Eigen::Vector3f(2.f, 0.f, 2.f));
|
timeline.schedule("player2", Eigen::Vector3f(2.f, 0.f, 2.f));
|
||||||
const FRG::TickMessage emptyTick = timeline.advance();
|
const FRG::TickMessage emptyTick = timeline.advance();
|
||||||
require(
|
require(
|
||||||
emptyTick.tickId == 1 && emptyTick.commands.empty(),
|
emptyTick.tickId == 1 && emptyTick.commands.empty(),
|
||||||
@@ -163,8 +163,8 @@ int main()
|
|||||||
commandTick.commands[1]).commandSequence == secondSequence,
|
commandTick.commands[1]).commandSequence == secondSequence,
|
||||||
"scheduled commands were not finalized in stable order");
|
"scheduled commands were not finalized in stable order");
|
||||||
|
|
||||||
timeline.scheduleCharacterEnabled("uni_exterior", "player2", true);
|
timeline.scheduleCharacterEnabled("park_korea", "player2", true);
|
||||||
timeline.scheduleCharacterAction("player", "clear_attack_target");
|
timeline.scheduleCharacterAction("player1", "clear_attack_target");
|
||||||
const FRG::TickMessage presenceTick = timeline.advance();
|
const FRG::TickMessage presenceTick = timeline.advance();
|
||||||
require(
|
require(
|
||||||
presenceTick.commands.size() == 1 &&
|
presenceTick.commands.size() == 1 &&
|
||||||
|
|||||||
@@ -59,13 +59,13 @@ int main()
|
|||||||
|
|
||||||
FRG::CampaignConfig campaign;
|
FRG::CampaignConfig campaign;
|
||||||
campaign.id = "test_lockstep";
|
campaign.id = "test_lockstep";
|
||||||
campaign.networkPlayerSlots = {"player", "player2"};
|
campaign.networkPlayerSlots = {"player1", "player2"};
|
||||||
|
|
||||||
FRG::InternalGameServer authoritative;
|
FRG::InternalGameServer authoritative;
|
||||||
authoritative.startCampaign(campaign, {}, true, false);
|
authoritative.startCampaign(campaign, {}, true, false);
|
||||||
nlohmann::json initialState;
|
nlohmann::json initialState;
|
||||||
authoritative.save(initialState);
|
authoritative.save(initialState);
|
||||||
initialState["players"]["player2"] = initialState["players"]["player"];
|
initialState["players"]["player2"] = initialState["players"]["player1"];
|
||||||
FRG::LocationGameState testLocation;
|
FRG::LocationGameState testLocation;
|
||||||
auto hostileNpc = std::make_shared<FRG::CharacterState>();
|
auto hostileNpc = std::make_shared<FRG::CharacterState>();
|
||||||
hostileNpc->enabled = true;
|
hostileNpc->enabled = true;
|
||||||
@@ -77,7 +77,7 @@ int main()
|
|||||||
initialState["locations"]["test_location"] = std::move(testLocationState);
|
initialState["locations"]["test_location"] = std::move(testLocationState);
|
||||||
authoritative.load(initialState);
|
authoritative.load(initialState);
|
||||||
FRG::WebsocketGameServer server(
|
FRG::WebsocketGameServer server(
|
||||||
authoritative, {"player", "player2"}, "lockstep-v2:test_lockstep",
|
authoritative, {"player1", "player2"}, "lockstep-v2:test_lockstep",
|
||||||
"127.0.0.1", 0);
|
"127.0.0.1", 0);
|
||||||
auto client = std::make_unique<FRG::WebsocketGameClient>(
|
auto client = std::make_unique<FRG::WebsocketGameClient>(
|
||||||
"127.0.0.1", std::to_string(server.port()), "/");
|
"127.0.0.1", std::to_string(server.port()), "/");
|
||||||
@@ -91,7 +91,7 @@ int main()
|
|||||||
return client->initialized();
|
return client->initialized();
|
||||||
}),
|
}),
|
||||||
"client did not receive initial state");
|
"client did not receive initial state");
|
||||||
require(client->localPlayerId() == "player", "wrong player slot assigned");
|
require(client->localPlayerId() == "player1", "wrong player slot assigned");
|
||||||
|
|
||||||
auto secondClient = std::make_unique<FRG::WebsocketGameClient>(
|
auto secondClient = std::make_unique<FRG::WebsocketGameClient>(
|
||||||
"127.0.0.1", std::to_string(server.port()), "/");
|
"127.0.0.1", std::to_string(server.port()), "/");
|
||||||
@@ -120,9 +120,9 @@ int main()
|
|||||||
client->update(0);
|
client->update(0);
|
||||||
secondClient->update(0);
|
secondClient->update(0);
|
||||||
return client->snapshot()->players.at("player2").state.enabled &&
|
return client->snapshot()->players.at("player2").state.enabled &&
|
||||||
secondClient->snapshot()->players.at("player").state.enabled;
|
secondClient->snapshot()->players.at("player1").state.enabled;
|
||||||
}) &&
|
}) &&
|
||||||
authoritative.snapshot()->players.at("player").state.enabled &&
|
authoritative.snapshot()->players.at("player1").state.enabled &&
|
||||||
authoritative.snapshot()->players.at("player2").state.enabled,
|
authoritative.snapshot()->players.at("player2").state.enabled,
|
||||||
"connection presence was not broadcast to all replicas");
|
"connection presence was not broadcast to all replicas");
|
||||||
|
|
||||||
@@ -140,16 +140,16 @@ int main()
|
|||||||
const auto first = client->snapshot();
|
const auto first = client->snapshot();
|
||||||
const auto second = secondClient->snapshot();
|
const auto second = secondClient->snapshot();
|
||||||
const auto host = authoritative.snapshot();
|
const auto host = authoritative.snapshot();
|
||||||
return first->players.at("player").state.requestedWalkTarget.isApprox(
|
return first->players.at("player1").state.requestedWalkTarget.isApprox(
|
||||||
Eigen::Vector3f(1.23f, 0.f, 2.35f)) &&
|
Eigen::Vector3f(1.23f, 0.f, 2.35f)) &&
|
||||||
second->players.at("player2").state.requestedWalkTarget.isApprox(
|
second->players.at("player2").state.requestedWalkTarget.isApprox(
|
||||||
Eigen::Vector3f(-1.23f, 0.f, 3.46f)) &&
|
Eigen::Vector3f(-1.23f, 0.f, 3.46f)) &&
|
||||||
first->players.at("player2").state.requestedWalkTarget.isApprox(
|
first->players.at("player2").state.requestedWalkTarget.isApprox(
|
||||||
second->players.at("player2").state.requestedWalkTarget) &&
|
second->players.at("player2").state.requestedWalkTarget) &&
|
||||||
second->players.at("player").state.requestedWalkTarget.isApprox(
|
second->players.at("player1").state.requestedWalkTarget.isApprox(
|
||||||
first->players.at("player").state.requestedWalkTarget) &&
|
first->players.at("player1").state.requestedWalkTarget) &&
|
||||||
host->players.at("player").state.requestedWalkTarget.isApprox(
|
host->players.at("player1").state.requestedWalkTarget.isApprox(
|
||||||
first->players.at("player").state.requestedWalkTarget) &&
|
first->players.at("player1").state.requestedWalkTarget) &&
|
||||||
host->players.at("player2").state.requestedWalkTarget.isApprox(
|
host->players.at("player2").state.requestedWalkTarget.isApprox(
|
||||||
second->players.at("player2").state.requestedWalkTarget);
|
second->players.at("player2").state.requestedWalkTarget);
|
||||||
});
|
});
|
||||||
@@ -160,9 +160,9 @@ int main()
|
|||||||
std::cerr << "destination revisions " << first->revision << ", "
|
std::cerr << "destination revisions " << first->revision << ", "
|
||||||
<< second->revision << ", " << host->revision << "\n"
|
<< second->revision << ", " << host->revision << "\n"
|
||||||
<< "player targets "
|
<< "player targets "
|
||||||
<< first->players.at("player").state.requestedWalkTarget.transpose()
|
<< first->players.at("player1").state.requestedWalkTarget.transpose()
|
||||||
<< " / "
|
<< " / "
|
||||||
<< host->players.at("player").state.requestedWalkTarget.transpose()
|
<< host->players.at("player1").state.requestedWalkTarget.transpose()
|
||||||
<< "\nplayer2 targets "
|
<< "\nplayer2 targets "
|
||||||
<< second->players.at("player2").state.requestedWalkTarget.transpose()
|
<< second->players.at("player2").state.requestedWalkTarget.transpose()
|
||||||
<< " / "
|
<< " / "
|
||||||
@@ -183,11 +183,11 @@ int main()
|
|||||||
waitFor([&]() {
|
waitFor([&]() {
|
||||||
client->update(0);
|
client->update(0);
|
||||||
secondClient->update(0);
|
secondClient->update(0);
|
||||||
return client->snapshot()->players.at("player").state.attackTargetId ==
|
return client->snapshot()->players.at("player1").state.attackTargetId ==
|
||||||
"hostile_npc" &&
|
"hostile_npc" &&
|
||||||
secondClient->snapshot()->players.at("player").state.attackTargetId ==
|
secondClient->snapshot()->players.at("player1").state.attackTargetId ==
|
||||||
"hostile_npc" &&
|
"hostile_npc" &&
|
||||||
authoritative.snapshot()->players.at("player").state.attackTargetId ==
|
authoritative.snapshot()->players.at("player1").state.attackTargetId ==
|
||||||
"hostile_npc";
|
"hostile_npc";
|
||||||
}),
|
}),
|
||||||
"target command was not delayed and rebroadcast");
|
"target command was not delayed and rebroadcast");
|
||||||
@@ -202,9 +202,9 @@ int main()
|
|||||||
waitFor([&]() {
|
waitFor([&]() {
|
||||||
client->update(0);
|
client->update(0);
|
||||||
secondClient->update(0);
|
secondClient->update(0);
|
||||||
return client->snapshot()->players.at("player").state.attackTargetId.empty() &&
|
return client->snapshot()->players.at("player1").state.attackTargetId.empty() &&
|
||||||
secondClient->snapshot()->players.at("player").state.attackTargetId.empty() &&
|
secondClient->snapshot()->players.at("player1").state.attackTargetId.empty() &&
|
||||||
authoritative.snapshot()->players.at("player").state.attackTargetId.empty();
|
authoritative.snapshot()->players.at("player1").state.attackTargetId.empty();
|
||||||
}),
|
}),
|
||||||
"clear attack target command was not delayed and rebroadcast");
|
"clear attack target command was not delayed and rebroadcast");
|
||||||
|
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ public:
|
|||||||
bool showWeapon = true;
|
bool showWeapon = true;
|
||||||
|
|
||||||
// --- Stable character references ---
|
// --- Stable character references ---
|
||||||
// Empty means no target. "player" is the campaign player; NPC IDs are
|
// Empty means no target. "player1" is the campaign player; NPC IDs are
|
||||||
// location-local and validated while loading location content.
|
// location-local and validated while loading location content.
|
||||||
std::string attackTargetId;
|
std::string attackTargetId;
|
||||||
std::string faceTargetId;
|
std::string faceTargetId;
|
||||||
|
|||||||
+4
-2
@@ -558,8 +558,10 @@ namespace FRG
|
|||||||
logger() << "[network] Campaign '" << campaignId
|
logger() << "[network] Campaign '" << campaignId
|
||||||
<< "' selected; creating WebSocket client" << std::endl;
|
<< "' selected; creating WebSocket client" << std::endl;
|
||||||
// Use "ws://127.0.0.1:8080/" for a local development server.
|
// Use "ws://127.0.0.1:8080/" for a local development server.
|
||||||
static const char* const kMultiplayerServerUrl =
|
|
||||||
"wss://api.shadowoverbishkek.com/server001/";
|
//static const char* const kMultiplayerServerUrl = "wss://api.shadowoverbishkek.com/server001/";
|
||||||
|
static const char* const kMultiplayerServerUrl = "ws://127.0.0.1:8080/";
|
||||||
|
|
||||||
WebsocketEndpoint endpoint;
|
WebsocketEndpoint endpoint;
|
||||||
if (!endpoint.parse(kMultiplayerServerUrl)) {
|
if (!endpoint.parse(kMultiplayerServerUrl)) {
|
||||||
logger() << "[network] Invalid multiplayer server URL: "
|
logger() << "[network] Invalid multiplayer server URL: "
|
||||||
|
|||||||
@@ -140,11 +140,16 @@ std::vector<PathFinder::DynamicObstacle> LocationGameState::collectPathObstacles
|
|||||||
std::sort(characters.begin(), characters.end(),
|
std::sort(characters.begin(), characters.end(),
|
||||||
[](const auto& lhs, const auto& rhs) { return lhs.first < rhs.first; });
|
[](const auto& lhs, const auto& rhs) { return lhs.first < rhs.first; });
|
||||||
|
|
||||||
|
const CharacterState* requester = findCharacter(requesterId, players);
|
||||||
|
const std::string attackTargetId =
|
||||||
|
requester ? requester->attackTargetId : std::string();
|
||||||
std::vector<PathFinder::DynamicObstacle> obstacles;
|
std::vector<PathFinder::DynamicObstacle> obstacles;
|
||||||
obstacles.reserve(characters.size());
|
obstacles.reserve(characters.size());
|
||||||
for (const auto& [id, character] : characters) {
|
for (const auto& [id, character] : characters) {
|
||||||
if (id == requesterId || !character->enabled || character->hp <= 0.f)
|
if (id == requesterId || !character->enabled || character->hp <= 0.f)
|
||||||
continue;
|
continue;
|
||||||
|
if (id == attackTargetId && id != forcedObstacleId)
|
||||||
|
continue;
|
||||||
if (character->movedLastTick && id != forcedObstacleId) continue;
|
if (character->movedLastTick && id != forcedObstacleId) continue;
|
||||||
obstacles.push_back({character->position, character->collisionRadius});
|
obstacles.push_back({character->position, character->collisionRadius});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ namespace FRG {
|
|||||||
ScriptEngine::~ScriptEngine() = default;
|
ScriptEngine::~ScriptEngine() = default;
|
||||||
|
|
||||||
void ScriptEngine::init(Location* loc, AudioPlayerAsync* audioPlayer, const std::string& scriptPath) {
|
void ScriptEngine::init(Location* loc, AudioPlayerAsync* audioPlayer, const std::string& scriptPath) {
|
||||||
|
logger() << "ScriptEngine::init with script path: " << scriptPath << std::endl;
|
||||||
impl = std::make_unique<Impl>();
|
impl = std::make_unique<Impl>();
|
||||||
sol::state& lua = impl->lua;
|
sol::state& lua = impl->lua;
|
||||||
|
|
||||||
|
|||||||
@@ -293,7 +293,7 @@ LocationDefinition LocationContentLoader::load(
|
|||||||
for (const auto& item : root.value("npcs", json::array())) {
|
for (const auto& item : root.value("npcs", json::array())) {
|
||||||
NpcData data;
|
NpcData data;
|
||||||
data.id = item.at("id").get<std::string>();
|
data.id = item.at("id").get<std::string>();
|
||||||
if (data.id.empty() || data.id == "player" ||
|
if (data.id.empty() || data.id == "player1" ||
|
||||||
!ids.insert(data.id).second) {
|
!ids.insert(data.id).second) {
|
||||||
throw std::runtime_error(
|
throw std::runtime_error(
|
||||||
"Missing, duplicate, or reserved NPC id in " +
|
"Missing, duplicate, or reserved NPC id in " +
|
||||||
|
|||||||
+28
-13
@@ -2,6 +2,7 @@
|
|||||||
#include "Environment.h"
|
#include "Environment.h"
|
||||||
#include "utils/Utils.h"
|
#include "utils/Utils.h"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <cmath>
|
||||||
#ifdef __ANDROID__
|
#ifdef __ANDROID__
|
||||||
#include <android/log.h>
|
#include <android/log.h>
|
||||||
#endif
|
#endif
|
||||||
@@ -75,14 +76,27 @@ extern "C" void EMSCRIPTEN_KEEPALIVE onFileSystemLoaded() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Single definition of the "logical" viewport size: the *visible* area of the page
|
||||||
|
// (visualViewport), not 100vh, which on mobile includes the area under the URL bar.
|
||||||
|
static void getViewportCssSize(int& w, int& h) {
|
||||||
|
w = EM_ASM_INT({
|
||||||
|
var vv = window.visualViewport;
|
||||||
|
return Math.round((vv && vv.width) || window.innerWidth);
|
||||||
|
});
|
||||||
|
h = EM_ASM_INT({
|
||||||
|
var vv = window.visualViewport;
|
||||||
|
return Math.round((vv && vv.height) || window.innerHeight);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
static void applyResize(int logicalW, int logicalH) {
|
static void applyResize(int logicalW, int logicalH) {
|
||||||
// Получаем коэффициент плотности пикселей (например, 2.625 на Pixel или 3.0 на Samsung)
|
// Получаем коэффициент плотности пикселей (например, 2.625 на Pixel или 3.0 на Samsung)
|
||||||
double dpr = emscripten_get_device_pixel_ratio();
|
double dpr = emscripten_get_device_pixel_ratio();
|
||||||
//double dpr = 1; // low quality
|
//double dpr = 1; // low quality
|
||||||
|
|
||||||
// Вычисляем реальные физические пиксели
|
// Вычисляем реальные физические пиксели
|
||||||
int physicalW = static_cast<int>(logicalW * dpr);
|
int physicalW = static_cast<int>(std::lround(logicalW * dpr));
|
||||||
int physicalH = static_cast<int>(logicalH * dpr);
|
int physicalH = static_cast<int>(std::lround(logicalH * dpr));
|
||||||
|
|
||||||
// Устанавливаем размер внутреннего буфера канваса
|
// Устанавливаем размер внутреннего буфера канваса
|
||||||
emscripten_set_canvas_element_size("#canvas", physicalW, physicalH);
|
emscripten_set_canvas_element_size("#canvas", physicalW, physicalH);
|
||||||
@@ -94,6 +108,11 @@ static void applyResize(int logicalW, int logicalH) {
|
|||||||
SDL_SetWindowSize(FRG::Environment::window, physicalW, physicalH);
|
SDL_SetWindowSize(FRG::Environment::window, physicalW, physicalH);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SDL may have written an inline CSS size derived from the physical pixels, which
|
||||||
|
// overrides the stylesheet. Re-assert the CSS box so its aspect always matches the
|
||||||
|
// drawing buffer (otherwise the image is stretched / cropped on mobile).
|
||||||
|
emscripten_set_element_css_size("#canvas", logicalW, logicalH);
|
||||||
|
|
||||||
// Пушим событие, чтобы движок пересчитал матрицы проекции
|
// Пушим событие, чтобы движок пересчитал матрицы проекции
|
||||||
SDL_Event e = {};
|
SDL_Event e = {};
|
||||||
e.type = SDL_WINDOWEVENT;
|
e.type = SDL_WINDOWEVENT;
|
||||||
@@ -118,8 +137,8 @@ void MainLoop() {
|
|||||||
|
|
||||||
// Теперь DOM перестроен, можно забирать актуальные размеры.
|
// Теперь DOM перестроен, можно забирать актуальные размеры.
|
||||||
// Если canvas должен занимать все окно браузера:
|
// Если canvas должен занимать все окно браузера:
|
||||||
int canvasW = EM_ASM_INT({ return window.innerWidth; });
|
int canvasW, canvasH;
|
||||||
int canvasH = EM_ASM_INT({ return window.innerHeight; });
|
getViewportCssSize(canvasW, canvasH);
|
||||||
|
|
||||||
applyResize(canvasW, canvasH);
|
applyResize(canvasW, canvasH);
|
||||||
}
|
}
|
||||||
@@ -141,14 +160,10 @@ EM_BOOL onWebGLContextRestored(int /*eventType*/, const void* /*reserved*/, void
|
|||||||
|
|
||||||
|
|
||||||
EM_BOOL onWindowResized(int /*eventType*/, const EmscriptenUiEvent* e, void* /*userData*/) {
|
EM_BOOL onWindowResized(int /*eventType*/, const EmscriptenUiEvent* e, void* /*userData*/) {
|
||||||
double w, h;
|
int w, h;
|
||||||
emscripten_get_element_css_size("window", &w, &h);
|
getViewportCssSize(w, h);
|
||||||
|
|
||||||
// Если "window" не поддерживается вашей версией SDK, используем данные из e:
|
applyResize(w, h);
|
||||||
// int logicalW = e->windowInnerWidth;
|
|
||||||
// int logicalH = e->windowInnerHeight;
|
|
||||||
|
|
||||||
applyResize(static_cast<int>(w), static_cast<int>(h));
|
|
||||||
return EM_FALSE;
|
return EM_FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,8 +196,8 @@ int main(int argc, char* argv[]) {
|
|||||||
emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, EM_FALSE, onWindowResized);
|
emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, EM_FALSE, onWindowResized);
|
||||||
emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, nullptr, EM_TRUE, onFullscreenChanged);
|
emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, nullptr, EM_TRUE, onFullscreenChanged);
|
||||||
|
|
||||||
int canvasW = EM_ASM_INT({ return window.innerWidth; });
|
int canvasW, canvasH;
|
||||||
int canvasH = EM_ASM_INT({ return window.innerHeight; });
|
getViewportCssSize(canvasW, canvasH);
|
||||||
applyResize(canvasW, canvasH);
|
applyResize(canvasW, canvasH);
|
||||||
|
|
||||||
SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS, "0");
|
SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS, "0");
|
||||||
|
|||||||
Reference in new issue
Block a user