Skip to content

Commit

Permalink
make Battle derived from Channel
Browse files Browse the repository at this point in the history
The chat for each battle now takes place in a channel.
Channels use for this purpose have names prefixed by __battle__
When a client joins/leaves a battle, he is auto-joined/left to this channel.
 (-> such channels can register with ChanServ and set a founder, topic, operators, mutes, bans, etc.) 
To speak in a battle, use SAY/SAYEX/SAYFROM into its channel.

-- Changes to protocol -- 
- JOINBATTLE (server) has an extra arg, containing the name of the channel associated to the battle
- removed SAYBATTLEPRIVATE SAYBATTLEPRIVATEEX 
- deprecated SAYBATTLE SAYBATTLEEX (-> use SAY/SAYEX)
- added !registerbattle and !mute/!unmute to ChanServ
- deprecated SETCHANNELKEY and ChanServ !lock/!unlock (-> use !setkey)
- fix #283: LEFT is reflected back to the user who leaves a channel 

Clients should send the 'u' compatibility flag to signal that they are prepared for the above.
Full backwards compatibility exists for clients lacking 'u'.
  • Loading branch information
silentwings committed Oct 10, 2018
1 parent 05f7acb commit a1c0e85
Show file tree
Hide file tree
Showing 6 changed files with 443 additions and 436 deletions.
29 changes: 18 additions & 11 deletions ChanServ.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ def HandleCommand(self, chan, user, cmd, args=None):
channel.operators = set()
channel.channelMessage('#%s has been unregistered'%chan)
self.db().unRegister(client, channel)
self.Respond('LEAVE %s' % chan)
return '#%s: Successfully unregistered.' % chan
else:
return '#%s: You must contact one of the server moderators or the owner of the channel to unregister a channel' % chan
Expand Down Expand Up @@ -169,20 +170,23 @@ def HandleCommand(self, chan, user, cmd, args=None):
return '#%s: Successfully removed <%s> from operator list' % (chan, args)
else:
return '#%s: You do not have permission to deop users' % chan
elif cmd == 'lock':
elif cmd == 'lock' or cmd == 'unlock':
return 'This command no longer exists, use setkey/removekey'
elif cmd == 'setkey':
if access in ['mod', 'founder', 'op']:
if not args: return '#%s: You must specify a channel key to lock a channel' % chan
if not args: return '#%s: You must specify a key for the channel' % chan
if channel.identity=='battle': return 'This is not currently possible, instead you can close and re-open the battle with a new password!'
channel.setKey(client, args)
self.db().setKey(channel, args)
## STUBS ARE BELOW
return '#%s: Locked' % chan
return '#%s: Set key' % chan
else:
return '#%s: You do not have permission to lock the channel' % chan
elif cmd == 'unlock':
elif cmd == 'removekey':
if access in ['mod', 'founder', 'op']:
if channel.identity=='battle': return 'This is not currently possible, instead you can close and re-open the battle without a password!'
channel.setKey(client, '*')
self.db().setKey(channel, '*')
return '#%s: Unlocked' % chan
return '#%s: Removed key' % chan
else:
return '#%s: You do not have permission to unlock the channel' % chan
elif cmd == 'kick':
Expand All @@ -200,7 +204,7 @@ def HandleCommand(self, chan, user, cmd, args=None):
else: return '#%s: user <%s> not found' % (chan, target)
else:
return '#%s: You do not have permission to kick users from the channel' % chan
elif cmd == 'history':
elif cmd == 'history': #FIXME: battles
if access in ['mod', 'founder', 'op']:
enable = not channel.store_history
if self.db().setHistory(channel, enable):
Expand Down Expand Up @@ -253,17 +257,20 @@ def HandleCommand(self, chan, user, cmd, args=None):
return '#%s: <%s> unbanned' % (chan, target.username)
return '#%s: user <%s> not found in banlist' % (chan, target_username)
else:
return '#%s: You do not have permission to ban users from this channel' % chan
if cmd == 'register':
return '#%s: You do not have permission to ban users from this channel' % chan
#FIXME: chanserv has no mute command
#FIXME: accept ymdhs durations for mutes/bans
#FIXME: ban should interact with ip & email ?
if cmd == 'register': #FIXME: way to register battles
if client.isMod():
if not args: args = user
self.Respond('JOIN %s' % chan)
if not chan in self._root.channels:
return '# Channel %s does not exist.' % (chan)
return '# Channel %s does not exist.' % chan
channel = self._root.channels[chan]
target = self._root.protocol.clientFromUsername(args, True)
if target:
channel.setFounder(client, target)
channel.register(client, target)
self.db().register(channel, target) # register channel in db
return '#%s: Successfully registered to <%s>' % (chan, args.split(' ',1)[0])
else:
Expand Down
1 change: 0 additions & 1 deletion Client.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@ def __init__(self, root, address, session_id):

