Refactor some quest code to make it reusable by mobile game

This commit is contained in:
drdev
2014-07-06 02:37:23 +00:00
parent acc648cf08
commit c89cc33cd6
35 changed files with 834 additions and 745 deletions

View File

@@ -1,6 +1,7 @@
package forge.interfaces;
import forge.UiCommand;
import forge.assets.FSkinProp;
public interface IButton {
boolean isEnabled();
@@ -13,4 +14,6 @@ public interface IButton {
void setSelected(boolean b0);
boolean requestFocusInWindow();
void setCommand(UiCommand command0);
void setTextColor(FSkinProp color);
void setTextColor(int r, int g, int b);
}

View File

@@ -0,0 +1,10 @@
package forge.interfaces;
public interface ICheckBox {
boolean isEnabled();
void setEnabled(boolean b0);
boolean isVisible();
void setVisible(boolean b0);
boolean isSelected();
void setSelected(boolean b0);
}

View File

@@ -0,0 +1,11 @@
package forge.interfaces;
public interface IComboBox<E> {
boolean isEnabled();
void setEnabled(boolean b0);
boolean isVisible();
void setVisible(boolean b0);
void setSelectedItem(E item);
void addItem(E item);
void removeAllItems();
}

View File

@@ -17,6 +17,7 @@ import forge.events.UiEvent;
import forge.game.Game;
import forge.game.GameEntity;
import forge.game.GameType;
import forge.game.Match;
import forge.game.card.Card;
import forge.game.combat.Combat;
import forge.game.event.GameEventTurnBegan;
@@ -86,6 +87,7 @@ public interface IGuiBase {
void copyToClipboard(String text);
void browseToUrl(String url) throws Exception;
LobbyPlayer getGuiPlayer();
LobbyPlayer getAiPlayer(String name);
LobbyPlayer createAiPlayer();
LobbyPlayer createAiPlayer(String name, int avatarIndex);
LobbyPlayer getQuestPlayer();
@@ -93,4 +95,9 @@ public interface IGuiBase {
IAudioMusic createAudioMusic(String filename);
void startAltSoundSystem(String filename, boolean isSynchronized);
void clearImageCache();
void startGame(Match match);
void continueMatch(Match match);
void showSpellShop();
void showBazaar();
void setPlayerAvatar(LobbyPlayer player, String iconImageKey);
}

View File

@@ -0,0 +1,32 @@
package forge.quest;
import forge.interfaces.IButton;
import forge.interfaces.ICheckBox;
import forge.interfaces.IComboBox;
/** Dictates methods required for a panel with stats/pet display. */
public interface IVQuestStats {
IButton getBtnRandomOpponent();
IButton getBtnBazaar();
IButton getBtnSpellShop();
IButton getBtnUnlock();
IButton getBtnTravel();
IButton getLblCredits();
IButton getLblLife();
IButton getLblWorld();
IButton getLblWins();
IButton getLblLosses();
IButton getLblNextChallengeInWins();
IButton getLblCurrentDeck();
IButton getLblWinStreak();
IComboBox<String> getCbxPet();
ICheckBox getCbPlant();
ICheckBox getCbCharm();
IButton getLblZep();
boolean isChallengesView();
}

View File

@@ -0,0 +1,200 @@
package forge.quest;
import java.util.ArrayList;
import java.util.List;
import forge.FThreads;
import forge.GuiBase;
import forge.deck.DeckGroup;
import forge.game.Game;
import forge.game.GameRules;
import forge.game.GameType;
import forge.game.Match;
import forge.game.player.RegisteredPlayer;
import forge.model.FModel;
import forge.properties.ForgePreferences.FPref;
import forge.quest.QuestEventDraft;
public class QuestDraftUtils {
private static List<DraftMatchup> matchups = new ArrayList<DraftMatchup>();
public static boolean matchInProgress = false;
public static boolean aiMatchInProgress = false;
private static boolean waitForUserInput = false;
public static void continueMatch(Game lastGame) {
if (lastGame.getMatch().isMatchOver()) {
matchInProgress = false;
}
GuiBase.getInterface().continueMatch(matchInProgress ? lastGame.getMatch() : null);
}
public static void startNextMatch() {
if (matchups.size() > 0) {
return;
}
matchups.clear();
QuestEventDraft draft = FModel.getQuest().getAchievements().getCurrentDraft();
String[] currentStandings = draft.getStandings();
int currentSet = -1;
for (int i = currentStandings.length - 1; i >= 0; i--) {
if (!currentStandings[i].equals(QuestEventDraft.UNDETERMINED)) {
currentSet = i;
break;
}
}
switch (currentSet) {
case 7:
addMatchup(0, 1, draft);
addMatchup(2, 3, draft);
addMatchup(4, 5, draft);
addMatchup(6, 7, draft);
break;
case 8:
addMatchup(2, 3, draft);
addMatchup(4, 5, draft);
addMatchup(6, 7, draft);
break;
case 9:
addMatchup(4, 5, draft);
addMatchup(6, 7, draft);
break;
case 10:
addMatchup(6, 7, draft);
break;
case 11:
addMatchup(8, 9, draft);
addMatchup(10, 11, draft);
break;
case 12:
addMatchup(10, 11, draft);
break;
case 13:
addMatchup(12, 13, draft);
break;
case 14:
default:
return;
}
update();
}
private static void addMatchup(final int player1, final int player2, final QuestEventDraft draft) {
DraftMatchup matchup = new DraftMatchup();
DeckGroup decks = FModel.getQuest().getAssets().getDraftDeckStorage().get(QuestEventDraft.DECK_NAME);
int humanIndex = -1;
int aiIndex = -1;
if (draft.getStandings()[player1].equals(QuestEventDraft.HUMAN)) {
humanIndex = player1;
aiIndex = player2;
} else if (draft.getStandings()[player2].equals(QuestEventDraft.HUMAN)) {
humanIndex = player2;
aiIndex = player1;
}
if (humanIndex > -1) {
matchup.hasHumanPlayer = true;
matchup.matchStarter.add(new RegisteredPlayer(decks.getHumanDeck()).setPlayer(GuiBase.getInterface().getGuiPlayer()));
int aiName = Integer.parseInt(draft.getStandings()[aiIndex]) - 1;
int aiDeckIndex = Integer.parseInt(draft.getStandings()[aiIndex]) - 1;
matchup.matchStarter.add(new RegisteredPlayer(decks.getAiDecks().get(aiDeckIndex)).setPlayer(GuiBase.getInterface().createAiPlayer(draft.getAINames()[aiName], draft.getAIIcons()[aiName])));
} else {
int aiName1 = Integer.parseInt(draft.getStandings()[player1]) - 1;
int aiName2 = Integer.parseInt(draft.getStandings()[player2]) - 1;
int aiDeckIndex = Integer.parseInt(draft.getStandings()[player1]) - 1;
matchup.matchStarter.add(new RegisteredPlayer(decks.getAiDecks().get(aiDeckIndex)).setPlayer(GuiBase.getInterface().createAiPlayer(draft.getAINames()[aiName1], draft.getAIIcons()[aiName1])));
aiDeckIndex = Integer.parseInt(draft.getStandings()[player2]) - 1;
matchup.matchStarter.add(new RegisteredPlayer(decks.getAiDecks().get(aiDeckIndex)).setPlayer(GuiBase.getInterface().createAiPlayer(draft.getAINames()[aiName2], draft.getAIIcons()[aiName2])));
}
matchups.add(matchup);
}
public static void update() {
if (matchups.isEmpty()) {
if (!matchInProgress) {
aiMatchInProgress = false;
}
return;
}
if (waitForUserInput) {
return;
}
if (matchInProgress) {
return;
}
GuiBase.getInterface().enableOverlay();
DraftMatchup nextMatch = matchups.remove(0);
matchInProgress = true;
if (!nextMatch.hasHumanPlayer) {
GuiBase.getInterface().disableOverlay();
waitForUserInput = false;
aiMatchInProgress = true;
}
else {
waitForUserInput = true;
aiMatchInProgress = false;
}
GameRules rules = new GameRules(GameType.QuestDraft);
rules.setPlayForAnte(false);
rules.setMatchAnteRarity(false);
rules.setGamesPerMatch(3);
rules.setManaBurn(FModel.getPreferences().getPrefBoolean(FPref.UI_MANABURN));
rules.canCloneUseTargetsImage = FModel.getPreferences().getPrefBoolean(FPref.UI_CLONE_MODE_SOURCE);
final Match match = new Match(rules, nextMatch.matchStarter);
FThreads.invokeInEdtLater(new Runnable(){
@Override
public void run() {
GuiBase.getInterface().startGame(match);
}
});
}
public static void continueMatches() {
waitForUserInput = false;
update();
}
private static class DraftMatchup {
private List<RegisteredPlayer> matchStarter = new ArrayList<RegisteredPlayer>();
private boolean hasHumanPlayer = false;
}
}

View File

@@ -17,18 +17,37 @@
*/
package forge.quest;
import forge.FThreads;
import forge.GuiBase;
import forge.LobbyPlayer;
import forge.assets.FSkinProp;
import forge.card.CardDb.SetPreference;
import forge.card.CardEdition;
import forge.card.CardRules;
import forge.deck.Deck;
import forge.game.GameRules;
import forge.game.GameType;
import forge.game.Match;
import forge.game.card.Card;
import forge.game.player.RegisteredPlayer;
import forge.interfaces.IButton;
import forge.item.IPaperCard;
import forge.item.PaperToken;
import forge.model.FModel;
import forge.properties.ForgePreferences.FPref;
import forge.quest.bazaar.QuestItemType;
import forge.quest.bazaar.QuestPetController;
import forge.quest.data.QuestAchievements;
import forge.quest.data.QuestAssets;
import forge.quest.data.QuestPreferences.QPref;
import forge.util.gui.SGuiChoose;
import forge.util.gui.SOptionPane;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang3.tuple.ImmutablePair;
/**
* <p>
* QuestUtil class.
@@ -170,5 +189,480 @@ public class QuestUtil {
// Standard card creation
return FModel.getMagicDb().getCommonCards().getCardFromEdition(name, SetPreference.Latest);
}
public static void travelWorld() {
if (!checkActiveQuest("Travel between worlds.")) {
return;
}
List<QuestWorld> worlds = new ArrayList<QuestWorld>();
final QuestController qCtrl = FModel.getQuest();
for (QuestWorld qw : FModel.getWorlds()) {
if (qCtrl.getWorld() != qw) {
worlds.add(qw);
}
}
if (worlds.size() < 1) {
SOptionPane.showErrorDialog("There are currently no worlds you can travel to\nin this version of Forge.", "No Worlds");
return;
}
final String setPrompt = "Where do you wish to travel?";
final QuestWorld newWorld = SGuiChoose.oneOrNone(setPrompt, worlds);
if (worlds.indexOf(newWorld) < 0) {
return;
}
if (qCtrl.getWorld() != newWorld) {
boolean needRemove = false;
if (nextChallengeInWins() < 1 && qCtrl.getAchievements().getCurrentChallenges().size() > 0) {
needRemove = true;
if (!SOptionPane.showConfirmDialog(
"You have uncompleted challenges in your current world. If you travel now, they will be LOST!"
+ "\nAre you sure you wish to travel anyway?\n"
+ "(Click \"No\" to go back and complete your current challenges first.)",
"WARNING: Uncompleted challenges")) {
return;
}
}
if (needRemove) {
// Remove current challenges.
while (nextChallengeInWins() == 0) {
qCtrl.getAchievements().addChallengesPlayed();
}
qCtrl.getAchievements().getCurrentChallenges().clear();
}
qCtrl.setWorld(newWorld);
qCtrl.resetDuelsManager();
qCtrl.resetChallengesManager();
// Note that the following can be (ab)used to re-randomize your opponents by travelling to a different
// world and back. To prevent this, simply delete the following line that randomizes DuelsManager.
// (OTOH, you can 'swap' opponents even more easily by simply selecting a different quest data file and
// then re-selecting your current quest data file.)
qCtrl.getDuelsManager().randomizeOpponents();
qCtrl.getCards().clearShopList();
qCtrl.save();
}
}
private static QuestEvent event;
private static QuestEventDraft draftEvent;
/**
* <p>
* nextChallengeInWins.
* </p>
*
* @return a int.
*/
public static int nextChallengeInWins() {
final QuestController qData = FModel.getQuest();
final int challengesPlayed = qData.getAchievements().getChallengesPlayed();
final int wins = qData.getAchievements().getWin();
final int turnsToUnlock = FModel.getQuest().getTurnsToUnlockChallenge();
final int delta;
// First challenge unlocks after minimum wins reached.
if (wins < 2 * turnsToUnlock) {
delta = 2 * turnsToUnlock - wins;
}
else {
// More than enough wins
if (wins / turnsToUnlock > challengesPlayed) {
delta = 0;
}
// This part takes the "unlimited challenge" bug into account;
// a player could have an inflated challengesPlayed value.
// Added 09-2012, can be removed after a while.
else if (wins < challengesPlayed * turnsToUnlock) {
delta = (challengesPlayed * turnsToUnlock - wins) + turnsToUnlock;
}
// Data OK, but not enough wins yet (default).
else {
delta = turnsToUnlock - wins % turnsToUnlock;
}
}
return (delta > 0) ? delta : 0;
}
private static void updatePlantAndPetForView(final IVQuestStats view, final QuestController qCtrl) {
for (int iSlot = 0; iSlot < QuestController.MAX_PET_SLOTS; iSlot++) {
final List<QuestPetController> petList = qCtrl.getPetsStorage().getAvaliablePets(iSlot, qCtrl.getAssets());
final String currentPetName = qCtrl.getSelectedPet(iSlot);
if (iSlot == 0) { // Plant visiblity
if (petList.isEmpty()) {
view.getCbPlant().setVisible(false);
}
else {
view.getCbPlant().setVisible(true);
view.getCbPlant().setSelected(currentPetName != null);
}
}
if (iSlot == 1) {
view.getCbxPet().removeAllItems();
// Pet list visibility
if (petList.size() > 0) {
view.getCbxPet().setVisible(true);
view.getCbxPet().addItem("Don't summon a pet");
for (final QuestPetController pet : petList) {
String name = "Summon " + pet.getName();
view.getCbxPet().addItem(name);
if (pet.getName().equals(currentPetName)) {
view.getCbxPet().setSelectedItem(name);
}
}
} else {
view.getCbxPet().setVisible(false);
}
}
}
if (qCtrl.getAssets().hasItem(QuestItemType.CHARM)) {
view.getCbCharm().setVisible(true);
}
else {
view.getCbCharm().setVisible(false);
}
if (view.isChallengesView()) {
view.getLblZep().setVisible(qCtrl.getAssets().hasItem(QuestItemType.ZEPPELIN));
if (qCtrl.getAssets().getItemLevel(QuestItemType.ZEPPELIN) == 2) {
view.getLblZep().setEnabled(false);
view.getLblZep().setTextColor(128, 128, 128);
}
else {
view.getLblZep().setEnabled(true);
view.getLblZep().setTextColor(FSkinProp.CLR_TEXT);
}
}
else {
view.getLblZep().setVisible(false);
}
}
/**
* Updates all quest info in a view, using
* retrieval methods dictated in IVQuestStats.<br>
* - Stats<br>
* - Pets<br>
* - Current deck info<br>
* - "Challenge In" info<br>
*
* @param view0 {@link forge.screens.home.quest.IVQuestStats}
*/
public static void updateQuestView(final IVQuestStats view0) {
final QuestController qCtrl = FModel.getQuest();
final QuestAchievements qA = qCtrl.getAchievements();
final QuestAssets qS = qCtrl.getAssets();
if (qA == null) { return; }
// Fantasy UI display
view0.getLblNextChallengeInWins().setVisible(true);
view0.getBtnBazaar().setVisible(true);
view0.getLblLife().setVisible(true);
// Stats panel
view0.getLblCredits().setText("Credits: " + qS.getCredits());
view0.getLblLife().setText("Life: " + qS.getLife(qCtrl.getMode()));
view0.getLblWins().setText("Wins: " + qA.getWin());
view0.getLblLosses().setText("Losses: " + qA.getLost());
view0.getLblWorld().setText("World: " + (qCtrl.getWorld() == null ? "(none)" : qCtrl.getWorld()));
// Show or hide the set unlocking button
view0.getBtnUnlock().setVisible(qCtrl.getUnlocksTokens() > 0 && qCtrl.getWorldFormat() == null);
// Challenge in wins
final int num = nextChallengeInWins();
final String str;
if (num == 0) {
str = "Your exploits have been noticed. An opponent has challenged you.";
}
else if (num == 1) {
str = "A new challenge will be available after 1 more win.";
}
else {
str = "A new challenge will be available in " + num + " wins.";
}
view0.getLblNextChallengeInWins().setText(str);
view0.getLblWinStreak().setText(
"<html>Win streak: " + qA.getWinStreakCurrent()
+ "<br>&nbsp; (Best: " + qA.getWinStreakBest() + ")</html>");
// Current deck message
final IButton lblCurrentDeck = view0.getLblCurrentDeck();
if (getCurrentDeck() == null) {
lblCurrentDeck.setTextColor(204, 0, 0);
lblCurrentDeck.setText("Build, then select a deck in the \"Quest Decks\" submenu.");
}
else {
lblCurrentDeck.setTextColor(FSkinProp.CLR_TEXT);
lblCurrentDeck.setText("Your current deck is \""
+ getCurrentDeck().getName() + "\".");
}
// Start panel: pet, plant, zep.
if (qCtrl.getMode() == QuestMode.Fantasy) {
updatePlantAndPetForView(view0, qCtrl);
}
else {
// Classic mode display changes
view0.getCbxPet().setVisible(false);
view0.getCbPlant().setVisible(false);
view0.getCbCharm().setVisible(false);
view0.getLblZep().setVisible(false);
view0.getLblNextChallengeInWins().setVisible(false);
view0.getBtnBazaar().setVisible(false);
view0.getLblLife().setVisible(false);
}
}
/** @return {@link forge.deck.Deck} */
public static Deck getCurrentDeck() {
Deck d = null;
if (FModel.getQuest().getAssets() != null) {
d = FModel.getQuest().getMyDecks().get(
FModel.getQuestPreferences().getPref(QPref.CURRENT_DECK));
}
return d;
}
/** Updates the current selected quest event, used when game is started.
* @param event0 {@link forge.quest.QuestEvent}
*/
public static void setEvent(final QuestEvent event0) {
event = event0;
}
public static void setDraftEvent(final QuestEventDraft event0) {
draftEvent = event0;
}
public static QuestEventDraft getDraftEvent() {
return draftEvent;
}
public static boolean checkActiveQuest(String location) {
QuestController qc = FModel.getQuest();
if (qc == null || qc.getAssets() == null) {
String msg = "Please create a Quest before attempting to " + location;
SOptionPane.showErrorDialog(msg, "No Quest");
System.out.println(msg);
return false;
}
return true;
}
/** */
public static void showSpellShop() {
if (!checkActiveQuest("Visit the Spell Shop.")) {
return;
}
GuiBase.getInterface().showSpellShop();
}
/** */
public static void showBazaar() {
if (!checkActiveQuest("Visit the Bazaar.")) {
return;
}
GuiBase.getInterface().showBazaar();
}
/** */
public static void chooseAndUnlockEdition() {
if (!checkActiveQuest("Unlock Editions.")) {
return;
}
final QuestController qData = FModel.getQuest();
ImmutablePair<CardEdition, Integer> toUnlock = QuestUtilUnlockSets.chooseSetToUnlock(qData, false, null);
if (toUnlock == null) {
return;
}
CardEdition unlocked = toUnlock.left;
qData.getAssets().subtractCredits(toUnlock.right);
SOptionPane.showMessageDialog("You have successfully unlocked " + unlocked.getName() + "!",
unlocked.getName() + " unlocked!", null);
QuestUtilUnlockSets.doUnlock(qData, unlocked);
}
/** */
public static void startGame() {
if (!checkActiveQuest("Start a duel.") || null == event) {
return;
}
final QuestController qData = FModel.getQuest();
Deck deck = null;
if (event instanceof QuestEventChallenge) {
// Predefined HumanDeck
deck = ((QuestEventChallenge) event).getHumanDeck();
}
if (deck == null) {
// If no predefined Deck, use the Player's Deck
deck = getCurrentDeck();
}
if (deck == null) {
String msg = "Please select a Quest Deck.";
SOptionPane.showErrorDialog(msg, "No Deck");
System.out.println(msg);
return;
}
if (FModel.getPreferences().getPrefBoolean(FPref.ENFORCE_DECK_LEGALITY)) {
String errorMessage = GameType.Quest.getDecksFormat().getDeckConformanceProblem(deck);
if (null != errorMessage) {
SOptionPane.showErrorDialog("Your deck " + errorMessage + " Please edit or choose a different deck.", "Invalid Deck");
return;
}
}
FThreads.invokeInBackgroundThread(new Runnable() {
@Override
public void run() {
qData.getDuelsManager().randomizeOpponents();
qData.setCurrentEvent(event);
qData.save();
}
});
int extraLifeHuman = 0;
Integer lifeHuman = null;
boolean useBazaar = true;
Boolean forceAnte = null;
int lifeAI = 20;
if (event instanceof QuestEventChallenge) {
QuestEventChallenge qc = ((QuestEventChallenge) event);
lifeAI = qc.getAILife();
lifeHuman = qc.getHumanLife();
if (qData.getAssets().hasItem(QuestItemType.ZEPPELIN)) {
extraLifeHuman = 3;
}
useBazaar = qc.isUseBazaar();
forceAnte = qc.isForceAnte();
}
RegisteredPlayer humanStart = new RegisteredPlayer(deck);
RegisteredPlayer aiStart = new RegisteredPlayer(event.getEventDeck());
if (lifeHuman != null) {
humanStart.setStartingLife(lifeHuman);
} else {
humanStart.setStartingLife(qData.getAssets().getLife(qData.getMode()) + extraLifeHuman);
}
if (useBazaar) {
humanStart.setCardsOnBattlefield(QuestUtil.getHumanStartingCards(qData, event));
aiStart.setStartingLife(lifeAI);
aiStart.setCardsOnBattlefield(QuestUtil.getComputerStartingCards(event));
}
List<RegisteredPlayer> starter = new ArrayList<RegisteredPlayer>();
starter.add(humanStart.setPlayer(GuiBase.getInterface().getQuestPlayer()));
LobbyPlayer aiPlayer = GuiBase.getInterface().getAiPlayer(event.getOpponent() == null ? event.getTitle() : event.getOpponent());
GuiBase.getInterface().setPlayerAvatar(aiPlayer, event.getIconImageKey());
starter.add(aiStart.setPlayer(aiPlayer));
boolean useRandomFoil = FModel.getPreferences().getPrefBoolean(FPref.UI_RANDOM_FOIL);
for(RegisteredPlayer rp : starter) {
rp.setRandomFoil(useRandomFoil);
}
boolean useAnte = FModel.getPreferences().getPrefBoolean(FPref.UI_ANTE);
boolean matchAnteRarity = FModel.getPreferences().getPrefBoolean(FPref.UI_ANTE_MATCH_RARITY);
if(forceAnte != null)
useAnte = forceAnte.booleanValue();
GameRules rules = new GameRules(GameType.Quest);
rules.setPlayForAnte(useAnte);
rules.setMatchAnteRarity(matchAnteRarity);
rules.setGamesPerMatch(qData.getCharmState() ? 5 : 3);
rules.setManaBurn(FModel.getPreferences().getPrefBoolean(FPref.UI_MANABURN));
rules.canCloneUseTargetsImage = FModel.getPreferences().getPrefBoolean(FPref.UI_CLONE_MODE_SOURCE);
final Match mc = new Match(rules, starter);
FThreads.invokeInEdtLater(new Runnable(){
@Override
public void run() {
GuiBase.getInterface().startGame(mc);
}
});
}
/**
* Checks to see if a game can be started and displays relevant dialogues.
* @return
*/
public static boolean canStartGame() {
if (!checkActiveQuest("Start a duel.") || null == event) {
return false;
}
Deck deck = null;
if (event instanceof QuestEventChallenge) {
// Predefined HumanDeck
deck = ((QuestEventChallenge) event).getHumanDeck();
}
if (deck == null) {
// If no predefined Deck, use the Player's Deck
deck = getCurrentDeck();
}
if (deck == null) {
String msg = "Please select a Quest Deck.";
SOptionPane.showErrorDialog(msg, "No Deck");
System.out.println(msg);
return false;
}
if (FModel.getPreferences().getPrefBoolean(FPref.ENFORCE_DECK_LEGALITY)) {
String errorMessage = GameType.Quest.getDecksFormat().getDeckConformanceProblem(deck);
if (null != errorMessage) {
SOptionPane.showErrorDialog("Your deck " + errorMessage + " Please edit or choose a different deck.", "Invalid Deck");
return false;
}
}
return true;
}
/** Duplicate in DeckEditorQuestMenu and
* probably elsewhere...can streamline at some point
* (probably shouldn't be here).
*
* @param in &emsp; {@link java.lang.String}
* @return {@link java.lang.String}
*/
public static String cleanString(final String in) {
final StringBuffer out = new StringBuffer();
final char[] c = in.toCharArray();
for (int i = 0; (i < c.length) && (i < 20); i++) {
if (Character.isLetterOrDigit(c[i]) || (c[i] == '-') || (c[i] == '_') || (c[i] == ' ')) {
out.append(c[i]);
}
}
return out.toString();
}
} // QuestUtil