Commit c9d3c2eb authored by Phedg1Studios's avatar Phedg1Studios
Browse files

Juggernaut fixes.

Fixed players not being awarded coins for killing the juggernaut.
Added a world healthDecreased event.
Added coin reward for damaging the juggernaut.
Juggernaut is now awarded to a random player that did damage to the juggernaut when it dies.
Juggernaut is now highlighted on the minimap.
Juggernaut is no longer always awarded to the player with the best connection at the beginning of a match.
The juggernaut game mode now ends at the correct number of points.
Moved juggernaut messages from gameplay to special.

--HG--
branch : Phedg1
parent aa4289f3f344
......@@ -16,6 +16,7 @@ from trosnoth.const import (
BOT_GOAL_KILL_THINGS, ACHIEVEMENT_AFTER_GAME,
ACHIEVEMENT_TACTICAL, TEAM_1_COLOUR, TEAM_2_COLOUR,
BONUS_COINS_FOR_WINNER, BONUS_COINS_FOR_RABBIT_SURVIVAL,
COINS_PER_KILL,
)
from trosnoth.levels.base import (
Level, play_level,
......@@ -28,13 +29,13 @@ from trosnoth.model.universe_base import NEUTRAL_TEAM_ID
from trosnoth.triggers.coins import (
SlowlyIncrementLivePlayerCoinsTrigger, AwardStartingCoinsTrigger,
)
from trosnoth.messages import ChangeHeadMsg, RespawnMsg
from trosnoth.messages import ChangeHeadMsg, RespawnMsg, SetCustomHealthMaxMsg, SetHealthMsg, NewJuggernautMsg
from trosnoth.triggers.rabbits import RabbitHuntTrigger
from trosnoth.triggers.juggernaut import (
JuggernautVictoryTrigger, PlayerKillScoreTrigger,
PlayerKillChangeTeamTrigger, MakeAllPlayersHuntersTrigger,
MakeSureThereIsAJuggernautTrigger, ForceJuggernautRespawn,
AddLimitedBotsTrigger,
AddLimitedBotsTrigger, PlayerHealthDecrasedTrigger, WaitForNoResyncing
)
from trosnoth.utils.event import waitForEvents
......@@ -83,11 +84,14 @@ class JuggernautMatchBase(Level):
self.juggernautIsDead = True
self.deadTicks = 0
self.juggernaut = None
self.badPlayerId = b'0'
self.juggernautCandidates = []
def getDuration(self):
return None
def pre_sync_setup(self):
#AwardStartingCoinsTrigger(self).activate()
super().pre_sync_setup()
self.hunter_team, self.juggernaut_team = self.pre_sync_create_teams(
[
......@@ -100,7 +104,7 @@ class JuggernautMatchBase(Level):
async def run(self):
team_option = self.level_options.get_team_option(self)
SlowlyIncrementLivePlayerCoinsTrigger(self).activate()
#SlowlyIncrementLivePlayerCoinsTrigger(self).activate()
await self.pregameCountdownPhase()
......@@ -119,24 +123,31 @@ class JuggernautMatchBase(Level):
return self.a_human_player_won, None
async def pregameCountdownPhase(self):
self.world.abilities.set(respawn=False, zoneCaps=False, balanceTeams=False)
startingCoinsTrigger = AwardStartingCoinsTrigger(self).activate()
self.setMainGameUserInfo()
self.world.clock.startCountDown(self.countdown_time, flashBelow=0)
self.world.clock.propagateToClients()
await self.world.clock.onZero.wait_future()
self.makeSureThereIsAMinimumNumberOfPlayersTrigger = AddLimitedBotsTrigger(self, MIN_BOTS, MAX_BOTS, BOT_CLASS, BOT_NICK, self.hunter_team).activate()
await self.makeSureThereIsAMinimumNumberOfPlayersTrigger.botsAdded.wait_future()
waitForNoResyncing = WaitForNoResyncing(self).activate()
await waitForNoResyncing.noResyncing.wait_future()
self.world.pauseStats()
self.world.abilities.set(respawn=False, leaveFriendlyZones=False, zoneCaps=False, balanceTeams=False)
self.world.onChangeVoiceChatRooms(self.world.teams, self.world.players)
await self.world.clock.onZero.wait_future()
startingCoinsTrigger.deactivate()
async def mainGamePhase(self):
self.makeAllPlayersHuntersTrigger.deactivate()
winTrigger = JuggernautVictoryTrigger(self, WINNING_SCORE).activate()
killScoreTrigger = PlayerKillScoreTrigger(self, KILL_SCORE, DIE_SCORE).activate()
playerHealthDecreasedTrigger = PlayerHealthDecrasedTrigger(self, self.juggernaut_team, COINS_PER_KILL).activate()
killTeamSwapTrigger = PlayerKillChangeTeamTrigger(self, True, self.juggernaut_team).activate()
self.makeSureThereIsAMinimumNumberOfPlayersTrigger = AddLimitedBotsTrigger(self, MIN_BOTS, MAX_BOTS, BOT_CLASS, BOT_NICK, self.hunter_team).activate()
makeSureThereIsAJuggernautTrigger = MakeSureThereIsAJuggernautTrigger(self, self.juggernaut_team, self.hunter_team).activate()
forceJuggernautRespawn = ForceJuggernautRespawn(self).activate()
......@@ -161,6 +172,7 @@ class JuggernautMatchBase(Level):
winTrigger.deactivate()
killScoreTrigger.deactivate()
playerHealthDecreasedTrigger.doDeactivate()
killTeamSwapTrigger.deactivate()
makeSureThereIsAJuggernautTrigger.deactivate()
forceJuggernautRespawn.deactivate()
......@@ -168,12 +180,17 @@ class JuggernautMatchBase(Level):
for player in self.world.players:
if player.team == self.juggernaut_team:
player.customHealthMax = 0
SetCustomHealthMaxMsg(player.id, player.customHealthMax)
healthOld = player.health
player.health = min(player.health, 1)
player.sendResync()
if healthOld != player.health:
SetCustomHealthMaxMsg(player.id, player.health)
player.head = self.juggernautHeadOld
self.world.sendServerCommand(ChangeHeadMsg(player.id, player.head))
self.world.sendServerCommand(NewJuggernautMsg(self.badPlayerId))
def respawnJuggernaut(self):
for zone in self.juggernautZones.keys():
self.world.sendServerCommand(RespawnMsg(self.juggernaut.id, self.juggernautZones[zone].id))
......@@ -238,24 +255,31 @@ class RandomJuggernautLevel(JuggernautMatchBase):
for zone in self.world.map.layout.zones:
zoneState = self.world.map.getZoneAtPoint(zone.pos)
zoneState.setOwnership(self.hunter_team, False)
zoneState.setOwnership(self.hunter_team, True)
self.world.onZoneStateChanged(zoneState)
self.juggernautZones = {}
layout = self.world.map.layout
startPoint = (layout.centreX, layout.centreY - 50)
for direction in [1, -1]:
currentOffset = 0
for directionX in [1, -1]:
currentOffsetX = 0
while True:
currentZoneState = self.world.map.getZoneAtPoint((startPoint[0], startPoint[1] + currentOffset * MapLayout.halfZoneHeight * 2))
currentZoneState = self.world.map.getZoneAtPoint((startPoint[0] + currentOffsetX * (MapLayout.zoneBodyWidth + MapLayout.zoneInterfaceWidth) * 2, startPoint[1]))
if currentZoneState != None:
self.juggernautZones[currentOffset] = currentZoneState
currentZoneState.setOwnership(self.juggernaut_team, True)
currentOffset += direction
for directionY in [1, -1]:
currentOffsetY = 0
while True:
currentZoneState = self.world.map.getZoneAtPoint((startPoint[0] + currentOffsetX * (MapLayout.zoneBodyWidth + MapLayout.zoneInterfaceWidth) * 2, startPoint[1] + currentOffsetY * MapLayout.halfZoneHeight * 2))
if currentZoneState != None:
self.juggernautZones[(currentOffsetX, currentOffsetY)] = currentZoneState
currentZoneState.setOwnership(self.juggernaut_team, True)
currentOffsetY += directionY
else:
break
currentOffsetX += directionX
else:
break
def getDuration(self):
return self.duration
......
......@@ -23,7 +23,8 @@ from trosnoth.messages.gameplay import ( # noqa
ResyncPlayerMsg, ResyncAcknowledgedMsg, PlayerHitMsg, PlayerAllDeadMsg,
UpdatePlayerStateMsg, AimPlayerAtMsg, ShootMsg, ShotFiredMsg,
FireShoxwaveMsg, ChatFromServerMsg, ChatMsg,
GrapplingHookMsg, EmoteMsg, EmoteRequestMsg, NewJuggernautMsg
GrapplingHookMsg, EmoteMsg, EmoteRequestMsg,
SetCustomHealthMaxMsg, SetHealthMsg,
)
from trosnoth.messages.setup import ( # noqa
ChangeNicknameMsg, PlayerIsReadyMsg, SetSuggestedTeamNameMsg, ChangeHeadMsg,
......@@ -38,7 +39,7 @@ from trosnoth.messages.special import ( # noqa
ThrowTrosballMsg, AchievementUnlockedMsg, SetUIOptionsMsg,
UpdateClockStateMsg, PlaySoundMsg, UpdateGameInfoMsg, SetWorldAbilitiesMsg,
UpdateScoreBoardModeMsg, SetTeamScoreMsg, SetPlayerScoreMsg,
SetTeamAbilitiesMsg, PingMsg, SetPlayerAbilitiesMsg,
SetTeamAbilitiesMsg, PingMsg, SetPlayerAbilitiesMsg, NewJuggernautMsg,
)
from trosnoth.messages.upgrades import ( # noqa
BuyUpgradeMsg, PlayerHasUpgradeMsg, PlayerCoinsSpentMsg,
......
......@@ -148,6 +148,7 @@ class ResyncAcknowledgedMsg(ClientCommand):
player = world.getPlayer(self.playerId)
player.resyncing = False
class RespawnMsg(ServerCommand):
idString = b'Resp'
fields = 'playerId', 'zoneId'
......@@ -560,8 +561,38 @@ class EmoteRequestMsg(AgentRequest):
def applyRequestToLocalState(self, localState):
localState.player.doEmote(self.emoteId)
class NewJuggernautMsg(ServerCommand):
idString = b'JugP'
fields = 'playerId'
packspec = 'c'
class SetHealthMsg(ServerCommand):
idString = b'StHl'
fields = 'playerId', 'health'
packspec = 'cI'
def applyOrderToWorld(self, world):
player = world.getPlayer(self.playerId)
if not player:
return
player.health = self.health
def applyOrderToLocalState(self, localState, world):
player = localState.player
if player and self.playerId == player.id:
player.health = self.health
class SetCustomHealthMaxMsg(ServerCommand):
idString = b'CHlM'
fields = 'playerId', 'health'
packspec = 'cI'
def applyOrderToWorld(self, world):
player = world.getPlayer(self.playerId)
if not player:
return
player.customHealthMax = self.health
def applyOrderToLocalState(self, localState, world):
player = localState.player
if player and self.playerId == player.id:
player.customHealthMax = self.health
......@@ -62,6 +62,15 @@ class ThrowTrosballMsg(AgentRequest):
game.world.trosballManager.throwTrosball()
class NewJuggernautMsg(ServerCommand):
idString = b'JugP'
fields = 'playerId'
packspec = 'c'
def applyOrderToWorld(self, world):
world.playerWithJuggernaut = world.getPlayer(self.playerId)
class AchievementUnlockedMsg(ServerCommand):
idString = b'Achm'
fields = 'playerId', 'achievementId'
......
......@@ -228,6 +228,7 @@ class Player(Unit):
result.onShotFired = Event()
result.onShotHurtPlayer = Event()
result.onHitByShot = Event()
result.onHealthDecreased = Event(['player', 'hitter', 'damage'])
result.onShieldDestroyed = Event()
result.onDestroyedShield = Event()
result.onPrivateChatReceived = Event()
......@@ -397,6 +398,7 @@ class Player(Unit):
def machineGunner(self):
return self.items.get(MachineGun)
@property
def hasSpeedyThing(self):
return self.items.get(SpeedyThing)
......@@ -1164,7 +1166,11 @@ class Player(Unit):
hitpoints = shield.hit(hitpoints, hitter, hitKind)
if hitpoints <= 0:
return
healthDecreased = self.health
self.health -= hitpoints
healthDecreased = healthDecreased - max(0, self.health)
if self.isCanonicalPlayer() and healthDecreased > 0:
self.world.onPlayerHealthDecreased(self, hitter, healthDecreased)
if self.health <= 0:
self.died(hitter, hitKind)
......
......@@ -790,6 +790,7 @@ class Universe(MessageConsumer):
self.onTeamChatReceived = Event() # (team, text, sender)
self.onPlayerKill = Event() # (killer, target, hitKind)
self.onPlayerRespawn = Event() # (player)
self.onPlayerHealthDecreased = Event(['player', 'hitter', 'damage'])
self.onGrenadeExplosion = Event() # (pos, radius)
self.onTrosballExplosion = Event() # (player)
self.onBomberExplosion = Event() # (player)
......@@ -805,6 +806,7 @@ class Universe(MessageConsumer):
self.isIncomplete = False
self.playerWithElephant = None
self.playerWithJuggernaut = None
self.physics = WorldPhysics(self)
self.playerWithId = {}
......@@ -1233,6 +1235,8 @@ class Universe(MessageConsumer):
],
'elephant': self.playerWithElephant.id
if self.playerWithElephant else None,
'juggernaut': self.playerWithJuggernaut.id
if self.playerWithJuggernaut else None,
'shots': [
{
'id': shot.id,
......@@ -1338,6 +1342,7 @@ class Universe(MessageConsumer):
upgradeClass.enabled = upgradeData['enabled']
self.playerWithElephant = self.getPlayer(data['elephant'])
self.playerWithJuggernaut = self.getPlayer(data['juggernaut'])
self.shotWithId = {}
for shotData in data['shots']:
......
......@@ -24,6 +24,7 @@ from trosnoth.messages import (
UpdateScoreBoardModeMsg, SetTeamScoreMsg, SetPlayerScoreMsg,
SetTeamAbilitiesMsg, EmoteMsg, AgentDetachedMsg, ChangeHeadMsg,
SetPlayerAbilitiesMsg, ChangeTeamMsg, NewJuggernautMsg,
SetCustomHealthMaxMsg, SetHealthMsg,
)
from trosnoth.network.base import MsgClientProtocol
from trosnoth.utils.event import Event
......@@ -95,6 +96,8 @@ clientMsgs = netmsg.MessageCollection(
PingMsg,
TickMsg,
NewJuggernautMsg,
SetCustomHealthMaxMsg,
SetHealthMsg,
)
......
......@@ -5,7 +5,7 @@ from trosnoth.utils.event import Event
from trosnoth.triggers.base import Trigger, DurationScoreTrigger
from trosnoth.messages import SetPlayerTeamMsg, ChangeHeadMsg
from trosnoth.messages import SetPlayerTeamMsg, ChangeHeadMsg, AwardPlayerCoinMsg, SetCustomHealthMaxMsg, SetHealthMsg, NewJuggernautMsg
from trosnoth.const import HEAD_CROSSHAIR
......@@ -18,20 +18,21 @@ def SetTeam(world, player, team):
world.sendServerCommand(SetPlayerTeamMsg(player.id, team.id))
def SetupJuggernaut(world, level, team, player):
player.customHealthMax = JUGGERNAUT_HEALTH
if player.health > 0:
player.health = JUGGERNAUT_HEALTH
world.sendServerCommand(SetHealthMsg(player.id, player.health))
level.juggernautIsDead = False
else:
level.juggernautIsDead = True
level.ticks = 0
player.sendResync()
world.sendServerCommand(SetCustomHealthMaxMsg(player.id, JUGGERNAUT_HEALTH))
level.juggernaut = player
level.juggernautHeadOld = player.head
world.sendServerCommand(ChangeHeadMsg(player.id, HEAD_CROSSHAIR))
SetTeam(world, player, team)
world.sendServerCommand(NewJuggernautMsg(player.id))
class JuggernautVictoryTrigger(Trigger):
'''
......@@ -43,17 +44,14 @@ class JuggernautVictoryTrigger(Trigger):
self.requiredScore = requiredScore
def doActivate(self):
pass
self.world.onPlayerKill.addListener(self.gotPlayerKill)
self.world.onScoreChanged.addListener(self.playerScored)
def doDeactivate(self):
self.world.onPlayerKill.removeListener(self.gotPlayerKill)
self.world.onScoreChanged.removeListener(self.playerScored)
def gotPlayerKill(self, killer, target, hitKind):
for player in self.world.scoreboard.playerScores.keys():
if self.world.scoreboard.playerScores[player] >= self.requiredScore:
self.onVictory([True])
break
def playerScored(self, player):
if self.world.scoreboard.playerScores[player] >= self.requiredScore:
self.onVictory([True])
class PlayerKillScoreTrigger(Trigger):
'''
......@@ -79,6 +77,31 @@ class PlayerKillScoreTrigger(Trigger):
self.world.scoreboard.playerScored(killer, self.killScore)
self.world.scoreboard.playerScored(target, self.dieScore)
class PlayerHealthDecrasedTrigger(Trigger):
'''
When a player is killed, award leaderboard points to the killer.
'''
def __init__(self, level, juggernautTeam, damageCoins):
super(PlayerHealthDecrasedTrigger, self).__init__(level)
self.level = level
self.juggernautTeam = juggernautTeam
self.damageCoins = damageCoins
def doActivate(self):
self.world.onPlayerHealthDecreased.addListener(self.playerHealthDecreased)
def doDeactivate(self):
self.world.onPlayerHealthDecreased.removeListener(self.playerHealthDecreased)
def playerHealthDecreased(self, player, hitter, damage):
if hitter != None:
if player.health <= 0:
damage -= 1
if damage > 0:
self.world.sendServerCommand(AwardPlayerCoinMsg(hitter.id, self.damageCoins))
self.level.juggernautCandidates.append(hitter)
class PlayerKillChangeTeamTrigger(Trigger):
'''
When a player is killed they are moved to the killer's team, and optionally the killer is moved to the player's old team
......@@ -92,20 +115,30 @@ class PlayerKillChangeTeamTrigger(Trigger):
def doActivate(self):
self.world.onPlayerKill.addListener(self.gotPlayerKill)
self.world.onPlayerRemoved.addListener(self.gotPlayerRemoved)
def doDeactivate(self):
self.world.onPlayerKill.removeListener(self.gotPlayerKill)
self.world.onPlayerRemoved.removeListener(self.gotPlayerRemoved)
def gotPlayerRemoved(self, player, *args, **kwargs):
candidates = len(self.level.juggernautCandidates)
for i in range(candidates):
if self.level.juggernautCandidates[candiates - 1 - i].id == player.id:
self.level.juggernautCandidates.pop(candiates - 1 - i)
def gotPlayerKill(self, killer, target, hitKind):
if not self.swapTeams or (self.swapTeams and target.team == self.targetTeam):
targetTeamOld = target.team
SetTeam(self.world, target, killer.team)
if len(self.level.juggernautCandidates) > 0:
targetTeamOld = target.team
SetTeam(self.world, target, killer.team)
if self.swapTeams:
target.customHealthMax = 0
target.sendResync()
self.world.sendServerCommand(ChangeHeadMsg(target.id, self.level.juggernautHeadOld))
SetupJuggernaut(self.world, self.level, targetTeamOld, killer)
if self.swapTeams:
self.world.sendServerCommand(SetCustomHealthMaxMsg(target.id, 0))
self.world.sendServerCommand(ChangeHeadMsg(target.id, self.level.juggernautHeadOld))
random.shuffle(self.level.juggernautCandidates)
SetupJuggernaut(self.world, self.level, targetTeamOld, self.level.juggernautCandidates[0])
self.level.juggernautCandidates = []
class MakeAllPlayersHuntersTrigger(Trigger):
'''
......@@ -135,6 +168,7 @@ class MakeSureThereIsAJuggernautTrigger(Trigger):
self.juggernautTeam = juggernautTeam
self.hunterTeam = hunterTeam
self.level = level
self.rebalanceRabitTeam()
def doActivate(self):
self.world.onPlayerAdded.addListener(self.gotPlayerAdded)
......@@ -146,10 +180,12 @@ class MakeSureThereIsAJuggernautTrigger(Trigger):
def gotPlayerAdded(self, player, *args, **kwargs):
SetTeam(self.world, player, self.hunterTeam)
self.rebalanceRabitTeam()
def gotPlayerRemoved(self, player, *args, **kwargs):
self.level.juggernaut = None
if self.level.juggernaut != None and self.level.juggernaut.id == player.id:
self.level.juggernaut = None
self.level.juggernautCandidates = []
self.world.sendServerCommand(NewJuggernautMsg(self.level.badPlayerId))
self.rebalanceRabitTeam()
def rebalanceRabitTeam(self):
......@@ -202,28 +238,19 @@ class AddLimitedBotsTrigger(Trigger):
self.botTeam = botTeam
self.bots = []
self._checking = False
self.botsAdded = Event([])
self.apply()
def doActivate(self):
self.world.onPlayerAdded.addListener(self.gotPlayerAdded)
self.world.onPlayerRemoved.addListener(self.gotPlayerRemoved)
self._check()
pass
def doDeactivate(self):
self.world.onPlayerAdded.removeListener(self.gotPlayerAdded)
self.world.onPlayerRemoved.removeListener(self.gotPlayerRemoved)
bots = self.bots
self.bots = []
for agent in bots:
self._stopBot(agent)
def gotPlayerAdded(self, player, *args, **kwargs):
self._check()
def gotPlayerRemoved(self, player, *args, **kwargs):
self._check()
def _stopBot(self, agent=None):
if not agent:
agent = self.bots.pop(-1)
......@@ -231,17 +258,17 @@ class AddLimitedBotsTrigger(Trigger):
self.world.game.detachAgent(agent)
@defer.inlineCallbacks
def _check(self):
def apply(self):
if self._checking:
return
self._checking = True
try:
while True:
humans = 0
for player in self.world.players:
if player.bot == False:
humans += 1
botCount = len(self.bots)
humans = 0
for player in self.world.players:
if player.bot == False:
humans += 1
botCount = len(self.bots)
target = max(0, self.minBots, self.maxBots - humans)
if botCount == target:
break
......@@ -250,12 +277,40 @@ class AddLimitedBotsTrigger(Trigger):
self._stopBot()
else:
nick = '{} #{}'.format(self.botNick, botCount + 1)
# Pretect against tricksy humans editing their nicks
while any(p.nick == nick for p in self.world.players):
nick += "'"
agent = yield self.level.addBot(
self.botTeam, nick, self.botClass)
# Pretect against tricksy humans editing their nicks
while any(p.nick == nick for p in self.world.players):
nick += "'"
agent = yield self.level.addBot(
self.botTeam, nick, self.botClass)
self.bots.append(agent)
finally:
yield
self._checking = False
self.botsAdded()
class WaitForNoResyncing(Trigger):
'''
Maintains a minimum number of players in a game
'''
def __init__(self, level):
super(WaitForNoResyncing, self).__init__(level)
self.level = level
self.noResyncing = Event([])
def doActivate(self):
self.level.world.onUnitsAllAdvanced.addListener(self.tick)
def doDeactivate(self):
self.level.world.onUnitsAllAdvanced.removeListener(self.tick)
def tick(self):
noResyncing = True
for player in self.level.world.players:
if player.resyncing:
noResyncing = False
break
if noResyncing:
self.noResyncing()
self.deactivate()
......@@ -181,6 +181,11 @@ class MiniMap(framework.Element):
sprite = self.universeGui.getPlayerSprite(player.id)
drawCircle(sprite, colours.white, 4)
player = self.universe.playerWithJuggernaut
if player:
sprite = self.universeGui.getPlayerSprite(player.id)
drawCircle(sprite, colours.white, 4)
# Go through and update the positions of the players on the screen.
for playerSprite in self.universeGui.iterPlayers():
self.drawPlayer(screen, sRect, playerSprite)
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment