Skip to content

Commit

Permalink
Hardening the cointosser
Browse files Browse the repository at this point in the history
  • Loading branch information
nsavch committed Jan 12, 2018
1 parent 54d57f0 commit ddb2013
Show file tree
Hide file tree
Showing 7 changed files with 114 additions and 57 deletions.
70 changes: 43 additions & 27 deletions xanmel/modules/xonotic/chat_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,47 +198,63 @@ class Cointoss(ChatCommand):
help_text = 'start a cointoss process.'
allowed_user_types = ['xonotic']

async def run(self, user, message, is_private=True, root=None):
rcon_server = self.parent.properties['rcon_server']
if not rcon_server.cointosser:
await user.reply('^1Cointoss not enabled on this server^7')
return
message = message.lower().strip()
if int(rcon_server.cvars['xanmel_wup_stage']) != 1:
async def run_status(self, server, user):
await user.public_reply(server.cointosser.format_status())

async def run_toss(self, server, user, side):
if int(server.cvars['xanmel_wup_stage']) != 1:
await user.reply('^3Cointoss can only be performed during warmup stage. ^2vcall ^5restart ^3or ^5endmatch^7.',
is_private=False)
return
if not rcon_server.active_duel_pair:
if not server.active_duel_pair:
await user.reply('^3Exactly ^2two ^3players must join the game before cointoss can be performed.^7',
is_private=False)
return
if rcon_server.cointosser.state != CointosserState.PENDING:
if server.cointosser.state != CointosserState.PENDING:
await user.reply(
'^1A cointoss is already activated. ^3Finish the games or ^2/cointoss stop^3 before starting a new one^7',
is_private=False)
return
await user.public_reply('^3Tossing coin...^7')
await asyncio.sleep(0.3)
if message in ('heads', 'tails'):
result = random.choice(('heads', 'tails'))
await user.public_reply('{}!'.format(result.upper()))
await asyncio.sleep(0.2)
rcon_server.cointosser.reset()
this_player = other_player = None
for i in rcon_server.active_duel_pair:
if i.nickname == user.unique_id():
this_player = i
else:
other_player = i
assert this_player and other_player, (this_player, other_player)
if message == result:
await user.public_reply('{} ^2wins ^3the cointoss!'.format(this_player.nickname.decode('utf8')))
rcon_server.cointosser.activate((this_player, other_player))

result = random.choice(('heads', 'tails'))
await user.public_reply('{}!'.format(result.upper()))
await asyncio.sleep(0.2)
server.cointosser.reset()
this_player = other_player = None
for i in server.active_duel_pair:
if i.nickname == user.unique_id():
this_player = i
else:
await user.public_reply('{} ^2wins ^3the cointoss!'.format(other_player.nickname.decode('utf8')))
rcon_server.cointosser.activate((other_player, this_player))
other_player = i
assert this_player and other_player, (this_player, other_player)
if side == result:
await user.public_reply('{} ^2wins ^3the cointoss!'.format(this_player.nickname.decode('utf8')))
server.cointosser.activate((this_player, other_player))
else:
await user.public_reply('{} ^2wins ^3the cointoss!'.format(other_player.nickname.decode('utf8')))
server.cointosser.activate((other_player, this_player))
await asyncio.sleep(0.3)
await user.public_reply(rcon_server.cointosser.format_status())
await user.public_reply(server.cointosser.format_status())

async def run_cancel(self, server, user):
await user.public_reply('Not yet implemented. Vcall endmatch.')

async def run(self, user, message, is_private=True, root=None):
rcon_server = self.parent.properties['rcon_server']
if not rcon_server.cointosser:
await user.reply('^1Cointoss not enabled on this server^7')
return
message = message.lower().strip()
if message in ('', 'status'):
await self.run_status(rcon_server, user)
elif message in ('heads', 'tails'):
await self.run_toss(rcon_server, user, message)
elif message == 'cancel':
await self.run_cancel(rcon_server, user)
else:
await user.reply('Unknown command {}. Available commands: status, heads, tails, cancel.'.format(message))


