Index: scripts/usr/local/lib/7dtd/commands/instances.sh
===================================================================
--- scripts/usr/local/lib/7dtd/commands/instances.sh	(revision 18)
+++ scripts/usr/local/lib/7dtd/commands/instances.sh	(revision 19)
@@ -3,5 +3,5 @@
 
 
-sdtdCommandInstances() {
+sdtdSubcommandInstancesList() {
 	printf "%-*s | %-*s | %-*s | %-*s\n" 20 "Instance name" 8 "Running" 7 "Players" 5 "Port"
 	printf -v line "%*s-+-%*s-+-%*s-+-%*s\n" 20 " " 8 " " 7 " " 5 " "
@@ -22,11 +22,135 @@
 		printf "%-*s | %*s |   %2s/%2d | %5d\n" 20 "$I" 8 "$run" $cur $max $port
 	done
-	exit 0
+}
+
+sdtdSubcommandInstancesCreate() {
+	while : ; do
+		readInstanceName
+		[ $(isValidInstance "$INSTANCE") -eq 0 ] && break
+		echo "Instance name already in use."
+		INSTANCE=
+	done
+	echo
+	
+	local IPATH=$(getInstancePath "$INSTANCE")
+	mkdir -p "$IPATH" 2>/dev/null
+
+	if [ $(configTemplateExists) -eq 1 ]; then
+		local USETEMPLATE
+		while : ; do
+			read -p "Use the config template? [Yn] " USETEMPLATE
+			USETEMPLATE=${USETEMPLATE:-Y}
+			case $USETEMPLATE in
+				y|Y)
+					cp $SDTD_BASE/templates/config.xml $IPATH/config.xml
+					loadCurrentConfigValues "$INSTANCE"
+					break
+					;;
+				n|N)
+					break
+					;;
+			esac
+		done
+	fi
+	configEditAll
+	echo
+	configSetAutoParameters "$INSTANCE"
+	echo
+	echo "Saving"
+	
+	if [ ! -f $IPATH/config.xml ]; then
+		echo "<ServerSettings/>" > $IPATH/config.xml
+	fi
+	saveCurrentConfigValues "$INSTANCE"
+	if [ -f "$SDTD_BASE/templates/admins.xml" ]; then
+		cp "$SDTD_BASE/templates/admins.xml" "$IPATH/"
+	fi
+	chown -R $SDTD_USER.$SDTD_GROUP $IPATH
+	echo "Done"
+}
+
+sdtdSubcommandInstancesEdit() {
+	if [ $(isValidInstance "$1") -eq 0 ]; then
+		echo "No instance given or not a valid instance!"
+		return
+	fi
+
+	if [ $(isRunning "$1") -eq 0 ]; then
+		INSTANCE=$1
+		loadCurrentConfigValues "$1"
+		configEditAll
+		echo
+		configSetAutoParameters "$INSTANCE"
+		echo
+		echo "Saving"
+		saveCurrentConfigValues "$1"
+		echo "Done"
+	else
+		echo "Instance $1 is currently running. Please stop it first."
+	fi
+}
+
+sdtdSubcommandInstancesDelete() {
+	if [ $(isValidInstance "$1") -eq 0 ]; then
+		echo "No instance given or not a valid instance!"
+		return
+	fi
+
+	if [ $(isRunning "$1") -eq 0 ]; then
+		local SECCODE=$(dd if=/dev/urandom bs=1 count=100 2>/dev/null \
+			| tr -cd '[:alnum:]' | head -c5)
+		local SECCODEIN
+		echo
+		echo "WARNING: Do you really want to delete the following instance?"
+		echo "    $1"
+		echo "This will delete all of its configuration and save data."
+		echo "If you REALLY want to continue enter the following security code:"
+		echo "    $SECCODE"
+		echo
+		read -p "Security code: " -e SECCODEIN
+		if [ "$SECCODE" = "$SECCODEIN" ]; then
+			rm -R "$(getInstancePath "$1")"
+			echo "Done"
+		else
+			echo "Security code did not match, aborting."
+		fi
+	else
+		echo "Instance $1 is currently running. Please stop it first."
+	fi
+}
+
+sdtdCommandInstances() {
+	SUBCMD=$1
+	shift
+	case $SUBCMD in
+		list)
+			sdtdSubcommandInstancesList "$@"
+			;;
+		create)
+			sdtdSubcommandInstancesCreate "$@"
+			;;
+		edit)
+			sdtdSubcommandInstancesEdit "$@"
+			;;
+		delete)
+			sdtdSubcommandInstancesDelete "$@"
+			;;
+		*)
+			sdtdCommandInstancesHelp
+			;;
+	esac
 }
 
 sdtdCommandInstancesHelp() {
-	echo "Usage: $(basename $0) instances"
-	echo
-	echo "List all defined instances and their status."
+	line() {
+		printf "  %-*s %s\n" 19 "$1" "$2"
+	}
+
+	echo "Usage: $(basename $0) instances <subcommand>"
+	echo "Subcommands are:"
+	line "list" "List all defined instances and their status."
+	line "create" "Create a new instance"
+	line "edit <instance>" "Edit an existing instance"
+	line "delete <instance>" "Delete an existing instance"
 }
 
@@ -34,2 +158,18 @@
 	echo "List all defined instances"
 }
