diff --git a/app/controllers/AdminController.scala b/app/controllers/AdminController.scala index 50f7cc2..bc00c41 100644 --- a/app/controllers/AdminController.scala +++ b/app/controllers/AdminController.scala @@ -25,6 +25,27 @@ class AdminController @Inject()( implicit val ec: ExecutionContext = cc.executionContext private val logger = org.slf4j.LoggerFactory.getLogger("controllers.AdminController") + // ---------- Ownership ---------- + + private def currentSteamId[A](request: Request[A]): String = + SecuredActions.steamIdOrUnknown(request) + + /** Редактировать и удалять десайдера может только его владелец. */ + private def owns[A](decider: repository.Decider, request: Request[A]): Boolean = + decider.ownerSteamId.contains(currentSteamId(request)) + + private def deciderJson(d: repository.Decider): JsObject = Json.obj( + "key" -> d.key, + "name" -> d.name, + "rules" -> d.rules, + "raceCount" -> d.raceCount, + "existAdditionalRaces" -> d.existAdditionalRaces, + "existTextFieldRaces" -> d.existTextFieldRaces, + "description" -> d.description, + "ownerSteamId" -> d.ownerSteamId, + "maps" -> d.maps.map(m => Json.obj("map" -> m.map, "description" -> m.description)) + ) + // ---------- Steam OpenID ---------- private val SteamLoginUrl = "https://steamcommunity.com/openid/login" @@ -89,7 +110,7 @@ class AdminController @Inject()( def index() = Secured { implicit request => implicit val messages: Messages = messagesApi.preferred(request) - Ok(views.html.admin(DeciderRepository.all)) + Ok(views.html.admin(DeciderRepository.all.filter(d => owns(d, request)))) } // ---------- API: list map images ---------- @@ -130,16 +151,7 @@ class AdminController @Inject()( // ---------- API: list all deciders ---------- def decidersList() = Secured { implicit request => - val list = DeciderRepository.all.map(d => Json.obj( - "key" -> d.key, - "name" -> d.name, - "rules" -> d.rules, - "raceCount" -> d.raceCount, - "existAdditionalRaces" -> d.existAdditionalRaces, - "existTextFieldRaces" -> d.existTextFieldRaces, - "description" -> d.description, - "maps" -> d.maps.map(m => Json.obj("map" -> m.map, "description" -> m.description)) - )) + val list = DeciderRepository.all.filter(d => owns(d, request)).map(deciderJson) Ok(Json.toJson(list)) } @@ -147,17 +159,10 @@ class AdminController @Inject()( def deciderGet(key: String) = Secured { implicit request => DeciderRepository.get(key) match { - case Some(d) => - Ok(Json.obj( - "key" -> d.key, - "name" -> d.name, - "rules" -> d.rules, - "raceCount" -> d.raceCount, - "existAdditionalRaces" -> d.existAdditionalRaces, - "existTextFieldRaces" -> d.existTextFieldRaces, - "description" -> d.description, - "maps" -> d.maps.map(m => Json.obj("map" -> m.map, "description" -> m.description)) - )) + case Some(d) if owns(d, request) => + Ok(deciderJson(d)) + case Some(_) => + Forbidden(Json.obj("error" -> "You don't own this decider")) case None => NotFound(Json.obj("error" -> s"Decider $key not found")) } @@ -167,49 +172,59 @@ class AdminController @Inject()( def deciderSave() = SecuredJson { implicit request => val json = request.body + val steamId = currentSteamId(request) (json \ "key").asOpt[String] match { case None => BadRequest(Json.obj("error" -> "Missing key")) case Some(key) => - Try { - val maps = (json \ "maps").as[List[JsValue]].map { m => - repository.DeciderMapConfig( - map = (m \ "map").as[String], - description = (m \ "description").asOpt[String] - ) - } - repository.Decider( - key = key, - name = (json \ "name").as[String], - rules = (json \ "rules").as[String], - raceCount = (json \ "raceCount").as[Int], - existAdditionalRaces = (json \ "existAdditionalRaces").as[Boolean], - existTextFieldRaces = (json \ "existTextFieldRaces").as[Boolean], - description = (json \ "description").as[Boolean], - maps = maps - ) - }.toEither match { - case Right(decider) => - val existed = DeciderRepository.get(decider.key).isDefined - DeciderRepository.save(decider) - DeciderRepository.reload() - if (existed) { - logger.info( - s"Admin ${SecuredActions.steamIdOrUnknown(request)} updated decider '${decider.key}' " + - s"(name='${decider.name}', maps=${decider.maps.size})" + val existing = DeciderRepository.get(key) + if (existing.exists(d => !owns(d, request))) { + Forbidden(Json.obj("error" -> "You don't own this decider")) + } else { + val name = (json \ "name").asOpt[String].map(_.trim).getOrElse("") + val duplicateName = existing.isEmpty && name.nonEmpty && + DeciderRepository.all.exists(_.name.trim.equalsIgnoreCase(name)) + if (duplicateName) { + Conflict(Json.obj("error" -> s"A decider named '$name' already exists")) + } else { + Try { + val maps = (json \ "maps").as[List[JsValue]].map { m => + repository.DeciderMapConfig( + map = (m \ "map").as[String], + description = (m \ "description").asOpt[String] + ) + } + repository.Decider( + key = key, + name = (json \ "name").as[String], + rules = (json \ "rules").as[String], + raceCount = (json \ "raceCount").as[Int], + existAdditionalRaces = (json \ "existAdditionalRaces").as[Boolean], + existTextFieldRaces = (json \ "existTextFieldRaces").as[Boolean], + description = (json \ "description").as[Boolean], + maps = maps, + // Владельца из запроса игнорируем: он либо уже назначен, либо + // им становится автор нового десайдера. + ownerSteamId = existing.flatMap(_.ownerSteamId).orElse(Some(steamId)) ) - } else { - logger.info( - s"Admin ${SecuredActions.steamIdOrUnknown(request)} created decider '${decider.key}' " + - s"(name='${decider.name}', maps=${decider.maps.size})" - ) - } - Ok(Json.obj("ok" -> true)) + }.toEither match { + case Right(decider) => + DeciderRepository.save(decider) + DeciderRepository.reload() + val action = if (existing.isDefined) "updated" else "created" + logger.info( + s"Admin $steamId $action decider '${decider.key}' " + + s"(name='${decider.name}', maps=${decider.maps.size}, " + + s"owner=${decider.ownerSteamId.getOrElse("-")})" + ) + Ok(Json.obj("ok" -> true)) - case Left(error) => - BadRequest(Json.obj("error" -> s"$error")) + case Left(error) => + BadRequest(Json.obj("error" -> s"$error")) + } + } } } } @@ -217,12 +232,17 @@ class AdminController @Inject()( // ---------- API: delete decider ---------- def deciderDelete(key: String) = SecuredApi { implicit request => - if (DeciderRepository.delete(key)) { - DeciderRepository.reload() - logger.info(s"Admin ${SecuredActions.steamIdOrUnknown(request)} deleted decider '$key'") - Ok(Json.obj("ok" -> true)) - } else { - NotFound(Json.obj("error" -> s"Decider $key not found")) + DeciderRepository.get(key) match { + case Some(d) if !owns(d, request) => + Forbidden(Json.obj("error" -> "You don't own this decider")) + case _ => + if (DeciderRepository.delete(key)) { + DeciderRepository.reload() + logger.info(s"Admin ${currentSteamId(request)} deleted decider '$key'") + Ok(Json.obj("ok" -> true)) + } else { + NotFound(Json.obj("error" -> s"Decider $key not found")) + } } } diff --git a/app/controllers/MapImageController.scala b/app/controllers/MapImageController.scala new file mode 100644 index 0000000..063f699 --- /dev/null +++ b/app/controllers/MapImageController.scala @@ -0,0 +1,38 @@ +package controllers + +import java.io.File +import javax.inject.{Inject, Singleton} + +import play.api.mvc.{AbstractController, ControllerComponents} +import repository.MapImageRepository + +import scala.concurrent.ExecutionContext + +/** + * Раздаёт картинки карт из каталога, заданного в конфиге (`maps.imagesDir`). + * + * Нужен потому, что каталог может находиться вне `public`/`app/assets` + * (например, на сервере), и стандартный Assets его не отдаст. + */ +@Singleton +class MapImageController @Inject()(cc: ControllerComponents) extends AbstractController(cc) { + + implicit val ec: ExecutionContext = cc.executionContext + + def image(file: String) = Action { implicit request => + MapImageRepository.findFile(file) match { + case Some(image) => + Ok.sendFile(image, inline = true) + .as(contentType(image)) + .withHeaders(CACHE_CONTROL -> "public, max-age=86400") + case None => NotFound + } + } + + private def contentType(file: File): String = { + val name = file.getName.toLowerCase + if (name.endsWith(".png")) "image/png" + else if (name.endsWith(".webp")) "image/webp" + else "image/jpeg" + } +} diff --git a/app/repository/DeciderRepository.scala b/app/repository/DeciderRepository.scala index edeabaf..6d96085 100644 --- a/app/repository/DeciderRepository.scala +++ b/app/repository/DeciderRepository.scala @@ -18,7 +18,9 @@ case class Decider( existAdditionalRaces: Boolean, existTextFieldRaces: Boolean, description: Boolean, - maps: List[DeciderMapConfig] + maps: List[DeciderMapConfig], + /** Steam ID владельца: только он может редактировать и удалять десайдера. */ + ownerSteamId: Option[String] = None ) /** @@ -32,6 +34,12 @@ case class Decider( */ object DeciderRepository { + /** + * Владелец по умолчанию. Записи без ownerSteamId (например, из старого + * application.conf) считаются принадлежащими этому Steam ID. + */ + val DefaultOwnerSteamId: String = "76561198137977374" + private def appConfig: Config = ConfigFactory.load() /** Файл, в который админка пишет десайдеров. */ @@ -88,7 +96,11 @@ object DeciderRepository { existAdditionalRaces = Try(d.getBoolean("existAdditionalRaces")).getOrElse(false), existTextFieldRaces = Try(d.getBoolean("existTextFieldRaces")).getOrElse(false), description = Try(d.getBoolean("description")).getOrElse(false), - maps = maps + maps = maps, + ownerSteamId = Try(d.getString("ownerSteamId")).toOption + .map(_.trim) + .filter(_.nonEmpty) + .orElse(Some(DefaultOwnerSteamId)) )) } } @@ -122,6 +134,9 @@ object DeciderRepository { deciders.foreach { d => sb.append(" ").append(quoteKey(d.key)).append(" {\n") sb.append(" name = ").append(quote(d.name)).append('\n') + sb.append(" ownerSteamId = ") + .append(quote(d.ownerSteamId.getOrElse(DefaultOwnerSteamId))) + .append('\n') sb.append(" raceCount = ").append(d.raceCount).append('\n') if (d.existAdditionalRaces) sb.append(" existAdditionalRaces = true\n") if (d.existTextFieldRaces) sb.append(" existTextFieldRaces = true\n") diff --git a/app/repository/MapImageRepository.scala b/app/repository/MapImageRepository.scala index 743991a..599afc7 100644 --- a/app/repository/MapImageRepository.scala +++ b/app/repository/MapImageRepository.scala @@ -1,19 +1,20 @@ package repository import java.io.File -import java.net.{JarURLConnection, URL} import java.nio.file.StandardCopyOption -import scala.collection.JavaConverters._ + +import com.typesafe.config.ConfigFactory + import scala.util.Try /** Изображение карты, доступное для выбора в админке. */ case class MapImage(name: String, file: String, players: Int) /** - * Список картинок карт из app/assets/images/maps. + * Список картинок карт из каталога, заданного в конфиге (`maps.imagesDir`). * - * Картинки читаются из исходников (при запуске через `sbt run`) либо из - * classpath (когда приложение собрано и assets упакованы в jar). + * Каталог читается при каждом обращении, поэтому загруженные через админку + * картинки сразу появляются в галерее без перезапуска приложения. */ object MapImageRepository { @@ -23,36 +24,62 @@ object MapImageRepository { private val IgnoredFiles = Set("default.jpg") private val IgnoredPattern = """^img(_\d+)?\.(jpg|jpeg|png|webp)$""".r - /** Каталоги с картинками: исходники и результат сборки assets. */ - private val MapDirectories = Seq( - "app/assets/images/maps", - "target/web/public/main/images/maps", - "public/images/maps" - ) + /** Ключ конфига с каталогом картинок карт. */ + private val ConfigKey = "maps.imagesDir" - private val ClasspathDirectory = "public/images/maps" + /** Значение по умолчанию, если ключ в конфиге отсутствует. */ + private val DefaultImagesDirectory = "app/assets/images/maps" - /** Каталог, в который сохраняются загруженные картинки (исходники). */ - private val SourceUploadDirectory = "app/assets/images/maps" - - /** Каталоги, из которых Play раздаёт assets в dev-режиме. */ - private val RuntimeUploadDirectory = "target/web/public/main/images/maps" + /** Каталог с картинками карт из конфига. */ + def imagesDirectory: File = { + val configured = Try(ConfigFactory.load().getString(ConfigKey)).toOption + .map(_.trim) + .filter(_.nonEmpty) + .getOrElse(DefaultImagesDirectory) + new File(configured) + } /** Максимальный размер загружаемого файла. */ private val MaxUploadBytes = 10L * 1024 * 1024 /** Все карты, отсортированные по количеству игроков и имени. */ - def all: Seq[MapImage] = - loadFileNames() - .distinct - .map(toMapImage) - .sortBy(image => (groupOrder(image.players), image.name.toLowerCase)) + def all: Seq[MapImage] = { + val directory = imagesDirectory + if (!directory.isDirectory) Seq.empty + else + Option(directory.listFiles()).toSeq.flatten + .filter(_.isFile) + .map(_.getName) + .filter(isMapImage) + .distinct + .map(toMapImage) + .sortBy(image => (groupOrder(image.players), image.name.toLowerCase)) + } + + /** + * Ищет картинку карты по имени файла. + * + * Используется для раздачи картинок через контроллер: имя проверяется так, + * чтобы запрос не мог выйти за пределы каталога из конфига. + */ + def findFile(fileName: String): Option[File] = { + val name = Option(fileName).map(_.trim).getOrElse("") + if (name.isEmpty || name != fileName || !isMapImage(name)) None + else { + val directory = imagesDirectory + val parent = Try(directory.getCanonicalFile).toOption + val candidate = Try(new File(directory, name).getCanonicalFile).toOption + (parent, candidate) match { + case (Some(dir), Some(file)) if file.getParentFile == dir && file.isFile => Some(file) + case _ => None + } + } + } /** * Сохраняет загруженную картинку карты и возвращает её описание. * - * Файл пишется и в исходники (app/assets), и в каталог, который раздаётся - * в dev-режиме (target/web/public/main), чтобы картинка сразу появилась + * Файл пишется в каталог из конфига, поэтому картинка сразу появляется * в галерее без перезапуска приложения. */ def saveUploaded(rawFileName: String, source: File): Either[String, MapImage] = { @@ -62,34 +89,23 @@ object MapImageRepository { else sanitizeFileName(rawFileName) match { case None => Left("Unsupported file (allowed types: jpg, jpeg, png, webp)") case Some(safeName) => - val directories = uploadDirectories() - if (directories.isEmpty) Left("Map images directory not found") - else { - val fileName = uniqueFileName(safeName, directories) - Try { - directories.foreach { dir => - java.nio.file.Files.copy( - source.toPath, - new File(dir, fileName).toPath, - StandardCopyOption.REPLACE_EXISTING - ) - } - }.toEither match { - case Right(_) => Right(toMapImage(fileName)) - case Left(error) => Left(s"Failed to save file: ${error.getMessage}") - } + val directory = imagesDirectory + Try { + if (!directory.isDirectory) directory.mkdirs() + val fileName = uniqueFileName(safeName, directory) + java.nio.file.Files.copy( + source.toPath, + new File(directory, fileName).toPath, + StandardCopyOption.REPLACE_EXISTING + ) + fileName + }.toEither match { + case Right(fileName) => Right(toMapImage(fileName)) + case Left(error) => Left(s"Failed to save file: ${error.getMessage}") } } } - /** Каталоги для записи: исходники создаются при необходимости, остальные — только существующие. */ - private def uploadDirectories(): Seq[File] = { - val source = new File(SourceUploadDirectory) - if (!source.isDirectory) source.mkdirs() - (source +: Seq(new File(RuntimeUploadDirectory))) - .filter(_.isDirectory) - } - /** Проверяет расширение и убирает из имени всё, что может сломать путь. */ private def sanitizeFileName(rawFileName: String): Option[String] = { val name = Option(rawFileName) @@ -105,8 +121,8 @@ object MapImageRepository { } /** Подбирает свободное имя, добавляя суффикс -1, -2, ... при совпадении. */ - private def uniqueFileName(fileName: String, directories: Seq[File]): String = { - def taken(name: String): Boolean = directories.exists(dir => new File(dir, name).exists()) + private def uniqueFileName(fileName: String, directory: File): String = { + def taken(name: String): Boolean = new File(directory, name).exists() if (!taken(fileName)) fileName else { @@ -120,17 +136,6 @@ object MapImageRepository { } } - private def loadFileNames(): Seq[String] = { - val fromDisk = MapDirectories - .map(new File(_)) - .find(_.isDirectory) - .map(dir => Option(dir.listFiles()).toSeq.flatten.filter(_.isFile).map(_.getName)) - .getOrElse(Seq.empty) - - val fileNames = if (fromDisk.nonEmpty) fromDisk else loadFromClasspath() - fileNames.filter(isMapImage) - } - private def isMapImage(fileName: String): Boolean = { val lower = fileName.toLowerCase ImageExtensions.exists(ext => lower.endsWith("." + ext)) && @@ -138,39 +143,6 @@ object MapImageRepository { IgnoredPattern.findFirstIn(lower).isEmpty } - private def loadFromClasspath(): Seq[String] = { - val loader = Option(getClass.getClassLoader).getOrElse(ClassLoader.getSystemClassLoader) - loader.getResources(ClasspathDirectory).asScala.toSeq.flatMap(readResource) - } - - private def readResource(url: URL): Seq[String] = url.getProtocol match { - case "file" => - Try(new File(url.toURI)).toOption - .filter(_.isDirectory) - .flatMap(dir => Option(dir.listFiles())) - .toSeq - .flatten - .filter(_.isFile) - .map(_.getName) - - case "jar" => - Try { - val connection = url.openConnection().asInstanceOf[JarURLConnection] - connection.setUseCaches(false) - val jar = connection.getJarFile - try { - jar.entries().asScala - .map(_.getName) - .filter(_.startsWith(ClasspathDirectory + "/")) - .map(_.stripPrefix(ClasspathDirectory + "/")) - .filterNot(_.contains("/")) - .toSeq - } finally jar.close() - }.getOrElse(Seq.empty) - - case _ => Seq.empty - } - private def toMapImage(fileName: String): MapImage = { val baseName = fileName.substring(0, fileName.lastIndexOf('.')) MapImage(baseName, fileName, playersFrom(fileName)) diff --git a/app/views/admin.scala.html b/app/views/admin.scala.html index 7898572..a5b03bd 100644 --- a/app/views/admin.scala.html +++ b/app/views/admin.scala.html @@ -81,9 +81,9 @@
@for(d <- deciders) { - +

@d.name

-

@d.key • maps: @d.maps.size

+

@d.key • maps: @d.maps.size • owner: @d.ownerSteamId.getOrElse("-")

} @if(deciders.isEmpty) { @@ -110,6 +110,17 @@

Unique decider identifier (letters, digits, hyphen, underscore)

+ +
+ +
+ + +

Owner cannot be changed

@@ -218,6 +229,8 @@ diff --git a/conf/application.conf b/conf/application.conf index 821395c..2ec18b5 100644 --- a/conf/application.conf +++ b/conf/application.conf @@ -14,6 +14,13 @@ admin { decidersFile = "conf/deciders.conf" } +# Каталог с картинками карт: из него читается галерея в админке и в него +# сохраняются загруженные картинки. Путь может быть абсолютным или +# относительным (от рабочего каталога приложения). +maps { + imagesDir = "app/assets/images/maps" +} + deciders{ classic{ maps = [["2p_battle_marshes"], diff --git a/conf/deciders.conf b/conf/deciders.conf new file mode 100644 index 0000000..698162b --- /dev/null +++ b/conf/deciders.conf @@ -0,0 +1,1604 @@ +# Конфигурация десайдеров. +# Файл управляется админкой (авторизация через Steam). +# Ручные правки могут быть перезаписаны при следующем сохранении. + +deciders { + 4soulchamp { + name = "4soulchamp tournament" + ownerSteamId = "76561198137977374" + raceCount = 1 + maps = [ + ["2p_ancient_jungle_BM"], + ["2p_consecrated_gardens_5p_BM"], + ["2p_cryptek_arkana_BM"], + ["2p_battle_marshes"], + ["2p_fallen_city_[Rem]"], + ["2p_deadly_fun_archeology"], + ["2p_quests_triumph"], + ["2p_shrine_of_excellion_[Rem]"], + ["2p_sugaroasis"], + ["2p_titan_fall_[Rem]"], + ["2p_tranquilitys_end_[Rem]"] + ] + rules = "Черк до трех последних. Финал - до 5 последних." + } + BalanceModCup2v2 { + name = "Dowstats Balance Mod Cup 2x2" + ownerSteamId = "76561198137977374" + raceCount = 2 + maps = [ + ["4p_biffys_peril"], + ["4p_cold_war"], + ["4p_imperial_area"], + ["4p_tainted_soul"], + ["4p_saints_square"], + ["4p_tartarus_center"], + ["4p_skerries"] + ] + rules = " " + } + BalanceModCup2x2 { + name = "Dowstats Balance Mod Cup 2x2" + ownerSteamId = "76561198137977374" + raceCount = 2 + maps = [ + ["4p_biffys_peril"], + ["4p_cold_war"], + ["4p_broken_infinity"], + ["4p_torrents"], + ["4p_imperial_area"], + ["4p_chaos_platenau"], + ["4p_saints_square"], + ["4p_tartarus_center"], + ["4p_skerries"] + ] + rules = "Первая карта выбирается вычеркиванием. Далее - лузерпик из всех карт маппула. Повтор карт запрещен. Некроны доступны. В рамках каждого БО1/3/5 игроки в десайдере выбирают расы. Одинаковые расы запрещены.\n" + } + Dow_Stats_Tournament { + name = "Dow Stats Tournament" + ownerSteamId = "76561198137977374" + raceCount = 0 + maps = [ + ["2p_cryptek_arkana_BM"], + ["2p_consecrated_gardens_5p_BM"], + ["2p_ancient_jungle_BM"], + ["2p_battle_marshes"], + ["2p_fallen_city"], + ["2p_deadly_fun_archeology"], + ["2p_quests_triumph"], + ["2p_shrine_of_excellion_[Rem]"], + ["2p_sugaroasis"], + ["2p_titan_fall_[Rem]"], + ["2p_tranquilitys_end_[Rem]"] + ] + rules = " " + } + Dow_Stats_Tournament_Race { + name = "Бан расы" + ownerSteamId = "76561198137977374" + raceCount = 1 + existAdditionalRaces = true + maps = [] + rules = "Если в текущем раунде вы выбираете СОБ или Космодесант, вы можете забанить две расы. Банить СоБ или СМ запрещено. If your team choose Space Marines or Sisters Of Battle, you can ban two races. You can't ban SOB or SM." + } + WC2026 { + name = "World Championship 2026" + ownerSteamId = "76561198101420451" + raceCount = 2 + maps = [ + ["2p_battle_marshes"], + ["2p_blood_river"], + ["2p_bloody_hell_[Ed]"], + ["2p_deadly_fun_archeology"], + ["2p_meeting_of_minds"], + ["2p_outer_reaches"], + ["2p_quests_triumph"], + ["2p_shrine_of_excellion"], + ["2p_titan_fall_[Rem]"], + ["2p_tranquilitys_end"], + ["2p_fata_morgana_[Rem]"] + ] + rules = "" + } + arena-bad-2 { + name = "Arena bad 2" + ownerSteamId = "76561198963125550" + raceCount = 1 + existAdditionalRaces = true + 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"] + ] + rules = "1 раса, но можно взять вторую при мирроре" + } + backtoskullcup { + name = "Back to Skull Cup cup" + ownerSteamId = "76561198137977374" + raceCount = 2 + description = true + maps = [ + ["4p_biffys_peril"], + ["4p_chaos_platenau"], + ["4p_colosseum_of_deadman"], + ["4p_gorhael_crater"], + ["4p_imperial_area"], + ["4p_saints_square"], + ["4p_tartarus_center"], + ["4p_gurmuns_pass"], + ["4p_torrents"] + ] + rules = "" + } + bad_vs_icc_2x2 { + name = "BAD vs ICC 2x2" + ownerSteamId = "76561198963125550" + raceCount = 3 + maps = [ + ["4p_biffys_peril"], + ["4p_colosseum_of_deadman"], + ["4p_cold_war"], + ["4p_imperial_area"], + ["4p_tainted_soul"], + ["4p_tartarus_center"], + ["4p_skerries"] + ] + rules = "" + } + bad_vs_icc_3x3 { + name = "BAD vs ICC 3x3" + ownerSteamId = "76561198963125550" + raceCount = 4 + maps = [ + ["6p_fury_island"], + ["6p_irridene"], + ["6p_trivian_groves"], + ["6p_platform"], + ["6p_principian_badlands"], + ["6p_testing_grounds"], + ["6p_gear"] + ] + rules = "" + } + bad_vs_icc_4x4 { + name = "BAD vs ICC 4x4" + ownerSteamId = "76561198963125550" + raceCount = 5 + maps = [ + ["8p_morriah_coast"], + ["8p_burial_grounds"], + ["8p_kier_harrad"], + ["8p_hydraphur"], + ["8p_forbidden_jungle"] + ] + rules = "" + } + badcup { + name = "BAD cup" + ownerSteamId = "76561198963125550" + 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]"], + ["2p_cryptek_arkana_BM"], + ["2p_consecrated_gardens_5p_BM"] + ] + rules = "Double elimination. БО3 (БО5 суперфинал). БО1 лузерсетка (БО3 финал лузер сетки). 2 Расы на турнир, без банов. Вторая раса берётся в случае лузерпика(и играется до окончания бо) или по договоренности обеих сторон в случае миррора." + } + badcup-5 { + name = "Bad cup 5" + ownerSteamId = "76561198963125550" + raceCount = 2 + description = true + maps = [ + ["2p_battle_marshes"], + ["2p_fallen_city_[Rem]"], + ["2p_consecrated_gardens_5p_BM"], + ["2p_quests_triumph"], + ["2p_shrine_of_excellion_[Rem]"], + ["2p_titan_fall_[Rem]"], + ["2p_tranquilitys_end_[Rem]"], + ["2p_outer_reaches"], + ["2p_blood_river_[Rem]"], + ["2p_deadly_fun_archeology"], + ["2p_meeting_of_minds"] + ] + rules = " " + } + balance-rage-2x2 { + name = "BALANCE RAGE 2V2 CUP" + ownerSteamId = "76561198137977374" + raceCount = 2 + maps = [ + ["4p_tartarus_center"], + ["4p_skerries"], + ["4p_chaos_platenau"], + ["4p_cold_war"], + ["4p_broken_infinity"], + ["4p_imperial_area"], + ["4p_saints_square"], + ["4p_gorhael_crater"], + ["4p_biffys_peril"] + ] + rules = "Для БО3 вычеркивание до 3х карт, на которых будут проходить все игры. Первая карта выбирается дальнейшим вычеркиванием. Для БО5 вычеркивание до 5 карт. Повтор карт запрещен. " + } + balancemodcup { + name = "Balance Mod Cup" + ownerSteamId = "76561198137977374" + raceCount = 2 + description = true + maps = [ + ["2p_cryptek_arkana_BM"], + ["2p_consecrated_gardens_5p_BM"], + ["2p_ancient_jungle_BM"], + ["2p_battle_marshes"], + ["2p_fallen_city"], + ["2p_deadly_fun_archeology"], + ["2p_quests_triumph"], + ["2p_shrine_of_excellion_[Rem]"], + ["2p_sugaroasis"], + ["2p_titan_fall_[Rem]"], + ["2p_tranquilitys_end_[Rem]"] + ] + rules = " Первая карта выбирается вычеркиванием. Далее - лузерпик. Повтор карт запрещен. " + } + beercup { + name = "Beer Cup" + ownerSteamId = "76561198057743239" + raceCount = 3 + description = true + maps = [ + ["2p_titan_fall_[Rem]"], + ["2p_fallen_city_[Rem]", "Обратите внимание, ремастер"], + ["2p_quests_triumph"], + ["2p_shrine_of_excellion_[Rem]"], + ["2p_tranquilitys_end_[Rem]"], + ["2p_deadly_fun_archeology"], + ["2p_fraziersdemise"], + ["2p_ancient_jungle_BM"], + ["2p_battle_marshes"], + ["2p_consecrated_gardens_5p_BM"], + ["2p_blood_river_[Rem]"], + ["2p_fata_morgana_[Rem]"], + ["2p_meeting_of_minds"], + ["2p_outer_reaches"], + ["2p_bloody_hell_[Ed]"] + ] + rules = " Выбор карт без лузерпиков, с тремя и пятью десайдерами соответственно. Игроки вычеркивают по очереди карты из маппула, пока их не останется 3 (для БО3) или 5 (для БО5). На этих картах и проходят все матчи встречи. Затем игроки вычеркивают выбранные карты, пока не останется только одна, на которой и играется первый матч встречи. Тот, кто первым начал вычеркивать из всего маппула, уступает оппоненту право вычеркивания первой карты из оставшихся карт-десайдеров. Во втором и последующем матчах проигравший выбирает карту из числа выбранных 3 (5) десайдеров. " + } + bestfriendscup { + name = "Best friends cup cup" + ownerSteamId = "76561198137977374" + raceCount = 1 + maps = [ + ["4p_gorhael_crater"], + ["4p_gurmuns_pass"], + ["4p_saints_square"], + ["4p_skerries"], + ["4p_panrea_lowlands"], + ["4p_doom_spiral"], + ["4p_tiboraxx"] + ] + rules = " Обе команды вычеркивают по очереди неудобные для себя карты. Каждая команда вычеркивает в общей сложности 3. На оставшейся, седьмой карте, проходит Ваш первый матч - она называется десайдером. Во всех последующих играх карту выбирает команда, проигравшая в последнем матче. Это называется лузерпиком - выбором проигравших. В суперфинале турнира сражается команда из сетки виннеров против финалистов из сетки лузеров, в формате БО5 (до трёх побед). " + } + bigbosswinter { + name = "Big boss of winter" + ownerSteamId = "76561198137977374" + raceCount = 1 + 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"] + ] + rules = "Выбор карт в БО3 и БО5 осуществляется без лузерпиков, с тремя и пятью десайдерами соответственно. Игроки вычеркивают по очереди карты из маппула, пока их не останется 3 (для БО3) или 5 (для БО5). На этих картах и проходят все матчи встречи. Затем игроки вычеркивают выбранные карты, пока не останется только одна, на которой и играется первый матч встречи. Тот, кто первым начал вычеркивать из всего маппула, уступает оппоненту право вычеркивания первой карты из оставшихся карт-десайдеров. Во втором и последующем матчах проигравший выбирает карту из числа выбранных 3 (5) десайдеров.В суперфинале турнира оба игрока вычеркивают по две карты. После этого из оставшихся 9 карт можно брать любые, выбирает их проигравший в предыдущем матче. Первый лузер-пик - за игроком из нижней сетки, который начинает с -1 очком. " + } + burgercup { + name = "Burger cup" + ownerSteamId = "76561198057743239" + 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"] + ] + rules = "Выбор карт в БО3 и БО5 осуществляется без лузерпиков, с тремя и пятью десайдерами соответственно. Игроки вычеркивают по очереди карты из маппула, пока их не останется 3 (для БО3) или 5 (для БО5). На этих картах и проходят все матчи встречи. Затем игроки вычеркивают выбранные карты, пока не останется только одна, на которой и играется первый матч встречи. Тот, кто первым начал вычеркивать из всего маппула, уступает оппоненту право вычеркивания первой карты из оставшихся карт-десайдеров. Во втором и последующем матчах проигравший выбирает карту из числа выбранных 3 (5) десайдеров." + } + burgercup-de { + name = "Burger cup" + ownerSteamId = "76561198057743239" + raceCount = 3 + 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"], + ["2p_consecrated_gardens_5p_BM"], + ["2p_ancient_jungle_BM"] + ] + rules = "Выбор карт в БО3 и БО5 осуществляется без лузерпиков, с тремя и пятью десайдерами соответственно. Игроки вычеркивают по очереди карты из маппула, пока их не останется 3 (для БО3) или 5 (для БО5). На этих картах и проходят все матчи встречи. Затем игроки вычеркивают выбранные карты, пока не останется только одна, на которой и играется первый матч встречи. Тот, кто первым начал вычеркивать из всего маппула, уступает оппоненту право вычеркивания первой карты из оставшихся карт-десайдеров. Во втором и последующем матчах проигравший выбирает карту из числа выбранных 3 (5) десайдеров." + } + casinocup { + name = "Casino cup" + ownerSteamId = "76561198057743239" + 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"] + ] + rules = "Выбор карт без лузерпиков, с тремя и пятью десайдерами соответственно. Игроки вычеркивают по очереди карты из маппула, пока их не останется 3 (для БО3) или 5 (для БО5). На этих картах и проходят все матчи встречи. Затем игроки вычеркивают выбранные карты, пока не останется только одна, на которой и играется первый матч встречи. Тот, кто первым начал вычеркивать из всего маппула, уступает оппоненту право вычеркивания первой карты из оставшихся карт-десайдеров. Во втором и последующем матчах проигравший выбирает карту из числа выбранных 3 (5) десайдеров. " + } + classic { + name = "Классический десайдер" + ownerSteamId = "76561198137977374" + raceCount = 1 + maps = [ + ["2p_battle_marshes"], + ["2p_fallen_city"], + ["2p_fata_morgana"], + ["2p_quests_triumph"], + ["2p_meeting_of_minds"], + ["2p_shrine_of_excellion"], + ["2p_titan_fall"], + ["2p_tranquilitys_end"], + ["2p_outer_reaches"], + ["2p_blood_river"], + ["2p_sugaroasis"] + ] + rules = "" + } + compatchcup { + name = "Community patch cup" + ownerSteamId = "76561198137977374" + raceCount = 1 + 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]"] + ] + rules = "см. в дискорде турнира" + } + customcup { + name = "Custom Cup" + ownerSteamId = "76561198137977374" + raceCount = 2 + 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]"] + ] + rules = "Выбор карт в БО3 и БО5 осуществляется без лузерпиков, с тремя и пятью десайдерами соответственно. Игроки вычеркивают по очереди карты из маппула, пока их не останется 3 (для БО3) или 5 (для БО5). На этих картах и проходят все матчи встречи. Затем игроки вычеркивают выбранные карты, пока не останется только одна, на которой и играется первый матч встречи. Тот, кто первым начал вычеркивать из всего маппула, уступает оппоненту право вычеркивания первой карты из оставшихся карт-десайдеров. Во втором и последующем матчах проигравший выбирает карту из числа выбранных 3 (5) десайдеров." + } + deadgamecup { + name = "Dead game cup" + ownerSteamId = "76561198137977374" + 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"] + ] + rules = " " + } + definitive-cup { + name = "Definitive Cup" + ownerSteamId = "76561198137977374" + raceCount = 3 + maps = [ + ["2p_battle_marshes"], + ["2p_blood_river"], + ["2p_fallen_city"], + ["2p_meeting_of_minds"], + ["2p_outer_reaches"], + ["2p_quests_triumph"], + ["2p_shrine_of_excellion"], + ["2p_titan_fall"], + ["2p_tranquilitys_end"] + ] + rules = "Первая карта выбирается вычеркиванием до 1 карты. Далее лузерпик из всех карт маппула. При выборе рас указываете основную(1), дополнительную(2) и нежелательную(3). The first map is selected by crossing out up to 1 map. Next, a loser is selected from all the maps in the mappool. At race selection - select main (1) additional(2) and unwanted(3) race." + } + dredicmappool { + name = "Десайдер DreDick" + ownerSteamId = "76561198963125550" + raceCount = 2 + 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]"] + ] + rules = "" + } + dredicmappool2x2 { + name = "Десайдер DreDick 2x2" + ownerSteamId = "76561198963125550" + raceCount = 2 + maps = [ + ["4p_tartarus_center"], + ["4p_skerries"], + ["4p_chaos_platenau"], + ["4p_cold_war"], + ["4p_broken_infinity"], + ["4p_colosseum_of_deadman"], + ["4p_imperial_area"], + ["4p_torrents"], + ["4p_saints_square"], + ["4p_marconia"], + ["4p_biffys_peril"] + ] + rules = "" + } + dredicmappool3 { + name = "Dredic map pool 3" + ownerSteamId = "76561198963125550" + raceCount = 2 + description = true + 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_deadly_fun_archeology"], + ["2p_meeting_of_minds"] + ] + rules = " " + } + dredicmappool3x3 { + name = "Десайдер 3x3" + ownerSteamId = "76561198963125550" + raceCount = 3 + maps = [ + ["6p_paynes_retribution"], + ["6p_fury_island"], + ["6p_testing_grounds"], + ["6p_alvarus"], + ["6p_shakun_coast"], + ["6p_team_streets_of_vogen"], + ["6p_irridene"], + ["6p_gear"], + ["6p_platform"] + ] + rules = "" + } + dredicmappool4x4 { + name = "Десайдер DreDick 4x4" + ownerSteamId = "76561198963125550" + 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_oasis_of_sharr.jpg"], + ["8p_verdant_isles_ed", "Количество стратегических/реликтовых/критических точек: 32/4/3\nСпорных точек: 0/4/3\nТермогенераторы: 0"] + ] + rules = "" + } + dvijcup { + name = "Dvij cup" + ownerSteamId = "76561198057743239" + raceCount = 1 + 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"], + ["2p_blood_river_[Rem]"], + ["2p_deadly_fun_archeology"] + ] + rules = "Выбор карт в БО3 и БО5 осуществляется без лузерпиков, с тремя и пятью десайдерами соответственно.\n Игроки вычеркивают по очереди карты из маппула, пока их не останется 3 (для БО3) или 5 (для БО5). На этих картах и проходят все матчи встречи.\n Затем игроки вычеркивают выбранные карты, пока не останется только одна, на которой и играется первый матч встречи.\n Тот, кто первым начал вычеркивать из всего маппула, уступает оппоненту право вычеркивания первой карты из оставшихся карт-десайдеров.\n Во втором и последующем матчах проигравший выбирает карту из числа выбранных 3 (5) десайдеров." + } + dvijcupseason { + name = "Dvij cup season" + ownerSteamId = "76561198057743239" + raceCount = 3 + existAdditionalRaces = true + description = true + 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_snowblind", "Количество точек: 22/2/3\nСпорных точек: 4/2/3\nТермогенераторы: отсутствуют"], + ["6p_trivian_groves_v2", "Количество точек: 32/4/3\nСпорных точек: 6/2/3\nТермогенераторы: 2"], + ["6p_gear", "Количество точек: 24/2/3\nСпорных точек: 2 крита, но ввиду контактности карты сражение может идти на большинстве внешних точек\nТермогенераторы: 2"] + ] + rules = "Классическая Double elimination с сеткой лузеров. В сетке виннеров - БО3, в сетке лузеров - БО1, в суперфинале - БО5. Игрок, прошедший в суперфинал из верхней сетки, имеет +1 очко. Игрок из нижней сетки начинает с лузерпиком. " + } + elmcupdow { + name = "ELM cup decider" + ownerSteamId = "76561198813722589" + raceCount = 2 + maps = [ + ["2p_blood_river_[Rem]"], + ["2p_fallen_city_[Rem]"], + ["2p_meeting_of_minds"], + ["2p_battle_marshes"], + ["2p_outer_reaches"], + ["2p_quests_triumph"], + ["2p_shrine_of_excellion_[Rem]"], + ["2p_titan_fall_[Rem]"], + ["2p_tranquilitys_end_[Rem]"], + ["2p_cryptek_arkana_BM"], + ["2p_ancient_jungle_BM"] + ] + rules = "" + } + everyonecup { + name = "Everyone cup" + ownerSteamId = "76561198137977374" + raceCount = 1 + 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"] + ] + rules = "Все матчи БО3. Игроки вычеркивают по очереди карты из маппула, пока их не останется 3. На этих картах и проходят все матчи встречи." + } + flazzomappool { + name = "Десайдер flazzo" + ownerSteamId = "76561199079574986" + raceCount = 2 + 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]"] + ] + rules = "" + } + freakcup { + name = "Десайдер freakcup" + ownerSteamId = "76561198137977374" + raceCount = 1 + 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"] + ] + rules = "" + } + frenchtournament { + name = "French tournament" + ownerSteamId = "76561198137977374" + raceCount = 1 + 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"] + ] + rules = " " + } + freneticmappool { + name = "Frenetic map pool" + ownerSteamId = "76561198813722589" + 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"] + ] + rules = " " + } + freneticmappool15 { + name = "Frenetic map pool (15 maps)" + ownerSteamId = "76561198813722589" + raceCount = 2 + description = true + maps = [ + ["2p_titan_fall_[Rem]"], + ["2p_fallen_city_[Rem]", "Обратите внимание, ремастер"], + ["2p_quests_triumph"], + ["2p_shrine_of_excellion_[Rem]"], + ["2p_tranquilitys_end_[Rem]"], + ["2p_cryptek_arkana_BM"], + ["2p_deadly_fun_archeology"], + ["2p_consecrated_gardens_6p_BM"], + ["2p_ancient_jungle_BM"], + ["2p_outer_reaches"], + ["2p_sugaroasis"], + ["2p_meeting_of_minds"], + ["2p_emerald_river"], + ["2p_battle_marshes"], + ["2p_gavrix_imperial_ale"] + ] + rules = " " + } + freneticmappool17 { + name = "Frenetic map pool (17 maps)" + ownerSteamId = "76561198813722589" + raceCount = 2 + description = true + maps = [ + ["2p_titan_fall_[Rem]"], + ["2p_fallen_city", "Обратите внимание, не ремастер"], + ["2p_quests_triumph"], + ["2p_shrine_of_excellion_[Rem]"], + ["2p_tranquilitys_end_[Rem]"], + ["2p_cryptek_arkana_BM"], + ["2p_deadly_fun_archeology"], + ["2p_consecrated_gardens_5p_BM"], + ["2p_ancient_jungle_BM"], + ["2p_outer_reaches"], + ["2p_sugaroasis"], + ["2p_meeting_of_minds"], + ["2p_emerald_river"], + ["2p_fraziersdemise"], + ["2p_bloody_hell_[Ed]"] + ] + rules = " " + } + freneticmappool2 { + name = "Frenetic map pool" + ownerSteamId = "76561198813722589" + 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"] + ] + rules = " " + } + freneticmappool2x2 { + name = "Frenetic map pool 2х2" + ownerSteamId = "76561198813722589" + raceCount = 2 + description = true + maps = [ + ["4p_tartarus_center"], + ["4p_saints_square"], + ["4p_cold_war"], + ["4p_marconia"], + ["4p_biffys_peril"], + ["4p_torrents"], + ["4p_chaos_platenau"], + ["4p_imperial_area"], + ["4p_broken_infinity"] + ] + rules = " " + } + freneticmappool3 { + name = "Frenetic map pool" + ownerSteamId = "76561198813722589" + 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_outer_reaches"], + ["2p_sugaroasis"], + ["2p_blood_river_[Rem]"], + ["2p_deadly_fun_archeology"], + ["2p_meeting_of_minds"] + ] + rules = " " + } + freneticmappool3x3 { + name = "Frenetic mappool 3x3" + ownerSteamId = "76561198813722589" + raceCount = 3 + maps = [ + ["6p_fury_island"], + ["6p_platform"], + ["6p_gear"], + ["6p_principian_badlands_BM"], + ["6p_trivian_groves_BM"], + ["6p_paynes_retribution"], + ["6p_team_streets_of_vogen"], + ["6p_shakun_coast"], + ["6p_constellation"] + ] + rules = "Frenetic mappool 3x3" + } + horusheresycup2 { + name = "Horus heresy cup 2" + ownerSteamId = "76561198137977374" + raceCount = 1 + 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"] + ] + rules = " Первая карта десайдер выбирается методом вычеркивания, остальные лузер пик. По обоюдному согласию игроков разрешен повтор карт. Сетка турнира Double Elimination.\n Сетка виннеров:\n Каждая игра до 2х победы (БО3),\n В суперфинале игра до 3-х побед (БО5).\n Сетка лузеров:\n Каждая игра до 1й победы (БО1),\n Финал сетки лузеров (БО3).\n Игрок, прошедший в суперфинал из верхней сетки, имеет +1 очко. Игрок из нижней сетки начинает с лузерпиком. " + } + madcup { + name = "Decider MAD" + ownerSteamId = "76561198861408552" + raceCount = 3 + maps = [ + ["2p_outer_reaches"], + ["2p_blood_river"], + ["2p_fata_morgana"], + ["2p_quests_triumph"], + ["2p_meeting_of_minds"], + ["2p_fallen_city"], + ["2p_tranquilitys_end"], + ["2p_shrine_of_excellion"], + ["2p_titan_fall"] + ] + rules = "" + } + mallusc-cup { + name = "Маллюск кап" + ownerSteamId = "76561198137977374" + raceCount = 1 + existAdditionalRaces = true + 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"] + ] + rules = "" + } + meatgrinder { + name = "Meat grinder" + ownerSteamId = "76561198137977374" + raceCount = 1 + 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]"] + ] + rules = "Десайдеры выбираются методом вычеркивания до 3(5) карт, далее из оставшихся карт методом вычеркивания определяется первая карта. Игрок, начавший вычеркивание из основного пула уступает право начать вычеркивание из оставшихся карт." + } + medskillcup { + name = "Med skill cup 4" + ownerSteamId = "76561198137977374" + raceCount = 1 + maps = [ + ["2p_battle_marshes"], + ["2p_fallen_city_[Rem]"], + ["2p_fata_morgana_[Rem]"], + ["2p_bloody_hell_[Ed]"], + ["2p_outer_reaches"], + ["2p_quests_triumph"], + ["2p_shrine_of_excellion_[Rem]"], + ["2p_titan_fall_[Rem]"], + ["2p_tranquilitys_end_[Rem]"], + ["2p_fraziersdemise"], + ["2p_emerald_river"], + ["2p_deadly_fun_archeology"], + ["2p_blood_river_[Rem]"] + ] + rules = "Вы и ваш оппонент перед началом матчей вычеркиваете из маппула карты, пока не останется 3. На них и играете. Для БО1 - та же самая система выбора карт. В суперфинале игрок из верхней сетки начинает с +1 очком, а игрок из нижней сетки - с лузерпиком. Далее оба игрока вычеркивают по 3 карты, и могут лузерпикать из оставшихся 6 в последующих матчах." + } + mironcup { + name = "Десайдер для турнира мироныча" + ownerSteamId = "76561198137977374" + raceCount = 3 + maps = [ + ["2p_battle_marshes"], + ["2p_fallen_city"], + ["2p_fata_morgana"], + ["2p_quests_triumph"], + ["2p_meeting_of_minds"], + ["2p_shrine_of_excellion"], + ["2p_titan_fall"], + ["2p_tranquilitys_end"], + ["2p_emerald_river"], + ["2p_outer_reaches"], + ["2p_blood_river"] + ] + rules = "" + } + mironcup1x1 { + name = "Турнир мироныча" + ownerSteamId = "76561198137977374" + raceCount = 1 + description = true + maps = [ + ["4p_abandoned_jungle"], + ["4p_antiga_bay"], + ["4p_chaos_platenau"], + ["4p_cold_war"], + ["4p_broken_infinity"], + ["4p_colosseum_of_deadman"], + ["4p_imperial_area"], + ["4p_ruined_greatway"], + ["4p_sad_spires"], + ["4p_snowblind"], + ["4p_summit"], + ["4p_mariza"], + ["4p_testcake"] + ] + rules = "Десайдеры выбирается методом вычеркивания до 3 карт, далее из оставшихся карт методом вычеркивания определяется первая карта. Игрок, начавший вычеркивание из основного пула уступает право начать вычеркивание из оставшихся карт." + } + mistrzostwa { + name = "Mistrzostwa Polski DoW 2026" + ownerSteamId = "76561197967830811" + raceCount = 1 + maps = [ + ["2p_battle_marshes"], + ["2p_fallen_city"], + ["2p_fata_morgana"], + ["2p_quests_triumph"], + ["2p_meeting_of_minds"], + ["2p_shrine_of_excellion"], + ["2p_titan_fall"], + ["2p_tranquilitys_end"], + ["2p_outer_reaches"], + ["2p_blood_river"] + ] + rules = "Each player would ban a total of four maps, leaving the final two maps to be played." + } + montanaz0r { + name = "-" + ownerSteamId = "76561197967830811" + raceCount = 2 + maps = [ + ["2p_battle_marshes"], + ["2p_blood_river"], + ["2p_fallen_city"], + ["2p_tranquilitys_end"], + ["2p_shrine_of_excellion"], + ["2p_quests_triumph"], + ["2p_outer_reaches"], + ["2p_fata_morgana"], + ["2p_titan_fall"], + ["2p_meeting_of_minds"], + ["2p_deadly_fun_archeology"] + ] + rules = "There are 11 maps in total. The veto process varies depending on the round: cross-pick continues until 1 map remains for Bo1, 3 maps remain for Bo3, or 5 maps remain for Bo5." + } + new-players-tournament { + name = "New player tournament" + ownerSteamId = "76561198137977374" + raceCount = 1 + 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_outer_reaches"], + ["2p_deadly_fun_archeology"], + ["2p_blood_river_[Rem]"] + ] + rules = "New player tournament." + } + noloody_mappool { + name = "Noloody map pool" + ownerSteamId = "76561198137977374" + raceCount = 2 + 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"] + ] + rules = "См. в дискорде" + } + noweaknesscup { + name = "No Weakness cup" + ownerSteamId = "76561198137977374" + raceCount = 1 + 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"] + ] + rules = "Выбор карт в БО3 и БО5 осуществляется без лузерпиков, с тремя и пятью десайдерами соответственно. Игроки вычеркивают по очереди карты из маппула, пока их не останется 3 (для БО3) или 5 (для БО5). На этих картах и проходят все матчи встречи. Затем игроки вычеркивают выбранные карты, пока не останется только одна, на которой и играется первый матч встречи. Тот, кто первым начал вычеркивать из всего маппула, уступает оппоненту право вычеркивания первой карты из оставшихся карт-десайдеров. Во втором и последующем матчах проигравший выбирает карту из числа выбранных 3 (5) десайдеров. " + } + ppcz { + name = "PPCZ" + ownerSteamId = "76561198137292489" + 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"] + ] + rules = "\n Игрок выбирает основную и дополнительную расу (по желанию). Первая игра всегда играется основной расой. Начиная со второй игры проигравший может взять дополнительную расу, при этом победивший не может поменять расу.\n\n Эльдар не имеет права лузерпикать против SM, IG, CSM (вместо этого карту выбирает оппонент)\n\n Некрон не имеет права лузерпикать против Tau (тоже, что и в примере выше)\n Против некрона нельзя выбирать SOE лузерпиком (Tau все еще может выбирать любую карту против некрона" + } + primus_inter_pares { + name = "Primus inter pares" + ownerSteamId = "76561198137977374" + raceCount = 1 + description = true + maps = [ + ["2p_battle_marshes"], + ["2p_blood_river_[Rem]"], + ["2p_fallen_city_[Rem]"], + ["2p_outer_reaches"], + ["2p_quests_triumph"], + ["2p_shrine_of_excellion_[Rem]"], + ["2p_titan_fall_[Rem]"], + ["2p_tranquilitys_end_[Rem]"], + ["2p_bloody_hell_[Ed]"], + ["2p_deadly_fun_archeology"], + ["2p_sugaroasis"] + ] + rules = " " + } + salats { + name = "Выбираем салат на новый год" + ownerSteamId = "76561198137977374" + raceCount = 1 + maps = [ + ["винегрет"], + ["грибная поляна"], + ["крабовый"], + ["мимоза"], + ["оливье"], + ["селедка под шубой"], + ["цезарь"], + ["из пекинской капусты"], + ["гнездо глухаря"], + ["летний"] + ] + rules = "" + } + shouldercup2x2 { + name = "Shoulder cup 2x2" + ownerSteamId = "76561198057743239" + raceCount = 2 + existAdditionalRaces = true + description = 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"] + ] + rules = "Расы в командах (как основные, так и дополнительные) должны соблюдать правило - в команде должен быть только один представитель следующих рас: тау, эльдар. В случае поражения на одной из карт, противники могут лузерпикать свои расы на дополнительные (причем допрасы берут все игроки команды и могут мешать), но при этом заранее уведомляют будут ли играть данной связкой рас на следующей карте (в случае если лузерпик произошел на второй карте в рамках Best of 3). Расы свободно выбираются каждое БО 1/3/5 на протяжении турнира. Позиции можно выбирать по договоренности с командами." + } + shouldercup4x4 { + name = "Shoulder to shoulder cup" + ownerSteamId = "76561198057743239" + raceCount = 4 + description = true + 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"] + ] + rules = "Черкается по правилу лузерпиков: сначала оставляется одна карта, далее команда выбирает, на какой карте будет играть. " + } + showmatch { + name = "Маллюск кап" + ownerSteamId = "76561198057743239" + raceCount = 1 + existAdditionalRaces = true + 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"] + ] + rules = "" + } + soulstormlegue { + name = "Soulstorm League" + ownerSteamId = "76561198137977374" + raceCount = 2 + maps = [ + ["2p_battle_marshes"], + ["2p_consecrated_gardens_5p_BM"], + ["2p_titan_fall_[Rem]"], + ["2p_sugaroasis"], + ["2p_gavrix_imperial_ale"], + ["2p_deadly_fun_archeology"], + ["2p_fallen_city"], + ["2p_meeting_of_minds"], + ["2p_tranquilitys_end_[Rem]"] + ] + rules = "Для БО3 вычеркивание до 3х карт, на которых будут проходить все игры. Первая карта выбирается дальнейшим вычеркиванием. Для БО5 вычеркивание до 5 карт. Повтор карт запрещен. " + } + stscup3x3 { + name = "Shoulder to Shoulder Cup" + ownerSteamId = "76561198057743239" + raceCount = 3 + existTextFieldRaces = true + description = true + 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_testing_grounds", "Количество точек: 32/6/5\nСпорных точек: 10/2/5\nТермогенераторы: 6"], + ["6p_gear", "Количество точек: 24/2/3\nСпорных точек: 2 крита, но ввиду контактности карты сражение может идти на большинстве внешних точек\nТермогенераторы: 2"], + ["6p_constellation"], + ["6p_trivian_groves_v2"], + ["6p_principian_badlands_BM"], + ["6p_team_streets_of_vogen"] + ] + rules = "Классическая Double elimination с сеткой лузеров. В сетке виннеров - БО3, в сетке лузеров - БО1, в финале лузеров - БО 3, в суперфинале - БО5. Дошедшие с сетки виннеров получают +1 очко, а вышедшие с сетки лузеров имеют право лузерпика." + } + summer-cup { + name = "Summer cup" + ownerSteamId = "76561198137977374" + raceCount = 2 + maps = [ + ["2p_outer_reaches"], + ["2p_battle_marshes"], + ["2p_fallen_city"], + ["2p_fata_morgana_[Rem]"], + ["2p_quests_triumph"], + ["2p_shrine_of_excellion"], + ["2p_titan_fall"], + ["2p_tranquilitys_end"], + ["2p_bloody_hell_[Ed]"], + ["2p_blood_river"], + ["2p_meeting_of_minds"] + ] + rules = "Вычеркивание до 3(5) карт, на них пройдут все игры встречи. Первая карта определяется дальнейшим вычеркиванием. Повтор карт запрещен. Можно играть на ремастерах указанных карт " + } + sweatybarrakcup { + name = "Sweaty barrack cup" + ownerSteamId = "76561198137977374" + raceCount = 1 + 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"] + ] + rules = "Перед началом матча, игроки поочередно вычеркивают по одной карте, пока не выберут десайдер. Если иного не сказано в секции \"Расы\" (см. ограничения для рас). Во второй и всех последующих играх - побежденный выбирает карту.\n Чтобы не было повторений карт, игроки могу выбрать всего 1 раз карту из списка. " + } + taktik-garik-winter-cup { + name = "TaktikGarik Winter Cup" + ownerSteamId = "76561198137977374" + raceCount = 2 + maps = [ + ["2p_battle_marshes"], + ["2p_fallen_city"], + ["2p_fata_morgana"], + ["2p_quests_triumph"], + ["2p_meeting_of_minds"], + ["2p_shrine_of_excellion"], + ["2p_titan_fall"], + ["2p_tranquilitys_end"], + ["2p_bloody_hell_[Ed]"], + ["2p_outer_reaches"], + ["2p_blood_river"] + ] + rules = "Crossed out up to 3(5) maps, and all games of the meeting will be played on them. The first map is determined by further crossing out. Repeating maps is prohibited. It is allowed to play on remasters of these maps." + } + test { + name = "Test 2" + ownerSteamId = "76561198137977374" + raceCount = 1 + maps = [ + ["2p_colosseum"] + ] + rules = "" + } + testcup { + name = "Тестовый турнир" + ownerSteamId = "76561198137977374" + raceCount = 3 + existAdditionalRaces = true + description = true + maps = [ + ["6p_irridene_v2", "Тут можно написать всякую фигню про точки и т.д."], + ["6p_principian_badlands_BM", "Вася"], + ["6p_trivian_groves_v2", "Петя"], + ["6p_desiderata_highlands", "Деса"], + ["6p_gear", "Иридэн где-то здесь"] + ] + rules = " Тест " + } + the_emperor_protects_race_ban { + name = "The Emperor protects 2x2. Races ban." + ownerSteamId = "76561198137977374" + raceCount = 1 + existAdditionalRaces = true + maps = [] + rules = "Бан рас для турнира The Emperor protects! Если в текущем раунде вы играете за СМ или СоБ, то можете забанить 2 расы. Если в вашей команде не будет СМ или СоБ, забанить можно только 1 расу. Из-за ограничений десайдера вы баните 2 раза одну расу! Запрещено банить СМ и СоБ! |||||||||||\n\t\t\t\t\t\tRaces ban decider. You can ban 2 races, if you are playing for SM or SoB in the current round. If your team does not have SM or SoB, you can only ban 1 race. Due to the restrictions of the Decider, you ban 2 times one race! Banning SM and SoB is prohibited!" + } + theemperorprotects { + name = "The emperor protects 2x2" + ownerSteamId = "76561198137977374" + raceCount = 2 + description = true + maps = [ + ["4p_biffys_peril"], + ["4p_cold_war"], + ["4p_colosseum_of_deadman"], + ["4p_imperial_area"], + ["4p_chaos_platenau"], + ["4p_saints_square"], + ["4p_tartarus_center"], + ["4p_skerries"], + ["4p_torrents"] + ] + rules = "Первая карта выбирается вычеркиванием в десайдере. Далее - лузерпик из всех карт маппула. Повтор карт запрещен." + } + tpmodcup { + name = "Турнир по ТП моду" + ownerSteamId = "76561198137977374" + raceCount = 1 + description = true + 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"] + ] + rules = "Первая карта десайдер выбирается методом вычеркивания, остальные лузер пик. " + } + trainercup { + name = "Trainer cup" + ownerSteamId = "76561198057743239" + raceCount = 2 + 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"] + ] + rules = "Выбор карт в рамках бо3 и бо5, с тремя и пятью картами соответственно. Игроки вычеркивают по очереди карты из маппула, пока их не останется 3 (для БО3) или 5 (для БО5). На этих картах и проходят все матчи встречи. Затем игроки вычеркивают выбранные карты, пока не останется только одна, на которой и играется первый матч встречи. Во втором и последующем матчах проигравший выбирает карту из числа выбранных карт. В сетке лузеров бо1, финал лузеров бо3, в супер-финале игрок из нижней сетки имеет право автоматического лузерпика, а игрок из верхней - плюс очко.." + } + true_arena_bad2 { + name = "BAD cup" + ownerSteamId = "76561198963125550" + 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]"] + ] + rules = "" + } + turtleshell { + name = "Turtle Shell tournament 3x3" + ownerSteamId = "76561198035987602" + raceCount = 1 + 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"] + ] + rules = "" + } + turtleshell3x3 { + name = "Turtle Shell tournament 3x3" + ownerSteamId = "76561198035987602" + raceCount = 3 + maps = [ + ["6p_fury_island"], + ["6p_irridene"], + ["6p_shakun_coast"], + ["6p_testing_grounds"], + ["6p_gear"], + ["6p_platform"] + ] + rules = "Turtle Shell tournament 3x3" + } + uacup { + name = "UA cup" + ownerSteamId = "76561198137977374" + 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"] + ] + rules = "" + } + wellplayed { + name = "Десайдер Wellplayed" + ownerSteamId = "76561198137977374" + raceCount = 1 + maps = [ + ["2p_battle_marshes"], + ["2p_fallen_city"], + ["2p_fata_morgana"], + ["2p_quests_triumph"], + ["2p_meeting_of_minds"], + ["2p_shrine_of_excellion"], + ["2p_titan_fall"], + ["2p_tranquilitys_end"], + ["2p_outer_reaches"], + ["2p_emerald_river"], + ["2p_blood_river"] + ] + rules = "1st map after veto (A-B-A-B etc) A is Top in Grid. After looserpick" + } + westerners-invitational { + name = "Westerners Invitational" + ownerSteamId = "76561198137977374" + raceCount = 2 + maps = [ + ["2p_battle_marshes"], + ["2p_fallen_city_[Rem]"], + ["2p_blood_river_[Rem]"], + ["2p_quests_triumph"], + ["2p_meeting_of_minds"], + ["2p_shrine_of_excellion_[Rem]"], + ["2p_gavrix_imperial_ale"], + ["2p_tranquilitys_end_[Rem]"], + ["2p_outer_reaches"] + ] + rules = "In case of a BO3, 3 maps must be left after the banning. Further the players must strike out a map each to decide the first map they play versus each other, the losers pick the next map (losers pick). In case of a BO5, 5 maps are needed." + } + winter_adventures { + name = "Winter adventures" + ownerSteamId = "76561198137977374" + raceCount = 3 + maps = [ + ["6p_ruined_greatway"], + ["6p_fury_island"], + ["6p_irridene"], + ["6p_testing_grounds"], + ["6p_paynes_retribution"], + ["6p_shakun_coast"], + ["6p_paynes_retribution"], + ["6p_gear"] + ] + rules = "Капитаны команд по очереди вычеркивают карты из маппула, пока их не останется 3-ри. На этих картах проходят все матчи встречи. Затем капитаны вычеркивают выбранные карты, пока не останется только одна, на которой и играется первый матч встречи. Выбор оставшихся карт встречи определяется правом лузерпика - проигравший определяет карту из оставшихся. " + } + world-championship-de-cup { + name = "Dawn of War World Championship - Definitive Edition" + ownerSteamId = "76561198101420451" + raceCount = 3 + maps = [ + ["2p_battle_marshes"], + ["2p_blood_river"], + ["2p_fallen_city"], + ["2p_gavrix_imperial_ale"], + ["2p_meeting_of_minds"], + ["2p_outer_reaches"], + ["2p_quests_triumph"], + ["2p_shrine_of_excellion"], + ["2p_deadly_fun_archeology"], + ["2p_titan_fall"], + ["2p_tranquilitys_end"] + ] + rules = "Group Stage: Best of 3 (BO3) matches — or Best of 1 (BO1), depending on total player count. Playoffs: Double-Elimination Bracket with: • Winners Bracket (BO3) • Losers Bracket (BO1) • Losers Final (BO3) • Grand Final (BO5); Grand Final Advantage: Winner of the Winners Bracket bans the first map, then picks the first map to play." + } + world_championship_2024 { + name = "World championsip 2024" + ownerSteamId = "76561198101420451" + 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_meeting_of_minds"], + ["2p_bloody_hell"], + ["2"], + ["2p_blood_river_[Rem]"], + ["2p_outer_reaches"] + ] + rules = "1) One player bans a map, and then the other player bans a map until there is only 3 maps left (5 if it’s a BO5, 1 if it’s a BO1) 2) Then players write down the maps that are not banned (in lobby) 3) Then the players write down a ban each (in lobby) to decide the first map (decider map) 4) The player that lost the first map, now gets to choose a map from the last 2 unbanned 5) If it goes to a third game, the last game will be on the last map that’s not played yet" + } + zikipchik { + name = "Zikipchik decider" + ownerSteamId = "76561199383122929" + raceCount = 2 + description = true + maps = [ + ["2p_consecrated_gardens_5p_BM"], + ["2p_battle_marshes"], + ["2p_blood_river_[Rem]"], + ["2p_deadly_fun_archeology"], + ["2p_fallen_city_[Rem]"], + ["2p_fata_morgana_[Rem]"], + ["2p_meeting_of_minds"], + ["2p_outer_reaches"], + ["2p_quests_triumph"], + ["2p_shrine_of_excellion_[Rem]"], + ["2p_sugaroasis"], + ["2p_titan_fall_[Rem]"], + ["2p_tranquilitys_end_[Rem]"] + ] + rules = " " + } +} diff --git a/conf/routes b/conf/routes index 76685fc..a3ea90e 100644 --- a/conf/routes +++ b/conf/routes @@ -21,5 +21,9 @@ DELETE /decider/admin/deciders/:key controllers.AdminController.deciderDel GET /decider/:name controllers.HomeController.index(name) +# Картинки карт из каталога maps.imagesDir. +# ВАЖНО: маршрут должен идти ДО /assets/*file, иначе его перехватит Assets. +GET /assets/images/maps/:file controllers.MapImageController.image(file) + # Map static resources from the /public folder to the /assets URL path GET /assets/*file controllers.Assets.at(path="/public", file)