/**
* THIS IS CREATED BY tom_mai78101. PLEASE GIVE CREDIT FOR WORKING ON A CLONE.
*
* ALL WORKS COPYRIGHTED TO The Pokémon Company and Nintendo. I REPEAT, THIS IS A CLONE.
*
* YOU MAY NOT SELL COMMERCIALLY, OR YOU WILL BE PROSECUTED BY The Pokémon Company AND Nintendo.
*
* THE CREATOR IS NOT LIABLE FOR ANY DAMAGES DONE. FOLLOW LOCAL LAWS, BE RESPECTFUL, AND HAVE A GOOD DAY!
* */
package dialogue;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Map;
import main.Keys;
import main.MainComponent;
import resources.Art;
import screen.BaseScreen;
import abstracts.Tile;
import entity.Player;
//TODO (6/25/2015): Check to see why modded scripts still suffer from blinking dialogue boxes. Non-modded scripts are fixed.
public class NewDialogue {
public static final int DIALOGUE_SPEECH = 0x40;
public static final int DIALOGUE_QUESTION = 0x41;
public static final int DIALOGUE_ALERT = 0x42;
public static final int HALF_STRING_LENGTH = 9;
// Dialogue max string length per line.
public static final int MAX_STRING_LENGTH = 18;
private ArrayList<String> completedLines;
private Keys input;
private int lineIterator;
private int lineLength;
private ArrayList<Map.Entry<String, Boolean>> lines;
private boolean nextFlag;
private boolean simpleQuestionFlag;
private boolean simpleSpeechFlag;
private boolean yesNoCursorPosition;
private Boolean yesNoAnswerFlag;
private byte nextTick;
private int scrollDistance;
private boolean scrollFlag;
private boolean showDialog;
private int subStringIterator;
private byte tickCount = 0x0;
private int totalDialogueLength;
private int type;
private NewDialogue(Keys keys) {
lines = new ArrayList<Map.Entry<String, Boolean>>();
completedLines = new ArrayList<String>();
this.subStringIterator = 0;
this.lineLength = 0;
this.totalDialogueLength = 0;
this.nextFlag = false;
this.simpleQuestionFlag = false;
this.simpleSpeechFlag = false;
this.scrollFlag = false;
this.scrollDistance = 0;
this.nextTick = 0x0;
this.lineIterator = 0;
this.input = keys;
this.showDialog = false;
this.type = 0;
this.yesNoCursorPosition = true;
this.yesNoAnswerFlag = null; // Default
}
public NewDialogue(NewDialogue dialogue) {
// Deep copy
this.completedLines = new ArrayList<String>();
for (String s: dialogue.completedLines)
this.completedLines.add(s);
this.input = dialogue.input;
this.lineIterator = dialogue.lineIterator;
this.lineLength = dialogue.lineLength;
this.lines = new ArrayList<Map.Entry<String, Boolean>>();
for (Map.Entry<String, Boolean> e: dialogue.lines)
this.lines.add(new AbstractMap.SimpleEntry<String, Boolean>(e.getKey(), e.getValue()));
this .nextFlag = dialogue.nextFlag;
this .simpleQuestionFlag = dialogue.simpleQuestionFlag;
this .simpleSpeechFlag = dialogue.simpleSpeechFlag;
this .yesNoCursorPosition = dialogue.yesNoCursorPosition;
this .yesNoAnswerFlag = dialogue.yesNoAnswerFlag;
this.nextTick = dialogue.nextTick;
this.scrollDistance = dialogue.scrollDistance;
this.scrollFlag = dialogue.scrollFlag;
this.showDialog = dialogue.showDialog;
this.subStringIterator = dialogue.subStringIterator;
this. tickCount = dialogue.tickCount;
this.totalDialogueLength = dialogue.totalDialogueLength;
this.type = dialogue.type;
}
public Boolean getAnswerToSimpleQuestion() {
if (this.yesNoAnswerFlag == null)
return null;
return this.yesNoAnswerFlag.booleanValue();
}
public int getDialogueType() {
return this.type;
}
public boolean isDialogueCompleted() {
return (this.lineIterator >= this.lines.size());
}
public boolean isScrolling() {
return this.scrollFlag;
}
public void resetDialogue() {
this.subStringIterator = 0;
this.nextFlag = false;
this.simpleQuestionFlag = false;
this.simpleSpeechFlag = false;
this.scrollFlag = false;
this.scrollDistance = 0;
this.nextTick = 0x0;
this.lineIterator = 0;
this.showDialog = true;
this.yesNoCursorPosition = true;
this.yesNoAnswerFlag = null;
this.completedLines.clear();
}
public boolean isDialogueTextSet() {
return !this.lines.isEmpty();
}
public boolean isShowingDialog() {
return this.showDialog;
}
public void render(BaseScreen output, Graphics graphics) {
render(output, graphics, 0, 6, 9, 2);
}
public void render(BaseScreen output, Graphics graphics, int x, int y, int w, int h) {
if (x < 0)
x = 0;
if (x > 9)
x = 9;
if (y < 0)
y = 0;
if (y > 8)
y = 8;
if (x + w > 9)
w = 9 - x;
if (y + h > 8)
h = 8 - y;
if (showDialog) {
switch (this.type) {
case DIALOGUE_SPEECH: {
renderDialogBackground(output, x, y, w, h);
renderDialogBorderBox(output, x, y, w, h);
if (this.nextFlag && this.nextTick < 0x8)
output.blit(Art.dialogue_next, MainComponent.GAME_WIDTH - 16, MainComponent.GAME_HEIGHT - 8);
Graphics2D g2d = output.getBufferedImage().createGraphics();
renderText(g2d);
g2d.dispose();
break;
}
case DIALOGUE_QUESTION: {
renderDialogBackground(output, x, y, w, h);
renderDialogBorderBox(output, x, y, w, h);
if (this.simpleQuestionFlag && !this.nextFlag) {
renderDialogBackground(output, 7, 3, 2, 2);
renderDialogBorderBox(output, 7, 3, 2, 2);
// Offset by -3 for the Y axis.
output.blit(Art.dialogue_pointer, MainComponent.GAME_WIDTH - Tile.WIDTH * 3 + 8, this.yesNoCursorPosition ? (Tile.HEIGHT * 4 - 3) : (Tile.HEIGHT * 5 - 3));
}
else if (!this.simpleQuestionFlag && (this.nextFlag && this.nextTick < 0x8))
output.blit(Art.dialogue_next, MainComponent.GAME_WIDTH - 16, MainComponent.GAME_HEIGHT - 8);
Graphics2D g2d = output.getBufferedImage().createGraphics();
renderText(g2d);
renderYesNoAnswerText(g2d);
g2d.dispose();
break;
}
case DIALOGUE_ALERT: {
renderDialogBackground(output, x, y, w, h);
renderDialogBorderBox(output, x, y, w, h);
Graphics2D g2d = output.getBufferedImage().createGraphics();
renderText(g2d);
g2d.dispose();
break;
}
}
}
}
public void renderYesNoAnswerText(Graphics g) {
if (this.simpleQuestionFlag) {
g.setFont(Art.font.deriveFont(8f));
g.setColor(Color.black);
final int X = Tile.WIDTH * 8;
final int YES_HEIGHT = Tile.HEIGHT * 4 + 4;
final int NO_HEIGHT = Tile.HEIGHT * 5 + 4;
try {
g.drawString("YES", X, YES_HEIGHT);
g.drawString("NO", X, NO_HEIGHT);
}
catch (Exception e) {
}
}
}
/**
* <p>Update method for NewDialogue class.</p>
*
* <p><b>WARNING</b> : The code content of this tick() method is deliberately setup and designed in such a way that it
* replicates the dialogues in Gen 1 and Gen 2 Pokémon games. May require a heavy amount of refactoring/rewriting.</p>
* */
public void tick() {
int count = 0;
try {
for (int i = 0; i < this.lineIterator; i++) {
count += this.lines.get(i).getKey().length();
if (i != this.lines.size() - 1)
count += 1;
}
}
catch (Exception e) {
count = 0;
}
if (count < this.totalDialogueLength && (!this.nextFlag && !this.scrollFlag)) {
tickCount++;
if (tickCount > 0x1)
tickCount = 0x0;
}
else if (this.nextFlag) {
switch (this.type) {
case DIALOGUE_QUESTION: {
if (count >= this.totalDialogueLength) {
this.simpleQuestionFlag = true;
this.nextFlag = false;
this.scrollFlag = false;
}
else {
this.nextTick++;
if (this.nextTick > 0xE)
this.nextTick = 0x0;
}
break;
}
case DIALOGUE_SPEECH: {
this.nextTick++;
if (this.nextTick > 0xE)
this.nextTick = 0x0;
break;
}
case DIALOGUE_ALERT: {
this.nextTick++;
if (this.nextTick > 0xE)
this.nextTick = 0x0;
break;
}
}
}
else if (count >= this.totalDialogueLength) {
if (this.lineIterator >= this.lines.size()) {
switch (this.type) {
case DIALOGUE_QUESTION:
this.simpleQuestionFlag = true;
this.scrollFlag = false;
this.nextFlag = false;
break;
case DIALOGUE_SPEECH:
this.simpleSpeechFlag = true;
this.nextFlag = false;
break;
case DIALOGUE_ALERT:
this.simpleSpeechFlag = true;
this.nextFlag = false;
break;
}
}
else {
Map.Entry<String, Boolean> entry = this.lines.get(this.lineIterator);
this.completedLines.add(entry.getKey());
this.lineIterator++;
switch (this.type) {
case DIALOGUE_SPEECH:
this.nextFlag = true;
break;
case DIALOGUE_QUESTION:
this.simpleQuestionFlag = true;
this.nextFlag = true;
break;
case DIALOGUE_ALERT:
this.nextFlag = true;
break;
}
}
}
try {
if (!this.nextFlag && !this.simpleQuestionFlag && !this.scrollFlag) {
if (tickCount == 0x0) {
if (!this.scrollFlag)
this.subStringIterator++;
if (this.subStringIterator >= this.lineLength) {
this.subStringIterator %= this.lineLength;
Map.Entry<String, Boolean> entry = this.lines.get(this.lineIterator);
this.completedLines.add(entry.getKey());
this.lineIterator++;
}
if (this.completedLines.size() == 2) {
if (!this.scrollFlag) {
switch (this.type) {
case DIALOGUE_SPEECH:
this.nextFlag = true;
break;
case DIALOGUE_QUESTION:
// Must get to the end of the entire dialogue before asking for answers.
if (this.lineIterator >= this.lines.size()) {
this.simpleQuestionFlag = true;
this.nextFlag = false;
}
else
this.nextFlag = true;
break;
case DIALOGUE_ALERT:
this.nextFlag = true;
break;
}
}
}
}
// Speeds up text speed.
if (this.type != DIALOGUE_ALERT) {
if ((input.Z.keyStateDown && !(input.Z.lastKeyState)) || (input.SLASH.keyStateDown && !input.SLASH.lastKeyState)) {
if (this.subStringIterator >= this.lineLength - 2) {
input.Z.lastKeyState = true;
input.SLASH.lastKeyState = true;
}
else if (this.subStringIterator < this.lineLength) {
this.subStringIterator++;
}
}
else if ((input.X.keyStateDown && !(input.X.lastKeyState)) || (input.PERIOD.keyStateDown && !input.PERIOD.lastKeyState)) {
if (this.subStringIterator < this.lineLength - 1) {
this.subStringIterator = this.lineLength - 1;
input.X.lastKeyState = true;
input.PERIOD.lastKeyState = true;
}
}
}
}
else if (this.simpleQuestionFlag && !this.nextFlag && !this.scrollFlag) {
// Making sure this doesn't trigger the "Next" arrow.
this.nextFlag = false;
if ((this.input.up.keyStateDown && !this.input.up.lastKeyState) || (this.input.W.keyStateDown && !this.input.W.lastKeyState)) {
this.input.up.lastKeyState = true;
this.input.W.lastKeyState = true;
// Made it consistent with Inventory's menu selection, where it doesn't wrap around.
this.yesNoCursorPosition = !this.yesNoCursorPosition;
}
else if ((this.input.down.keyStateDown && !this.input.down.lastKeyState) || (this.input.S.keyStateDown && !this.input.S.lastKeyState)) {
this.input.down.lastKeyState = true;
this.input.S.lastKeyState = true;
// Made it consistent with Inventory's menu selection, where it doesn't wrap around.
this.yesNoCursorPosition = !this.yesNoCursorPosition;
}
if ((this.input.Z.keyStateDown && !this.input.Z.lastKeyState) || (this.input.SLASH.keyStateDown && !this.input.SLASH.lastKeyState)) {
this.input.Z.lastKeyState = true;
this.input.SLASH.lastKeyState = true;
// The answer to simple questions have already been set by UP and DOWN.
this.yesNoAnswerFlag = this.yesNoCursorPosition;
this.simpleQuestionFlag = false;
this.closeDialog();
}
else if ((this.input.X.keyStateDown && !this.input.X.lastKeyState) || (this.input.PERIOD.keyStateDown && !this.input.PERIOD.lastKeyState)) {
this.input.X.lastKeyState = true;
this.input.PERIOD.lastKeyState = true;
// Always negative for cancel button.
this.yesNoAnswerFlag = false;
this.yesNoCursorPosition = false;
this.simpleQuestionFlag = false;
this.closeDialog();
}
}
else if (this.simpleSpeechFlag) {
//Handles only the simplest forms of dialogues.
if ((input.Z.keyStateDown && !(input.Z.lastKeyState) || (input.SLASH.keyStateDown && !input.SLASH.lastKeyState))) {
input.Z.lastKeyState = true;
input.SLASH.lastKeyState = true;
this.closeDialog();
}
}
else {
if ((input.Z.keyStateDown && !(input.Z.lastKeyState)) || (input.SLASH.keyStateDown && !(input.SLASH.lastKeyState))
|| (input.X.keyStateDown && !(input.X.lastKeyState)) || (input.PERIOD.keyStateDown && !(input.PERIOD.lastKeyState))) {
input.Z.lastKeyState = true;
input.SLASH.lastKeyState = true;
input.X.lastKeyState = true;
input.PERIOD.lastKeyState = true;
switch (this.type) {
case DIALOGUE_SPEECH:
this.nextFlag = false;
this.scrollFlag = true;
break;
case DIALOGUE_QUESTION:
// Must get to the end of the entire dialogue before asking questions.
this.simpleQuestionFlag = false;
this.nextFlag = false;
this.scrollFlag = true;
break;
case DIALOGUE_ALERT:
this.nextFlag = false;
this.scrollFlag = true;
break;
}
}
else if (this.scrollFlag) {
if (this.lineIterator >= this.lines.size()) {
switch (this.type) {
case DIALOGUE_QUESTION:
this.simpleQuestionFlag = true;
this.scrollFlag = false;
this.nextFlag = false;
break;
case DIALOGUE_SPEECH:
this.closeDialog();
return;
case DIALOGUE_ALERT:
this.closeDialog();
return;
}
}
else
this.scrollDistance += 8;
}
}
}
catch (Exception e) {
if (this.lineIterator >= this.lines.size()) {
if (this.scrollFlag) {
this.closeDialog();
}
else {
switch (this.type) {
case DIALOGUE_SPEECH:
case DIALOGUE_ALERT:
this.nextFlag = true;
break;
case DIALOGUE_QUESTION:
this.simpleQuestionFlag = true;
this.nextFlag = false;
break;
}
}
}
}
}
public boolean yesNoQuestionHasBeenAnswered() {
return this.yesNoAnswerFlag != null;
}
public void closeDialog() {
this.showDialog = false;
}
public void clearDialogueLines() {
if (!this.lines.isEmpty())
this.lines.clear();
}
private void renderText(Graphics g) {
final int X = 8;
final int Y1 = 120;
final int Y2 = 136;
final Rectangle rect = new Rectangle(X, Y1 - Tile.HEIGHT, MainComponent.GAME_WIDTH - 16, Tile.HEIGHT * 2);
g.setFont(Art.font.deriveFont(8f));
g.setColor(Color.black);
String string = null;
try {
switch (this.completedLines.size()) {
case 0:
// None completed.
string = this.lines.get(this.lineIterator).getKey();
if (this.subStringIterator > string.length()) {
g.drawString(string.substring(0, string.length()), X, Y1);
this.subStringIterator = this.lineLength;
}
else
g.drawString(string.substring(0, this.subStringIterator), X, Y1);
break;
case 1:
// One line completed.
g.drawString(this.completedLines.get(0), X, Y1);
string = this.lines.get(this.lineIterator).getKey();
if (this.subStringIterator > string.length()) {
g.drawString(string.substring(0, string.length()), X, Y2);
this.subStringIterator = this.lineLength;
}
else
g.drawString(string.substring(0, this.subStringIterator), X, Y2);
break;
case 2:
// Two lines completed.
if (!this.scrollFlag) {
g.drawString(this.completedLines.get(0), X, Y1);
g.drawString(this.completedLines.get(1), X, Y2);
}
else {
// Time to scroll.
// DEBUG: Needs testing to see if there's any problem with
// it.
Graphics g_clipped = g.create();
g_clipped.setClip(rect.x, rect.y, rect.width, rect.height);
g_clipped.drawString(this.completedLines.get(0), X, Y1 - scrollDistance);
g_clipped.drawString(this.completedLines.get(1), X, Y2 - scrollDistance);
if (tickCount == 0x0) {
if (scrollDistance >= Y2 - Y1) {
this.scrollFlag = false;
this.scrollDistance = 0;
this.subStringIterator = 0;
this.completedLines.remove(0);
this.lines.get(this.lineIterator).setValue(true);
}
}
g_clipped.dispose();
}
break;
}
}
catch (Exception e) {
}
}
public static NewDialogue createEmptyDialogue() {
return new NewDialogue(MainComponent.getMainInput());
}
public static NewDialogue createText(String dialogue, int length, int type, boolean lock) {
NewDialogue dialogues = new NewDialogue(MainComponent.getMainInput());
dialogues.lines = toLines(dialogue, length);
dialogues.lineLength = length;
dialogues.totalDialogueLength = dialogue.length();
dialogues.type = type;
dialogues.showDialog = true;
if (lock) {
if (!Player.isMovementsLocked())
Player.lockMovements();
}
return dialogues;
}
public static void renderDialogBox(BaseScreen output, int x, int y, int centerWidth, int centerHeight) {
output.blit(Art.dialogue_top_left, x * Tile.WIDTH, y * Tile.HEIGHT);
for (int i = 0; i < centerWidth - 1; i++) {
output.blit(Art.dialogue_top, ((x + 1) * Tile.WIDTH) + (i * Tile.WIDTH), y * Tile.HEIGHT);
}
output.blit(Art.dialogue_top_right, (x + centerWidth) * Tile.WIDTH, y * Tile.HEIGHT);
for (int j = 0; j < centerHeight - 1; j++) {
output.blit(Art.dialogue_left, x * Tile.WIDTH, ((y + 1) * Tile.HEIGHT) + j * Tile.HEIGHT);
for (int i = 0; i < centerWidth - 1; i++) {
output.blit(Art.dialogue_background, ((x + 1) * Tile.WIDTH) + (i * Tile.WIDTH), ((y + 1) * Tile.HEIGHT) + j * Tile.HEIGHT);
}
output.blit(Art.dialogue_right, (x + centerWidth) * Tile.WIDTH, ((y + 1) * Tile.HEIGHT) + j * Tile.HEIGHT);
}
output.blit(Art.dialogue_bottom_left, x * Tile.WIDTH, ((y + centerHeight) * Tile.HEIGHT));
for (int i = 0; i < centerWidth - 1; i++) {
output.blit(Art.dialogue_bottom, ((x + 1) * Tile.WIDTH) + (i * Tile.WIDTH), ((y + centerHeight) * Tile.HEIGHT));
}
output.blit(Art.dialogue_bottom_right, (x + centerWidth) * Tile.WIDTH, ((y + centerHeight) * Tile.HEIGHT));
}
public static ArrayList<Map.Entry<NewDialogue, Integer>> loadDialogues(String filename) {
ArrayList<Map.Entry<NewDialogue, Integer>> result = new ArrayList<Map.Entry<NewDialogue, Integer>>();
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(NewDialogue.class.getClassLoader().getResourceAsStream(filename)));
String line;
String[] tokens;
int dialogueID = 0;
NewDialogue temp = null;
while ((line = reader.readLine()) != null) {
if (line.startsWith("#")) {
// Dialogue ID
tokens = line.split("#");
dialogueID = Integer.valueOf(tokens[1]);
}
else if (line.startsWith("@")) {
// Dialogue message
tokens = line.split("@");
temp = NewDialogue.createText(tokens[1], NewDialogue.MAX_STRING_LENGTH, NewDialogue.DIALOGUE_SPEECH, false);
}
else if (line.startsWith("-")) {
// Dialogue delimiter
Map.Entry<NewDialogue, Integer> entry = new AbstractMap.SimpleEntry<NewDialogue, Integer>(temp, dialogueID);
result.add(entry);
}
}
}
catch (Exception e) {
return null;
}
return result;
}
public static ArrayList<Map.Entry<String, Boolean>> toLines(String all, final int regex) {
ArrayList<Map.Entry<String, Boolean>> lines = new ArrayList<>();
String[] words = all.split("\\s");
String line = "";
int length = 0;
for (String w : words) {
if (length + w.length() + 1 > regex) {
if (w.length() >= regex) {
line += w;
lines.add(new AbstractMap.SimpleEntry<String, Boolean>(line, false));
line = "";
continue;
}
lines.add(new AbstractMap.SimpleEntry<String, Boolean>(line, false));
line = "";
length = 0;
}
if (length > 0) {
line += " ";
length += 1;
}
line += w;
length += w.length();
}
if (line.length() > 0)
lines.add(new AbstractMap.SimpleEntry<String, Boolean>(line, false));
return lines;
}
private static void renderDialogBackground(BaseScreen output, int x, int y, int centerWidth, int centerHeight) {
for (int j = 0; j < centerHeight - 1; j++) {
for (int i = 0; i < centerWidth - 1; i++) {
output.blit(Art.dialogue_background, ((x + 1) * Tile.WIDTH) + (i * Tile.WIDTH), ((y + 1) * Tile.HEIGHT) + j * Tile.HEIGHT);
}
}
}
private static void renderDialogBorderBox(BaseScreen output, int x, int y, int centerWidth, int centerHeight) {
output.blit(Art.dialogue_top_left, x * Tile.WIDTH, y * Tile.HEIGHT);
for (int i = 0; i < centerWidth - 1; i++) {
output.blit(Art.dialogue_top, ((x + 1) * Tile.WIDTH) + (i * Tile.WIDTH), y * Tile.HEIGHT);
}
output.blit(Art.dialogue_top_right, (x + centerWidth) * Tile.WIDTH, y * Tile.HEIGHT);
for (int j = 0; j < centerHeight - 1; j++) {
output.blit(Art.dialogue_left, x * Tile.WIDTH, ((y + 1) * Tile.HEIGHT) + j * Tile.HEIGHT);
output.blit(Art.dialogue_right, (x + centerWidth) * Tile.WIDTH, ((y + 1) * Tile.HEIGHT) + j * Tile.HEIGHT);
}
output.blit(Art.dialogue_bottom_left, x * Tile.WIDTH, ((y + centerHeight) * Tile.HEIGHT));
for (int i = 0; i < centerWidth - 1; i++) {
output.blit(Art.dialogue_bottom, ((x + 1) * Tile.WIDTH) + (i * Tile.WIDTH), ((y + centerHeight) * Tile.HEIGHT));
}
output.blit(Art.dialogue_bottom_right, (x + centerWidth) * Tile.WIDTH, ((y + centerHeight) * Tile.HEIGHT));
}
}