+
+sdtdCommandInstancesExpects() {
+	case $1 in
+		2)
+			echo "list create edit delete"
+			;;
+		3)
+			case $2 in
+				edit|delete)
+					echo "$(getInstanceList)"
+					;;
+			esac
+			;;
+	esac
+}
+
Index: scripts/usr/local/lib/7dtd/commands/status.sh
===================================================================
--- scripts/usr/local/lib/7dtd/commands/status.sh	(revision 18)
+++ scripts/usr/local/lib/7dtd/commands/status.sh	(revision 19)
@@ -53,5 +53,4 @@
 
 	echo
-	exit 0
 }
 
Index: scripts/usr/local/lib/7dtd/commands/updateengine.sh
===================================================================
--- scripts/usr/local/lib/7dtd/commands/updateengine.sh	(revision 18)
+++ scripts/usr/local/lib/7dtd/commands/updateengine.sh	(revision 19)
@@ -7,5 +7,5 @@
 	for I in $(getInstanceList); do
 		if [ $(isRunning $I) -eq 1 ]; then
-			echo "At least one instance is still running."
+			echo "At least one instance is still running (\"$I\")."
 			echo "Before updating the engine please stop all instances!"
 			return
Index: scripts/usr/local/lib/7dtd/common.sh
===================================================================
--- scripts/usr/local/lib/7dtd/common.sh	(revision 18)
+++ scripts/usr/local/lib/7dtd/common.sh	(revision 19)
@@ -22,4 +22,18 @@
 }
 
+# Check if the given instance name is valid (no blanks, no special chars,
+# only letters, digits, underscore, hyphen -> [A-Za-z0-9_\-])
+# Params:
+#   1: Instance name
+# Returns:
+#   0/1 instance not valid/valid
+isValidInstanceName() {
+	if [[ "$1" =~ ^[A-Za-z0-9_\-]+$ ]]; then
+		echo 1
+	else
+		echo 0
+	fi
+}
+
 # Check if the given instance name is an existing instance
 # Params:
@@ -28,17 +42,15 @@
 #   0/1 instance not valid/valid
 isValidInstance() {
-	if [ -z $1 ]; then
-		echo 0
-	else
-		if [ ! -d $(getInstancePath $1) ]; then
-			echo 0
-		else
-			if [ ! -f $(getInstancePath $1)/config.xml ]; then
-				echo 0
-			else
-				echo 1
-			fi
-		fi
-	fi
+	if [ ! -z "$1" ]; then
+		if [ $(isValidInstanceName "$1") -eq 1 ]; then
+			if [ -d $(getInstancePath "$1") ]; then
+				if [ -f $(getInstancePath "$1")/config.xml ]; then
+					echo 1
+					return
+				fi
+			fi
+		fi
+	fi
+	echo 0
 }
 
