04.02.2016, 11:40
Thanks for the feedback! 
@SystemX:
If you need some explanations, examples or statements why using Shoebill, you can write me, i really can recommend Shoebill, easily, fast and lots of external function support.
@FreAkeD:
Thanks, the only animation bug occurs when you are try to display a message like this:
, because i trimmed the text for this reason, that i did not know that textdraws will display whitespaces at the end, but i can fix this soon.
@valych:
Yeah, i try to reach the limits of SAMP and its performance so i play with textdraws and calculation, but this chat works great and have a advanced animation
You easily can change the appearance of the textdraw in createTextdraw(), it depends on the taste of every person^^
I improved the code to pre-calculate the string snippets and handle the chat with a better displayed performance!
ChatMessage.java
Chat.java

@SystemX:
If you need some explanations, examples or statements why using Shoebill, you can write me, i really can recommend Shoebill, easily, fast and lots of external function support.
@FreAkeD:
Thanks, the only animation bug occurs when you are try to display a message like this:
Код:
"WHITE SPACES"
@valych:
Yeah, i try to reach the limits of SAMP and its performance so i play with textdraws and calculation, but this chat works great and have a advanced animation

You easily can change the appearance of the textdraw in createTextdraw(), it depends on the taste of every person^^
I improved the code to pre-calculate the string snippets and handle the chat with a better displayed performance!
ChatMessage.java
Код:
/**
*
*/
package me.alf21.util.chat;
import java.util.regex.Matcher;
import net.gtaun.shoebill.constant.TextDrawAlign;
import net.gtaun.shoebill.constant.TextDrawFont;
import net.gtaun.shoebill.data.Color;
import net.gtaun.shoebill.object.Destroyable;
import net.gtaun.shoebill.object.Player;
import net.gtaun.shoebill.object.PlayerTextdraw;
import net.gtaun.shoebill.object.Timer;
/**
* @author Alf21
*
*/
public class ChatMessage implements Destroyable{
private PlayerTextdraw textdraw;
private int slot;
private Timer timer;
private boolean sliding;
private String text;
private int count;
private Player player;
private Long time;
private String[] slideInStrings;
private String[] slideOutStrings;
public ChatMessage(Player player, String text, int slot) {
this.player = player;
this.text = removeNonColorCodes(text).trim();
this.slot = slot;
this.count = 1;
createTextdraw(0,0);
//initialize the string snippets to slide the text in
slideInStrings = new String[getTextLength()];
slideOutStrings = new String[getTextLength()-1];
//calculate the string snippets
int currentEndIndex = 0;
for(int i=0; i<slideInStrings.length; i++) {
currentEndIndex++;
currentEndIndex = cutTextIn(currentEndIndex);
slideInStrings[i] = text.substring(0, currentEndIndex);
}
currentEndIndex = text.length();
for(int i=0; i<slideOutStrings.length; i++) {
currentEndIndex--;
currentEndIndex = cutTextOut(currentEndIndex);
slideOutStrings[i] = text.substring(0, currentEndIndex);
}
time = System.currentTimeMillis();
}
public void slideIn() {
sliding = true;
reset();
final int length = slideInStrings.length;
final int interval = (int) Chat.slideTime/length;
timer = Timer.create(interval, length, (factualInterval) -> {
textdraw.hide();
textdraw.setText(slideInStrings[count-1]);
textdraw.show();
if(count == length) {
sliding = false;
timer.stop();
}
count++;
});
timer.start();
}
public void slideOut() {
sliding = true;
reset();
final int length = slideOutStrings.length;
final int interval = (int) Chat.slideTime/length;
timer = Timer.create(interval, length, (factualInterval) -> {
textdraw.hide();
textdraw.setText(slideOutStrings[count-1]);
textdraw.show();
if(count == length) {
sliding = false;
timer.stop();
}
count++;
});
timer.start();
}
public void moveUp() {
reset();
final int maxCount = 15;
final int interval = (int) Chat.slideTime/maxCount;
timer = Timer.create(interval, maxCount, (factualInterval) -> {
textdraw.hide();
textdraw.destroy();
createTextdraw(0, (float) -count*(15f/(float) maxCount));
textdraw.show();
if(count == maxCount) {
slot++;
timer.stop();
}
count++;
});
timer.start();
}
public void hide() {
textdraw.hide();
}
public void show() {
textdraw.show();
}
private void reset() {
if(timer != null)
timer.destroy();
count = 1;
}
private void createTextdraw(float x, float y) {
textdraw = PlayerTextdraw.create(player, 630+x, 385-15*slot+y);
textdraw.setText(text);
textdraw.setAlignment(TextDrawAlign.RIGHT);
textdraw.setBackgroundColor(Color.BLACK);
textdraw.setFont(TextDrawFont.get(1));
textdraw.setLetterSize(0.5f, 1);
textdraw.setColor(Color.WHITE);
textdraw.setOutlineSize(1);
textdraw.setProportional(true);
textdraw.setSelectable(false);
}
/**
* @return the sliding
*/
public boolean isSliding() {
return sliding;
}
/**
* @param sliding the sliding to set
*/
public void setSliding(boolean sliding) {
this.sliding = sliding;
}
/**
* @return the time when textdraw was created
*/
public Long getTime() {
return time;
}
/**
* @return the textdraw
*/
public PlayerTextdraw getTextdraw() {
return textdraw;
}
/**
* @return the player
*/
public Player getPlayer() {
return player;
}
/* (non-Javadoc)
* @see net.gtaun.shoebill.object.Destroyable#destroy()
*/
@Override
public void destroy() {
if(timer != null) {
if(timer.isRunning())
timer.stop();
timer.destroy();
timer = null;
}
if(textdraw != null) {
textdraw.hide();
textdraw.destroy();
textdraw = null;
}
}
/* (non-Javadoc)
* @see net.gtaun.shoebill.object.Destroyable#isDestroyed()
*/
@Override
public boolean isDestroyed() {
if(timer != null || textdraw != null)
return false;
return true;
}
private String removeNonColorCodes(String text) {
final String prefixOfColorCodes = "COLOR"; //The prefix of any color code like "COLOR_RED" <- HERE>> if String == "": "_RED"
final String connectionOfColorCodes = "\u005F"; //The connection label or Unicode, here: "\u005F" == "_" <- HERE>> if String == "": "COLORRED"
String replaceStr = prefixOfColorCodes + connectionOfColorCodes;
text = text.replaceAll("\u007E" + "w" + "\u007E", replaceStr + "WHITE");
text = text.replaceAll("\u007E" + "r" + "\u007E", replaceStr + "RED");
text = text.replaceAll("\u007E" + "g" + "\u007E", replaceStr + "GREEN");
text = text.replaceAll("\u007E" + "b" + "\u007E", replaceStr + "DARK" + connectionOfColorCodes + "BLUE");
text = text.replaceAll("\u007E" + "y" + "\u007E", replaceStr + "YELLOW");
text = text.replaceAll("\u007E" + "p" + "\u007E", replaceStr + "PURPLE");
text = text.replaceAll("\u007E" + "l" + "\u007E", replaceStr + "BLACK");
text = text.replaceAll("\u007E" + "h" + "\u007E", replaceStr + "LIGHTER");
text = text.replaceAll("\u007E+([\\w[\u003C\u003E&&[\\S]]]?)\u007E", ""); //replace unknown colors and Commands
text = text.replaceAll("\u007E", ""); //replace other "~"
text = text.replaceAll(replaceStr + "WHITE", "\u007E" + "w" + "\u007E");
text = text.replaceAll(replaceStr + "RED", "\u007E" + "r" + "\u007E");
text = text.replaceAll(replaceStr + "GREEN", "\u007E" + "g" + "\u007E");
text = text.replaceAll(replaceStr + "DARK" + connectionOfColorCodes + "BLUE", "\u007E" + "b" + "\u007E");
text = text.replaceAll(replaceStr + "YELLOW", "\u007E" + "y" + "\u007E");
text = text.replaceAll(replaceStr + "PURPLE", "\u007E" + "p" + "\u007E");
text = text.replaceAll(replaceStr + "BLACK", "\u007E" + "l" + "\u007E");
text = text.replaceAll(replaceStr + "LIGHTER", "\u007E" + "h" + "\u007E");
return text;
}
private int cutTextIn(int endIndex) {
int currentEndIndex = endIndex;
String string = text.substring(0, currentEndIndex);
while(string.trim().length() < string.length()){
currentEndIndex++;
string = text.substring(0, currentEndIndex);
}
//check whether string contains color codes
Matcher matcher = Chat.colorStringPattern.matcher(text);
if(matcher.find()) {
int matcherState = 0;
matcher = Chat.colorStringEndPattern.matcher(string);
Matcher matcher2 = Chat.wrongColorStringPattern.matcher(string);
if(matcher.find() && !matcher2.find()) {
matcher = Chat.colorStringEndPattern.matcher(string); //update the matcher
matcherState = 3;
}
else {
matcher = Chat.tildeLetterPattern.matcher(string);
if(matcher.find()) {
matcher = Chat.tildeLetterPattern.matcher(string); //update the matcher
matcherState = 4;
}
else {
matcher = Chat.tildePattern.matcher(string); //update the matcher
matcherState = 5;
}
}
while(matcher.find()) {
//also check whether color code is after this color code
if(currentEndIndex+matcherState <= text.length()) {
currentEndIndex += matcherState;
string = text.substring(0, currentEndIndex);
while(string.trim().length() < string.length()){
currentEndIndex++;
string = text.substring(0, currentEndIndex);
}
matcher = Chat.colorStringEndPattern.matcher(string); //update the matcher
if(!matcher.find()) {
currentEndIndex -= 2;
string = text.substring(0, currentEndIndex);
}
else
matcher = Chat.colorStringEndPattern.matcher(string); //update the matcher
matcherState = 3;
}
}
string = text.substring(0, currentEndIndex);
while(string.trim().length() < string.length()){
currentEndIndex++;
string = text.substring(0, currentEndIndex);
}
matcher2 = Chat.tildePattern.matcher(string);
if(matcher2.find()) {
if(currentEndIndex+3 <= text.length())
currentEndIndex += 3;
}
}
return currentEndIndex;
}
private int cutTextOut(int endIndex) {
int currentEndIndex = endIndex;
String string = text.substring(0, currentEndIndex);
while(string.trim().length() < string.length()){
currentEndIndex--;
string = text.substring(0, currentEndIndex);
}
//check whether string contains color codes
Matcher matcher = Chat.colorStringPattern.matcher(text);
if(matcher.find()) {
int matcherState = 0;
matcher = Chat.colorStringEndPattern.matcher(string);
if(matcher.find()) {
matcher = Chat.colorStringEndPattern.matcher(string); //update the matcher
matcherState = 3;
}
else {
matcher = Chat.tildeLetterPattern.matcher(string);
String string2 = text.substring(0, currentEndIndex+1);
Matcher matcher2 = Chat.colorStringEndPattern.matcher(string2);
if(matcher.find() && matcher2.find()) {
matcher = Chat.tildeLetterPattern.matcher(string); //update the matcher
matcherState = 2;
}
else {
matcher = Chat.tildePattern.matcher(string); //update the matcher
matcherState = 1;
}
}
while(matcher.find()) {
//also check whether color code is before this color code
if(currentEndIndex-matcherState > 0) {
currentEndIndex -= matcherState;
string = text.substring(0, currentEndIndex);
while(string.trim().length() < string.length()){
currentEndIndex--;
string = text.substring(0, currentEndIndex);
}
matcher = Chat.colorStringEndPattern.matcher(string); //update the matcher
matcherState = 3;
}
}
string = text.substring(0, currentEndIndex);
while(string.trim().length() < string.length()){
currentEndIndex--;
string = text.substring(0, currentEndIndex);
}
}
return currentEndIndex;
}
private int getTextLength() {
int length = text.replace(" ", "").length();
Matcher matcher = Chat.colorStringPattern.matcher(text);
while(matcher.find()) {
length -= 3;
}
return length;
}
}
Код:
/**
*
*/
package me.alf21.util.chat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;
import net.gtaun.shoebill.object.Destroyable;
import net.gtaun.shoebill.object.Player;
import net.gtaun.shoebill.object.Timer;
/**
* @author Alf21
*/
public class Chat implements Destroyable {
private Timer timer;
public static final int updateDelay = 50;
public static final int displayTime = 5000;
public static final int slideTime = 500;
public static final Pattern colorStringPattern = Pattern.compile("\u007E+([\\w[\u003C\u003E&&[\\S]]]{1})\u007E"), //...~r~...
colorStringEndPattern = Pattern.compile("\u007E+([\\w[\u003C\u003E&&[\\S]]]{1})\u007E\\z"), //...~r~|
tildeLetterPattern = Pattern.compile("\u007E+([\\w[\u003C\u003E&&[\\S]]]{1})\\z"), //...~r|
tildePattern = Pattern.compile("\u007E\\z"), //...~|
wrongColorStringPattern = Pattern.compile("\u007E+([\\w[\u003C\u003E&&[\\S]]]{1})\u007E+([\\w[\u003C\u003E&&[\\S]]]{1})\u007E\\z"); //...~s~r|
private ArrayList<ChatMessage> chatMessages;
private ChatMessage currentChatMessage;
private Map<Player, String> queue;
public Chat() {
reset();
initializeTimer();
}
public void sendMessage(Player player, String text) {
queue.put(player, text);
if(!timer.isRunning())
timer.start();
}
private void reset() {
if(chatMessages != null && !chatMessages.isEmpty()) {
chatMessages.stream().forEach((chatMessage) -> {
chatMessage.hide();
chatMessage.destroy();
});
}
chatMessages = new ArrayList<ChatMessage>();
queue = new HashMap<Player, String>();
currentChatMessage = null;
}
private void initializeTimer() {
timer = Timer.create(updateDelay, (factualInterval) -> {
Long systemTime = System.currentTimeMillis();
ArrayList<ChatMessage> removeMessages = new ArrayList<ChatMessage>(); //TODO Solution with Iterator
if(chatMessages != null && !chatMessages.isEmpty()) {
chatMessages.stream().forEach((chatMessage) -> {
if(systemTime-chatMessage.getTime() >= slideTime) {
if(currentChatMessage != null) {
currentChatMessage = null;
}
}
if(systemTime-chatMessage.getTime() >= displayTime+2*slideTime) {
removeMessages.add(chatMessage);
}
else if(systemTime-chatMessage.getTime() >= displayTime+slideTime) {
if(!chatMessage.isSliding()) {
chatMessage.slideOut();
}
}
});
removeMessages.stream().forEach((removeMessage) -> {
removeChatMessage(removeMessage);
});
removeMessages.clear();
}
if(currentChatMessage == null && !queue.isEmpty()) {
Map.Entry<Player,String> entry = queue.entrySet().iterator().next();
Player player = entry.getKey();
currentChatMessage = new ChatMessage(player, entry.getValue(), 0);
queue.remove(player);
chatMessages.add(currentChatMessage);
}
if(currentChatMessage != null) {
if(chatMessages.size() > 1) {
chatMessages.stream().forEach((chatMessage) -> {
if(chatMessage != currentChatMessage)
chatMessage.moveUp();
});
}
if(!currentChatMessage.isSliding()) {
currentChatMessage.slideIn();
}
}
if(chatMessages != null && chatMessages.isEmpty()) {
timer.stop();
reset();
}
});
}
private void removeChatMessage(ChatMessage chatMessage) {
if(chatMessages.contains(chatMessage)) {
chatMessages.remove(chatMessage);
}
chatMessage.destroy();
}
/* (non-Javadoc)
* @see net.gtaun.shoebill.object.Destroyable#destroy()
*/
@Override
public void destroy() {
if(timer != null) {
if(timer.isRunning())
timer.stop();
timer.destroy();
timer = null;
}
if(currentChatMessage != null) {
currentChatMessage.destroy();
currentChatMessage = null;
}
if(chatMessages != null) {
chatMessages.stream().forEach((chatMessage) -> chatMessage.destroy());
chatMessages = null;
}
if(queue != null) {
queue = null;
}
}
/* (non-Javadoc)
* @see net.gtaun.shoebill.object.Destroyable#isDestroyed()
*/
@Override
public boolean isDestroyed() {
if(timer != null || currentChatMessage != null || chatMessages != null || queue != null)
return false;
return true;
}
//TODO: add background box and increase if new message and resize on message destroy / destroy if there is no message
}

