made anti chat spam work
thank you maniaplay for making me find out about magic algorithm
This commit is contained in:
parent
bf7fd7df93
commit
402ddef938
4 changed files with 51 additions and 291 deletions
|
@ -21,6 +21,8 @@ repositories {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation 'org.apache.commons:commons-text:1.10.0' // Use the version that best suits your project
|
||||||
|
|
||||||
// To change the versions see the gradle.properties file
|
// To change the versions see the gradle.properties file
|
||||||
minecraft "com.mojang:minecraft:${project.minecraft_version}"
|
minecraft "com.mojang:minecraft:${project.minecraft_version}"
|
||||||
mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2"
|
mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2"
|
||||||
|
|
|
@ -43,8 +43,9 @@ public class Configuration {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class AntiSpam {
|
public static class AntiSpam {
|
||||||
public int matchingMessagesToBeSpam = 50;
|
public int matchingMessagesToBeSpam = 20;
|
||||||
public int messageTimeInTicks = 100;
|
public int messageTimeInTicks = 100;
|
||||||
public int blockedPatternDurationInTicks = 72000;
|
public int blockedPatternDurationInTicks = 0; //TODO: remove
|
||||||
|
public int minimumLevenshteinDistanceToBeSpam = 20;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,10 +51,11 @@ public class ChatHudMixin {
|
||||||
listener.chatMessageReceived(message);
|
listener.chatMessageReceived(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (AntiChatSpamModule.BlockedPattern pattern : AntiChatSpamModule.instance.patterns) {
|
// for (AntiChatSpamModule.BlockedPattern pattern : AntiChatSpamModule.instance.patterns) {
|
||||||
if(pattern.pattern().matcher(message.getString()).matches()) ci.cancel();
|
// if(pattern.pattern().matcher(message.getString()).matches()) ci.cancel();
|
||||||
}
|
// }
|
||||||
new AntiChatSpamModule.ChatMessage(message.getString());
|
AntiChatSpamModule.ChatMessage cmessage = new AntiChatSpamModule.ChatMessage(message.getString());
|
||||||
|
if(cmessage.hidden) ci.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,320 +1,76 @@
|
||||||
package land.chipmunk.chipmunkmod.testclient.modules.utility;
|
package land.chipmunk.chipmunkmod.testclient.modules.utility;
|
||||||
|
|
||||||
import land.chipmunk.chipmunkmod.ChipmunkMod;
|
import land.chipmunk.chipmunkmod.ChipmunkMod;
|
||||||
import land.chipmunk.chipmunkmod.Configuration;
|
|
||||||
import land.chipmunk.chipmunkmod.testclient.gui.components.Module;
|
import land.chipmunk.chipmunkmod.testclient.gui.components.Module;
|
||||||
import land.chipmunk.chipmunkmod.util.Chat;
|
|
||||||
import land.chipmunk.chipmunkmod.util.Debug;
|
import land.chipmunk.chipmunkmod.util.Debug;
|
||||||
import land.chipmunk.chipmunkmod.util.Executor;
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
|
||||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
|
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
|
||||||
import net.minecraft.client.MinecraftClient;
|
import org.apache.commons.text.similarity.LevenshteinDistance;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.concurrent.Future;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
public class AntiChatSpamModule extends Module {
|
public class AntiChatSpamModule extends Module {
|
||||||
public static AntiChatSpamModule instance = new AntiChatSpamModule();
|
public static AntiChatSpamModule instance = new AntiChatSpamModule();
|
||||||
public ArrayList<ChatMessage> messages = new ArrayList<>();
|
public ArrayList<ChatMessage> messages = new ArrayList<>();
|
||||||
public ArrayList<BlockedPattern> patterns = new ArrayList<>();
|
|
||||||
private int threadCheckStateTimer = 0;
|
|
||||||
private static final String debugCallerPrefix = "AntiChatSpam.";
|
private static final String debugCallerPrefix = "AntiChatSpam.";
|
||||||
private static final String debugTickedCaller = debugCallerPrefix + "tick";
|
private static final String debugTickedCaller = debugCallerPrefix + "tick";
|
||||||
private static final String debugThreadCaller = debugCallerPrefix + "thread";
|
private static final String debugLevenshteinDistanceCaller = debugCallerPrefix + "levenshtein_distance";
|
||||||
private static final String debugRegexProcessCaller = debugCallerPrefix + "regex_process";
|
private static final String debugLevenshteinDistanceCallerSpamless = debugCallerPrefix + "levenshtein_distance_spamless";
|
||||||
private static final String debugRegexOutputCaller = debugCallerPrefix + "regex_output";
|
|
||||||
private static final String debugComparableStringCaller = debugCallerPrefix + "comparable_string";
|
|
||||||
public AntiChatSpamModule() {
|
public AntiChatSpamModule() {
|
||||||
super("Anti chat spam");
|
super("Anti chat spam");
|
||||||
ClientTickEvents.END_CLIENT_TICK.register(client -> {
|
ClientTickEvents.END_CLIENT_TICK.register(client -> {
|
||||||
for (int i = 0; i < messages.size(); i++) {
|
for (int i = 0; i < messages.size(); i++) {
|
||||||
Debug.debug("Ticked message: "+messages.get(i).content, debugTickedCaller);
|
Debug.debug("Ticked message: " + messages.get(i).content, debugTickedCaller);
|
||||||
messages.get(i).tick();
|
messages.get(i).tick();
|
||||||
}
|
}
|
||||||
for (int i = 0; i < patterns.size(); i++) {
|
|
||||||
Debug.debug("Ticked pattern: "+patterns.get(i).pattern().toString(), debugTickedCaller);
|
|
||||||
patterns.get(i).tick();
|
|
||||||
}
|
|
||||||
if(threadCheckStateTimer >= 10) {
|
|
||||||
Debug.debug("Thread done: "+thread.isDone(), debugThreadCaller);
|
|
||||||
threadCheckStateTimer = -1;
|
|
||||||
}
|
|
||||||
threadCheckStateTimer++;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// ig make theredaadd thingy here because else it says it's null and stuff or idk
|
|
||||||
thread = Executor.submit(() -> {
|
|
||||||
boolean interrupted = false;
|
|
||||||
String caller = debugThreadCaller;
|
|
||||||
while(!interrupted) {
|
|
||||||
Debug.debug("woke up", caller);
|
|
||||||
while(threadQueue.size()>0) {
|
|
||||||
threadQueue.get(0).run();
|
|
||||||
threadQueue.remove(0);
|
|
||||||
Debug.debug("ran a thing in the queue now "+threadQueue.size()+" left", caller);
|
|
||||||
}
|
|
||||||
Debug.debug("going zzz", caller);
|
|
||||||
try {
|
|
||||||
Thread.sleep(50);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
interrupted = true;
|
|
||||||
Debug.debug("Thread interrupted by exception!", caller);
|
|
||||||
} catch (Exception e) {
|
|
||||||
Debug.debug("Got an exception or idk printing trace", caller);
|
|
||||||
e.printStackTrace();
|
|
||||||
interrupted = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Debug.debug("out of while loop or idk bye ig!!!", caller);
|
|
||||||
});
|
|
||||||
|
|
||||||
} // 72000 ticks for expiry
|
|
||||||
// and ig remember messages for 100 ticks
|
|
||||||
|
|
||||||
public static class ComparableString{
|
|
||||||
@Getter @Setter private String value;
|
|
||||||
public int index = 0;
|
|
||||||
public ComparableString(String value) {
|
|
||||||
Debug.debug("Created comparable string with value: "+value, debugComparableStringCaller);
|
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
public String getChar() {
|
|
||||||
if (index < 0 || value.length() < index) throw new RuntimeException("Index is out of bounds! (INDEX:"+index+", LENGTH:"+value.length()+")");
|
|
||||||
String toReturn = switch (value.charAt(index)) {
|
|
||||||
case '.' -> "\\.";
|
|
||||||
case '[' -> "\\[";
|
|
||||||
case ']' -> "\\]";
|
|
||||||
case '(' -> "\\(";
|
|
||||||
case ')' -> "\\)";
|
|
||||||
case '*' -> "\\*";
|
|
||||||
case '?' -> "\\?";
|
|
||||||
default -> String.valueOf(value.charAt(index));
|
|
||||||
};
|
|
||||||
Debug.debug("Got character: "+toReturn, debugComparableStringCaller);
|
|
||||||
return toReturn;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void repeat(RepeatableRunnable runnable) {
|
|
||||||
while(index<value.length()) {
|
|
||||||
Debug.debug("Repeated some with index "+index, debugComparableStringCaller);
|
|
||||||
if(runnable.run(index)) {
|
|
||||||
Debug.debug("Brokne the repeat stuffthingy index "+index, debugComparableStringCaller);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
Debug.debug("while loop ended (index: "+index+" length: "+value.length()+")", debugComparableStringCaller);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Pattern getPattern(ComparableString firstString, ComparableString secondString) {
|
|
||||||
Debug.debug("Started getting pattern", debugRegexProcessCaller);
|
|
||||||
Debug.debug("First string: "+firstString.value(), debugRegexProcessCaller);
|
|
||||||
Debug.debug("Second string: "+secondString.value(), debugRegexProcessCaller);
|
|
||||||
ArrayList<CharacterInPattern> characters = new ArrayList<>();
|
|
||||||
firstString.index = 0;
|
|
||||||
firstString.repeat(i -> {
|
|
||||||
Debug.debug("[A] Char index "+i+": "+firstString.getChar(), debugRegexProcessCaller);
|
|
||||||
secondString.index = 0;
|
|
||||||
secondString.repeat(j -> {
|
|
||||||
Debug.debug(" [B] Char index "+j+": "+secondString.getChar(), debugRegexProcessCaller);
|
|
||||||
if(firstString.getChar().equals(secondString.getChar())) {
|
|
||||||
Debug.debug("OMGOMG THEY MATCH adding to characters array", debugRegexProcessCaller);
|
|
||||||
characters.add(new CharacterInPattern(firstString.getChar(), i, j));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
Debug.debug("--- SORTING CHARS ---", debugRegexProcessCaller);
|
|
||||||
// sort based on index sum
|
|
||||||
Comparator<CharacterInPattern> comparator = new Comparator<CharacterInPattern>() {
|
|
||||||
@Override
|
|
||||||
public int compare(CharacterInPattern character1, CharacterInPattern character2) {
|
|
||||||
return Integer.compare(character1.indexesSum(), character2.indexesSum());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
characters.sort(comparator);
|
|
||||||
for (CharacterInPattern character : characters) {
|
|
||||||
Debug.debug(character.indexesSum() + " - " + character.character(), debugRegexProcessCaller);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Debug.debug("--- CONFIRMING & PLACING CHARACTERS ---", debugRegexProcessCaller);
|
|
||||||
ArrayList<CharacterInPattern> confirmedCharacters = new ArrayList<>();
|
|
||||||
for (int k = 0, charactersSize = characters.size(); k < charactersSize; k++) {
|
|
||||||
CharacterInPattern character = characters.get(k);
|
|
||||||
Debug.debug("Finding if B has been placed", debugRegexProcessCaller);
|
|
||||||
//find if index B has been placed somewhere
|
|
||||||
AtomicBoolean isIndexBTaken = new AtomicBoolean(false);
|
|
||||||
confirmedCharacters.forEach(character1 -> {
|
|
||||||
if (character.indexB() == character1.indexB()) isIndexBTaken.set(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
// if B has already been placed somewhere, resume analysis
|
|
||||||
if (isIndexBTaken.get()) {
|
|
||||||
Debug.debug("It has! resuping analysis", debugRegexProcessCaller);
|
|
||||||
firstString.index = character.indexA();
|
|
||||||
secondString.index = character.indexB()+1;
|
|
||||||
Debug.debug("[A] Char index " + firstString.index + ": " + firstString.getChar(), debugRegexProcessCaller);
|
|
||||||
secondString.repeat(j -> {
|
|
||||||
Debug.debug(" [B] Char index " + j + ": " + secondString.getChar(), debugRegexProcessCaller);
|
|
||||||
if (firstString.getChar().equals(secondString.getChar())) {
|
|
||||||
Debug.debug("OMGOMG THEY MATCH adding to characters array", debugRegexProcessCaller);
|
|
||||||
// characters.add(new CharacterInPattern(firstString.getChar(), firstString.index, j));
|
|
||||||
for (int i = 0, sortedCharactersSize = characters.size(); i < sortedCharactersSize; i++) {
|
|
||||||
CharacterInPattern sortedCharacter = characters.get(i);
|
|
||||||
if (sortedCharacter.indexesSum() < j) {
|
|
||||||
characters.add(i, new CharacterInPattern(firstString.getChar(), firstString.index, j));
|
|
||||||
Debug.debug("Added to sorted chars at index " + i, debugRegexProcessCaller);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if you can place it somewhere in the output
|
|
||||||
// this means that one index must never be greater than another character's if the other is not.
|
|
||||||
|
|
||||||
// if it's the first character, add it no matter what. No need to check lol
|
|
||||||
if (character.indexesSum() == 0) {
|
|
||||||
Debug.debug("First char so putting it at start of output", debugRegexProcessCaller);
|
|
||||||
confirmedCharacters.add(character);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if output is empty and it's not the first character, add a .*? and then the character
|
|
||||||
if (confirmedCharacters.isEmpty()) {
|
|
||||||
Debug.debug("Not first char but output empty so putting .*? then char at start", debugRegexProcessCaller);
|
|
||||||
confirmedCharacters.add(new CharacterInPattern(".*?", 0, 0));
|
|
||||||
confirmedCharacters.add(character);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Debug.debug("Not at the start so checking where tf it is", debugRegexProcessCaller);
|
|
||||||
// end of special cases now just check if u can place it somewhere
|
|
||||||
AtomicInteger index = new AtomicInteger();
|
|
||||||
boolean rightAfter = false;
|
|
||||||
for (int i = 0; i < confirmedCharacters.size(); i++) {
|
|
||||||
CharacterInPattern character1 = confirmedCharacters.get(i);
|
|
||||||
Debug.debug("Checking character " + character1.character(), debugRegexProcessCaller);
|
|
||||||
// right after the character
|
|
||||||
if (character.indexA() == character1.indexA() + 1 && character.indexB() == character1.indexB() + 1) {
|
|
||||||
Debug.debug("It's right after the char so placing it there", debugRegexProcessCaller);
|
|
||||||
i++;
|
|
||||||
if (i >= confirmedCharacters.size()) confirmedCharacters.add(character);
|
|
||||||
else confirmedCharacters.add(i, character);
|
|
||||||
rightAfter = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (int i = 0; i < confirmedCharacters.size() && !rightAfter; i++) {
|
|
||||||
CharacterInPattern character1 = confirmedCharacters.get(i);
|
|
||||||
Debug.debug("Checking character " + character1.character(), debugRegexProcessCaller);
|
|
||||||
//somewhere else after che character
|
|
||||||
if (character1.indexA() < character.indexA() && character1.indexB() < character.indexB()) {
|
|
||||||
Debug.debug("It's somewhere after the char so putting it there with .*? in front", debugRegexProcessCaller);
|
|
||||||
if (index.get() >= confirmedCharacters.size()) {
|
|
||||||
i++;
|
|
||||||
confirmedCharacters.add(new CharacterInPattern(".*?", character1.indexA() + 1, character.indexB() + 1));
|
|
||||||
i++;
|
|
||||||
confirmedCharacters.add(character);
|
|
||||||
} else {
|
|
||||||
i++;
|
|
||||||
confirmedCharacters.add(i, new CharacterInPattern(".*?", character1.indexA() + 1, character.indexB() + 1));
|
|
||||||
i++;
|
|
||||||
confirmedCharacters.add(i, character);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
index.getAndIncrement();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
StringBuilder regex = new StringBuilder();
|
|
||||||
for (int i = 0, confirmedCharactersSize = confirmedCharacters.size(); i < confirmedCharactersSize; i++) {
|
|
||||||
CharacterInPattern c = confirmedCharacters.get(i);
|
|
||||||
regex.append(c.character);
|
|
||||||
}
|
|
||||||
Debug.debug("Returning regex "+regex, debugRegexProcessCaller);
|
|
||||||
Debug.debug("Returning regex '"+regex+"' for strings:", debugRegexOutputCaller);
|
|
||||||
Debug.debug(" - "+firstString, debugRegexOutputCaller);
|
|
||||||
Debug.debug(" - "+secondString, debugRegexOutputCaller);
|
|
||||||
return Pattern.compile(regex.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class CharacterInPattern {
|
|
||||||
@Getter private final String character;
|
|
||||||
@Getter private final int indexA;
|
|
||||||
@Getter private final int indexB; // setter so I can change it while resuming analysis later on
|
|
||||||
@Getter private final int indexesSum;
|
|
||||||
public CharacterInPattern(String character, int indexA, int indexB) {
|
|
||||||
this.character = character;
|
|
||||||
this.indexA = indexA;
|
|
||||||
this.indexB = indexB;
|
|
||||||
indexesSum = indexA + indexB;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface RepeatableRunnable{
|
|
||||||
/**
|
|
||||||
* @param i the current index
|
|
||||||
* @return if the loop should break
|
|
||||||
*/
|
|
||||||
boolean run(int i); // to repeat for the length of the string ig
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class ChatMessage {
|
public static class ChatMessage {
|
||||||
@Getter private final String content;
|
@Getter
|
||||||
|
private final String content;
|
||||||
|
public boolean hidden = false;
|
||||||
public int timer = ChipmunkMod.CONFIG.antiSpam.messageTimeInTicks;
|
public int timer = ChipmunkMod.CONFIG.antiSpam.messageTimeInTicks;
|
||||||
|
|
||||||
public ChatMessage(String content) {
|
public ChatMessage(String content) {
|
||||||
this.content = content;
|
this.content = content;
|
||||||
|
|
||||||
threadQueue.add(() -> {
|
ArrayList<ChatMessage> chatMessages = instance.messages;
|
||||||
ArrayList<ChatMessage> chatMessages = instance.messages;
|
int similarMessages = 0;
|
||||||
for (int i = 0; i < chatMessages.size(); i++) {
|
LevenshteinDistance ld = new LevenshteinDistance(); // thanks maniaplay fo r teaching me about levenshtein distance
|
||||||
ChatMessage message = chatMessages.get(i);
|
for (int i = 0; i < chatMessages.size(); i++) {
|
||||||
Pattern pattern = getPattern(new ComparableString(this.content()), new ComparableString(message.content()));
|
ChatMessage message = chatMessages.get(i);
|
||||||
int matching = 0;
|
int distance = ld.apply(this.content(), message.content());
|
||||||
for (ChatMessage message1 : instance.messages) {
|
Debug.debug("Distance: " + distance, debugLevenshteinDistanceCaller);
|
||||||
if (pattern.matcher(message1.content()).matches()) matching++;
|
if (distance <= ChipmunkMod.CONFIG.antiSpam.minimumLevenshteinDistanceToBeSpam) similarMessages++;
|
||||||
}
|
// Pattern pattern = getPattern(new ComparableString(this.content()), new ComparableString(message.content()));
|
||||||
if (matching >= ChipmunkMod.CONFIG.antiSpam.matchingMessagesToBeSpam) {
|
// int matching = 0;
|
||||||
instance.patterns.add(new BlockedPattern(pattern));
|
// ArrayList<ChatMessage> chatMessageArrayList = instance.messages;
|
||||||
}
|
// for (int j = 0; j < chatMessageArrayList.size(); j++) {
|
||||||
}
|
// ChatMessage message1 = chatMessageArrayList.get(j);
|
||||||
});
|
// if (pattern.matcher(message1.content()).matches()) matching++;
|
||||||
|
// }
|
||||||
|
// if (matching >= ChipmunkMod.CONFIG.antiSpam.matchingMessagesToBeSpam) {
|
||||||
|
// instance.patterns.add(new BlockedPattern(pattern));
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
Debug.debug("Similar messages: " + similarMessages, debugLevenshteinDistanceCaller);
|
||||||
|
Debug.debug("Similar messages: " + similarMessages, debugLevenshteinDistanceCallerSpamless);
|
||||||
|
if (similarMessages >= ChipmunkMod.CONFIG.antiSpam.matchingMessagesToBeSpam) hidden = true;
|
||||||
|
Debug.debug("Hidden: " + hidden, debugLevenshteinDistanceCaller);
|
||||||
|
Debug.debug("Hidden: " + hidden, debugLevenshteinDistanceCallerSpamless);
|
||||||
instance.messages.add(this);
|
instance.messages.add(this);
|
||||||
}
|
// threadQueue.add(() -> {
|
||||||
public void tick() {timer--; if(timer <= 0) instance.messages.remove(this);}
|
// // code above used to be here but i cant decide if i should show it or not depending on the thread cuz i cant make it wait
|
||||||
}
|
// });
|
||||||
|
|
||||||
public static class BlockedPattern {
|
|
||||||
@Getter private final Pattern pattern;
|
|
||||||
public int timer = ChipmunkMod.CONFIG.antiSpam.blockedPatternDurationInTicks;
|
|
||||||
public BlockedPattern(Pattern pattern) {
|
|
||||||
this.pattern = pattern;
|
|
||||||
instance.patterns.add(this);
|
|
||||||
Chat.send("[AntiSpam] Blocked pattern "+pattern.toString());
|
|
||||||
}
|
}
|
||||||
public void tick() {timer--; if(timer <= 0) instance.patterns.remove(this);}
|
|
||||||
}
|
|
||||||
public static ArrayList<Runnable> threadQueue = new ArrayList<>();
|
|
||||||
public static Future<?> thread;
|
|
||||||
|
|
||||||
public static void debug(String message) {
|
public void tick() {
|
||||||
Debug.debug(message, "AntiChatSpam");
|
timer--;
|
||||||
|
if (timer <= 0) instance.messages.remove(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} // holy shit 200 lines and i havent ran this once this is not going to go well
|
}
|
||||||
// it did not go well
|
|
Loading…
Reference in a new issue