Commit 8b183d5f authored by talljosh's avatar talljosh

Merging to include unstable, in preparation for releasing version 1.12.0.

......@@ -12,6 +12,13 @@
<option name="DICT_NEW_LINE_BEFORE_RIGHT_BRACE" value="true" />
<option name="OPTIMIZE_IMPORTS_JOIN_FROM_IMPORTS_WITH_SAME_SOURCE" value="true" />
</Python>
<codeStyleSettings language="HTML">
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="4" />
<option name="TAB_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="Python">
<option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
<option name="ALIGN_MULTILINE_PARAMETERS_IN_CALLS" value="false" />
......
v1.12.0
* Added indicator lights around orbs to show the ownership status of
surrounding zones.
* Added box on minimap showing current view area.
* Added guage to all ghosts to show how close they are to respawning.
* Adjusted item costs based on actual usage in a tournament.
* Display more timing info when timing info is enabled.
* Trosnoth server can now run multiple arenas, each of which runs in its
own process. Clients have the option of which arena to join.
* Updates to server web interface, including admin controls to pause and
resume games, reset to lobby, and enable/disable shooting and zone caps
for each team.
* Added new levels: Cat Among Pigeons, Hunted, Free for All, Orb Chase.
* In the lobby, players can vote for the next level to play from the top menu.
* Spectators are no longer prompted to join the game every time the map
resets.
* When joining a game, the join screen now only shows the teams allowed by
the current level.
* Fixed and updated server game record displays, including updating player
score calculation, and fixing record of player teams.
* Fixed Trosball bug where sometimes the ball would start with a player
rather than starting in the centre.
* Fixed bug in chat display that caused issues after a player had received
100 chat messages.
* Fixed some bugs in bots.
* Updated logging to show asctime rather than milliseconds since startup.
* Other minor bug fixes.
v1.11.0
* Standard games now automatically add a bot to the game to keep teams even.
* Player movement is now based on momentum (retro mode can be activated
......
#!/usr/bin/env python
from __future__ import print_function
import logging
from optparse import OptionParser
import os
......@@ -14,9 +15,6 @@ from trosnoth.game import RemoteGame
from trosnoth.gamerecording import replays
from trosnoth.gui.screenManager.screenManager import ScreenFonts
from trosnoth.gui.sound.soundPlayer import SoundPlayer
from trosnoth.model.gameStates import (
InProgress, Solo, Trosball, SoloTrosball,
)
from trosnoth.model.mapLayout import LayoutDatabase
from trosnoth.settings import DisplaySettings
from trosnoth.trosnothgui.ingame import viewManager
......@@ -42,10 +40,10 @@ CAMERA_TERMINAL_VELOCITY = 4800 # game units / s
CAMERA_ACCELERATION = 2400 # game units / s / s
SHOW_EVERY = 1
MINIMAP_MAX_SIZE = (100000, 100000)
MINIMAP_ONLY = True
MINIMAP_ONLY = False
# To speed things up when debugging
NO_ZOOM = True
NO_ZOOM = False
NO_VIDEOS = False
NO_COUNTDOWN = False
......@@ -61,7 +59,7 @@ class CustomArgVideoWriter(VideoWriter):
import subprocess
cmd = [
self.convert_command, '-loglevel', 'error', '-f', 'rawvideo',
'ffmpeg', '-loglevel', 'error', '-f', 'rawvideo',
'-pix_fmt', 'rgb24', '-s', '%dx%d' %(self.width, self.height),
'-r', str(self.fps), '-i', '-']
codecs_map = {
......@@ -76,6 +74,7 @@ class CustomArgVideoWriter(VideoWriter):
vcodec = self.fourcc
cmd += ['-vcodec', vcodec, '-b:v', self.video_bitrate]
cmd += [self.filename]
log.debug('Calling %r', cmd)
self.proc = subprocess.Popen(cmd, stdin=subprocess.PIPE)
......@@ -101,7 +100,7 @@ class FakeSoundPlayer(SoundPlayer):
def setTime(self, t):
self.time = t
def addSound(self, filename, action, maxSimultaneous=8):
def addSound(self, filename, action, channel=None):
self.sounds[action] = AudioSegment.from_ogg(getPath(sound, filename))
def play(self, action, volume=1):
......@@ -155,15 +154,15 @@ class EventListener(object):
self.app.soundPlayer.play(sound)
def subscribe(self):
self.world.onGameOver.addListener(self.gameOver)
self.world.onGameStateChanged.addListener(self.startGame)
self.world.onSound.addListener(self.gotSound)
self.world.onGrenadeExplosion.addListener(self.grenadeExploded)
self.world.onTrosballExplosion.addListener(self.trosballExploded)
self.world.onBomberExplosion.addListener(self.bomberExploded)
self.world.onPlayerAdded.addListener(self.playerAdded)
self.world.onPlayerRemoved.addListener(self.playerRemoved)
# TODO: trosball goal sound
def gotSound(self, filename):
self.app.soundPlayer.playFromServerCommand(filename)
def playerAdded(self, player):
player.onShotFired.addListener(self.shotFired)
......@@ -173,13 +172,6 @@ class EventListener(object):
player.onShotFired.removeListener(self.shotFired)
player.onUsedUpgrade.removeListener(self.upgradeUsed)
def gameOver(self, *args, **kwargs):
self.playSound('gameLose')
def startGame(self, *args, **kwargs):
if self.world.state in (InProgress, Solo, Trosball, SoloTrosball):
self.playSound('startGame')
def grenadeExploded(self, pos, radius):
self.playSound('explodeGrenade')
self.app.worldgui.addExplosion(pos)
......@@ -614,7 +606,7 @@ class Main(object):
if MINIMAP_ONLY:
filenames = [minimapVid]
else:
filename = [replayVid, minimapVid, progressVid, messageVid]
filenames = [replayVid, minimapVid, progressVid, messageVid]
for filename in filenames:
if os.path.exists(filename):
os.unlink(filename)
......@@ -689,7 +681,7 @@ class Main(object):
finally:
for w in writers:
w.release()
print 'Saving audio...'
print('Saving audio...')
self.soundPlayer.save(
os.path.join(self.opts.output_path, 'replay.mp3'))
......
#!/usr/bin/env python
# coding=utf-8
# Make sure that this version of trosnoth is run.
from __future__ import print_function
import os
import sys
sys.path.insert(0, os.path.abspath(os.path.dirname(__file__)))
# Init django environment for database use
import django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'trosnoth.server.settings')
django.setup()
from trosnoth.const import POINT_VALUES
from trosnoth.djangoapp.models import GameRecord
def get_score(player):
score = 0
entry = {
'player': player,
'nick': player.user.nick if player.user else player.botName,
'accuracy': (100.0 * player.shotsHit / player.shotsFired
) if player.shotsFired else 0.,
'score': 0,
'kdr': u'{:2.2f}'.format(player.kills / player.deaths
) if player.deaths else u'∞',
'adr': u'{:2.2f}'.format(player.timeAlive / player.timeDead
) if player.timeDead else u'∞',
}
for stat, weighting in POINT_VALUES.items():
if stat in entry:
value = entry[stat]
else:
value = getattr(player, stat)
score += value * weighting
return score
player_scores = {}
for parameter in sys.argv[1:]:
game_id = int(parameter)
game = GameRecord.objects.get(pk=game_id)
for player in game.gameplayer_set.all():
if player.user:
username = player.user.username
player_scores.setdefault(username, 0)
player_scores[username] += get_score(player)
scores = [(score, username) for username, score in player_scores.items()]
scores.sort(reverse=True)
for score, player in scores:
print('{}\t{:.2f}'.format(player, score))
#!/usr/bin/env python
from __future__ import print_function
from lxml import etree
import os
from subprocess import Popen, PIPE, STDOUT
......@@ -445,17 +446,17 @@ def isSymmetrical(obstacles):
def warning(message):
print >> sys.stderr, 'WARNING: ' + message
print('WARNING: ' + message, file=sys.stderr)
def error(message):
print >> sys.stderr, 'ERROR: ' + message
print('ERROR: ' + message, file=sys.stderr)
sys.exit(1)
if __name__ == '__main__':
try:
print run_export(sys.argv[1])
print(run_export(sys.argv[1]))
except SystemExit:
raise
except:
......
......@@ -17,6 +17,7 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.
from __future__ import print_function
from lxml import etree
import os
import sys
......@@ -208,11 +209,11 @@ def storeGraphics(filename, data):
try:
open(imgPath, 'wb').write(data.decode('base64'))
except IOError:
print >> sys.stderr, 'WARNING: could not write image data'
print('WARNING: could not write image data', file=sys.stderr)
if __name__ == '__main__':
try:
print run_import(sys.argv[1])
print(run_import(sys.argv[1]))
except:
traceback.print_exc(file=sys.stderr)
sys.exit(1)
......@@ -66,7 +66,7 @@ def main():
'data_files': data,
'options': {
'py2exe': {
'includes': 'zope.interface,pygame._view,trosnoth.bots.john,trosnoth.bots.ranger',
'includes': 'zope.interface,pygame._view,trosnoth.bots.john,trosnoth.bots.ranger,trosnoth.bots.balance,trosnoth.bots.sirrobin,trosnoth.bots.puppet,trosnoth.bots.onezone',
},
},
}
......
......@@ -104,9 +104,16 @@ class AIAgent(ConcreteAgent):
self.ai = None
self.team = None
self.requestedNick = None
self._onBotSet = Event()
self._onBotSet = Event([])
self._loop = WeakLoopingCall(self, '_tick')
def __str__(self):
if self.ai:
bot = self.ai
else:
bot = 'None (was {})'.format(self.aiClass.__name__)
return '{}<{}>'.format(self.__class__.__name__, bot)
def start(self, team=None):
self.team = team
self._loop.start(2)
......@@ -169,21 +176,19 @@ class AIAgent(ConcreteAgent):
if player:
self.requestedNick = None
self.ai = self.aiClass(self.world, self.localState.player, self)
self._onBotSet()
self._onBotSet(self.ai)
self._onBotSet.clear()
@defer.inlineCallbacks
def getBot(self):
'''
@return: a Deferred which fires with this agent's Bot object,
as soon as it has one.
'''
d = defer.Deferred()
if self.ai is not None:
d.callback(self.ai)
else:
self._onBotSet.addListener(d.callback, weak=False)
return d
defer.returnValue(self.ai)
yield self._onBotSet.wait()
defer.returnValue(self.ai)
@TickMsg.handler
def handle_TickMsg(self, msg):
......@@ -236,6 +241,11 @@ class Bot(MessageConsumer):
self.start()
def __str__(self):
return '{}: {}'.format(
self.__class__.__name__,
repr(self.player.nick) if self.player else '-')
def sendRequest(self, msg):
self.agent.sendRequest(msg)
......
......@@ -78,6 +78,7 @@ class MoveGhostToPoint(BotOrder):
super(MoveGhostToPoint, self).__init__(bot)
self.pos = pos
self.stopOnZoneEntry = stopOnZoneEntry
self.recheckIn = 0
def start(self):
self._doAim()
......@@ -90,9 +91,11 @@ class MoveGhostToPoint(BotOrder):
self.bot.sendRequest(
AimPlayerAtMsg(theta, 1.0, self.bot.world.lastTickId))
self.recheckIn = 20
def tick(self):
if self.bot.player.ghostThrust == 0:
self.recheckIn -= 1
if self.bot.player.ghostThrust == 0 or self.recheckIn <= 0:
self._doAim()
if self.stopOnZoneEntry:
......
......@@ -7,6 +7,7 @@ from trosnoth.bots.goalsetter import (
)
from trosnoth.const import (
BOT_GOAL_HUNT_RABBITS, BOT_GOAL_SCORE_TROSBALL_POINT, BOT_GOAL_CAPTURE_MAP,
BOT_GOAL_KILL_THINGS,
)
from trosnoth.model import upgrades
from trosnoth.utils.math import distance
......@@ -42,6 +43,8 @@ class ReachCurrentObjective(Goal):
self.setSubGoal(HuntTheRabbits(self.bot, self))
elif botGoal == BOT_GOAL_CAPTURE_MAP:
self.setSubGoal(WinStandardTrosnothGame(self.bot, self))
elif botGoal == BOT_GOAL_KILL_THINGS:
self.setSubGoal(HuntEnemies(self.bot, self))
else:
self.setSubGoal(RunAroundKillingHumans(self.bot, self))
......@@ -98,6 +101,7 @@ class RunAroundKillingThings(Goal):
def stop(self):
super(RunAroundKillingThings, self).stop()
self.cancelNextCheck()
def scheduleNextCheck(self):
self.cancelNextCheck()
......@@ -159,17 +163,17 @@ class RunAroundKillingHumans(RunAroundKillingThings):
for p in zone.players)
class HuntTheRabbits(Goal):
class HuntingGoal(Goal):
def start(self):
self.huntedPlayer = None
self.bot.onOrderFinished.addListener(self.orderFinished)
self.bot.player.onRespawned.addListener(self.respawned)
super(HuntTheRabbits, self).start()
super(HuntingGoal, self).start()
def stop(self):
self.bot.onOrderFinished.removeListener(self.orderFinished)
self.bot.player.onRespawned.removeListener(self.respawned)
super(HuntTheRabbits, self).stop()
super(HuntingGoal, self).stop()
def orderFinished(self):
if self.subGoal is None:
......@@ -190,12 +194,7 @@ class HuntTheRabbits(Goal):
rabbits = [
p for p in self.bot.world.players if self.isEnemyRabbit(p)]
if not rabbits:
if self.bot.player.dead:
self.bot.respawn()
return
# All survining rabbits are on this player's team
self.setSubGoal(RunAway(self.bot, self))
self.noMoreRabbits()
return
self.huntedPlayer = random.choice(rabbits)
......@@ -209,6 +208,29 @@ class HuntTheRabbits(Goal):
self.bot.attackPlayer(self.huntedPlayer)
def noMoreRabbits(self):
raise NotImplementedError('{}.noMoreRabbits'.format(
self.__class__.__name__))
class HuntTheRabbits(HuntingGoal):
def noMoreRabbits(self):
if self.bot.player.dead:
self.bot.respawn()
return
# All survining rabbits are on this player's team
self.setSubGoal(RunAway(self.bot, self))
class HuntEnemies(HuntingGoal):
def noMoreRabbits(self):
if self.bot.player.dead:
self.bot.respawn()
return
self.bot.world.callLater(3, self.reevaluate)
self.setSubGoal(MessAroundInZone(self.bot, self))
class RunAway(Goal):
'''
......
import logging
import random
from trosnoth.bots.goalsetter import GoalSetterBot, Goal, MessAroundInZone
log = logging.getLogger(__name__)
class BravelyRunAway(Goal):
'''
He was not at all afraid to be killed in nasty ways,
Brave, brave, brave, brave Sir Robin!
'''
def __init__(self, bot, parent):
super(BravelyRunAway, self).__init__(bot, parent)
self.nextCheck = None
self.messing = False
def start(self):
self.bot.setAggression(False)
self.bot.setDodgesBullets(True)
self.bot.onOrderFinished.addListener(self.orderFinished)
self.bot.onTick.addListener(self.tick)
super(BravelyRunAway, self).start()
self.scheduleNextCheck()
def stop(self):
super(BravelyRunAway, self).stop()
self.bot.onOrderFinished.removeListener(self.orderFinished)
self.bot.onTick.removeListener(self.tick)
self.cancelNextCheck()
def scheduleNextCheck(self):
self.cancelNextCheck()
self.nextCheck = self.bot.world.callLater(5, self.reevaluate)
def cancelNextCheck(self):
if self.nextCheck:
self.nextCheck.cancel()
self.nextCheck = None
def tick(self):
if self.messing and self.inScaryZone():
self.reevaluate()
def orderFinished(self):
self.reevaluate()
def reevaluate(self):
self.cancelNextCheck()
self.messing = False
if self.bot.player.dead:
self.setSubGoal(RespawnFarAway(self.bot, self))
return
if self.inScaryZone():
self.runAway()
else:
self.messing = True
self.setSubGoal(MessAroundInZone(self.bot, self))
self.scheduleNextCheck()
def inScaryZone(self):
thisPlayer = self.bot.player
zone = thisPlayer.getZone()
if zone is None:
return True
return any(not thisPlayer.isFriendsWith(p) for p in zone.players)
def runAway(self):
player = self.bot.player
curZone = player.getZone()
neighbours = list(curZone.getUnblockedNeighbours())
if curZone is None:
targetZone = random.choice(list(self.bot.world.zones))
elif not neighbours:
targetZone = curZone
else:
targetZone = getBestZone(player, neighbours)
self.setSubGoal(None)
self.bot.moveToOrb(targetZone)
def getBestZone(player, zones, prefer=None):
enemiesByZone = {
z: sum(not player.isFriendsWith(p) for p in z.players)
for z in zones
}
minEnemies = min(enemiesByZone.values())
okZones = [z for z in zones if enemiesByZone[z] == minEnemies]
if prefer and prefer in okZones:
return prefer
return random.choice(okZones)
class RespawnFarAway(Goal):
'''
Respawns somewhere away from enemies.
'''
def start(self):
self.bot.onOrderFinished.addListener(self.orderFinished)
super(RespawnFarAway, self).start()
def stop(self):
super(RespawnFarAway, self).stop()
self.bot.onOrderFinished.removeListener(self.orderFinished)
def orderFinished(self):
self.reevaluate()
def reevaluate(self):
player = self.bot.player
if not player.dead:
self.returnToParent()
return
curZone = player.getZone()
okZones = [
z for z in self.bot.world.zones
if player.isZoneRespawnable(z)]
zone = getBestZone(player, okZones, prefer=curZone)
if zone != curZone:
self.bot.moveToOrb(zone)
return
if player.timeTillRespawn > 0:
self.bot.world.callLater(player.timeTillRespawn, self.reevaluate)
else:
self.bot.respawn()
class SirRobinBot(GoalSetterBot):
nick = 'SirRobinBot'
generic = True
MainGoalClass = BravelyRunAway
BotClass = SirRobinBot
from trosnoth.bots.goalsetter import Goal
from trosnoth.bots.ranger import RangerBot, HuntEnemies
class KillThings(Goal):
'''
Just kill things. Forever.
'''
def start(self):