Compare commits
6 Commits
74c2f786a1
...
be2aa76f8b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be2aa76f8b | ||
|
|
e8f1786a2a | ||
|
|
e30f8a4c70 | ||
|
|
f8642efa23 | ||
| 5da5a754fe | |||
| fb1b0a1c2e |
21
resources/config/crosshair_config.json
Normal file
21
resources/config/crosshair_config.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"enabled": true,
|
||||||
|
|
||||||
|
"referenceResolution": [1280, 720],
|
||||||
|
|
||||||
|
"color": [1.0, 1.0, 1.0],
|
||||||
|
"cl_crosshairalpha": 1.0,
|
||||||
|
"cl_crosshairthickness": 2.0,
|
||||||
|
|
||||||
|
"centerGapPx": 10.0,
|
||||||
|
|
||||||
|
"top": {
|
||||||
|
"lengthPx": 14.0,
|
||||||
|
"angleDeg": 90.0
|
||||||
|
},
|
||||||
|
|
||||||
|
"arms": [
|
||||||
|
{ "lengthPx": 20.0, "angleDeg": 210.0 },
|
||||||
|
{ "lengthPx": 20.0, "angleDeg": 330.0 }
|
||||||
|
]
|
||||||
|
}
|
||||||
BIN
resources/multiplayer_menu/AvailableServers.png
(Stored with Git LFS)
BIN
resources/multiplayer_menu/AvailableServers.png
(Stored with Git LFS)
Binary file not shown.
BIN
resources/multiplayer_menu/Backbutton.png
(Stored with Git LFS)
BIN
resources/multiplayer_menu/Backbutton.png
(Stored with Git LFS)
Binary file not shown.
BIN
resources/multiplayer_menu/Button.png
(Stored with Git LFS)
BIN
resources/multiplayer_menu/Button.png
(Stored with Git LFS)
Binary file not shown.
BIN
resources/multiplayer_menu/Button2.png
(Stored with Git LFS)
BIN
resources/multiplayer_menu/Button2.png
(Stored with Git LFS)
Binary file not shown.
BIN
resources/multiplayer_menu/Button3.png
(Stored with Git LFS)
BIN
resources/multiplayer_menu/Button3.png
(Stored with Git LFS)
Binary file not shown.
BIN
resources/multiplayer_menu/Filledbuttons.png
(Stored with Git LFS)
BIN
resources/multiplayer_menu/Filledbuttons.png
(Stored with Git LFS)
Binary file not shown.
BIN
resources/multiplayer_menu/JoinServer.png
(Stored with Git LFS)
BIN
resources/multiplayer_menu/JoinServer.png
(Stored with Git LFS)
Binary file not shown.
BIN
resources/multiplayer_menu/ServerName.png
(Stored with Git LFS)
BIN
resources/multiplayer_menu/ServerName.png
(Stored with Git LFS)
Binary file not shown.
BIN
resources/multiplayer_menu/title.png
(Stored with Git LFS)
BIN
resources/multiplayer_menu/title.png
(Stored with Git LFS)
Binary file not shown.
@ -323,7 +323,6 @@ private:
|
|||||||
receivedState.shipType = this->shipType;
|
receivedState.shipType = this->shipType;
|
||||||
timedClientStates.add_state(receivedState);
|
timedClientStates.add_state(receivedState);
|
||||||
|
|
||||||
retranslateMessage(cleanMessage);
|
|
||||||
}
|
}
|
||||||
else if (type == "RESPAWN") {
|
else if (type == "RESPAWN") {
|
||||||
{
|
{
|
||||||
@ -433,16 +432,6 @@ private:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void retranslateMessage(const std::string& msg) {
|
|
||||||
std::string event_msg = "EVENT:" + std::to_string(id_) + ":" + msg;
|
|
||||||
|
|
||||||
std::lock_guard<std::mutex> lock(g_sessions_mutex);
|
|
||||||
for (auto& session : g_sessions) {
|
|
||||||
if (session->get_id() != id_) {
|
|
||||||
session->send_message(event_msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
void broadcastToAll(const std::string& message) {
|
void broadcastToAll(const std::string& message) {
|
||||||
@ -454,273 +443,257 @@ void broadcastToAll(const std::string& message) {
|
|||||||
|
|
||||||
void update_world(net::steady_timer& timer, net::io_context& ioc) {
|
void update_world(net::steady_timer& timer, net::io_context& ioc) {
|
||||||
|
|
||||||
static auto last_snapshot_time = std::chrono::steady_clock::now();
|
static auto last_snapshot_time = std::chrono::system_clock::now();
|
||||||
auto now = std::chrono::steady_clock::now();
|
|
||||||
/*static uint64_t lastTickCount = 0;
|
|
||||||
|
|
||||||
if (lastTickCount == 0) {
|
auto now = std::chrono::system_clock::now();
|
||||||
//lastTickCount = SDL_GetTicks64();
|
uint64_t now_ms = static_cast<uint64_t>(
|
||||||
lastTickCount = std::chrono::duration_cast<std::chrono::milliseconds>(
|
std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count());
|
||||||
std::chrono::system_clock::now().time_since_epoch()
|
|
||||||
).count();
|
|
||||||
|
|
||||||
lastTickCount = (lastTickCount / 50) * 50;
|
// --- Snapshot every 500ms ---
|
||||||
|
/*if (std::chrono::duration_cast<std::chrono::milliseconds>(now - last_snapshot_time).count() >= 500) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
auto newTickCount = std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
||||||
std::chrono::system_clock::now().time_since_epoch()
|
|
||||||
).count();
|
|
||||||
|
|
||||||
newTickCount = (newTickCount / 50) * 50;
|
|
||||||
|
|
||||||
int64_t deltaMs = static_cast<int64_t>(newTickCount - lastTickCount);
|
|
||||||
|
|
||||||
std::chrono::system_clock::time_point nowRounded = std::chrono::system_clock::time_point(std::chrono::milliseconds(newTickCount));
|
|
||||||
*/
|
|
||||||
// For each player
|
|
||||||
// Get letest state + add time (until newTickCount)
|
|
||||||
// Calculate if collisions with boxes
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Рассылка Snapshot раз в 1000мс
|
|
||||||
/*
|
|
||||||
if (std::chrono::duration_cast<std::chrono::milliseconds>(now - last_snapshot_time).count() >= 1000) {
|
|
||||||
last_snapshot_time = now;
|
last_snapshot_time = now;
|
||||||
|
|
||||||
auto system_now = std::chrono::system_clock::now();
|
std::string snapshot_msg = "SNAPSHOT:" + std::to_string(now_ms);
|
||||||
|
|
||||||
std::string snapshot_msg = "SNAPSHOT:" + std::to_string(
|
|
||||||
std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
||||||
system_now.time_since_epoch()).count()
|
|
||||||
);
|
|
||||||
|
|
||||||
std::lock_guard<std::mutex> lock(g_sessions_mutex);
|
std::lock_guard<std::mutex> lock(g_sessions_mutex);
|
||||||
|
|
||||||
// Формируем общую строку состояний всех игроков
|
|
||||||
for (auto& session : g_sessions) {
|
for (auto& session : g_sessions) {
|
||||||
ClientState st = session->get_latest_state(system_now);
|
ClientState st = session->get_latest_state(now);
|
||||||
snapshot_msg += "|" + std::to_string(session->get_id()) + ":" + st.formPingMessageContent();
|
snapshot_msg += "|" + std::to_string(session->get_id()) + ":" + st.formPingMessageContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto& session : g_sessions) {
|
for (auto& session : g_sessions) {
|
||||||
session->send_message(snapshot_msg);
|
session->send_message(snapshot_msg);
|
||||||
}
|
}
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
const std::chrono::milliseconds interval(50);
|
// --- Tick: broadcast each player's latest state to all others (20Hz) ---
|
||||||
timer.expires_after(interval);
|
// Send the raw last-known state with its original timestamp, NOT an extrapolated one.
|
||||||
|
// Extrapolating here causes snap-back: if a player stops rotating, the server would
|
||||||
|
// keep sending over-rotated positions until the new state arrives, then B snaps back.
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(g_sessions_mutex);
|
||||||
|
for (auto& sender : g_sessions) {
|
||||||
|
if (sender->timedClientStates.timedStates.empty()) continue;
|
||||||
|
|
||||||
timer.async_wait([&](const boost::system::error_code& ec) {
|
const ClientState& st = sender->timedClientStates.timedStates.back();
|
||||||
if (ec) return;
|
uint64_t stateTime = static_cast<uint64_t>(
|
||||||
|
std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||||
|
st.lastUpdateServerTime.time_since_epoch()).count());
|
||||||
|
|
||||||
auto now = std::chrono::system_clock::now();
|
std::string event_msg = "EVENT:" + std::to_string(sender->get_id()) +
|
||||||
uint64_t now_ms = static_cast<uint64_t>(std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count());
|
":UPD:" + std::to_string(stateTime) + ":" + st.formPingMessageContent();
|
||||||
|
|
||||||
std::vector<DeathInfo> deathEvents;
|
for (auto& receiver : g_sessions) {
|
||||||
|
if (receiver->get_id() != sender->get_id()) {
|
||||||
{
|
receiver->send_message(event_msg);
|
||||||
std::lock_guard<std::mutex> pl(g_projectiles_mutex);
|
|
||||||
std::vector<int> indicesToRemove;
|
|
||||||
|
|
||||||
float dt = 50.0f / 1000.0f;
|
|
||||||
|
|
||||||
for (size_t i = 0; i < g_projectiles.size(); ++i) {
|
|
||||||
auto& pr = g_projectiles[i];
|
|
||||||
|
|
||||||
pr.pos += pr.vel * dt;
|
|
||||||
|
|
||||||
if (now_ms > pr.spawnMs + static_cast<uint64_t>(pr.lifeMs)) {
|
|
||||||
indicesToRemove.push_back(static_cast<int>(i));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool hitDetected = false;
|
|
||||||
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lm(g_sessions_mutex);
|
|
||||||
std::lock_guard<std::mutex> gd(g_dead_mutex);
|
|
||||||
|
|
||||||
for (auto& session : g_sessions) {
|
|
||||||
int targetId = session->get_id();
|
|
||||||
|
|
||||||
if (targetId == pr.shooterId) continue;
|
|
||||||
if (g_dead_players.find(targetId) != g_dead_players.end()) continue;
|
|
||||||
|
|
||||||
ClientState targetState;
|
|
||||||
if (!session->fetchStateAtTime(now, targetState)) continue;
|
|
||||||
|
|
||||||
Eigen::Vector3f diff = pr.pos - targetState.position;
|
|
||||||
const float shipRadius = 15.0f;
|
|
||||||
const float projectileRadius = 1.5f;
|
|
||||||
float combinedRadius = shipRadius + projectileRadius;
|
|
||||||
|
|
||||||
if (diff.squaredNorm() <= combinedRadius * combinedRadius) {
|
|
||||||
DeathInfo death;
|
|
||||||
death.targetId = targetId;
|
|
||||||
death.serverTime = now_ms;
|
|
||||||
death.position = pr.pos;
|
|
||||||
death.killerId = pr.shooterId;
|
|
||||||
|
|
||||||
deathEvents.push_back(death);
|
|
||||||
g_dead_players.insert(targetId);
|
|
||||||
indicesToRemove.push_back(static_cast<int>(i));
|
|
||||||
hitDetected = true;
|
|
||||||
|
|
||||||
std::cout << "Server: *** HIT DETECTED! ***" << std::endl;
|
|
||||||
std::cout << "Server: Projectile at ("
|
|
||||||
<< pr.pos.x() << ", " << pr.pos.y() << ", " << pr.pos.z()
|
|
||||||
<< ") hit player " << targetId << std::endl;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hitDetected) continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!indicesToRemove.empty()) {
|
|
||||||
std::sort(indicesToRemove.rbegin(), indicesToRemove.rend());
|
|
||||||
for (int idx : indicesToRemove) {
|
|
||||||
if (idx >= 0 && idx < (int)g_projectiles.size()) {
|
|
||||||
g_projectiles.erase(g_projectiles.begin() + idx);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
{
|
// --- Tick: projectile movement and hit detection ---
|
||||||
std::lock_guard<std::mutex> bm(g_boxes_mutex);
|
const float dt = 50.0f / 1000.0f;
|
||||||
//const float projectileHitRadius = 1.5f;
|
std::vector<DeathInfo> deathEvents;
|
||||||
const float projectileHitRadius = 5.0f;
|
|
||||||
const float boxCollisionRadius = 2.0f;
|
|
||||||
|
|
||||||
std::vector<std::pair<size_t, size_t>> boxProjectileCollisions;
|
{
|
||||||
|
std::lock_guard<std::mutex> pl(g_projectiles_mutex);
|
||||||
|
std::vector<int> indicesToRemove;
|
||||||
|
|
||||||
for (size_t bi = 0; bi < g_serverBoxes.size(); ++bi) {
|
for (size_t i = 0; i < g_projectiles.size(); ++i) {
|
||||||
if (g_serverBoxes[bi].destroyed) continue;
|
auto& pr = g_projectiles[i];
|
||||||
|
|
||||||
Eigen::Vector3f boxWorld = g_serverBoxes[bi].position + Eigen::Vector3f(0.0f, 0.0f, 45000.0f);
|
pr.pos += pr.vel * dt;
|
||||||
|
|
||||||
for (size_t pi = 0; pi < g_projectiles.size(); ++pi) {
|
if (now_ms > pr.spawnMs + static_cast<uint64_t>(pr.lifeMs)) {
|
||||||
const auto& pr = g_projectiles[pi];
|
indicesToRemove.push_back(static_cast<int>(i));
|
||||||
Eigen::Vector3f diff = pr.pos - boxWorld;
|
continue;
|
||||||
//std::cout << "diff norm is " << diff.norm() << std::endl;
|
|
||||||
float thresh = boxCollisionRadius + projectileHitRadius;
|
|
||||||
|
|
||||||
if (diff.squaredNorm() <= thresh * thresh) {
|
|
||||||
boxProjectileCollisions.push_back({ bi, pi });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& [boxIdx, projIdx] : boxProjectileCollisions) {
|
bool hitDetected = false;
|
||||||
g_serverBoxes[boxIdx].destroyed = true;
|
|
||||||
|
|
||||||
Eigen::Vector3f boxWorld = g_serverBoxes[boxIdx].position + Eigen::Vector3f(0.0f, 0.0f, 45000.0f);
|
{
|
||||||
|
std::lock_guard<std::mutex> lm(g_sessions_mutex);
|
||||||
BoxDestroyedInfo destruction;
|
std::lock_guard<std::mutex> gd(g_dead_mutex);
|
||||||
destruction.boxIndex = static_cast<int>(boxIdx);
|
|
||||||
destruction.serverTime = now_ms;
|
|
||||||
destruction.position = boxWorld;
|
|
||||||
destruction.destroyedBy = g_projectiles[projIdx].shooterId;
|
|
||||||
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> dm(g_boxDestructions_mutex);
|
|
||||||
g_boxDestructions.push_back(destruction);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::cout << "Server: Box " << boxIdx << " destroyed by projectile from player "
|
|
||||||
<< g_projectiles[projIdx].shooterId << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> bm(g_boxes_mutex);
|
|
||||||
std::lock_guard<std::mutex> lm(g_sessions_mutex);
|
|
||||||
|
|
||||||
const float shipCollisionRadius = 15.0f;
|
|
||||||
const float boxCollisionRadius = 2.0f;
|
|
||||||
|
|
||||||
for (size_t bi = 0; bi < g_serverBoxes.size(); ++bi) {
|
|
||||||
if (g_serverBoxes[bi].destroyed) continue;
|
|
||||||
|
|
||||||
Eigen::Vector3f boxWorld = g_serverBoxes[bi].position + Eigen::Vector3f(0.0f, 0.0f, 45000.0f);
|
|
||||||
|
|
||||||
for (auto& session : g_sessions) {
|
for (auto& session : g_sessions) {
|
||||||
{
|
int targetId = session->get_id();
|
||||||
std::lock_guard<std::mutex> gd(g_dead_mutex);
|
|
||||||
if (g_dead_players.find(session->get_id()) != g_dead_players.end()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ClientState shipState;
|
if (targetId == pr.shooterId) continue;
|
||||||
if (!session->fetchStateAtTime(now, shipState)) continue;
|
if (g_dead_players.find(targetId) != g_dead_players.end()) continue;
|
||||||
|
|
||||||
Eigen::Vector3f diff = shipState.position - boxWorld;
|
ClientState targetState;
|
||||||
float thresh = shipCollisionRadius + boxCollisionRadius;
|
if (!session->fetchStateAtTime(now, targetState)) continue;
|
||||||
|
|
||||||
if (diff.squaredNorm() <= thresh * thresh) {
|
Eigen::Vector3f diff = pr.pos - targetState.position;
|
||||||
g_serverBoxes[bi].destroyed = true;
|
const float shipRadius = 15.0f;
|
||||||
|
const float projectileRadius = 1.5f;
|
||||||
|
float combinedRadius = shipRadius + projectileRadius;
|
||||||
|
|
||||||
BoxDestroyedInfo destruction;
|
if (diff.squaredNorm() <= combinedRadius * combinedRadius) {
|
||||||
destruction.boxIndex = static_cast<int>(bi);
|
DeathInfo death;
|
||||||
destruction.serverTime = now_ms;
|
death.targetId = targetId;
|
||||||
destruction.position = boxWorld;
|
death.serverTime = now_ms;
|
||||||
destruction.destroyedBy = session->get_id();
|
death.position = pr.pos;
|
||||||
|
death.killerId = pr.shooterId;
|
||||||
|
|
||||||
{
|
deathEvents.push_back(death);
|
||||||
std::lock_guard<std::mutex> dm(g_boxDestructions_mutex);
|
g_dead_players.insert(targetId);
|
||||||
g_boxDestructions.push_back(destruction);
|
indicesToRemove.push_back(static_cast<int>(i));
|
||||||
}
|
hitDetected = true;
|
||||||
|
|
||||||
std::cout << "Server: Box " << bi << " destroyed by ship collision with player "
|
std::cout << "Server: *** HIT DETECTED! ***" << std::endl;
|
||||||
<< session->get_id() << std::endl;
|
std::cout << "Server: Projectile at ("
|
||||||
|
<< pr.pos.x() << ", " << pr.pos.y() << ", " << pr.pos.z()
|
||||||
|
<< ") hit player " << targetId << std::endl;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hitDetected) continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!deathEvents.empty()) {
|
if (!indicesToRemove.empty()) {
|
||||||
for (const auto& death : deathEvents) {
|
std::sort(indicesToRemove.rbegin(), indicesToRemove.rend());
|
||||||
std::string deadMsg = "DEAD:" +
|
for (int idx : indicesToRemove) {
|
||||||
std::to_string(death.serverTime) + ":" +
|
if (idx >= 0 && idx < (int)g_projectiles.size()) {
|
||||||
std::to_string(death.targetId) + ":" +
|
g_projectiles.erase(g_projectiles.begin() + idx);
|
||||||
std::to_string(death.position.x()) + ":" +
|
}
|
||||||
std::to_string(death.position.y()) + ":" +
|
}
|
||||||
std::to_string(death.position.z()) + ":" +
|
}
|
||||||
std::to_string(death.killerId);
|
}
|
||||||
|
|
||||||
broadcastToAll(deadMsg);
|
// --- Tick: box-projectile collisions ---
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> bm(g_boxes_mutex);
|
||||||
|
const float projectileHitRadius = 5.0f;
|
||||||
|
const float boxCollisionRadius = 2.0f;
|
||||||
|
|
||||||
std::cout << "Server: Sent DEAD event - Player " << death.targetId
|
std::vector<std::pair<size_t, size_t>> boxProjectileCollisions;
|
||||||
<< " killed by " << death.killerId << std::endl;
|
|
||||||
|
for (size_t bi = 0; bi < g_serverBoxes.size(); ++bi) {
|
||||||
|
if (g_serverBoxes[bi].destroyed) continue;
|
||||||
|
|
||||||
|
Eigen::Vector3f boxWorld = g_serverBoxes[bi].position + Eigen::Vector3f(0.0f, 0.0f, 45000.0f);
|
||||||
|
|
||||||
|
for (size_t pi = 0; pi < g_projectiles.size(); ++pi) {
|
||||||
|
const auto& pr = g_projectiles[pi];
|
||||||
|
Eigen::Vector3f diff = pr.pos - boxWorld;
|
||||||
|
float thresh = boxCollisionRadius + projectileHitRadius;
|
||||||
|
|
||||||
|
if (diff.squaredNorm() <= thresh * thresh) {
|
||||||
|
boxProjectileCollisions.push_back({ bi, pi });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
for (const auto& [boxIdx, projIdx] : boxProjectileCollisions) {
|
||||||
std::lock_guard<std::mutex> dm(g_boxDestructions_mutex);
|
g_serverBoxes[boxIdx].destroyed = true;
|
||||||
for (const auto& destruction : g_boxDestructions) {
|
|
||||||
std::string boxMsg = "BOX_DESTROYED:" +
|
|
||||||
std::to_string(destruction.boxIndex) + ":" +
|
|
||||||
std::to_string(destruction.serverTime) + ":" +
|
|
||||||
std::to_string(destruction.position.x()) + ":" +
|
|
||||||
std::to_string(destruction.position.y()) + ":" +
|
|
||||||
std::to_string(destruction.position.z()) + ":" +
|
|
||||||
std::to_string(destruction.destroyedBy);
|
|
||||||
|
|
||||||
broadcastToAll(boxMsg);
|
Eigen::Vector3f boxWorld = g_serverBoxes[boxIdx].position + Eigen::Vector3f(0.0f, 0.0f, 45000.0f);
|
||||||
std::cout << "Server: Broadcasted BOX_DESTROYED for box " << destruction.boxIndex << std::endl;
|
|
||||||
|
BoxDestroyedInfo destruction;
|
||||||
|
destruction.boxIndex = static_cast<int>(boxIdx);
|
||||||
|
destruction.serverTime = now_ms;
|
||||||
|
destruction.position = boxWorld;
|
||||||
|
destruction.destroyedBy = g_projectiles[projIdx].shooterId;
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> dm(g_boxDestructions_mutex);
|
||||||
|
g_boxDestructions.push_back(destruction);
|
||||||
}
|
}
|
||||||
g_boxDestructions.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
std::cout << "Server: Box " << boxIdx << " destroyed by projectile from player "
|
||||||
|
<< g_projectiles[projIdx].shooterId << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Tick: box-ship collisions ---
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> bm(g_boxes_mutex);
|
||||||
|
std::lock_guard<std::mutex> lm(g_sessions_mutex);
|
||||||
|
|
||||||
|
const float shipCollisionRadius = 15.0f;
|
||||||
|
const float boxCollisionRadius = 2.0f;
|
||||||
|
|
||||||
|
for (size_t bi = 0; bi < g_serverBoxes.size(); ++bi) {
|
||||||
|
if (g_serverBoxes[bi].destroyed) continue;
|
||||||
|
|
||||||
|
Eigen::Vector3f boxWorld = g_serverBoxes[bi].position + Eigen::Vector3f(0.0f, 0.0f, 45000.0f);
|
||||||
|
|
||||||
|
for (auto& session : g_sessions) {
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> gd(g_dead_mutex);
|
||||||
|
if (g_dead_players.find(session->get_id()) != g_dead_players.end()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ClientState shipState;
|
||||||
|
if (!session->fetchStateAtTime(now, shipState)) continue;
|
||||||
|
|
||||||
|
Eigen::Vector3f diff = shipState.position - boxWorld;
|
||||||
|
float thresh = shipCollisionRadius + boxCollisionRadius;
|
||||||
|
|
||||||
|
if (diff.squaredNorm() <= thresh * thresh) {
|
||||||
|
g_serverBoxes[bi].destroyed = true;
|
||||||
|
|
||||||
|
BoxDestroyedInfo destruction;
|
||||||
|
destruction.boxIndex = static_cast<int>(bi);
|
||||||
|
destruction.serverTime = now_ms;
|
||||||
|
destruction.position = boxWorld;
|
||||||
|
destruction.destroyedBy = session->get_id();
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> dm(g_boxDestructions_mutex);
|
||||||
|
g_boxDestructions.push_back(destruction);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "Server: Box " << bi << " destroyed by ship collision with player "
|
||||||
|
<< session->get_id() << std::endl;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Broadcast deaths ---
|
||||||
|
for (const auto& death : deathEvents) {
|
||||||
|
std::string deadMsg = "DEAD:" +
|
||||||
|
std::to_string(death.serverTime) + ":" +
|
||||||
|
std::to_string(death.targetId) + ":" +
|
||||||
|
std::to_string(death.position.x()) + ":" +
|
||||||
|
std::to_string(death.position.y()) + ":" +
|
||||||
|
std::to_string(death.position.z()) + ":" +
|
||||||
|
std::to_string(death.killerId);
|
||||||
|
|
||||||
|
broadcastToAll(deadMsg);
|
||||||
|
|
||||||
|
std::cout << "Server: Sent DEAD event - Player " << death.targetId
|
||||||
|
<< " killed by " << death.killerId << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Broadcast box destructions ---
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> dm(g_boxDestructions_mutex);
|
||||||
|
for (const auto& destruction : g_boxDestructions) {
|
||||||
|
std::string boxMsg = "BOX_DESTROYED:" +
|
||||||
|
std::to_string(destruction.boxIndex) + ":" +
|
||||||
|
std::to_string(destruction.serverTime) + ":" +
|
||||||
|
std::to_string(destruction.position.x()) + ":" +
|
||||||
|
std::to_string(destruction.position.y()) + ":" +
|
||||||
|
std::to_string(destruction.position.z()) + ":" +
|
||||||
|
std::to_string(destruction.destroyedBy);
|
||||||
|
|
||||||
|
broadcastToAll(boxMsg);
|
||||||
|
std::cout << "Server: Broadcasted BOX_DESTROYED for box " << destruction.boxIndex << std::endl;
|
||||||
|
}
|
||||||
|
g_boxDestructions.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Schedule next tick in 50ms ---
|
||||||
|
timer.expires_after(std::chrono::milliseconds(50));
|
||||||
|
timer.async_wait([&timer, &ioc](const boost::system::error_code& ec) {
|
||||||
|
if (ec) return;
|
||||||
update_world(timer, ioc);
|
update_world(timer, ioc);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
314
src/Space.cpp
314
src/Space.cpp
@ -361,6 +361,19 @@ namespace ZL
|
|||||||
throw std::runtime_error("Failed to load spark emitter config file!");
|
throw std::runtime_error("Failed to load spark emitter config file!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
crosshairCfgLoaded = loadCrosshairConfig("resources/config/crosshair_config.json");
|
||||||
|
std::cerr << "[Crosshair] loaded=" << crosshairCfgLoaded
|
||||||
|
<< " enabled=" << crosshairCfg.enabled
|
||||||
|
<< " w=" << Environment::width << " h=" << Environment::height
|
||||||
|
<< " alpha=" << crosshairCfg.alpha
|
||||||
|
<< " thickness=" << crosshairCfg.thicknessPx
|
||||||
|
<< " gap=" << crosshairCfg.gapPx << "\n";
|
||||||
|
if (!crosshairCfgLoaded) {
|
||||||
|
std::cerr << "Failed to load crosshair_config.json, using defaults\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
textRenderer = std::make_unique<ZL::TextRenderer>();
|
textRenderer = std::make_unique<ZL::TextRenderer>();
|
||||||
if (!textRenderer->init(renderer, "resources/fonts/DroidSans.ttf", 32, CONST_ZIP_FILE)) {
|
if (!textRenderer->init(renderer, "resources/fonts/DroidSans.ttf", 32, CONST_ZIP_FILE)) {
|
||||||
std::cerr << "Failed to init TextRenderer\n";
|
std::cerr << "Failed to init TextRenderer\n";
|
||||||
@ -597,6 +610,7 @@ namespace ZL
|
|||||||
drawBoxesLabels();
|
drawBoxesLabels();
|
||||||
drawShip();
|
drawShip();
|
||||||
|
|
||||||
|
drawCrosshair();
|
||||||
drawTargetHud();
|
drawTargetHud();
|
||||||
CheckGlError();
|
CheckGlError();
|
||||||
}
|
}
|
||||||
@ -726,6 +740,230 @@ namespace ZL
|
|||||||
glEnable(GL_DEPTH_TEST);
|
glEnable(GL_DEPTH_TEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// хелпер прицела: добавляет повернутую 2D-линию в меш прицела
|
||||||
|
static void AppendRotatedRect2D(
|
||||||
|
VertexDataStruct& out,
|
||||||
|
float cx, float cy,
|
||||||
|
float length, float thickness,
|
||||||
|
float angleRad,
|
||||||
|
float z,
|
||||||
|
const Eigen::Vector3f& rgb)
|
||||||
|
{
|
||||||
|
// прямоугольник вдоль локальной оси +X: [-L/2..+L/2] и [-T/2..+T/2]
|
||||||
|
float hl = length * 0.5f;
|
||||||
|
float ht = thickness * 0.5f;
|
||||||
|
|
||||||
|
Eigen::Vector2f p0(-hl, -ht);
|
||||||
|
Eigen::Vector2f p1(-hl, +ht);
|
||||||
|
Eigen::Vector2f p2(+hl, +ht);
|
||||||
|
Eigen::Vector2f p3(+hl, -ht);
|
||||||
|
|
||||||
|
float c = std::cos(angleRad);
|
||||||
|
float s = std::sin(angleRad);
|
||||||
|
|
||||||
|
auto rot = [&](const Eigen::Vector2f& p) -> Vector3f {
|
||||||
|
float rx = p.x() * c - p.y() * s;
|
||||||
|
float ry = p.x() * s + p.y() * c;
|
||||||
|
return Vector3f(cx + rx, cy + ry, z);
|
||||||
|
};
|
||||||
|
|
||||||
|
Vector3f v0 = rot(p0);
|
||||||
|
Vector3f v1 = rot(p1);
|
||||||
|
Vector3f v2 = rot(p2);
|
||||||
|
Vector3f v3 = rot(p3);
|
||||||
|
|
||||||
|
// 2 треугольника
|
||||||
|
out.PositionData.push_back(v0);
|
||||||
|
out.PositionData.push_back(v1);
|
||||||
|
out.PositionData.push_back(v2);
|
||||||
|
out.PositionData.push_back(v2);
|
||||||
|
out.PositionData.push_back(v3);
|
||||||
|
out.PositionData.push_back(v0);
|
||||||
|
|
||||||
|
for (int i = 0; i < 6; ++i) out.ColorData.push_back(rgb);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Space::loadCrosshairConfig(const std::string& path)
|
||||||
|
{
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
std::string content;
|
||||||
|
try {
|
||||||
|
if (std::string(CONST_ZIP_FILE).empty()) content = readTextFile(path);
|
||||||
|
else {
|
||||||
|
auto buf = readFileFromZIP(path, CONST_ZIP_FILE);
|
||||||
|
if (buf.empty()) return false;
|
||||||
|
content.assign(buf.begin(), buf.end());
|
||||||
|
}
|
||||||
|
json j = json::parse(content);
|
||||||
|
|
||||||
|
if (j.contains("enabled")) crosshairCfg.enabled = j["enabled"].get<bool>();
|
||||||
|
|
||||||
|
if (j.contains("referenceResolution") && j["referenceResolution"].is_array() && j["referenceResolution"].size() == 2) {
|
||||||
|
crosshairCfg.refW = j["referenceResolution"][0].get<int>();
|
||||||
|
crosshairCfg.refH = j["referenceResolution"][1].get<int>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (j.contains("scale")) crosshairCfg.scaleMul = j["scale"].get<float>();
|
||||||
|
crosshairCfg.scaleMul = std::clamp(crosshairCfg.scaleMul, 0.1f, 3.0f);
|
||||||
|
|
||||||
|
if (j.contains("color") && j["color"].is_array() && j["color"].size() == 3) {
|
||||||
|
crosshairCfg.color = Eigen::Vector3f(
|
||||||
|
j["color"][0].get<float>(),
|
||||||
|
j["color"][1].get<float>(),
|
||||||
|
j["color"][2].get<float>()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (j.contains("cl_crosshairalpha")) crosshairCfg.alpha = j["cl_crosshairalpha"].get<float>();
|
||||||
|
if (j.contains("cl_crosshairthickness")) crosshairCfg.thicknessPx = j["cl_crosshairthickness"].get<float>();
|
||||||
|
if (j.contains("centerGapPx")) crosshairCfg.gapPx = j["centerGapPx"].get<float>();
|
||||||
|
|
||||||
|
if (j.contains("top") && j["top"].is_object()) {
|
||||||
|
auto t = j["top"];
|
||||||
|
if (t.contains("lengthPx")) crosshairCfg.topLenPx = t["lengthPx"].get<float>();
|
||||||
|
if (t.contains("angleDeg")) crosshairCfg.topAngleDeg = t["angleDeg"].get<float>();
|
||||||
|
}
|
||||||
|
|
||||||
|
crosshairCfg.arms.clear();
|
||||||
|
if (j.contains("arms") && j["arms"].is_array()) {
|
||||||
|
for (auto& a : j["arms"]) {
|
||||||
|
CrosshairConfig::Arm arm;
|
||||||
|
arm.lenPx = a.value("lengthPx", 20.0f);
|
||||||
|
arm.angleDeg = a.value("angleDeg", 210.0f);
|
||||||
|
crosshairCfg.arms.push_back(arm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// дефолт
|
||||||
|
crosshairCfg.arms.push_back({ 20.0f, 210.0f });
|
||||||
|
crosshairCfg.arms.push_back({ 20.0f, 330.0f });
|
||||||
|
}
|
||||||
|
|
||||||
|
// clamp
|
||||||
|
crosshairCfg.alpha = std::clamp(crosshairCfg.alpha, 0.0f, 1.0f);
|
||||||
|
crosshairCfg.thicknessPx = max(0.5f, crosshairCfg.thicknessPx);
|
||||||
|
crosshairCfg.gapPx = max(0.0f, crosshairCfg.gapPx);
|
||||||
|
|
||||||
|
crosshairMeshValid = false; // пересобрать
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (...) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// пересобирает mesh прицела при изменениях/ресайзе
|
||||||
|
void Space::rebuildCrosshairMeshIfNeeded()
|
||||||
|
{
|
||||||
|
if (!crosshairCfg.enabled) return;
|
||||||
|
|
||||||
|
// если ничего не изменилось — не трогаем VBO
|
||||||
|
if (crosshairMeshValid &&
|
||||||
|
crosshairLastW == Environment::width &&
|
||||||
|
crosshairLastH == Environment::height &&
|
||||||
|
std::abs(crosshairLastAlpha - crosshairCfg.alpha) < 1e-6f &&
|
||||||
|
std::abs(crosshairLastThickness - crosshairCfg.thicknessPx) < 1e-6f &&
|
||||||
|
std::abs(crosshairLastGap - crosshairCfg.gapPx) < 1e-6f &&
|
||||||
|
std::abs(crosshairLastScaleMul - crosshairCfg.scaleMul) < 1e-6f)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
crosshairLastW = Environment::width;
|
||||||
|
crosshairLastH = Environment::height;
|
||||||
|
crosshairLastAlpha = crosshairCfg.alpha;
|
||||||
|
crosshairLastThickness = crosshairCfg.thicknessPx;
|
||||||
|
crosshairLastGap = crosshairCfg.gapPx;
|
||||||
|
crosshairLastScaleMul = crosshairCfg.scaleMul;
|
||||||
|
|
||||||
|
float cx = Environment::width * 0.5f;
|
||||||
|
float cy = Environment::height * 0.5f;
|
||||||
|
|
||||||
|
// масштаб от reference (стандартно: по высоте)
|
||||||
|
float scale = (crosshairCfg.refH > 0) ? (Environment::height / (float)crosshairCfg.refH) : 1.0f;
|
||||||
|
scale *= crosshairCfg.scaleMul;
|
||||||
|
|
||||||
|
float thickness = crosshairCfg.thicknessPx * scale;
|
||||||
|
float gap = crosshairCfg.gapPx * scale;
|
||||||
|
|
||||||
|
VertexDataStruct v;
|
||||||
|
v.PositionData.reserve(6 * (1 + (int)crosshairCfg.arms.size()));
|
||||||
|
v.ColorData.reserve(6 * (1 + (int)crosshairCfg.arms.size()));
|
||||||
|
|
||||||
|
const float z = 0.0f;
|
||||||
|
const Eigen::Vector3f rgb = crosshairCfg.color;
|
||||||
|
|
||||||
|
auto deg2rad = [](float d) { return d * 3.1415926535f / 180.0f; };
|
||||||
|
|
||||||
|
// TOP (короткая палочка сверху)
|
||||||
|
{
|
||||||
|
float len = crosshairCfg.topLenPx * scale;
|
||||||
|
float ang = deg2rad(crosshairCfg.topAngleDeg);
|
||||||
|
|
||||||
|
// сдвигаем сегмент от центра на gap + len/2 по направлению
|
||||||
|
float dx = std::cos(ang);
|
||||||
|
float dy = std::sin(ang);
|
||||||
|
float mx = cx + dx * (gap + len * 0.5f);
|
||||||
|
float my = cy + dy * (gap + len * 0.5f);
|
||||||
|
|
||||||
|
AppendRotatedRect2D(v, mx, my, len, thickness, ang, z, rgb);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ARMS (2 луча вниз-влево и вниз-вправо)
|
||||||
|
for (auto& a : crosshairCfg.arms)
|
||||||
|
{
|
||||||
|
float len = a.lenPx * scale;
|
||||||
|
float ang = deg2rad(a.angleDeg);
|
||||||
|
|
||||||
|
float dx = std::cos(ang);
|
||||||
|
float dy = std::sin(ang);
|
||||||
|
float mx = cx + dx * (gap + len * 0.5f);
|
||||||
|
float my = cy + dy * (gap + len * 0.5f);
|
||||||
|
|
||||||
|
AppendRotatedRect2D(v, mx, my, len, thickness, ang, z, rgb);
|
||||||
|
}
|
||||||
|
|
||||||
|
crosshairMesh.AssignFrom(v);
|
||||||
|
crosshairMesh.RefreshVBO();
|
||||||
|
crosshairMeshValid = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Space::drawCrosshair()
|
||||||
|
{
|
||||||
|
if (!crosshairCfg.enabled) return;
|
||||||
|
|
||||||
|
rebuildCrosshairMeshIfNeeded();
|
||||||
|
if (!crosshairMeshValid) return;
|
||||||
|
|
||||||
|
glDisable(GL_DEPTH_TEST);
|
||||||
|
glEnable(GL_BLEND);
|
||||||
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||||
|
|
||||||
|
renderer.shaderManager.PushShader("defaultColor");
|
||||||
|
renderer.PushProjectionMatrix((float)Environment::width, (float)Environment::height, 0.f, 1.f);
|
||||||
|
renderer.PushMatrix();
|
||||||
|
renderer.LoadIdentity();
|
||||||
|
|
||||||
|
renderer.EnableVertexAttribArray("vPosition");
|
||||||
|
renderer.EnableVertexAttribArray("vColor");
|
||||||
|
|
||||||
|
Eigen::Vector4f uColor(crosshairCfg.color.x(), crosshairCfg.color.y(), crosshairCfg.color.z(), crosshairCfg.alpha);
|
||||||
|
renderer.RenderUniform4fv("uColor", uColor.data());
|
||||||
|
|
||||||
|
renderer.DrawVertexRenderStruct(crosshairMesh);
|
||||||
|
|
||||||
|
renderer.DisableVertexAttribArray("vPosition");
|
||||||
|
renderer.DisableVertexAttribArray("vColor");
|
||||||
|
|
||||||
|
renderer.PopMatrix();
|
||||||
|
renderer.PopProjectionMatrix();
|
||||||
|
renderer.shaderManager.PopShader();
|
||||||
|
|
||||||
|
glDisable(GL_BLEND);
|
||||||
|
glEnable(GL_DEPTH_TEST);
|
||||||
|
}
|
||||||
|
|
||||||
int Space::pickTargetId() const
|
int Space::pickTargetId() const
|
||||||
{
|
{
|
||||||
int bestId = -1;
|
int bestId = -1;
|
||||||
@ -896,41 +1134,38 @@ namespace ZL
|
|||||||
Vector3f shooterVel = ForwardFromRotation(Environment::shipState.rotation) * Environment::shipState.velocity;
|
Vector3f shooterVel = ForwardFromRotation(Environment::shipState.rotation) * Environment::shipState.velocity;
|
||||||
Vector3f targetVel = ForwardFromRotation(st.rotation) * st.velocity;
|
Vector3f targetVel = ForwardFromRotation(st.rotation) * st.velocity;
|
||||||
|
|
||||||
// условие "если враг не движется — круг не рисуем"
|
|
||||||
const float minTargetSpeed = 0.5f; // подобрать (в твоих единицах)
|
const float minTargetSpeed = 0.5f; // подобрать (в твоих единицах)
|
||||||
bool targetMoving = (targetVel.norm() > minTargetSpeed);
|
bool targetMoving = (targetVel.norm() > minTargetSpeed);
|
||||||
|
|
||||||
|
// альфа круга
|
||||||
|
float leadAlpha = targetMoving ? 1.0f : 0.5f;
|
||||||
|
|
||||||
Vector3f leadWorld = shipWorld;
|
Vector3f leadWorld = shipWorld;
|
||||||
bool haveLead = false;
|
bool haveLead = false;
|
||||||
|
|
||||||
//if (targetMoving) {
|
// чтобы круг не улетал далеко: максимум 4 секунды (подстроить под игру)
|
||||||
// float tLead = 0.0f;
|
float distToTarget = (Environment::shipState.position - shipWorld).norm();
|
||||||
// if (SolveLeadInterceptTime(shooterPos, shooterVel, shipWorld, targetVel, projectileSpeed, tLead)) {
|
float maxLeadTime = std::clamp((distToTarget / projectileSpeed) * 1.2f, 0.05f, 4.0f);
|
||||||
// // ограничим случаи, чтобы круг не улетал далеко
|
|
||||||
// if (tLead > 0.0f && tLead < 8.0f) {
|
|
||||||
// // подобрать максимум (сек)
|
|
||||||
// leadWorld = shipWorld + targetVel * tLead;
|
|
||||||
// haveLead = true;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
||||||
if (targetMoving) {
|
if (!targetMoving) {
|
||||||
|
// Цель стоит: рисуем lead прямо на ней, но полупрозрачный
|
||||||
|
leadWorld = shipWorld;
|
||||||
|
haveLead = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
float tLead = 0.0f;
|
float tLead = 0.0f;
|
||||||
float distToTarget = (Environment::shipState.position - shipWorld).norm();
|
|
||||||
|
|
||||||
const float leadMaxDist = 2500.0f; // максимум
|
// 1) Пытаемся “правильное” решение перехвата
|
||||||
float allowedDist = min(distToTarget, leadMaxDist);
|
bool ok = SolveLeadInterceptTime(shooterPos, shooterVel, shipWorld, targetVel, projectileSpeed, tLead);
|
||||||
|
|
||||||
// + небольшой запас 10–20% чтобы не моргало на границе
|
// 2) Если решения нет / оно плохое — fallback (чтобы круг не пропадал при пролёте "вбок")
|
||||||
const float maxLeadTime = (allowedDist / projectileSpeed) * 1.2f;
|
// Это ключевое изменение: lead всегда будет.
|
||||||
|
if (!ok || !(tLead > 0.0f) || tLead > maxLeadTime) {
|
||||||
if (SolveLeadInterceptTime(shooterPos, shooterVel, shipWorld, targetVel, projectileSpeed, tLead)) {
|
tLead = std::clamp(distToTarget / projectileSpeed, 0.05f, maxLeadTime);
|
||||||
if (tLead > 0.0f && tLead < maxLeadTime) {
|
|
||||||
leadWorld = shipWorld + targetVel * tLead;
|
|
||||||
haveLead = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
leadWorld = shipWorld + targetVel * tLead;
|
||||||
|
haveLead = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2) проекция
|
// 2) проекция
|
||||||
@ -1006,7 +1241,10 @@ namespace ZL
|
|||||||
|
|
||||||
renderer.EnableVertexAttribArray("vPosition");
|
renderer.EnableVertexAttribArray("vPosition");
|
||||||
|
|
||||||
// рисуем кружок упреждения (только если есть решение)
|
Eigen::Vector4f hudColor = enemyColor;
|
||||||
|
renderer.RenderUniform4fv("uColor", hudColor.data());
|
||||||
|
|
||||||
|
|
||||||
if (haveLead) {
|
if (haveLead) {
|
||||||
float leadNdcX, leadNdcY, leadNdcZ, leadClipW;
|
float leadNdcX, leadNdcY, leadNdcZ, leadClipW;
|
||||||
if (projectToNDC(leadWorld, leadNdcX, leadNdcY, leadNdcZ, leadClipW) && leadClipW > 0.0f) {
|
if (projectToNDC(leadWorld, leadNdcX, leadNdcY, leadNdcZ, leadClipW) && leadClipW > 0.0f) {
|
||||||
@ -1021,28 +1259,30 @@ namespace ZL
|
|||||||
float thicknessPx = 2.5f;
|
float thicknessPx = 2.5f;
|
||||||
float innerR = max(1.0f, r - thicknessPx);
|
float innerR = max(1.0f, r - thicknessPx);
|
||||||
float outerR = r + thicknessPx;
|
float outerR = r + thicknessPx;
|
||||||
|
Eigen::Vector4f leadColor = enemyColor;
|
||||||
|
leadColor.w() = leadAlpha;
|
||||||
|
renderer.RenderUniform4fv("uColor", leadColor.data());
|
||||||
|
|
||||||
VertexDataStruct ring = MakeRing2D(lx, ly, innerR, outerR, 0.0f, 32, enemyColor);
|
VertexDataStruct ring = MakeRing2D(lx, ly, innerR, outerR, 0.0f, 32, enemyColor);
|
||||||
hudTempMesh.AssignFrom(ring);
|
hudTempMesh.AssignFrom(ring);
|
||||||
renderer.DrawVertexRenderStruct(hudTempMesh);
|
renderer.DrawVertexRenderStruct(hudTempMesh);
|
||||||
|
|
||||||
|
renderer.RenderUniform4fv("uColor", hudColor.data());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderer.EnableVertexAttribArray("vPosition");
|
||||||
// верх-лево: горизонт + вертикаль
|
|
||||||
drawBar(left + cornerLen * 0.5f, top, cornerLen, thickness);
|
drawBar(left + cornerLen * 0.5f, top, cornerLen, thickness);
|
||||||
drawBar(left, top - cornerLen * 0.5f, thickness, cornerLen);
|
drawBar(left, top - cornerLen * 0.5f, thickness, cornerLen);
|
||||||
|
|
||||||
// верх-право
|
|
||||||
drawBar(right - cornerLen * 0.5f, top, cornerLen, thickness);
|
drawBar(right - cornerLen * 0.5f, top, cornerLen, thickness);
|
||||||
drawBar(right, top - cornerLen * 0.5f, thickness, cornerLen);
|
drawBar(right, top - cornerLen * 0.5f, thickness, cornerLen);
|
||||||
|
|
||||||
// низ-лево
|
|
||||||
drawBar(left + cornerLen * 0.5f, bottom, cornerLen, thickness);
|
drawBar(left + cornerLen * 0.5f, bottom, cornerLen, thickness);
|
||||||
drawBar(left, bottom + cornerLen * 0.5f, thickness, cornerLen);
|
drawBar(left, bottom + cornerLen * 0.5f, thickness, cornerLen);
|
||||||
|
|
||||||
// низ-право
|
|
||||||
drawBar(right - cornerLen * 0.5f, bottom, cornerLen, thickness);
|
drawBar(right - cornerLen * 0.5f, bottom, cornerLen, thickness);
|
||||||
drawBar(right, bottom + cornerLen * 0.5f, thickness, cornerLen);
|
drawBar(right, bottom + cornerLen * 0.5f, thickness, cornerLen);
|
||||||
|
|
||||||
@ -1060,12 +1300,9 @@ namespace ZL
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6) Если цель offscreen: рисуем стрелку на краю
|
|
||||||
// dir: куда “смотреть” в NDC
|
|
||||||
float dirX = ndcX;
|
float dirX = ndcX;
|
||||||
float dirY = ndcY;
|
float dirY = ndcY;
|
||||||
|
|
||||||
// если позади камеры — разворачиваем направление
|
|
||||||
if (behind) {
|
if (behind) {
|
||||||
dirX = -dirX;
|
dirX = -dirX;
|
||||||
dirY = -dirY;
|
dirY = -dirY;
|
||||||
@ -1076,7 +1313,6 @@ namespace ZL
|
|||||||
dirX /= len;
|
dirX /= len;
|
||||||
dirY /= len;
|
dirY /= len;
|
||||||
|
|
||||||
// пересечение луча с прямоугольником [-1..1] с отступом
|
|
||||||
float marginNdc = 0.08f;
|
float marginNdc = 0.08f;
|
||||||
float maxX = 1.0f - marginNdc;
|
float maxX = 1.0f - marginNdc;
|
||||||
float maxY = 1.0f - marginNdc;
|
float maxY = 1.0f - marginNdc;
|
||||||
@ -1091,16 +1327,13 @@ namespace ZL
|
|||||||
float edgeX = (edgeNdcX * 0.5f + 0.5f) * Environment::width;
|
float edgeX = (edgeNdcX * 0.5f + 0.5f) * Environment::width;
|
||||||
float edgeY = (edgeNdcY * 0.5f + 0.5f) * Environment::height;
|
float edgeY = (edgeNdcY * 0.5f + 0.5f) * Environment::height;
|
||||||
|
|
||||||
// лёгкая анимация “зова”: смещение по направлению
|
|
||||||
float bob = std::sin(t * 6.0f) * 6.0f;
|
float bob = std::sin(t * 6.0f) * 6.0f;
|
||||||
edgeX += dirX * bob;
|
edgeX += dirX * bob;
|
||||||
edgeY += dirY * bob;
|
edgeY += dirY * bob;
|
||||||
|
|
||||||
// стрелка как треугольник + маленький “хвост”
|
|
||||||
float arrowLen = 26.0f;
|
float arrowLen = 26.0f;
|
||||||
float arrowWid = 14.0f;
|
float arrowWid = 14.0f;
|
||||||
|
|
||||||
// перпендикуляр
|
|
||||||
float px = -dirY;
|
float px = -dirY;
|
||||||
float py = dirX;
|
float py = dirX;
|
||||||
|
|
||||||
@ -1139,23 +1372,18 @@ namespace ZL
|
|||||||
renderer.PushMatrix();
|
renderer.PushMatrix();
|
||||||
renderer.LoadIdentity();
|
renderer.LoadIdentity();
|
||||||
|
|
||||||
// треугольник-стрелка
|
|
||||||
drawTri(tip, left, right);
|
drawTri(tip, left, right);
|
||||||
|
|
||||||
// “хвост” (короткая черта)
|
|
||||||
float tailLen = 14.0f;
|
float tailLen = 14.0f;
|
||||||
float tailX = edgeX - dirX * 6.0f;
|
float tailX = edgeX - dirX * 6.0f;
|
||||||
float tailY = edgeY - dirY * 6.0f;
|
float tailY = edgeY - dirY * 6.0f;
|
||||||
// хвост рисуем как тонкий прямоугольник, ориентированный примерно по направлению:
|
|
||||||
// (упрощение: горизонт/вертикаль не поворачиваем, но выглядит ок. Хочешь — сделаем поворот матрицей)
|
|
||||||
drawBar(tailX, tailY, max(thickness, tailLen), thickness);
|
drawBar(tailX, tailY, max(thickness, tailLen), thickness);
|
||||||
|
|
||||||
renderer.PopMatrix();
|
renderer.PopMatrix();
|
||||||
renderer.PopProjectionMatrix();
|
renderer.PopProjectionMatrix();
|
||||||
renderer.shaderManager.PopShader();
|
renderer.shaderManager.PopShader();
|
||||||
|
|
||||||
// дистанция рядом со стрелкой
|
|
||||||
// (у тебя ещё будет “статично под прицелом” — это просто другой TextView / drawText)
|
|
||||||
{
|
{
|
||||||
std::string d = std::to_string((int)dist) + "m";
|
std::string d = std::to_string((int)dist) + "m";
|
||||||
float tx = edgeX + px * 18.0f;
|
float tx = edgeX + px * 18.0f;
|
||||||
|
|||||||
36
src/Space.h
36
src/Space.h
@ -139,6 +139,42 @@ namespace ZL {
|
|||||||
int pickTargetId() const; // ???????? ???? (????: ????????? ????? ????????? ?????)
|
int pickTargetId() const; // ???????? ???? (????: ????????? ????? ????????? ?????)
|
||||||
|
|
||||||
void clearTextRendererCache();
|
void clearTextRendererCache();
|
||||||
|
|
||||||
|
// Crosshair HUD
|
||||||
|
struct CrosshairConfig {
|
||||||
|
bool enabled = true;
|
||||||
|
int refW = 1280;
|
||||||
|
int refH = 720;
|
||||||
|
|
||||||
|
float scaleMul = 1.0f;
|
||||||
|
|
||||||
|
Eigen::Vector3f color = { 1.f, 1.f, 1.f };
|
||||||
|
float alpha = 1.0f; // cl_crosshairalpha
|
||||||
|
float thicknessPx = 2.0f; // cl_crosshairthickness
|
||||||
|
float gapPx = 10.0f;
|
||||||
|
|
||||||
|
float topLenPx = 14.0f;
|
||||||
|
float topAngleDeg = 90.0f;
|
||||||
|
|
||||||
|
struct Arm { float lenPx; float angleDeg; };
|
||||||
|
std::vector<Arm> arms;
|
||||||
|
};
|
||||||
|
|
||||||
|
CrosshairConfig crosshairCfg;
|
||||||
|
bool crosshairCfgLoaded = false;
|
||||||
|
|
||||||
|
// кеш геометрии
|
||||||
|
VertexRenderStruct crosshairMesh;
|
||||||
|
bool crosshairMeshValid = false;
|
||||||
|
int crosshairLastW = 0, crosshairLastH = 0;
|
||||||
|
float crosshairLastAlpha = -1.0f;
|
||||||
|
float crosshairLastThickness = -1.0f;
|
||||||
|
float crosshairLastGap = -1.0f;
|
||||||
|
float crosshairLastScaleMul = -1.0f;
|
||||||
|
|
||||||
|
bool loadCrosshairConfig(const std::string& path);
|
||||||
|
void rebuildCrosshairMeshIfNeeded();
|
||||||
|
void drawCrosshair();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -169,7 +169,7 @@ void ClientState::apply_lag_compensation(std::chrono::system_clock::time_point n
|
|||||||
|
|
||||||
while (deltaMsLeftover > 0)
|
while (deltaMsLeftover > 0)
|
||||||
{
|
{
|
||||||
long long miniDelta = 50;
|
long long miniDelta = std::min(50LL, deltaMsLeftover);
|
||||||
simulate_physics(miniDelta);
|
simulate_physics(miniDelta);
|
||||||
deltaMsLeftover -= miniDelta;
|
deltaMsLeftover -= miniDelta;
|
||||||
}
|
}
|
||||||
@ -207,7 +207,7 @@ void ClientState::handle_full_sync(const std::vector<std::string>& parts, int st
|
|||||||
discreteAngle = std::stoi(parts[startFrom + 13]);
|
discreteAngle = std::stoi(parts[startFrom + 13]);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string ClientState::formPingMessageContent()
|
std::string ClientState::formPingMessageContent() const
|
||||||
{
|
{
|
||||||
Eigen::Quaternionf q(rotation);
|
Eigen::Quaternionf q(rotation);
|
||||||
|
|
||||||
|
|||||||
@ -50,7 +50,7 @@ struct ClientState {
|
|||||||
|
|
||||||
void handle_full_sync(const std::vector<std::string>& parts, int startFrom);
|
void handle_full_sync(const std::vector<std::string>& parts, int startFrom);
|
||||||
|
|
||||||
std::string formPingMessageContent();
|
std::string formPingMessageContent() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ClientStateInterval
|
struct ClientStateInterval
|
||||||
|
|||||||
@ -7,8 +7,8 @@
|
|||||||
namespace ZL {
|
namespace ZL {
|
||||||
void WebSocketClientEmscripten::Connect(const std::string& host, uint16_t port) {
|
void WebSocketClientEmscripten::Connect(const std::string& host, uint16_t port) {
|
||||||
// Формируем URL. Обратите внимание, что в Web часто лучше использовать ws://localhost
|
// Формируем URL. Обратите внимание, что в Web часто лучше использовать ws://localhost
|
||||||
//std::string url = "ws://" + host + ":" + std::to_string(port);
|
std::string url = "ws://" + host + ":" + std::to_string(port);
|
||||||
std::string url = "wss://api.spacegame.fishrungames.com";
|
//std::string url = "wss://api.spacegame.fishrungames.com";
|
||||||
|
|
||||||
EmscriptenWebSocketCreateAttributes attr = {
|
EmscriptenWebSocketCreateAttributes attr = {
|
||||||
url.c_str(),
|
url.c_str(),
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user