Skip to content

Commit

Permalink
Merge branch 'release-1.7'
Browse files Browse the repository at this point in the history
  • Loading branch information
alexanderkress committed Oct 2, 2016
2 parents c399f12 + c6e2867 commit d734601
Show file tree
Hide file tree
Showing 6 changed files with 61 additions and 45 deletions.
1 change: 0 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
language: python
python:
- "2.6"
- "2.7"
branches:
only:
Expand Down
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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


Expand Down Expand Up @@ -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!
Expand Down
4 changes: 3 additions & 1 deletion doc/Commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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>`
Expand Down
Binary file modified lib/GeoIP.dat
Binary file not shown.
4 changes: 2 additions & 2 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ license-file = LICENSE
[sdist]
formats=zip,gztar

[bdist_wheel]
python-tag = py27
[bdist_wheel]
python-tag = py27
85 changes: 50 additions & 35 deletions spunky.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -22,7 +22,7 @@
Run the bot: python spunky.py
"""

__version__ = '1.6.1'
__version__ = '1.7.0'


### IMPORTS
Expand Down Expand Up @@ -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']
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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):
"""
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand All @@ -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']
Expand All @@ -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()))
Expand Down Expand Up @@ -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()))
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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'))
Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit d734601

Please sign in to comment.