self.battle_bots = {}
self.current_battle = None
self.battle_bans = []
self.went_ingame = 0
self.spectator = False
self.battlestatus = {'ready':'0', 'id':'0000', 'ally':'0000', 'mode':'0', 'sync':'00', 'side':'00', 'handicap':'0000000'}
Expand Down
67 changes: 34 additions & 33 deletions DataHandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import ChanServ
import ip2country
import datetime
from protocol import Protocol, Channel
from protocol import Protocol, Channel, Battle

import logging
from logging.handlers import TimedRotatingFileHandler
Expand Down Expand Up @@ -64,11 +64,11 @@ def __init__(self):
self.cert = None

# lists of online stuff
self.channels = {}
self.usernames = {}
self.clients = {}
self.user_ids = {}
self.battles = {}
self.channels = {} #channame->channel/battle
self.battles = {} #battle_id->battle
self.usernames = {} #username->client
self.user_ids = {} #user_id->client
self.clients = {} #session_id->client

self.bridge_location_bots = {}

Expand Down Expand Up @@ -106,45 +106,46 @@ def _fk_pragma_on_connect(dbapi_con, con_record):
self.verificationdb = SQLUsers.VerificationsHandler(self, self.engine)
self.bandb = SQLUsers.BansHandler(self, self.engine)

self.parseFiles()
self.protocol = Protocol.Protocol(self)

self.channeldb = SQLUsers.ChannelsHandler(self, self.engine)
channels = self.channeldb.all_channels()
operators = self.channeldb.all_operators()

# set up channels/battles from db
for name in channels:
channel = channels[name]

owner_user_id = None
client = self.userdb.clientFromID(channel['owner_user_id'])
if client and client.id:
owner_user_id = client.id

assert(name not in self.channels)
newchan = Channel.Channel(self, name)
newchan.chanserv = bool(owner_user_id)
newchan.id = channel['id']
newchan.owner_user_id = owner_user_id
newchan.operators = set()
if channel['key'] in ('', None, '*'):
newchan.key=None
else:
newchan.key = channel['key']
newchan.antispam = channel['antispam']
topic_client = self.userdb.clientFromID(channel['topic_user_id'])
dbchannel = channels[name]
channel = Channel.Channel(self, name)
if name.startswith('__battle__'):
channel = Battle.Battle(self, name)

owner = self.userdb.clientFromID(dbchannel['owner_user_id'])
if owner:
channel.owner_user_id = owner.id

channel.chanserv = True
channel.antispam = dbchannel['antispam']
channel.store_history = dbchannel['store_history']
channel.id = dbchannel['id']
channel.key = dbchannel['key']
if channel.key in ('', None, '*'):
channel.key = None

channel.topic_user_id = dbchannel['topic_user_id']
topic_client = self.userdb.clientFromID(dbchannel['topic_user_id'])
topic_name = 'ChanServ'
if topic_client:
topic_name = topic_client.username
newchan.topic={'user':topic_name, 'text':channel['topic'], 'time':int(time.time())}
newchan.store_history = channel['store_history']
self.channels[name] = newchan

channel.topic={'user':topic_name, 'text':dbchannel['topic'], 'time':int(time.time())}
self.channels[name] = channel

for op in operators:
dbchannel = self.channeldb.channel_from_id(op['channel_id'])
if dbchannel:
self.channels[dbchannel.name].operators.add(op['user_id'])

self.parseFiles()
self.protocol = Protocol.Protocol(self)

self.channels[dbchannel.name].operators.add(op['user_id'])

self.chanserv = ChanServ.ChanServClient(self, (self.online_ip, 0), self.session_id)
for name in channels:
self.chanserv.HandleProtocolCommand("JOIN %s" %(name))
Expand Down
150 changes: 124 additions & 26 deletions protocol/Battle.py
Original file line number Diff line number Diff line change
@@ -1,43 +1,141 @@
from Channel import Channel

class Battle():
def __init__(self, root, id, type, natType, password, port, maxplayers,
hashcode, rank, maphash, map, title, modname,
passworded, host, users):
class Battle(Channel):
def __init__(self, root, name):

Channel.__init__(self, root, name)
self.identity = 'battle'

# battle
self.battle_id = None #FIXME: it would be great to remove this and use battle.name to identify battles, but it causes a big change to protocol -> wait for #58
self.host = None
self.type = ''
self.natType = ''
self.port = 0

self._root = root
self.id = id #name
self.passworded = passworded #key
self.password = password #key
self.users = users #users (set) # list with all session_ids of joined users
self.host = host #founder # client.session_id
self.mutelist = {} #mutelist

self.title = title
self.title = ''
self.map = ''
self.maphash = None
self.modname = ''
self.hashcode = None
self.engine = ''
self.version = ''
self.modname = modname
self.map = map
self.maxplayers = maxplayers

self.hashcode = hashcode
self.maphash = maphash

