notify end of correspondence games - closes #2037
parent
924032fea0
commit
68e1195e13
|
@ -9,7 +9,7 @@ net {
|
|||
ip = "5.196.91.160"
|
||||
asset {
|
||||
domain = ${net.domain}
|
||||
version = 966
|
||||
version = 967
|
||||
}
|
||||
}
|
||||
forcedev = false
|
||||
|
@ -410,7 +410,6 @@ gameSearch {
|
|||
round {
|
||||
active.ttl = 30 seconds
|
||||
uid.timeout = 10 seconds
|
||||
finisher.lock.timeout = 20 seconds
|
||||
animation.duration = ${chessground.animation.duration}
|
||||
moretime = 15 seconds
|
||||
casual_only = ${app.casual_only}
|
||||
|
|
|
@ -52,9 +52,7 @@ case class Player(
|
|||
|
||||
def goBerserk = copy(berserk = true)
|
||||
|
||||
def finish(winner: Boolean) = copy(
|
||||
isWinner = if (winner) Some(true) else None
|
||||
)
|
||||
def finish(winner: Boolean) = copy(isWinner = winner option true)
|
||||
|
||||
def offerDraw(turn: Int) = copy(
|
||||
isOfferingDraw = true,
|
||||
|
|
|
@ -46,6 +46,11 @@ private object BSONHandlers {
|
|||
implicit val BlogTitleHandler = stringAnyValHandler[NewBlogPost.Title](_.value, NewBlogPost.Title.apply)
|
||||
implicit val NewBlogPostHandler = Macros.handler[NewBlogPost]
|
||||
|
||||
implicit val GameEndGameIdHandler = stringAnyValHandler[GameEnd.GameId](_.value, GameEnd.GameId.apply)
|
||||
implicit val GameEndOpponentHandler = stringAnyValHandler[GameEnd.OpponentId](_.value, GameEnd.OpponentId.apply)
|
||||
implicit val GameEndWinHandler = booleanAnyValHandler[GameEnd.Win](_.value, GameEnd.Win.apply)
|
||||
implicit val GameEndHandler = Macros.handler[GameEnd]
|
||||
|
||||
implicit val ColorBSONHandler = new BSONHandler[BSONBoolean, chess.Color] {
|
||||
def read(b: BSONBoolean) = chess.Color(b.value)
|
||||
def write(c: chess.Color) = BSONBoolean(c.white)
|
||||
|
@ -64,6 +69,7 @@ private object BSONHandlers {
|
|||
case t: TeamJoined => TeamJoinedHandler.write(t)
|
||||
case b: NewBlogPost => NewBlogPostHandler.write(b)
|
||||
case LimitedTournamentInvitation => $empty
|
||||
case x: GameEnd => GameEndHandler.write(x)
|
||||
}
|
||||
} ++ $doc("type" -> notificationContent.key)
|
||||
|
||||
|
@ -86,13 +92,14 @@ private object BSONHandlers {
|
|||
}
|
||||
|
||||
def reads(reader: Reader): NotificationContent = reader.str("type") match {
|
||||
case "mention" => readMentionedNotification(reader)
|
||||
case "invitedStudy" => readInvitedStudyNotification(reader)
|
||||
case "privateMessage" => PrivateMessageHandler read reader.doc
|
||||
case "qaAnswer" => QaAnswerHandler read reader.doc
|
||||
case "teamJoined" => TeamJoinedHandler read reader.doc
|
||||
case "newBlogPost" => NewBlogPostHandler read reader.doc
|
||||
case "u" => LimitedTournamentInvitation
|
||||
case "mention" => readMentionedNotification(reader)
|
||||
case "invitedStudy" => readInvitedStudyNotification(reader)
|
||||
case "privateMessage" => PrivateMessageHandler read reader.doc
|
||||
case "qaAnswer" => QaAnswerHandler read reader.doc
|
||||
case "teamJoined" => TeamJoinedHandler read reader.doc
|
||||
case "newBlogPost" => NewBlogPostHandler read reader.doc
|
||||
case "u" => LimitedTournamentInvitation
|
||||
case "gameEnd" => GameEndHandler read reader.doc
|
||||
}
|
||||
|
||||
def writes(writer: Writer, n: NotificationContent): dsl.Bdoc = {
|
||||
|
|
|
@ -37,6 +37,10 @@ final class JSONHandlers(
|
|||
"slug" -> slug.value,
|
||||
"title" -> title.value)
|
||||
case LimitedTournamentInvitation => Json.obj()
|
||||
case GameEnd(gameId, opponentId, win) => Json.obj(
|
||||
"id" -> gameId.value,
|
||||
"opponent" -> opponentId.map(_.value).flatMap(getLightUser),
|
||||
"win" -> win.map(_.value))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -102,3 +102,14 @@ object NewBlogPost {
|
|||
}
|
||||
|
||||
case object LimitedTournamentInvitation extends NotificationContent("u")
|
||||
|
||||
case class GameEnd(
|
||||
gameId: GameEnd.GameId,
|
||||
opponentId: Option[GameEnd.OpponentId],
|
||||
win: Option[GameEnd.Win]) extends NotificationContent("gameEnd")
|
||||
|
||||
object GameEnd {
|
||||
case class GameId(value: String) extends AnyVal
|
||||
case class OpponentId(value: String) extends AnyVal
|
||||
case class Win(value: Boolean) extends AnyVal
|
||||
}
|
||||
|
|
|
@ -7,6 +7,8 @@ import scala.concurrent.duration._
|
|||
|
||||
import actorApi.{ GetSocketStatus, SocketStatus }
|
||||
import lila.common.PimpedConfig._
|
||||
import lila.game.{ Game, Pov }
|
||||
import lila.hub.actorApi.HasUserId
|
||||
import lila.hub.actorApi.map.{ Ask, Tell }
|
||||
import lila.socket.actorApi.GetVersion
|
||||
import makeTimeout.large
|
||||
|
@ -23,6 +25,7 @@ final class Env(
|
|||
lightUser: String => Option[lila.common.LightUser],
|
||||
userJsonView: lila.user.JsonView,
|
||||
rankingApi: lila.user.RankingApi,
|
||||
notifyApi: lila.notify.NotifyApi,
|
||||
uciMemo: lila.game.UciMemo,
|
||||
rematch960Cache: lila.memo.ExpireSetMemo,
|
||||
isRematchCache: lila.memo.ExpireSetMemo,
|
||||
|
@ -41,7 +44,6 @@ final class Env(
|
|||
val Moretime = config duration "moretime"
|
||||
val SocketName = config getString "socket.name"
|
||||
val SocketTimeout = config duration "socket.timeout"
|
||||
val FinisherLockTimeout = config duration "finisher.lock.timeout"
|
||||
val NetDomain = config getString "net.domain"
|
||||
val ActorMapName = config getString "actor.map.name"
|
||||
val CasualOnly = config getBoolean "casual_only"
|
||||
|
@ -126,13 +128,18 @@ final class Env(
|
|||
coll = db(CollectionForecast),
|
||||
roundMap = hub.actor.roundMap)
|
||||
|
||||
private lazy val notifier = new RoundNotifier(
|
||||
timeline = hub.actor.timeline,
|
||||
isUserPresent = isUserPresent,
|
||||
notifyApi = notifyApi)
|
||||
|
||||
private lazy val finisher = new Finisher(
|
||||
messenger = messenger,
|
||||
perfsUpdater = perfsUpdater,
|
||||
crosstableApi = crosstableApi,
|
||||
notifier = notifier,
|
||||
playban = playban,
|
||||
bus = system.lilaBus,
|
||||
timeline = hub.actor.timeline,
|
||||
casualOnly = CasualOnly)
|
||||
|
||||
private lazy val rematcher = new Rematcher(
|
||||
|
@ -165,6 +172,9 @@ final class Env(
|
|||
private def getSocketStatus(gameId: String): Fu[SocketStatus] =
|
||||
socketHub ? Ask(gameId, GetSocketStatus) mapTo manifest[SocketStatus]
|
||||
|
||||
private def isUserPresent(game: Game, userId: lila.user.User.ID): Fu[Boolean] =
|
||||
socketHub ? Ask(game.id, HasUserId(userId)) mapTo manifest[Boolean]
|
||||
|
||||
lazy val jsonView = new JsonView(
|
||||
noteApi = noteApi,
|
||||
userJsonView = userJsonView,
|
||||
|
@ -192,12 +202,12 @@ final class Env(
|
|||
val tvBroadcast = system.actorOf(Props(classOf[TvBroadcast]))
|
||||
system.lilaBus.subscribe(tvBroadcast, 'moveEvent, 'changeFeaturedGame)
|
||||
|
||||
def checkOutoftime(game: lila.game.Game) {
|
||||
def checkOutoftime(game: Game) {
|
||||
if (game.playable && game.started && !game.isUnlimited)
|
||||
roundMap ! Tell(game.id, actorApi.round.Outoftime)
|
||||
}
|
||||
|
||||
def resign(pov: lila.game.Pov) {
|
||||
def resign(pov: Pov) {
|
||||
if (pov.game.abortable)
|
||||
roundMap ! Tell(pov.game.id, actorApi.round.Abort(pov.playerId))
|
||||
else if (pov.game.playable)
|
||||
|
@ -219,6 +229,7 @@ object Env {
|
|||
lightUser = lila.user.Env.current.lightUser,
|
||||
userJsonView = lila.user.Env.current.jsonView,
|
||||
rankingApi = lila.user.Env.current.rankingApi,
|
||||
notifyApi = lila.notify.Env.current.api,
|
||||
uciMemo = lila.game.Env.current.uciMemo,
|
||||
rematch960Cache = lila.game.Env.current.cached.rematch960,
|
||||
isRematchCache = lila.game.Env.current.cached.isRematch,
|
||||
|
|
|
@ -15,9 +15,9 @@ private[round] final class Finisher(
|
|||
messenger: Messenger,
|
||||
perfsUpdater: PerfsUpdater,
|
||||
playban: PlaybanApi,
|
||||
notifier: RoundNotifier,
|
||||
crosstableApi: lila.game.CrosstableApi,
|
||||
bus: lila.common.Bus,
|
||||
timeline: akka.actor.ActorSelection,
|
||||
casualOnly: Boolean) {
|
||||
|
||||
def abort(pov: Pov)(implicit proxy: GameProxy): Fu[Events] = apply(pov.game, _.Aborted) >>- {
|
||||
|
@ -56,8 +56,7 @@ private[round] final class Finisher(
|
|||
message: Option[SelectI18nKey] = None)(implicit proxy: GameProxy): Fu[Events] = {
|
||||
val status = makeStatus(Status)
|
||||
val prog = game.finish(status, winner)
|
||||
if (game.nonAi && game.isCorrespondence)
|
||||
Color.all foreach notifyTimeline(prog.game)
|
||||
if (game.nonAi && game.isCorrespondence) Color.all foreach notifier.gameEnd(prog.game)
|
||||
lila.mon.game.finish(status.name)()
|
||||
casualOnly.fold(
|
||||
GameRepo unrate prog.game.id inject prog.game.copy(mode = chess.Mode.Casual),
|
||||
|
@ -86,19 +85,6 @@ private[round] final class Finisher(
|
|||
}
|
||||
} >>- proxy.invalidate
|
||||
|
||||
private def notifyTimeline(game: Game)(color: Color) = {
|
||||
import lila.hub.actorApi.timeline.{ Propagate, GameEnd }
|
||||
if (!game.aborted) game.player(color).userId foreach { userId =>
|
||||
game.perfType foreach { perfType =>
|
||||
timeline ! (Propagate(GameEnd(
|
||||
playerId = game fullIdOf color,
|
||||
opponent = game.player(!color).userId,
|
||||
win = game.winnerColor map (color ==),
|
||||
perf = perfType.key)) toUser userId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def updateCountAndPerfs(finish: FinishGame): Funit =
|
||||
(!finish.isVsSelf && !finish.game.aborted) ?? {
|
||||
(finish.white |@| finish.black).tupled ?? {
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
package lila.round
|
||||
|
||||
import lila.hub.actorApi.timeline.{ Propagate, GameEnd => TLGameEnd }
|
||||
import lila.notify.{ GameEnd, Notification, NotifyApi }
|
||||
|
||||
import lila.game.Game
|
||||
import lila.user.User
|
||||
|
||||
private final class RoundNotifier(
|
||||
timeline: akka.actor.ActorSelection,
|
||||
isUserPresent: (Game, User.ID) => Fu[Boolean],
|
||||
notifyApi: NotifyApi) {
|
||||
|
||||
def gameEnd(game: Game)(color: chess.Color) = {
|
||||
if (!game.aborted) game.player(color).userId foreach { userId =>
|
||||
game.perfType foreach { perfType =>
|
||||
timeline ! (Propagate(TLGameEnd(
|
||||
playerId = game fullIdOf color,
|
||||
opponent = game.player(!color).userId,
|
||||
win = game.winnerColor map (color ==),
|
||||
perf = perfType.key)) toUser userId)
|
||||
}
|
||||
isUserPresent(game, userId) foreach {
|
||||
case false => notifyApi.addNotification(Notification(
|
||||
Notification.Notifies(userId),
|
||||
GameEnd(
|
||||
GameEnd.GameId(game.id),
|
||||
game.opponent(color).userId map GameEnd.OpponentId.apply,
|
||||
game.wonBy(color) map GameEnd.Win.apply)))
|
||||
case _ =>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
var m = require('mithril');
|
||||
|
||||
function userFullName(u) {
|
||||
if (!u) return '?';
|
||||
if (!u) return 'Anonymous';
|
||||
return u.title ? u.title + ' ' + u.name : u.name;
|
||||
}
|
||||
|
||||
|
@ -145,6 +145,45 @@ var handlers = {
|
|||
text: function(n) {
|
||||
return 'Game with ' + n.content.opponentName + '.';
|
||||
}
|
||||
},
|
||||
gameEnd: {
|
||||
html: function(notification) {
|
||||
var content = notification.content
|
||||
var url = "/" + content.id;
|
||||
var result;
|
||||
switch (content.win) {
|
||||
case true:
|
||||
result = 'Congratulations, you won!';
|
||||
break;
|
||||
case false:
|
||||
result = 'You lost!';
|
||||
break;
|
||||
default:
|
||||
result = "It's a draw.";
|
||||
}
|
||||
|
||||
return genericNotification(notification, url, ';', [
|
||||
m('span', [
|
||||
m('strong', 'Game vs ' + userFullName(content.opponent)),
|
||||
drawTime(notification)
|
||||
]),
|
||||
m('span', result)
|
||||
]);
|
||||
},
|
||||
text: function(n) {
|
||||
var result;
|
||||
switch (n.content.win) {
|
||||
case true:
|
||||
result = 'Victory';
|
||||
break;
|
||||
case false:
|
||||
result = 'Defeat';
|
||||
break;
|
||||
default:
|
||||
result = 'Draw';
|
||||
}
|
||||
return result + ' vs ' + userFullName(n.content.opponent);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in New Issue