class PickDropBase(ChatCommand):
Expand Down
67 changes: 45 additions & 22 deletions xanmel/modules/xonotic/cointoss.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,9 +143,9 @@ def format_status(self):
expected_player = self.players[current_step['player'] - 1]

if current_step['action'] == CointosserAction.P:
res.append('^7{}, ^3please pick a map using ^2/pick ^5<mapname>'.format(expected_player.nickname.decode('utf8')))
res.append('^7{}, ^3please ^2pick^3 a map using ^2/pick ^5<mapname>'.format(expected_player.nickname.decode('utf8')))
else:
res.append('^7{}, ^3please drop a map using ^2/drop ^5<mapname>'.format(expected_player.nickname.decode('utf8')))
res.append('^7{}, ^3please ^1drop^3 a map using ^1/drop ^5<mapname>'.format(expected_player.nickname.decode('utf8')))
return res

def get_total_score(self):
Expand All @@ -168,33 +168,56 @@ def start_playing(self):

async def map_ended(self, map_name: str, scores: Dict[Player, int]) -> None:
if self.selected_maps[self.current_map_index] != map_name:
# FIXME: HOW TO HANDLE THIS?
self.rcon_server.say('Expected map {}, got map {}'.format(self.selected_maps[self.current_map_index],
map_name))
await asyncio.sleep(1)
self.rcon_server.say(self.format_status())
await asyncio.sleep(3)
self.gotomap()
return
if len(scores) != 2:
# FIXME: HOW TO HANDLE THIS?
self.rcon_server.say('Got scores with {} players, expected a duel!'.format(len(scores)))
await asyncio.sleep(1)
self.rcon_server.say(self.format_status())
await asyncio.sleep(3)
self.gotomap()
return
frags = [0, 0]
if int(self.rcon_server.cvars['xanmel_wup_stage']) != 1:
self.rcon_server.say('Match ended during warmup stage, restarting!')
await asyncio.sleep(1)
self.rcon_server.say(self.format_status())
await asyncio.sleep(3)
self.gotomap()
return

players = list(scores.keys())
if not (players[0].crypto_idfp and players[1].crypto_idfp):
# TODO: TEST THIS CASE!!!
if players[0].nickname == self.players[0].nickname:
frags = (scores[players[0]], scores[players[1]])
else:
frags = (scores[players[1]], scores[players[0]])
elif players[0].crypto_idfp:
if players[0].crypto_idfp == self.players[0].crypto_idfp:
frags = (scores[players[0]], scores[players[1]])
else:
frags = (scores[players[1]], scores[players[0]])

expected_crypto_idfps = [i.crypto_idfp for i in self.players]
for i in players:
if i.crypto_idfp not in expected_crypto_idfps:
self.rcon_server.say('Unexpected player {}, expected {} and {}! Restarting.'.format(
i.nickname.decode('utf8'),
self.players[0].nickname.decode('utf8'),
self.players[1].nickname.decode('utf8')
))
await asyncio.sleep(1)
self.rcon_server.say(self.format_status())
await asyncio.sleep(3)
self.gotomap()
return

if players[0].crypto_idfp == self.players[0].crypto_idfp:
frags = (scores[players[0]], scores[players[1]])
else:
if players[1].crypto_idfp == self.players[1].crypto_idfp:
frags = (scores[players[0]], scores[players[1]])
else:
frags = (scores[players[1]], scores[players[0]])
print(scores)
print(frags)
frags = (scores[players[1]], scores[players[0]])