self.type = type
self.rank = 0
self.maxplayers = 0
self.spectators = 0 # duplicated info?
self.locked = False
self.rank = rank

self.pending_users = set()

self.bots = {}
self.script_tags = {}
self.startrects = {}
self.disabled_units = []
self.port = port
self.natType = natType

self.replay_script = {}
self.replay_script = {} #FIXME: inaccessible via protocol
self.replay = False
self.sending_replay_script = False

self.pending_users = set() #users who requested to join, but haven't heard back from the host yet
self.spectators = 0 # duplicated info?

def joinBattle(self, client):
# client joins battle + notifies others
if client.compat['u']:
client.Send('JOINBATTLE %s %s %s' % (self.battle_id, self.hashcode, self.name))
self.addUser(client) # join the battles channel
else:
client.Send('JOINBATTLE %s %s' % (self.battle_id, self.hashcode))
self.users.add(client.session_id)

scriptPassword = client.scriptPassword
host = self._root.protocol.clientFromSession(self.host)
if scriptPassword and host.compat['sp']:
host.Send('JOINEDBATTLE %s %s %s' % (self.battle_id, client.username, scriptPassword))
if client!=host:
client.Send('JOINEDBATTLE %s %s %s' % (self.battle_id, client.username, scriptPassword))
else:
host.Send('JOINEDBATTLE %s %s' % (self.battle_id, client.username))
if client!=host:
client.Send('JOINEDBATTLE %s %s' % (self.battle_id, client.username))

scripttags = []
for tag, val in self.script_tags.items():
scripttags.append('%s=%s'%(tag, val))
client.Send('SETSCRIPTTAGS %s'%'\t'.join(scripttags))
if self.disabled_units:
client.Send('DISABLEUNITS %s' % ' '.join(self.disabled_units))
self._root.broadcast('JOINEDBATTLE %s %s' % (self.battle_id, client.username), ignore=set([self.host, client.session_id]))

if self.natType > 0:
if battle.host == client.session_id:
raise NameError('%s is having an identity crisis' % (client.name))
if client.udpport:
self._root.usernames[host].Send('CLIENTIPPORT %s %s %s' % (username, client.ip_address, client.udpport))

specs = 0
for sessionid in self.users:
battle_client = self._root.protocol.clientFromSession(sessionid)
if battle_client and battle_client.battlestatus['mode'] == '0':
specs += 1
battlestatus = self.calc_battlestatus(battle_client)
client.Send('CLIENTBATTLESTATUS %s %s %s' % (battle_client.username, battlestatus, battle_client.teamcolor))

for iter in self.bots:
bot = self.bots[iter]
client.Send('ADDBOT %s %s' % (self.battle_id, iter)+' %(owner)s %(battlestatus)s %(teamcolor)s %(AIDLL)s' % (bot))

for allyno in self.startrects:
rect = self.startrects[allyno]
client.Send('ADDSTARTRECT %s' % (allyno)+' %(left)s %(top)s %(right)s %(bottom)s' % (rect))

client.battlestatus = {'ready':'0', 'id':'0000', 'ally':'0000', 'mode':'0', 'sync':'00', 'side':'00', 'handicap':'0000000'}
client.teamcolor = '0'
client.current_battle = self.battle_id
client.Send('REQUESTBATTLESTATUS')

def leaveBattle(self, client):
self.removeUser(client)

client.scriptPassword = None
client.current_battle = None
client.hostport = None

for bot in list(client.battle_bots):
del client.battle_bots[bot]
if bot in self.bots:
del self.bots[bot]
self._root.broadcast_battle('REMOVEBOT %s %s' % (self.battle_id, bot), battle.id)
self._root.broadcast('LEFTBATTLE %s %s'%(self.battle_id, client.username))
if client.session_id == self.host:
return

oldspecs = self.spectators
specs = 0
for session_id in self.users:
user = self._root.protocol.clientFromSession(session_id)
if user and user.battlestatus['mode'] == '0':
specs += 1
self.spectators = specs
if oldspecs != specs:
self._root.broadcast('UPDATEBATTLEINFO %s %i %i %s %s' % (self.battle_id, self.spectators, self.locked, self.maphash, self.map))

def calc_battlestatus(self, client):
battlestatus = client.battlestatus
status = self._root.protocol._bin2dec('0000%s%s0000%s%s%s%s%s0'%(battlestatus['side'],
battlestatus['sync'], battlestatus['handicap'],
battlestatus['mode'], battlestatus['ally'],
battlestatus['id'], battlestatus['ready']))
return status

def hasBotflag(self):
host = self._root.protocol.clientFromSession(self.host)
return host.bot

def canChangeSettings(self, client):
return client.session_id == self.host

def setKey():
return #todo: currently there is no way to inform clients when a new channel/battle key is set/unset
def passworded(self):
return 0 if self.key in ('*', None) else 1

Loading

0 comments on commit a1c0e85

Please sign in to comment.