Skip to content
Permalink
Browse files

Merge branch 'release-1.7'

  • Loading branch information...
alexanderkress committed Oct 2, 2016
2 parents c399f12 + c6e2867 commit d73460174599c89786b6e460825a6929583cd570
Showing with 61 additions and 45 deletions.
  1. +0 −1 .travis.yml
  2. +6 −6 README.md
  3. +3 −1 doc/Commands.md
  4. BIN lib/GeoIP.dat
  5. +2 −2 setup.cfg
  6. +50 −35 spunky.py
@@ -1,6 +1,5 @@
language: python
python:
- "2.6"
- "2.7"
branches:
only:
@@ -3,13 +3,13 @@ This README is just a quick start document. You can find more detailed documenta
# What is Spunky Bot?

**Spunky Bot** is a lightweight game server administration bot and RCON tool.
Its purpose is to administer, manage and maintain an [Urban Terror](http://www.urbanterror.info) 4.1 / 4.2 server and to provide real time statistical data for players.
Its purpose is to administer, manage and maintain an [Urban Terror](http://www.urbanterror.info) 4.1 / 4.2 / 4.3 server and to provide real time statistical data for players.
Spunky Bot is a cross-platform package and offers in-game commands without authentication and automated administration even when no admin is online.
The code of Spunky Bot is inspired by the eb2k9 bot by Shawn Haggard, which was released under the Beerware License.

[![Build Status](https://travis-ci.org/SpunkyBot/spunkybot.png?branch=master)](https://travis-ci.org/SpunkyBot/spunkybot)
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/SpunkyBot/spunkybot/blob/master/LICENSE)
![Version](https://img.shields.io/badge/version-1.6.1-orange.svg)
![Version](https://img.shields.io/badge/version-1.7.0-orange.svg)
[![PyPI version](https://img.shields.io/pypi/v/spunkybot.svg)](https://pypi.python.org/pypi/spunkybot)
[![Code Health](https://landscape.io/github/SpunkyBot/spunkybot/master/landscape.svg)](https://landscape.io/github/SpunkyBot/spunkybot/master)

@@ -32,10 +32,10 @@ If you want to know more, this is a list of selected starting points:


## Environment
- Urban Terror 4.1.1 and 4.2.023
- Python 2.6.x / 2.7.x
- Urban Terror 4.1.1 / 4.2.023 / 4.3.0
- Python 2.6 / 2.7
- SQLite 3 database
- Cross-platform (tested on Debian 6 / 7 / 8, Ubuntu 12 / 14, CentOS 6.5 / 7.1, Mac OS X 10.11, Windows 7 / 10)
- Cross-platform (tested on Debian 6 / 7 / 8, Ubuntu 12 / 14 / 16, CentOS 6 / 7, Mac OS X 10.12, Windows 7 / 10)
- Supporting 32-bit and 64-bit operating systems


@@ -131,7 +131,7 @@ The code of Spunky Bot is released under the MIT License. See the [LICENSE](http
- Schedule: [schedule.py](https://github.com/dbader/schedule)
- This file is released under the MIT License.

Urban Terror™ and FrozenSand™ are trademarks of 0870760 B.C. Ltd.
Urban Terror™ and FrozenSand™ are trademarks of Frozensand Games Limited.


## Thank you!
@@ -61,6 +61,8 @@
- Short: `!lt [<name>]`
- **list** - list all connected players
- Usage: `!list`
- **locate** - display geolocation info of the player
- Usage: `!locate <name>`
- **nextmap** - display the next map in rotation
- Usage: `!nextmap`
- **mute** - mute or unmute a player
@@ -75,7 +77,7 @@
- **warn** - warn user
- Usage: `!warn <name> [<reason>]`
- Short: `!w <name> [<reason>]`
- Available short form reasons: _tk_, _obj_, _spec_, _ping_, _spam_, _camp_, _lang_, _racism_
- Available short form reasons: _tk_, _obj_, _spec_, _ping_, _spam_, _camp_, _lang_, _racism_, _name_, _skill_, _whiner_
- **warninfo** - display how many warnings the player has
- Usage: `!warninfo <name>`
- Short: `!wi <name>`
BIN +179 KB (120%) lib/GeoIP.dat
Binary file not shown.
@@ -5,5 +5,5 @@ license-file = LICENSE
[sdist]
formats=zip,gztar

[bdist_wheel]
python-tag = py27
[bdist_wheel]
python-tag = py27
@@ -11,8 +11,8 @@
## About ##
Spunky Bot is a lightweight game server administration bot and RCON tool,
inspired by the eb2k9 bot by Shawn Haggard.
The purpose of Spunky Bot is to administrate an Urban Terror 4.1 / 4.2 server
and provide statistical data for players.
The purpose of Spunky Bot is to administrate an Urban Terror 4.1 / 4.2 / 4.3
server and provide statistical data for players.
## Configuration ##
Modify the UrT server config as follows:
@@ -22,7 +22,7 @@
Run the bot: python spunky.py
"""

__version__ = '1.6.1'
__version__ = '1.7.0'


### IMPORTS
@@ -73,20 +73,20 @@ def __init__(self, config_file):
self.hit_item = {1: "UT_MOD_KNIFE", 2: "UT_MOD_BERETTA", 3: "UT_MOD_DEAGLE", 4: "UT_MOD_SPAS", 5: "UT_MOD_MP5K",
6: "UT_MOD_UMP45", 8: "UT_MOD_LR300", 9: "UT_MOD_G36", 10: "UT_MOD_PSG1", 14: "UT_MOD_SR8",
15: "UT_MOD_AK103", 17: "UT_MOD_NEGEV", 19: "UT_MOD_M4", 20: "UT_MOD_GLOCK", 21: "UT_MOD_COLT1911",
22: "UT_MOD_MAC11", 23: "UT_MOD_BLED", 24: "UT_MOD_KICKED", 25: "UT_MOD_KNIFE_THROWN"}
22: "UT_MOD_MAC11", 23: "UT_MOD_BLED"}
self.death_cause = {1: "MOD_WATER", 3: "MOD_LAVA", 5: "UT_MOD_TELEFRAG", 6: "MOD_FALLING", 7: "UT_MOD_SUICIDE",
9: "MOD_TRIGGER_HURT", 10: "MOD_CHANGE_TEAM", 12: "UT_MOD_KNIFE", 13: "UT_MOD_KNIFE_THROWN",
14: "UT_MOD_BERETTA", 15: "UT_MOD_DEAGLE", 16: "UT_MOD_SPAS", 17: "UT_MOD_UMP45", 18: "UT_MOD_MP5K",
19: "UT_MOD_LR300", 20: "UT_MOD_G36", 21: "UT_MOD_PSG1", 22: "UT_MOD_HK69", 23: "UT_MOD_BLED",
24: "UT_MOD_KICKED", 25: "UT_MOD_HEGRENADE", 28: "UT_MOD_SR8", 30: "UT_MOD_AK103",
31: "UT_MOD_SPLODED", 32: "UT_MOD_SLAPPED", 33: "UT_MOD_SMITED", 34: "UT_MOD_BOMBED",
35: "UT_MOD_NUKED", 36: "UT_MOD_NEGEV", 37: "UT_MOD_HK69_HIT", 38: "UT_MOD_M4",
39: "UT_MOD_GLOCK", 40: "UT_MOD_COLT1911", 41: "UT_MOD_MAC11", 42: "UT_MOD_FLAG", 43: "UT_MOD_GOOMBA"}
39: "UT_MOD_GLOCK", 40: "UT_MOD_COLT1911", 41: "UT_MOD_MAC11"}

# RCON commands for the different admin roles
self.user_cmds = ['bombstats', 'ctfstats', 'freezestats', 'forgiveall, forgiveprev', 'hestats', 'hs', 'hits',
'knife', 'register', 'regtest', 'spree', 'stats', 'teams', 'time', 'xlrstats', 'xlrtopstats']
self.mod_cmds = self.user_cmds + ['admintest', 'country', 'leveltest', 'list', 'nextmap', 'mute', 'poke',
self.mod_cmds = self.user_cmds + ['admintest', 'country', 'leveltest', 'list', 'locate', 'nextmap', 'mute', 'poke',
'seen', 'shuffleteams', 'warn', 'warninfo', 'warnremove', 'warns', 'warntest']
self.admin_cmds = self.mod_cmds + ['admins', 'aliases', 'bigtext', 'find', 'force', 'kick', 'nuke', 'say',
'tempban', 'warnclear']
@@ -137,11 +137,12 @@ def __init__(self, config_file):
self.freeze_gametype = False
self.ts_do_team_balance = False
self.allow_cmd_teams = True
self.urt42_modversion = True
self.urt_modversion = None
self.game = None
self.players_lock = RLock()
self.firstblood = False
self.firstnadekill = False
self.firstknifekill = False

# enable/disable autokick for team killing
self.tk_autokick = config.getboolean('bot', 'teamkill_autokick') if config.has_option('bot', 'teamkill_autokick') else True
@@ -217,24 +218,27 @@ def find_game_start(self):
tmp = line.split()
if len(tmp) > 1 and tmp[1] == "InitGame:":
game_start = True
if 'g_modversion\\4.1' in line:
if 'g_modversion\\4.3' in line:
self.hit_item.update({23: "UT_MOD_FRF1", 24: "UT_MOD_BENELLI", 25: "UT_MOD_P90",
26: "UT_MOD_MAGNUM", 29: "UT_MOD_KICKED", 30: "UT_MOD_KNIFE_THROWN"})
self.death_cause.update({42: "UT_MOD_FRF1", 43: "UT_MOD_BENELLI", 44: "UT_MOD_P90", 45: "UT_MOD_MAGNUM",
46: "UT_MOD_TOD50", 47: "UT_MOD_FLAG", 48: "UT_MOD_GOOMBA"})
self.urt_modversion = 43
logger.info("Game modversion : 4.3")
elif 'g_modversion\\4.2' in line:
self.hit_item.update({23: "UT_MOD_BLED", 24: "UT_MOD_KICKED", 25: "UT_MOD_KNIFE_THROWN"})
self.death_cause.update({42: "UT_MOD_FLAG", 43: "UT_MOD_GOOMBA"})
self.urt_modversion = 42
logger.info("Game modversion : 4.2")
elif 'g_modversion\\4.1' in line:
# hit zone support for UrT 4.1
self.hit_points = {0: "HEAD", 1: "HELMET", 2: "TORSO", 3: "KEVLAR", 4: "ARMS", 5: "LEGS", 6: "BODY"}
self.hit_item = {1: "UT_MOD_KNIFE", 2: "UT_MOD_BERETTA", 3: "UT_MOD_DEAGLE", 4: "UT_MOD_SPAS",
5: "UT_MOD_MP5K", 6: "UT_MOD_UMP45", 8: "UT_MOD_LR300", 9: "UT_MOD_G36",
10: "UT_MOD_PSG1", 14: "UT_MOD_SR8", 15: "UT_MOD_AK103", 17: "UT_MOD_NEGEV",
19: "UT_MOD_M4", 21: "UT_MOD_KICKED", 22: "UT_MOD_KNIFE_THROWN"}
self.death_cause = {1: "MOD_WATER", 3: "MOD_LAVA", 5: "UT_MOD_TELEFRAG", 6: "MOD_FALLING",
7: "UT_MOD_SUICIDE", 9: "MOD_TRIGGER_HURT", 10: "MOD_CHANGE_TEAM",
12: "UT_MOD_KNIFE", 13: "UT_MOD_KNIFE_THROWN", 14: "UT_MOD_BERETTA",
15: "UT_MOD_DEAGLE", 16: "UT_MOD_SPAS", 17: "UT_MOD_UMP45", 18: "UT_MOD_MP5K",
19: "UT_MOD_LR300", 20: "UT_MOD_G36", 21: "UT_MOD_PSG1", 22: "UT_MOD_HK69",
23: "UT_MOD_BLED", 24: "UT_MOD_KICKED", 25: "UT_MOD_HEGRENADE",
28: "UT_MOD_SR8", 30: "UT_MOD_AK103", 31: "UT_MOD_SPLODED", 32: "UT_MOD_SLAPPED",
33: "UT_MOD_BOMBED", 34: "UT_MOD_NUKED", 35: "UT_MOD_NEGEV", 37: "UT_MOD_HK69_HIT",
38: "UT_MOD_M4", 39: "UT_MOD_FLAG", 40: "UT_MOD_GOOMBA"}
self.urt42_modversion = False
logger.info("Game modversion 4.1 detected")
self.hit_item.update({21: "UT_MOD_KICKED", 22: "UT_MOD_KNIFE_THROWN"})
self.death_cause.update({33: "UT_MOD_BOMBED", 34: "UT_MOD_NUKED", 35: "UT_MOD_NEGEV",
39: "UT_MOD_FLAG", 40: "UT_MOD_GOOMBA"})
self.urt_modversion = 41
logger.info("Game modversion : 4.1")

if 'g_gametype\\0\\' in line or 'g_gametype\\1\\' in line or 'g_gametype\\9\\' in line or 'g_gametype\\11\\' in line:
# disable teamkill event and some commands for FFA (0), LMS (1), Jump (9), Gun (11)
self.ffa_lms_gametype = True
@@ -281,7 +285,7 @@ def read_log(self):
self.find_game_start()

# create instance of Game
self.game = Game(self.config_file, self.urt42_modversion)
self.game = Game(self.config_file, self.urt_modversion)

self.log_file.seek(0, 2)
while self.log_file:
@@ -541,9 +545,11 @@ def stats_reset(self, store_score=False):
if self.show_first_kill_msg and not self.ffa_lms_gametype:
self.firstblood = True
self.firstnadekill = True
self.firstknifekill = True
else:
self.firstblood = False
self.firstnadekill = False
self.firstknifekill = False

def handle_userinfo(self, line):
"""
@@ -687,7 +693,8 @@ def handle_hit(self, line):
10: 'awesome!',
15: 'unbelievable!',
20: '^1MANIAC!',
25: '^2AIMBOT?'}
25: '^2AIMBOT?',
30: 'stop that'}
if self.spam_headshot_hits_msg and hitter_hs_count in hs_msg:
self.game.rcon_bigtext("^3%s: ^2%d ^7HeadShots, %s" % (hitter_name, hitter_hs_count, hs_msg[hitter_hs_count]))
hs_plural = "headshots" if hitter_hs_count > 1 else "headshot"
@@ -768,9 +775,14 @@ def handle_kill(self, line):
self.firstblood = False
if death_cause == 'UT_MOD_HEGRENADE':
self.firstnadekill = False
if death_cause == 'UT_MOD_KNIFE' or death_cause == 'UT_MOD_KNIFE_THROWN':
self.firstknifekill = False
elif self.firstnadekill and death_cause == 'UT_MOD_HEGRENADE':
self.game.rcon_bigtext("^3%s: ^7first HE grenade kill" % killer_name)
self.firstnadekill = False
elif self.firstknifekill and (death_cause == 'UT_MOD_KNIFE' or death_cause == 'UT_MOD_KNIFE_THROWN'):
self.game.rcon_bigtext("^3%s: ^7first knife kill" % killer_name)
self.firstknifekill = False

# bomb mode
if self.bomb_gametype:
@@ -922,7 +934,7 @@ def clean_cmd_list(self, cmd_list):
elif self.freeze_gametype:
disabled_cmds = ['bombstats', 'ctfstats']

if not self.urt42_modversion:
if self.urt_modversion == 41:
disabled_cmds += ['kill']

for item in disabled_cmds:
@@ -947,6 +959,7 @@ def handle_say(self, line):
'spec': 'spectator too long on full server',
'ci': 'connection interrupted',
'whiner': 'stop complaining about camp, lag or block',
'skill': 'skill too low for this server',
'name': 'do not use offensive names'}

poke_options = ['Go', 'Wake up', '*poke*', 'Attention', 'Get up', 'Move out']
@@ -968,7 +981,7 @@ def handle_say(self, line):
self.game.rcon_tell(sar['player_num'], "^2%d ^7total hits - ^2%d ^7headshots" % (self.game.players[sar['player_num']].get_all_hits(), self.game.players[sar['player_num']].get_headshots()))
self.game.rcon_tell(sar['player_num'], "^2%d ^7HE grenade kills" % self.game.players[sar['player_num']].get_he_kills())
if self.ctf_gametype:
if self.urt42_modversion:
if self.urt_modversion > 41:
self.game.rcon_tell(sar['player_num'], "^7flags captured: ^2%d ^7- flags returned: ^2%d ^7- fastest cap: ^2%s ^7sec" % (self.game.players[sar['player_num']].get_flags_captured(), self.game.players[sar['player_num']].get_flags_returned(), self.game.players[sar['player_num']].get_flag_capture_time()))
else:
self.game.rcon_tell(sar['player_num'], "^7flags captured: ^2%d ^7- flags returned: ^2%d" % (self.game.players[sar['player_num']].get_flags_captured(), self.game.players[sar['player_num']].get_flags_returned()))
@@ -1056,7 +1069,7 @@ def handle_say(self, line):
# ctfstats - display ctf statistics
elif sar['command'] == '!ctfstats':
if self.ctf_gametype:
if self.urt42_modversion:
if self.urt_modversion > 41:
self.game.rcon_tell(sar['player_num'], "^7flags captured: ^2%d ^7- flags returned: ^2%d ^7- fastest cap: ^2%s ^7sec" % (self.game.players[sar['player_num']].get_flags_captured(), self.game.players[sar['player_num']].get_flags_returned(), self.game.players[sar['player_num']].get_flag_capture_time()))
else:
self.game.rcon_tell(sar['player_num'], "^7flags captured: ^2%d ^7- flags returned: ^2%d" % (self.game.players[sar['player_num']].get_flags_captured(), self.game.players[sar['player_num']].get_flags_returned()))
@@ -1100,6 +1113,8 @@ def handle_say(self, line):
self.game.rcon_tell(sar['player_num'], "^7Stats %s: ^7K ^2%d ^7D ^3%d ^7TK ^1%d ^7Ratio ^5%s ^7HS ^2%d" % (player.get_name(), player.get_db_kills(), player.get_db_deaths(), player.get_db_tks(), ratio, player.get_db_headshots()))
else:
self.game.rcon_tell(sar['player_num'], "^7Sorry, this player is not registered")
else:
self.game.rcon_tell(sar['player_num'], "^7No player found matching ^3%s" % arg)
else:
if self.game.players[sar['player_num']].get_registered_user():
ratio = round(float(self.game.players[sar['player_num']].get_db_kills()) / float(self.game.players[sar['player_num']].get_db_deaths()), 2) if self.game.players[sar['player_num']].get_db_deaths() > 0 else 1.0
@@ -1151,8 +1166,8 @@ def handle_say(self, line):
player_admin_role = self.game.players[sar['player_num']].get_admin_role()
self.game.rcon_tell(sar['player_num'], "^7%s [^3@%s^7] is ^3%s ^7[^2%d^7]" % (self.game.players[sar['player_num']].get_name(), self.game.players[sar['player_num']].get_player_id(), self.game.players[sar['player_num']].roles[player_admin_role], player_admin_role))

# country
elif (sar['command'] == '!country' or sar['command'] == '@country') and self.game.players[sar['player_num']].get_admin_role() >= 20:
# country / locate
elif (sar['command'] == '!country' or sar['command'] == '@country' or sar['command'] == '!locate') and self.game.players[sar['player_num']].get_admin_role() >= 20:
if line.split(sar['command'])[1]:
user = line.split(sar['command'])[1].strip()
found, victim, msg = self.player_found(user)
@@ -1764,7 +1779,7 @@ def handle_say(self, line):

# kill - kill a player
elif sar['command'] == '!kill' and self.game.players[sar['player_num']].get_admin_role() >= 80:
if self.urt42_modversion:
if self.urt_modversion > 41:
if line.split(sar['command'])[1]:
user = line.split(sar['command'])[1].strip()
found, victim, msg = self.player_found(user)
@@ -2082,7 +2097,7 @@ def handle_team_balance(self):
else:
if self.ts_gametype or self.bomb_gametype or self.freeze_gametype:
self.ts_do_team_balance = True
self.game.rcon_say("^7Teams will be balanced at the end of the round!")
self.game.rcon_say("^7Teams will be balanced at the end of this round!")
else:
self.game.rcon_say("^7Teams are already balanced")
self.ts_do_team_balance = False
@@ -2800,7 +2815,7 @@ class Game(object):
"""
Game class
"""
def __init__(self, config_file, urt42_modversion):
def __init__(self, config_file, urt_modversion):
"""
create a new instance of Game
@@ -2813,7 +2828,7 @@ def __init__(self, config_file, urt42_modversion):
self.maplist = []
self.players = {}
self.live = False
self.urt42_modversion = urt42_modversion
self.urt_modversion = urt_modversion
game_cfg = ConfigParser.ConfigParser()
game_cfg.read(config_file)
self.rcon_handle = Rcon(game_cfg.get('server', 'server_ip'), game_cfg.get('server', 'server_port'), game_cfg.get('server', 'rcon_password'))
@@ -2921,7 +2936,7 @@ def kick_player(self, player_num, reason=''):
@param reason: Reason for kick
@type reason: String
"""
if reason and self.urt42_modversion:
if reason and self.urt_modversion > 41:
self.send_rcon('kick %d "%s"' % (player_num, reason))
else:
self.send_rcon('kick %d' % player_num)

0 comments on commit d734601

Please sign in to comment.
You can’t perform that action at this time.