@@ -62,6 +74,7 @@
 #   List of instances
 getInstanceList() {
+	local IF
 	for IF in $SDTD_BASE/instances/*; do
-		I=`basename $IF`
+		local I=`basename $IF`
 		if [ $(isValidInstance $I) -eq 1 ]; then
 			echo $I
@@ -84,40 +97,23 @@
 }
 
-# Get a single value from a serverconfig
-# Params:
-#   1: Instance name
-#   2: Property name
-# Returns:
-#   Property value
-getConfigValue() {
-	CONF=$(getInstancePath $1)/config.xml
-	$XMLSTARLET sel -t -v "/ServerSettings/property[@name='$2']/@value" $CONF
-}
-
-# Update a single value in a serverconfig
-# Params:
-#   1: Instance name
-#   2: Property name
-#   3: New value
-setConfigValue() {
-	CONF=$(getInstancePath $1)/config.xml
-	$XMLSTARLET ed -L -u "/ServerSettings/property[@name='$2']/@value" -v "$3" $CONF
-}
-
 # Check if a given port range (baseport, baseport+1, baseport+2 each udp)
-# is already in use by any instance
+# is already in use by any other instance
 # Params:
 #   1: Baseport
+#   2: Current instance (ignored)
 # Returns:
 #   0/1 not in use/in use
 checkGamePortUsed() {
-	PORTMIN=$1
-	PORTMAX=$(( $1 + 2 ))
+	local PORTMIN=$1
+	local PORTMAX=$(( $1 + 2 ))
+	local I
 	for I in $(getInstanceList); do
-		CURPORTMIN=$(getConfigValue $I "ServerPort")
-		CURPORTMAX=$(( $CURPORTMIN + 2 ))
-		if [ $PORTMAX -ge $CURPORTMIN -a $PORTMIN -le $CURPORTMAX ]; then
-			echo 1
-			return
+		if [ "$2" != "$I" ]; then
+			local CURPORTMIN=$(getConfigValue $I "ServerPort")
+			local CURPORTMAX=$(( $CURPORTMIN + 2 ))
+			if [ $PORTMAX -ge $CURPORTMIN -a $PORTMIN -le $CURPORTMAX ]; then
+				echo 1
+				return
+			fi
 		fi
 	done
@@ -125,16 +121,26 @@
 }
 
-# Check if a given telnet port is already in use by any instance
+# Check if a given TCP port is already in use by any instance (either by control
+# panel or telnet)
 # Params:
 #   1: Port
 # Returns:
 #   0/1 not in use/in use
-checkTelnetPortUsed() {
+checkTCPPortUsed() {
+	local I
 	for I in $(getInstanceList); do
-		CURENABLED=$(getConfigValue $I "TelnetEnabled")
-		CURPORT=$(getConfigValue $I "TelnetPort")
-		if [ "$CURENABLED" = "true" -a $CURPORT -eq $1 ]; then
-			echo 1
-			return
+		if [ "$2" != "$I" ]; then
+			local CURENABLED=$(getConfigValue $I "TelnetEnabled")
+			local CURPORT=$(getConfigValue $I "TelnetPort")
+			if [ "$CURENABLED" = "true" -a $CURPORT -eq $1 ]; then
+				echo 1
+				return
+			fi
+			CURENABLED=$(getConfigValue $I "ControlPanelEnabled")
+			CURPORT=$(getConfigValue $I "ControlPanelPort")
+			if [ "$CURENABLED" = "true" -a $CURPORT -eq $1 ]; then
+				echo 1
+				return
+			fi
 		fi
 	done
@@ -149,7 +155,7 @@
 #   String of telnet output
 telnetCommand() {
-	TEL_ENABLED=$(getConfigValue $1 TelnetEnabled)
-	TEL_PORT=$(getConfigValue $1 TelnetPort)
-	TEL_PASS=$(getConfigValue $1 TelnetPassword)	
+	local TEL_ENABLED=$(getConfigValue $1 TelnetEnabled)
+	local TEL_PORT=$(getConfigValue $1 TelnetPort)
+	local TEL_PASS=$(getConfigValue $1 TelnetPassword)	
 	if [ "$TEL_ENABLED" = "true" ] && [ -n "$TEL_PASS" ]; then
 		echo -e "$TEL_PASS\n$2\nexit" | nc -q 2 127.0.0.1 $TEL_PORT
@@ -166,4 +172,5 @@
 getHooksFor() {
 	if [ -d $SDTD_BASE/hooks/$1 ]; then
+		local H
 		for H in $SDTD_BASE/hooks/$1/*.sh; do
 			echo "$H"
@@ -191,10 +198,58 @@
 }
 
+# Check if given value is a (integer) number
+# Params:
+#   1: Value
+# Returns:
+#   0/1 for NaN / is a number
+isANumber() {
+	if [[ $1 =~ ^[0-9]+$ ]] ; then
+		echo "1"
+	else
+		echo "0"
+	fi
+}
+
+# Check if given value is a boolean (true/false, yes/no, y/n)
+# Params:
+#   1: Value
+# Returns:
+#   0/1
+isABool() {
+	local LOW=$(lowercase "$1")
+	if [ "$LOW" = "false" -o "$LOW" = "true"\
+		-o "$LOW" = "yes" -o "$LOW" = "y"\
+		-o "$LOW" = "no" -o "$LOW" = "n" ]; then
+		echo 1
+	else
+		echo 0
+	fi
+}
+
+# Convert the given value to a boolean 0/1
+# Params:
+#   1: Value
+# Returns:
+#   0/1 as false/true
+getBool() {
+	if [ $(isABool "$1") -eq 0 ]; then
+		echo 0
+	else
+		local LOW=$(lowercase "$1")
+		if [ "$LOW" = "true" -o "$LOW" = "yes" -o "$LOW" = "y" ]; then
+			echo 1
+		else
+			echo 0
+		fi
+	fi
+}
+
 listCommands() {
+	local C
 	for C in $(declare -F | cut -d\  -f3 | grep "^sdtdCommand"\
 			| grep -v "Help$"\
 			| grep -v "Description$"\
 			| grep -v "Expects$"); do
-		CMD=$(lowercase "${C#sdtdCommand}")
+		local CMD=$(lowercase "${C#sdtdCommand}")
 		printf "%s " "$CMD"
 	done
@@ -203,4 +258,5 @@
 . /usr/local/lib/7dtd/help.sh
 . /usr/local/lib/7dtd/playerlog.sh
+. /usr/local/lib/7dtd/serverconfig.sh
 for M in /usr/local/lib/7dtd/commands/*.sh; do
 	. $M
Index: scripts/usr/local/lib/7dtd/serverconfig.sh
===================================================================
--- scripts/usr/local/lib/7dtd/serverconfig.sh	(revision 19)
+++ scripts/usr/local/lib/7dtd/serverconfig.sh	(revision 19)
@@ -0,0 +1,953 @@
+#!/bin/bash
+# Version 4
+
+# Provides functions to query and validate values for serverconfig.xml
+
+serverconfig_ServerPort_QueryName() {
+	echo "Base Port"
+}
+serverconfig_ServerPort_Type() {
+	echo "number"
+}
+serverconfig_ServerPort_Default() {
+	echo "25000"
+}
+serverconfig_ServerPort_Range() {
+	echo "1024-65533"
+}
+serverconfig_ServerPort_Validate() {
+	local I=${INSTANCE:-!}
+	if [ $(checkGamePortUsed "$1" "$I") -eq 0 ]; then
+		echo "1"
+	else
+		echo "0"
+	fi
+}
+serverconfig_ServerPort_ErrorMessage() {
+	echo "Illegal port number or port already in use by another instance."
+}
+
+
+
+serverconfig_ServerIsPublic_QueryName() {
+	echo "Public server"
+}
+serverconfig_ServerIsPublic_Type() {
+	echo "boolean"
+}
+serverconfig_ServerIsPublic_Default() {
+	echo "true"
+}
+serverconfig_ServerIsPublic_ErrorMessage() {
+	echo "Not a valid boolean given (true/false or yes/no or y/n)."
+}
+
+
+
+serverconfig_ServerName_QueryName() {
+	echo "Server name"
+}
+serverconfig_ServerName_Type() {
+	echo "string"
+}
+serverconfig_ServerName_Validate() {
+	if [ ! -z "$1" ]; then
+		echo "1"
+	else
+		echo "0"
+	fi
+}
+serverconfig_ServerName_ErrorMessage() {
+	echo "Server name cannot be empty."
+}
+
+
+
+serverconfig_ServerPassword_QueryName() {
+	echo "Server password"
+}
+serverconfig_ServerPassword_Type() {
+	echo "string"
+}
+
+
+
+serverconfig_ServerMaxPlayerCount_QueryName() {
+	echo "Max players"
+}
+serverconfig_ServerMaxPlayerCount_Type() {
+	echo "number"
+}
+serverconfig_ServerMaxPlayerCount_Default() {
+	echo "4"
+}
+serverconfig_ServerMaxPlayerCount_Range() {
+	echo "1-64"
+}
+
+
+
+serverconfig_GameWorld_QueryName() {
+	echo "World name"
+}
+serverconfig_GameWorld_Type() {
+	echo "enum"
+}
+serverconfig_GameWorld_Default() {
+	echo "1"
+}
+serverconfig_GameWorld_Values() {
+	config_allowed_values=("Navezgane" "MP Wasteland Horde" "MP Wasteland Skirmish" "MP Wasteland War")
+}
+
+
+
+serverconfig_GameName_QueryName() {
+	echo "Game name"
+}
+serverconfig_GameName_Type() {
+	echo "string"
+}
+serverconfig_GameName_Validate() {
+	if [ ! -z "$1" ]; then
+		echo "1"
+	else
+		echo "0"
+	fi
+}
+serverconfig_GameName_ErrorMessage() {
+	echo "Game name cannot be empty."
+}
+
+
+
+serverconfig_GameDifficulty_QueryName() {
+	echo "Difficulty"
+}
+serverconfig_GameDifficulty_Type() {
+	echo "number"
+}
+serverconfig_GameDifficulty_Default() {
+	echo "2"
+}
+serverconfig_GameDifficulty_Range() {
+	echo "0-4"
+}
+serverconfig_GameDifficulty_Values() {
+	config_allowed_values=("Very easy" "Easy" "Medium" "Hard" "Very hard")
+}
+
+
+
+serverconfig_GameMode_QueryName() {
+	echo "Game mode"
+}
+serverconfig_GameMode_Type() {
+	echo "enum"
+}
+serverconfig_GameMode_Default() {
+	echo "1"
+}
+serverconfig_GameMode_Values() {
+	config_allowed_values=("GameModeSurvivalMP" "GameModeSurvivalSP")
+}
+
+
+
+serverconfig_ZombiesRun_QueryName() {
+	echo "Zombies run"
+}
+serverconfig_ZombiesRun_Type() {
+	echo "number"
+}
+serverconfig_ZombiesRun_Default() {
+	echo "0"
+}
+serverconfig_ZombiesRun_Range() {
+	echo "0-2"
+}
+serverconfig_ZombiesRun_Values() {
+	config_allowed_values=("Default day/night walk/run" "Never run" "Always run")
+}
+
+
+
+serverconfig_ShowAllPlayersOnMap_QueryName() {
+	echo "Show all players on map"
+}
+serverconfig_ShowAllPlayersOnMap_Type() {
+	echo "boolean"
+}
+serverconfig_ShowAllPlayersOnMap_Default() {
+	echo "true"
+}
+serverconfig_ShowAllPlayersOnMap_ErrorMessage() {
+	echo "Not a valid boolean given (true/false or yes/no or y/n)."
+}
+
+
+
+serverconfig_BuildCreate_QueryName() {
+	echo "Item spawn menu"
+}
+serverconfig_BuildCreate_Type() {
+	echo "boolean"
+}
+serverconfig_BuildCreate_Default() {
+	echo "true"
+}
+serverconfig_BuildCreate_ErrorMessage() {
+	echo "Not a valid boolean given (true/false or yes/no or y/n)."
+}
+
+
+
+serverconfig_DayNightLength_QueryName() {
+	echo "Length of one day"
+}
+serverconfig_DayNightLength_Type() {
+	echo "number"
+}
+serverconfig_DayNightLength_Default() {
+	echo "45"
+}
+
+
+
+serverconfig_FriendlyFire_QueryName() {
+	echo "Friendly fire"
+}
+serverconfig_FriendlyFire_Type() {
+	echo "boolean"
+}
+serverconfig_FriendlyFire_Default() {
+	echo "false"
+}
+serverconfig_FriendlyFire_ErrorMessage() {
+	echo "Not a valid boolean given (true/false or yes/no or y/n)."
+}
+
+
+
+serverconfig_DayCount_QueryName() {
+	echo "Day Count (Horde)"
+}
+serverconfig_DayCount_Type() {
+	echo "number"
+}
+serverconfig_DayCount_Default() {
+	echo "3"
+}
+
+
+
+serverconfig_FragLimit_QueryName() {
+	echo "Frag limit (DM)"
+}
+serverconfig_FragLimit_Type() {
+	echo "number"
+}
+serverconfig_FragLimit_Default() {
+	echo "5"
+}
+
+
+
+serverconfig_MatchLength_QueryName() {
+	echo "Match length (DM)"
+}
+serverconfig_MatchLength_Type() {
+	echo "number"
+}
+serverconfig_MatchLength_Default() {
+	echo "15"
+}
+
+
+
+serverconfig_RebuildMap_QueryName() {
+	echo "Rebuild map on round restart"
+}
+serverconfig_RebuildMap_Type() {
+	echo "boolean"
+}
+serverconfig_RebuildMap_Default() {
+	echo "false"
+}
+serverconfig_RebuildMap_ErrorMessage() {
+	echo "Not a valid boolean given (true/false or yes/no or y/n)."
+}
+
+
+
+serverconfig_ControlPanelEnabled_QueryName() {
+	echo "Enable control panel"
+}
+serverconfig_ControlPanelEnabled_Type() {
+	echo "boolean"
+}
+serverconfig_ControlPanelEnabled_Default() {
+	echo "false"
+}
+serverconfig_ControlPanelEnabled_ErrorMessage() {
+	echo "Not a valid boolean given (true/false or yes/no or y/n)."
+}
+
+
+
+serverconfig_ControlPanelPort_QueryName() {
+	echo "Control panel port"
+}
+serverconfig_ControlPanelPort_Type() {
+	echo "number"
+}
+serverconfig_ControlPanelPort_Default() {
+	echo "8080"
+}
+serverconfig_ControlPanelPort_Range() {
+	echo "1024-65535"
+}
+serverconfig_ControlPanelPort_Validate() {
+	local I=${INSTANCE:-!}
+	if [ $(checkTCPPortUsed "$1" "$I") -eq 0 ]; then
+		echo "1"
+	else
+		echo "0"
+	fi
+}
+serverconfig_ControlPanelPort_ErrorMessage() {
+	echo "Illegal port number or port already in use by another instance."
+}
+
+
+
+serverconfig_ControlPanelPassword_QueryName() {
+	echo "Control panel password"
+}
+serverconfig_ControlPanelPassword_Type() {
+	echo "string"
+}
+
+
+
+serverconfig_TelnetPort_QueryName() {
+	echo "Telnet port"
+}
+serverconfig_TelnetPort_Type() {
+	echo "number"
+}
+serverconfig_TelnetPort_Default() {
+	echo "8081"
+}
+serverconfig_TelnetPort_Range() {
+	echo "1024-65535"
+}
+serverconfig_TelnetPort_Validate() {
+	local I=${INSTANCE:-!}
+	if [ $(checkTCPPortUsed "$1" "$I") -eq 0 ]; then
+		echo "1"
+	else
+		echo "0"
+	fi
+}
+serverconfig_TelnetPort_ErrorMessage() {
+	echo "Illegal port number or port already in use by another instance."
+}
+
+
+
+serverconfig_TelnetPassword_QueryName() {
+	echo "Telnet password"
+}
+serverconfig_TelnetPassword_Type() {
+	echo "string"
+}
+serverconfig_TelnetPassword_Validate() {
+	if [ -z $1 ]; then
+		echo "0"
+	else
+		echo "1"
+	fi
+}
+serverconfig_TelnetPassword_ErrorMessage() {
+	echo "Telnet must have a password set to function."
+}
+
+
+
+serverconfig_DisableNAT_QueryName() {
+	echo "Disable NAT"
+}
+serverconfig_DisableNAT_Type() {
+	echo "boolean"
+}
+serverconfig_DisableNAT_Default() {
+	echo "true"
+}
+serverconfig_DisableNAT_ErrorMessage() {
+	echo "Not a valid boolean given (true/false or yes/no or y/n)."
+}
+
+
+
+
+serverconfig_DropOnDeath_QueryName() {
+	echo "Drop on Death"
+}
+serverconfig_DropOnDeath_Type() {
+	echo "number"
+}
+serverconfig_DropOnDeath_Default() {
+	echo "0"
+}
+serverconfig_DropOnDeath_Range() {
+	echo "0-3"
+}
+serverconfig_DropOnDeath_Values() {
+	config_allowed_values=("Everything" "Toolbelt only" "Backpack only" "Delete all")
+}
+
+
+serverconfig_DropOnQuit_QueryName() {
+	echo "Drop on Quit"
+}
+serverconfig_DropOnQuit_Type() {
+	echo "number"
+}
+serverconfig_DropOnQuit_Default() {
+	echo "0"
+}
+serverconfig_DropOnQuit_Range() {
+	echo "0-3"
+}
+serverconfig_DropOnQuit_Values() {
+	config_allowed_values=("Nothing" "Everything" "Toolbelt only" "Backpack only")
+}
+
+
+
+
+serverconfig_CraftTimer_QueryName() {
+	echo "Craft speed"
+}
+serverconfig_CraftTimer_Type() {
+	echo "number"
+}
+serverconfig_CraftTimer_Default() {
+	echo "1"
+}
+serverconfig_CraftTimer_Range() {
+	echo "0-2"
+}
+serverconfig_CraftTimer_Values() {
+	config_allowed_values=("Instant" "Normal" "Double")
+}
+
+
+serverconfig_LootTimer_QueryName() {
+	echo "Loot open speed"
+}
+serverconfig_LootTimer_Type() {
+	echo "number"
+}
+serverconfig_LootTimer_Default() {
+	echo "1"
+}
+serverconfig_LootTimer_Range() {
+	echo "0-2"
+}
+serverconfig_LootTimer_Values() {
+	config_allowed_values=("Instant" "Normal" "Double")
+}
+
+
+
+
+serverconfig_PlayerDamageGiven_QueryName() {
+	echo "Damage Player->Zombies"
+}
+serverconfig_PlayerDamageGiven_Type() {
+	echo "number"
+}
+serverconfig_PlayerDamageGiven_Default() {
+	echo "2"
+}
+serverconfig_PlayerDamageGiven_Range() {
+	echo "0-4"
+}
+serverconfig_PlayerDamageGiven_Values() {
+	config_allowed_values=("50%" "75%" "100%" "150%" "200%")
+}
+
+
+serverconfig_PlayerDamageRecieved_QueryName() {
+	echo "Damage Zombies->Player"
+}
+serverconfig_PlayerDamageRecieved_Type() {
+	echo "number"
+}
+serverconfig_PlayerDamageRecieved_Default() {
+	echo "2"
+}
+serverconfig_PlayerDamageRecieved_Range() {
+	echo "0-4"
+}
+serverconfig_PlayerDamageRecieved_Values() {
+	config_allowed_values=("50%" "75%" "100%" "150%" "200%")
+}
+
+
+serverconfig_EnemySenseMemory_QueryName() {
+	echo "Sense memory (seconds)"
+}
+serverconfig_EnemySenseMemory_Type() {
+	echo "number"
+}
+serverconfig_EnemySenseMemory_Default() {
+	echo "60"
+}
+
+
+serverconfig_EnemySpawnMode_QueryName() {
+	echo "Spawn mode"
+}
+serverconfig_EnemySpawnMode_Type() {
+	echo "number"
+}
+serverconfig_EnemySpawnMode_Default() {
+	echo "3"
+}
+serverconfig_EnemySpawnMode_Range() {
+	echo "0-5"
+}
+serverconfig_EnemySpawnMode_Values() {
+	config_allowed_values=("Disabled" "50%" "75%" "100%" "125%" "150%")
+}
+
+
+serverconfig_EnemyDifficulty_QueryName() {
+	echo "Enemy difficulty"
+}
+serverconfig_EnemyDifficulty_Type() {
+	echo "number"
+}
+serverconfig_EnemyDifficulty_Default() {
+	echo "0"
+}
+serverconfig_EnemyDifficulty_Range() {
+	echo "0-1"
+}
+serverconfig_EnemyDifficulty_Values() {
+	config_allowed_values=("Normal" "Feral")
+}
+
+
+
+
+serverconfig_NightPercentage_QueryName() {
+	echo "Night percentage"
+}
+serverconfig_NightPercentage_Type() {
+	echo "number"
+}
+serverconfig_NightPercentage_Default() {
+	echo "35"
+}
+serverconfig_NightPercentage_Range() {
+	echo "10-90"
+}
+
+
+
+serverconfig_BlockDurabilityModifier_QueryName() {
+	echo "Block durability (%)"
+}
+serverconfig_BlockDurabilityModifier_Type() {
+	echo "number"
+}
+serverconfig_BlockDurabilityModifier_Default() {
+	echo "100"
+}
+
+
+
+
+serverconfig_LootAbundance_QueryName() {
+	echo "Loot abundance (%)"
+}
+serverconfig_LootAbundance_Type() {
+	echo "number"
+}
+serverconfig_LootAbundance_Default() {
+	echo "100"
+}
+
+
+serverconfig_LootRespawnDays_QueryName() {
+	echo "Loot respawn delay (days)"
+}
+serverconfig_LootRespawnDays_Type() {
+	echo "number"
+}
+serverconfig_LootRespawnDays_Default() {
+	echo "7"
+}
+
+
+
+
+serverconfig_LandClaimSize_QueryName() {
+	echo "Land claim size"
+}
+serverconfig_LandClaimSize_Type() {
+	echo "number"
+}
+serverconfig_LandClaimSize_Default() {
+	echo "7"
+}
+
+
+serverconfig_LandClaimDeadZone_QueryName() {
+	echo "Minimum keystone distance"
+}
+serverconfig_LandClaimDeadZone_Type() {
+	echo "number"
+}
+serverconfig_LandClaimDeadZone_Default() {
+	echo "30"
+}
+
+
+serverconfig_LandClaimExpiryTime_QueryName() {
+	echo "Claim expiry time (days)"
+}
+serverconfig_LandClaimExpiryTime_Type() {
+	echo "number"
+}
+serverconfig_LandClaimExpiryTime_Default() {
+	echo "3"
+}
+
+
+serverconfig_LandClaimDecayMode_QueryName() {
+	echo "Claim decay mode"
+}
+serverconfig_LandClaimDecayMode_Type() {
+	echo "number"
+}
+serverconfig_LandClaimDecayMode_Default() {
+	echo "0"
+}
+serverconfig_LandClaimDecayMode_Range() {
+	echo "0-2"
+}
+serverconfig_LandClaimDecayMode_Values() {
+	config_allowed_values=("Linear" "Exponential" "Full protection")
+}
+
+
+serverconfig_LandClaimOnlineDurabilityModifier_QueryName() {
+	echo "Claim durability modifier - online"
+}
+serverconfig_LandClaimOnlineDurabilityModifier_Type() {
+	echo "number"
+}
+serverconfig_LandClaimOnlineDurabilityModifier_Default() {
+	echo "4"
+}
+
+
+serverconfig_LandClaimOfflineDurabilityModifier_QueryName() {
+	echo "Claim durability modifier - offline"
+}
+serverconfig_LandClaimOfflineDurabilityModifier_Type() {
+	echo "number"
+}
+serverconfig_LandClaimOfflineDurabilityModifier_Default() {
+	echo "4"
+}
+
+
+
+
+serverconfig_AirDropFrequency_QueryName() {
+	echo "Airdrop delay (hours)"
+}
+serverconfig_AirDropFrequency_Type() {
+	echo "number"
+}
+serverconfig_AirDropFrequency_Default() {
+	echo "24"
+}
+
+
+
+
+############
+listConfigValues() {
+	local CV
+	for CV in $(declare -F | cut -d\  -f3 | grep "^serverconfig_.*_Type$"); do
+		CV=${CV#serverconfig_}
+		CV=${CV%_Type}
+		printf "%s " "$CV"
+	done
+}
+
+
+#  1: Option name
+#  2: Value
+isValidOptionValue() {
+	local TYPE=$(serverconfig_$1_Type)
+	local RANGE=""
+
+	if [ "$TYPE" = "enum" ]; then
+		TYPE="number"
+		serverconfig_$1_Values
+		RANGE=1-${#config_allowed_values[@]}
+	else
+		if [ "$(type -t serverconfig_$1_Range)" = "function" ]; then
+			RANGE=$(serverconfig_$1_Range)
+		fi
+	fi
+
+	case "$TYPE" in
+		number)
+			if [ $(isANumber "$2") -eq 0 ]; then
+				echo "0"
+				return
+			fi
+			if [ ! -z "$RANGE" ]; then
+				local MIN=$(cut -d- -f1 <<< "$RANGE")
+				local MAX=$(cut -d- -f2 <<< "$RANGE")
+				if [ $2 -lt $MIN -o $2 -gt $MAX ]; then
+					echo "0"
+					return
+				fi
+			fi
+			;;
+		boolean)
+			if [ $(isABool "$2") -eq 0 ]; then
+				echo "0"
+				return
+			fi
+			;;
+		string)
+			;;
+	esac
+	
+
+	if [ "$(type -t serverconfig_$1_Validate)" = "function" ]; then
+		if [ $(serverconfig_$1_Validate "$2") -eq 0 ]; then
+			echo "0"
+			return
+		fi
+	fi
+	
+	echo "1"
+}
+
+#  1: Option name
+#  2: Target variable
+configQueryValue() {
+	local TYPE=$(serverconfig_$1_Type)
+	local NAME=""
+	local RANGE=""
+	local DEFAULT=""
+	local currentValName=configCurrent_$1
+
+	if [ "$(type -t serverconfig_$1_Values)" = "function" ]; then
+		echo "$(serverconfig_$1_QueryName), options:"
+		serverconfig_$1_Values
+		NAME="Select option"
+		if [ "$TYPE" = "enum" ]; then
+			local OPTOFFSET=1
+		else
+			local OPTOFFSET=0
+		fi
+		for (( i=$OPTOFFSET; i < ${#config_allowed_values[@]}+$OPTOFFSET; i++ )); do
+			printf "  %2d: %s\n" $i "${config_allowed_values[$i-$OPTOFFSET]}"
+		done
+	else
+		NAME=$(serverconfig_$1_QueryName)
+	fi
+
+	if [ "$TYPE" = "enum" ]; then
+		RANGE=1-${#config_allowed_values[@]}
+		if [ ! -z "${!currentValName}" ]; then
+			for (( i=1; i < ${#config_allowed_values[@]}+1; i++ )); do
+				if [ "${!currentValName}" = "${config_allowed_values[$i-1]}" ]; then
+					DEFAULT=$i
+				fi
+			done
+			export $currentValName=
+		fi
+	else
+		if [ "$(type -t serverconfig_$1_Range)" = "function" ]; then
+			RANGE=$(serverconfig_$1_Range)
+		fi
+	fi
+
+	if [ -z "$DEFAULT" ]; then
+		if [ ! -z "${!currentValName}" ]; then
+			DEFAULT=${!currentValName}
+		else
+			if [ "$(type -t serverconfig_$1_Default)" = "function" ]; then
+				DEFAULT=$(serverconfig_$1_Default)
+			fi
+		fi
+	fi
+
+	local prompt=$(printf "%s" "$NAME")
+	if [ ! -z "$RANGE" ]; then
+		prompt=$(printf "%s (%s)" "$prompt" "$RANGE")
+	fi
+	if [ ! -z "$DEFAULT" ]; then
+		prompt=$(printf "%s [%s]" "$prompt" "$DEFAULT")
+	fi
+	prompt=$(printf "%s:" "$prompt")
+	prompt=$(printf "%-*s " 35 "$prompt")
+
+	while : ; do
+		read -p "$prompt" $currentValName
+		export $currentValName="${!currentValName:-$DEFAULT}"
+		if [ $(isValidOptionValue "$1" "${!currentValName}") -eq 0 ]; then
+			if [ "$(type -t serverconfig_$1_ErrorMessage)" = "function" ]; then
+				serverconfig_$1_ErrorMessage "${!currentValName}"
+			fi
+		fi
+		[ $(isValidOptionValue "$1" "${!currentValName}") -eq 1 ] && break
+	done
+	
+	if [ "$TYPE" = "enum" ]; then
+		export $currentValName="${config_allowed_values[$currentValName-1]}"
+	fi
+}
+
+#   1: Instance name
+configSetAutoParameters() {
+	configCurrent_TelnetEnabled=true
+	configCurrent_AdminFileName=admins.xml
+	configCurrent_SaveGameFolder="$(winepath -w $(getInstancePath "$1") 2>/dev/null | grep instances)"
+}
+
+configEditAll() {
+	local CV
+	for CV in \
+			ServerName ServerPort ServerIsPublic ServerPassword ServerMaxPlayerCount \
+			DisableNAT \
+			ControlPanelEnabled ControlPanelPort ControlPanelPassword \
+			TelnetPort TelnetPassword \
+			GameWorld GameName GameMode \
+			ShowAllPlayersOnMap FriendlyFire BuildCreate \
+			DayCount FragLimit MatchLength RebuildMap \
+			DropOnDeath DropOnQuit \
+			LootAbundance LootRespawnDays AirDropFrequency \
+			CraftTimer LootTimer \
+			DayNightLength NightPercentage \
+			GameDifficulty ZombiesRun \
+			PlayerDamageGiven PlayerDamageRecieved EnemySenseMemory EnemySpawnMode EnemyDifficulty \
+			BlockDurabilityModifier \
+			LandClaimSize LandClaimDeadZone LandClaimExpiryTime LandClaimDecayMode \
+			LandClaimOnlineDurabilityModifier LandClaimOfflineDurabilityModifier \
+			; do
+		configQueryValue $CV
+		echo
+	done
+}
+
+printCurrentConfig() {
+	local CV
+	for CV in $(listConfigValues); do
+		local currentValName=configCurrent_$CV
+		printf "%-20s = %s\n" "$CV" "${!currentValName}"
+	done
+}
+
+readInstanceName() {
+	until [ $(isValidInstanceName "$INSTANCE") -eq 1 ]; do
+		read -p "Instance name: " INSTANCE
+		if [ $(isValidInstanceName "$INSTANCE") -eq 0 ]; then
+			echo "Invalid instance name, may only contain:"
+			echo " - letters (A-Z / a-z)"
+			echo " - digits (0-9)"
+			echo " - underscores (_)"
+			echo " - hyphens (-)"
+		fi
+	done
+}
+
+unsetAllConfigValues() {
+	local CV
+	for CV in $(listConfigValues); do
+		local currentValName=configCurrent_$CV
+		export $currentValName=
+	done
+}
+
+#   1: Instance name
+loadCurrentConfigValues() {
+	local CV
+	for CV in $(listConfigValues); do
+		local currentValName=configCurrent_$CV
+		local cfile=$(getInstancePath "$1")/config.xml
+		local XPATH="/ServerSettings/property[@name='$CV']/@value"
+		local VAL=$($XMLSTARLET sel -t -v "$XPATH" $cfile)
+		if [ ! -z "$VAL" ]; then
+			export $currentValName="$VAL"
+		fi
+	done
+}
+
+#   1: Instance name
+saveCurrentConfigValues() {
+	local CV
+	for CV in $(listConfigValues) TelnetEnabled AdminFileName SaveGameFolder; do
+		local currentValName=configCurrent_$CV
+		local val="${!currentValName}"
+		local cfile=$(getInstancePath "$1")/config.xml
+
+		XPATHBASE="/ServerSettings/property[@name='$CV']"
+
+		if [ -z $($XMLSTARLET sel -t -v "$XPATHBASE/@name" $cfile) ]; then
+			$XMLSTARLET ed -L \
+				-s "/ServerSettings" -t elem -n "property" -v "" \
+				-i "/ServerSettings/property[not(@name)]" -t attr -n "name" -v "$CV" \
+				-i "$XPATHBASE" -t attr -n "value" -v "$val" \
+				$cfile
+		else
+			$XMLSTARLET ed -L \
+				-u "$XPATHBASE/@value" -v "$val" \
+				$cfile
+		fi
+	done
+}
+
+configTemplateExists() {
+	if [ -f $SDTD_BASE/templates/config.xml ]; then
+		echo 1
+	else
+		echo 0
+	fi
+}
+
+# Get a single value from a serverconfig
+# Params:
+#   1: Instance name
+#   2: Property name
+# Returns:
+#   Property value
+getConfigValue() {
+	local CONF=$(getInstancePath $1)/config.xml
+	$XMLSTARLET sel -t -v "/ServerSettings/property[@name='$2']/@value" $CONF
+}
+
+# Update a single value in a serverconfig
+# Params:
+#   1: Instance name
+#   2: Property name
+#   3: New value
+setConfigValue() {
+	local CONF=$(getInstancePath $1)/config.xml
+	$XMLSTARLET ed -L -u "/ServerSettings/property[@name='$2']/@value" -v "$3" $CONF
+}
+
