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
import actors.UserActor.{BanMapMessage, GetName, HostLeaveLobby, LobbyFatal, Message, RefreshLobbyInfo, SecondPlayerLeaveLobby, SendBanMapMessage, SendMessage, SetUserAsHost, SetUserAsObs, SetUserAsSecondPlayer}
import akka.actor.{Actor, ActorLogging, ActorRef}
import actors.UserActor._
import akka.actor.{Actor, ActorRef}
import akka.event.LoggingReceive
import com.typesafe.scalalogging.LazyLogging
import akka.pattern.ask
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.immutable.{HashSet, Queue}
import scala.concurrent.Await
import scala.collection.immutable.HashSet
import scala.concurrent.duration._
import scala.util.Try
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
* 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()
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 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] = {
val configMaps = config.getStringList("maps").asScala
val configMaps = config.getList(s"deciders.$deciderName.maps").asScala
configMaps.map(e => {
DeciderMap(e)
val mapConfig = e.unwrapped().asInstanceOf[util.ArrayList[String]].asScala
DeciderMap(mapConfig.head, mapConfig.tail.headOption)
}).toSet
}
hostUser.actorRef.tell(
SetUserAsHost(LobbyInfo(
UserInfo(hostUser.name,firstPlayerReady),
UserInfo(hostUser.name, firstPlayerReady),
UserInfo("", secondPlayerReady),
self.path.name,
deciderName,
status.toString(),
playerTurn,
lobbyType.toString(),
@ -69,49 +76,53 @@ class LobbieActor(hostUser: LobbyUser) extends Actor with LazyLogging {
// notify watchers
logger.info(s"Ban map $mapName by ${sender.path.name}")
if(status == Draft()){
if((playerTurn == 1 && sender == host.actorRef) ||
(playerTurn == 2 && secondPlayer.exists(_.actorRef == sender))){
if (status == Draft()) {
if ((playerTurn == 1 && sender == host.actorRef) ||
(playerTurn == 2 && secondPlayer.exists(_.actorRef == sender))) {
mapsLobby.find(p => p.map == mapName) match {
case Some(map) =>
if(!map.isBanned){
if (!map.isBanned) {
map.isBanned = true
if (playerTurn== 1) playerTurn = 2 else playerTurn = 1
if(mapsLobby.count(_.isBanned == false) == 3 && lobbyType == Last3() ||
mapsLobby.count(_.isBanned == false) == 4 && lobbyType == Last4() ||
mapsLobby.count(_.isBanned == false) == 5 && lobbyType == Last5() ||
mapsLobby.count(_.isBanned == false) == 1 && lobbyType == LooserPick()){
if (playerTurn == 1) playerTurn = 2 else playerTurn = 1
if (isFinish) {
status = Finish()
}
users.foreach(_ ! RefreshLobbyInfo(getLobbyInfoResponse))
val senderName: String = if (sender == host.actorRef) {
host.name
} else secondPlayer.get.name
users.foreach(_ ! SendBanMapMessage(BanMapMessage(senderName, mapName)))
refreshAndBanMap(mapName)
} else {
logger.warn(s"User ban map $mapName, but map already banned")
}
case None =>
logger.error(s"Map $mapName not exist")
}
} else{
} else {
logger.warn(s"Player ${sender.path.name} ban map, but turn is $playerTurn")
}
} else {
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 =>
// notify watchers
var readyPlayer = sender()
if(readyPlayer == hostUser.actorRef){
if (readyPlayer == hostUser.actorRef) {
firstPlayerReady = true
} else if (secondPlayer.exists(sp => sp.actorRef == readyPlayer)) {
secondPlayerReady = true
}
if(firstPlayerReady && secondPlayerReady){
status = Draft()
if (firstPlayerReady && secondPlayerReady) {
status = SelectRace()
}
users.foreach(_ ! RefreshLobbyInfo(getLobbyInfoResponse))
@ -119,7 +130,7 @@ class LobbieActor(hostUser: LobbyUser) extends Actor with LazyLogging {
case SetNotReady =>
// notify watchers
var notReadyPlayer = sender()
if(notReadyPlayer == hostUser.actorRef){
if (notReadyPlayer == hostUser.actorRef) {
firstPlayerReady = false
} else if (secondPlayer.exists(sp => sp.actorRef == notReadyPlayer)) {
secondPlayerReady = false
@ -128,14 +139,15 @@ class LobbieActor(hostUser: LobbyUser) extends Actor with LazyLogging {
case ChangeLobbyType(lobbyTypeNew) =>
lobbyTypeNew match {
case "last1" =>
lobbyType = Last1()
case "last3" =>
lobbyType = Last3()
case "last4" =>
lobbyType = Last4()
case "last5" =>
lobbyType = Last5()
case "looserpick" =>
lobbyType = LooserPick()
case "last7" =>
lobbyType = Last7()
}
users.foreach(_ ! RefreshLobbyInfo(getLobbyInfoResponse))
@ -150,7 +162,7 @@ class LobbieActor(hostUser: LobbyUser) extends Actor with LazyLogging {
case MessageForLobby(message) =>
if (sender() == host.actorRef) {
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)))
}
case JoinLobbyAsPlayer(sp) =>
@ -171,16 +183,16 @@ class LobbieActor(hostUser: LobbyUser) extends Actor with LazyLogging {
sender ! SetUserAsObs(getLobbyInfoResponse)
case LeaveLobby =>
users = users - sender
if(host.actorRef == sender()){
if (host.actorRef == sender()) {
users.foreach(_ ! HostLeaveLobby)
context.stop(self)
}else if(secondPlayer.exists(_.actorRef == sender())){
} else if (secondPlayer.exists(_.actorRef == sender())) {
secondPlayerReady = false
secondPlayer = None
if(status == Draft()){
if (status == Draft() || status == SelectRace()) {
users.foreach(_ ! SecondPlayerLeaveLobby)
context.stop(self)
}else {
} else {
users.foreach(_ ! RefreshLobbyInfo(getLobbyInfoResponse))
}
}
@ -196,21 +208,38 @@ class LobbieActor(hostUser: LobbyUser) extends Actor with LazyLogging {
private def getLobbyInfoResponse: LobbyInfo = {
val user2Name = secondPlayer.map(_.name).getOrElse("")
LobbyInfo(
UserInfo(host.name, firstPlayerReady),
UserInfo(user2Name, secondPlayerReady),
UserInfo(host.name, firstPlayerReady, firstPlayerSelectedRaces),
UserInfo(user2Name, secondPlayerReady, secondPlayerSelectedRaces),
self.path.name,
deciderName,
status.toString(),
playerTurn,
lobbyType.toString(),
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 MapsUpdate(maps: Set[String])
case class CreateLobby(userName: String)
case class CreateLobby(userName: String, deciderName: String)
case class JoinLobbyAsPlayer(lobbyUser: LobbyUser)
@ -228,13 +257,18 @@ case class MessageForLobby(message: String)
case class ChangeLobbyType(lobbyType: String)
case class ChangeIsNecronSelected(isSelected: Boolean)
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,
user2Info: UserInfo,
lobbyActorName: String,
deciderName: String,
status: String,
playerTurn: BigDecimal,
selectedType: String,
@ -243,12 +277,21 @@ case class LobbyInfo(user1Info: UserInfo,
class LobbyStatus
sealed case class NotStarted() extends LobbyStatus
sealed case class SelectRace() extends LobbyStatus
sealed case class Draft() extends LobbyStatus
sealed case class Finish() extends LobbyStatus
class LobbyType
sealed case class Last1() extends LobbyType
sealed case class Last3() extends LobbyType
sealed case class Last4() 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()
def receive: Receive = LoggingReceive {
case CreateLobby(hostName) =>
case CreateLobby(hostName, deciderName) =>
val hostActorRef = sender
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}")
lobbyActor.tell(WatchLobby("watchIt"), hostActorRef)
case JoinLobbyByActorName(lobbyName, userName) =>

View File

@ -13,21 +13,27 @@ import scala.concurrent.duration.DurationInt
import scala.util.{Failure, Success}
class UserActor(out: ActorRef,
userParentActor: ActorRef) extends Actor with LazyLogging {
userParentActor: ActorRef,
ip: String) extends Actor with LazyLogging {
implicit val timeout: Timeout = 3.seconds
val ipAddress: String = ip
var lobbieActor: Option[ActorRef] = None
implicit val mapJson: Writes[DeciderMap] = new Writes[DeciderMap] {
def writes(deciderMap: DeciderMap): JsValue = {
Json.obj(
"map" -> deciderMap.map,
"description" -> deciderMap.description,
"isBanned" -> deciderMap.isBanned
)
}
}
implicit val selectedRacesWrites: OWrites[SelectedRaces] = Json.writes[SelectedRaces]
implicit val userInfoWrites: OWrites[UserInfo] = Json.writes[UserInfo]
implicit val lobbyWrites: Writes[LobbyInfo] = new Writes[LobbyInfo]{
@ -35,6 +41,7 @@ class UserActor(out: ActorRef,
"user1Info" -> lobby.user1Info,
"user2Info" -> lobby.user2Info,
"lobbyActorName" -> lobby.lobbyActorName,
"deciderName" -> lobby.deciderName,
"status" -> lobby.status,
"playerTurn" -> lobby.playerTurn,
"selectedType" -> lobby.selectedType,
@ -53,6 +60,7 @@ class UserActor(out: ActorRef,
"user1Info" -> lobby.user1Info,
"user2Info" -> lobby.user2Info,
"lobbyActorName" -> lobby.lobbyActorName,
"deciderName" -> lobby.deciderName,
"status" -> lobby.status,
"playerTurn" -> lobby.playerTurn,
"selectedType" -> lobby.selectedType,
@ -127,7 +135,8 @@ class UserActor(out: ActorRef,
logger.debug(s"Set user name: $name for actor ${this.self}")
case Some("createDecider") =>
LobbiesActor.actor ! CreateLobby(name)
val deciderName = (json \ "deciderName").as[String]
LobbiesActor.actor ! CreateLobby(name, deciderName)
case Some("leaveDecider") =>
lobbieActor.foreach(lobby => lobby ! LeaveLobby)
@ -163,6 +172,12 @@ class UserActor(out: ActorRef,
val map = (json \ "map").as[String]
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") =>
logger.debug("Get all lobby request")
(LobbiesActor.actor ? GetAllLobbies).mapTo[List[RefreshLobbyInfo]] onComplete {
@ -192,10 +207,11 @@ class UserParentActor(actorSystem: ActorSystem) extends Actor with ActorLogging
import UserParentActor._
override def receive: Receive = LoggingReceive {
case Create(id, out) =>
val child: ActorRef = actorSystem.actorOf(Props(classOf[UserActor], out, self), s"userActor-$id")
case Create(id, ipAddress, out) =>
val child: ActorRef = actorSystem.actorOf(Props(classOf[UserActor], out, self, ipAddress), s"userActor-$id")
sender() ! child
case GetAllUsers =>
System.out.print("12123" + context.children)
sender() ! context.children
}
}
@ -203,7 +219,7 @@ class UserParentActor(actorSystem: ActorSystem) extends Actor with ActorLogging
object UserParentActor {
case class Create(id: String, out: ActorRef)
case class Create(id: String, ipAddress: String, out: ActorRef)
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_alvarus": "Alvarus (6)",
"6p_kaurav_city": "Kaurav City (6)",
"6p_snowblind": "Snowblind (6)",
"6p_ruined_greatway": "Ruined Greatway (6)",
"8p_forbidden_jungle": "Forbidden Jungle (8)",
"8p_rhean_jungle": "Rhean Jungle (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 = ""
window.onload = function() {
var raceCount = $("#raceCount").html()
var existAdditionalRaces = $("#existAdditionalRaces").html() === "true"
var isMuted = false;
var isDraftStarted = false;
window.onload = function () {
'use strict';
@ -28,26 +34,23 @@ window.onload = function() {
"click .title": "check" // Обработчик клика на кнопке "Проверить"
},
joinAsPlayer: function () {
this.elCount = "azaz" + getRandomInt(100000000);
this.render();
},
render: function() {
render: function () {
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;">' +
'</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;">' +
'</button>';
return memo + '<tr>\n' +
' <td>'+ lobby.user1Info.name.substring(0, 20) +'</td>\n' +
' <td>'+ lobby.user2Info.name.substring(0, 20) +'</td>\n' +
' <td>'+ lobby.status +'</td>\n' +
' <td>' + lobby.user1Info.name.substring(0, 20) + '</td>\n' +
' <td>' + lobby.user2Info.name.substring(0, 20) + '</td>\n' +
' <td>' + lobby.status + '</td>\n' +
' <td>' + joinButton + ' ' + observerButton +
' </td>' +
'</tr>';
@ -74,14 +77,16 @@ window.onload = function() {
$("#lobbiesList").append(lobbyList.$el);
lobbyList.render();
$("#createDecider").click(function(event) {
$("#createDecider").click(function (event) {
event.preventDefault();
isObserver = false;
ws.send(JSON.stringify({
type: "createDecider"
type: "createDecider",
deciderName: $("#deciderName").html()
}));
});
$("#submitmsg").click(function(event) {
$("#submitmsg").click(function (event) {
sendMessage();
});
$("#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();
ws.send(JSON.stringify({
type: "leaveDecider"
@ -101,24 +115,34 @@ window.onload = function() {
$(".navbar").show();
});
if($.cookie('user')=== undefined){
var result = prompt("Enter nickname: ", );
$.cookie('user', result, { expires: 1337 });
function requestName() {
if ($.cookie('user') === undefined || $.cookie('user') === "null" || $.cookie('user') === "") {
var result = prompt("Enter nickname: ",);
$.cookie('user', result, {expires: 1337});
userName = result;
}else{
requestName();
} else {
userName = $.cookie('user');
}
}
requestName();
$("#playerName").html(userName);
ws = new WebSocket($("body").data("ws-url"));
ws.onmessage = function(event) {
ws.onmessage = function (event) {
var message;
message = JSON.parse(event.data);
switch (message.type) {
case "refreshLobby":
if (message.lobby.status === "SelectRace()" && (!isObserver)) {
isDraftStarted = false;
renderRaces();
} else {
renderMaps(message.lobby.maps, message.lobby.status);
}
renderPlayersAndStats(message.lobby);
break;
case "switchToLobby":
@ -132,7 +156,7 @@ window.onload = function() {
break;
case "sendMapBanMessage":
console.log(message);
addBanMapMessageToChat(message.message);
handleMapBanEvent(message.message);
break;
case "lobbyError":
console.log(message);
@ -146,6 +170,7 @@ window.onload = function() {
default:
}
};
ws.onopen = function () {
ws.send(JSON.stringify({
type: "userName",
@ -156,7 +181,7 @@ window.onload = function() {
}));
}
var timerId = setInterval(() =>
setInterval(() =>
ws.send(JSON.stringify({
type: "getLobbies"
})), 2000);
@ -164,9 +189,9 @@ window.onload = function() {
function changeNick() {
result = prompt("Введите ник", $.cookie('user'));
if(result == null) return;
if (result == null) return;
result = result.substr(0, 20);
$.cookie('user', result, { expires: 7 });
$.cookie('user', result, {expires: 7});
ws.send(JSON.stringify({
type: "userName",
name: $.cookie('user')
@ -177,7 +202,6 @@ function changeNick() {
}
// -----ф-ии прихода с сервера
function switchToLobby(maps, status) {
@ -199,6 +223,125 @@ function disconnectLobby(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) {
var resHtml = "";
@ -206,23 +349,47 @@ function renderMaps(maps, lobbyStatus) {
var banHtml = "";
var banClass = "";
if(map.isBanned){
if (map.isBanned) {
banHtml = "<div class = 'banX'>&#x2715;</div>";
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+'\')">'+
'<div class="mapImageAndText '+banClass+'">' +
'<img class="img-rounded center-block" style="width:128px;" src="/assets/images/maps/'+encodeURI(map.map)+'.jpg">' +
'</div>'+convertMapName(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 + '">' +
'<img class="img-rounded center-block" ' + title + ' style="width:128px;" src="/assets/images/maps/' + encodeURI(map.map) + '.jpg">' +
'</div>' + convertMapName(map.map) +
banHtml + '</button></div>';
})
$("#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) {
var resHtml = "<center>";
var player1ReadyBtn = "";
@ -231,30 +398,30 @@ function renderPlayersAndStats(lobby) {
var notReadyImg = "<img src='/assets/images/buttons/isNotAuto.png' class='readyImg'/>";
var warningClass = (lobby.user2Info.name !== "") ? "warningButton" : "";
if(lobby.user1Info.name === userName){
if(lobby.user1Info.isReady){
player1ReadyBtn = "<button class='btn btn-default' onclick='setNotReady()'>"+readyImg+" Ready "+readyImg+"</button>"
}else{
player1ReadyBtn = "<button class='btn btn-default "+warningClass+"' onclick='setReady()'>"+notReadyImg+" Not ready "+notReadyImg+"</button>"
if (lobby.user1Info.name === userName) {
if (lobby.user1Info.isReady) {
player1ReadyBtn = "<button class='btn btn-default' onclick='setNotReady()'>" + readyImg + " Ready " + readyImg + "</button>"
} else {
player1ReadyBtn = "<button class='btn btn-default " + warningClass + "' onclick='setReady()'>" + notReadyImg + " Not ready " + notReadyImg + "</button>"
}
}else{
if(lobby.user1Info.isReady){
} else {
if (lobby.user1Info.isReady) {
player1ReadyBtn = readyImg
}else{
} else {
player1ReadyBtn = notReadyImg
}
}
if(lobby.user2Info.name === userName){
if(lobby.user2Info.isReady){
player2ReadyBtn = "<button class='btn btn-default' onclick='setNotReady()'>"+readyImg+" Ready "+readyImg+"</button>"
}else{
player2ReadyBtn = "<button class='btn btn-default "+warningClass+"' onclick='setReady()'>"+notReadyImg+" Not ready "+notReadyImg+"</button>"
if (lobby.user2Info.name === userName) {
if (lobby.user2Info.isReady) {
player2ReadyBtn = "<button class='btn btn-default' onclick='setNotReady()'>" + readyImg + " Ready " + readyImg + "</button>"
} else {
player2ReadyBtn = "<button class='btn btn-default " + warningClass + "' onclick='setReady()'>" + notReadyImg + " Not ready " + notReadyImg + "</button>"
}
} else {
if(lobby.user2Info.isReady){
if (lobby.user2Info.isReady) {
player2ReadyBtn = readyImg
}else{
} else {
player2ReadyBtn = notReadyImg
}
}
@ -266,68 +433,86 @@ function renderPlayersAndStats(lobby) {
}
var lobbyTypeText = "";
var last1Selected = "";
var last3Selected = "";
var last4Selected = "";
var last5Selected = "";
var looserPickSelected = "";
var last7Selected = "";
var isNecronsSelected = "";
if (lobby.isNecrons) isNecronsSelected = "checked";
switch (lobby.selectedType) {
case "Last1()":
looserPickSelected = "selected";
lobbyTypeText = "Play on last map";
break;
case "Last3()":
last3Selected = "selected";
lobbyTypeText = "Play on last 3 maps";
break;
case "Last4()":
last4Selected = "selected";
lobbyTypeText = "Play on last 4 maps";
break;
case "Last5()":
last5Selected = "selected";
lobbyTypeText = "Play on last 5 maps";
break;
case "LooserPick()":
looserPickSelected = "selected";
lobbyTypeText = "Play on last map";
case "Last7()":
last7Selected = "selected";
lobbyTypeText = "Play on last 7 maps";
break;
}
console.log(lobby.status);
switch (lobby.status) {
case "NotStarted()":
resHtml = "<div style='float: left; padding-top: 10px;'> <div>" + lobby.user1Info.name + ": " + player1ReadyBtn +"</div><br/>"
if(lobby.user2Info.name !== ""){
resHtml = "<div style='float: left; padding-top: 10px;'> <div>" + lobby.user1Info.name + ": " + player1ReadyBtn + "</div><br/>"
if (lobby.user2Info.name !== "") {
var kickBtn = ""
if(lobby.user1Info.name === userName){
if (lobby.user1Info.name === userName) {
kickBtn = " - <button class='btn btn-danger' onclick='kickSecondPlayer()'>Kick</button>";
}
resHtml += "<div>" + lobby.user2Info.name + ": " + player2ReadyBtn + kickBtn + "</div>"
}else{
} else {
resHtml += "<div>waiting 2-nd player...</div>"
}
var disabledText = "";
if(lobby.user1Info.name !== userName){
if (lobby.user1Info.name !== userName) {
disabledText = "disabled";
}
resHtml += "<br/><select class=\"form-control\" id = 'deciderOption' onChange='changeLobbyType()' "+disabledText+" >" +
"<option "+last3Selected+" value = 'last3'>Play on last 3 maps (BO3)</option>" +
"<option "+last5Selected+" value = 'last5'>Play on last 5 maps (BO5)</option>" +
"</select>"
//TODO: here from config
resHtml += "<br/><select class=\"form-control\" id = 'deciderOption' onChange='changeLobbyType()' " + disabledText + " >" +
"<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>";
break;
case "SelectRace()":
resHtml = "<center>" + lobby.user1Info.name + " vs " + lobby.user2Info.name + " - " + lobbyTypeText + "<br/> players select races</center>";
break;
case "Draft()":
console.log(lobby.turn);
if (!isDraftStarted) {
isDraftStarted = true;
}
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;
case "Finish()":
var lastMaps = _.filter(lobby.maps, function (map){
return map.isBanned === false;
});
resHtml = "<center><b>";
_.forEach(lastMaps,function (map){
resHtml += convertMapName(map.map) + "; "
})
resHtml += "</b></center>";
renderFinish(lobby.maps, lobby.user1Info, lobby.user2Info);
break;
}
resHtml = resHtml + "</center>"
@ -335,40 +520,67 @@ function renderPlayersAndStats(lobby) {
$("#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 chatBox = $("#chatbox");
chatBox.append(messageHtml);
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 chatBox = $("#chatbox");
chatBox.append(messageHtml);
chatBox.scrollTop(40000);
}
function banMap(map){
function banMap(map) {
ws.send(JSON.stringify({
type: "banMap",
map: map
}));
}
function setReady(){
function setReady() {
ws.send(JSON.stringify({
type: "setReady"
}));
}
function setNotReady(){
function setNotReady() {
ws.send(JSON.stringify({
type: "setNotReady"
}));
}
function changeLobbyType(){
function changeLobbyType() {
var lobbyType = $("#deciderOption").val();
ws.send(JSON.stringify({
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({
type: "joinDecider",
lobbyActorName: actorName
}));
}
function observerDecider(actorName){
function observerDecider(actorName) {
isObserver = true;
ws.send(JSON.stringify({
type: "observerDecider",
lobbyActorName: actorName
}));
}
function sendMessage(){
function sendMessage() {
var userInput = $("#usermsg");
var message = userInput.val();
userInput.val("");
if(message !== ""){
if (message !== "") {
ws.send(JSON.stringify({
type: "sendMessage",
message: message
@ -402,13 +624,17 @@ function sendMessage(){
}
}
function kickSecondPlayer(){
function kickSecondPlayer() {
ws.send(JSON.stringify({
type: "kickSecondPlayer"
}));
}
function convertMapName (techMapName) {
var mapName = techMapName.replace("2p_", "").replaceAll("_", " ") + " (2)";
function convertMapName(techMapName) {
var mapPlayerSize = techMapName.charAt(0);
var mapName = techMapName.replace(mapPlayerSize + "p_", "").replaceAll("_", " ") +
" (" + mapPlayerSize + ")";
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 браузеры */
}
.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{
margin: 4px;
position:relative;
@ -29,6 +36,39 @@
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 {
0% {
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
import actors.UserParentActor.GetAllUsers
import actors._
import akka.NotUsed
import akka.actor._
@ -9,35 +10,39 @@ import akka.stream._
import akka.stream.scaladsl._
import akka.util.Timeout
import com.typesafe.config.ConfigFactory
import javax.inject._
import org.reactivestreams.Publisher
import play.api.libs.json._
import play.api.mvc._
import scala.concurrent.duration._
import scala.concurrent.{ExecutionContext, Future}
import scala.concurrent.{Await, ExecutionContext, Future}
/**
* This class creates the actions and the websocket needed.
*/
@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 ec: ExecutionContext = defaultExecutionContext
implicit val timeout = Timeout(100.millis)
// Use a direct reference to SLF4J
private val logger = org.slf4j.LoggerFactory.getLogger("controllers.HomeController")
val userParentActor: ActorRef = homeController.userParentActor
// Home page that renders template
def viewAllLobbies() = Action { implicit request =>
def viewAllUsers() = Action { implicit request =>
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 =>
ConfigFactory.invalidateCaches()

View File

@ -9,12 +9,17 @@ import akka.pattern.ask
import akka.stream._
import akka.stream.scaladsl._
import akka.util.Timeout
import com.typesafe.config.ConfigFactory
import org.reactivestreams.Publisher
import play.api.libs.json._
import play.api.mvc._
import scala.concurrent.duration._
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.
@ -25,6 +30,8 @@ class HomeController @Inject()(cc: ControllerComponents) extends AbstractControl
implicit val actorSystem: ActorSystem = ActorSystem()
implicit val ec: ExecutionContext = defaultExecutionContext
val config = ConfigFactory.load()
// Use a direct reference to SLF4J
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))
// 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}")
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.
*/
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()
// 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...
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.
* @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.
val userActorFuture = {
implicit val timeout = Timeout(100.millis)
(userParentActor ? UserParentActor.Create(name, webSocketOut)).mapTo[ActorRef]
(userParentActor ? UserParentActor.Create(name, remoteAddress, webSocketOut)).mapTo[ActorRef]
}
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>
<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>
<head>
<title>Soulstorm tournament decider</title>
<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")">
<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/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/jquery-cookie/jquery.cookie.js")"></script>
<script type='text/javascript' src='@routes.Assets.at("javascripts/index.js?030721")'></script>
<script type='text/javascript' src='@routes.Assets.at("javascripts/index.js?170823")'></script>
</head>
<body data-ws-url="@routes.HomeController.ws.webSocketURL()">
<div class="navbar navbar-inverse navbar-fixed-top">
@ -37,6 +44,7 @@
<div id = "decider" style="display:none">
<button id = "exit" class="btn btn-primary">Exit</button>
<button id = "mute" class="btn btn-primary">🔊</button>
<br/>
<div id = "mapList" class="container"></div>
<div id = "playersStatsList"></div>
@ -51,14 +59,9 @@
</div>
</div>
<div style="clear: both"></div>
<div><i><b>Правила турнира Big Boss of Summer 2021</b><br/>
<p>Выбор карт в БО3 и БО5 осуществляется без лузерпиков, с тремя и пятью десайдерами соответственно.
Игроки вычеркивают по очереди карты из маппула, пока их не останется 3 (для БО3) или 5 (для БО5).
На этих картах и проходят все матчи встречи. Затем игроки вычеркивают выбранные карты, пока не останется только одна, на которой и играется первый матч встречи.
Тот, кто первым начал вычеркивать из всего маппула, уступает оппоненту право вычеркивания первой карты из оставшихся карт-десайдеров.
Во втором и последующем матчах проигравший выбирает карту из числа выбранных 3 (5) десайдеров.</p>
<p>В суперфинале турнира оба игрока вычеркивают по две карты. После этого из оставшихся 9 карт можно брать любые, выбирает их проигравший в предыдущем матче.
Первый лузер-пик - за игроком из нижней сетки, который начинает с -1 очком.</p>
<div><i><b>Правила турнира "@deciderHumanName"</b><br/>
<p>@rules</p>
<br>
</i>
</div>

View File

@ -6,6 +6,7 @@ lazy val root = (project in file(".")).enablePlugins(PlayScala)
scalaVersion := "2.12.8"
cancelable in Global := true
// scalaz-bintray resolver needed for specs2 library
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" % "backbonejs" % "1.3.3"
libraryDependencies += guice
libraryDependencies += "com.typesafe.scala-logging" %% "scala-logging" % "3.9.2"

View File

@ -2,15 +2,204 @@
# https://www.playframework.com/documentation/latest/AllowedHostsFilter
# Allow requests to localhost:9000.
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.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_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_quests_triumph",
"2p_shrine_of_excellion_[Rem]",
@ -20,4 +209,564 @@ maps = ["2p_battle_marshes",
"2p_emerald_river",
"2p_deadly_fun_archeology",
"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>
<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>
<conversionRule conversionWord="coloredLevel" converterClass="play.api.libs.logback.ColoredLevel" />
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%coloredLevel %logger{15} - [%marker] %message%n%xException{10}</pattern>
<pattern>%coloredLevel %logger{15} - %message%n%xException{10}</pattern>
</encoder>
</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="akka" level="DEBUG"/>
<logger name="play" level="INFO" />
<logger name="actors" level="DEBUG"/>
<logger name="com.gargoylesoftware.htmlunit.javascript" level="OFF" />
<!-- controllers -->
<logger name="controllers" level="DEBUG"/>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<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">
<appender-ref ref="STDOUT"/>
<appender-ref ref="FILE"/>
<appender-ref ref="ASYNCFILE" />
<appender-ref ref="ASYNCSTDOUT" />
</root>
<shutdownHook class="ch.qos.logback.core.hook.DelayingShutdownHook"/>
</configuration>

View File

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