mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 17:21:49 -10:00
Add live force game mode management setting
Co-Authored-By: Codex (GPT-6) <[email protected]>
This commit is contained in:
1 parent
9c7f42d9b7
commit
138cbc1254
7 files changed
+103
-6
No files matched your search
@@ -637,6 +637,8 @@ struct ServerState {
|
||||
max_players: usize,
|
||||
/// The mutable `gamemode` server property (`DedicatedServer.gameMode`).
|
||||
game_mode: i32,
|
||||
/// The mutable `force-gamemode` server property (`DedicatedServer.forceGameMode`).
|
||||
force_game_mode: bool,
|
||||
}
|
||||
|
||||
/// The dedicated-only commands' replies vanilla sends with `broadcast`
|
||||
@@ -1005,6 +1007,7 @@ impl Game<'_> {
|
||||
broadcast_rcon: props.broadcast_rcon_to_ops,
|
||||
op_level: props.op_permission_level,
|
||||
game_mode: props.game_mode,
|
||||
force_game_mode: props.force_game_mode,
|
||||
report: None,
|
||||
view: (
|
||||
props.view_distance.clamp(2, 32) as u32,
|
||||
|
||||
@@ -89,6 +89,22 @@ impl Game<'_> {
|
||||
self.set_default_game_mode(state.game_mode);
|
||||
return json!({ "value": GAME_MODES[state.game_mode as usize] });
|
||||
}
|
||||
"serversettings/force_game_mode" => {
|
||||
return json!({ "value": state.force_game_mode });
|
||||
}
|
||||
"serversettings/force_game_mode/set" => {
|
||||
let Some(force) = body["value"].as_bool() else {
|
||||
return json!({ "error": "force game mode must be a boolean" });
|
||||
};
|
||||
rpc(format!(
|
||||
"Update force game mode from {} to {}",
|
||||
state.force_game_mode, force
|
||||
));
|
||||
self.store_property("force-gamemode", force.to_string());
|
||||
state.force_game_mode = force;
|
||||
self.set_force_game_mode(force);
|
||||
return json!({ "value": state.force_game_mode });
|
||||
}
|
||||
"serversettings/difficulty" => {
|
||||
return json!({ "value": DIFFICULTIES[self.level.difficulty_id() as usize] });
|
||||
}
|
||||
|
||||
@@ -1695,6 +1695,15 @@ impl Game<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply the default mode to connected players when forcing it is enabled.
|
||||
pub(super) fn set_force_game_mode(&mut self, force: bool) {
|
||||
if let Some(host) = self.lan.as_mut() {
|
||||
host.settings.force_game_mode = force;
|
||||
host.hub.config.force_game_mode = force;
|
||||
enforce_guest_game_mode(host, &self.world, self.collision);
|
||||
}
|
||||
}
|
||||
|
||||
/// New command access and game mode forcing settings of the open world.
|
||||
pub(super) fn lan_apply_settings(&mut self, guest_commands: bool, force: bool) {
|
||||
if let Some(server) = &self.integrated {
|
||||
@@ -1706,11 +1715,9 @@ impl Game<'_> {
|
||||
let effective = guest_commands && (!self.serving() || self.allow_commands());
|
||||
if let Some(host) = self.lan.as_mut() {
|
||||
host.settings.guest_commands = guest_commands;
|
||||
host.settings.force_game_mode = force;
|
||||
host.hub.config.guest_commands = effective;
|
||||
host.hub.config.force_game_mode = force;
|
||||
enforce_guest_game_mode(host, &self.world, self.collision);
|
||||
}
|
||||
self.set_force_game_mode(force);
|
||||
self.resend_commands_filtered();
|
||||
}
|
||||
|
||||
|
||||
@@ -59,6 +59,15 @@ pub fn call(shared: &Shared, client: u32, short: &str, argument: &Value) -> Resu
|
||||
.ok_or_else(|| invalid_params("Not a string"))?,
|
||||
),
|
||||
),
|
||||
"serversettings/force_game_mode" => game.force_game_mode(client, None),
|
||||
"serversettings/force_game_mode/set" => game.force_game_mode(
|
||||
client,
|
||||
Some(
|
||||
argument
|
||||
.as_bool()
|
||||
.ok_or_else(|| invalid_params(format!("Not a boolean: {argument}")))?,
|
||||
),
|
||||
),
|
||||
"allowlist" => Ok(lists.allowlist()),
|
||||
"bans" => Ok(lists.bans()),
|
||||
"ip_bans" => Ok(lists.ip_bans()),
|
||||
@@ -290,6 +299,29 @@ struct Game<'a> {
|
||||
}
|
||||
|
||||
impl Game<'_> {
|
||||
/// `MinecraftServerSettingsServiceImpl.forceGameMode` and `setForceGameMode`.
|
||||
fn force_game_mode(&self, client: u32, value: Option<bool>) -> Result<Value, Failure> {
|
||||
let mut request = json!({ "method": "serversettings/force_game_mode" });
|
||||
if let Some(value) = value {
|
||||
request["method"] = json!("serversettings/force_game_mode/set");
|
||||
request["value"] = json!(value);
|
||||
}
|
||||
let reply = self.ask(&format!("management {client} {request}"))?;
|
||||
if let Some(error) = reply["error"].as_str() {
|
||||
return Err((INTERNAL_ERROR, Some(error.to_owned())));
|
||||
}
|
||||
if let Some(value) = value {
|
||||
crate::config::update(
|
||||
&self.shared.dir,
|
||||
"properties",
|
||||
"force-gamemode",
|
||||
toml::Value::Boolean(value),
|
||||
)
|
||||
.map_err(|e| (INTERNAL_ERROR, Some(e)))?;
|
||||
}
|
||||
Ok(reply["value"].clone())
|
||||
}
|
||||
|
||||
/// `MinecraftServerSettingsServiceImpl.getGameMode` and `setGameMode`.
|
||||
fn game_mode(&self, client: u32, value: Option<&str>) -> Result<Value, Failure> {
|
||||
let mut request = json!({ "method": "serversettings/game_mode" });
|
||||
|
||||
@@ -87,7 +87,7 @@ MinecraftServer.fillSystemReport skip:debug system report for crash and debug o
|
||||
MinecraftServer.setDifficulty port crates/horizon-client/src/game/commands.rs::Game::cmd_difficulty gap: player: LAN guests are not sent ChangeDifficulty when /difficulty changes; they get it on join, respawn or dimension change
|
||||
MinecraftServer.updateMobSpawningFlags inline crates/horizon-client/src/game/natural_spawning.rs::Game::spawn_natural rules are read at each spawn; no cached spawn flag
|
||||
MinecraftServer.sendDifficultyUpdate inline crates/horizon-client/src/lan/hub.rs::Hub::place_new_player gap: player: sent when a guest joins, respawns or changes dimension; see setDifficulty
|
||||
MinecraftServer.enforceGameTypeForPlayers inline crates/horizon-client/src/game/lan.rs::Game::lan_apply_settings gap: player: guests already online keep their mode when force changes; the default applies at join (lan_guest_joined)
|
||||
MinecraftServer.enforceGameTypeForPlayers inline crates/horizon-client/src/game/lan.rs::enforce_guest_game_mode applies the default mode to online players when force game mode is enabled
|
||||
MinecraftServer.executeIfPossible skip:java rejects tasks during shutdown
|
||||
MinecraftServer.reloadResources port crates/horizon-client/src/game/functions.rs::Game::cmd_reload gap: minor: data packs only, via reload_datapacks
|
||||
MinecraftServer.configurePackRepository port crates/horizon-client/src/game/functions.rs::Game::reload_datapacks gap: minor: picks the enabled packs and adds new ones; no feature-flag check and no safe mode
|
||||
|
||||
|
@@ -23,7 +23,8 @@ DedicatedServer.convertOldUsers skip:other one-time import of pre-1.7 .txt user
|
||||
DedicatedServer.runCommand port crates/horizon-server/src/rcon.rs::run_on_game gap: minor: runs as the console (`console <line>` on the control endpoint), not as an Rcon source
|
||||
DedicatedServer.setEntityBroadcastRangePercentage skip:server JSON-RPC management API only (ServerSettingsService); Horizon has no such API
|
||||
DedicatedServer.createTextFilterForPlayer skip:service external chat filter service; Horizon has no text filter
|
||||
DedicatedServer.setForceGameMode skip:server JSON-RPC management API only (ServerSettingsService); Horizon has no such API
|
||||
DedicatedServer.setForceGameMode port crates/horizon-client/src/game/dedicated/management.rs::Game::management stores force-gamemode, updates the host and applies the default mode to online players
|
||||
MinecraftServerSettingsServiceImpl.setForceGameMode port crates/horizon-server/src/management_methods.rs::Game::force_game_mode forwards the live setting to the game and persists it in server.toml
|
||||
DedicatedServer.setGameMode port crates/horizon-client/src/game/lan.rs::Game::set_default_game_mode the /defaultgamemode command; gap: minor: the value is not written back to server.properties
|
||||
DedicatedServer.setAcceptsTransfers skip:server JSON-RPC management API only (ServerSettingsService); Horizon has no such API
|
||||
DedicatedServer.setPauseWhenEmptySeconds skip:server JSON-RPC management API only (ServerSettingsService); Horizon has no such API
|
||||
|
||||
|
@@ -16,7 +16,7 @@ sent the same things over TLS, and every answer is compared exactly:
|
||||
vanilla's data generator), `server/status`, the four lists, `players`, kicking nobody, `server/save` and
|
||||
`server/system_message` with no one online, the four lists changed (`add`, `remove`, `set`,
|
||||
`clear`; with no profile service, players named resolve to offline players on both), the live server
|
||||
message, difficulty and default game mode read and changed (including their saved properties), and
|
||||
message, difficulty, default game mode and forced game mode read and changed (including saved properties), and
|
||||
`server/stop`;
|
||||
- the TLS version each server agrees on.
|
||||
|
||||
@@ -138,6 +138,15 @@ MESSAGES = [
|
||||
'{"jsonrpc":"2.0","id":52,"method":"minecraft:serversettings/game_mode"}',
|
||||
'{"jsonrpc":"2.0","id":53,"method":"minecraft:serversettings/game_mode/set","params":["spectator"]}',
|
||||
'{"jsonrpc":"2.0","id":54,"method":"minecraft:serversettings/game_mode"}',
|
||||
'{"jsonrpc":"2.0","id":55,"method":"minecraft:serversettings/force_game_mode"}',
|
||||
'{"jsonrpc":"2.0","id":56,"method":"minecraft:serversettings/force_game_mode/set","params":[true]}',
|
||||
'{"jsonrpc":"2.0","id":57,"method":"minecraft:serversettings/force_game_mode"}',
|
||||
'{"jsonrpc":"2.0","id":58,"method":"minecraft:serversettings/force_game_mode/set","params":[false]}',
|
||||
'{"jsonrpc":"2.0","id":59,"method":"minecraft:serversettings/force_game_mode"}',
|
||||
'{"jsonrpc":"2.0","id":60,"method":"minecraft:serversettings/force_game_mode/set","params":[true]}',
|
||||
'{"jsonrpc":"2.0","id":61,"method":"minecraft:serversettings/force_game_mode"}',
|
||||
'{"jsonrpc":"2.0","id":62,"method":"minecraft:serversettings/force_game_mode/set","params":["yes"]}',
|
||||
'{"jsonrpc":"2.0","id":63,"method":"minecraft:serversettings/force_game_mode"}',
|
||||
]
|
||||
|
||||
|
||||
@@ -360,12 +369,32 @@ def main():
|
||||
sides[side]['saved_game_mode'] = next(
|
||||
(line.partition('=')[2] for line in properties.splitlines()
|
||||
if line.startswith('gamemode=')), None)
|
||||
sides[side]['saved_force_game_mode'] = next(
|
||||
(line.partition('=')[2] for line in properties.splitlines()
|
||||
if line.startswith('force-gamemode=')), None)
|
||||
if side == 'horizon':
|
||||
world = (folder / 'server.toml').read_text().partition('[world]')[2].split('\n[', 1)[0]
|
||||
sides[side]['saved_config_difficulty'] = 'difficulty = "peaceful"' in world
|
||||
sides[side]['saved_config_game_mode'] = 'game_mode = "spectator"' in world
|
||||
sides[side]['saved_config_force_game_mode'] = 'force-gamemode = true' in (
|
||||
folder / 'server.toml').read_text()
|
||||
finally:
|
||||
server.stop()
|
||||
restarted = Server(command, folder, build / f'{side}-restart.log')
|
||||
try:
|
||||
restarted.wait_for(DONE, 600)
|
||||
ws = Socket(management, context, [f'Authorization: Bearer {SECRET}'])
|
||||
try:
|
||||
ws.send('{"jsonrpc":"2.0","id":64,"method":"minecraft:serversettings/force_game_mode"}')
|
||||
while True:
|
||||
reply = normal(ws.receive(timeout=60))
|
||||
if not isinstance(reply, dict) or 'method' not in reply or 'id' in reply:
|
||||
break
|
||||
sides[side]['restarted_force_game_mode'] = reply
|
||||
finally:
|
||||
ws.sock.close()
|
||||
finally:
|
||||
restarted.stop()
|
||||
vanilla, horizon = sides['vanilla'], sides['horizon']
|
||||
failures = []
|
||||
for key in ('no secret', 'wrong secret', 'origin not allowed', 'subprotocol', 'tls', 'stop'):
|
||||
@@ -382,7 +411,16 @@ def main():
|
||||
failures.append(f"saved game mode: vanilla {vanilla['saved_game_mode']!r}, Horizon {horizon['saved_game_mode']!r}")
|
||||
if not horizon['saved_config_game_mode']:
|
||||
failures.append('Horizon did not save spectator game mode in server.toml')
|
||||
if vanilla['saved_force_game_mode'] != horizon['saved_force_game_mode'] or vanilla['saved_force_game_mode'] != 'true':
|
||||
failures.append(f"saved forced game mode: vanilla {vanilla['saved_force_game_mode']!r}, Horizon {horizon['saved_force_game_mode']!r}")
|
||||
if not horizon['saved_config_force_game_mode']:
|
||||
failures.append('Horizon did not save forced game mode in server.toml')
|
||||
if vanilla['restarted_force_game_mode'] != horizon['restarted_force_game_mode'] or \
|
||||
vanilla['restarted_force_game_mode'] != {'jsonrpc': '2.0', 'id': 64, 'result': True}:
|
||||
failures.append(f"restarted forced game mode: vanilla {vanilla['restarted_force_game_mode']!r}, Horizon {horizon['restarted_force_game_mode']!r}")
|
||||
(build / 'report.json').write_text(json.dumps(sides, indent=1))
|
||||
if failures:
|
||||
sys.exit(f'{NAME}: {len(failures)} differences (logs in {build}):\n' + '\n'.join(failures))
|
||||
report = lab.LOCAL / 'datagen/out/reports/json-rpc-api-schema.json'
|
||||
discovered = horizon['answers'][MESSAGES.index('{"jsonrpc":"2.0","id":12,"method":"rpc.discover"}')]
|
||||
if json.loads(report.read_text()) != discovered.get('result'):
|
||||
|
||||
Reference in new issue
Block a user