Moved min/default/max player limit, spectator limit, score limit and blank card limit to pyx.properties
This commit is contained in:
parent
8e1c5e805b
commit
631fe14d12
|
@ -32,10 +32,11 @@ created for the user now.
|
|||
<%@ page import="com.google.inject.Key" %>
|
||||
<%@ page import="com.google.inject.TypeLiteral" %>
|
||||
<%@ page import="javax.servlet.http.HttpSession" %>
|
||||
<%@ page import="net.socialgamer.cah.CahModule.AllowBlankCards" %>
|
||||
<%@ page import="net.socialgamer.cah.RequestWrapper" %>
|
||||
<%@ page import="net.socialgamer.cah.StartupUtils" %>
|
||||
<%@ page import="net.socialgamer.cah.data.GameOptions" %>
|
||||
<%@ page import="net.socialgamer.cah.CahModule" %>
|
||||
<%@ page import="net.socialgamer.cah.CahModule.*" %>
|
||||
<%
|
||||
// Ensure a session exists for the user.
|
||||
@SuppressWarnings("unused")
|
||||
|
@ -425,9 +426,9 @@ boolean allowBlankCards = injector.getInstance(Key.get(new TypeLiteral<Boolean>(
|
|||
<label id="score_limit_template_label" for="score_limit_template">Score limit:</label>
|
||||
<select id="score_limit_template" class="score_limit">
|
||||
<%
|
||||
for (int i = GameOptions.MIN_SCORE_LIMIT; i <= GameOptions.MAX_SCORE_LIMIT; i++) {
|
||||
for (int i = injector.getInstance(Key.get(Integer.class, MinScoreLimit.class)); i <= injector.getInstance(Key.get(Integer.class, MaxScoreLimit.class)); i++) {
|
||||
%>
|
||||
<option <%= i == GameOptions.DEFAULT_SCORE_LIMIT ? "selected='selected' " : "" %>value="<%= i %>"><%= i %></option>
|
||||
<option <%=(i == injector.getInstance(Key.get(Integer.class, DefaultScoreLimit.class))) ? "selected='selected' " : "" %>value="<%= i %>"><%= i %></option>
|
||||
<% } %>
|
||||
</select>
|
||||
<br/>
|
||||
|
@ -435,9 +436,9 @@ boolean allowBlankCards = injector.getInstance(Key.get(new TypeLiteral<Boolean>(
|
|||
<select id="player_limit_template" class="player_limit"
|
||||
aria-label="Player limit. Having more than 10 players may cause issues both for screen readers and traditional browsers.">
|
||||
<%
|
||||
for (int i = GameOptions.MIN_PLAYER_LIMIT; i <= GameOptions.MAX_PLAYER_LIMIT; i++) {
|
||||
for (int i = injector.getInstance(Key.get(Integer.class, MinPlayerLimit.class)); i <= injector.getInstance(Key.get(Integer.class, MaxPlayerLimit.class)); i++) {
|
||||
%>
|
||||
<option <%= i == GameOptions.DEFAULT_PLAYER_LIMIT ? "selected='selected' " : "" %>value="<%= i %>"><%= i %></option>
|
||||
<option <%= i == injector.getInstance(Key.get(Integer.class, DefaultPlayerLimit.class)) ? "selected='selected' " : "" %>value="<%= i %>"><%= i %></option>
|
||||
<% } %>
|
||||
</select>
|
||||
Having more than 10 players may get cramped!
|
||||
|
@ -446,9 +447,9 @@ boolean allowBlankCards = injector.getInstance(Key.get(new TypeLiteral<Boolean>(
|
|||
<select id="spectator_limit_template" class="spectator_limit"
|
||||
aria-label="Spectator limit.">
|
||||
<%
|
||||
for (int i = GameOptions.MIN_SPECTATOR_LIMIT; i <= GameOptions.MAX_SPECTATOR_LIMIT; i++) {
|
||||
for (int i = injector.getInstance(Key.get(Integer.class, MinSpectatorLimit.class)); i <= injector.getInstance(Key.get(Integer.class, MaxSpectatorLimit.class)); i++) {
|
||||
%>
|
||||
<option <%= i == GameOptions.DEFAULT_SPECTATOR_LIMIT ? "selected='selected' " : "" %>value="<%= i %>"><%= i %></option>
|
||||
<option <%= i == injector.getInstance(Key.get(Integer.class, DefaultSpectatorLimit.class)) ? "selected='selected' " : "" %>value="<%= i %>"><%= i %></option>
|
||||
<% } %>
|
||||
</select>
|
||||
Spectators can watch and chat, but not actually play. Not even as Czar.
|
||||
|
@ -486,9 +487,9 @@ boolean allowBlankCards = injector.getInstance(Key.get(new TypeLiteral<Boolean>(
|
|||
<label id="blanks_limit_label" title="Blank cards allow a player to type in their own answer.">
|
||||
Also include <select id="blanks_limit_template" class="blanks_limit">
|
||||
<%
|
||||
for (int i = GameOptions.MIN_BLANK_CARD_LIMIT; i <= GameOptions.MAX_BLANK_CARD_LIMIT; i++) {
|
||||
for (int i = injector.getInstance(Key.get(Integer.class, MinBlankCardLimit.class)); i <= injector.getInstance(Key.get(Integer.class, MaxBlankCardLimit.class)); i++) {
|
||||
%>
|
||||
<option <%= i == GameOptions.DEFAULT_BLANK_CARD_LIMIT ? "selected='selected' " : "" %>value="<%= i %>"><%= i %></option>
|
||||
<option <%= i == injector.getInstance(Key.get(Integer.class, DefaultBlankCardLimit.class)) ? "selected='selected' " : "" %>value="<%= i %>"><%= i %></option>
|
||||
<% } %>
|
||||
</select> blank white cards.
|
||||
</label>
|
||||
|
|
|
@ -59,6 +59,19 @@ pyx.game.flood_count=5
|
|||
# seconds
|
||||
pyx.game.flood_time=30
|
||||
|
||||
# Game options
|
||||
pyx.game.min_score_limit=4
|
||||
pyx.game.default_score_limit=8
|
||||
pyx.game.max_score_limit=69
|
||||
pyx.game.min_player_limit=3
|
||||
pyx.game.default_player_limit=10
|
||||
pyx.game.max_player_limit=20
|
||||
pyx.game.min_spectator_limit=0
|
||||
pyx.game.default_spectator_limit=10
|
||||
pyx.game.max_spectator_limit=20
|
||||
pyx.game.min_blank_card_limit=0
|
||||
pyx.game.default_blank_card_limit=0
|
||||
pyx.game.max_blank_card_limit=30
|
||||
|
||||
# for production use, use postgres
|
||||
#hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
|
||||
|
|
|
@ -25,6 +25,19 @@ pyx.chat.game.flood_time=${pyx.game.flood_time}
|
|||
pyx.build=${buildNumber}
|
||||
pyx.banned_nicks=${pyx.banned_nicks}
|
||||
|
||||
pyx.game.min_score_limit=${pyx.game.min_score_limit}
|
||||
pyx.game.default_score_limit=${pyx.game.default_score_limit}
|
||||
pyx.game.max_score_limit=${pyx.game.max_score_limit}
|
||||
pyx.game.min_player_limit=${pyx.game.min_player_limit}
|
||||
pyx.game.default_player_limit=${pyx.game.default_player_limit}
|
||||
pyx.game.max_player_limit=${pyx.game.max_player_limit}
|
||||
pyx.game.min_spectator_limit=${pyx.game.min_spectator_limit}
|
||||
pyx.game.default_spectator_limit=${pyx.game.default_spectator_limit}
|
||||
pyx.game.max_spectator_limit=${pyx.game.max_spectator_limit}
|
||||
pyx.game.min_blank_card_limit=${pyx.game.min_blank_card_limit}
|
||||
pyx.game.default_blank_card_limit=${pyx.game.default_blank_card_limit}
|
||||
pyx.game.max_blank_card_limit=${pyx.game.max_blank_card_limit}
|
||||
|
||||
pyx.metrics.game.enabled=${pyx.metrics.game.enabled}
|
||||
pyx.metrics.game.url_format=${pyx.metrics.game.url_format}
|
||||
pyx.metrics.round.enabled=${pyx.metrics.round.enabled}
|
||||
|
|
|
@ -37,14 +37,12 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||
|
||||
import javax.servlet.ServletContext;
|
||||
|
||||
import com.google.inject.*;
|
||||
import net.socialgamer.cah.data.GameOptions;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.hibernate.Session;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.BindingAnnotation;
|
||||
import com.google.inject.Provides;
|
||||
import com.google.inject.TypeLiteral;
|
||||
import com.google.inject.assistedinject.FactoryModuleBuilder;
|
||||
|
||||
import net.socialgamer.cah.data.GameManager;
|
||||
|
@ -427,4 +425,160 @@ public class CahModule extends AbstractModule {
|
|||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface AllowBlankCards {
|
||||
}
|
||||
|
||||
@BindingAnnotation
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface MinScoreLimit {
|
||||
}
|
||||
|
||||
@Provides
|
||||
@MinScoreLimit
|
||||
Integer provideMinScoreLimit() {
|
||||
synchronized (properties) {
|
||||
return Integer.valueOf(properties.getProperty("pyx.game.min_score_limit", "4"));
|
||||
}
|
||||
}
|
||||
|
||||
@BindingAnnotation
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface DefaultScoreLimit {
|
||||
}
|
||||
|
||||
@Provides
|
||||
@DefaultScoreLimit
|
||||
Integer provideDefaultScoreLimit() {
|
||||
synchronized (properties) {
|
||||
return Integer.valueOf(properties.getProperty("pyx.game.default_score_limit", "8"));
|
||||
}
|
||||
}
|
||||
|
||||
@BindingAnnotation
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface MaxScoreLimit {
|
||||
}
|
||||
|
||||
@Provides
|
||||
@MaxScoreLimit
|
||||
Integer provideMaxScoreLimit() {
|
||||
synchronized (properties) {
|
||||
return Integer.valueOf(properties.getProperty("pyx.game.max_score_limit", "69"));
|
||||
}
|
||||
}
|
||||
|
||||
@BindingAnnotation
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface MinPlayerLimit {
|
||||
}
|
||||
|
||||
@Provides
|
||||
@MinPlayerLimit
|
||||
Integer provideMinPlayerLimit() {
|
||||
synchronized (properties) {
|
||||
return Integer.valueOf(properties.getProperty("pyx.game.min_player_limit", "3"));
|
||||
}
|
||||
}
|
||||
|
||||
@BindingAnnotation
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface DefaultPlayerLimit {
|
||||
}
|
||||
|
||||
@Provides
|
||||
@DefaultPlayerLimit
|
||||
Integer provideDefaultPlayerLimit() {
|
||||
synchronized (properties) {
|
||||
return Integer.valueOf(properties.getProperty("pyx.game.default_player_limit", "10"));
|
||||
}
|
||||
}
|
||||
|
||||
@BindingAnnotation
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface MaxPlayerLimit {
|
||||
}
|
||||
|
||||
@Provides
|
||||
@MaxPlayerLimit
|
||||
Integer provideMaxPlayerLimit() {
|
||||
synchronized (properties) {
|
||||
return Integer.valueOf(properties.getProperty("pyx.game.max_player_limit", "20"));
|
||||
}
|
||||
}
|
||||
|
||||
@BindingAnnotation
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface MinSpectatorLimit {
|
||||
}
|
||||
|
||||
@Provides
|
||||
@MinSpectatorLimit
|
||||
Integer provideMinSpectatorLimit() {
|
||||
synchronized (properties) {
|
||||
return Integer.valueOf(properties.getProperty("pyx.game.min_spectator_limit", "0"));
|
||||
}
|
||||
}
|
||||
|
||||
@BindingAnnotation
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface DefaultSpectatorLimit {
|
||||
}
|
||||
|
||||
@Provides
|
||||
@DefaultSpectatorLimit
|
||||
Integer provideDefaultSpectatorLimit() {
|
||||
synchronized (properties) {
|
||||
return Integer.valueOf(properties.getProperty("pyx.game.default_spectator_limit", "10"));
|
||||
}
|
||||
}
|
||||
|
||||
@BindingAnnotation
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface MaxSpectatorLimit {
|
||||
}
|
||||
|
||||
@Provides
|
||||
@MaxSpectatorLimit
|
||||
Integer provideMaxSpectatorLimit() {
|
||||
synchronized (properties) {
|
||||
return Integer.valueOf(properties.getProperty("pyx.game.max_spectator_limit", "20"));
|
||||
}
|
||||
}
|
||||
|
||||
@BindingAnnotation
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface MinBlankCardLimit {
|
||||
}
|
||||
|
||||
@Provides
|
||||
@MinBlankCardLimit
|
||||
Integer provideMinBlankCardLimit() {
|
||||
synchronized (properties) {
|
||||
return Integer.valueOf(properties.getProperty("pyx.game.min_blank_card_limit", "0"));
|
||||
}
|
||||
}
|
||||
|
||||
@BindingAnnotation
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface DefaultBlankCardLimit {
|
||||
}
|
||||
|
||||
@Provides
|
||||
@DefaultBlankCardLimit
|
||||
Integer provideDefaultBlankCardLimit() {
|
||||
synchronized (properties) {
|
||||
return Integer.valueOf(properties.getProperty("pyx.game.default_blank_card_limit", "0"));
|
||||
}
|
||||
}
|
||||
|
||||
@BindingAnnotation
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface MaxBlankCardLimit {
|
||||
}
|
||||
|
||||
@Provides
|
||||
@MaxBlankCardLimit
|
||||
Integer provideMaxBlankCardLimit() {
|
||||
synchronized (properties) {
|
||||
return Integer.valueOf(properties.getProperty("pyx.game.max_blank_card_limit", "30"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
/**
|
||||
* Copyright (c) 2012-2018, Andy Janata
|
||||
* All rights reserved.
|
||||
*
|
||||
* <p>
|
||||
* Redistribution and use in source and binary forms, with or without modification, are permitted
|
||||
* provided that the following conditions are met:
|
||||
*
|
||||
* <p>
|
||||
* * Redistributions of source code must retain the above copyright notice, this list of conditions
|
||||
* and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright notice, this list of
|
||||
* conditions and the following disclaimer in the documentation and/or other materials provided
|
||||
* with the distribution.
|
||||
*
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
|
||||
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||
|
@ -23,28 +23,28 @@
|
|||
|
||||
package net.socialgamer.cah.cardcast;
|
||||
|
||||
import com.google.inject.*;
|
||||
import net.socialgamer.cah.CahModule;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.BindingAnnotation;
|
||||
import com.google.inject.Provides;
|
||||
|
||||
import net.socialgamer.cah.data.GameOptions;
|
||||
|
||||
|
||||
public class CardcastModule extends AbstractModule {
|
||||
|
||||
AtomicInteger cardId = new AtomicInteger(-(GameOptions.MAX_BLANK_CARD_LIMIT + 1));
|
||||
private AtomicInteger cardId;
|
||||
private Provider<Integer> maxBlankCardLimitProvider;
|
||||
|
||||
@Override
|
||||
protected void configure() {
|
||||
maxBlankCardLimitProvider = getProvider(Key.get(Integer.class, CahModule.MaxBlankCardLimit.class));
|
||||
}
|
||||
|
||||
@Provides
|
||||
@CardcastCardId
|
||||
Integer provideCardId() {
|
||||
if (cardId == null) cardId = new AtomicInteger(-(maxBlankCardLimitProvider.get() + 1));
|
||||
return cardId.decrementAndGet();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
/**
|
||||
* Copyright (c) 2012-2018, Andy Janata
|
||||
* All rights reserved.
|
||||
*
|
||||
* <p>
|
||||
* Redistribution and use in source and binary forms, with or without modification, are permitted
|
||||
* provided that the following conditions are met:
|
||||
*
|
||||
* <p>
|
||||
* * Redistributions of source code must retain the above copyright notice, this list of conditions
|
||||
* and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright notice, this list of
|
||||
* conditions and the following disclaimer in the documentation and/or other materials provided
|
||||
* with the distribution.
|
||||
*
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
|
||||
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||
|
@ -23,54 +23,26 @@
|
|||
|
||||
package net.socialgamer.cah.data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.hibernate.Session;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
|
||||
import net.socialgamer.cah.CahModule.AllowBlankCards;
|
||||
import net.socialgamer.cah.CahModule.GamePermalinkUrlFormat;
|
||||
import net.socialgamer.cah.CahModule.RoundPermalinkUrlFormat;
|
||||
import net.socialgamer.cah.CahModule.ShowGamePermalink;
|
||||
import net.socialgamer.cah.CahModule.ShowRoundPermalink;
|
||||
import net.socialgamer.cah.CahModule.UniqueId;
|
||||
import net.socialgamer.cah.Constants.AjaxResponse;
|
||||
import net.socialgamer.cah.Constants.BlackCardData;
|
||||
import net.socialgamer.cah.Constants.ErrorCode;
|
||||
import net.socialgamer.cah.Constants.GameInfo;
|
||||
import net.socialgamer.cah.Constants.GamePlayerInfo;
|
||||
import net.socialgamer.cah.Constants.GamePlayerStatus;
|
||||
import net.socialgamer.cah.Constants.GameState;
|
||||
import net.socialgamer.cah.Constants.LongPollEvent;
|
||||
import net.socialgamer.cah.Constants.LongPollResponse;
|
||||
import net.socialgamer.cah.Constants.ReturnableData;
|
||||
import net.socialgamer.cah.Constants.WhiteCardData;
|
||||
import net.socialgamer.cah.CahModule.*;
|
||||
import net.socialgamer.cah.Constants.*;
|
||||
import net.socialgamer.cah.cardcast.CardcastDeck;
|
||||
import net.socialgamer.cah.cardcast.CardcastService;
|
||||
import net.socialgamer.cah.data.GameManager.GameId;
|
||||
import net.socialgamer.cah.data.QueuedMessage.MessageType;
|
||||
import net.socialgamer.cah.metrics.Metrics;
|
||||
import net.socialgamer.cah.task.SafeTimerTask;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.hibernate.Session;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -93,45 +65,10 @@ import net.socialgamer.cah.task.SafeTimerTask;
|
|||
* @author Andy Janata (ajanata@socialgamer.net)
|
||||
*/
|
||||
public class Game {
|
||||
private static final Logger logger = Logger.getLogger(Game.class);
|
||||
|
||||
private final int id;
|
||||
/**
|
||||
* All players present in the game.
|
||||
*/
|
||||
private final List<Player> players = Collections.synchronizedList(new ArrayList<Player>(10));
|
||||
/**
|
||||
* Players participating in the current round.
|
||||
*/
|
||||
private final List<Player> roundPlayers = Collections.synchronizedList(new ArrayList<Player>(9));
|
||||
private final PlayerPlayedCardsTracker playedCards = new PlayerPlayedCardsTracker();
|
||||
private final List<User> spectators = Collections.synchronizedList(new ArrayList<User>(10));
|
||||
private final ConnectedUsers connectedUsers;
|
||||
private final GameManager gameManager;
|
||||
private Player host;
|
||||
private final Provider<Session> sessionProvider;
|
||||
private BlackDeck blackDeck;
|
||||
private BlackCard blackCard;
|
||||
private final Object blackCardLock = new Object();
|
||||
private WhiteDeck whiteDeck;
|
||||
private GameState state;
|
||||
private final GameOptions options = new GameOptions();
|
||||
private final Set<String> cardcastDeckIds = Collections.synchronizedSet(new HashSet<String>());
|
||||
private final Metrics metrics;
|
||||
private final Provider<Boolean> showGameLinkProvider;
|
||||
private final Provider<String> gamePermalinkFormatProvider;
|
||||
private final Provider<Boolean> showRoundLinkProvider;
|
||||
private final Provider<String> roundPermalinkFormatProvider;
|
||||
private final Provider<Boolean> allowBlankCardsProvider;
|
||||
private final long created = System.currentTimeMillis();
|
||||
|
||||
private int judgeIndex = 0;
|
||||
|
||||
/**
|
||||
* The minimum number of black cards that must be added to a game for it to be able to start.
|
||||
*/
|
||||
public final static int MINIMUM_BLACK_CARDS = 50;
|
||||
|
||||
/**
|
||||
* The minimum number of white cards per player limit slots that must be added to a game for it to
|
||||
* be able to start.
|
||||
|
@ -139,8 +76,7 @@ public class Game {
|
|||
* We need 20 * maxPlayers cards. This allows black cards up to "draw 9" to work correctly.
|
||||
*/
|
||||
public final static int MINIMUM_WHITE_CARDS_PER_PLAYER = 20;
|
||||
|
||||
// All of these delays could be moved to pyx.properties.
|
||||
private static final Logger logger = Logger.getLogger(Game.class);
|
||||
/**
|
||||
* Time, in milliseconds, to delay before starting a new round.
|
||||
*/
|
||||
|
@ -169,26 +105,57 @@ public class Game {
|
|||
private final static int JUDGE_TIMEOUT_PER_CARD = 7 * 1000;
|
||||
private final static int MAX_SKIPS_BEFORE_KICK = 2;
|
||||
private final static Set<String> FINITE_PLAYTIMES;
|
||||
static
|
||||
{
|
||||
|
||||
static {
|
||||
final Set<String> finitePlaytimes = new TreeSet<String>(Arrays.asList(
|
||||
new String[]{"0.25x", "0.5x", "0.75x", "1x", "1.25x", "1.5x", "1.75x", "2x", "2.5x", "3x", "4x", "5x", "10x"}));
|
||||
FINITE_PLAYTIMES = Collections.unmodifiableSet(finitePlaytimes);
|
||||
}
|
||||
|
||||
private final int id;
|
||||
/**
|
||||
* All players present in the game.
|
||||
*/
|
||||
private final List<Player> players = Collections.synchronizedList(new ArrayList<Player>(10));
|
||||
/**
|
||||
* Players participating in the current round.
|
||||
*/
|
||||
private final List<Player> roundPlayers = Collections.synchronizedList(new ArrayList<Player>(9));
|
||||
private final PlayerPlayedCardsTracker playedCards = new PlayerPlayedCardsTracker();
|
||||
private final List<User> spectators = Collections.synchronizedList(new ArrayList<User>(10));
|
||||
private final ConnectedUsers connectedUsers;
|
||||
private final GameManager gameManager;
|
||||
private final Provider<Session> sessionProvider;
|
||||
private final Object blackCardLock = new Object();
|
||||
private final GameOptions options;
|
||||
private final Set<String> cardcastDeckIds = Collections.synchronizedSet(new HashSet<String>());
|
||||
private final Metrics metrics;
|
||||
private final Provider<Boolean> showGameLinkProvider;
|
||||
private final Provider<String> gamePermalinkFormatProvider;
|
||||
private final Provider<Boolean> showRoundLinkProvider;
|
||||
private final Provider<String> roundPermalinkFormatProvider;
|
||||
|
||||
// All of these delays could be moved to pyx.properties.
|
||||
private final Provider<Boolean> allowBlankCardsProvider;
|
||||
private final long created = System.currentTimeMillis();
|
||||
/**
|
||||
* Lock object to prevent judging during idle judge detection and vice-versa.
|
||||
*/
|
||||
private final Object judgeLock = new Object();
|
||||
|
||||
/**
|
||||
* Lock to prevent missing timer updates.
|
||||
*/
|
||||
private final Object roundTimerLock = new Object();
|
||||
private volatile ScheduledFuture<?> lastScheduledFuture;
|
||||
private final ScheduledThreadPoolExecutor globalTimer;
|
||||
private final Provider<CardcastService> cardcastServiceProvider;
|
||||
private final Provider<String> uniqueIdProvider;
|
||||
private Player host;
|
||||
private BlackDeck blackDeck;
|
||||
private BlackCard blackCard;
|
||||
private WhiteDeck whiteDeck;
|
||||
private GameState state;
|
||||
private int judgeIndex = 0;
|
||||
private volatile ScheduledFuture<?> lastScheduledFuture;
|
||||
private String currentUniqueId;
|
||||
/**
|
||||
* Sequence number of cards dealt. This allows re-shuffles and re-deals to still be tracked as
|
||||
|
@ -219,8 +186,10 @@ public class Game {
|
|||
@RoundPermalinkUrlFormat final Provider<String> roundPermalinkFormatProvider,
|
||||
@ShowGamePermalink final Provider<Boolean> showGameLinkProvider,
|
||||
@GamePermalinkUrlFormat final Provider<String> gamePermalinkFormatProvider,
|
||||
@AllowBlankCards final Provider<Boolean> allowBlankCardsProvider) {
|
||||
@AllowBlankCards final Provider<Boolean> allowBlankCardsProvider,
|
||||
final Provider<GameOptions> gameOptionsProvider) {
|
||||
this.id = id;
|
||||
this.options = gameOptionsProvider.get();
|
||||
this.connectedUsers = connectedUsers;
|
||||
this.gameManager = gameManager;
|
||||
this.globalTimer = globalTimer;
|
||||
|
@ -752,8 +721,7 @@ public class Game {
|
|||
final List<CardSet> cardSets = new ArrayList<>();
|
||||
|
||||
if (!options.getPyxCardSetIds().isEmpty()) {
|
||||
@SuppressWarnings("unchecked")
|
||||
final List<CardSet> pyxCardSets = session
|
||||
@SuppressWarnings("unchecked") final List<CardSet> pyxCardSets = session
|
||||
.createQuery("from PyxCardSet where id in (:ids)")
|
||||
.setParameterList("ids", options.getPyxCardSetIds()).list();
|
||||
cardSets.addAll(pyxCardSets);
|
||||
|
@ -791,7 +759,7 @@ public class Game {
|
|||
}
|
||||
|
||||
public WhiteDeck loadWhiteDeck(final List<CardSet> cardSets) {
|
||||
return new WhiteDeck(cardSets, allowBlankCardsProvider.get() ? options.blanksInDeck : 0);
|
||||
return new WhiteDeck(options.maxBlankCardLimit, cardSets, allowBlankCardsProvider.get() ? options.blanksInDeck : 0);
|
||||
}
|
||||
|
||||
public int getRequiredWhiteCardCount() {
|
||||
|
@ -914,8 +882,7 @@ public class Game {
|
|||
return Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
if(FINITE_PLAYTIMES.contains(tm))
|
||||
{
|
||||
if (FINITE_PLAYTIMES.contains(tm)) {
|
||||
factor = Double.valueOf(tm.substring(0, tm.length() - 1));
|
||||
}
|
||||
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
/**
|
||||
* Copyright (c) 2012-2018, Andy Janata
|
||||
* All rights reserved.
|
||||
*
|
||||
* <p>
|
||||
* Redistribution and use in source and binary forms, with or without modification, are permitted
|
||||
* provided that the following conditions are met:
|
||||
*
|
||||
* <p>
|
||||
* * Redistributions of source code must retain the above copyright notice, this list of conditions
|
||||
* and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright notice, this list of
|
||||
* conditions and the following disclaimer in the documentation and/or other materials provided
|
||||
* with the distribution.
|
||||
*
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
|
||||
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||
|
@ -23,14 +23,17 @@
|
|||
|
||||
package net.socialgamer.cah.data;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
import net.socialgamer.cah.CahModule.*;
|
||||
import net.socialgamer.cah.Constants.GameOptionData;
|
||||
import net.socialgamer.cah.JsonWrapper;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import net.socialgamer.cah.Constants.GameOptionData;
|
||||
import net.socialgamer.cah.JsonWrapper;
|
||||
|
||||
|
||||
/**
|
||||
* Options for an individual game.
|
||||
|
@ -38,29 +41,76 @@ import net.socialgamer.cah.JsonWrapper;
|
|||
* @author Gavin Lambert (uecasm)
|
||||
*/
|
||||
public class GameOptions {
|
||||
// TODO move these out to pyx.properties
|
||||
public static final int MIN_SCORE_LIMIT = 4;
|
||||
public static final int DEFAULT_SCORE_LIMIT = 8;
|
||||
public static final int MAX_SCORE_LIMIT = 69;
|
||||
public static final int MIN_PLAYER_LIMIT = 3;
|
||||
public static final int DEFAULT_PLAYER_LIMIT = 10;
|
||||
public static final int MAX_PLAYER_LIMIT = 20;
|
||||
public static final int MIN_SPECTATOR_LIMIT = 0;
|
||||
public static final int DEFAULT_SPECTATOR_LIMIT = 10;
|
||||
public static final int MAX_SPECTATOR_LIMIT = 20;
|
||||
public static final int MIN_BLANK_CARD_LIMIT = 0;
|
||||
public static final int DEFAULT_BLANK_CARD_LIMIT = 0;
|
||||
public static final int MAX_BLANK_CARD_LIMIT = 30;
|
||||
|
||||
// These are the default values new games get.
|
||||
public int blanksInDeck = DEFAULT_BLANK_CARD_LIMIT;
|
||||
public int playerLimit = DEFAULT_PLAYER_LIMIT;
|
||||
public int spectatorLimit = DEFAULT_SPECTATOR_LIMIT;
|
||||
public int scoreGoal = DEFAULT_SCORE_LIMIT;
|
||||
public final Set<Integer> cardSetIds = new HashSet<Integer>();
|
||||
public final int maxPlayerLimit;
|
||||
public final int defaultPlayerLimit;
|
||||
public final int minPlayerLimit;
|
||||
public final int maxSpectatorLimit;
|
||||
public final int defaultSpectatorLimit;
|
||||
public final int minSpectatorLimit;
|
||||
public final int minScoreLimit;
|
||||
public final int maxScoreLimit;
|
||||
public final int defaultScoreLimit;
|
||||
public final int minBlankCardLimit;
|
||||
public final int defaultBlankCardLimit;
|
||||
public final int maxBlankCardLimit;
|
||||
// These are the default values new games get.
|
||||
public int blanksInDeck;
|
||||
public int playerLimit;
|
||||
public int spectatorLimit;
|
||||
public int scoreGoal;
|
||||
public String password = "";
|
||||
public String timerMultiplier = "1x";
|
||||
|
||||
@Inject
|
||||
public GameOptions(@MaxPlayerLimit int maxPlayerLimit, @DefaultPlayerLimit int defaultPlayerLimit, @MinPlayerLimit int minPlayerLimit,
|
||||
@MaxSpectatorLimit int maxSpectatorLimit, @DefaultSpectatorLimit int defaultSpectatorLimit, @MinSpectatorLimit int minSpectatorLimit,
|
||||
@MinScoreLimit int minScoreLimit, @MaxScoreLimit int maxScoreLimit, @DefaultScoreLimit int defaultScoreLimit,
|
||||
@MinBlankCardLimit int minBlankCardLimit, @DefaultBlankCardLimit int defaultBlankCardLimit, @MaxBlankCardLimit int maxBlankCardLimit) {
|
||||
this.maxPlayerLimit = maxPlayerLimit;
|
||||
this.defaultPlayerLimit = playerLimit = defaultPlayerLimit;
|
||||
this.minPlayerLimit = minPlayerLimit;
|
||||
this.maxSpectatorLimit = maxSpectatorLimit;
|
||||
this.defaultSpectatorLimit = spectatorLimit = defaultSpectatorLimit;
|
||||
this.minSpectatorLimit = minSpectatorLimit;
|
||||
this.minScoreLimit = minScoreLimit;
|
||||
this.maxScoreLimit = maxScoreLimit;
|
||||
this.defaultScoreLimit = scoreGoal = defaultScoreLimit;
|
||||
this.minBlankCardLimit = minBlankCardLimit;
|
||||
this.defaultBlankCardLimit = blanksInDeck = defaultBlankCardLimit;
|
||||
this.maxBlankCardLimit = maxBlankCardLimit;
|
||||
}
|
||||
|
||||
public static GameOptions deserialize(Provider<GameOptions> provider, final String text) {
|
||||
final GameOptions options = provider.get();
|
||||
|
||||
if (text == null || text.isEmpty()) {
|
||||
return options;
|
||||
}
|
||||
|
||||
final JsonWrapper json = new JsonWrapper(text);
|
||||
|
||||
final String[] cardSetsParsed = json.getString(GameOptionData.CARD_SETS, "").split(",");
|
||||
for (final String cardSetId : cardSetsParsed) {
|
||||
if (!cardSetId.isEmpty()) {
|
||||
options.cardSetIds.add(Integer.parseInt(cardSetId));
|
||||
}
|
||||
}
|
||||
|
||||
options.blanksInDeck = Math.max(options.minBlankCardLimit, Math.min(options.maxBlankCardLimit,
|
||||
json.getInteger(GameOptionData.BLANKS_LIMIT, options.blanksInDeck)));
|
||||
options.playerLimit = Math.max(options.minPlayerLimit, Math.min(options.maxPlayerLimit,
|
||||
json.getInteger(GameOptionData.PLAYER_LIMIT, options.playerLimit)));
|
||||
options.spectatorLimit = Math.max(options.minSpectatorLimit, Math.min(options.maxSpectatorLimit,
|
||||
json.getInteger(GameOptionData.SPECTATOR_LIMIT, options.spectatorLimit)));
|
||||
options.scoreGoal = Math.max(options.minScoreLimit, Math.min(options.maxScoreLimit,
|
||||
json.getInteger(GameOptionData.SCORE_LIMIT, options.scoreGoal)));
|
||||
options.timerMultiplier = json.getString(GameOptionData.TIMER_MULTIPLIER, options.timerMultiplier);
|
||||
options.password = json.getString(GameOptionData.PASSWORD, options.password);
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the options in-place (so that the Game doesn't need more locks).
|
||||
*
|
||||
|
@ -104,36 +154,6 @@ public class GameOptions {
|
|||
return info;
|
||||
}
|
||||
|
||||
public static GameOptions deserialize(final String text) {
|
||||
final GameOptions options = new GameOptions();
|
||||
|
||||
if (text == null || text.isEmpty()) {
|
||||
return options;
|
||||
}
|
||||
|
||||
final JsonWrapper json = new JsonWrapper(text);
|
||||
|
||||
final String[] cardSetsParsed = json.getString(GameOptionData.CARD_SETS, "").split(",");
|
||||
for (final String cardSetId : cardSetsParsed) {
|
||||
if (!cardSetId.isEmpty()) {
|
||||
options.cardSetIds.add(Integer.parseInt(cardSetId));
|
||||
}
|
||||
}
|
||||
|
||||
options.blanksInDeck = Math.max(MIN_BLANK_CARD_LIMIT, Math.min(MAX_BLANK_CARD_LIMIT,
|
||||
json.getInteger(GameOptionData.BLANKS_LIMIT, options.blanksInDeck)));
|
||||
options.playerLimit = Math.max(MIN_PLAYER_LIMIT, Math.min(MAX_PLAYER_LIMIT,
|
||||
json.getInteger(GameOptionData.PLAYER_LIMIT, options.playerLimit)));
|
||||
options.spectatorLimit = Math.max(MIN_SPECTATOR_LIMIT, Math.min(MAX_SPECTATOR_LIMIT,
|
||||
json.getInteger(GameOptionData.SPECTATOR_LIMIT, options.spectatorLimit)));
|
||||
options.scoreGoal = Math.max(MIN_SCORE_LIMIT, Math.min(MAX_SCORE_LIMIT,
|
||||
json.getInteger(GameOptionData.SCORE_LIMIT, options.scoreGoal)));
|
||||
options.timerMultiplier = json.getString(GameOptionData.TIMER_MULTIPLIER, options.timerMultiplier);
|
||||
options.password = json.getString(GameOptionData.PASSWORD, options.password);
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Selected card set IDs which are local to PYX, for querying the database.
|
||||
*/
|
||||
|
|
|
@ -46,13 +46,13 @@ public class WhiteDeck {
|
|||
/**
|
||||
* Create a new white card deck, loading the cards from the database and shuffling them.
|
||||
*/
|
||||
public WhiteDeck(final Collection<CardSet> cardSets, final int numBlanks) {
|
||||
public WhiteDeck(int maxBlankCardLimit, final Collection<CardSet> cardSets, final int numBlanks) {
|
||||
final Set<WhiteCard> allCards = new HashSet<WhiteCard>();
|
||||
for (final CardSet cardSet : cardSets) {
|
||||
allCards.addAll(cardSet.getWhiteCards());
|
||||
}
|
||||
deck = new ArrayList<WhiteCard>(allCards);
|
||||
for (int i = 0; i < numBlanks && i < GameOptions.MAX_BLANK_CARD_LIMIT; i++) {
|
||||
for (int i = 0; i < numBlanks && i < maxBlankCardLimit; i++) {
|
||||
deck.add(createBlankCard());
|
||||
}
|
||||
Collections.shuffle(deck);
|
||||
|
|
|
@ -5,6 +5,8 @@ import java.util.Map;
|
|||
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
import com.google.inject.Provider;
|
||||
import net.socialgamer.cah.CahModule;
|
||||
import net.socialgamer.cah.Constants.AjaxOperation;
|
||||
import net.socialgamer.cah.Constants.AjaxRequest;
|
||||
import net.socialgamer.cah.Constants.ErrorCode;
|
||||
|
@ -22,10 +24,12 @@ import com.google.inject.Inject;
|
|||
public class ChangeGameOptionHandler extends GameWithPlayerHandler {
|
||||
|
||||
public static final String OP = AjaxOperation.CHANGE_GAME_OPTIONS.toString();
|
||||
private Provider<GameOptions> gameOptionsProvider;
|
||||
|
||||
@Inject
|
||||
public ChangeGameOptionHandler(final GameManager gameManager) {
|
||||
public ChangeGameOptionHandler(final GameManager gameManager, Provider<GameOptions> gameOptionsProvider) {
|
||||
super(gameManager);
|
||||
this.gameOptionsProvider = gameOptionsProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -40,7 +44,7 @@ public class ChangeGameOptionHandler extends GameWithPlayerHandler {
|
|||
} else {
|
||||
try {
|
||||
final String value = request.getParameter(AjaxRequest.GAME_OPTIONS);
|
||||
final GameOptions options = GameOptions.deserialize(value);
|
||||
final GameOptions options = GameOptions.deserialize(gameOptionsProvider, value);
|
||||
final String oldPassword = game.getPassword();
|
||||
game.updateGameSettings(options);
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@ import javax.servlet.http.HttpServletResponse;
|
|||
import com.google.inject.Injector;
|
||||
import com.google.inject.Key;
|
||||
|
||||
import net.socialgamer.cah.CahModule;
|
||||
import net.socialgamer.cah.CahModule.BroadcastConnectsAndDisconnects;
|
||||
import net.socialgamer.cah.CahModule.CookieDomain;
|
||||
import net.socialgamer.cah.CahModule.GameChatEnabled;
|
||||
|
|
|
@ -79,6 +79,15 @@ public class GameManagerTest {
|
|||
private int gameId;
|
||||
private final ScheduledThreadPoolExecutor timer = new ScheduledThreadPoolExecutor(1);
|
||||
private Metrics metricsMock;
|
||||
private final Provider<GameOptions> gameOptionsProvider = new Provider<GameOptions>() {
|
||||
@Override
|
||||
public GameOptions get() {
|
||||
return new GameOptions(20, 10, 3,
|
||||
20, 10, 0,
|
||||
4, 69, 8,
|
||||
0, 0, 30);
|
||||
}
|
||||
};
|
||||
private final Provider<Boolean> falseProvider = new Provider<Boolean>() {
|
||||
@Override
|
||||
public Boolean get() {
|
||||
|
@ -123,6 +132,7 @@ public class GameManagerTest {
|
|||
bind(Boolean.class).annotatedWith(ShowGamePermalink.class).toProvider(falseProvider);
|
||||
bind(String.class).annotatedWith(GamePermalinkUrlFormat.class).toProvider(formatProvider);
|
||||
bind(Boolean.class).annotatedWith(AllowBlankCards.class).toProvider(falseProvider);
|
||||
bind(GameOptions.class).toProvider(gameOptionsProvider);
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
@ -173,15 +183,15 @@ public class GameManagerTest {
|
|||
assertEquals(0, gameManager.get().intValue());
|
||||
gameManager.getGames().put(0,
|
||||
new Game(0, cuMock, gameManager, timer, null, null, null, metricsMock, falseProvider,
|
||||
formatProvider, falseProvider, formatProvider, falseProvider));
|
||||
formatProvider, falseProvider, formatProvider, falseProvider, gameOptionsProvider));
|
||||
assertEquals(1, gameManager.get().intValue());
|
||||
gameManager.getGames().put(1,
|
||||
new Game(1, cuMock, gameManager, timer, null, null, null, metricsMock, falseProvider,
|
||||
formatProvider, falseProvider, formatProvider, falseProvider));
|
||||
formatProvider, falseProvider, formatProvider, falseProvider, gameOptionsProvider));
|
||||
assertEquals(2, gameManager.get().intValue());
|
||||
gameManager.getGames().put(2,
|
||||
new Game(2, cuMock, gameManager, timer, null, null, null, metricsMock, falseProvider,
|
||||
formatProvider, falseProvider, formatProvider, falseProvider));
|
||||
formatProvider, falseProvider, formatProvider, falseProvider, gameOptionsProvider));
|
||||
// make sure it says it can't make any more
|
||||
assertEquals(-1, gameManager.get().intValue());
|
||||
|
||||
|
@ -191,7 +201,7 @@ public class GameManagerTest {
|
|||
assertEquals(1, gameManager.get().intValue());
|
||||
gameManager.getGames().put(1,
|
||||
new Game(1, cuMock, gameManager, timer, null, null, null, metricsMock, falseProvider,
|
||||
formatProvider, falseProvider, formatProvider, falseProvider));
|
||||
formatProvider, falseProvider, formatProvider, falseProvider, gameOptionsProvider));
|
||||
assertEquals(-1, gameManager.get().intValue());
|
||||
|
||||
// remove game 1 out from under it, to make sure it'll fix itself
|
||||
|
@ -199,7 +209,7 @@ public class GameManagerTest {
|
|||
assertEquals(1, gameManager.get().intValue());
|
||||
gameManager.getGames().put(1,
|
||||
new Game(1, cuMock, gameManager, timer, null, null, null, metricsMock, falseProvider,
|
||||
formatProvider, falseProvider, formatProvider, falseProvider));
|
||||
formatProvider, falseProvider, formatProvider, falseProvider, gameOptionsProvider));
|
||||
assertEquals(-1, gameManager.get().intValue());
|
||||
|
||||
gameManager.destroyGame(2);
|
||||
|
|
|
@ -60,6 +60,15 @@ public class GameTest {
|
|||
private GameManager gmMock;
|
||||
private Metrics metricsMock;
|
||||
private final ScheduledThreadPoolExecutor timer = new ScheduledThreadPoolExecutor(1);
|
||||
private final Provider<GameOptions> gameOptionsProvider = new Provider<GameOptions>() {
|
||||
@Override
|
||||
public GameOptions get() {
|
||||
return new GameOptions(20, 10, 3,
|
||||
20, 10, 0,
|
||||
4, 69, 8,
|
||||
0, 0, 30);
|
||||
}
|
||||
};
|
||||
private final Provider<Boolean> falseProvider = new Provider<Boolean>() {
|
||||
@Override
|
||||
public Boolean get() {
|
||||
|
@ -79,7 +88,7 @@ public class GameTest {
|
|||
gmMock = createMock(GameManager.class);
|
||||
metricsMock = createMock(Metrics.class);
|
||||
game = new Game(0, cuMock, gmMock, timer, null, null, null, metricsMock, falseProvider,
|
||||
formatProvider, falseProvider, formatProvider, falseProvider);
|
||||
formatProvider, falseProvider, formatProvider, falseProvider, gameOptionsProvider);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
|
Loading…
Reference in New Issue