diff --git a/luprex/cpp/core/lpxserver.cpp b/luprex/cpp/core/lpxserver.cpp index 2673551f..864bcd8b 100644 --- a/luprex/cpp/core/lpxserver.cpp +++ b/luprex/cpp/core/lpxserver.cpp @@ -89,7 +89,29 @@ public: sb->overwrite_int32(tw_1, tw_2 - tw_1); } + void redirect(int64_t id1, int64_t id2) { + for (auto &client : clients_) { + if (client->actor_id_ == id1) { + bool ok = world_->redirected(id1, id2); + if (!ok) delete_client(client); + } + } + } + + // Redirection is touchy: almost any world->invoke could + // call tangible.redirect. After that point, the world is + // relying on us not to control the wrong character. + void process_redirects() { + if (world_->have_redirects()) { + World::Redirects redirects = world_->fetch_redirects(); + for (const auto &pair : redirects) { + redirect(pair.first, pair.second); + } + } + } + bool handle_invocation(UniqueClient &client) { + if (client == nullptr) return false; StreamBuffer *sb = client->channel_->in(); if (sb->empty()) return false; int64_t tr_before = sb->total_reads(); @@ -131,6 +153,7 @@ public: // if (inv.actor() == client->actor_id_) { world_->invoke(inv); + process_redirects(); } return true; } @@ -183,8 +206,9 @@ public: // Get the clock. double clock = get_clock(); - // Execute any queued invocations. - // We just feed these directly into the master model. + // Execute any delayed invocations. These always + // come from the local command line. We just feed + // these directly into the master model. for (const Invocation &inv : delayed_invocations_) { world_->invoke(inv); } @@ -225,6 +249,11 @@ public: if (next_tick_ < clock + 0.3) next_tick_ = clock + 0.3; } + // This handles any redirects triggered from the server command + // line or by the tick function. + process_redirects(); + util::remove_nullptrs(clients_); + // Traverse all existing channels, process any communication. for (UniqueClient &client : clients_) { if (client->channel_->closed()) { diff --git a/luprex/cpp/core/world-accessor.cpp b/luprex/cpp/core/world-accessor.cpp index d1d61726..118f52ff 100644 --- a/luprex/cpp/core/world-accessor.cpp +++ b/luprex/cpp/core/world-accessor.cpp @@ -282,8 +282,8 @@ LuaDefine(tangible_getclass, "tan", LuaDefine(tangible_delete, "tan", "|Delete the specified tangible." "|" - "|This cannot be used to delete player tangibles," - "|To delete a player, use tangible.redirect" + "|This cannot be used to delete actor tangibles," + "|To delete a actor, use tangible.deleteactor" "|") { LuaArg tanobj; LuaDefStack LS(L, tanobj); @@ -332,14 +332,16 @@ LuaDefine(tangible_deleteactor, "tan", LuaDefine(tangible_keepactor, "tan", "|Mark an actor tangible to not 'delete_on_disconnect'." "|" - "|When a client connects to the server, a login actor is created and the" - "|client is put in control of the login actor. The client typically" - "|controls the login actor just long enough to type his username and password." - "|Then, he is redirected to the real actor." + "|When a client connects to the server, a new 'login' actor is " + "|created and the client is put in control of the login actor. " + "|The login actor typically presents a login dialog. After the " + "|client types his name and password, tangible.redirect is used " + "|to tell the client to control the real actor." "|" - "|When the client is redirected to the real actor, the login actor is no longer" - "|needed. The login actor has the flag 'delete_on_disconnect', so when the" - "|client detaches from the login actor, it is garbage collected." + "|When the client is redirected to the real actor, the login " + "|actor is no longer needed. The login actor has the " + "|flag 'delete_on_disconnect', so when the client detaches " + "|from the login actor, it is garbage collected." "|" "|However, if the player clicks 'NEW PLAYER', then it is necessary to" "|convert the login actor into a permanent actor. The most important step is to" @@ -357,6 +359,38 @@ LuaDefine(tangible_keepactor, "tan", return LS.result(); } +LuaDefine(tangible_redirect, "actor1, actor2", + "|Cause the client controlling actor1 to take a control of actor2" + "|" + "|When a client connects to the server, a new 'login' actor is " + "|created and the client is put in control of the login actor. " + "|The login actor typically presents a login dialog. After the " + "|client types his name and password, tangible.redirect is used " + "|to tell the client to control the real actor." + "|" + "|Actor1 must be currently logged in. Actor2 must not be. This " + "|function doesn't error check that the actors are valid: it just " + "|queues the redirect, then the system error checks later. This " + "|is because conditions can change. An invalid redirect will cause " + "|a disconnection." + "|") { + LuaArg lactor1, lactor2; + LuaDefStack LS(L, lactor1, lactor2); + World *w = World::fetch_global_pointer(L); + Tangible *actor1 = w->tangible_get(LS, lactor1, true); + Tangible *actor2 = w->tangible_get(LS, lactor2, true); + if (!actor1->is_controlled_) { + luaL_error(L, "Actor1 is not a controlled actor."); + return 0; + } + if ((!actor2->can_be_controlled_) || (actor2->is_controlled_)) { + luaL_error(L, "Actor2 is not an uncontrolled actor."); + return 0; + } + w->add_redirect(actor1->id(), actor2->id()); + return LS.result(); +} + LuaDefine(tangible_build, "config", "|Build a new tangible object." "|" diff --git a/luprex/cpp/core/world-core.cpp b/luprex/cpp/core/world-core.cpp index 7c90eb2f..4e977f64 100644 --- a/luprex/cpp/core/world-core.cpp +++ b/luprex/cpp/core/world-core.cpp @@ -345,6 +345,17 @@ void World::disconnected(int64_t actor_id) { } } +bool World::redirected(int64_t actor_id, int64_t new_id) { + disconnected(actor_id); + Tangible *tan = tangible_get(new_id); + if (tan && (tan->can_be_controlled_) && (!tan->is_controlled_)) { + tan->is_controlled_ = true; + return true; + } else { + return false; + } +} + eng::string World::probe_lua_expr(int64_t actor_id, std::string_view lua) { assert(stack_is_clear()); lua_State *L = state(); diff --git a/luprex/cpp/core/world.hpp b/luprex/cpp/core/world.hpp index d87a31a1..747acfbc 100644 --- a/luprex/cpp/core/world.hpp +++ b/luprex/cpp/core/world.hpp @@ -243,10 +243,24 @@ public: // void disconnected(int64_t actor_id); + // Notify the world that the front end has implemented a + // redirect. This can return null to indicate a last-minute + // failure, in which case the front end must disconnect. + // + bool redirected(int64_t actor_id, int64_t new_id); + + // Add a redirect. + // + void add_redirect(int64_t a, int64_t b) { redirects_.emplace(a,b); } + // Fetch all redirects and clear the redirects table. // Redirects fetch_redirects(); + // Return true if there are any redirects. + // + bool have_redirects() const { return !redirects_.empty(); } + // Probe an arbitrary lua expression. // // Any print-statements in the lua code are sent into