Compare commits

...

10 Commits

Author SHA1 Message Date
v.holodov
bda5a53bca Add readme 2024-03-17 14:31:58 +03:00
v.holodov
b0dfc5b67a Sync conf 2024-03-17 14:06:09 +03:00
v.holodov
ecc69cd7d3 Additional races 2023-08-05 16:08:50 +03:00
v.holodov
31ce4b7beb Рабочий десайдер 2023-05-04 23:58:43 +03:00
v.holodov
dc228d82be Add fourth player, add 4x4 maps 2022-11-19 01:27:55 +03:00
v.holodov
750ea483b6 2 new map, fix map name 4+ players 2022-10-05 23:22:08 +03:00
v.holodov
d3e90fb01e Видосы и параметризованный выбор кол-ва рас 2022-06-03 21:07:54 +03:00
Viktor Kholodov
aa692419cf Tournaments can be configured now 2021-10-23 14:13:36 +03:00
Viktor Kholodov
52baef061b Autumn meatgrinder 2021-09-25 03:14:47 +03:00
Viktor Kholodov
e71480c1f7 with superfinal 2021-09-12 13:27:46 +03:00
69 changed files with 1600 additions and 481 deletions

View File

@ -1 +1,18 @@
This is decider for Soulstorm #Десайдер для турниров по Soulstorm
##Стек технологий backend:
- akka (не типизированные акторы)
- play framework 2.8.2
- scala 2.12.8
##Стек технологий frontend:
- jquery 1.4.1
- верстка/ui: bootstrap 3.3.6
Для запуска проекта необходимо установить sbt(scala build tools) и выполнить команду ```sbt run```.
Если при переходе на ```http://localhost:9000/decider/classic``` откроется десайдер - всё сделано правильно.
В проекте предусмотрена защита от xss атак *(хотя, тут воровать то нечего, но всё же)*, потому при развертывании
где-либо, необходимо добавить в HomeController в originMatches ip адрес хостинга или домен.

View File