if frags[0] == frags[1]:
self.rcon_server.say('Scores are equal, expected a winner! Restarting.')
await asyncio.sleep(1)
self.rcon_server.say(self.format_status())
await asyncio.sleep(3)
self.gotomap()
return
self.scores.append(tuple(frags))
games, _ = self.get_total_score()
if (max(games) > len(self.selected_maps) / 2) or (self.current_map_index == len(self.selected_maps) - 1):
Expand Down
4 changes: 2 additions & 2 deletions xanmel/modules/xonotic/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,7 @@ async def handle(self, event):
player = event.properties['player']
server = event.properties['server']

if player.elo_url:
await player.get_elo()
await player.get_elo()
self.module.xanmel.loop.create_task(player.update_identification())
if not player.really_joined:
return
Expand Down Expand Up @@ -568,6 +567,7 @@ class CointossChoiceCompleteHandler(Handler):

async def handle(self, event):
server = event.properties['server']
await asyncio.sleep(2)
server.say('^3Starting rounds in ^25 ^3seconds!^7')
await asyncio.sleep(5)
server.cointosser.start_playing()
Expand Down
14 changes: 9 additions & 5 deletions xanmel/modules/xonotic/players.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import logging
from concurrent.futures import ThreadPoolExecutor
from urllib.parse import unquote
from urllib.parse import unquote, quote
import asyncio

import datetime
Expand Down Expand Up @@ -77,9 +77,12 @@ async def get_db_obj_anon(self):
return await self.server.db.mgr.create(DBPlayer, raw_nickname=raw_nickname, nickname=nickname)

async def get_elo(self):
if self.elo_url is None:
self.crypto_idfp = await self.server.prvm_edictget(self.number2, 'crypto_idfp')
if not self.crypto_idfp:
return
self.crypto_idfp = self.get_crypto_idfp()
print(self.crypto_idfp)
quoted_crypto_idfp = quote(quote(quote(self.crypto_idfp, safe='')))
self.elo_url = 'http://stats.xonotic.org/player/{}/elo.txt'.format(quoted_crypto_idfp)
sig = self.server.config.get('elo_request_signature')
if not sig:
return
Expand Down Expand Up @@ -108,8 +111,9 @@ async def get_elo(self):
logger.debug('Failed to parse elo %s', text, exc_info=True)
else:
logger.debug('Got basic elo for %r', self.nickname)
await self.update_db()
logger.debug('DB updated for %r', self.nickname)
if self.server.db.is_up:
await self.update_db()
logger.debug('DB updated for %r', self.nickname)
await self.get_elo_advanced()
logger.debug('Got advanced elo for %r', self.nickname)
return
Expand Down
13 changes: 13 additions & 0 deletions xanmel/modules/xonotic/rcon.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,4 +202,17 @@ def say(self, message: Union[str, list, tuple], nick: str=None) -> None:
else:
self.say_say(message, nick)

async def prvm_edictget(self, entity_id, variable, program_name='server'):
internal_variable = 'xanmel_{}_{}_{}'.format(program_name, entity_id, variable)
self.send('prvm_edictget {} {} {} {}'.format(
program_name,
entity_id,
variable,
internal_variable
))
self.cvars.pop(internal_variable, None)
await self.execute_with_retry(internal_variable,
lambda: internal_variable in self.cvars)
return self.cvars[internal_variable]


2 changes: 1 addition & 1 deletion xanmel/modules/xonotic/rcon_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,7 @@ class RconLogParser(CombinedParser):
GameStartedParser,
NameChangeParser,
ChatMessageParser,
EloParser,
# EloParser,
VoteKeeptwoParser,
VoteFinishedParser,
VoteVcallParser,
Expand Down
1 change: 1 addition & 0 deletions xanmel/modules/xonotic/test/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ def xon_server(xon_module, mocked_coro):
srv = xon_module.servers[0]
srv.update_server_stats_orig = srv.update_server_stats
srv.update_server_stats = mocked_coro()
srv.prvm_edictget = mocked_coro(return_value=None)
return srv


Expand Down

0 comments on commit ddb2013

Please sign in to comment.