diff --git a/Server/Test/server_plugin_tests.cpp b/Server/Test/server_plugin_tests.cpp index 416317b7..39fc2059 100644 --- a/Server/Test/server_plugin_tests.cpp +++ b/Server/Test/server_plugin_tests.cpp @@ -206,6 +206,19 @@ int run() { end) )"); + // One that says what the level is, and when it changes. + write(root / "Plugins" / "follower" / "reskate-plugin.json", + R"({"api": 1, "entry": "plugin/main.lua", "side": "server", "permissions": ["console"]})"); + write(root / "Plugins" / "follower" / "plugin" / "main.lua", R"( + reskate.on("level_unloading", function(name) reskate.console.print("unloading " .. name) end) + reskate.on("level_loaded", function(name) reskate.console.print("loaded " .. name) end) + local ticks = 0 + reskate.on("tick", function() + ticks += 1 + if ticks % 20 == 0 then reskate.console.print("level " .. reskate.level.current().name) end + end) + )"); + std::vector log; SteamTransport transport; Host host(config, transport, [&](const std::string &line) { log.push_back(line); }); @@ -274,6 +287,18 @@ int run() { log.clear(); run_for(1100000); check(has(log, "skater 76561198000000001 at 10.0 2.0 -30.0 facing 1.00, stale"), "A skater's age did not grow without poses"); + + // The level, as players' games name it, and its change heard when the server changes it. + const auto *first = find_level(config.map); + const auto other = std::find_if(levels().begin(), levels().end(), [&](const auto &l) { return l.asset != first->asset; }); + check(first && other != levels().end(), "The test needs two retail maps"); + check(has(log, "level " + first->asset), "level.current() is not the server's map: " + first->asset); + host.change_map(other->name); + log.clear(); + run_for(1100000); + check(has(log, "unloading " + first->asset) && has(log, "loaded " + other->asset), "The plugin did not hear the map change"); + check(has(log, "level " + other->asset) && !has(log, "level " + first->asset), "level.current() did not follow the map change"); + check(!has(log, "skaters 2"), "Skaters stayed on the old map's places after the map changed"); if (failures) for (const auto &line : log) std::fprintf(stderr, "log: %s\n", line.c_str()); return failures ? 1 : 0; diff --git a/Server/server_host.cpp b/Server/server_host.cpp index 6ba1a32e..b4cc006b 100644 --- a/Server/server_host.cpp +++ b/Server/server_host.cpp @@ -240,6 +240,11 @@ void Host::fetching(Guest &guest) { } std::string Host::invite() const { return format_invite({id_, secret_}); } std::string Host::map_name() const { return map_label(config_.map); } +std::string Host::map_level() const { + const auto destination = map_destination(config_.map); + const auto split = destination.find('|'); + return split == std::string::npos ? destination : destination.substr(split + 1); +} std::string Host::wire_map_label() const { // players without the map's mod still see its name auto label = map_name(); cut_text(label, max_member_name); diff --git a/Server/server_host.h b/Server/server_host.h index 07d2d920..67507f2d 100644 --- a/Server/server_host.h +++ b/Server/server_host.h @@ -47,6 +47,9 @@ class Host { std::string invite() const; std::string map_name() const; + // The level players load for the map, as their games name it: a destination's level, after + // its "|". What the server's plugins see as reskate.level.current(). + std::string map_level() const; // "maps reload": reads Mods again and says what changed. Also after a map was updated. std::string reload_maps(); // The mod folder of the map in play while anyone is on (an update leaves it alone), or diff --git a/Server/server_plugins.cpp b/Server/server_plugins.cpp index b2223bf9..3f4685ed 100644 --- a/Server/server_plugins.cpp +++ b/Server/server_plugins.cpp @@ -210,6 +210,9 @@ void ServerPlugins::tick(std::uint64_t now_us) { } const double dt = s.last_tick ? static_cast(now_us - s.last_tick) / 1000.0 : 0; s.last_tick = now_us; + // The map the server runs, so plugins hear level_unloading and level_loaded when it + // changes, as a game's plugins do (when the server changes it; players load it after). + s.plugins.set_level(s.server.map_level()); s.plugins.tick(dt); for (auto &bytes : std::exchange(s.environment.outbox, {})) s.server.send_plugin_message(wire::to_of(bytes), bytes); } catch (const std::exception &error) {