diff --git a/.gitignore b/.gitignore
index 78ab6fe4..1304c24a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,12 +22,12 @@
build/
enet/build/
venv/
+env/
-# feature server
-feature_server/logs/
-feature_server/data/
-feature_server/config.txt
-feature_server/maps/*.txtc
+# config/userdata
+configs/maps/*.txtc
+configs/logs/
+configs/data/
# py2exe
py2exe/dist/
diff --git a/.travis.yml b/.travis.yml
index 9fc5e879..e9487b3e 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,13 +4,7 @@ python:
- 2.7
install:
- - pip install cython
- - pip install twisted
- - pip install jinja2
- - pip install pillow
- - pip install pygeoip
- - pip install pycrypto
- - pip install pyasn1
+ - pip install -r requirements.txt
script:
- - sh build.sh
\ No newline at end of file
+ - sh build.sh
diff --git a/README.md b/README.md
index fdc965f3..c88e923f 100644
--- a/README.md
+++ b/README.md
@@ -1,60 +1,74 @@
-![PySnip](http://i.imgur.com/QFgqcRM.png)
-
-[![Build Status](https://travis-ci.org/NateShoffner/PySnip.svg?branch=master)](https://travis-ci.org/NateShoffner/PySnip)
-
-PySnip is an robust, open-source and cross-platform server implementation for [Ace of Spades](http://buildandshoot.com). It is fully customizable with extensions and scripts.
-
-### Features ###
-
-* Many administrator features
-* A lot of epic commands
-* A remote console (using SSH)
-* Map rotation
-* Map metadata (name, version, author, and map configuration)
-* Map extensions (water damage, etc.)
-* A map generator
-* An IRC client for managing your server
-* A JSON query webserver
-* A status server with map overview
-* Server/map scripts
-* Airstrikes
-* Melee attacks with the pickaxe
-* New gamemodes (deathmatch / runningman)
-* Rollback feature (rolling back to the original map)
-* Spectator mode
-* Dirt grenades
-* Platforms with buttons
-* Ban subscribe service
-* A ton of other features
-
-### Installing ###
-#### For windows ####
-Go to [releases](https://github.com/NateShoffner/PySnip/releases) and download desired version.
-
-#### For linux ####
-Grab it from repo.
-```bash
-git clone https://github.com/NateShoffner/PySnip
-cd PySnip
-```
-Create virtualenv
-```bash
-virtualenv -p python2 venv
-source ./venv/bin/activate
-```
-Install dependencies
-```bash
-pip install cython twisted jinja2 pillow pygeoip pycrypto pyasn1
-```
-Compile
-```bash
-./build.sh
-```
-Run
-```bash
-./run_server.sh
-```
-
-### Support ###
-
-Feel free to post a question on the [forums](http://buildandshoot.com/viewforum.php?f=19) if you need any help or hop onto [IRC](http://webchat.quakenet.org/?channels=%23buildandshoot) to to chat.
+![PySnip](http://i.imgur.com/QFgqcRM.png)
+
+[![Build Status](https://travis-ci.org/NateShoffner/PySnip.svg?branch=master)](https://travis-ci.org/NateShoffner/PySnip)
+
+PySnip is an robust, open-source and cross-platform server implementation for [Ace of Spades](http://buildandshoot.com). It is fully customizable with extensions and scripts.
+
+### Features ###
+
+* Many administrator features
+* A lot of epic commands
+* A remote console (using SSH)
+* Map rotation
+* Map metadata (name, version, author, and map configuration)
+* Map extensions (water damage, etc.)
+* A map generator
+* An IRC client for managing your server
+* A JSON query webserver
+* A status server with map overview
+* Server/map scripts
+* Airstrikes
+* Melee attacks with the pickaxe
+* New gamemodes (deathmatch / runningman)
+* Rollback feature (rolling back to the original map)
+* Spectator mode
+* Dirt grenades
+* Platforms with buttons
+* Ban subscribe service
+* A ton of other features
+
+### Installing ###
+#### For windows ####
+Go to [releases](https://github.com/NateShoffner/PySnip/releases) and download desired version.
+
+#### For linux ####
+Grab it from repo.
+```bash
+git clone https://github.com/NateShoffner/PySnip
+cd PySnip
+```
+Create virtualenv
+```bash
+virtualenv -p python2 venv
+source ./venv/bin/activate
+```
+Install dependencies
+```bash
+pip install cython twisted jinja2 pillow pygeoip pycrypto pyasn1
+```
+Compile
+```bash
+./build.sh
+```
+Run
+```bash
+./run_server.sh
+```
+
+### FAQs
+
+#### GeoLiteCity.dat error
+
+If you get the error: `('from' command disabled - missing data/GeoLiteCity.dat)` in the server log, you will need to
+download a `GeoLiteCity.dat` file into the data directory in your config directory.
+
+A script is included to automate this - run the following:
+
+```
+cd ~/.pysnip/
+/path/to/pysnip/feature_server/update_geoip.py
+```
+
+### Support ###
+
+Feel free to post a question on the [forums](http://buildandshoot.com/viewforum.php?f=19) if you need any help or hop onto [IRC](http://webchat.quakenet.org/?channels=%23buildandshoot) to chat.
diff --git a/configs/README.md b/configs/README.md
new file mode 100644
index 00000000..4714f034
--- /dev/null
+++ b/configs/README.md
@@ -0,0 +1,25 @@
+# Config directory
+
+Structure as follows:
+
+```
+.
+├── config.json # default config file if no file specified
+├── config.json.default # backup config file (same as config.json as supplied)
+├── logs # logs directory
+│ └── log.txt # default server logfile
+├── maps # maps in .txt generate script form or .vxl data
+│ ├── classicgen.txt # includes a couple of simple maps - add any maps you want to use here
+│ └── ...
+└── scripts # contains all scripts and extensions - load by adding name to script list in config
+ ├── __init__.py # don't delete this - needed so python can import scripts for this directory
+ ├── afk.py # huge number of scripts included - see each file for more information, instructions, and attributions
+ ├── aimblock.py # place any other scripts you want to use in this directory
+ ├── aimbot2.py
+ ├── airstrike.py
+ ├── antijerk.py
+ ├── arena.py
+ ├── autohelp.py
+ └── ...
+```
+
diff --git a/configs/config.json b/configs/config.json
new file mode 100644
index 00000000..f93ba1f1
--- /dev/null
+++ b/configs/config.json
@@ -0,0 +1,146 @@
+{
+ "name" : "PySnip server",
+ "motd" : [
+ "Welcome to %(server_name)s",
+ "Map: %(map_name)s by %(map_author)s",
+ "Game mode: %(game_mode)s",
+ "Server powered by PySnip and BuildAndShoot.com"
+ ],
+ "help" : [
+ "Server name: %(server_name)s",
+ "Map: %(map_name)s by %(map_author)s",
+ "Game mode: %(game_mode)s",
+ "/STREAK Shows how many kills in a row you got without dying",
+ "/INTEL Tells you who's got the enemy intel",
+ "/VOTEKICK Start a vote to temporarily ban a disruptive player",
+ "/TIME Remaining time until forced map reset"
+ ],
+ "tips" : [
+ "You are playing %(game_mode)s on %(server_name)s",
+ "Type /help for info & commands"
+ ],
+ "tip_frequency" : 5,
+ "rules" : [
+ "Cheating isn't welcome. Griefing is frowned upon. Have fun!"
+ ],
+ "master" : false,
+ "max_players" : 32,
+ "max_connections_per_ip" : 3,
+ "port" : 32887,
+ "network_interface" : "",
+
+ "game_mode" : "ctf",
+ "cap_limit" : 10,
+ "default_time_limit" : 120,
+ "advance_on_win" : true,
+ "maps" : ["classicgen", "random"],
+ "random_rotation" : false,
+
+ "respawn_time" : 16,
+ "respawn_waves" : true,
+ "friendly_fire" : "on_grief",
+ "grief_friendly_fire_time" : 5,
+ "spade_teamkills_on_grief" : false,
+ "balanced_teams" : 2,
+ "teamswitch_interval" : 0,
+
+ "speedhack_detect" : false,
+ "votekick_percentage" : 35,
+ "votekick_ban_duration" : 30,
+ "votekick_public_votes" : true,
+ "votemap_public_votes" : true,
+ "votemap_extension_time" : 15,
+ "votemap_player_driven" : false,
+ "votemap_autoschedule" : false,
+ "votemap_time" : 120,
+ "votemap_percentage" : 80,
+
+ "melee_damage" : 80,
+ "fall_damage" : true,
+ "user_blocks_only" : false,
+ "set_god_build" : false,
+ "server_prefix" : "",
+ "time_announcements" : [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 30, 60, 120, 180,
+ 240, 300, 600, 900, 1200, 1800, 2400, 3000],
+ "login_retries" : 3,
+ "default_ban_duration" : 1440,
+
+ "logfile" : "./logs/log.txt",
+ "rotate_daily" : true,
+ "debug_log" : false,
+ "profile" : false,
+
+ "team1" : {
+ "name" : "Blue",
+ "color" : [0, 0, 255]
+ },
+ "team2" : {
+ "name" : "Green",
+ "color" : [0, 255, 0]
+ },
+ "passwords" : {
+ "admin" : ["adminpass1", "adminpass2", "adminpass3"],
+ "moderator" : ["modpass"],
+ "guard" : ["guardpass"],
+ "trusted" : ["trustedpass"]
+ },
+ "rights" : {
+ "moderator" : ["advance", "cancel", "dban", "fog", "from", "goto", "hackinfo", "hban", "invisible", "ip", "kick", "kickafk", "kill", "map", "master", "move", "mute", "resetgame", "switch", "teleport", "teleport_other", "tpsilent", "togglebuild", "togglekill", "togglevotekick", "trust", "undoban", "unmute", "unstick", "where", "whowas"],
+ "guard" : ["cancel", "fog", "from", "goto", "hackinfo", "hban", "ip", "kick", "kickafk", "kill", "move", "mute", "switch", "teleport", "teleport_other", "togglebuild", "togglekill", "togglevotekick", "trust", "unmute", "unstick", "where", "whowas"]
+ },
+ "ssh" : {
+ "enabled" : false,
+ "port" : 32887,
+ "users" : {
+ "user" : "ssh_pass_change_this"
+ }
+ },
+ "status_server" : {
+ "enabled" : true,
+ "port" : 32886,
+ "logging" : true
+ },
+ "ban_publish" : {
+ "enabled" : false,
+ "port" : 32885
+ },
+ "ban_subscribe" : {
+ "enabled" : true,
+ "urls" : [
+ ["http://www.blacklist.spadille.net/subscribe.json", []]
+ ]
+ },
+ "irc" : {
+ "enabled" : false,
+ "nickname" : "PySnip",
+ "username" : "PySnip",
+ "realname" : "PySnip",
+ "server" : "irc.quakenet.org",
+ "port" : 6667,
+ "channel" : "#MyServerChannel",
+ "password" : "",
+ "commandprefix" : "!",
+ "chatprefix" : "."
+ },
+ "scripts" : [
+ "rollback",
+ "protect",
+ "map_extensions",
+ "disco",
+ "votekick",
+ "trusted",
+ "ratio",
+ "passreload",
+ "blockinfo",
+ "bugfix",
+ "fbpatch",
+ "afk"
+ ],
+
+ "squad_respawn_time" : 32,
+ "squad_size" : 4,
+ "auto_squad" : false,
+ "load_saved_map" : false,
+ "rollback_on_game_end" : false,
+ "afk_time_limit" : 30
+}
diff --git a/feature_server/config.txt.default b/configs/config.json.default
similarity index 100%
rename from feature_server/config.txt.default
rename to configs/config.json.default
diff --git a/feature_server/maps/classicgen.txt b/configs/maps/classicgen.txt
similarity index 100%
rename from feature_server/maps/classicgen.txt
rename to configs/maps/classicgen.txt
diff --git a/feature_server/maps/random.txt b/configs/maps/random.txt
similarity index 100%
rename from feature_server/maps/random.txt
rename to configs/maps/random.txt
diff --git a/feature_server/scripts/__init__.py b/configs/scripts/__init__.py
similarity index 100%
rename from feature_server/scripts/__init__.py
rename to configs/scripts/__init__.py
diff --git a/feature_server/scripts/afk.py b/configs/scripts/afk.py
similarity index 100%
rename from feature_server/scripts/afk.py
rename to configs/scripts/afk.py
diff --git a/contrib/scripts/aimblock.py b/configs/scripts/aimblock.py
similarity index 100%
rename from contrib/scripts/aimblock.py
rename to configs/scripts/aimblock.py
diff --git a/contrib/scripts/aimbot2.py b/configs/scripts/aimbot2.py
similarity index 100%
rename from contrib/scripts/aimbot2.py
rename to configs/scripts/aimbot2.py
diff --git a/feature_server/scripts/airstrike.py b/configs/scripts/airstrike.py
similarity index 100%
rename from feature_server/scripts/airstrike.py
rename to configs/scripts/airstrike.py
diff --git a/feature_server/scripts/antijerk.py b/configs/scripts/antijerk.py
similarity index 100%
rename from feature_server/scripts/antijerk.py
rename to configs/scripts/antijerk.py
diff --git a/contrib/scripts/arena.py b/configs/scripts/arena.py
similarity index 100%
rename from contrib/scripts/arena.py
rename to configs/scripts/arena.py
diff --git a/feature_server/scripts/autohelp.py b/configs/scripts/autohelp.py
similarity index 100%
rename from feature_server/scripts/autohelp.py
rename to configs/scripts/autohelp.py
diff --git a/contrib/scripts/babel.py b/configs/scripts/babel.py
similarity index 100%
rename from contrib/scripts/babel.py
rename to configs/scripts/babel.py
diff --git a/contrib/scripts/badmin.py b/configs/scripts/badmin.py
similarity index 100%
rename from contrib/scripts/badmin.py
rename to configs/scripts/badmin.py
diff --git a/feature_server/scripts/blockinfo.py b/configs/scripts/blockinfo.py
similarity index 100%
rename from feature_server/scripts/blockinfo.py
rename to configs/scripts/blockinfo.py
diff --git a/feature_server/scripts/bugfix.py b/configs/scripts/bugfix.py
similarity index 100%
rename from feature_server/scripts/bugfix.py
rename to configs/scripts/bugfix.py
diff --git a/feature_server/scripts/commandhelp.py b/configs/scripts/commandhelp.py
similarity index 100%
rename from feature_server/scripts/commandhelp.py
rename to configs/scripts/commandhelp.py
diff --git a/feature_server/scripts/daycycle.py b/configs/scripts/daycycle.py
similarity index 100%
rename from feature_server/scripts/daycycle.py
rename to configs/scripts/daycycle.py
diff --git a/feature_server/scripts/demolitionman.py b/configs/scripts/demolitionman.py
similarity index 100%
rename from feature_server/scripts/demolitionman.py
rename to configs/scripts/demolitionman.py
diff --git a/feature_server/scripts/dirtnade.py b/configs/scripts/dirtnade.py
similarity index 100%
rename from feature_server/scripts/dirtnade.py
rename to configs/scripts/dirtnade.py
diff --git a/feature_server/scripts/disco.py b/configs/scripts/disco.py
similarity index 100%
rename from feature_server/scripts/disco.py
rename to configs/scripts/disco.py
diff --git a/contrib/scripts/dynfog.py b/configs/scripts/dynfog.py
similarity index 100%
rename from contrib/scripts/dynfog.py
rename to configs/scripts/dynfog.py
diff --git a/feature_server/scripts/fbpatch.py b/configs/scripts/fbpatch.py
similarity index 100%
rename from feature_server/scripts/fbpatch.py
rename to configs/scripts/fbpatch.py
diff --git a/feature_server/scripts/flagreturn.py b/configs/scripts/flagreturn.py
similarity index 100%
rename from feature_server/scripts/flagreturn.py
rename to configs/scripts/flagreturn.py
diff --git a/contrib/scripts/freeforall.py b/configs/scripts/freeforall.py
similarity index 100%
rename from contrib/scripts/freeforall.py
rename to configs/scripts/freeforall.py
diff --git a/feature_server/scripts/grownade.py b/configs/scripts/grownade.py
similarity index 100%
rename from feature_server/scripts/grownade.py
rename to configs/scripts/grownade.py
diff --git a/feature_server/scripts/infiltration.py b/configs/scripts/infiltration.py
similarity index 100%
rename from feature_server/scripts/infiltration.py
rename to configs/scripts/infiltration.py
diff --git a/feature_server/scripts/map_extensions.py b/configs/scripts/map_extensions.py
similarity index 100%
rename from feature_server/scripts/map_extensions.py
rename to configs/scripts/map_extensions.py
diff --git a/contrib/scripts/mapmakingtools.py b/configs/scripts/mapmakingtools.py
similarity index 100%
rename from contrib/scripts/mapmakingtools.py
rename to configs/scripts/mapmakingtools.py
diff --git a/feature_server/scripts/markers.py b/configs/scripts/markers.py
similarity index 100%
rename from feature_server/scripts/markers.py
rename to configs/scripts/markers.py
diff --git a/feature_server/scripts/match.py b/configs/scripts/match.py
similarity index 100%
rename from feature_server/scripts/match.py
rename to configs/scripts/match.py
diff --git a/feature_server/scripts/medkit.py b/configs/scripts/medkit.py
similarity index 100%
rename from feature_server/scripts/medkit.py
rename to configs/scripts/medkit.py
diff --git a/feature_server/scripts/memcheck.py b/configs/scripts/memcheck.py
similarity index 100%
rename from feature_server/scripts/memcheck.py
rename to configs/scripts/memcheck.py
diff --git a/feature_server/scripts/minefield.py b/configs/scripts/minefield.py
similarity index 100%
rename from feature_server/scripts/minefield.py
rename to configs/scripts/minefield.py
diff --git a/contrib/scripts/onectf.py b/configs/scripts/onectf.py
similarity index 100%
rename from contrib/scripts/onectf.py
rename to configs/scripts/onectf.py
diff --git a/feature_server/scripts/paint.py b/configs/scripts/paint.py
similarity index 100%
rename from feature_server/scripts/paint.py
rename to configs/scripts/paint.py
diff --git a/feature_server/scripts/passreload.py b/configs/scripts/passreload.py
similarity index 100%
rename from feature_server/scripts/passreload.py
rename to configs/scripts/passreload.py
diff --git a/feature_server/scripts/platform.py b/configs/scripts/platform.py
similarity index 100%
rename from feature_server/scripts/platform.py
rename to configs/scripts/platform.py
diff --git a/feature_server/scripts/protect.py b/configs/scripts/protect.py
similarity index 100%
rename from feature_server/scripts/protect.py
rename to configs/scripts/protect.py
diff --git a/feature_server/scripts/query.py b/configs/scripts/query.py
similarity index 100%
rename from feature_server/scripts/query.py
rename to configs/scripts/query.py
diff --git a/feature_server/scripts/rampage.py b/configs/scripts/rampage.py
similarity index 100%
rename from feature_server/scripts/rampage.py
rename to configs/scripts/rampage.py
diff --git a/feature_server/scripts/rangedamage.py b/configs/scripts/rangedamage.py
similarity index 100%
rename from feature_server/scripts/rangedamage.py
rename to configs/scripts/rangedamage.py
diff --git a/feature_server/scripts/rapid.py b/configs/scripts/rapid.py
similarity index 100%
rename from feature_server/scripts/rapid.py
rename to configs/scripts/rapid.py
diff --git a/feature_server/scripts/ratio.py b/configs/scripts/ratio.py
similarity index 100%
rename from feature_server/scripts/ratio.py
rename to configs/scripts/ratio.py
diff --git a/feature_server/scripts/rollback.py b/configs/scripts/rollback.py
similarity index 100%
rename from feature_server/scripts/rollback.py
rename to configs/scripts/rollback.py
diff --git a/feature_server/scripts/runningman.py b/configs/scripts/runningman.py
similarity index 100%
rename from feature_server/scripts/runningman.py
rename to configs/scripts/runningman.py
diff --git a/feature_server/scripts/savemap.py b/configs/scripts/savemap.py
similarity index 100%
rename from feature_server/scripts/savemap.py
rename to configs/scripts/savemap.py
diff --git a/contrib/scripts/smartnade.py b/configs/scripts/smartnade.py
similarity index 100%
rename from contrib/scripts/smartnade.py
rename to configs/scripts/smartnade.py
diff --git a/feature_server/scripts/spawn_protect.py b/configs/scripts/spawn_protect.py
similarity index 100%
rename from feature_server/scripts/spawn_protect.py
rename to configs/scripts/spawn_protect.py
diff --git a/contrib/scripts/spectatorcontrol.py b/configs/scripts/spectatorcontrol.py
similarity index 100%
rename from contrib/scripts/spectatorcontrol.py
rename to configs/scripts/spectatorcontrol.py
diff --git a/feature_server/scripts/squad.py b/configs/scripts/squad.py
similarity index 100%
rename from feature_server/scripts/squad.py
rename to configs/scripts/squad.py
diff --git a/feature_server/scripts/stats.py b/configs/scripts/stats.py
similarity index 100%
rename from feature_server/scripts/stats.py
rename to configs/scripts/stats.py
diff --git a/feature_server/scripts/strongblock.py b/configs/scripts/strongblock.py
similarity index 100%
rename from feature_server/scripts/strongblock.py
rename to configs/scripts/strongblock.py
diff --git a/feature_server/scripts/tdm.py b/configs/scripts/tdm.py
similarity index 100%
rename from feature_server/scripts/tdm.py
rename to configs/scripts/tdm.py
diff --git a/contrib/scripts/timedmute.py b/configs/scripts/timedmute.py
similarity index 100%
rename from contrib/scripts/timedmute.py
rename to configs/scripts/timedmute.py
diff --git a/feature_server/scripts/tow.py b/configs/scripts/tow.py
similarity index 100%
rename from feature_server/scripts/tow.py
rename to configs/scripts/tow.py
diff --git a/feature_server/scripts/trusted.py b/configs/scripts/trusted.py
similarity index 100%
rename from feature_server/scripts/trusted.py
rename to configs/scripts/trusted.py
diff --git a/feature_server/scripts/votekick.py b/configs/scripts/votekick.py
similarity index 100%
rename from feature_server/scripts/votekick.py
rename to configs/scripts/votekick.py
diff --git a/feature_server/scripts/votemap.py b/configs/scripts/votemap.py
similarity index 100%
rename from feature_server/scripts/votemap.py
rename to configs/scripts/votemap.py
diff --git a/feature_server/scripts/welcome.py b/configs/scripts/welcome.py
similarity index 100%
rename from feature_server/scripts/welcome.py
rename to configs/scripts/welcome.py
diff --git a/feature_server/scripts/zoc.py b/configs/scripts/zoc.py
similarity index 100%
rename from feature_server/scripts/zoc.py
rename to configs/scripts/zoc.py
diff --git a/contrib/scripts/README.txt b/contrib/scripts/README.txt
deleted file mode 100644
index fc5610e1..00000000
--- a/contrib/scripts/README.txt
+++ /dev/null
@@ -1 +0,0 @@
-This contrib sub-directory contains user-made scripts.
\ No newline at end of file
diff --git a/feature_server/cfg.py b/feature_server/cfg.py
new file mode 100644
index 00000000..680b95f5
--- /dev/null
+++ b/feature_server/cfg.py
@@ -0,0 +1,10 @@
+
+# to have global configuration variables
+# similar to http://effbot.org/pyfaq/how-do-i-share-global-variables-across-modules.htm
+
+import os
+
+config = {}
+config_dir = os.path.join(os.path.expanduser("~"), ".pysnip")
+config_file = 'config.json'
+
diff --git a/feature_server/commands.py b/feature_server/commands.py
index b8ebffb8..a4a9a728 100644
--- a/feature_server/commands.py
+++ b/feature_server/commands.py
@@ -15,6 +15,7 @@
# You should have received a copy of the GNU General Public License
# along with pyspades. If not, see .
+import os
import math
from random import choice
from pyspades.constants import *
@@ -24,6 +25,8 @@
from map import check_rotation
+import cfg
+
class InvalidPlayer(Exception):
pass
@@ -932,7 +935,7 @@ def add(func, name = None):
# optional commands
try:
import pygeoip
- database = pygeoip.GeoIP('./data/GeoLiteCity.dat')
+ database = pygeoip.GeoIP(os.path.join(cfg.config_dir, 'data/GeoLiteCity.dat'))
@name('from')
def where_from(connection, value = None):
diff --git a/feature_server/data/dummy b/feature_server/data/dummy
deleted file mode 100644
index e69de29b..00000000
diff --git a/feature_server/map.py b/feature_server/map.py
index 33adb9d3..e849a77e 100644
--- a/feature_server/map.py
+++ b/feature_server/map.py
@@ -22,8 +22,6 @@
import math
import random
-# load dir is overridden to use resource directory in run.py
-DEFAULT_LOAD_DIR = './maps'
class MapNotFound(Exception):
def __init__(self, map):
@@ -33,7 +31,7 @@ def __init__(self, map):
def __nonzero__(self):
return False
-def check_rotation(maps, load_dir = DEFAULT_LOAD_DIR):
+def check_rotation(maps, load_dir):
infos = []
for map in maps:
if type(map) is not RotationInfo:
@@ -45,7 +43,7 @@ def check_rotation(maps, load_dir = DEFAULT_LOAD_DIR):
return infos
class Map(object):
- def __init__(self, rot_info, load_dir = DEFAULT_LOAD_DIR):
+ def __init__(self, rot_info, load_dir):
self.load_information(rot_info, load_dir)
if self.gen_script:
@@ -55,13 +53,14 @@ def __init__(self, rot_info, load_dir = DEFAULT_LOAD_DIR):
self.data = self.gen_script(rot_info.name, rot_info.get_seed())
else:
print "Loading map '%s'..." % self.name
- self.load_vxl(rot_info, load_dir)
+ self.load_vxl(rot_info)
print 'Map loaded successfully.'
def load_information(self, rot_info, load_dir):
+ self.load_dir = load_dir
try:
- info = imp.load_source(rot_info.name, rot_info.get_meta_filename())
+ info = imp.load_source(rot_info.name, rot_info.get_meta_filename(load_dir))
except IOError:
info = None
self.info = info
@@ -92,9 +91,9 @@ def apply_script(self, protocol, connection, config):
protocol, connection = self.script(protocol, connection, config)
return protocol, connection
- def load_vxl(self, rot_info, load_dir):
+ def load_vxl(self, rot_info):
try:
- fp = open(rot_info.get_map_filename(load_dir), 'rb')
+ fp = open(rot_info.get_map_filename(self.load_dir), 'rb')
except OSError:
raise MapNotFound(rot_info.name)
self.data = VXLData(fp)
@@ -117,10 +116,10 @@ def get_seed(self):
self.seed = random.randint(0, math.pow(2, 31))
return self.seed
- def get_map_filename(self, load_dir = DEFAULT_LOAD_DIR):
+ def get_map_filename(self, load_dir):
return os.path.join(load_dir, '%s.vxl' % self.name)
- def get_meta_filename(self, load_dir = DEFAULT_LOAD_DIR):
+ def get_meta_filename(self, load_dir):
return os.path.join(load_dir, '%s.txt' % self.name)
def __str__(self):
diff --git a/feature_server/run.py b/feature_server/run.py
index 5f7d4f87..8e516ba0 100644
--- a/feature_server/run.py
+++ b/feature_server/run.py
@@ -30,15 +30,17 @@
import argparse
+import cfg
+
arg_parser = argparse.ArgumentParser(prog="pysnip",
description="PySnip is an open-source Python server implementation for the voxel-based game \"Ace of Spades\".")
-arg_parser.add_argument("-c","--config-file", default="config.txt",
- help="Specify alternate config file (default is feature_server/config.txt).")
+arg_parser.add_argument("-c","--config-file", default="config.json",
+ help="specify alternate config file (relative to config dir if relative path)")
arg_parser.add_argument("-j","--json-parameters",
- help="Add extra json parameters, overwriting that in config file.")
-arg_parser.add_argument("-r","--resource-dir", default=".",
- help="The directory which contains maps,scripts,etc (in correctly named subdirs). - default is in directory of run.py, in feature_server.")
+ help="add extra json parameters, overwriting that in config file")
+arg_parser.add_argument("-d","--config-dir", default=os.path.join(os.path.expanduser("~"), ".pysnip"),
+ help="he directory which contains maps,scripts,etc (in correctly named subdirs) - default is ~/.pysnip/")
args = arg_parser.parse_args()
@@ -49,10 +51,16 @@ def choose_path(base,top):
return os.path.join(base,top)
return top
-# ok, so we use the resource directory to search for maps, etc. (alternative to the feature_server dir)
-RESOURCE_DIR = args.resource_dir
+# ok, so we use the resource directory to search for maps, etc.
+config_dir = args.config_dir
+cfg.config_dir = config_dir
+
+# add it to the path so we can import scripts
+sys.path.append(config_dir)
+
# fix the path for the config file - handles differering directories and relative or absolute paths
-CONFIG_FILE = choose_path(RESOURCE_DIR,args.config_file)
+config_file = choose_path(config_dir,args.config_file)
+cfg.config_file = config_file
# default passwords hardcoded in config
DEFAULT_PASSWORDS = {
@@ -62,17 +70,17 @@ def choose_path(base,top):
'trusted' : ['trustedpass']
}
-for index, name in enumerate((CONFIG_FILE, 'config.txt.default')):
- try:
- config = json.load(open(name, 'rb'))
- if index != 0:
- print '(creating config.txt from %s)' % name
- shutil.copy(name, CONFIG_FILE)
- break
- except IOError, e:
- pass
-else:
- raise SystemExit('no config file found')
+try:
+ with open(config_file, 'rb') as f:
+ config = json.load(f)
+ cfg.config = config
+except IOError as e:
+ print("Error reading config from {}: ".format(config_file) + str(e))
+ sys.exit(1)
+except ValueError as e:
+ print("Error in config file {}: ".format(config_file) + str(e))
+ sys.exit(1)
+
# update with parameters from args
if args.json_parameters:
@@ -589,7 +597,7 @@ def __init__(self, interface, config):
self.win_count = itertools.count(1)
self.bans = NetworkDict()
try:
- self.bans.read_list(json.load(open(os.path.join(RESOURCE_DIR,'bans.txt'), 'rb')))
+ self.bans.read_list(json.load(open(os.path.join(config_dir,'bans.txt'), 'rb')))
except IOError:
pass
self.hard_bans = set() # possible DDoS'ers are added here
@@ -640,7 +648,7 @@ def __init__(self, interface, config):
self.set_god_build = config.get('set_god_build', False)
self.debug_log = config.get('debug_log', False)
if self.debug_log:
- pyspades.debug.open_debug_log(os.path.join(RESOURCE_DIR,'debug.log'))
+ pyspades.debug.open_debug_log(os.path.join(config_dir,'debug.log'))
ssh = config.get('ssh', {})
if ssh.get('enabled', False):
from ssh import RemoteConsole
@@ -661,9 +669,10 @@ def __init__(self, interface, config):
if ban_subscribe.get('enabled', True):
import bansubscribe
self.ban_manager = bansubscribe.BanManager(self, ban_subscribe)
+
# logfile location in resource dir if not abs path given
- logfile = choose_path(RESOURCE_DIR,config.get('logfile', None))
- if logfile is not None and logfile.strip():
+ logfile = choose_path(config_dir,config.get('logfile', ''))
+ if logfile.strip(): # catches empty filename
if config.get('rotate_daily', False):
create_filename_path(logfile)
logging_file = DailyLogFile(logfile, '.')
@@ -791,11 +800,11 @@ def set_map_name(self, rot_info):
return True
def get_map(self, rot_info):
- return Map(rot_info, os.path.join(RESOURCE_DIR,'maps'))
+ return Map(rot_info, os.path.join(config_dir,'maps'))
def set_map_rotation(self, maps, now = True):
try:
- maps = check_rotation(maps, os.path.join(RESOURCE_DIR,'maps'))
+ maps = check_rotation(maps, os.path.join(config_dir,'maps'))
except MapNotFound, e:
return e
self.maps = maps
@@ -917,7 +926,7 @@ def undo_last_ban(self):
return result
def save_bans(self):
- json.dump(self.bans.make_list(), open_create(os.path.join(RESOURCE_DIR,'bans.txt'), 'wb'))
+ json.dump(self.bans.make_list(), open_create(os.path.join(config_dir,'bans.txt'), 'wb'))
if self.ban_publish is not None:
self.ban_publish.update()
@@ -1051,10 +1060,6 @@ def get_advance_time(self):
script_names.append(game_mode)
-# temporarily allow loading from the scripts folder in the resource directory
-ORIG_PATH = sys.path
-sys.path = [RESOURCE_DIR] + ORIG_PATH
-
for script in script_names[:]:
try:
module = __import__('scripts.%s' % script, globals(), locals(),
@@ -1064,9 +1069,6 @@ def get_advance_time(self):
print "(script '%s' not found: %r)" % (script, e)
script_names.remove(script)
-# change back to original path
-sys.path = ORIG_PATH
-
for script in script_objects:
protocol_class, connection_class = script.apply_script(protocol_class,
diff --git a/feature_server/web/templates/status.html b/feature_server/web/templates/status.html
index 260e10b6..d306faab 100644
--- a/feature_server/web/templates/status.html
+++ b/feature_server/web/templates/status.html
@@ -1 +1,181 @@
-
{{server.name}}
Server Details:
Game Mode: |
{{server.game_mode_name}} |
Map: |
{{server.map_info.name}} v{{server.map_info.version}} |
Map: |
{{server.map_info.author}} |
Uptime: |
|
Enabled Scripts: |
{{ ', '.join(server.config['scripts'])}} |
Players: ({{server.connections|count}}/{{server.max_players}})
Name |
Latency (ms) |
Team |
Kills |
{% for player in server.players.values() %}
{{player.name}} |
{{player.latency}} |
{{player.team.name}} |
{{player.kills}} |
{% endfor %}
Map Overview:
\ No newline at end of file
+
+
+
+
+ {{server.name}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Server Details:
+
+
+
+
+
+
+
+ Game Mode: |
+ {{server.game_mode_name}} |
+
+
+ Map: |
+ {{server.map_info.name}} v{{server.map_info.version}} |
+
+
+ Map: |
+ {{server.map_info.author}} |
+
+
+ Uptime: |
+ |
+
+
+ Enabled Scripts: |
+ {{ ', '.join(server.config['scripts'])}} |
+
+
+
+
+
+
Players: ({{server.connections|count}}/{{server.max_players}})
+
+
+
+ Name |
+ Latency (ms) |
+ Team |
+ Kills |
+
+
+
+ {% for player in server.players.values() %}
+
+ {{player.name}} |
+ {{player.latency}} |
+ {{player.team.name}} |
+ {{player.kills}} |
+
+ {% endfor %}
+
+
+
+
+
Map Overview:
+
+
+
+
+
+
+
+
+
+
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 00000000..be2d6401
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,7 @@
+cython
+twisted
+jinja2
+pillow
+pygeoip
+pycrypto
+pyasn1