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 "core/config.h"
|
||||
#include "core/logger.h"
|
||||
#include "core/util.h"
|
||||
#include "data/excel.h"
|
||||
#include "data/scene_res.h"
|
||||
#include "game/player_store.h"
|
||||
#include "game/srtools.h"
|
||||
|
||||
@@ -11,6 +13,57 @@ namespace {
|
||||
|
||||
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.
|
||||
void seedDefaultSquad(LineupBook& book) {
|
||||
if (book.cur().count() != 0) return;
|
||||
@@ -60,6 +113,8 @@ void Player::load() {
|
||||
loadPlayerState(*this);
|
||||
seedDefaultSquad(lineups_);
|
||||
lineups_.clampMp();
|
||||
leaveStrandedArena(*this);
|
||||
landOnAnchor(*this);
|
||||
}
|
||||
|
||||
void Player::save() {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#include "game/rescue.h"
|
||||
|
||||
#include "core/config.h"
|
||||
#include "core/logger.h"
|
||||
#include "data/scene_res.h"
|
||||
#include "game/challenge.h"
|
||||
#include "game/peak.h"
|
||||
#include "game/player.h"
|
||||
@@ -85,5 +87,26 @@ std::string unstick(net::Session& session, uint32_t entryId) {
|
||||
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 game
|
||||
@@ -23,5 +23,10 @@ namespace rescue {
|
||||
// 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);
|
||||
|
||||
// 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 game
|
||||
+8
-2
@@ -18,7 +18,8 @@ namespace sdk {
|
||||
|
||||
void registerAdminRoutes(http::Server& server, net::Gateway& gateway) {
|
||||
// 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
|
||||
// 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;
|
||||
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");
|
||||
out["message"] = "no client is connected";
|
||||
} else {
|
||||
|
||||
Reference in new issue
Block a user