mirror of
https://gitlab.com/Deukhoofd/BattleSim.git
synced 2025-10-27 18:00:03 +00:00
Added Find Battle Unranked
This commit is contained in:
@@ -269,7 +269,6 @@ class @Move
|
||||
weatherModifier: (battle, user, target) ->
|
||||
# TODO: This is wrong.
|
||||
type = @getType(battle, user, target)
|
||||
console.log(type)
|
||||
if type == 'Fire' && battle.hasWeather(Weather.SUN)
|
||||
0x1800
|
||||
else if type == 'Fire' && battle.hasWeather(Weather.RAIN)
|
||||
@@ -280,8 +279,6 @@ class @Move
|
||||
0x800
|
||||
else if type == 'Dark' && battle.hasWeather(Weather.MOON)
|
||||
0x159A
|
||||
else if type == 'Ghost' && battle.hasWeather(Weather.MOON)
|
||||
0x159A
|
||||
else if type == 'Fairy' && battle.hasWeather(Weather.MOON)
|
||||
0xC00
|
||||
else
|
||||
|
||||
@@ -12,322 +12,321 @@ ConditionHash = {}
|
||||
createCondition = (condition, effects = {}) ->
|
||||
ConditionHash[condition] = effects
|
||||
|
||||
# Attaches each condition to the Battle facade.
|
||||
@attach = (battleFacade) ->
|
||||
battle = battleFacade.battle
|
||||
for condition in battle.conditions
|
||||
if condition not of ConditionHash
|
||||
throw new Error("Undefined condition: #{condition}")
|
||||
hash = ConditionHash[condition] || {}
|
||||
# Attach each condition's event listeners
|
||||
for eventName, callback of hash.attach
|
||||
battle.on(eventName, callback)
|
||||
|
||||
# Extend battle with each function
|
||||
# TODO: Attach to prototype, and only once.
|
||||
for funcName, funcRef of hash.extend
|
||||
battle[funcName] = funcRef
|
||||
|
||||
for funcName, funcRef of hash.extendFacade
|
||||
battleFacade[funcName] = funcRef
|
||||
|
||||
# validates an entire team
|
||||
@validateTeam = (conditions, team, genData) ->
|
||||
errors = []
|
||||
for condition in conditions
|
||||
if condition not of ConditionHash
|
||||
throw new Error("Undefined condition: #{condition}")
|
||||
validator = ConditionHash[condition].validateTeam
|
||||
continue if !validator
|
||||
errors.push(validator(team, genData)...)
|
||||
return errors
|
||||
|
||||
# validates a single pokemon
|
||||
@validatePokemon = (conditions, pokemon, genData, prefix) ->
|
||||
errors = []
|
||||
for condition in conditions
|
||||
if condition not of ConditionHash
|
||||
throw new Error("Undefined condition: #{condition}")
|
||||
validator = ConditionHash[condition].validatePokemon
|
||||
continue if !validator
|
||||
errors.push(validator(pokemon, genData, prefix)...)
|
||||
return errors
|
||||
|
||||
createPBVCondition = (totalPBV) ->
|
||||
createCondition Conditions["PBV_#{totalPBV}"],
|
||||
validateTeam: (team, genData) ->
|
||||
errors = []
|
||||
if pbv.determinePBV(genData, team) > totalPBV
|
||||
errors.push "Total team PBV cannot surpass #{totalPBV}."
|
||||
if team.length != 6
|
||||
errors.push "Your team must have 6 pokemon."
|
||||
return errors
|
||||
|
||||
validatePokemon: (pokemon, genData, prefix) ->
|
||||
errors = []
|
||||
MAX_INDIVIDUAL_PBV = Math.floor(totalPBV / 3)
|
||||
individualPBV = pbv.determinePBV(genData, pokemon)
|
||||
|
||||
if individualPBV > MAX_INDIVIDUAL_PBV
|
||||
errors.push "#{prefix}: This Pokemon's PBV is #{individualPBV}. Individual
|
||||
PBVs cannot go over 1/3 the total (over #{MAX_INDIVIDUAL_PBV} PBV)."
|
||||
|
||||
return errors
|
||||
|
||||
createPBVCondition(1000)
|
||||
createPBVCondition(500)
|
||||
|
||||
createTierCondition = (conditionName, tier) ->
|
||||
createCondition Conditions[conditionName],
|
||||
validateTeam: (team, genData) ->
|
||||
errors = []
|
||||
tierdata = Tiers[tier]
|
||||
teamtier = tiering.determineTier(genData, team)
|
||||
if teamtier.tierRank > tierdata.tierRank
|
||||
errors.push "Your team tier may not exceed the #{tierdata.humanName} tier"
|
||||
if team.length != 6
|
||||
errors.push "Your team must have 6 pokemon."
|
||||
return errors
|
||||
|
||||
for key, val of Tiers
|
||||
createTierCondition("TIER_#{key}", key)
|
||||
|
||||
createCondition Conditions.SLEEP_CLAUSE,
|
||||
attach:
|
||||
initialize: ->
|
||||
for team in @getTeams()
|
||||
for p in team.pokemon
|
||||
p.attach(@getAttachment("SleepClause"))
|
||||
|
||||
createCondition Conditions.SPECIES_CLAUSE,
|
||||
validateTeam: (team, genData) ->
|
||||
errors = []
|
||||
species = team.map((p) -> p.species)
|
||||
species.sort()
|
||||
for i in [1...species.length]
|
||||
speciesName = species[i - 1]
|
||||
if speciesName == species[i]
|
||||
errors.push("Cannot have the same species: #{speciesName}")
|
||||
while speciesName == species[i]
|
||||
i++
|
||||
return errors
|
||||
|
||||
createCondition Conditions.EVASION_CLAUSE,
|
||||
validatePokemon: (pokemon, genData, prefix) ->
|
||||
{moves, ability} = pokemon
|
||||
errors = []
|
||||
|
||||
# Check evasion abilities
|
||||
if ability in [ "Moody" ]
|
||||
errors.push("#{prefix}: #{ability} is banned under Evasion Clause.")
|
||||
|
||||
# Check evasion moves
|
||||
for moveName in moves || []
|
||||
move = genData.MoveData[moveName]
|
||||
continue if !move
|
||||
if move.primaryBoostStats? && move.primaryBoostStats.evasion > 0 &&
|
||||
move.primaryBoostTarget == 'self'
|
||||
errors.push("#{prefix}: #{moveName} is banned under Evasion Clause.")
|
||||
|
||||
return errors
|
||||
|
||||
createCondition Conditions.OHKO_CLAUSE,
|
||||
validatePokemon: (pokemon, genData, prefix) ->
|
||||
{moves} = pokemon
|
||||
errors = []
|
||||
|
||||
# Check OHKO moves
|
||||
for moveName in moves || []
|
||||
move = genData.MoveData[moveName]
|
||||
continue if !move
|
||||
if "ohko" in move.flags
|
||||
errors.push("#{prefix}: #{moveName} is banned under One-Hit KO Clause.")
|
||||
|
||||
return errors
|
||||
|
||||
createCondition Conditions.PRANKSTER_SWAGGER_CLAUSE,
|
||||
validatePokemon: (pokemon, genData, prefix) ->
|
||||
errors = []
|
||||
if "Swagger" in pokemon.moves && "Prankster" == pokemon.ability
|
||||
errors.push("#{prefix}: A Pokemon can't have both Prankster and Swagger.")
|
||||
return errors
|
||||
|
||||
createCondition Conditions.UNRELEASED_BAN,
|
||||
validatePokemon: (pokemon, genData, prefix) ->
|
||||
# Check for unreleased items
|
||||
errors = []
|
||||
if pokemon.item && genData.ItemData[pokemon.item]?.unreleased
|
||||
errors.push("#{prefix}: The item '#{pokemon.item}' is unreleased.")
|
||||
# Check for unreleased abilities
|
||||
forme = genData.FormeData[pokemon.species][pokemon.forme || "default"]
|
||||
if forme.unreleasedHidden && pokemon.ability == forme.hiddenAbility &&
|
||||
forme.hiddenAbility not in forme.abilities
|
||||
errors.push("#{prefix}: The ability #{pokemon.ability} is unreleased.")
|
||||
# Check for unreleased Pokemon
|
||||
if forme.unreleased
|
||||
errors.push("#{prefix}: The Pokemon #{pokemon.species} is unreleased.")
|
||||
return errors
|
||||
|
||||
createCondition Conditions.RATED_BATTLE,
|
||||
attach:
|
||||
end: (winnerId) ->
|
||||
return if !winnerId
|
||||
index = @getPlayerIndex(winnerId)
|
||||
loserId = @playerIds[1 - index]
|
||||
ratings = require './ratings'
|
||||
|
||||
winner = @getPlayer(winnerId)
|
||||
loser = @getPlayer(loserId)
|
||||
|
||||
winnerId = winner.ratingKey
|
||||
loserId = loser.ratingKey
|
||||
|
||||
ratings.getRatings [ winnerId, loserId ], (err, oldRatings) =>
|
||||
ratings.updatePlayers winnerId, loserId, ratings.results.WIN, (err, result) =>
|
||||
return @message "An error occurred updating rankings :(" if err
|
||||
|
||||
oldRating = Math.floor(oldRatings[0])
|
||||
newRating = Math.floor(result[0])
|
||||
@cannedText('RATING_UPDATE', index, oldRating, newRating)
|
||||
|
||||
oldRating = Math.floor(oldRatings[1])
|
||||
newRating = Math.floor(result[1])
|
||||
@cannedText('RATING_UPDATE', 1 - index, oldRating, newRating)
|
||||
|
||||
@emit('ratingsUpdated')
|
||||
@sendUpdates()
|
||||
|
||||
createCondition Conditions.TIMED_BATTLE,
|
||||
attach:
|
||||
initialize: ->
|
||||
@playerTimes = {}
|
||||
@lastActionTimes = {}
|
||||
now = Date.now()
|
||||
|
||||
# Set up initial values
|
||||
for id in @playerIds
|
||||
@playerTimes[id] = now + @TEAM_PREVIEW_TIMER
|
||||
|
||||
# Set up timers and event listeners
|
||||
check = () =>
|
||||
@startBattle()
|
||||
@sendUpdates()
|
||||
@teamPreviewTimerId = setTimeout(check, @TEAM_PREVIEW_TIMER)
|
||||
@once('end', => clearTimeout(@teamPreviewTimerId))
|
||||
@once('start', => clearTimeout(@teamPreviewTimerId))
|
||||
|
||||
start: ->
|
||||
nowTime = Date.now()
|
||||
for id in @playerIds
|
||||
@playerTimes[id] = nowTime + @DEFAULT_TIMER
|
||||
# Remove first turn since we'll be increasing it again.
|
||||
@playerTimes[id] -= @TIMER_PER_TURN_INCREASE
|
||||
@startTimer()
|
||||
|
||||
requestActions: (playerId) ->
|
||||
# If a player has selected a move, then there's an amount of time spent
|
||||
# between move selection and requesting another action that was "lost".
|
||||
# We grant this back here.
|
||||
if @lastActionTimes[playerId]
|
||||
now = Date.now()
|
||||
leftoverTime = now - @lastActionTimes[playerId]
|
||||
delete @lastActionTimes[playerId]
|
||||
@addTime(playerId, leftoverTime)
|
||||
# In either case, we tell people that this player's timer resumes.
|
||||
@send('resumeTimer', @id, @getPlayerIndex(playerId))
|
||||
|
||||
addAction: (playerId, action) ->
|
||||
# Record the last action for use
|
||||
@lastActionTimes[playerId] = Date.now()
|
||||
@recalculateTimers()
|
||||
|
||||
undoCompletedRequest: (playerId) ->
|
||||
delete @lastActionTimes[playerId]
|
||||
@recalculateTimers()
|
||||
|
||||
# Show players updated times
|
||||
beginTurn: ->
|
||||
remainingTimes = []
|
||||
for id in @playerIds
|
||||
@addTime(id, @TIMER_PER_TURN_INCREASE)
|
||||
remainingTimes.push(@timeRemainingFor(id))
|
||||
@send('updateTimers', @id, remainingTimes)
|
||||
|
||||
continueTurn: ->
|
||||
for id in @playerIds
|
||||
@send('pauseTimer', @id, @getPlayerIndex(id))
|
||||
|
||||
spectateBattle: (user) ->
|
||||
playerId = user.name
|
||||
remainingTimes = (@timeRemainingFor(id) for id in @playerIds)
|
||||
user.send('updateTimers', @id, remainingTimes)
|
||||
|
||||
# Pause timer for players who have already chosen a move.
|
||||
if @hasCompletedRequests(playerId)
|
||||
index = @getPlayerIndex(playerId)
|
||||
timeSinceLastAction = Date.now() - @lastActionTimes[playerId]
|
||||
user.send('pauseTimer', @id, index, timeSinceLastAction)
|
||||
|
||||
extend:
|
||||
DEFAULT_TIMER: 3 * 60 * 1000 # three minutes
|
||||
TIMER_PER_TURN_INCREASE: 15 * 1000 # fifteen seconds
|
||||
TIMER_CAP: 3 * 60 * 1000 # three minutes
|
||||
TEAM_PREVIEW_TIMER: 1.5 * 60 * 1000 # 1 minute and 30 seconds
|
||||
|
||||
startTimer: (msecs) ->
|
||||
msecs ?= @DEFAULT_TIMER
|
||||
@timerId = setTimeout(@declareWinner.bind(this), msecs)
|
||||
@once('end', => clearTimeout(@timerId))
|
||||
|
||||
addTime: (id, msecs) ->
|
||||
@playerTimes[id] += msecs
|
||||
remainingTime = @timeRemainingFor(id)
|
||||
if remainingTime > @TIMER_CAP
|
||||
diff = remainingTime - @TIMER_CAP
|
||||
@playerTimes[id] -= diff
|
||||
@recalculateTimers()
|
||||
@playerTimes[id]
|
||||
|
||||
recalculateTimers: ->
|
||||
playerTimes = for id in @playerIds
|
||||
if @lastActionTimes[id] then Infinity else @timeRemainingFor(id)
|
||||
leastTime = Math.min(playerTimes...)
|
||||
clearTimeout(@timerId)
|
||||
if 0 < leastTime < Infinity
|
||||
@timerId = setTimeout(@declareWinner.bind(this), leastTime)
|
||||
else if leastTime <= 0
|
||||
@declareWinner()
|
||||
|
||||
timeRemainingFor: (playerId) ->
|
||||
endTime = @playerTimes[playerId]
|
||||
nowTime = @lastActionTimes[playerId] || Date.now()
|
||||
return endTime - nowTime
|
||||
|
||||
playersWithLeastTime: ->
|
||||
losingIds = []
|
||||
leastTimeRemaining = Infinity
|
||||
for id in @playerIds
|
||||
timeRemaining = @timeRemainingFor(id)
|
||||
if timeRemaining < leastTimeRemaining
|
||||
losingIds = [ id ]
|
||||
leastTimeRemaining = timeRemaining
|
||||
else if timeRemaining == leastTimeRemaining
|
||||
losingIds.push(id)
|
||||
return losingIds
|
||||
|
||||
declareWinner: ->
|
||||
loserIds = @playersWithLeastTime()
|
||||
loserId = @rng.choice(loserIds, "timer")
|
||||
index = @getPlayerIndex(loserId)
|
||||
winnerIndex = 1 - index
|
||||
@timerWin(winnerIndex)
|
||||
|
||||
timerWin: (winnerIndex) ->
|
||||
@tell(Protocol.TIMER_WIN, winnerIndex)
|
||||
@emit('end', @playerIds[winnerIndex])
|
||||
@sendUpdates()
|
||||
|
||||
|
||||
# Attaches each condition to the Battle facade.
|
||||
@attach = (battleFacade) ->
|
||||
battle = battleFacade.battle
|
||||
for condition in battle.conditions
|
||||
if condition not of ConditionHash
|
||||
throw new Error("Undefined condition: #{condition}")
|
||||
hash = ConditionHash[condition] || {}
|
||||
# Attach each condition's event listeners
|
||||
for eventName, callback of hash.attach
|
||||
battle.on(eventName, callback)
|
||||
|
||||
# Extend battle with each function
|
||||
# TODO: Attach to prototype, and only once.
|
||||
for funcName, funcRef of hash.extend
|
||||
battle[funcName] = funcRef
|
||||
|
||||
for funcName, funcRef of hash.extendFacade
|
||||
battleFacade[funcName] = funcRef
|
||||
|
||||
# validates an entire team
|
||||
@validateTeam = (conditions, team, genData) ->
|
||||
errors = []
|
||||
for condition in conditions
|
||||
if condition not of ConditionHash
|
||||
throw new Error("Undefined condition: #{condition}")
|
||||
validator = ConditionHash[condition].validateTeam
|
||||
continue if !validator
|
||||
errors.push(validator(team, genData)...)
|
||||
return errors
|
||||
|
||||
# validates a single pokemon
|
||||
@validatePokemon = (conditions, pokemon, genData, prefix) ->
|
||||
errors = []
|
||||
for condition in conditions
|
||||
if condition not of ConditionHash
|
||||
throw new Error("Undefined condition: #{condition}")
|
||||
validator = ConditionHash[condition].validatePokemon
|
||||
continue if !validator
|
||||
errors.push(validator(pokemon, genData, prefix)...)
|
||||
return errors
|
||||
|
||||
createPBVCondition = (totalPBV) ->
|
||||
createCondition Conditions["PBV_#{totalPBV}"],
|
||||
validateTeam: (team, genData) ->
|
||||
errors = []
|
||||
if pbv.determinePBV(genData, team) > totalPBV
|
||||
errors.push "Total team PBV cannot surpass #{totalPBV}."
|
||||
if team.length != 6
|
||||
errors.push "Your team must have 6 pokemon."
|
||||
return errors
|
||||
|
||||
validatePokemon: (pokemon, genData, prefix) ->
|
||||
errors = []
|
||||
MAX_INDIVIDUAL_PBV = Math.floor(totalPBV / 3)
|
||||
individualPBV = pbv.determinePBV(genData, pokemon)
|
||||
|
||||
if individualPBV > MAX_INDIVIDUAL_PBV
|
||||
errors.push "#{prefix}: This Pokemon's PBV is #{individualPBV}. Individual
|
||||
PBVs cannot go over 1/3 the total (over #{MAX_INDIVIDUAL_PBV} PBV)."
|
||||
|
||||
return errors
|
||||
|
||||
createPBVCondition(1000)
|
||||
createPBVCondition(500)
|
||||
|
||||
createTierCondition = (conditionName, tier) ->
|
||||
createCondition Conditions[conditionName],
|
||||
validateTeam: (team, genData) ->
|
||||
errors = []
|
||||
tierdata = Tiers[tier]
|
||||
teamtier = tiering.determineTier(genData, team)
|
||||
if teamtier.tierRank > tierdata.tierRank
|
||||
errors.push "Your team tier may not exceed the #{tierdata.humanName} tier"
|
||||
if team.length != 6
|
||||
errors.push "Your team must have 6 pokemon."
|
||||
return errors
|
||||
|
||||
for key, val of Tiers
|
||||
createTierCondition("TIER_#{key}", key)
|
||||
|
||||
createCondition Conditions.SLEEP_CLAUSE,
|
||||
attach:
|
||||
initialize: ->
|
||||
for team in @getTeams()
|
||||
for p in team.pokemon
|
||||
p.attach(@getAttachment("SleepClause"))
|
||||
|
||||
createCondition Conditions.SPECIES_CLAUSE,
|
||||
validateTeam: (team, genData) ->
|
||||
errors = []
|
||||
species = team.map((p) -> p.species)
|
||||
species.sort()
|
||||
for i in [1...species.length]
|
||||
speciesName = species[i - 1]
|
||||
if speciesName == species[i]
|
||||
errors.push("Cannot have the same species: #{speciesName}")
|
||||
while speciesName == species[i]
|
||||
i++
|
||||
return errors
|
||||
|
||||
createCondition Conditions.EVASION_CLAUSE,
|
||||
validatePokemon: (pokemon, genData, prefix) ->
|
||||
{moves, ability} = pokemon
|
||||
errors = []
|
||||
|
||||
# Check evasion abilities
|
||||
if ability in [ "Moody" ]
|
||||
errors.push("#{prefix}: #{ability} is banned under Evasion Clause.")
|
||||
|
||||
# Check evasion moves
|
||||
for moveName in moves || []
|
||||
move = genData.MoveData[moveName]
|
||||
continue if !move
|
||||
if move.primaryBoostStats? && move.primaryBoostStats.evasion > 0 &&
|
||||
move.primaryBoostTarget == 'self'
|
||||
errors.push("#{prefix}: #{moveName} is banned under Evasion Clause.")
|
||||
|
||||
return errors
|
||||
|
||||
createCondition Conditions.OHKO_CLAUSE,
|
||||
validatePokemon: (pokemon, genData, prefix) ->
|
||||
{moves} = pokemon
|
||||
errors = []
|
||||
|
||||
# Check OHKO moves
|
||||
for moveName in moves || []
|
||||
move = genData.MoveData[moveName]
|
||||
continue if !move
|
||||
if "ohko" in move.flags
|
||||
errors.push("#{prefix}: #{moveName} is banned under One-Hit KO Clause.")
|
||||
|
||||
return errors
|
||||
|
||||
createCondition Conditions.PRANKSTER_SWAGGER_CLAUSE,
|
||||
validatePokemon: (pokemon, genData, prefix) ->
|
||||
errors = []
|
||||
if "Swagger" in pokemon.moves && "Prankster" == pokemon.ability
|
||||
errors.push("#{prefix}: A Pokemon can't have both Prankster and Swagger.")
|
||||
return errors
|
||||
|
||||
createCondition Conditions.UNRELEASED_BAN,
|
||||
validatePokemon: (pokemon, genData, prefix) ->
|
||||
# Check for unreleased items
|
||||
errors = []
|
||||
if pokemon.item && genData.ItemData[pokemon.item]?.unreleased
|
||||
errors.push("#{prefix}: The item '#{pokemon.item}' is unreleased.")
|
||||
# Check for unreleased abilities
|
||||
forme = genData.FormeData[pokemon.species][pokemon.forme || "default"]
|
||||
if forme.unreleasedHidden && pokemon.ability == forme.hiddenAbility &&
|
||||
forme.hiddenAbility not in forme.abilities
|
||||
errors.push("#{prefix}: The ability #{pokemon.ability} is unreleased.")
|
||||
# Check for unreleased Pokemon
|
||||
if forme.unreleased
|
||||
errors.push("#{prefix}: The Pokemon #{pokemon.species} is unreleased.")
|
||||
return errors
|
||||
|
||||
createCondition Conditions.RATED_BATTLE,
|
||||
attach:
|
||||
end: (winnerId) ->
|
||||
return if !winnerId
|
||||
index = @getPlayerIndex(winnerId)
|
||||
loserId = @playerIds[1 - index]
|
||||
ratings = require './ratings'
|
||||
|
||||
winner = @getPlayer(winnerId)
|
||||
loser = @getPlayer(loserId)
|
||||
|
||||
winnerId = winner.ratingKey
|
||||
loserId = loser.ratingKey
|
||||
|
||||
ratings.getRatings [ winnerId, loserId ], (err, oldRatings) =>
|
||||
ratings.updatePlayers winnerId, loserId, ratings.results.WIN, (err, result) =>
|
||||
return @message "An error occurred updating rankings :(" if err
|
||||
|
||||
oldRating = Math.floor(oldRatings[0])
|
||||
newRating = Math.floor(result[0])
|
||||
@cannedText('RATING_UPDATE', index, oldRating, newRating)
|
||||
|
||||
oldRating = Math.floor(oldRatings[1])
|
||||
newRating = Math.floor(result[1])
|
||||
@cannedText('RATING_UPDATE', 1 - index, oldRating, newRating)
|
||||
|
||||
@emit('ratingsUpdated')
|
||||
@sendUpdates()
|
||||
|
||||
createCondition Conditions.TIMED_BATTLE,
|
||||
attach:
|
||||
initialize: ->
|
||||
@playerTimes = {}
|
||||
@lastActionTimes = {}
|
||||
now = Date.now()
|
||||
|
||||
# Set up initial values
|
||||
for id in @playerIds
|
||||
@playerTimes[id] = now + @TEAM_PREVIEW_TIMER
|
||||
|
||||
# Set up timers and event listeners
|
||||
check = () =>
|
||||
@startBattle()
|
||||
@sendUpdates()
|
||||
@teamPreviewTimerId = setTimeout(check, @TEAM_PREVIEW_TIMER)
|
||||
@once('end', => clearTimeout(@teamPreviewTimerId))
|
||||
@once('start', => clearTimeout(@teamPreviewTimerId))
|
||||
|
||||
start: ->
|
||||
nowTime = Date.now()
|
||||
for id in @playerIds
|
||||
@playerTimes[id] = nowTime + @DEFAULT_TIMER
|
||||
# Remove first turn since we'll be increasing it again.
|
||||
@playerTimes[id] -= @TIMER_PER_TURN_INCREASE
|
||||
@startTimer()
|
||||
|
||||
requestActions: (playerId) ->
|
||||
# If a player has selected a move, then there's an amount of time spent
|
||||
# between move selection and requesting another action that was "lost".
|
||||
# We grant this back here.
|
||||
if @lastActionTimes[playerId]
|
||||
now = Date.now()
|
||||
leftoverTime = now - @lastActionTimes[playerId]
|
||||
delete @lastActionTimes[playerId]
|
||||
@addTime(playerId, leftoverTime)
|
||||
# In either case, we tell people that this player's timer resumes.
|
||||
@send('resumeTimer', @id, @getPlayerIndex(playerId))
|
||||
|
||||
addAction: (playerId, action) ->
|
||||
# Record the last action for use
|
||||
@lastActionTimes[playerId] = Date.now()
|
||||
@recalculateTimers()
|
||||
|
||||
undoCompletedRequest: (playerId) ->
|
||||
delete @lastActionTimes[playerId]
|
||||
@recalculateTimers()
|
||||
|
||||
# Show players updated times
|
||||
beginTurn: ->
|
||||
remainingTimes = []
|
||||
for id in @playerIds
|
||||
@addTime(id, @TIMER_PER_TURN_INCREASE)
|
||||
remainingTimes.push(@timeRemainingFor(id))
|
||||
@send('updateTimers', @id, remainingTimes)
|
||||
|
||||
continueTurn: ->
|
||||
for id in @playerIds
|
||||
@send('pauseTimer', @id, @getPlayerIndex(id))
|
||||
|
||||
spectateBattle: (user) ->
|
||||
playerId = user.name
|
||||
remainingTimes = (@timeRemainingFor(id) for id in @playerIds)
|
||||
user.send('updateTimers', @id, remainingTimes)
|
||||
|
||||
# Pause timer for players who have already chosen a move.
|
||||
if @hasCompletedRequests(playerId)
|
||||
index = @getPlayerIndex(playerId)
|
||||
timeSinceLastAction = Date.now() - @lastActionTimes[playerId]
|
||||
user.send('pauseTimer', @id, index, timeSinceLastAction)
|
||||
|
||||
extend:
|
||||
DEFAULT_TIMER: 3 * 60 * 1000 # three minutes
|
||||
TIMER_PER_TURN_INCREASE: 15 * 1000 # fifteen seconds
|
||||
TIMER_CAP: 3 * 60 * 1000 # three minutes
|
||||
TEAM_PREVIEW_TIMER: 1.5 * 60 * 1000 # 1 minute and 30 seconds
|
||||
|
||||
startTimer: (msecs) ->
|
||||
msecs ?= @DEFAULT_TIMER
|
||||
@timerId = setTimeout(@declareWinner.bind(this), msecs)
|
||||
@once('end', => clearTimeout(@timerId))
|
||||
|
||||
addTime: (id, msecs) ->
|
||||
@playerTimes[id] += msecs
|
||||
remainingTime = @timeRemainingFor(id)
|
||||
if remainingTime > @TIMER_CAP
|
||||
diff = remainingTime - @TIMER_CAP
|
||||
@playerTimes[id] -= diff
|
||||
@recalculateTimers()
|
||||
@playerTimes[id]
|
||||
|
||||
recalculateTimers: ->
|
||||
playerTimes = for id in @playerIds
|
||||
if @lastActionTimes[id] then Infinity else @timeRemainingFor(id)
|
||||
leastTime = Math.min(playerTimes...)
|
||||
clearTimeout(@timerId)
|
||||
if 0 < leastTime < Infinity
|
||||
@timerId = setTimeout(@declareWinner.bind(this), leastTime)
|
||||
else if leastTime <= 0
|
||||
@declareWinner()
|
||||
|
||||
timeRemainingFor: (playerId) ->
|
||||
endTime = @playerTimes[playerId]
|
||||
nowTime = @lastActionTimes[playerId] || Date.now()
|
||||
return endTime - nowTime
|
||||
|
||||
playersWithLeastTime: ->
|
||||
losingIds = []
|
||||
leastTimeRemaining = Infinity
|
||||
for id in @playerIds
|
||||
timeRemaining = @timeRemainingFor(id)
|
||||
if timeRemaining < leastTimeRemaining
|
||||
losingIds = [ id ]
|
||||
leastTimeRemaining = timeRemaining
|
||||
else if timeRemaining == leastTimeRemaining
|
||||
losingIds.push(id)
|
||||
return losingIds
|
||||
|
||||
declareWinner: ->
|
||||
loserIds = @playersWithLeastTime()
|
||||
loserId = @rng.choice(loserIds, "timer")
|
||||
index = @getPlayerIndex(loserId)
|
||||
winnerIndex = 1 - index
|
||||
@timerWin(winnerIndex)
|
||||
|
||||
timerWin: (winnerIndex) ->
|
||||
@tell(Protocol.TIMER_WIN, winnerIndex)
|
||||
@emit('end', @playerIds[winnerIndex])
|
||||
@sendUpdates()
|
||||
|
||||
createCondition Conditions.TEAM_PREVIEW,
|
||||
attach:
|
||||
initialize: ->
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{_} = require('underscore')
|
||||
{Formats} = require('../shared/conditions')
|
||||
ConditionsFunc = require('../shared/conditions')
|
||||
config = require('../knexfile')[process.env.NODE_ENV || 'development']
|
||||
|
||||
knex = require('knex')(config)
|
||||
@@ -39,7 +39,8 @@ Battle = bookshelf.Model.extend
|
||||
@get('name') || @getPlayerNames().join(' vs. ') || 'Untitled'
|
||||
|
||||
getFormat: ->
|
||||
Formats[@get('format')].humanName
|
||||
allformats = ConditionsFunc.Formats()
|
||||
allformats[@get('format')].humanName
|
||||
|
||||
getPlayerNames: ->
|
||||
# players is denormalized. It's an array with a comma delimiter.
|
||||
|
||||
@@ -88,6 +88,8 @@ class @Attachment.Livewire extends @TeamAttachment
|
||||
|
||||
#On switch in
|
||||
switchIn: (pokemon) ->
|
||||
if pokemon.isFainted()
|
||||
return
|
||||
#if pokemon is part electric and not immune to ground, remove livewire. Also if has ground and is not immune to ground
|
||||
if (pokemon.hasType("Electric") && !pokemon.isImmune("Ground")) || (pokemon.hasType("Ground") && !pokemon.isImmune("Ground"))
|
||||
@team.unattach(@constructor)
|
||||
|
||||
@@ -798,12 +798,12 @@
|
||||
"Fighting"
|
||||
],
|
||||
"stats": {
|
||||
"attack": 165,
|
||||
"attack": 155,
|
||||
"defense": 100,
|
||||
"hp": 65,
|
||||
"specialAttack": 70,
|
||||
"specialDefense": 60,
|
||||
"speed": 130
|
||||
"specialAttack": 80,
|
||||
"specialDefense": 70,
|
||||
"speed": 120
|
||||
},
|
||||
"abilities": [
|
||||
"Technician"
|
||||
@@ -6259,6 +6259,7 @@
|
||||
"Psychic"
|
||||
],
|
||||
"weight": 3,
|
||||
"unreleased": true,
|
||||
"pokeBattleValue": 145,
|
||||
"tier": [ "OU" ]
|
||||
}
|
||||
@@ -8206,19 +8207,18 @@
|
||||
"Steel"
|
||||
],
|
||||
"stats": {
|
||||
"attack": 145,
|
||||
"defense": 125,
|
||||
"attack": 155,
|
||||
"defense": 100,
|
||||
"hp": 65,
|
||||
"specialAttack": 60,
|
||||
"specialDefense": 85,
|
||||
"speed": 105
|
||||
"specialAttack": 80,
|
||||
"specialDefense": 70,
|
||||
"speed": 120
|
||||
},
|
||||
"abilities": [
|
||||
"Moxie"
|
||||
],
|
||||
"isBattleOnly": true,
|
||||
"weight": 700,
|
||||
"unreleased": true,
|
||||
"pokeBattleValue": 1001,
|
||||
"tier": [ "Uber" ]
|
||||
}
|
||||
@@ -10770,6 +10770,7 @@
|
||||
"Grass"
|
||||
],
|
||||
"weight": 50,
|
||||
"unreleased": true,
|
||||
"pokeBattleValue": 150,
|
||||
"tier": [ "OU" ]
|
||||
}
|
||||
@@ -12977,6 +12978,7 @@
|
||||
],
|
||||
"weight": 2500,
|
||||
"pokeBattleValue": 130,
|
||||
"unreleased": true,
|
||||
"tier": [ "UU" ]
|
||||
}
|
||||
},
|
||||
@@ -13904,6 +13906,7 @@
|
||||
"Psychic"
|
||||
],
|
||||
"weight": 856,
|
||||
"unreleased": true,
|
||||
"pokeBattleValue": 140,
|
||||
"tier": [ "OU" ]
|
||||
}
|
||||
@@ -14699,6 +14702,7 @@
|
||||
"types": [
|
||||
"Dark"
|
||||
],
|
||||
"unreleased": true,
|
||||
"weight": 505,
|
||||
"pokeBattleValue": 1001,
|
||||
"tier": [ "Uber" ]
|
||||
@@ -15598,6 +15602,7 @@
|
||||
"Psychic"
|
||||
],
|
||||
"weight": 608,
|
||||
"unreleased": true,
|
||||
"pokeBattleValue": 1001,
|
||||
"tier": [ "Uber" ]
|
||||
},
|
||||
@@ -15693,6 +15698,7 @@
|
||||
"Psychic"
|
||||
],
|
||||
"weight": 608,
|
||||
"unreleased": true,
|
||||
"pokeBattleValue": 200,
|
||||
"tier": [ "Uber" ]
|
||||
},
|
||||
@@ -15732,6 +15738,7 @@
|
||||
],
|
||||
"weight": 608,
|
||||
"pokeBattleValue": 200,
|
||||
"unreleased": true,
|
||||
"tier": [ "Uber" ]
|
||||
}
|
||||
},
|
||||
@@ -16066,6 +16073,7 @@
|
||||
}
|
||||
},
|
||||
"pokeBattleValue": 155,
|
||||
"unreleased": true,
|
||||
"tier": [ "OU" ]
|
||||
},
|
||||
"mega": {
|
||||
@@ -31349,6 +31357,7 @@
|
||||
"Fighting"
|
||||
],
|
||||
"weight": 485,
|
||||
"unreleased": true,
|
||||
"pokeBattleValue": 175,
|
||||
"tier": [ "OU" ]
|
||||
},
|
||||
@@ -31438,6 +31447,7 @@
|
||||
"Fighting"
|
||||
],
|
||||
"weight": 485,
|
||||
"unreleased": true,
|
||||
"pokeBattleValue": 175,
|
||||
"tier": [ "OU" ]
|
||||
}
|
||||
@@ -32623,6 +32633,7 @@
|
||||
"Dragon",
|
||||
"Ice"
|
||||
],
|
||||
"unreleased": true,
|
||||
"weight": 3250,
|
||||
"pokeBattleValue": 180,
|
||||
"tier": [ "OU" ]
|
||||
@@ -33052,6 +33063,7 @@
|
||||
"Flying"
|
||||
],
|
||||
"weight": 680,
|
||||
"unreleased": true,
|
||||
"pokeBattleValue": 180,
|
||||
"tier": [ "Uber" ]
|
||||
},
|
||||
@@ -33148,6 +33160,7 @@
|
||||
"Flying"
|
||||
],
|
||||
"weight": 680,
|
||||
"unreleased": true,
|
||||
"pokeBattleValue": 175,
|
||||
"tier": [ "OU" ]
|
||||
}
|
||||
@@ -33606,6 +33619,7 @@
|
||||
"Psychic"
|
||||
],
|
||||
"weight": 400,
|
||||
"unreleased": true,
|
||||
"pokeBattleValue": 165,
|
||||
"tier": [ "OU" ]
|
||||
},
|
||||
@@ -33627,8 +33641,8 @@
|
||||
],
|
||||
"isBattleOnly": true,
|
||||
"weight": 520,
|
||||
"pokeBattleValue": 1001,
|
||||
"tier": [ "Uber" ]
|
||||
"pokeBattleValue": 190,
|
||||
"tier": [ "OU"]
|
||||
}
|
||||
},
|
||||
"Latios": {
|
||||
@@ -33730,6 +33744,7 @@
|
||||
"Dragon",
|
||||
"Psychic"
|
||||
],
|
||||
"unreleased": true,
|
||||
"weight": 600,
|
||||
"pokeBattleValue": 175,
|
||||
"tier": [ "OU" ]
|
||||
@@ -33752,8 +33767,8 @@
|
||||
],
|
||||
"isBattleOnly": true,
|
||||
"weight": 700,
|
||||
"pokeBattleValue": 1001,
|
||||
"tier": [ "Uber" ]
|
||||
"pokeBattleValue": 190,
|
||||
"tier": [ "OU" ]
|
||||
}
|
||||
},
|
||||
"Leafeon": {
|
||||
@@ -39046,6 +39061,7 @@
|
||||
"Psychic"
|
||||
],
|
||||
"weight": 3,
|
||||
"unreleased": true,
|
||||
"pokeBattleValue": 130,
|
||||
"tier": [ "UU" ]
|
||||
}
|
||||
@@ -44893,6 +44909,7 @@
|
||||
"Water"
|
||||
],
|
||||
"weight": 31,
|
||||
"unreleased": true,
|
||||
"pokeBattleValue": 80,
|
||||
"tier": [ "Unsorted" ]
|
||||
}
|
||||
@@ -52850,8 +52867,9 @@
|
||||
"Grass"
|
||||
],
|
||||
"weight": 21,
|
||||
"pokeBattleValue": 140,
|
||||
"tier": [ "OU" ]
|
||||
"unreleased": true,
|
||||
"pokeBattleValue": 130,
|
||||
"tier": [ "UU" ]
|
||||
},
|
||||
"sky": {
|
||||
"abilities": [
|
||||
@@ -52915,6 +52933,7 @@
|
||||
"Flying"
|
||||
],
|
||||
"weight": 52,
|
||||
"unreleased": true,
|
||||
"pokeBattleValue": 335,
|
||||
"tier": [ "Uber" ]
|
||||
}
|
||||
@@ -60197,6 +60216,7 @@
|
||||
"Fighting"
|
||||
],
|
||||
"weight": 2600,
|
||||
"unreleased": true,
|
||||
"pokeBattleValue": 160,
|
||||
"tier": [ "OU" ]
|
||||
}
|
||||
@@ -61197,6 +61217,7 @@
|
||||
"Flying"
|
||||
],
|
||||
"weight": 630,
|
||||
"unreleased": true,
|
||||
"pokeBattleValue": 160,
|
||||
"tier": [ "OU" ]
|
||||
},
|
||||
@@ -61291,6 +61312,7 @@
|
||||
"Flying"
|
||||
],
|
||||
"weight": 630,
|
||||
"unreleased": true,
|
||||
"pokeBattleValue": 170,
|
||||
"tier": [ "OU" ]
|
||||
}
|
||||
@@ -62387,12 +62409,12 @@
|
||||
"Fire"
|
||||
],
|
||||
"stats": {
|
||||
"attack": 85,
|
||||
"defense": 78,
|
||||
"attack": 90,
|
||||
"defense": 88,
|
||||
"hp": 78,
|
||||
"specialAttack": 160,
|
||||
"specialDefense": 109,
|
||||
"speed": 125
|
||||
"specialDefense": 110,
|
||||
"speed": 109
|
||||
},
|
||||
"abilities": [
|
||||
"Hubris"
|
||||
@@ -63118,6 +63140,7 @@
|
||||
"types": [
|
||||
"Psychic"
|
||||
],
|
||||
"unreleased": true,
|
||||
"weight": 3,
|
||||
"pokeBattleValue": 130,
|
||||
"tier": [ "UU" ]
|
||||
@@ -64309,6 +64332,7 @@
|
||||
"Fighting"
|
||||
],
|
||||
"weight": 2000,
|
||||
"unreleased": true,
|
||||
"pokeBattleValue": 130,
|
||||
"tier": [ "UU" ]
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
"mega"
|
||||
],
|
||||
"spriteId": 407,
|
||||
"unreleased": true,
|
||||
"type": "megastone"
|
||||
},
|
||||
"Absorb Bulb": {
|
||||
@@ -55,6 +56,7 @@
|
||||
"mega"
|
||||
],
|
||||
"spriteId": 397,
|
||||
"unreleased": true,
|
||||
"type": "megastone"
|
||||
},
|
||||
"Aguav Berry": {
|
||||
@@ -81,6 +83,7 @@
|
||||
"mega"
|
||||
],
|
||||
"spriteId": 409,
|
||||
"unreleased": true,
|
||||
"type": "megastone"
|
||||
},
|
||||
"Amaze Mulch": {
|
||||
@@ -193,6 +196,7 @@
|
||||
"mega"
|
||||
],
|
||||
"spriteId": 398,
|
||||
"unreleased": true,
|
||||
"type": "megastone"
|
||||
},
|
||||
|
||||
@@ -204,7 +208,8 @@
|
||||
"mega"
|
||||
],
|
||||
"spriteId": 404,
|
||||
"type": "megastone"
|
||||
"type": "megastone",
|
||||
"unreleased": true
|
||||
}, "Belue Berry": {
|
||||
"description": "No competitive use.",
|
||||
"flingPower": 10,
|
||||
@@ -213,6 +218,7 @@
|
||||
"type": "Electric"
|
||||
},
|
||||
"spriteId": 166,
|
||||
|
||||
"type": "berries"
|
||||
},
|
||||
"Berry Juice": {
|
||||
@@ -259,6 +265,7 @@
|
||||
"mega"
|
||||
],
|
||||
"spriteId": 404,
|
||||
"unreleased": true,
|
||||
"type": "megastone"
|
||||
}, "Black Belt": {
|
||||
"description": "The holder's Fighting-type attacks have their power multiplied by 1.2.",
|
||||
@@ -382,7 +389,6 @@
|
||||
"Bug Gem": {
|
||||
"description": "The holder's first successful Bug-type attack has its power multiplied by 1.3. Single use.",
|
||||
"flingPower": 0,
|
||||
"unreleased": true,
|
||||
"spriteId": 342,
|
||||
"type": "misc"
|
||||
},
|
||||
@@ -455,6 +461,7 @@
|
||||
"mega-x"
|
||||
],
|
||||
"spriteId": 390,
|
||||
"unreleased": true,
|
||||
"type": "megastone"
|
||||
},
|
||||
"Charizardite Y": {
|
||||
@@ -619,7 +626,6 @@
|
||||
"Custap Berry": {
|
||||
"description": "The holder moves first in its priority bracket if it has 1/4 or less of its maximum HP. Single use.",
|
||||
"flingPower": 10,
|
||||
"unreleased": true,
|
||||
"naturalGift": {
|
||||
"power": 100,
|
||||
"type": "Ghost"
|
||||
@@ -648,7 +654,6 @@
|
||||
"Dark Gem": {
|
||||
"description": "The holder's first successful Dark-type attack has its power multiplied by 1.3. Single use.",
|
||||
"flingPower": 0,
|
||||
"unreleased": true,
|
||||
"spriteId": 346,
|
||||
"type": "misc"
|
||||
},
|
||||
@@ -782,6 +787,7 @@
|
||||
"mega"
|
||||
],
|
||||
"spriteId": 404,
|
||||
"unreleased": true,
|
||||
"type": "megastone"
|
||||
}, "Douse Drive": {
|
||||
"description": "The holder's Techno Blast is Water type.",
|
||||
@@ -804,7 +810,6 @@
|
||||
"Dragon Gem": {
|
||||
"description": "The holder's first successful Dragon-type attack has its power multiplied by 1.3. Single use.",
|
||||
"flingPower": 0,
|
||||
"unreleased": true,
|
||||
"spriteId": 345,
|
||||
"type": "misc"
|
||||
},
|
||||
@@ -885,7 +890,6 @@
|
||||
"Electric Gem": {
|
||||
"description": "The holder's first successful Electric-type attack has its power multiplied by 1.3. Single use.",
|
||||
"flingPower": 0,
|
||||
"unreleased": true,
|
||||
"spriteId": 334,
|
||||
"type": "misc"
|
||||
},
|
||||
@@ -950,7 +954,6 @@
|
||||
"Fairy Gem": {
|
||||
"description": "The holder's first successful Fairy-type attack has its power multiplied by 1.3. Single use.",
|
||||
"flingPower": 0,
|
||||
"unreleased": true,
|
||||
"spriteId": 425,
|
||||
"type": "misc"
|
||||
},
|
||||
@@ -979,7 +982,6 @@
|
||||
}, "Fighting Gem": {
|
||||
"description": "The holder's first successful Fighting-type attack has its power multiplied by 1.3. Single use.",
|
||||
"flingPower": 0,
|
||||
"unreleased": true,
|
||||
"spriteId": 337,
|
||||
"type": "misc"
|
||||
},
|
||||
@@ -996,7 +998,6 @@
|
||||
"Fire Gem": {
|
||||
"description": "The holder's first successful Fire-type attack has its power multiplied by 1.3. Single use.",
|
||||
"flingPower": 0,
|
||||
"unreleased": true,
|
||||
"spriteId": 332,
|
||||
"type": "misc"
|
||||
},
|
||||
@@ -1048,7 +1049,6 @@
|
||||
"Flying Gem": {
|
||||
"description": "The holder's first successful Flying-type attack has its power multiplied by 1.3. Single use.",
|
||||
"flingPower": 0,
|
||||
"unreleased": false,
|
||||
"spriteId": 340,
|
||||
"type": "misc"
|
||||
},
|
||||
@@ -1102,6 +1102,7 @@
|
||||
"mega"
|
||||
],
|
||||
"spriteId": 404,
|
||||
"unreleased": true,
|
||||
"type": "megastone"
|
||||
},
|
||||
"Ganlon Berry": {
|
||||
@@ -1122,6 +1123,7 @@
|
||||
"mega"
|
||||
],
|
||||
"spriteId": 413,
|
||||
"unreleased": true,
|
||||
"type": "megastone"
|
||||
},
|
||||
"Gardevoirite": {
|
||||
@@ -1132,6 +1134,7 @@
|
||||
"mega"
|
||||
],
|
||||
"spriteId": 387,
|
||||
"unreleased": true,
|
||||
"type": "megastone"
|
||||
},
|
||||
"Gengarite": {
|
||||
@@ -1153,7 +1156,6 @@
|
||||
"Ghost Gem": {
|
||||
"description": "The holder's first successful Ghost-type attack has its power multiplied by 1.3. Single use.",
|
||||
"flingPower": 0,
|
||||
"unreleased": true,
|
||||
"spriteId": 344,
|
||||
"type": "misc"
|
||||
},
|
||||
@@ -1194,7 +1196,6 @@
|
||||
"Grass Gem": {
|
||||
"description": "The holder's first successful Grass-type attack has its power multiplied by 1.3. Single use.",
|
||||
"flingPower": 0,
|
||||
"unreleased": true,
|
||||
"spriteId": 335,
|
||||
"type": "misc"
|
||||
},
|
||||
@@ -1248,7 +1249,6 @@
|
||||
"Ground Gem": {
|
||||
"description": "The holder's first successful Ground-type attack has its power multiplied by 1.3. Single use.",
|
||||
"flingPower": 0,
|
||||
"unreleased": true,
|
||||
"spriteId": 339,
|
||||
"type": "misc"
|
||||
},
|
||||
@@ -1272,6 +1272,7 @@
|
||||
"mega"
|
||||
],
|
||||
"spriteId": 406,
|
||||
"unreleased": true,
|
||||
"type": "megastone"
|
||||
},
|
||||
"HP Up": {
|
||||
@@ -1372,6 +1373,7 @@
|
||||
"mega"
|
||||
],
|
||||
"spriteId": 396,
|
||||
"unreleased": true,
|
||||
"type": "megastone"
|
||||
},
|
||||
"Hyper Potion": {
|
||||
@@ -1393,7 +1395,6 @@
|
||||
"Ice Gem": {
|
||||
"description": "The holder's first successful Ice-type attack has its power multiplied by 1.3. Single use.",
|
||||
"flingPower": 0,
|
||||
"unreleased": true,
|
||||
"spriteId": 336,
|
||||
"type": "misc"
|
||||
},
|
||||
@@ -1471,6 +1472,7 @@
|
||||
"mega"
|
||||
],
|
||||
"spriteId": 405,
|
||||
"unreleased": true,
|
||||
"type": "megastone"
|
||||
},
|
||||
"Kasib Berry": {
|
||||
@@ -1646,6 +1648,7 @@
|
||||
"mega"
|
||||
],
|
||||
"spriteId": 404,
|
||||
"unreleased": true,
|
||||
"type": "megastone"
|
||||
},
|
||||
"Love Ball": {
|
||||
@@ -1662,6 +1665,7 @@
|
||||
"mega"
|
||||
],
|
||||
"spriteId": 403,
|
||||
"unreleased": true,
|
||||
"type": "megastone"
|
||||
},
|
||||
"Luck Incense": {
|
||||
@@ -1741,6 +1745,7 @@
|
||||
"description": "No competitive use. Evolves Magmar into Magmortar when traded.",
|
||||
"flingPower": 80,
|
||||
"spriteId": 306,
|
||||
"unreleased": true,
|
||||
"type": "misc"
|
||||
},
|
||||
"Magnet": {
|
||||
@@ -1777,6 +1782,7 @@
|
||||
"mega"
|
||||
],
|
||||
"spriteId": 412,
|
||||
"unreleased": true,
|
||||
"type": "megastone"
|
||||
},
|
||||
"Maranga Berry": {
|
||||
@@ -1859,6 +1865,7 @@
|
||||
"mega"
|
||||
],
|
||||
"spriteId": 395,
|
||||
"unreleased": true,
|
||||
"type": "megastone"
|
||||
}, "Meganiumite": {
|
||||
"description": "If the holder is a Meganium, this item allows it to Mega Evolve into Mega Meganium in battle.",
|
||||
@@ -1868,6 +1875,7 @@
|
||||
"mega"
|
||||
],
|
||||
"spriteId": 404,
|
||||
"unreleased": true,
|
||||
"type": "megastone"
|
||||
},
|
||||
"Mental Herb": {
|
||||
@@ -1927,7 +1935,6 @@
|
||||
"Micle Berry": {
|
||||
"description": "The holder's next move has its accuracy multiplied by 1.2 when it has 1/4 or less of its maximum HP. Single use.",
|
||||
"flingPower": 10,
|
||||
"unreleased": true,
|
||||
"naturalGift": {
|
||||
"power": 100,
|
||||
"type": "Rock"
|
||||
@@ -1941,6 +1948,7 @@
|
||||
"Milotic",
|
||||
"mega"
|
||||
],
|
||||
"unreleased": true,
|
||||
"spriteId": 404,
|
||||
"type": "megastone"
|
||||
}, "Miltankite": {
|
||||
@@ -2237,6 +2245,7 @@
|
||||
"mega"
|
||||
],
|
||||
"spriteId": 401,
|
||||
"unreleased": true,
|
||||
"type": "megastone"
|
||||
},
|
||||
"Pixie Plate": {
|
||||
@@ -2260,7 +2269,6 @@
|
||||
"Poison Gem": {
|
||||
"description": "The holder's first successful Poison-type attack has its power multiplied by 1.3. Single use.",
|
||||
"flingPower": 0,
|
||||
"unreleased": true,
|
||||
"spriteId": 338,
|
||||
"type": "misc"
|
||||
},
|
||||
@@ -2373,7 +2381,6 @@
|
||||
"Psychic Gem": {
|
||||
"description": "The holder's first successful Psychic-type attack has its power multiplied by 1.3. Single use.",
|
||||
"flingPower": 0,
|
||||
"unreleased": true,
|
||||
"spriteId": 341,
|
||||
"type": "misc"
|
||||
},
|
||||
@@ -2621,7 +2628,6 @@
|
||||
"Rock Gem": {
|
||||
"description": "The holder's first successful Rock-type attack has its power multiplied by 1.3. Single use.",
|
||||
"flingPower": 0,
|
||||
"unreleased": true,
|
||||
"spriteId": 343,
|
||||
"type": "misc"
|
||||
},
|
||||
@@ -2748,6 +2754,7 @@
|
||||
"mega"
|
||||
],
|
||||
"spriteId": 400,
|
||||
"unreleased": true,
|
||||
"type": "megastone"
|
||||
},
|
||||
"Scope Lens": {
|
||||
@@ -2783,6 +2790,7 @@
|
||||
"mega"
|
||||
],
|
||||
"spriteId": 404,
|
||||
"unreleased": true,
|
||||
"type": "megastone"
|
||||
},
|
||||
"Shed Shell": {
|
||||
@@ -3001,7 +3009,6 @@
|
||||
"Steel Gem": {
|
||||
"description": "The holder's first successful Steel-type attack has its power multiplied by 1.3. Single use.",
|
||||
"flingPower": 0,
|
||||
"unreleased": true,
|
||||
"spriteId": 347,
|
||||
"type": "misc"
|
||||
},
|
||||
@@ -3227,7 +3234,6 @@
|
||||
"Water Gem": {
|
||||
"description": "The holder's first successful Water-type attack has its power multiplied by 1.3. Single use.",
|
||||
"flingPower": 0,
|
||||
"unreleased": true,
|
||||
"spriteId": 333,
|
||||
"type": "misc"
|
||||
},
|
||||
|
||||
@@ -357,6 +357,23 @@ CLIENT_VERSION = assets.getVersion()
|
||||
server.removePlayer(user.name)
|
||||
user.send("findBattleCanceled")
|
||||
|
||||
spark.on 'findBattleunranked', (format, team, altName=null) ->
|
||||
return unless _.isString(format)
|
||||
return unless _.isObject(team)
|
||||
return unless !altName || _.isString(altName)
|
||||
# Note: If altName == null, then isAltOwnedBy will return true
|
||||
alts.isAltOwnedBy user.name, altName, (err, valid) ->
|
||||
if not valid
|
||||
user.error(errors.INVALID_ALT_NAME, "You do not own this alt")
|
||||
else
|
||||
validationErrors = server.queuePlayerunranked(user.name, team, format, altName)
|
||||
if validationErrors.length > 0
|
||||
user.error(errors.FIND_BATTLE, validationErrors)
|
||||
|
||||
spark.on 'cancelFindBattleunranked', ->
|
||||
server.removePlayerunranked(user.name)
|
||||
user.send("findBattleCanceledUnranked")
|
||||
|
||||
spark.on 'sendMove', (battleId, moveName, slot, forTurn, options, callback) ->
|
||||
return unless _.isString(moveName)
|
||||
return unless _.isFinite(slot)
|
||||
@@ -425,7 +442,20 @@ CLIENT_VERSION = assets.getVersion()
|
||||
lobby.message(message)
|
||||
setTimeout(battleSearch, 5 * 1000)
|
||||
|
||||
battleSearchUnranked = ->
|
||||
server.beginBattlesunranked (err, battleIds) ->
|
||||
if err then return
|
||||
for id in battleIds
|
||||
battle = server.findBattle(id)
|
||||
playerIds = battle.getPlayerIds()
|
||||
ratingKeys = playerIds.map((id) -> battle.getPlayer(id).ratingKey)
|
||||
ratings.getRanks ratingKeys, (err, fullRanks) ->
|
||||
ranks = _.compact(fullRanks)
|
||||
setTimeout(battleSearchUnranked, 5 * 1000)
|
||||
|
||||
|
||||
battleSearch()
|
||||
battleSearchUnranked()
|
||||
|
||||
httpServer.listen(port)
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ class @BattleQueue
|
||||
@newPlayers = []
|
||||
@recentlyMatched = {}
|
||||
@length = 0
|
||||
@ranked = true
|
||||
|
||||
# Adds a player to the queue.
|
||||
# "name" can either be the real name, or an alt
|
||||
@@ -40,6 +41,9 @@ class @BattleQueue
|
||||
@newPlayers.push(player)
|
||||
@length += 1
|
||||
return true
|
||||
|
||||
setUnranked: ->
|
||||
@ranked = false
|
||||
|
||||
remove: (playerIds) ->
|
||||
playerIds = Array(playerIds) if playerIds not instanceof Array
|
||||
@@ -71,23 +75,26 @@ class @BattleQueue
|
||||
# An internal function which loads ratings for newly queued players
|
||||
# and removes them from the newly queued list
|
||||
updateNewPlayers: (next) ->
|
||||
ratingKeys = (queued.player.ratingKey for queued in @newPlayers)
|
||||
return next(null) if ratingKeys.length == 0
|
||||
if @ranked == true
|
||||
ratingKeys = (queued.player.ratingKey for queued in @newPlayers)
|
||||
return next(null) if ratingKeys.length == 0
|
||||
|
||||
ratings.getRatings ratingKeys, (err, returnedRatings) =>
|
||||
if err then return next(err)
|
||||
|
||||
ratings.setActive ratingKeys, (err) =>
|
||||
ratings.getRatings ratingKeys, (err, returnedRatings) =>
|
||||
if err then return next(err)
|
||||
|
||||
# Update the ratings in the player objects
|
||||
for rating, i in returnedRatings
|
||||
continue unless @hasUserId(@newPlayers[i].player.id)
|
||||
@newPlayers[i].rating = rating
|
||||
ratings.setActive ratingKeys, (err) =>
|
||||
if err then return next(err)
|
||||
|
||||
# reset the new players list, we're done
|
||||
@newPlayers.splice(0, @newPlayers.length)
|
||||
next(null)
|
||||
# Update the ratings in the player objects
|
||||
for rating, i in returnedRatings
|
||||
continue unless @hasUserId(@newPlayers[i].player.id)
|
||||
@newPlayers[i].rating = rating
|
||||
|
||||
# reset the new players list, we're done
|
||||
@newPlayers.splice(0, @newPlayers.length)
|
||||
next(null)
|
||||
else
|
||||
next(null)
|
||||
|
||||
# Returns an array of pairs. Each pair is a queue object that contains
|
||||
# a player and team key, corresponding to the player socket and player's team.
|
||||
@@ -98,7 +105,8 @@ class @BattleQueue
|
||||
if err then return next(err, null)
|
||||
|
||||
sortedPlayers = (queued for id, queued of @queue)
|
||||
sortedPlayers.sort((a, b) -> a.rating - b.rating)
|
||||
if @ranked
|
||||
sortedPlayers.sort((a, b) -> a.rating - b.rating)
|
||||
|
||||
alreadyMatched = (false for [0...sortedPlayers.length])
|
||||
|
||||
@@ -115,20 +123,19 @@ class @BattleQueue
|
||||
rightPlayer = right.player
|
||||
|
||||
# Continue if these two players already played
|
||||
continue if @hasRecentlyMatched(leftPlayer.id, rightPlayer.id)
|
||||
|
||||
continue if @hasRecentlyMatched(leftPlayer.id, rightPlayer.id) and @ranked
|
||||
# If the rating difference is too large break out, we have no possible match for left
|
||||
break unless left.intersectsWith(right)
|
||||
|
||||
# Everything checks out, so make the pair and break out
|
||||
pairs.push([leftPlayer, rightPlayer])
|
||||
@remove([leftPlayer.id, rightPlayer.id])
|
||||
@addRecentMatch(leftPlayer.id, rightPlayer.id)
|
||||
@addRecentMatch(leftPlayer.id, rightPlayer.id) if @ranked
|
||||
alreadyMatched[leftIdx] = alreadyMatched[rightIdx] = true
|
||||
break
|
||||
|
||||
# Expand the range of all unmatched players
|
||||
queued.range += RANGE_INCREMENT for id, queued of @queue
|
||||
|
||||
# Return the list of paired players
|
||||
|
||||
next(null, pairs)
|
||||
|
||||
@@ -30,14 +30,28 @@ FIND_BATTLE_CONDITIONS = [
|
||||
Conditions.UNRELEASED_BAN
|
||||
]
|
||||
|
||||
FIND_BATTLE_CONDITIONS_UNRANKED = [
|
||||
Conditions.TEAM_PREVIEW
|
||||
Conditions.TIMED_BATTLE
|
||||
Conditions.SLEEP_CLAUSE
|
||||
Conditions.EVASION_CLAUSE
|
||||
Conditions.SPECIES_CLAUSE
|
||||
Conditions.PRANKSTER_SWAGGER_CLAUSE
|
||||
Conditions.OHKO_CLAUSE
|
||||
Conditions.UNRELEASED_BAN
|
||||
]
|
||||
|
||||
MAX_NICKNAME_LENGTH = 15
|
||||
|
||||
class @BattleServer
|
||||
constructor: ->
|
||||
@queues = {}
|
||||
@unrankedqueues = {}
|
||||
allformats = ConditionsFunc.Formats()
|
||||
for format of allformats
|
||||
@queues[format] = new BattleQueue()
|
||||
@unrankedqueues[format] = new BattleQueue()
|
||||
@unrankedqueues[format].setUnranked()
|
||||
@battles = {}
|
||||
|
||||
# A hash mapping users to battles.
|
||||
@@ -239,7 +253,46 @@ class @BattleServer
|
||||
return next(err) if err
|
||||
next(null, _.flatten(battleIds))
|
||||
return true
|
||||
#########################################################################################
|
||||
# Adds the player to the queue. Note that there is no validation on whether altName
|
||||
# is correct, so make
|
||||
queuePlayerunranked: (playerId, team, format = DEFAULT_FORMAT, altName) ->
|
||||
if @isLockedDown()
|
||||
err = ["The server is restarting after all battles complete. No new battles can start at this time."]
|
||||
else
|
||||
err = @validateTeam(team, format, FIND_BATTLE_CONDITIONS)
|
||||
if err.length == 0
|
||||
name = @users.get(playerId).name
|
||||
ratingKey = alts.uniqueId(playerId, altName)
|
||||
@unrankedqueues[format].add(playerId, altName || name, team, ratingKey)
|
||||
return err
|
||||
|
||||
queuedPlayersunranked: (format = DEFAULT_FORMAT) ->
|
||||
@unrankedqueues[format].queuedPlayers()
|
||||
|
||||
removePlayerunranked: (playerId, format = DEFAULT_FORMAT) ->
|
||||
return false if format not of @unrankedqueues
|
||||
@unrankedqueues[format].remove(playerId)
|
||||
return true
|
||||
|
||||
beginBattlesunranked: (next) ->
|
||||
allformats = ConditionsFunc.Formats()
|
||||
array = for format in Object.keys(allformats)
|
||||
do (format) => (callback) =>
|
||||
@unrankedqueues[format].pairPlayers (err, pairs) =>
|
||||
if err then console.log(err)
|
||||
if err then return callback(err)
|
||||
# Create a battle for each pair
|
||||
battleIds = []
|
||||
for pair in pairs
|
||||
id = @createBattle(format, pair, FIND_BATTLE_CONDITIONS_UNRANKED)
|
||||
battleIds.push(id)
|
||||
callback(null, battleIds)
|
||||
async.parallel array, (err, battleIds) ->
|
||||
return next(err) if err
|
||||
next(null, _.flatten(battleIds))
|
||||
return true
|
||||
#########################################################################################
|
||||
# Creates a battle and returns its battleId
|
||||
createBattle: (rawFormat = DEFAULT_FORMAT, pair = [], conditions = []) ->
|
||||
allformats = ConditionsFunc.Formats()
|
||||
|
||||
Reference in New Issue
Block a user