fix(server/game): unstick without a client connected
This commit is contained in:
1 parent
9dd37f8087
commit
9030af1c4d
4 files changed
+91
-2
No files matched your search
@@ -1,8 +1,10 @@
|
|||||||
#include "game/player.h"
|
#include "game/player.h"
|
||||||
|
|
||||||
#include "core/config.h"
|
#include "core/config.h"
|
||||||
|
#include "core/logger.h"
|
||||||
#include "core/util.h"
|
#include "core/util.h"
|
||||||
#include "data/excel.h"
|
#include "data/excel.h"
|
||||||
|
#include "data/scene_res.h"
|
||||||
#include "game/player_store.h"
|
#include "game/player_store.h"
|
||||||
#include "game/srtools.h"
|
#include "game/srtools.h"
|
||||||
|
|
||||||
@@ -11,6 +13,57 @@ namespace {
|
|||||||
|
|
||||||
constexpr uint64_t kSaveIntervalMs = 2000;
|
constexpr uint64_t kSaveIntervalMs = 2000;
|
||||||
|
|
||||||
|
// MazePlane's Challenge type, as the scene dump numbers it, and the Express floor to
|
||||||
|
// fall back to.
|
||||||
|
constexpr uint32_t kChallengePlaneType = 4;
|
||||||
|
constexpr uint32_t kStartEntry = 1000002;
|
||||||
|
|
||||||
|
// A run that never got to finish -- a server restart mid-floor, a crash -- leaves the
|
||||||
|
// saved spot inside a challenge arena with nothing left to rebuild it from. The client
|
||||||
|
// then gets a floor with no ground and no party under it: a black screen it can only
|
||||||
|
// look around in. Start on the Express instead.
|
||||||
|
void leaveStrandedArena(Player& player) {
|
||||||
|
const data::SceneRes& res = data::SceneRes::get();
|
||||||
|
const data::ResFloor* floor = res.byEntry(player.location().entryId);
|
||||||
|
if (floor == nullptr || floor->planeType != kChallengePlaneType) return;
|
||||||
|
|
||||||
|
const data::ResFloor* start = res.byEntry(kStartEntry);
|
||||||
|
if (start == nullptr) return;
|
||||||
|
logging::info("player", "uid {} was saved inside arena {}; starting at {} instead",
|
||||||
|
player.uid(), player.location().entryId, kStartEntry);
|
||||||
|
player.location().entryId = kStartEntry;
|
||||||
|
player.location().planeId = start->planeId;
|
||||||
|
player.location().floorId = start->floorId;
|
||||||
|
player.position() = Position{};
|
||||||
|
}
|
||||||
|
|
||||||
|
// The last saved spot can be mid-jump or mid-fall, and coming back to it leaves the
|
||||||
|
// player hanging in the air. Logging in lands on the floor's nearest anchor instead.
|
||||||
|
void landOnAnchor(Player& player) {
|
||||||
|
const data::ResFloor* floor = data::SceneRes::get().byEntry(player.location().entryId);
|
||||||
|
if (floor == nullptr) return;
|
||||||
|
|
||||||
|
Position& at = player.position();
|
||||||
|
const data::ResTeleport* nearest = nullptr;
|
||||||
|
int64_t best = 0;
|
||||||
|
for (const data::ResGroup& group : floor->groups) {
|
||||||
|
for (const data::ResTeleport& anchor : group.teleports) {
|
||||||
|
int64_t dx = static_cast<int64_t>(anchor.pos.x) - at.x;
|
||||||
|
int64_t dz = static_cast<int64_t>(anchor.pos.z) - at.z;
|
||||||
|
int64_t distance = dx * dx + dz * dz;
|
||||||
|
if (nearest == nullptr || distance < best) {
|
||||||
|
nearest = &anchor;
|
||||||
|
best = distance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (nearest == nullptr) return;
|
||||||
|
at.x = nearest->pos.x;
|
||||||
|
at.y = nearest->pos.y;
|
||||||
|
at.z = nearest->pos.z;
|
||||||
|
at.rotY = nearest->rot.y;
|
||||||
|
}
|
||||||
|
|
||||||
// A squad that at least exists, so a fresh account is not stranded with no party.
|
// A squad that at least exists, so a fresh account is not stranded with no party.
|
||||||
void seedDefaultSquad(LineupBook& book) {
|
void seedDefaultSquad(LineupBook& book) {
|
||||||
if (book.cur().count() != 0) return;
|
if (book.cur().count() != 0) return;
|
||||||
@@ -60,6 +113,8 @@ void Player::load() {
|
|||||||
loadPlayerState(*this);
|
loadPlayerState(*this);
|
||||||
seedDefaultSquad(lineups_);
|
seedDefaultSquad(lineups_);
|
||||||
lineups_.clampMp();
|
lineups_.clampMp();
|
||||||
|
leaveStrandedArena(*this);
|
||||||
|
landOnAnchor(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Player::save() {
|
void Player::save() {
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
#include "game/rescue.h"
|
#include "game/rescue.h"
|
||||||
|
|
||||||
|
#include "core/config.h"
|
||||||
#include "core/logger.h"
|
#include "core/logger.h"
|
||||||
|
#include "data/scene_res.h"
|
||||||
#include "game/challenge.h"
|
#include "game/challenge.h"
|
||||||
#include "game/peak.h"
|
#include "game/peak.h"
|
||||||
#include "game/player.h"
|
#include "game/player.h"
|
||||||
@@ -85,5 +87,26 @@ std::string unstick(net::Session& session, uint32_t entryId) {
|
|||||||
return did;
|
return did;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string relocate(uint32_t entryId) {
|
||||||
|
if (data::SceneRes::get().byEntry(entryId) == nullptr) {
|
||||||
|
return "entry " + std::to_string(entryId) + " is not in the scene dump";
|
||||||
|
}
|
||||||
|
|
||||||
|
Player player(core::Config::get().player.uid);
|
||||||
|
player.load();
|
||||||
|
// scene::load only re-anchors when the floor changes, and the saved spot may already
|
||||||
|
// be on this one. Clearing the floor forces the entrance anchor -- keeping old
|
||||||
|
// coordinates, or worse (0, 0, 0), drops the player outside the geometry.
|
||||||
|
player.location().floorId = 0;
|
||||||
|
proto::SceneInfo scene;
|
||||||
|
if (!scene::load(player, entryId, 0, true, scene)) {
|
||||||
|
return "entry " + std::to_string(entryId) + " could not be loaded";
|
||||||
|
}
|
||||||
|
player.saveNow();
|
||||||
|
|
||||||
|
logging::info("rescue", "no client connected; the save now starts at entry {}", entryId);
|
||||||
|
return "saved spot moved to entry " + std::to_string(entryId);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace rescue
|
} // namespace rescue
|
||||||
} // namespace game
|
} // namespace game
|
||||||
@@ -23,5 +23,10 @@ namespace rescue {
|
|||||||
// Returns a one-line summary of what was done, for the log and the http reply.
|
// Returns a one-line summary of what was done, for the log and the http reply.
|
||||||
std::string unstick(net::Session& session, uint32_t entryId);
|
std::string unstick(net::Session& session, uint32_t entryId);
|
||||||
|
|
||||||
|
// The same escape with nobody connected: rewrites the saved spot so the next login
|
||||||
|
// starts somewhere else. For the floor the client cannot finish loading at all, where
|
||||||
|
// there is no session left to push anything to.
|
||||||
|
std::string relocate(uint32_t entryId);
|
||||||
|
|
||||||
} // namespace rescue
|
} // namespace rescue
|
||||||
} // namespace game
|
} // namespace game
|
||||||
+8
-2
@@ -18,7 +18,8 @@ namespace sdk {
|
|||||||
|
|
||||||
void registerAdminRoutes(http::Server& server, net::Gateway& gateway) {
|
void registerAdminRoutes(http::Server& server, net::Gateway& gateway) {
|
||||||
// GET /unstick -- push the current scene again
|
// GET /unstick -- push the current scene again
|
||||||
// GET /unstick?entry=1000002 -- and move to the Astral Express while doing it
|
// GET /unstick?entry=1000002 -- and move to the Astral Express while doing it,
|
||||||
|
// or move the save there when nothing is connected
|
||||||
//
|
//
|
||||||
// The client has no timeout on a transition it started, so a scene or a battle the
|
// The client has no timeout on a transition it started, so a scene or a battle the
|
||||||
// server never finished leaves it looking at nothing forever. Pushing a scene is
|
// server never finished leaves it looking at nothing forever. Pushing a scene is
|
||||||
@@ -34,7 +35,12 @@ void registerAdminRoutes(http::Server& server, net::Gateway& gateway) {
|
|||||||
if (session == nullptr) continue;
|
if (session == nullptr) continue;
|
||||||
out["sessions"].push_back(game::rescue::unstick(*session, entryId));
|
out["sessions"].push_back(game::rescue::unstick(*session, entryId));
|
||||||
}
|
}
|
||||||
if (out["sessions"].empty()) {
|
if (out["sessions"].empty() && entryId != 0) {
|
||||||
|
// Nobody is connected, so move the saved spot instead. A client that cannot
|
||||||
|
// finish loading the floor it was left on has no session to rescue, and this
|
||||||
|
// is the only way out of it short of editing the save by hand.
|
||||||
|
out["message"] = game::rescue::relocate(entryId);
|
||||||
|
} else if (out["sessions"].empty()) {
|
||||||
logging::warn("rescue", "nothing to unstick -- no client is connected");
|
logging::warn("rescue", "nothing to unstick -- no client is connected");
|
||||||
out["message"] = "no client is connected";
|
out["message"] = "no client is connected";
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in new issue
Block a user