@ -1,26 +1,27 @@
package actors package actors
import actors.UserActor.{BanMapMessage, GetName, HostLeaveLobby, LobbyFatal, Message, RefreshLobbyInfo, SecondPlayerLeaveLobby, SendBanMapMessage, SendMessage, SetUserAsHost, SetUserAsObs, SetUserAsSecondPlayer} import actors.UserActor._
import akka.actor.{Actor, ActorLogging, ActorRef} import akka.actor.{Actor, ActorRef}
import akka.event.LoggingReceive import akka.event.LoggingReceive
import com.typesafe.scalalogging.LazyLogging
import akka.pattern.ask
import akka.util.Timeout import akka.util.Timeout
import com.typesafe.config.{Config, ConfigFactory} import com.typesafe.config.ConfigFactory
import com.typesafe.scalalogging.LazyLogging
import java.util
import scala.collection.JavaConverters.iterableAsScalaIterableConverter import scala.collection.JavaConverters.iterableAsScalaIterableConverter
import scala.collection.immutable.{HashSet, Queue} import scala.collection.immutable.HashSet
import scala.concurrent.Await
import scala.concurrent.duration._ import scala.concurrent.duration._
import scala.util.Try
case class LobbyUser(name: String, actorRef: ActorRef) case class LobbyUser(name: String, actorRef: ActorRef)
case class DeciderMap(map: String, var isBanned: Boolean = false)
case class DeciderMap(map: String, description: Option[String] = None, var isBanned: Boolean = false)
/** /**
* There is one StockActor per stock symbol. The StockActor maintains a list of users watching the stock and the stock * There is one StockActor per stock symbol. The StockActor maintains a list of users watching the stock and the stock
* values. Each StockActor updates a rolling dataset of randomly generated stock values. * values. Each StockActor updates a rolling dataset of randomly generated stock values.
*/ */
class LobbieActor(hostUser: LobbyUser) extends Actor with LazyLogging { class LobbieActor(hostUser: LobbyUser, deciderName: String) extends Actor with LazyLogging {
val config = ConfigFactory.load() val config = ConfigFactory.load()
logger.info(s"Create lobby... host: ${hostUser.actorRef.path.name}") logger.info(s"Create lobby... host: ${hostUser.actorRef.path.name}")
@ -41,20 +42,26 @@ class LobbieActor(hostUser: LobbyUser) extends Actor with LazyLogging {
private var status: LobbyStatus = NotStarted() private var status: LobbyStatus = NotStarted()
private var lobbyType: LobbyType = Last3() private var lobbyType: LobbyType = Last1()
private var firstPlayerSelectedRaces: Option[SelectedRaces] = None
private var secondPlayerSelectedRaces: Option[SelectedRaces] = None
private val mapsLobby: Set[DeciderMap] = { private val mapsLobby: Set[DeciderMap] = {
val configMaps = config.getStringList("maps").asScala val configMaps = config.getList(s"deciders.$deciderName.maps").asScala
configMaps.map(e => { configMaps.map(e => {
DeciderMap(e) val mapConfig = e.unwrapped().asInstanceOf[util.ArrayList[String]].asScala
DeciderMap(mapConfig.head, mapConfig.tail.headOption)
}).toSet }).toSet
} }
hostUser.actorRef.tell( hostUser.actorRef.tell(
SetUserAsHost(LobbyInfo( SetUserAsHost(LobbyInfo(
UserInfo(hostUser.name,firstPlayerReady), UserInfo(hostUser.name, firstPlayerReady),
UserInfo("", secondPlayerReady), UserInfo("", secondPlayerReady),
self.path.name, self.path.name,
deciderName,
status.toString(), status.toString(),
playerTurn, playerTurn,
lobbyType.toString(), lobbyType.toString(),
@ -69,49 +76,53 @@ class LobbieActor(hostUser: LobbyUser) extends Actor with LazyLogging {
// notify watchers // notify watchers
logger.info(s"Ban map $mapName by ${sender.path.name}") logger.info(s"Ban map $mapName by ${sender.path.name}")
if(status == Draft()){ if (status == Draft()) {
if((playerTurn == 1 && sender == host.actorRef) || if ((playerTurn == 1 && sender == host.actorRef) ||
(playerTurn == 2 && secondPlayer.exists(_.actorRef == sender))){ (playerTurn == 2 && secondPlayer.exists(_.actorRef == sender))) {
mapsLobby.find(p => p.map == mapName) match { mapsLobby.find(p => p.map == mapName) match {
case Some(map) => case Some(map) =>
if(!map.isBanned){ if (!map.isBanned) {
map.isBanned = true map.isBanned = true
if (playerTurn== 1) playerTurn = 2 else playerTurn = 1 if (playerTurn == 1) playerTurn = 2 else playerTurn = 1
if(mapsLobby.count(_.isBanned == false) == 3 && lobbyType == Last3() || if (isFinish) {
mapsLobby.count(_.isBanned == false) == 4 && lobbyType == Last4() ||
mapsLobby.count(_.isBanned == false) == 5 && lobbyType == Last5() ||
mapsLobby.count(_.isBanned == false) == 1 && lobbyType == LooserPick()){
status = Finish() status = Finish()
} }
users.foreach(_ ! RefreshLobbyInfo(getLobbyInfoResponse)) refreshAndBanMap(mapName)
val senderName: String = if (sender == host.actorRef) {
host.name
} else secondPlayer.get.name
users.foreach(_ ! SendBanMapMessage(BanMapMessage(senderName, mapName)))
} else { } else {
logger.warn(s"User ban map $mapName, but map already banned") logger.warn(s"User ban map $mapName, but map already banned")
} }
case None => case None =>
logger.error(s"Map $mapName not exist") logger.error(s"Map $mapName not exist")
} }
} else{ } else {
logger.warn(s"Player ${sender.path.name} ban map, but turn is $playerTurn") logger.warn(s"Player ${sender.path.name} ban map, but turn is $playerTurn")
} }
} else { } else {
logger.warn(s"Player ${sender.path.name} ban map, but lobby status is '$status' status") logger.warn(s"Player ${sender.path.name} ban map, but lobby status is '$status' status")
} }
case selectedRaces: SelectedRaces =>
if (sender == host.actorRef) {
firstPlayerSelectedRaces = Some(selectedRaces)
logger.info(s"First player select $selectedRaces")
} else if (sender == secondPlayer.get.actorRef) {
secondPlayerSelectedRaces = Some(selectedRaces)
logger.info(s"Second player select $selectedRaces")
}
if (firstPlayerSelectedRaces.nonEmpty && secondPlayerSelectedRaces.nonEmpty) {
status = Draft()
users.foreach(_ ! RefreshLobbyInfo(getLobbyInfoResponse))
}
case SetReady => case SetReady =>
// notify watchers // notify watchers
var readyPlayer = sender() var readyPlayer = sender()
if(readyPlayer == hostUser.actorRef){ if (readyPlayer == hostUser.actorRef) {
firstPlayerReady = true firstPlayerReady = true
} else if (secondPlayer.exists(sp => sp.actorRef == readyPlayer)) { } else if (secondPlayer.exists(sp => sp.actorRef == readyPlayer)) {
secondPlayerReady = true secondPlayerReady = true
} }
if(firstPlayerReady && secondPlayerReady){ if (firstPlayerReady && secondPlayerReady) {
status = Draft() status = SelectRace()
} }
users.foreach(_ ! RefreshLobbyInfo(getLobbyInfoResponse)) users.foreach(_ ! RefreshLobbyInfo(getLobbyInfoResponse))
@ -119,7 +130,7 @@ class LobbieActor(hostUser: LobbyUser) extends Actor with LazyLogging {
case SetNotReady => case SetNotReady =>
// notify watchers // notify watchers
var notReadyPlayer = sender() var notReadyPlayer = sender()
if(notReadyPlayer == hostUser.actorRef){ if (notReadyPlayer == hostUser.actorRef) {
firstPlayerReady = false firstPlayerReady = false
} else if (secondPlayer.exists(sp => sp.actorRef == notReadyPlayer)) { } else if (secondPlayer.exists(sp => sp.actorRef == notReadyPlayer)) {
secondPlayerReady = false secondPlayerReady = false
@ -128,14 +139,15 @@ class LobbieActor(hostUser: LobbyUser) extends Actor with LazyLogging {
case ChangeLobbyType(lobbyTypeNew) => case ChangeLobbyType(lobbyTypeNew) =>
lobbyTypeNew match { lobbyTypeNew match {
case "last1" =>
lobbyType = Last1()
case "last3" => case "last3" =>
lobbyType = Last3() lobbyType = Last3()
case "last4" =>
lobbyType = Last4()
case "last5" => case "last5" =>
lobbyType = Last5() lobbyType = Last5()
case "looserpick" => case "last7" =>
lobbyType = LooserPick() lobbyType = Last7()
} }
users.foreach(_ ! RefreshLobbyInfo(getLobbyInfoResponse)) users.foreach(_ ! RefreshLobbyInfo(getLobbyInfoResponse))
@ -150,7 +162,7 @@ class LobbieActor(hostUser: LobbyUser) extends Actor with LazyLogging {
case MessageForLobby(message) => case MessageForLobby(message) =>
if (sender() == host.actorRef) { if (sender() == host.actorRef) {
users.foreach(_ ! SendMessage(Message(host.name, message))) users.foreach(_ ! SendMessage(Message(host.name, message)))
}else if (secondPlayer.exists(sp => sp.actorRef == sender())){ } else if (secondPlayer.exists(sp => sp.actorRef == sender())) {
users.foreach(_ ! SendMessage(Message(secondPlayer.get.name, message))) users.foreach(_ ! SendMessage(Message(secondPlayer.get.name, message)))
} }
case JoinLobbyAsPlayer(sp) => case JoinLobbyAsPlayer(sp) =>
@ -171,16 +183,16 @@ class LobbieActor(hostUser: LobbyUser) extends Actor with LazyLogging {
sender ! SetUserAsObs(getLobbyInfoResponse) sender ! SetUserAsObs(getLobbyInfoResponse)
case LeaveLobby => case LeaveLobby =>
users = users - sender users = users - sender
if(host.actorRef == sender()){ if (host.actorRef == sender()) {
users.foreach(_ ! HostLeaveLobby) users.foreach(_ ! HostLeaveLobby)
context.stop(self) context.stop(self)
}else if(secondPlayer.exists(_.actorRef == sender())){ } else if (secondPlayer.exists(_.actorRef == sender())) {
secondPlayerReady = false secondPlayerReady = false
secondPlayer = None secondPlayer = None
if(status == Draft()){ if (status == Draft() || status == SelectRace()) {
users.foreach(_ ! SecondPlayerLeaveLobby) users.foreach(_ ! SecondPlayerLeaveLobby)
context.stop(self) context.stop(self)
}else { } else {
users.foreach(_ ! RefreshLobbyInfo(getLobbyInfoResponse)) users.foreach(_ ! RefreshLobbyInfo(getLobbyInfoResponse))
} }
} }
@ -196,21 +208,38 @@ class LobbieActor(hostUser: LobbyUser) extends Actor with LazyLogging {
private def getLobbyInfoResponse: LobbyInfo = { private def getLobbyInfoResponse: LobbyInfo = {
val user2Name = secondPlayer.map(_.name).getOrElse("") val user2Name = secondPlayer.map(_.name).getOrElse("")
LobbyInfo( LobbyInfo(
UserInfo(host.name, firstPlayerReady), UserInfo(host.name, firstPlayerReady, firstPlayerSelectedRaces),
UserInfo(user2Name, secondPlayerReady), UserInfo(user2Name, secondPlayerReady, secondPlayerSelectedRaces),
self.path.name, self.path.name,
deciderName,
status.toString(), status.toString(),
playerTurn, playerTurn,
lobbyType.toString(), lobbyType.toString(),
mapsLobby) mapsLobby)
} }
private def isFinish: Boolean = {
mapsLobby.count(_.isBanned == false) == 1 && lobbyType == Last1() ||
mapsLobby.count(_.isBanned == false) == 3 && lobbyType == Last3() ||
mapsLobby.count(_.isBanned == false) == 5 && lobbyType == Last5() ||
mapsLobby.count(_.isBanned == false) == 7 && lobbyType == Last7()
}
private def refreshAndBanMap(mapName: String): Unit = {
val senderName: String = if (sender == host.actorRef) {
host.name
} else secondPlayer.get.name
users.foreach(_ ! SendBanMapMessage(BanMapMessage(senderName, mapName)))
users.foreach(_ ! RefreshLobbyInfo(getLobbyInfoResponse))
}
} }
case class BanMap(mapName: String) case class BanMap(mapName: String)
case class MapsUpdate(maps: Set[String]) case class MapsUpdate(maps: Set[String])
case class CreateLobby(userName: String) case class CreateLobby(userName: String, deciderName: String)
case class JoinLobbyAsPlayer(lobbyUser: LobbyUser) case class JoinLobbyAsPlayer(lobbyUser: LobbyUser)
@ -228,13 +257,18 @@ case class MessageForLobby(message: String)
case class ChangeLobbyType(lobbyType: String) case class ChangeLobbyType(lobbyType: String)
case class ChangeIsNecronSelected(isSelected: Boolean)
case object InfoQuery case object InfoQuery
case class UserInfo(name: String, isReady: Boolean) case class SelectedRaces(mainRaces: List[Int], additionalRaces: List[Int])
case class UserInfo(name: String, isReady: Boolean, selectedRaces: Option[SelectedRaces] = None)
case class LobbyInfo(user1Info: UserInfo, case class LobbyInfo(user1Info: UserInfo,
user2Info: UserInfo, user2Info: UserInfo,
lobbyActorName: String, lobbyActorName: String,
deciderName: String,
status: String, status: String,
playerTurn: BigDecimal, playerTurn: BigDecimal,
selectedType: String, selectedType: String,
@ -243,12 +277,21 @@ case class LobbyInfo(user1Info: UserInfo,
class LobbyStatus class LobbyStatus
sealed case class NotStarted() extends LobbyStatus sealed case class NotStarted() extends LobbyStatus
sealed case class SelectRace() extends LobbyStatus
sealed case class Draft() extends LobbyStatus sealed case class Draft() extends LobbyStatus
sealed case class Finish() extends LobbyStatus sealed case class Finish() extends LobbyStatus
class LobbyType class LobbyType
sealed case class Last1() extends LobbyType
sealed case class Last3() extends LobbyType sealed case class Last3() extends LobbyType
sealed case class Last4() extends LobbyType
sealed case class Last5() extends LobbyType sealed case class Last5() extends LobbyType
sealed case class LooserPick() extends LobbyType
sealed case class Last7() extends LobbyType
sealed case class Superfinal() extends LobbyType

View File

@ -17,10 +17,10 @@ class LobbiesActor extends Actor with LazyLogging {
val lobbies: ListBuffer[String] = ListBuffer() val lobbies: ListBuffer[String] = ListBuffer()
def receive: Receive = LoggingReceive { def receive: Receive = LoggingReceive {
case CreateLobby(hostName) => case CreateLobby(hostName, deciderName) =>
val hostActorRef = sender val hostActorRef = sender
logger.info(s"Player ${hostActorRef.path.name} create lobby.") logger.info(s"Player ${hostActorRef.path.name} create lobby.")
val lobbyActor = context.actorOf(Props(new LobbieActor(LobbyUser(hostName, hostActorRef))), val lobbyActor = context.actorOf(Props(new LobbieActor(LobbyUser(hostName, hostActorRef), deciderName)),
s"lobbyActor-${(math.random * 100000000L).toLong}") s"lobbyActor-${(math.random * 100000000L).toLong}")
lobbyActor.tell(WatchLobby("watchIt"), hostActorRef) lobbyActor.tell(WatchLobby("watchIt"), hostActorRef)
case JoinLobbyByActorName(lobbyName, userName) => case JoinLobbyByActorName(lobbyName, userName) =>

View File

@ -13,21 +13,27 @@ import scala.concurrent.duration.DurationInt
import scala.util.{Failure, Success} import scala.util.{Failure, Success}
class UserActor(out: ActorRef, class UserActor(out: ActorRef,
userParentActor: ActorRef) extends Actor with LazyLogging { userParentActor: ActorRef,
ip: String) extends Actor with LazyLogging {
implicit val timeout: Timeout = 3.seconds implicit val timeout: Timeout = 3.seconds
val ipAddress: String = ip
var lobbieActor: Option[ActorRef] = None var lobbieActor: Option[ActorRef] = None
implicit val mapJson: Writes[DeciderMap] = new Writes[DeciderMap] { implicit val mapJson: Writes[DeciderMap] = new Writes[DeciderMap] {
def writes(deciderMap: DeciderMap): JsValue = { def writes(deciderMap: DeciderMap): JsValue = {
Json.obj( Json.obj(
"map" -> deciderMap.map, "map" -> deciderMap.map,
"description" -> deciderMap.description,
"isBanned" -> deciderMap.isBanned "isBanned" -> deciderMap.isBanned
) )
} }
} }
implicit val selectedRacesWrites: OWrites[SelectedRaces] = Json.writes[SelectedRaces]
implicit val userInfoWrites: OWrites[UserInfo] = Json.writes[UserInfo] implicit val userInfoWrites: OWrites[UserInfo] = Json.writes[UserInfo]
implicit val lobbyWrites: Writes[LobbyInfo] = new Writes[LobbyInfo]{ implicit val lobbyWrites: Writes[LobbyInfo] = new Writes[LobbyInfo]{
@ -35,6 +41,7 @@ class UserActor(out: ActorRef,
"user1Info" -> lobby.user1Info, "user1Info" -> lobby.user1Info,
"user2Info" -> lobby.user2Info, "user2Info" -> lobby.user2Info,
"lobbyActorName" -> lobby.lobbyActorName, "lobbyActorName" -> lobby.lobbyActorName,
"deciderName" -> lobby.deciderName,
"status" -> lobby.status, "status" -> lobby.status,
"playerTurn" -> lobby.playerTurn, "playerTurn" -> lobby.playerTurn,
"selectedType" -> lobby.selectedType, "selectedType" -> lobby.selectedType,
@ -53,6 +60,7 @@ class UserActor(out: ActorRef,
"user1Info" -> lobby.user1Info, "user1Info" -> lobby.user1Info,
"user2Info" -> lobby.user2Info, "user2Info" -> lobby.user2Info,
"lobbyActorName" -> lobby.lobbyActorName, "lobbyActorName" -> lobby.lobbyActorName,
"deciderName" -> lobby.deciderName,
"status" -> lobby.status, "status" -> lobby.status,
"playerTurn" -> lobby.playerTurn, "playerTurn" -> lobby.playerTurn,
"selectedType" -> lobby.selectedType, "selectedType" -> lobby.selectedType,
@ -127,7 +135,8 @@ class UserActor(out: ActorRef,
logger.debug(s"Set user name: $name for actor ${this.self}") logger.debug(s"Set user name: $name for actor ${this.self}")
case Some("createDecider") => case Some("createDecider") =>
LobbiesActor.actor ! CreateLobby(name) val deciderName = (json \ "deciderName").as[String]
LobbiesActor.actor ! CreateLobby(name, deciderName)
case Some("leaveDecider") => case Some("leaveDecider") =>
lobbieActor.foreach(lobby => lobby ! LeaveLobby) lobbieActor.foreach(lobby => lobby ! LeaveLobby)
@ -163,6 +172,12 @@ class UserActor(out: ActorRef,
val map = (json \ "map").as[String] val map = (json \ "map").as[String]
lobbieActor.foreach(lobby => lobby ! BanMap(map)) lobbieActor.foreach(lobby => lobby ! BanMap(map))
case Some("selectRace") =>
logger.info("RACES: " + json.toString())
val races = (json \ "mainRaces").as[List[Int]]
val additionalRaces = (json \ "additionalRaces").as[List[Int]]
lobbieActor.foreach(lobby => lobby ! SelectedRaces(races, additionalRaces))
case Some("getLobbies") => case Some("getLobbies") =>
logger.debug("Get all lobby request") logger.debug("Get all lobby request")
(LobbiesActor.actor ? GetAllLobbies).mapTo[List[RefreshLobbyInfo]] onComplete { (LobbiesActor.actor ? GetAllLobbies).mapTo[List[RefreshLobbyInfo]] onComplete {
@ -192,10 +207,11 @@ class UserParentActor(actorSystem: ActorSystem) extends Actor with ActorLogging
import UserParentActor._ import UserParentActor._
override def receive: Receive = LoggingReceive { override def receive: Receive = LoggingReceive {
case Create(id, out) => case Create(id, ipAddress, out) =>
val child: ActorRef = actorSystem.actorOf(Props(classOf[UserActor], out, self), s"userActor-$id") val child: ActorRef = actorSystem.actorOf(Props(classOf[UserActor], out, self, ipAddress), s"userActor-$id")
sender() ! child sender() ! child
case GetAllUsers => case GetAllUsers =>
System.out.print("12123" + context.children)
sender() ! context.children sender() ! context.children
} }
} }
@ -203,7 +219,7 @@ class UserParentActor(actorSystem: ActorSystem) extends Actor with ActorLogging
object UserParentActor { object UserParentActor {
case class Create(id: String, out: ActorRef) case class Create(id: String, ipAddress: String, out: ActorRef)
case object GetAllUsers case object GetAllUsers

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -132,6 +132,8 @@
"6p_agamar_desert": "Agamar Desert (6)", "6p_agamar_desert": "Agamar Desert (6)",
"6p_alvarus": "Alvarus (6)", "6p_alvarus": "Alvarus (6)",
"6p_kaurav_city": "Kaurav City (6)", "6p_kaurav_city": "Kaurav City (6)",
"6p_snowblind": "Snowblind (6)",
"6p_ruined_greatway": "Ruined Greatway (6)",
"8p_forbidden_jungle": "Forbidden Jungle (8)", "8p_forbidden_jungle": "Forbidden Jungle (8)",
"8p_rhean_jungle": "Rhean Jungle (8)", "8p_rhean_jungle": "Rhean Jungle (8)",
"8p_thurabis_plateau": "ThurAbis Plateau (8)", "8p_thurabis_plateau": "ThurAbis Plateau (8)",

Binary file not shown.

After

Width:  |  Height:  |  Size: 565 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 565 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 565 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 502 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 502 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 502 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 499 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

View File

@ -2,7 +2,13 @@ var ws;
var userName = "" var userName = ""
window.onload = function() { var raceCount = $("#raceCount").html()
var existAdditionalRaces = $("#existAdditionalRaces").html() === "true"
var isMuted = false;
var isDraftStarted = false;
window.onload = function () {
'use strict'; 'use strict';
@ -28,26 +34,23 @@ window.onload = function() {
"click .title": "check" // Обработчик клика на кнопке "Проверить" "click .title": "check" // Обработчик клика на кнопке "Проверить"
}, },
joinAsPlayer: function () { render: function () {
this.elCount = "azaz" + getRandomInt(100000000);
this.render();
},
render: function() {
var lobbiesAsTableRows = _.reduce(this.lobbies, function (memo, lobby) { var lobbiesAsTableRows = _.reduce(this.lobbies, function (memo, lobby) {
var joinButton = (lobby.status === "NotStarted()") ? '<button class="btn btn-primary join-as-player" onclick="joinDecider(\''+lobby.lobbyActorName+'\')">' + if ($("#deciderName").html() !== lobby.deciderName) return memo;
var joinButton = (lobby.status === "NotStarted()") ? '<button class="btn btn-primary join-as-player" onclick="joinDecider(\'' + lobby.lobbyActorName + '\')">' +
'<img src="/assets/images/buttons/isAuto.png" style="height: 25px;"> Join lobby <img src="/assets/images/buttons/isAuto.png" style="height: 25px;">' + '<img src="/assets/images/buttons/isAuto.png" style="height: 25px;"> Join lobby <img src="/assets/images/buttons/isAuto.png" style="height: 25px;">' +
'</button>' : ""; '</button>' : "";
var observerButton = '<button class="btn btn-success join-as-observer" onclick="observerDecider(\''+lobby.lobbyActorName+'\')">' + var observerButton = '<button class="btn btn-success join-as-observer" onclick="observerDecider(\'' + lobby.lobbyActorName + '\')">' +
'<img src="/assets/images/buttons/Ulthwe.png" style="height: 25px;"> Observer <img src="/assets/images/buttons/Ulthwe.png" style="height: 25px;">' + '<img src="/assets/images/buttons/Ulthwe.png" style="height: 25px;"> Observer <img src="/assets/images/buttons/Ulthwe.png" style="height: 25px;">' +
'</button>'; '</button>';
return memo + '<tr>\n' + return memo + '<tr>\n' +
' <td>'+ lobby.user1Info.name.substring(0, 20) +'</td>\n' + ' <td>' + lobby.user1Info.name.substring(0, 20) + '</td>\n' +
' <td>'+ lobby.user2Info.name.substring(0, 20) +'</td>\n' + ' <td>' + lobby.user2Info.name.substring(0, 20) + '</td>\n' +
' <td>'+ lobby.status +'</td>\n' + ' <td>' + lobby.status + '</td>\n' +
' <td>' + joinButton + ' ' + observerButton + ' <td>' + joinButton + ' ' + observerButton +
' </td>' + ' </td>' +
'</tr>'; '</tr>';
@ -74,14 +77,16 @@ window.onload = function() {
$("#lobbiesList").append(lobbyList.$el); $("#lobbiesList").append(lobbyList.$el);
lobbyList.render(); lobbyList.render();
$("#createDecider").click(function(event) { $("#createDecider").click(function (event) {
event.preventDefault(); event.preventDefault();
isObserver = false;
ws.send(JSON.stringify({ ws.send(JSON.stringify({
type: "createDecider" type: "createDecider",
deciderName: $("#deciderName").html()
})); }));
}); });
$("#submitmsg").click(function(event) { $("#submitmsg").click(function (event) {
sendMessage(); sendMessage();
}); });
$("#usermsg").on('keyup', function (e) { $("#usermsg").on('keyup', function (e) {
@ -90,8 +95,17 @@ window.onload = function() {
} }
}); });
$("#mute").click(function (event) {
if (isMuted) {
isMuted = false;
$("#mute").html("🔊");
} else {
isMuted = true;
$("#mute").html("🔈");
}
});
$("#exit").click(function(event) { $("#exit").click(function (event) {
event.preventDefault(); event.preventDefault();
ws.send(JSON.stringify({ ws.send(JSON.stringify({
type: "leaveDecider" type: "leaveDecider"
@ -101,24 +115,34 @@ window.onload = function() {
$(".navbar").show(); $(".navbar").show();
}); });
if($.cookie('user')=== undefined){ function requestName() {
var result = prompt("Enter nickname: ", ); if ($.cookie('user') === undefined || $.cookie('user') === "null" || $.cookie('user') === "") {
$.cookie('user', result, { expires: 1337 }); var result = prompt("Enter nickname: ",);
$.cookie('user', result, {expires: 1337});
userName = result; userName = result;
}else{ requestName();
} else {
userName = $.cookie('user'); userName = $.cookie('user');
} }
}
requestName();
$("#playerName").html(userName); $("#playerName").html(userName);
ws = new WebSocket($("body").data("ws-url")); ws = new WebSocket($("body").data("ws-url"));
ws.onmessage = function(event) { ws.onmessage = function (event) {
var message; var message;
message = JSON.parse(event.data); message = JSON.parse(event.data);
switch (message.type) { switch (message.type) {
case "refreshLobby": case "refreshLobby":
if (message.lobby.status === "SelectRace()" && (!isObserver)) {
isDraftStarted = false;
renderRaces();
} else {
renderMaps(message.lobby.maps, message.lobby.status); renderMaps(message.lobby.maps, message.lobby.status);
}
renderPlayersAndStats(message.lobby); renderPlayersAndStats(message.lobby);
break; break;
case "switchToLobby": case "switchToLobby":
@ -132,7 +156,7 @@ window.onload = function() {
break; break;
case "sendMapBanMessage": case "sendMapBanMessage":
console.log(message); console.log(message);
addBanMapMessageToChat(message.message); handleMapBanEvent(message.message);
break; break;
case "lobbyError": case "lobbyError":
console.log(message); console.log(message);
@ -146,6 +170,7 @@ window.onload = function() {
default: default:
} }
}; };
ws.onopen = function () { ws.onopen = function () {
ws.send(JSON.stringify({ ws.send(JSON.stringify({
type: "userName", type: "userName",
@ -156,7 +181,7 @@ window.onload = function() {
})); }));
} }
var timerId = setInterval(() => setInterval(() =>
ws.send(JSON.stringify({ ws.send(JSON.stringify({
type: "getLobbies" type: "getLobbies"
})), 2000); })), 2000);
@ -164,9 +189,9 @@ window.onload = function() {
function changeNick() { function changeNick() {
result = prompt("Введите ник", $.cookie('user')); result = prompt("Введите ник", $.cookie('user'));
if(result == null) return; if (result == null) return;
result = result.substr(0, 20); result = result.substr(0, 20);
$.cookie('user', result, { expires: 7 }); $.cookie('user', result, {expires: 7});
ws.send(JSON.stringify({ ws.send(JSON.stringify({
type: "userName", type: "userName",
name: $.cookie('user') name: $.cookie('user')
@ -177,7 +202,6 @@ function changeNick() {
} }
// -----ф-ии прихода с сервера // -----ф-ии прихода с сервера
function switchToLobby(maps, status) { function switchToLobby(maps, status) {
@ -199,6 +223,125 @@ function disconnectLobby(error) {
alert(error); alert(error);
} }
// ---------------Выбор рас --------------
function getRaceImageByNumber(number) {
switch (number) {
case 1:
return "/assets/images/raceIcons/SM.gif";
case 2:
return "/assets/images/raceIcons/Chaos.gif";
case 3:
return "/assets/images/raceIcons/Eldar.gif";
case 4:
return "/assets/images/raceIcons/Orks.gif";
case 5:
return "/assets/images/raceIcons/IG.gif";
case 6:
return "/assets/images/raceIcons/Nec.gif";
case 7:
return "/assets/images/raceIcons/Tau.gif";
case 8:
return "/assets/images/raceIcons/Sob.gif";
case 9:
return "/assets/images/raceIcons/De.gif";
}
}
function getRaceVideoByNumber(number) {
switch (number) {
case 1:
return "/assets/video/races/SM.mp4";
case 2:
return "/assets/video/races/Chaos.mp4";
case 3:
return "/assets/video/races/Eldar.mp4";
case 4:
return "/assets/video/races/Orks.mp4";
case 5:
return "/assets/video/races/IG.mp4";
case 6:
return "/assets/video/races/Nec.mp4";
case 7:
return "/assets/video/races/Tau.mp4";
case 8:
return "/assets/video/races/Sob.mp4";
case 9:
return "/assets/video/races/SM.mp4";
}
}
function renderRaces() {
function getRaceSelectHtml(raceType, raceArray) {
var resHtml = "";
for(var raceNum = 1; raceNum <= raceCount; raceNum++){
resHtml += "<div class='col-xs-4'><center><h3>" + raceType + " race " + raceNum + "</h3>";
for (var i = 1; i <= 9; i++) {
var selected = "raceNotSelected";
if ($.cookie("race-" + raceType + "-" + raceNum) == i) {
selected = "raceSelected";
console.log("setted: " + i);
raceArray[raceNum] = i;
}
let raceIconClass = "raceIcon-" + raceNum + "-" + raceType;
resHtml += '<a href = "#" class="' + selected + ' ' + raceIconClass + '"' +
' onclick="selectRace(this, '+i+', '+raceNum+', \''+raceType+'\')"><img class="raceIcon" src="' + getRaceImageByNumber(i) + '"></a> ';
}
resHtml += "</center></div>";
}
return resHtml;
}
var resHtml = "";
resHtml += getRaceSelectHtml("main", selectedRaces);
if(existAdditionalRaces) resHtml += getRaceSelectHtml("additional", selectedAdditionalRaces);
resHtml += "<div style='clear: both;'></div></br>";
resHtml += "<center><button id = 'selectRace' class='btn btn-primary' disabled onclick='sendSelectedRacesToServer()'>OK</button></center>"
$("#mapList").html(resHtml);
checkRaceSelectFinish();
}
var selectedRaces = [];
var selectedAdditionalRaces = [];
function selectRace(button, race, raceNum, raceType) {
if(raceType === "main"){
selectedRaces[raceNum] = race;
}else{
selectedAdditionalRaces[raceNum] = race;
}
$.cookie("race-" + raceType + "-" + raceNum, race, {expires: 1});
_.each($(".raceIcon-" + raceNum + "-" + raceType), function (el) {
$(el).removeClass("raceSelected");
$(el).addClass("raceNotSelected");
});
$(button).removeClass("raceNotSelected");
$(button).addClass("raceSelected");
checkRaceSelectFinish();
}
function checkRaceSelectFinish() {
if(selectedRaces.filter(x => x).length >= raceCount){
$("#selectRace").removeAttr("disabled");
}
}
function sendSelectedRacesToServer() {
console.log(selectedRaces);
ws.send(JSON.stringify({
type: "selectRace",
mainRaces: selectedRaces.filter(x => x),
additionalRaces: selectedAdditionalRaces.filter(x => x)
}));
$("#mapList").html("<center>Waiting another player...</center>");
}
// ---------------------------------------------
function renderMaps(maps, lobbyStatus) { function renderMaps(maps, lobbyStatus) {
var resHtml = ""; var resHtml = "";
@ -206,23 +349,47 @@ function renderMaps(maps, lobbyStatus) {
var banHtml = ""; var banHtml = "";
var banClass = ""; var banClass = "";
if(map.isBanned){ if (map.isBanned) {
banHtml = "<div class = 'banX'>&#x2715;</div>"; banHtml = "<div class = 'banX'>&#x2715;</div>";
banClass = "bannedMap"; banClass = "bannedMap";
} }
if(lobbyStatus === "NotStarted()"){
banClass = "bannedMap"; var title = ""
if (map.description != null) {
title = "title='" + map.description + "'";
} }
resHtml = resHtml + '<div class="col-xs-4 col-md-3 col-lg-2"><button class = "mapSelect" onmousedown="banMap(\''+map.map+'\')">'+ resHtml = resHtml + '<div class="col-xs-4 col-md-3 col-lg-2"><button class = "mapSelect" onmousedown="banMap(\'' + map.map + '\')">' +
'<div class="mapImageAndText '+banClass+'">' + '<div class="mapImageAndText ' + banClass + '">' +
'<img class="img-rounded center-block" style="width:128px;" src="/assets/images/maps/'+encodeURI(map.map)+'.jpg">' + '<img class="img-rounded center-block" ' + title + ' style="width:128px;" src="/assets/images/maps/' + encodeURI(map.map) + '.jpg">' +
'</div>'+convertMapName(map.map)+ '</div>' + convertMapName(map.map) +
banHtml + '</button></div>'; banHtml + '</button></div>';
}) })
$("#mapList").html(resHtml); $("#mapList").html(resHtml);
} }
var isObserver = false;
function renderFinish(maps, firstPlayerInfo, secondPlayerInfo) {
var resHtml = "<center>";
_.each(maps, function (map) {
if (!map.isBanned) {
resHtml = resHtml + '<div class="col-xs-4 col-md-3 col-lg-2"><button class = "mapSelect" >' +
'<div class="mapImageAndText ">' +
'<img class="img-rounded center-block" style="width:128px;" src="/assets/images/maps/' + encodeURI(map.map) + '.jpg">' +
'</div>' + convertMapName(map.map) + '</button></div>';
}
})
resHtml += "<div style='clear: both;'>";
resHtml += getRenderRaces(firstPlayerInfo, secondPlayerInfo);
resHtml += "</center>";
$("#mapList").html(resHtml);
}
function renderPlayersAndStats(lobby) { function renderPlayersAndStats(lobby) {
var resHtml = "<center>"; var resHtml = "<center>";
var player1ReadyBtn = ""; var player1ReadyBtn = "";
@ -231,30 +398,30 @@ function renderPlayersAndStats(lobby) {
var notReadyImg = "<img src='/assets/images/buttons/isNotAuto.png' class='readyImg'/>"; var notReadyImg = "<img src='/assets/images/buttons/isNotAuto.png' class='readyImg'/>";
var warningClass = (lobby.user2Info.name !== "") ? "warningButton" : ""; var warningClass = (lobby.user2Info.name !== "") ? "warningButton" : "";
if(lobby.user1Info.name === userName){ if (lobby.user1Info.name === userName) {
if(lobby.user1Info.isReady){ if (lobby.user1Info.isReady) {
player1ReadyBtn = "<button class='btn btn-default' onclick='setNotReady()'>"+readyImg+" Ready "+readyImg+"</button>" player1ReadyBtn = "<button class='btn btn-default' onclick='setNotReady()'>" + readyImg + " Ready " + readyImg + "</button>"
}else{ } else {
player1ReadyBtn = "<button class='btn btn-default "+warningClass+"' onclick='setReady()'>"+notReadyImg+" Not ready "+notReadyImg+"</button>" player1ReadyBtn = "<button class='btn btn-default " + warningClass + "' onclick='setReady()'>" + notReadyImg + " Not ready " + notReadyImg + "</button>"
} }
}else{ } else {
if(lobby.user1Info.isReady){ if (lobby.user1Info.isReady) {
player1ReadyBtn = readyImg player1ReadyBtn = readyImg
}else{ } else {
player1ReadyBtn = notReadyImg player1ReadyBtn = notReadyImg
} }
} }
if(lobby.user2Info.name === userName){ if (lobby.user2Info.name === userName) {
if(lobby.user2Info.isReady){ if (lobby.user2Info.isReady) {
player2ReadyBtn = "<button class='btn btn-default' onclick='setNotReady()'>"+readyImg+" Ready "+readyImg+"</button>" player2ReadyBtn = "<button class='btn btn-default' onclick='setNotReady()'>" + readyImg + " Ready " + readyImg + "</button>"
}else{ } else {
player2ReadyBtn = "<button class='btn btn-default "+warningClass+"' onclick='setReady()'>"+notReadyImg+" Not ready "+notReadyImg+"</button>" player2ReadyBtn = "<button class='btn btn-default " + warningClass + "' onclick='setReady()'>" + notReadyImg + " Not ready " + notReadyImg + "</button>"
} }
} else { } else {
if(lobby.user2Info.isReady){ if (lobby.user2Info.isReady) {
player2ReadyBtn = readyImg player2ReadyBtn = readyImg
}else{ } else {
player2ReadyBtn = notReadyImg player2ReadyBtn = notReadyImg
} }
} }
@ -266,68 +433,86 @@ function renderPlayersAndStats(lobby) {
} }
var lobbyTypeText = ""; var lobbyTypeText = "";
var last1Selected = "";
var last3Selected = ""; var last3Selected = "";
var last4Selected = "";
var last5Selected = ""; var last5Selected = "";
var looserPickSelected = ""; var last7Selected = "";
var isNecronsSelected = "";
if (lobby.isNecrons) isNecronsSelected = "checked";
switch (lobby.selectedType) { switch (lobby.selectedType) {
case "Last1()":
looserPickSelected = "selected";
lobbyTypeText = "Play on last map";
break;
case "Last3()": case "Last3()":
last3Selected = "selected"; last3Selected = "selected";
lobbyTypeText = "Play on last 3 maps"; lobbyTypeText = "Play on last 3 maps";
break; break;
case "Last4()":
last4Selected = "selected";
lobbyTypeText = "Play on last 4 maps";
break;
case "Last5()": case "Last5()":
last5Selected = "selected"; last5Selected = "selected";
lobbyTypeText = "Play on last 5 maps"; lobbyTypeText = "Play on last 5 maps";
break; break;
case "LooserPick()": case "Last7()":
looserPickSelected = "selected"; last7Selected = "selected";
lobbyTypeText = "Play on last map"; lobbyTypeText = "Play on last 7 maps";
break; break;
} }
console.log(lobby.status);
switch (lobby.status) { switch (lobby.status) {
case "NotStarted()": case "NotStarted()":
resHtml = "<div style='float: left; padding-top: 10px;'> <div>" + lobby.user1Info.name + ": " + player1ReadyBtn +"</div><br/>" resHtml = "<div style='float: left; padding-top: 10px;'> <div>" + lobby.user1Info.name + ": " + player1ReadyBtn + "</div><br/>"
if(lobby.user2Info.name !== ""){ if (lobby.user2Info.name !== "") {
var kickBtn = "" var kickBtn = ""
if(lobby.user1Info.name === userName){ if (lobby.user1Info.name === userName) {
kickBtn = " - <button class='btn btn-danger' onclick='kickSecondPlayer()'>Kick</button>"; kickBtn = " - <button class='btn btn-danger' onclick='kickSecondPlayer()'>Kick</button>";
} }
resHtml += "<div>" + lobby.user2Info.name + ": " + player2ReadyBtn + kickBtn + "</div>" resHtml += "<div>" + lobby.user2Info.name + ": " + player2ReadyBtn + kickBtn + "</div>"
}else{ } else {
resHtml += "<div>waiting 2-nd player...</div>" resHtml += "<div>waiting 2-nd player...</div>"
} }
var disabledText = ""; var disabledText = "";
if(lobby.user1Info.name !== userName){ if (lobby.user1Info.name !== userName) {
disabledText = "disabled"; disabledText = "disabled";
} }
resHtml += "<br/><select class=\"form-control\" id = 'deciderOption' onChange='changeLobbyType()' "+disabledText+" >" +
"<option "+last3Selected+" value = 'last3'>Play on last 3 maps (BO3)</option>" + //TODO: here from config
"<option "+last5Selected+" value = 'last5'>Play on last 5 maps (BO5)</option>" + resHtml += "<br/><select class=\"form-control\" id = 'deciderOption' onChange='changeLobbyType()' " + disabledText + " >" +
"</select>" "<option " + last1Selected + " value = 'last1'>Play on last map</option>" +
"<option " + last3Selected + " value = 'last3'>Play on last 3 maps</option>" +
"<option " + last5Selected + " value = 'last5'>Play on last 5 maps</option>" +
"<option " + last7Selected + " value = 'last7'>Play on last 7 maps</option>" +
"</select>";
if (lobby.user1Info.name !== userName && lobby.user2Info.name !== userName) {
disabledText = "disabled";
} else {
disabledText = "";
}
resHtml += "</div>"; resHtml += "</div>";
break; break;
case "SelectRace()":
resHtml = "<center>" + lobby.user1Info.name + " vs " + lobby.user2Info.name + " - " + lobbyTypeText + "<br/> players select races</center>";
break;
case "Draft()": case "Draft()":
console.log(lobby.turn); if (!isDraftStarted) {
isDraftStarted = true;
}
var playerTurn = (lobby.playerTurn === 1) ? lobby.user1Info.name : lobby.user2Info.name var playerTurn = (lobby.playerTurn === 1) ? lobby.user1Info.name : lobby.user2Info.name
resHtml = "<center>"+lobby.user1Info.name + " vs " + lobby.user2Info.name + " - " +lobbyTypeText+ "<br/><b>" + playerTurn +"</b> turn</center>"; resHtml = "<center><br/>";
resHtml += getRenderRaces(lobby.user1Info, lobby.user2Info);
resHtml += "<br/> <b>" + playerTurn + "</b> turn" + "</center>";
break; break;
case "Finish()": case "Finish()":
var lastMaps = _.filter(lobby.maps, function (map){ renderFinish(lobby.maps, lobby.user1Info, lobby.user2Info);
return map.isBanned === false; break;
});
resHtml = "<center><b>";
_.forEach(lastMaps,function (map){
resHtml += convertMapName(map.map) + "; "
})
resHtml += "</b></center>";
} }
resHtml = resHtml + "</center>" resHtml = resHtml + "</center>"
@ -335,40 +520,67 @@ function renderPlayersAndStats(lobby) {
$("#playersStatsList").html(resHtml); $("#playersStatsList").html(resHtml);
} }
function addMessageToChat(message){ function getRenderRaces(userInfo1, userInfo2) {
function getRacesBlock(playerRaces, name){
let resHtml = "<div style='display: inline-block; border: black 1px solid; background-color: #e3e3e3;'>" + "<h4>" + name + "</h4>";
resHtml += "main:";
playerRaces.mainRaces.forEach((raceId) =>
resHtml += "<img class=\"raceIcon\" src=" + getRaceImageByNumber(raceId) + "> ");
resHtml += "<br/> addt:";
playerRaces.additionalRaces.forEach((raceId) =>
resHtml += "<img class=\"raceIcon\" src=" + getRaceImageByNumber(raceId) + "> ");
resHtml += "</div>";
return resHtml;
}
let resHtml = "";
resHtml += getRacesBlock(userInfo1.selectedRaces, userInfo1.name);
resHtml += "<div style='width: 100px; font-size: 24px; display: inline-block;'><b>VS</b></div>";
resHtml += getRacesBlock(userInfo2.selectedRaces, userInfo2.name);
return resHtml;
}
function addMessageToChat(message) {
var messageHtml = "<div class='msgln'> <b>" + message.userName + "</b>: " + message.message + "<br></div>"; var messageHtml = "<div class='msgln'> <b>" + message.userName + "</b>: " + message.message + "<br></div>";
var chatBox = $("#chatbox"); var chatBox = $("#chatbox");
chatBox.append(messageHtml); chatBox.append(messageHtml);
chatBox.scrollTop(40000); chatBox.scrollTop(40000);
} }
function addBanMapMessageToChat(message){ function handleMapBanEvent(message) {
if (!isMuted) {
var audioPick = new Audio('/assets/sound/pick.mp3.mpeg');
audioPick.volume = 0.1;
audioPick.play();
}
var messageHtml = "<div class='msgln'><span style='color: #2C3D9B'>" + convertMapName(message.mapTechName) + "</span> banned by " + message.user + "<br></div>"; var messageHtml = "<div class='msgln'><span style='color: #2C3D9B'>" + convertMapName(message.mapTechName) + "</span> banned by " + message.user + "<br></div>";
var chatBox = $("#chatbox"); var chatBox = $("#chatbox");
chatBox.append(messageHtml); chatBox.append(messageHtml);
chatBox.scrollTop(40000); chatBox.scrollTop(40000);
} }
function banMap(map){ function banMap(map) {
ws.send(JSON.stringify({ ws.send(JSON.stringify({
type: "banMap", type: "banMap",
map: map map: map
})); }));
} }
function setReady(){ function setReady() {
ws.send(JSON.stringify({ ws.send(JSON.stringify({
type: "setReady" type: "setReady"
})); }));
} }
function setNotReady(){ function setNotReady() {
ws.send(JSON.stringify({ ws.send(JSON.stringify({
type: "setNotReady" type: "setNotReady"
})); }));
} }
function changeLobbyType(){ function changeLobbyType() {
var lobbyType = $("#deciderOption").val(); var lobbyType = $("#deciderOption").val();
ws.send(JSON.stringify({ ws.send(JSON.stringify({
type: "changeLobbyType", type: "changeLobbyType",
@ -376,25 +588,35 @@ function changeLobbyType(){
})); }));
} }
function joinDecider(actorName){ function changeIsNecronSelected() {
var isNecronSelected = $("#isNecron").is(':checked');
ws.send(JSON.stringify({
type: "changeIsNecronSelected",
isNecronSelected: isNecronSelected
}));
}
function joinDecider(actorName) {
isObserver = false;
ws.send(JSON.stringify({ ws.send(JSON.stringify({
type: "joinDecider", type: "joinDecider",
lobbyActorName: actorName lobbyActorName: actorName
})); }));
} }
function observerDecider(actorName){ function observerDecider(actorName) {
isObserver = true;
ws.send(JSON.stringify({ ws.send(JSON.stringify({
type: "observerDecider", type: "observerDecider",
lobbyActorName: actorName lobbyActorName: actorName
})); }));
} }
function sendMessage(){ function sendMessage() {
var userInput = $("#usermsg"); var userInput = $("#usermsg");
var message = userInput.val(); var message = userInput.val();
userInput.val(""); userInput.val("");
if(message !== ""){ if (message !== "") {
ws.send(JSON.stringify({ ws.send(JSON.stringify({
type: "sendMessage", type: "sendMessage",
message: message message: message
@ -402,13 +624,17 @@ function sendMessage(){
} }
} }
function kickSecondPlayer(){ function kickSecondPlayer() {
ws.send(JSON.stringify({ ws.send(JSON.stringify({
type: "kickSecondPlayer" type: "kickSecondPlayer"
})); }));
} }
function convertMapName (techMapName) { function convertMapName(techMapName) {
var mapName = techMapName.replace("2p_", "").replaceAll("_", " ") + " (2)"; var mapPlayerSize = techMapName.charAt(0);
var mapName = techMapName.replace(mapPlayerSize + "p_", "").replaceAll("_", " ") +
" (" + mapPlayerSize + ")";
return mapName.charAt(0).toUpperCase() + mapName.slice(1); return mapName.charAt(0).toUpperCase() + mapName.slice(1);
} }

Binary file not shown.

BIN
app/assets/sound/blip.mp3 Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -20,6 +20,13 @@
-webkit-filter: grayscale(1); /* Webkit браузеры */ -webkit-filter: grayscale(1); /* Webkit браузеры */
} }
.bannedRace {
height: 96px;
background-color: red;
clip-path: polygon(20% 0%, 0% 20%, 30% 50%, 0% 80%, 20% 100%, 50% 70%, 80% 100%, 100% 80%, 70% 50%, 100% 20%, 80% 0%, 50% 30%);
border: 2px solid red;
}
.mapSelect{ .mapSelect{
margin: 4px; margin: 4px;
position:relative; position:relative;
@ -29,6 +36,39 @@
overflow: hidden; overflow: hidden;
} }
.raceIcon{
width: 60px;
border-radius: 5px;
margin: 3px;
border: 1px solid;
}
.raceNotSelected{
-webkit-filter: grayscale(1); /* Webkit браузеры */
}
.raceNotSelected:hover{
-webkit-filter: grayscale(0); /* Webkit браузеры */
}
.raceSelected{
-webkit-filter: brightness(1.3);
}
.tooltip span{
border-radius: 5px 5px 5px 5px;
visibility: hidden;
position: absolute;
left: 200px;
background: #fff;
box-shadow: -2px 2px 10px -1px #333;
border-radius: 2px;
}
.tooltip:hover span{
visibility: visible;
}
@keyframes glowing { @keyframes glowing {
0% { 0% {
box-shadow: 0 0 2px #074673; box-shadow: 0 0 2px #074673;

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,5 +1,6 @@
package controllers package controllers
import actors.UserParentActor.GetAllUsers
import actors._ import actors._
import akka.NotUsed import akka.NotUsed
import akka.actor._ import akka.actor._
@ -9,35 +10,39 @@ import akka.stream._
import akka.stream.scaladsl._ import akka.stream.scaladsl._
import akka.util.Timeout import akka.util.Timeout
import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigFactory
import javax.inject._ import javax.inject._
import org.reactivestreams.Publisher import org.reactivestreams.Publisher
import play.api.libs.json._ import play.api.libs.json._
import play.api.mvc._ import play.api.mvc._
import scala.concurrent.duration._ import scala.concurrent.duration._
import scala.concurrent.{ExecutionContext, Future} import scala.concurrent.{Await, ExecutionContext, Future}
/** /**
* This class creates the actions and the websocket needed. * This class creates the actions and the websocket needed.
*/ */
@Singleton @Singleton
class AdminController @Inject()(cc: ControllerComponents) extends AbstractController(cc) { class AdminController @Inject()(cc: ControllerComponents, homeController: HomeController) extends AbstractController(cc) {
implicit val actorSystem: ActorSystem = ActorSystem() implicit val actorSystem: ActorSystem = ActorSystem()
implicit val ec: ExecutionContext = defaultExecutionContext implicit val ec: ExecutionContext = defaultExecutionContext
implicit val timeout = Timeout(100.millis)
// Use a direct reference to SLF4J // Use a direct reference to SLF4J
private val logger = org.slf4j.LoggerFactory.getLogger("controllers.HomeController") private val logger = org.slf4j.LoggerFactory.getLogger("controllers.HomeController")
val userParentActor: ActorRef = homeController.userParentActor
// Home page that renders template // Home page that renders template
def viewAllLobbies() = Action { implicit request => def viewAllUsers() = Action { implicit request =>
logger.info(s"Received request from: ${request.remoteAddress}") logger.info(s"Received request from: ${request.remoteAddress}")
Ok(views.html.admin())
val allUsers = userParentActor ? GetAllUsers
val res = Await.result(allUsers, Duration.Inf)
Ok(res.toString)
} }
def banLobby(lobbyActorName: String): Unit = {
}
def reloadConfig()= Action { implicit request => def reloadConfig()= Action { implicit request =>
ConfigFactory.invalidateCaches() ConfigFactory.invalidateCaches()

View File

@ -9,12 +9,17 @@ import akka.pattern.ask
import akka.stream._ import akka.stream._
import akka.stream.scaladsl._ import akka.stream.scaladsl._
import akka.util.Timeout import akka.util.Timeout
import com.typesafe.config.ConfigFactory
import org.reactivestreams.Publisher import org.reactivestreams.Publisher
import play.api.libs.json._ import play.api.libs.json._
import play.api.mvc._ import play.api.mvc._
import scala.concurrent.duration._ import scala.concurrent.duration._
import scala.concurrent.{ExecutionContext, Future} import scala.concurrent.{ExecutionContext, Future}
import scala.util.Try
case class LastMapsSelectConfig(last1: Boolean, last3: Boolean, last5: Boolean, last7: Boolean)
case class RaceSelect(select: Boolean, showAtStart: Boolean)
/** /**
* This class creates the actions and the websocket needed. * This class creates the actions and the websocket needed.
@ -25,6 +30,8 @@ class HomeController @Inject()(cc: ControllerComponents) extends AbstractControl
implicit val actorSystem: ActorSystem = ActorSystem() implicit val actorSystem: ActorSystem = ActorSystem()
implicit val ec: ExecutionContext = defaultExecutionContext implicit val ec: ExecutionContext = defaultExecutionContext
val config = ConfigFactory.load()
// Use a direct reference to SLF4J // Use a direct reference to SLF4J
private val logger = org.slf4j.LoggerFactory.getLogger("controllers.HomeController") private val logger = org.slf4j.LoggerFactory.getLogger("controllers.HomeController")
@ -32,9 +39,15 @@ class HomeController @Inject()(cc: ControllerComponents) extends AbstractControl
val userParentActor: ActorRef = actorSystem.actorOf(Props(classOf[UserParentActor], actorSystem)) val userParentActor: ActorRef = actorSystem.actorOf(Props(classOf[UserParentActor], actorSystem))
// Home page that renders template // Home page that renders template
def index() = Action { implicit request => def index(deciderName: String) = Action { implicit request =>
logger.info(s"Received request from: ${request.remoteAddress}") logger.info(s"Received request from: ${request.remoteAddress}")
Ok(views.html.index()) val deciderHumanName = config.getString(s"deciders.$deciderName.name")
val deciderDescription = config.getString(s"deciders.$deciderName.rules")
val existAdditionalRaces = Try(config.getBoolean(s"deciders.$deciderName.existAdditionalRaces")).getOrElse(false)
val raceCount = Try(config.getInt(s"deciders.$deciderName.raceCount")).getOrElse(1)
val lastmapsSettings = LastMapsSelectConfig(true, true, true, true)
val raceSelect = RaceSelect(true, false)
Ok(views.html.index(deciderName, deciderHumanName, raceCount, deciderDescription, raceSelect, lastmapsSettings, existAdditionalRaces))
} }
/** /**
@ -89,7 +102,7 @@ class HomeController @Inject()(cc: ControllerComponents) extends AbstractControl
* Returns true if the value of the Origin header contains an acceptable value. * Returns true if the value of the Origin header contains an acceptable value.
*/ */
def originMatches(origin: String, remoteAddress: String): Boolean = { def originMatches(origin: String, remoteAddress: String): Boolean = {
origin.contains("139.59.210.74") || origin.contains("localhost") || origin.contains("localhost:9000") || origin.contains("localhost:19001") origin.contains("89.108.83.108") || origin.contains("crosspick.ru") || origin.contains("localhost") || origin.contains("localhost:9000") || origin.contains("localhost:19001")
} }
/** /**
@ -100,7 +113,7 @@ class HomeController @Inject()(cc: ControllerComponents) extends AbstractControl
val (webSocketOut: ActorRef, webSocketIn: Publisher[JsValue]) = createWebSocketConnections() val (webSocketOut: ActorRef, webSocketIn: Publisher[JsValue]) = createWebSocketConnections()
// Create a user actor off the request id and attach it to the source // Create a user actor off the request id and attach it to the source
val userActorFuture = createUserActor(request.id.toString, webSocketOut) val userActorFuture = createUserActor(request.id.toString, request.remoteAddress, webSocketOut)
// Once we have an actor available, create a flow... // Once we have an actor available, create a flow...
userActorFuture.map { userActor => userActorFuture.map { userActor =>
@ -178,11 +191,11 @@ class HomeController @Inject()(cc: ControllerComponents) extends AbstractControl
* @param webSocketOut the "write" side of the websocket, that the user actor sends JsValue to. * @param webSocketOut the "write" side of the websocket, that the user actor sends JsValue to.
* @return a user actor for this ws connection. * @return a user actor for this ws connection.
*/ */
private def createUserActor(name: String, webSocketOut: ActorRef): Future[ActorRef] = { private def createUserActor(name: String, remoteAddress: String, webSocketOut: ActorRef): Future[ActorRef] = {
// Use guice assisted injection to instantiate and configure the child actor. // Use guice assisted injection to instantiate and configure the child actor.
val userActorFuture = { val userActorFuture = {
implicit val timeout = Timeout(100.millis) implicit val timeout = Timeout(100.millis)
(userParentActor ? UserParentActor.Create(name, webSocketOut)).mapTo[ActorRef] (userParentActor ? UserParentActor.Create(name, remoteAddress, webSocketOut)).mapTo[ActorRef]
} }
userActorFuture userActorFuture
} }

View File

@ -1,19 +1,26 @@
@()(implicit r: Request[_]) @(deciderName: String, deciderHumanName: String, raceCount: Int, rules: String, raceSelect: RaceSelect, boSettings: LastMapsSelectConfig, existAdditionalRaces: Boolean)(implicit r: Request[_])
<!DOCTYPE html> <!DOCTYPE html>
<span id="deciderName" style="display: none">@deciderName</span>
<span id="raceCount" style="display: none">@raceCount</span>
<span id="existAdditionalRaces" style="display: none">@existAdditionalRaces</span>
<span id="last1Presence" style="display: none">@boSettings.last1</span>
<span id="last3Presence" style="display: none">@boSettings.last3</span>
<span id="last5Presence" style="display: none">@boSettings.last5</span>
<span id="last7Presence" style="display: none">@boSettings.last7</span>
<html> <html>
<head> <head>
<title>Soulstorm tournament decider</title> <title>Soulstorm tournament decider</title>
<link rel='stylesheet' href='@routes.Assets.at("lib/bootstrap/css/bootstrap.min.css")'> <link rel='stylesheet' href='@routes.Assets.at("lib/bootstrap/css/bootstrap.min.css")'>
<link rel="stylesheet" media="screen" href="@routes.Assets.at("stylesheets/main.css")"> <link rel="stylesheet" media="screen" href="@routes.Assets.at("stylesheets/main.css?080422")">
<link rel="shortcut icon" type="image/png" href="@routes.Assets.at("images/favicon.png")"> <link rel="shortcut icon" type="image/png" href="@routes.Assets.at("images/favicon.png")">
<script type='text/javascript' src='@routes.Assets.at("lib/jquery/jquery.min.js")'></script> <script type='text/javascript' src='@routes.Assets.at("lib/jquery/jquery.min.js")'></script>
<script type='text/javascript' src='@routes.Assets.at("lib/flot/jquery.flot.js")'></script> <script type='text/javascript' src='@routes.Assets.at("lib/flot/jquery.flot.js")'></script>
<script type='text/javascript' src='@routes.Assets.at("lib/underscore/underscore.js")'></script> <script type='text/javascript' src='@routes.Assets.at("lib/underscore/underscore.js")'></script>
<script type='text/javascript' src='@routes.Assets.at("lib/backbonejs/backbone.js")'></script> <script type='text/javascript' src='@routes.Assets.at("lib/backbonejs/backbone.js")'></script>
<script type="text/javascript" src="@routes.Assets.at("lib/jquery-cookie/jquery.cookie.js")"></script> <script type="text/javascript" src="@routes.Assets.at("lib/jquery-cookie/jquery.cookie.js")"></script>
<script type='text/javascript' src='@routes.Assets.at("javascripts/index.js?170823")'></script>
<script type='text/javascript' src='@routes.Assets.at("javascripts/index.js?030721")'></script>
</head> </head>
<body data-ws-url="@routes.HomeController.ws.webSocketURL()"> <body data-ws-url="@routes.HomeController.ws.webSocketURL()">
<div class="navbar navbar-inverse navbar-fixed-top"> <div class="navbar navbar-inverse navbar-fixed-top">
@ -37,6 +44,7 @@
<div id = "decider" style="display:none"> <div id = "decider" style="display:none">
<button id = "exit" class="btn btn-primary">Exit</button> <button id = "exit" class="btn btn-primary">Exit</button>
<button id = "mute" class="btn btn-primary">🔊</button>
<br/> <br/>
<div id = "mapList" class="container"></div> <div id = "mapList" class="container"></div>
<div id = "playersStatsList"></div> <div id = "playersStatsList"></div>
@ -51,14 +59,9 @@
</div> </div>
</div> </div>
<div style="clear: both"></div> <div style="clear: both"></div>
<div><i><b>Правила турнира Big Boss of Summer 2021</b><br/> <div><i><b>Правила турнира "@deciderHumanName"</b><br/>
<p>Выбор карт в БО3 и БО5 осуществляется без лузерпиков, с тремя и пятью десайдерами соответственно. <p>@rules</p>
Игроки вычеркивают по очереди карты из маппула, пока их не останется 3 (для БО3) или 5 (для БО5). <br>
На этих картах и проходят все матчи встречи. Затем игроки вычеркивают выбранные карты, пока не останется только одна, на которой и играется первый матч встречи.
Тот, кто первым начал вычеркивать из всего маппула, уступает оппоненту право вычеркивания первой карты из оставшихся карт-десайдеров.
Во втором и последующем матчах проигравший выбирает карту из числа выбранных 3 (5) десайдеров.</p>
<p>В суперфинале турнира оба игрока вычеркивают по две карты. После этого из оставшихся 9 карт можно брать любые, выбирает их проигравший в предыдущем матче.
Первый лузер-пик - за игроком из нижней сетки, который начинает с -1 очком.</p>
</i> </i>
</div> </div>

View File

@ -6,6 +6,7 @@ lazy val root = (project in file(".")).enablePlugins(PlayScala)
scalaVersion := "2.12.8" scalaVersion := "2.12.8"
cancelable in Global := true
// scalaz-bintray resolver needed for specs2 library // scalaz-bintray resolver needed for specs2 library
resolvers += "scalaz-bintray" at "https://dl.bintray.com/scalaz/releases" resolvers += "scalaz-bintray" at "https://dl.bintray.com/scalaz/releases"
@ -18,6 +19,7 @@ libraryDependencies += "org.webjars" % "jquery-cookie" % "1.4.1-1"
libraryDependencies += "org.webjars.npm" % "underscore" % "1.11.0" libraryDependencies += "org.webjars.npm" % "underscore" % "1.11.0"
libraryDependencies += "org.webjars" % "backbonejs" % "1.3.3" libraryDependencies += "org.webjars" % "backbonejs" % "1.3.3"
libraryDependencies += guice libraryDependencies += guice
libraryDependencies += "com.typesafe.scala-logging" %% "scala-logging" % "3.9.2" libraryDependencies += "com.typesafe.scala-logging" %% "scala-logging" % "3.9.2"

View File

@ -2,15 +2,204 @@
# https://www.playframework.com/documentation/latest/AllowedHostsFilter # https://www.playframework.com/documentation/latest/AllowedHostsFilter
# Allow requests to localhost:9000. # Allow requests to localhost:9000.
play.filters.hosts { play.filters.hosts {
allowed = ["localhost:9000", "localhost", "139.59.210.74", "*"] allowed = ["localhost:9000", "localhost", "89.108.83.108", "*", "crosspick.ru"]
} }
play.http.secret.key="QCY?tAnfk?aZ?iwrNwnxIlR6CTf:123123Latabg@5241AB`R5W:1uDFN];Ik@n" play.http.secret.key="QCY?tAnfk?aZ?iwrNwnxIlR6CTf:123123Latabg@5241AB`R5W:1uDFN];Ik@n"
play.server.http.port = 80 play.server.http.port = 1337
maps = ["2p_battle_marshes", deciders{
classic{
maps = [["2p_battle_marshes"],
["2p_fallen_city_[Rem]"],
["2p_fata_morgana_[Rem]"],
["2p_quests_triumph"],
["2p_meeting_of_minds"],
["2p_shrine_of_excellion_[Rem]"],
["2p_titan_fall_[Rem]"],
["2p_tranquilitys_end_[Rem]"],
["2p_fraziersdemise"],
["2p_outer_reaches"],
["2p_blood_river_[Rem]"],
["2p_deadly_fun_archeology"],
["2p_bloody_hell_[Ed]"]]
name = "Классический десайдер"
rules = """"""
}
freakcup{
maps = [["2p_battle_marshes"],
["2p_quests_triumph"],
["2p_meeting_of_minds"],
["2p_shrine_of_excellion_[Rem]"],
["2p_titan_fall_[Rem]"],
["2p_tranquilitys_end_[Rem]"],
["2p_outer_reaches"],
["2p_blood_river_[Rem]"],
["2p_emerald_river"]]
raceCount = 1
name = "Десайдер freakcup"
rules = """"""
}
dredicmappool{
maps = [["2p_battle_marshes"],
["2p_fallen_city_[Rem]"],
["2p_quests_triumph"],
["2p_meeting_of_minds"],
["2p_shrine_of_excellion_[Rem]"],
["2p_titan_fall_[Rem]"],
["2p_tranquilitys_end_[Rem]"],
["2p_outer_reaches"],
["2p_emerald_river"],
["2p_sugaroasis"],
["2p_blood_river_[Rem]"],
["2p_deadly_fun_archeology"],
["2p_bloody_hell_[Ed]"]]
raceCount = 2
name = "Десайдер DreDick"
rules = """"""
}
flazzomappool{
maps = [["2p_battle_marshes"],
["2p_fallen_city_[Rem]"],
["2p_fata_morgana_[Rem]"],
["2p_quests_triumph"],
["2p_meeting_of_minds"],
["2p_shrine_of_excellion_[Rem]"],
["2p_titan_fall_[Rem]"],
["2p_tranquilitys_end_[Rem]"],
["2p_fraziersdemise"],
["2p_outer_reaches"],
["2p_emerald_river"],
["2p_sugaroasis"],
["2p_blood_river_[Rem]"],
["2p_deadly_fun_archeology"],
["2p_bloody_hell_[Ed]"]]
raceCount = 2
name = "Десайдер flazzo"
rules = """"""
}
salats{
maps = [["винегрет"],
["грибная поляна"],
["крабовый"],
["мимоза"],
["оливье"],
["селедка под шубой"],
["цезарь"],
["из пекинской капусты"],
["гнездо глухаря"],
["летний"]]
name = "Выбираем салат на новый год"
rules = """"""
}
turtleshell{
maps = [["2p_battle_marshes"],
["2p_fallen_city_[Rem]"],
["2p_fata_morgana_[Rem]"],
["2p_quests_triumph"],
["2p_meeting_of_minds"],
["2p_shrine_of_excellion_[Rem]"],
["2p_titan_fall_[Rem]"],
["2p_tranquilitys_end_[Rem]"],
["2p_fraziersdemise"],
["2p_outer_reaches"],
["2p_blood_river_[Rem]"],
["2p_deadly_fun_archeology"],
["2p_bloody_hell_[Ed]"],
["2p_sugaroasis"],
["2p_vortex_plateau"]]
name = "Turtle Shell tournament 3x3"
rules = """"""
}
turtleshell3x3{
maps = [["6p_fury_island"],
["6p_irridene"],
["6p_shakun_coast"],
["6p_testing_grounds"],
["6p_gear"],
["6p_platform"]]
raceCount = 3
name = "Turtle Shell tournament 3x3"
rules = """Turtle Shell tournament 3x3"""
}
winter_adventures{
maps = [["6p_ruined_greatway"],
["6p_fury_island"],
["6p_irridene"],
["6p_testing_grounds"],
["6p_paynes_retribution"],
["6p_shakun_coast"],
["6p_paynes_retribution"],
["6p_gear"]]
name = "Winter adventures"
raceCount = 3
rules = """Капитаны команд по очереди вычеркивают карты из маппула, пока их не останется 3-ри. На этих картах проходят все матчи встречи. Затем капитаны вычеркивают выбранные карты, пока не останется только одна, на которой и играется первый матч встречи. Выбор оставшихся карт встречи определяется правом лузерпика - проигравший определяет карту из оставшихся. """
}
noloody_mappool{
maps = [["2p_battle_marshes"],
["2p_fallen_city"],
["2p_outer_reaches"],
["2p_quests_triumph"],
["2p_shrine_of_excellion_[Rem]"],
["2p_titan_fall_[Rem]"],
["2p_tranquilitys_end_[Rem]"],
["2p_fraziersdemise"],
["2p_bloody_hell_[Ed]"],
["2p_blood_river_[Rem]"],
["2p_deadly_fun_archeology"]]
name = "Noloody map pool"
raceCount = 2
rules = """См. в дискорде"""
}
dvijcup{
maps = ["2p_battle_marshes",
"2p_fallen_city_[Rem]", "2p_fallen_city_[Rem]",
"2p_fata_morgana_[Rem]", "2p_fata_morgana_[Rem]",
"2p_meeting_of_minds", "2p_quests_triumph",
"2p_shrine_of_excellion_[Rem]",
"2p_titan_fall_[Rem]",
"2p_tranquilitys_end_[Rem]",
"2p_fraziersdemise",
"2p_bloody_hell",
"2p_blood_river_[Rem]",
"2p_deadly_fun_archeology"]
name = "Dvij cup"
rules = """Выбор карт в БО3 и БО5 осуществляется без лузерпиков, с тремя и пятью десайдерами соответственно.
Игроки вычеркивают по очереди карты из маппула, пока их не останется 3 (для БО3) или 5 (для БО5). На этих картах и проходят все матчи встречи.
Затем игроки вычеркивают выбранные карты, пока не останется только одна, на которой и играется первый матч встречи.
Тот, кто первым начал вычеркивать из всего маппула, уступает оппоненту право вычеркивания первой карты из оставшихся карт-десайдеров.
Во втором и последующем матчах проигравший выбирает карту из числа выбранных 3 (5) десайдеров."""
}
meatgrinder{
maps = [["2p_battle_marshes"],
["2p_fallen_city"],
["2p_deadly_fun_archeology"],
["2p_bloody_hell_[Ed]"],
["2p_outer_reaches"],
["2p_quests_triumph"],
["2p_shrine_of_excellion_[Rem]"],
["2p_titan_fall_[Rem]"],
["2p_tranquilitys_end_[Rem]"],
["2p_sugaroasis"],
["2p_blood_river_[Rem]"]]
name = "Meat grinder"
raceCount = 1
rules = """Десайдеры выбираются методом вычеркивания до 3(5) карт, далее из оставшихся карт методом вычеркивания определяется первая карта. Игрок, начавший вычеркивание из основного пула уступает право начать вычеркивание из оставшихся карт."""
}
compatchcup{
maps = [["2p_fallen_city"],
["2p_deadly_fun_archeology"], ["2p_quests_triumph"], ["2p_battle_marshes"],
["2p_titan_fall_[Rem]"],
["2p_tranquilitys_end_[Rem]"],
["2p_blood_river_[Rem]"]]
name = "Community patch cup"
raceCount = 1
rules = """см. в дискорде турнира"""}
medskillcup{
maps = ["2p_battle_marshes",
"2p_fallen_city_[Rem]",
"2p_fata_morgana_[Rem]",
"2p_bloody_hell_[Ed]",
"2p_outer_reaches", "2p_outer_reaches",
"2p_quests_triumph", "2p_quests_triumph",
"2p_shrine_of_excellion_[Rem]", "2p_shrine_of_excellion_[Rem]",
@ -20,4 +209,564 @@ maps = ["2p_battle_marshes",
"2p_emerald_river", "2p_emerald_river",
"2p_deadly_fun_archeology", "2p_deadly_fun_archeology",
"2p_blood_river_[Rem]"] "2p_blood_river_[Rem]"]
name = "Med skill cup 4"
rules = """Вы и ваш оппонент перед началом матчей вычеркиваете из маппула карты, пока не останется 3. На них и играете. Для БО1 - та же самая система выбора карт. В суперфинале игрок из верхней сетки начинает с +1 очком, а игрок из нижней сетки - с лузерпиком. Далее оба игрока вычеркивают по 3 карты, и могут лузерпикать из оставшихся 6 в последующих матчах."""
}
burgercup{
maps = [["2p_battle_marshes"],
["2p_fallen_city_[Rem]"],
["2p_fata_morgana_[Rem]"],
["2p_quests_triumph"],
["2p_shrine_of_excellion_[Rem]"],
["2p_titan_fall_[Rem]"],
["2p_tranquilitys_end_[Rem]"],
["2p_fraziersdemise"],
["2p_bloody_hell_[Ed]"],
["2p_blood_river_[Rem]"],
["2p_deadly_fun_archeology"]]
name = "Burger cup"
raceCount = 2
rules = """Выбор карт в БО3 и БО5 осуществляется без лузерпиков, с тремя и пятью десайдерами соответственно. Игроки вычеркивают по очереди карты из маппула, пока их не останется 3 (для БО3) или 5 (для БО5). На этих картах и проходят все матчи встречи. Затем игроки вычеркивают выбранные карты, пока не останется только одна, на которой и играется первый матч встречи. Тот, кто первым начал вычеркивать из всего маппула, уступает оппоненту право вычеркивания первой карты из оставшихся карт-десайдеров. Во втором и последующем матчах проигравший выбирает карту из числа выбранных 3 (5) десайдеров."""
}
trainercup{
maps = [["2p_battle_marshes"],
["2p_fallen_city_[Rem]"],
["2p_fata_morgana_[Rem]"],
["2p_blood_river_[Rem]"],
["2p_quests_triumph"],
["2p_shrine_of_excellion_[Rem]"],
["2p_titan_fall_[Rem]"],
["2p_tranquilitys_end_[Rem]"],
["2p_fraziersdemise"],
["2p_bloody_hell_[Ed]"],
["2p_deadly_fun_archeology"]]
name = "Trainer cup"
raceCount = 2
rules = """Выбор карт в БО3 и БО5 осуществляется без лузерпиков, с тремя и пятью десайдерами соответственно. Игроки вычеркивают по очереди карты из маппула, пока их не останется 3 (для БО3) или 5 (для БО5). На этих картах и проходят все матчи встречи. Затем игроки вычеркивают выбранные карты, пока не останется только одна, на которой и играется первый матч встречи. Тот, кто первым начал вычеркивать из всего маппула, уступает оппоненту право вычеркивания первой карты из оставшихся карт-десайдеров. Во втором и последующем матчах проигравший выбирает карту из числа выбранных 3 (5) десайдеров."""
}
customcup{
maps = [["2p_battle_marshes"],
["2p_blood_river_[Rem]"],
["2p_fallen_city_[Rem]"],
["2p_outer_reaches"],
["2p_quests_triumph"],
["2p_shrine_of_excellion_[Rem]"],
["2p_sugaroasis"],
["2p_titan_fall_[Rem]"],
["2p_tranquilitys_end_[Rem]"]]
name = "Custom Cup"
raceCount = 2
rules = """Выбор карт в БО3 и БО5 осуществляется без лузерпиков, с тремя и пятью десайдерами соответственно. Игроки вычеркивают по очереди карты из маппула, пока их не останется 3 (для БО3) или 5 (для БО5). На этих картах и проходят все матчи встречи. Затем игроки вычеркивают выбранные карты, пока не останется только одна, на которой и играется первый матч встречи. Тот, кто первым начал вычеркивать из всего маппула, уступает оппоненту право вычеркивания первой карты из оставшихся карт-десайдеров. Во втором и последующем матчах проигравший выбирает карту из числа выбранных 3 (5) десайдеров."""
}
everyonecup{
maps = [["2p_battle_marshes"],
["2p_fallen_city_[Rem]"],
["2p_moonbase"],
["2p_fata_morgana_[Rem]"],
["2p_meeting_of_minds"],
["2p_emperors_valley"],
["2p_titan_fall_[Rem]"],
["2p_tranquilitys_end_[Rem]"],
["2p_fraziersdemise"],
["2p_bloody_hell_[Ed]"],
["2p_blood_river_[Rem]"],
["2p_deadly_fun_archeology"],
["2p_sugaroasis"]]
name = "Everyone cup"
raceCount = 1
rules = """Все матчи БО3. Игроки вычеркивают по очереди карты из маппула, пока их не останется 3. На этих картах и проходят все матчи встречи."""
}
bigbosswinter{
maps = ["2p_battle_marshes",
"2p_fallen_city_[Rem]",
"2p_fata_morgana_[Rem]",
"2p_meeting_of_minds",
"2p_outer_reaches",
"2p_quests_triumph",
"2p_shrine_of_excellion_[Rem]",
"2p_titan_fall_[Rem]",
"2p_tranquilitys_end_[Rem]",
"2p_bloody_hell_[Ed]",
"2p_emerald_river",
"2p_deadly_fun_archeology",
"2p_winter_confrontation"]
name = "Big boss of winter"
rules = """Выбор карт в БО3 и БО5 осуществляется без лузерпиков, с тремя и пятью десайдерами соответственно. Игроки вычеркивают по очереди карты из маппула, пока их не останется 3 (для БО3) или 5 (для БО5). На этих картах и проходят все матчи встречи. Затем игроки вычеркивают выбранные карты, пока не останется только одна, на которой и играется первый матч встречи. Тот, кто первым начал вычеркивать из всего маппула, уступает оппоненту право вычеркивания первой карты из оставшихся карт-десайдеров. Во втором и последующем матчах проигравший выбирает карту из числа выбранных 3 (5) десайдеров.В суперфинале турнира оба игрока вычеркивают по две карты. После этого из оставшихся 9 карт можно брать любые, выбирает их проигравший в предыдущем матче. Первый лузер-пик - за игроком из нижней сетки, который начинает с -1 очком. """
}
frenchtournament{
maps = [["2p_battle_marshes"],
["2p_fallen_city_[Rem]"],
["2p_fata_morgana_[Rem]"],
["2p_quests_triumph"],
["2p_shrine_of_excellion_[Rem]"],
["2p_titan_fall_[Rem]"],
["2p_tranquilitys_end_[Rem]"],
["2p_fraziersdemise"],
["2p_outer_reaches"],
["2p_blood_river_[Rem]"],
["2p_sugaroasis"],
["2p_meeting_of_minds"],
["2p_emerald_river"],
["2p_vortex_plateau"],
["2p_deadly_fun_archeology"]]
name = "French tournament"
raceCount = 1
rules = """ """
}
arena-bad-2{
maps = [["2p_outer_reaches"],
["2p_battle_marshes"],
["2p_fallen_city_[Rem]"],
["2p_fata_morgana_[Rem]"],
["2p_quests_triumph"],
["2p_shrine_of_excellion_[Rem]"],
["2p_titan_fall_[Rem]"],
["2p_tranquilitys_end_[Rem]"],
["2p_sugaroasis"],
["2p_bloody_hell_[Ed]"],
["2p_blood_river_[Rem]"],
["2p_deadly_fun_archeology"],
["2p_meeting_of_minds"]]
name = "Arena bad 2"
raceCount = 1
existAdditionalRaces = true
rules = """1 раса, но можно взять вторую при мирроре"""
}
mallusc-cup{
maps = [["2p_outer_reaches"],
["2p_battle_marshes"],
["2p_fallen_city_[Rem]"],
["2p_fata_morgana_[Rem]"],
["2p_quests_triumph"],
["2p_shrine_of_excellion_[Rem]"],
["2p_titan_fall_[Rem]"],
["2p_tranquilitys_end_[Rem]"],
["2p_sugaroasis"],
["2p_bloody_hell_[Ed]"],
["2p_blood_river_[Rem]"],
["2p_deadly_fun_archeology"],
["2p_meeting_of_minds"],
["2p_emerald_river"],
["2p_fraziersdemise"]]
name = "Маллюск кап"
raceCount = 1
existAdditionalRaces = true
rules = """"""
}
showmatch{
maps = [["2p_outer_reaches"],
["2p_battle_marshes"],
["2p_vortex_plateau"],
["2p_velvet_duress"],
["2p_tranquilitys_end_[Rem]"],
["2p_fallen_city_[Rem]"],
["2p_fata_morgana_[Rem]"],
["2p_quests_triumph"],
["2p_shrine_of_excellion_[Rem]"],
["2p_titan_fall_[Rem]"],
["2p_tranquilitys_end_[Rem]"],
["2p_sugaroasis"],
["2p_bloody_hell_[Ed]"],
["2p_blood_river_[Rem]"],
["2p_deadly_fun_archeology"],
["2p_meeting_of_minds"],
["2p_emerald_river"],
["2p_fraziersdemise"]]
name = "Маллюск кап"
raceCount = 1
existAdditionalRaces = true
rules = """"""
}
mallusc-cup{
maps = [["2p_outer_reaches"],
["2p_battle_marshes"],
["2p_fallen_city_[Rem]"],
["2p_fata_morgana_[Rem]"],
["2p_quests_triumph"],
["2p_shrine_of_excellion_[Rem]"],
["2p_titan_fall_[Rem]"],
["2p_tranquilitys_end_[Rem]"],
["2p_sugaroasis"],
["2p_bloody_hell_[Ed]"],
["2p_blood_river_[Rem]"],
["2p_deadly_fun_archeology"],
["2p_meeting_of_minds"],
["2p_emerald_river"],
["2p_fraziersdemise"]]
name = "Маллюск кап"
raceCount = 1
existAdditionalRaces = true
rules = """"""
}
horusheresycup2{
maps = ["2p_battle_marshes",
"2p_fallen_city_[Rem]",
"2p_fata_morgana_[Rem]",
"2p_quests_triumph",
"2p_shrine_of_excellion_[Rem]",
"2p_titan_fall_[Rem]",
"2p_tranquilitys_end_[Rem]",
"2p_outer_reaches",
"2p_blood_river_[Rem]",
"2p_sugaroasis",
"2p_meeting_of_minds"]
name = "Horus heresy cup 2"
rules = """ Первая карта десайдер выбирается методом вычеркивания, остальные лузер пик. По обоюдному согласию игроков разрешен повтор карт. Сетка турнира Double Elimination.
Сетка виннеров:
Каждая игра до 2х победы (БО3),
В суперфинале игра до 3-х побед (БО5).
Сетка лузеров:
Каждая игра до 1й победы (БО1),
Финал сетки лузеров (БО3).
Игрок, прошедший в суперфинал из верхней сетки, имеет +1 очко. Игрок из нижней сетки начинает с лузерпиком. """
}
shouldercup{
description = true
existAdditionalRaces = true
raceCount = 3
maps = [["6p_paynes_retribution", "Количество точек: 28/2/2\nСпорных точек: 10/0/2\nТермогенераторы: 2"],
["6p_fury_island", "Количество точек: 26/6/1\nСпорных точек: 6 + 1 крит\nТермогенераторы: Отсутствуют"],
["6p_irridene", "Количество точек: 21/6/3\nСпорных точек: 6/0/3\nТермогенераторы: 6"],
["6p_shakun_coast", "Количество точек: 24/4/1\nСпорных точек: 6/2/1\nТермогенераторы: 2"],
"2p_tranquilitys_end_[Rem]",
"2p_outer_reaches",
"2p_blood_river_[Rem]",
"2p_sugaroasis",
"2p_meeting_of_minds"]
name = "Horus heresy cup 2"
rules = """ Первая карта десайдер выбирается методом вычеркивания, остальные лузер пик. По обоюдному согласию игроков разрешен повтор карт. Сетка турнира Double Elimination.
Сетка виннеров:
Каждая игра до 2х победы (БО3),
В суперфинале игра до 3-х побед (БО5).
Сетка лузеров:
Каждая игра до 1й победы (БО1),
Финал сетки лузеров (БО3).
Игрок, прошедший в суперфинал из верхней сетки, имеет +1 очко. Игрок из нижней сетки начинает с лузерпиком. """
}
shouldercup{
description = true
existAdditionalRaces = true
raceCount = 3
maps = [["6p_paynes_retribution", "Количество точек: 28/2/2\nСпорных точек: 10/0/2\nТермогенераторы: 2"],
["6p_fury_island", "Количество точек: 26/6/1\nСпорных точек: 6 + 1 крит\nТермогенераторы: Отсутствуют"],
["6p_irridene", "Количество точек: 21/6/3\nСпорных точек: 6/0/3\nТермогенераторы: 6"],
["6p_shakun_coast", "Количество точек: 24/4/1\nСпорных точек: 6/2/1\nТермогенераторы: 2"],
"2p_tranquilitys_end_[Rem]",
"2p_outer_reaches",
"2p_blood_river_[Rem]",
"2p_sugaroasis",
"2p_meeting_of_minds"]
name = "Horus heresy cup 2"
rules = """ Первая карта десайдер выбирается методом вычеркивания, остальные лузер пик. По обоюдному согласию игроков разрешен повтор карт. Сетка турнира Double Elimination.
Сетка виннеров:
Каждая игра до 2х победы (БО3),
В суперфинале игра до 3-х побед (БО5).
Сетка лузеров:
Каждая игра до 1й победы (БО1),
Финал сетки лузеров (БО3).
Игрок, прошедший в суперфинал из верхней сетки, имеет +1 очко. Игрок из нижней сетки начинает с лузерпиком. """
}
shouldercup{
description = true
existAdditionalRaces = true
raceCount = 3
maps = [["6p_paynes_retribution", "Количество точек: 28/2/2\nСпорных точек: 10/0/2\nТермогенераторы: 2"],
["6p_fury_island", "Количество точек: 26/6/1\nСпорных точек: 6 + 1 крит\nТермогенераторы: Отсутствуют"],
["6p_irridene", "Количество точек: 21/6/3\nСпорных точек: 6/0/3\nТермогенераторы: 6"],
["6p_shakun_coast", "Количество точек: 24/4/1\nСпорных точек: 6/2/1\nТермогенераторы: 2"],
["6p_platform", "Количество точек: 26/2/3\nТермогенераторы: 2"],
["6p_temple_cyrene", "Количество точек: 25/4/5\nСпорных точек: 8/0/3\nТермогенераторы: 2"],
["6p_testing_grounds", "Количество точек: 32/6/5\nСпорных точек: 10/2/5\nТермогенераторы: 6"],
["6p_ruined_greatway", "Количество точек: 22/2/1\nСпорных точек: 2/2/1\nТермогенераторы: 2"],
["6p_gear", "Количество точек: 24/2/3\nСпорных точек: 2 крита, но ввиду контактности карты сражение может идти на большинстве внешних точек\nТермогенераторы: 2"]]
name = "Shoulder to shoulder cup"
rules = """Классическая Double elimination с сеткой лузеров. В сетке виннеров - БО3, в сетке лузеров - БО1, в суперфинале - БО5. Игрок, прошедший в суперфинал из верхней сетки, имеет +1 очко. Игрок из нижней сетки начинает с лузерпиком. """
}
shouldercup4x4{
description = true
raceCount = 4
maps = [["8p_burial_grounds", "Количество стратегических/реликтовых/критических точек: 32/2/4\nСпорных точек: 4/2/2\nТермогенераторы: 3"],
["8p_daturias_pits", "Количество стратегических/реликтовых/критических точек: 32/4/5\nСпорных точек: 2/2/1\nТермогенераторы: 0"],
["8p_forbidden_jungle", "Количество стратегических/реликтовых/критических точек: 36/5/0\nСпорных точек: 12/5/0\nТермогенераторы: 4"],
["8p_demes_northlands", "Количество стратегических/реликтовых/критических точек: 32/4/3\nСпорных точек: 3/2/2\nТермогенераторы: 2"],
["8p_glacier", "Количество стратегических/реликтовых/критических точек: 24/4/5\nСпорных точек: 0/0/4\nТермогенераторы: 4"],
["8p_jalaganda_lowlands", "Количество стратегических/реликтовых/критических точек: 26/5/3\nСпорных точек: 2/1/3\nТермогенераторы: 0"],
["8p_monse", "Количество стратегических/реликтовых/критических точек: 26/4/4\nСпорных точек: 2/2/4\nТермогенераторы: 1"],
["8p_thurabis_plateau", "Количество стратегических/реликтовых/критических точек: 32/2/0\nСпорных точек: 12/2/0\nТермогенераторы: 0"],
["8p_verdant_isles_ed", "Количество стратегических/реликтовых/критических точек: 32/4/3\nСпорных точек: 0/4/3\nТермогенераторы: 0"]]
name = "Shoulder to shoulder cup"
rules = """Черкается по правилу лузерпиков: сначала оставляется одна карта, далее команда выбирает, на какой карте будет играть. """
}
springcup{
description = true
maps = [["4p_biffys_peril"],
["4p_cold_war"],
["4p_colosseum_of_deadman"],
["4p_gorhael_crater"],
["4p_imperial_area"],
["4p_saints_square"],
["4p_tartarus_center"],
["4p_testcake"],
["4p_skerries"],
["4p_tiboraxx"],
["4p_torrents"]]
name = "Spring cup 2x2"
rules = """Десайдеры выбирается методом вычеркивания до 3 карт, далее из оставшихся карт методом вычеркивания определяется первая карта. Игрок, начавший вычеркивание из основного пула уступает право начать вычеркивание из оставшихся карт. Исключение суперфинал: Команда прошедшая в суперфинал с верхней сетки имеет преимущество в выборе очередности карт из оставшихся 3 после вычеркивания."""
}
shouldercup2x2{
description = true
raceCount = 2
existAdditionalRaces = true
maps = [["4p_biffys_peril"],
["4p_cold_war"],
["4p_colosseum_of_deadman"],
["4p_chaos_platenau"],
["4p_imperial_area"],
["4p_saints_square"],
["4p_tartarus_center"],
["4p_skerries"],
["4p_gurmuns_pass"]]
name = "Shoulder cup 2x2"
rules = """Расы в командах (как основные, так и дополнительные) должны соблюдать правило - в команде должен быть только один представитель следующих рас: тау, эльдар. В случае поражения на одной из карт, противники могут лузерпикать свои расы на дополнительные (причем допрасы берут все игроки команды и могут мешать), но при этом заранее уведомляют будут ли играть данной связкой рас на следующей карте (в случае если лузерпик произошел на второй карте в рамках Best of 3). Расы свободно выбираются каждое БО 1/3/5 на протяжении турнира. Позиции можно выбирать по договоренности с командами."""
}
sweatybarrakcup{
description = true
maps =[["2p_chaos_gate"],
["2p_colosseum_suicide"],
["2p_emerald_river"],
["2p_faceoff"],
["2p_moonbase_[Ed]"],
["2p_outer_reaches"],
["2p_fear"],
["2p_fraziersdemise"],
["2p_sugaroasis"],
["2p_terror_psyclaw"],
["2p_titan_fall_[Rem]"],
["2p_tranquilitys_end_[Rem]"],
["2p_velvet_duress"],
["2p_winter_confortation"],
["2p_deadly_fun_archeology"]]
name = "Sweaty barrack cup"
rules = """Перед началом матча, игроки поочередно вычеркивают по одной карте, пока не выберут десайдер. Если иного не сказано в секции "Расы" (см. ограничения для рас). Во второй и всех последующих играх - побежденный выбирает карту.
Чтобы не было повторений карт, игроки могу выбрать всего 1 раз карту из списка. """
}
tpmodcup{
description = true
raceCount = 1
maps = [["2p_battle_marshes"],
["2p_fallen_city_[Rem]"],
["2p_quests_triumph"],
["2p_fear"],
["2p_shrine_of_excellion_[Rem]"],
["2p_titan_fall_[Rem]"],
["2p_tranquilitys_end_[Rem]"],
["2p_blood_river_[Rem]"],
["2p_sugaroasis"],
["2p_faceoff"],
["2p_deadly_fun_archeology"],
["2p_terror_psyclaw"],
["2p_meeting_of_minds_pro", "Модифицированная версия Meeting of minds v 1.0: 6 точек на игрока, одна из которой находится за реликтом."],
["2p_edemus_gamble"],
["[tp_mod]jungle_morning"],
["2p_fraziersdemise"],
["[tp_mod]light_brigade"],
["2p_outer_reaches"],
["2p_vortex_plateau"]]
name = "Турнир по ТП моду"
rules = """Первая карта десайдер выбирается методом вычеркивания, остальные лузер пик. """
}
bestfriendscup{
maps = ["4p_gorhael_crater",
"4p_gurmuns_pass",
"4p_saints_square",
"4p_skerries",
"4p_panrea_lowlands",
"4p_doom_spiral",
"4p_tiboraxx"]
name = "Best friends cup cup"
rules = """ Обе команды вычеркивают по очереди неудобные для себя карты. Каждая команда вычеркивает в общей сложности 3. На оставшейся, седьмой карте, проходит Ваш первый матч - она называется десайдером. Во всех последующих играх карту выбирает команда, проигравшая в последнем матче. Это называется лузерпиком - выбором проигравших. В суперфинале турнира сражается команда из сетки виннеров против финалистов из сетки лузеров, в формате БО5 (до трёх побед). """
}
noweaknesscup{
maps = [["2p_battle_marshes"],
["2p_fallen_city_[Rem]"],
["2p_fata_morgana_[Rem]"],
["2p_quests_triumph"],
["2p_shrine_of_excellion_[Rem]"],
["2p_titan_fall_[Rem]"],
["2p_tranquilitys_end_[Rem]"],
["2p_fraziersdemise"],
["2p_bloody_hell_[Ed]"],
["2p_blood_river_[Rem]"],
["2p_deadly_fun_archeology"]]
name = "No Weakness cup"
rules = """Выбор карт в БО3 и БО5 осуществляется без лузерпиков, с тремя и пятью десайдерами соответственно. Игроки вычеркивают по очереди карты из маппула, пока их не останется 3 (для БО3) или 5 (для БО5). На этих картах и проходят все матчи встречи. Затем игроки вычеркивают выбранные карты, пока не останется только одна, на которой и играется первый матч встречи. Тот, кто первым начал вычеркивать из всего маппула, уступает оппоненту право вычеркивания первой карты из оставшихся карт-десайдеров. Во втором и последующем матчах проигравший выбирает карту из числа выбранных 3 (5) десайдеров. """
}
freneticmappool{
raceCount = 1
description = true
maps =[["2p_titan_fall_[Rem]"],
["2p_fallen_city_[Rem]"],
["2p_quests_triumph"],
["2p_shrine_of_excellion_[Rem]"],
["2p_tranquilitys_end_[Rem]"],
["2p_blood_river_[Rem]"],
["2p_deadly_fun_archeology"],
["2p_emerald_river"],
["2p_chaos_gate"],
["2p_fraziersdemise"],
["2p_meeting_of_minds"]],
name = "Frenetic map pool"
rules = """ """
}
freneticmappool2{
raceCount = 2
description = true
maps =[["2p_battle_marshes"],
["2p_fallen_city_[Rem]"],
["2p_fata_morgana_[Rem]"],
["2p_emerald_river"],
["2p_quests_triumph"],
["2p_shrine_of_excellion_[Rem]"],
["2p_titan_fall_[Rem]"],
["2p_tranquilitys_end_[Rem]"],
["2p_fraziersdemise"],
["2p_bloody_hell_[Ed]"],
["2p_blood_river_[Rem]"],
["2p_deadly_fun_archeology"],
["2p_meeting_of_minds"]]
name = "Frenetic map pool"
rules = """ """
}
freneticmappool2x2{
raceCount = 2
description = true
maps =[["4p_biffys_peril"],
["4p_chaos_platenau"],
["4p_cold_war"],
["4p_gorhael_crater"],
["4p_gurmuns_pass"],
["4p_imperial_area"],
["4p_marconia"],
["4p_panrea_lowlands"],
["4p_saints_square"],
["4p_snowblind"],
["4p_tartarus_center"],
["4p_skerries"],
["4p_doom_spiral"]]
name = "Frenetic map pool"
rules = """ """
}
deadgamecup{
raceCount = 2
description = true
maps =[["2p_battle_marshes"],
["2p_fallen_city", "Обратите внимание, не ремастер"],
["2p_quests_triumph"],
["2p_shrine_of_excellion_[Rem]"],
["2p_titan_fall_[Rem]"],
["2p_tranquilitys_end_[Rem]"],
["2p_blood_river_[Rem]"],
["2p_sugaroasis"],
["2p_outer_reaches"]]
name = "Dead game cup"
rules = """ """
}
casinocup {
raceCount = 2
maps = [["2p_battle_marshes"],
["2p_fallen_city_[Rem]"],
["2p_fata_morgana_[Rem]"],
["2p_quests_triumph"],
["2p_shrine_of_excellion_[Rem]"],
["2p_titan_fall_[Rem]"],
["2p_tranquilitys_end_[Rem]"],
["2p_fraziersdemise"],
["2p_bloody_hell_[Ed]"],
["2p_blood_river_[Rem]"],
["2p_deadly_fun_archeology"],
["2p_outer_reaches"],
["2p_meeting_of_minds"]]
name = "Casino cup"
rules = """Выбор карт без лузерпиков, с тремя и пятью десайдерами соответственно. Игроки вычеркивают по очереди карты из маппула, пока их не останется 3 (для БО3) или 5 (для БО5). На этих картах и проходят все матчи встречи. Затем игроки вычеркивают выбранные карты, пока не останется только одна, на которой и играется первый матч встречи. Тот, кто первым начал вычеркивать из всего маппула, уступает оппоненту право вычеркивания первой карты из оставшихся карт-десайдеров. Во втором и последующем матчах проигравший выбирает карту из числа выбранных 3 (5) десайдеров. """
}
badcup {
raceCount = 2
maps = [["2p_outer_reaches"],
["2p_battle_marshes"],
["2p_fallen_city_[Rem]"],
["2p_quests_triumph"],
["2p_shrine_of_excellion_[Rem]"],
["2p_titan_fall_[Rem]"],
["2p_tranquilitys_end_[Rem]"],
["2p_sugaroasis"],
["2p_blood_river_[Rem]"]
["2p_deadly_fun_archeology"],
["2p_meeting_of_minds"],
["2p_emerald_river"],
["2p_bloody_hell_[Ed]"],
]
name = "BAD cup"
rules = """Double elimination. БО3 (БО5 суперфинал). БО1 лузерсетка (БО3 финал лузер сетки). 2 Расы на турнир, без банов. Вторая раса берётся в случае лузерпика(и играется до окончания бо) или по договоренности обеих сторон в случае миррора."""
}
uacup{
raceCount = 0
maps = [["2p_belltower"],
["2p_titan_fall_[Rem]"],
["2p_blood_river_[Rem]"],
["2p_bloody_hell_[Ed]"],
["2p_emerald_river"],
["2p_shrine_of_excellion_[Rem]"],
["2p_deadly_fun_archeology"],
["2p_fallen_city_[Rem]"],
["2p_fata_morgana_[Rem]"],
["2p_tranquilitys_end_[Rem]"],
["2p_outer_reaches"],
]
name = "UA cup"
rules = """"""
}
true_arena_bad2{
raceCount = 2
maps = [["2p_outer_reaches"],
["2p_battle_marshes"],
["2p_fallen_city_[Rem]"],
["2p_quests_triumph"],
["2p_shrine_of_excellion_[Rem]"],
["2p_titan_fall_[Rem]"],
["2p_tranquilitys_end_[Rem]"],
["2p_sugaroasis"],
["2p_blood_river_[Rem]"]
["2p_deadly_fun_archeology"],
["2p_deadly_fun_archeology"],
["2p_meeting_of_minds"],
["2p_fata_morgana_[Rem]"],
["2p_bloody_hell_[Ed]"],
]
name = "BAD cup"
rules = """"""
}
ppcz {
raceCount = 2
maps = [["2p_fata_morgana_[Rem]"],
["2p_titan_fall_[Rem]"],
["2p_tranquilitys_end_[Rem]"],
["2p_shrine_of_excellion_[Rem]"],
["2p_quests_triumph"],
["2p_fallen_city_[Rem]"],
["2p_outer_reaches"],
["2p_battle_marshes"],
["2p_sugaroasis"],
["2p_bloody_hell_[Ed]"],
["2p_deadly_fun_archeology"],
["2p_blood_river_[Rem]"],
["2p_meeting_of_minds"]]
name = "PPCZ"
rules = """
Игрок выбирает основную и дополнительную расу (по желанию). Первая игра всегда играется основной расой. Начиная со второй игры проигравший может взять дополнительную расу, при этом победивший не может поменять расу.
Эльдар не имеет права лузерпикать против SM, IG, CSM (вместо этого карту выбирает оппонент)
Некрон не имеет права лузерпикать против Tau (тоже, что и в примере выше)
Против некрона нельзя выбирать SOE лузерпиком (Tau все еще может выбирать любую карту против некрона"""
}
}

View File

@ -1,41 +1,48 @@
<!--
Copyright (C) Lightbend Inc. <https://www.lightbend.com>
-->
<!-- The default logback configuration that Play uses if no other configuration is provided -->
<configuration> <configuration>
<conversionRule conversionWord="coloredLevel" converterClass="play.api.libs.logback.ColoredLevel"/> <conversionRule conversionWord="coloredLevel" converterClass="play.api.libs.logback.ColoredLevel" />
<property name="LOG_FILE" value="logs\decider-log" />
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- daily rollover -->
<fileNamePattern>${LOG_FILE}.%d{yyyy-MM-dd}.gz</fileNamePattern>
<!-- keep 30 days' worth of history capped at 3GB total size -->
<maxHistory>30</maxHistory>
<totalSizeCap>3GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
</encoder>
</appender>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder> <encoder>
<pattern>%coloredLevel %logger{15} - [%marker] %message%n%xException{10}</pattern> <pattern>%coloredLevel %logger{15} - %message%n%xException{10}</pattern>
</encoder> </encoder>
</appender> </appender>
<logger name="play" level="INFO"/> <appender name="ASYNCSTDOUT" class="ch.qos.logback.classic.AsyncAppender">
<!-- increases the default queue size -->
<queueSize>512</queueSize>
<!-- don't discard messages -->
<discardingThreshold>0</discardingThreshold>
<!-- block when queue is full -->
<neverBlock>false</neverBlock>
<appender-ref ref="STDOUT" />
</appender>
<!-- actors logging --> <logger name="play" level="INFO" />
<logger name="akka" level="DEBUG"/>
<logger name="actors" level="DEBUG"/> <logger name="com.gargoylesoftware.htmlunit.javascript" level="OFF" />
<!-- controllers --> <appender name="FILE" class="ch.qos.logback.core.FileAppender">
<logger name="controllers" level="DEBUG"/> <file>${application.home:-.}/logs/application.log</file>
<encoder>
<pattern>%date [%level] from %logger in %thread - %message%n%xException</pattern>
</encoder>
</appender>
<appender name="ASYNCFILE" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="FILE" />
</appender>
<root level="INFO"> <root level="INFO">
<appender-ref ref="STDOUT"/> <appender-ref ref="ASYNCFILE" />
<appender-ref ref="FILE"/> <appender-ref ref="ASYNCSTDOUT" />
</root> </root>
<shutdownHook class="ch.qos.logback.core.hook.DelayingShutdownHook"/>
</configuration> </configuration>

View File

@ -2,15 +2,11 @@
# This file defines all application routes (Higher priority routes first) # This file defines all application routes (Higher priority routes first)
# ~~~~ # ~~~~
GET /meatgrinder controllers.HomeController.index GET /decider/:name controllers.HomeController.index(name)
GET /bigbossofsummer controllers.HomeController.index
GET /dvijcup controllers.HomeController.index
GET /ws controllers.HomeController.ws GET /ws controllers.HomeController.ws
GET /reloadconfig controllers.AdminController.reloadConfig GET /admin/users controllers.AdminController.viewAllUsers()
GET /lobbyadmin controllers.AdminController.viewAllLobbies
# Map static resources from the /public folder to the /assets URL path # Map static resources from the /public folder to the /assets URL path
GET /assets/*file controllers.Assets.at(path="/public", file) GET /assets/*file controllers.Assets.at(path="/public", file)