/*
* This file is part of the Illarion project.
*
* Copyright © 2015 - Illarion e.V.
*
* Illarion is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Illarion is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
package illarion.easynpc;
import illarion.common.types.ServerCoordinate;
import illarion.easynpc.EasyNpcScript.Line;
import illarion.easynpc.data.*;
import illarion.easynpc.parsed.ParsedData;
import illarion.easynpc.writer.LuaWritable;
import javolution.util.FastTable;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.text.Normalizer;
import java.text.Normalizer.Form;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
/**
* This class contains a parsed NPC structure. A detailed and analyzed version of a easyNPC script that is easily
* maintainable by this application and easy to convert into a LUA or a easyNPC script.
*
* @author Martin Karing <nitram@illarion.org>
*/
public final class ParsedNpc implements Iterable<ParsedData> {
/**
* This support class is used to store all information regarding a error that was found in the script. It stores
* the position where it was found as well as the description of the error.
*/
public static final class Error implements Comparable<Error> {
/**
* The line the error occurred at.
*/
private final int lineNumber;
/**
* The character the error occurred at.
*/
private final int characterNumber;
/**
* The error message.
*/
private final String message;
/**
* Create this error message.
*
* @param problemLine the line that caused this problem
* @param errorMsg the message of this error
*/
Error(int problemLine, int problemChar, String errorMsg) {
lineNumber = problemLine;
characterNumber = problemChar;
message = errorMsg;
}
/**
* Compare method used to sort the errors.
*/
@Override
public int compareTo(@Nonnull Error o) {
int lineNrCompare = Integer.compare(lineNumber, o.lineNumber);
if (lineNrCompare == 0) {
return Integer.compare(characterNumber, o.characterNumber);
}
return lineNrCompare;
}
@Override
public int hashCode() {
return lineNumber + (characterNumber << 13);
}
@Override
public boolean equals(Object o) {
if (super.equals(o)) {
return true;
}
if (o instanceof Error) {
Error errObj = (Error) o;
return (errObj.lineNumber == lineNumber) && (errObj.characterNumber == characterNumber) &&
errObj.message.equals(message);
}
return false;
}
/**
* Get the line the error occurred on.
*
* @return the line with the error
*/
public int getLine() {
return lineNumber;
}
/**
* Get the message describing the error.
*
* @return the error message
*/
public String getMessage() {
return message;
}
}
/**
* The town affiliation of this NPC.
*/
private Towns affiliation;
/**
* The list of authors who wrote this script.
*/
private FastTable<String> authors;
/**
* The auto introduce flag of the NPC. If this is set to false, the NPC will not introduce automatically.
*/
private boolean autoIntroduce = true;
/**
* The language the NPC is talking by default.
*/
@Nullable
private CharacterLanguage defaultLanguage;
/**
* A list of errors occurred while parsing this NPC.
*/
private List<Error> errors;
/**
* Flag that stores if the error messages are sorted correctly or not.
*/
private boolean errorOrderDirty;
/**
* The job of the NPC.
*/
private String job;
/**
* The list of languages the NPC is able to speak.
*/
private Collection<CharacterLanguage> languages;
/**
* The German version of the message displayed in case a character looks at
* the NPC.
*/
private String lookAtDe;
/**
* The English version of the message displayed in case a character looks at the NPC.
*/
private String lookAtUs;
/**
* The additional data the NPC contains.
*/
private List<ParsedData> npcData;
/**
* The direction the NPC is looking at.
*/
private CharacterDirection npcDir;
/**
* The name of this NPC.
*/
private String npcName;
/**
* The name of the module of the NPC and in the same consequence the name of the file the NPC needs to be stored
* in.
*/
@Nullable
private String moduleName;
/**
* The position of this NPC.
*/
private ServerCoordinate npcPos;
/**
* The race of the NPC.
*/
private CharacterRace npcRace;
/**
* The sex of this NPC.
*/
private CharacterSex npcSex;
/**
* The German version of the message displayed in case the player uses the NPC.
*/
private String useMsgDe;
/**
* The English version of the message displayed in case the player uses the NPC.
*/
private String useMsgUs;
/**
* The German version of the message the NPC speaks in case a character talks to him in a invalid language.
*/
private String wrongLanguageDe;
/**
* The English version of the message the NPC speaks in case a character talks to him in a invalid language.
*/
private String wrongLanguageUs;
/**
* Constructor for the class that creates the required object to store all
* data for this NPC.
*/
public ParsedNpc() {
affiliation = Towns.None;
job = "none"; //$NON-NLS-1$
errorOrderDirty = false;
defaultLanguage = null;
}
/**
* Add one name to the list of authors.
*
* @param author the name of the author to add
*/
public void addAuthor(String author) {
if (authors == null) {
authors = new FastTable<>();
}
authors.add(author);
}
/**
* Add a error to the list of errors that occurred while this NPC was
* parsed.
*
* @param line the line the error occurred at
* @param message the message describing the error
*/
@Deprecated
public void addError(@Nonnull Line line, String message) {
addError(line.getLineNumber(), message);
}
/**
* Add a error to the list of errors that occurred while this NPC was
* parsed.
*
* @param line the line the error occurred at
* @param message the message describing the error
*/
void addError(int line, String message) {
addError(line, 0, message);
}
/**
* Add a error to the list of errors that occurred while this NPC was
* parsed.
*
* @param line the line the error occurred at
* @param message the message describing the error
*/
public void addError(int line, int charNr, String message) {
if (errors == null) {
errors = new FastTable<>();
}
errors.add(new Error(line, charNr, message));
errorOrderDirty = true;
}
/**
* Add a language to the list of languages the NPC is able to speak.
*
* @param lang the language to add
*/
public void addLanguage(CharacterLanguage lang) {
if (languages == null) {
languages = new FastTable<>();
}
if (!languages.contains(lang)) {
languages.add(lang);
}
}
/**
* Add some complex parsed data to this NPC.
*
* @param data the data to add
*/
public void addNpcData(ParsedData data) {
if (npcData == null) {
npcData = new FastTable<>();
}
npcData.add(data);
}
/**
* Get the affiliation of this NPC.
*
* @return the affiliation of this NPC
*/
public Towns getAffiliation() {
if (affiliation == null) {
return Towns.None;
}
return affiliation;
}
/**
* Get the list of the names of the authors of this script.
*
* @return a array of the author names
*/
@Nonnull
public Collection<String> getAuthors() {
if (authors == null) {
return Collections.emptyList();
}
return Collections.unmodifiableCollection(authors);
}
/**
* Get the current value of the auto introduce flag.
*
* @return the auto introduce flag
*/
public boolean getAutoIntroduce() {
return autoIntroduce;
}
/**
* Get the amount of data entries that are written in this NPC.
*
* @return the amount of data entries
*/
public int getDataCount() {
if (npcData == null) {
return 0;
}
return npcData.size();
}
/**
* Get the language this character is speaking by default.
*
* @return the language this character is speaking by default
*/
@Nullable
public CharacterLanguage getDefaultLanguage() {
if (defaultLanguage == null) {
return CharacterLanguage.common;
}
return defaultLanguage;
}
/**
* Get the English version of the text that is displayed in case the player
* looks at the NPC.
*
* @return the message
*/
public String getEnglishLookat() {
if (lookAtUs == null) {
return "This is a NPC who's developer was too lazy to type in a description.";
}
return lookAtUs;
}
/**
* Get the English version of the text displayed in case the player uses the
* NPC.
*
* @return the message
*/
public String getEnglishUse() {
if (useMsgUs == null) {
return "Do not touch me!";
}
return useMsgUs;
}
/**
* Get the English version of the message that is displayed in case the
* player talks to the NPC in the wrong language.
*
* @return the message
*/
public String getEnglishWrongLang() {
if (wrongLanguageDe == null) {
return "#me looks at you confused.";
}
return wrongLanguageUs;
}
/**
* Get the error stored at a index.
*
* @param index the index of the error that is requested
* @return the error stored with this index
*/
public Error getError(int index) {
if (errors == null) {
throw new IndexOutOfBoundsException("No errors stored.");
}
if (errorOrderDirty) {
Collections.sort(errors);
errorOrderDirty = false;
}
return errors.get(index);
}
/**
* Get the amount of errors found in this script.
*
* @return the amount of errors in this script.
*/
public int getErrorCount() {
if (errors == null) {
return 0;
}
return errors.size();
}
/**
* Get the German version of the text that is displayed in case the player
* looks at the NPC.
*
* @return the message
*/
public String getGermanLookat() {
if (lookAtDe == null) {
return "Das ist ein NPC dessen Entwickler zu faul war eine Beschreibung einzutragen.";
}
return lookAtDe;
}
/**
* Get the German version of the text displayed in case the player uses the
* NPC.
*
* @return the message
*/
public String getGermanUse() {
if (useMsgDe == null) {
return "Fass mich nicht an!";
}
return useMsgDe;
}
/**
* Get the German version of the message that is displayed in case the
* player talks to the NPC in the wrong language.
*
* @return the message
*/
public String getGermanWrongLang() {
if (wrongLanguageDe == null) {
return "#me schaut dich verwirrt an.";
}
return wrongLanguageDe;
}
/**
* Get the Job of this NPC.
*
* @return the job of this NPC
*/
public String getJob() {
if (job == null) {
return "unspecified"; //$NON-NLS-1$
}
return job;
}
/**
* Get the languages this NPC is able to speak.
*
* @return the array of languages this NPC is able to speak
*/
@Nonnull
public CharacterLanguage[] getLanguages() {
if (languages == null) {
languages = new FastTable<>();
}
if (languages.isEmpty()) {
languages.add(CharacterLanguage.common);
switch (getNpcRace()) {
case human:
languages.add(CharacterLanguage.human);
break;
case dwarf:
languages.add(CharacterLanguage.dwarf);
break;
case elf:
languages.add(CharacterLanguage.elf);
break;
case orc:
languages.add(CharacterLanguage.orc);
break;
case halfling:
languages.add(CharacterLanguage.halfling);
break;
case lizardman:
languages.add(CharacterLanguage.lizard);
break;
}
CharacterLanguage[] result = languages.toArray(new CharacterLanguage[languages.size()]);
languages.clear();
return result;
}
return languages.toArray(new CharacterLanguage[languages.size()]);
}
/**
* Get the LUA data sources for the writer.
*
* @param index the index of the data
* @return the data at the selected index
*/
public LuaWritable getLuaData(int index) {
return npcData.get(index);
}
/**
* Get the correct name for the LUA script file according to the data saved
* in this script.
*
* @return the correct lua script file
*/
@Nonnull
public String getLuaFilename() {
return getModuleName() + ".lua";
}
@Nonnull
public String getModuleName() {
if (moduleName == null) {
return convertToModuleName(getNpcName()).toLowerCase();
}
return moduleName;
}
@Nonnull
public static String convertToModuleName(@Nonnull CharSequence string) {
return Normalizer.normalize(string, Form.NFC).replaceAll("[^\\p{ASCII}]", "").replace(' ', '_');
}
public void setModuleName(@Nullable String moduleName) {
this.moduleName = moduleName;
}
/**
* Get the looking direction of this NPC.
*
* @return the looking direction of this NPC
*/
public CharacterDirection getNpcDir() {
if (npcDir == null) {
return CharacterDirection.north;
}
return npcDir;
}
/**
* Get the name of this NPC.
*
* @return the name of this NPC
*/
public String getNpcName() {
if (npcName == null) {
if (getNpcSex() == CharacterSex.male) {
return "John Doe"; //$NON-NLS-1$
}
return "Jane Doe"; //$NON-NLS-1$
}
return npcName;
}
/**
* Get the location of this NPC.
*
* @return the current location of this NPC.
*/
public ServerCoordinate getNpcPos() {
if (npcPos == null) {
return new ServerCoordinate(0, 0, 0);
}
return npcPos;
}
/**
* Get the race of this NPC.
*
* @return The current set race of this NPC
*/
public CharacterRace getNpcRace() {
if (npcRace == null) {
return CharacterRace.human;
}
return npcRace;
}
/**
* Get the sex value of this NPC.
*
* @return the sex value of this NPC
*/
public CharacterSex getNpcSex() {
if (npcSex == null) {
return CharacterSex.male;
}
return npcSex;
}
/**
* Check if the script contains any errors.
*
* @return {@code true} if there are any errors stored in this parseNPC
*/
public boolean hasErrors() {
return (errors != null) && !errors.isEmpty();
}
/**
* Set the affiliation of this NPC.
*
* @param aff the affiliation of this NPC
*/
public void setAffiliation(Towns aff) {
affiliation = aff;
}
/**
* Set the value for the auto introduce flag.
*
* @param newValue the new value for the auto introduce flag
*/
public void setAutoIntroduce(boolean newValue) {
autoIntroduce = newValue;
}
/**
* Set the language this character is speaking by default.
*
* @param lang the language this character is speaking by default
*/
public void setDefaultLanguage(@Nullable CharacterLanguage lang) {
defaultLanguage = lang;
}
/**
* Set the English version of the text that is displayed in case the player
* looks at the NPC.
*
* @param msg the message
*/
public void setEnglishLookAt(String msg) {
lookAtUs = msg;
}
/**
* Set the English version of the text displayed in case the player uses the
* NPC.
*
* @param msg the message
*/
public void setEnglishUse(String msg) {
useMsgUs = msg;
}
/**
* Set the English version of the message that is displayed in case the
* player talks to the NPC in the wrong language.
*
* @param msg the message
*/
public void setEnglishWrongLang(String msg) {
wrongLanguageUs = msg;
}
/**
* Set the German version of the text that is displayed in case the player
* looks at the NPC.
*
* @param msg the message
*/
public void setGermanLookAt(String msg) {
lookAtDe = msg;
}
/**
* Set the German version of the text displayed in case the player uses the
* NPC.
*
* @param msg the message
*/
public void setGermanUse(String msg) {
useMsgDe = msg;
}
/**
* Set the German version of the message that is displayed in case the
* player talks to the NPC in the wrong language.
*
* @param msg the message
*/
public void setGermanWrongLang(String msg) {
wrongLanguageDe = msg;
}
/**
* Set the job of this NPC.
*
* @param newJob the job of this NPC
*/
public void setJob(String newJob) {
job = newJob;
}
/**
* Set the new direction of this NPC.
*
* @param newNpcDir the new looking direction of this NPC
*/
public void setNpcDir(CharacterDirection newNpcDir) {
npcDir = newNpcDir;
}
/**
* Set the name of this NPC.
*
* @param newNpcName the new name of this NPC.
*/
public void setNpcName(String newNpcName) {
npcName = newNpcName;
}
/**
* Set the position of this NPC.
*
* @param newNpcPos the new position of this NPC
*/
public void setNpcPos(@Nonnull ServerCoordinate newNpcPos) {
npcPos = newNpcPos;
}
/**
* Set the race of this NPC to a new value.
*
* @param newNpcRace the race of this NPC
*/
public void setNpcRace(CharacterRace newNpcRace) {
npcRace = newNpcRace;
}
/**
* Set the sex of this NPC to a new value.
*
* @param newNpcSex the new sex value of the NPC
*/
public void setNpcSex(CharacterSex newNpcSex) {
npcSex = newNpcSex;
}
@Nonnull
@Override
public Iterator<ParsedData> iterator() {
return npcData.iterator();
}
}