/*
* Copyright 2014 MovingBlocks
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.terasology.logic.console.commands;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.terasology.config.Config;
import org.terasology.engine.GameEngine;
import org.terasology.entitySystem.entity.EntityManager;
import org.terasology.entitySystem.entity.EntityRef;
import org.terasology.entitySystem.systems.BaseComponentSystem;
import org.terasology.entitySystem.systems.RegisterSystem;
import org.terasology.logic.common.DisplayNameComponent;
import org.terasology.logic.console.Console;
import org.terasology.logic.console.commandSystem.annotations.Command;
import org.terasology.logic.console.commandSystem.annotations.CommandParam;
import org.terasology.logic.console.commandSystem.annotations.Sender;
import org.terasology.logic.console.suggesters.UsernameSuggester;
import org.terasology.logic.permission.PermissionManager;
import org.terasology.logic.players.PlayerUtil;
import org.terasology.math.geom.Vector3i;
import org.terasology.network.Client;
import org.terasology.network.ClientComponent;
import org.terasology.network.ClientInfoComponent;
import org.terasology.network.NetworkComponent;
import org.terasology.network.NetworkSystem;
import org.terasology.persistence.StorageManager;
import org.terasology.registry.In;
import org.terasology.world.chunks.ChunkProvider;
/**
* Commands to administer a remote server
*
*/
@RegisterSystem
public class ServerCommands extends BaseComponentSystem {
private static final Logger logger = LoggerFactory.getLogger(ServerCommands.class);
@In
private EntityManager entityManager;
@In
private StorageManager storageManager;
@In
private ChunkProvider chunkProvider;
@In
private NetworkSystem networkSystem;
@In
private Config config;
@In
private GameEngine gameEngine;
@Command(shortDescription = "Shutdown the server", runOnServer = true,
requiredPermission = PermissionManager.SERVER_MANAGEMENT_PERMISSION)
public String shutdownServer(@Sender EntityRef sender) {
// TODO: verify permissions of sender
EntityRef clientInfo = sender.getComponent(ClientComponent.class).clientInfo;
DisplayNameComponent name = clientInfo.getComponent(DisplayNameComponent.class);
logger.info("Shutdown triggered by {}", name.name);
gameEngine.shutdown();
return "Server shutdown triggered";
}
@Command(shortDescription = "Kick user by name", runOnServer = true,
requiredPermission = PermissionManager.USER_MANAGEMENT_PERMISSION)
public String kickUser(@CommandParam("username") String username) {
for (EntityRef clientEntity : entityManager.getEntitiesWith(ClientComponent.class)) {
EntityRef clientInfo = clientEntity.getComponent(ClientComponent.class).clientInfo;
DisplayNameComponent name = clientInfo.getComponent(DisplayNameComponent.class);
if (username.equalsIgnoreCase(name.name)) {
return kick(clientEntity);
}
}
throw new IllegalArgumentException("No such user '" + username + "'");
}
@Command(shortDescription = "Rename a user", runOnServer = true,
requiredPermission = PermissionManager.USER_MANAGEMENT_PERMISSION)
public String renameUser(
@CommandParam(value = "userName", suggester = UsernameSuggester.class) String userName,
@CommandParam(value = "newUserName") String newUserName) {
Iterable<EntityRef> clientInfoEntities = entityManager.getEntitiesWith(ClientInfoComponent.class);
for (EntityRef clientInfo : clientInfoEntities) {
DisplayNameComponent nameComp = clientInfo.getComponent(DisplayNameComponent.class);
if (newUserName.equalsIgnoreCase(nameComp.name)) {
throw new IllegalArgumentException("New user name is already in use");
}
}
for (EntityRef clientInfo : clientInfoEntities) {
DisplayNameComponent nameComp = clientInfo.getComponent(DisplayNameComponent.class);
if (userName.equalsIgnoreCase(nameComp.name)) {
nameComp.name = newUserName;
clientInfo.saveComponent(nameComp);
return "User " + userName + " has been renamed to " + newUserName;
}
}
throw new IllegalArgumentException("No such user '" + userName + "'");
}
@Command(shortDescription = "Kick user by ID", runOnServer = true,
requiredPermission = PermissionManager.USER_MANAGEMENT_PERMISSION)
public String kickUserByID(@CommandParam("userId") int userId) {
// TODO: verify permissions of sender
for (EntityRef clientEntity : entityManager.getEntitiesWith(ClientComponent.class)) {
EntityRef clientInfo = clientEntity.getComponent(ClientComponent.class).clientInfo;
NetworkComponent nc = clientInfo.getComponent(NetworkComponent.class);
if (userId == nc.getNetworkId()) {
return kick(clientEntity);
}
}
throw new IllegalArgumentException("No such user with ID " + userId);
}
@Command(shortDescription = "List users",
requiredPermission = PermissionManager.USER_MANAGEMENT_PERMISSION)
public String listUsers() {
StringBuilder stringBuilder = new StringBuilder();
for (EntityRef clientInfo : entityManager.getEntitiesWith(ClientInfoComponent.class)) {
DisplayNameComponent dnc = clientInfo.getComponent(DisplayNameComponent.class);
NetworkComponent nc = clientInfo.getComponent(NetworkComponent.class);
String playerText = PlayerUtil.getColoredPlayerName(clientInfo);
String line = String.format("%s - %s (%d)", playerText, dnc.description, nc.getNetworkId());
stringBuilder.append(line);
stringBuilder.append(Console.NEW_LINE);
}
return stringBuilder.toString();
}
private String kick(EntityRef clientEntity) {
Client client = networkSystem.getOwner(clientEntity);
if (!client.isLocal()) {
EntityRef clientInfo = clientEntity.getComponent(ClientComponent.class).clientInfo;
DisplayNameComponent name = clientInfo.getComponent(DisplayNameComponent.class);
logger.info("Kicking user {}", name.name);
networkSystem.forceDisconnect(client);
return "User kick triggered for '" + name.name + "'";
}
return "Request declined";
}
@Command(shortDescription = "Triggers the creation of a save game", runOnServer = true,
requiredPermission = PermissionManager.SERVER_MANAGEMENT_PERMISSION)
public void save() {
storageManager.requestSaving();
}
@Command(shortDescription = "Invalidates the specified chunk and recreates it (requires storage manager disabled)", runOnServer = true)
public String reloadChunk(@CommandParam("x") int x, @CommandParam("y") int y, @CommandParam("z") int z) {
Vector3i pos = new Vector3i(x, y, z);
if (config.getSystem().isWriteSaveGamesEnabled()) {
return "Writing save games is enabled! Invalidating chunk has no effect";
}
boolean success = chunkProvider.reloadChunk(pos);
return success
? "Cleared chunk " + pos + " from cache and triggered reload"
: "Chunk " + pos + " did not exist in the cache";
}
@Command(shortDescription = "Deletes the current world and generated new chunks", runOnServer = true)
public void purgeWorld() {
chunkProvider.purgeWorld();
}
}