Skip to content

Commit

Permalink
Add emotes!
Browse files Browse the repository at this point in the history
Emotes are implemented as unicode characters from the Private Use Area, limited to 2048 unique emotes.
Some of the FontRenderer's methods have been hooked into to implement custom rendering of those characters.
Currently, twitch global, BetterTTV global & channel and FrankerFaceZ global & channel emotes are supported - both static & animated. (twitch channel emotes coming soon™)
Emotes are downloaded when the game is loading, in the postInit phase and when the twitch client is started or a channel is joined.
Emotes are cached in the `streamchatmod/emotes` directories in your minecraft directory.
It is preferred to download emotes at load-time, since loading emotes while playing will make the game freeze for a couple seconds, as dynamic textures cannot be loaded in a non-main thread.
Hovering over an emote in chat will show extended information about that emote.
Rendering of each emote type can be toggled using `/twitch emote`. The amount of used emote slots is also shown there.
Note that disabling emote rendering does NOT disable downloading & indexing of emotes!
  • Loading branch information
mini-bomba committed Dec 21, 2021
1 parent 055a8fa commit 25b13d4
Show file tree
Hide file tree
Showing 22 changed files with 1,627 additions and 24 deletions.
33 changes: 26 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,26 +42,45 @@ All Twitch related configuration commands can be viewed by running `/twitch help
## Moderation from Minecraft

Some moderation commands are available from in-game. They are listed below:
* `/twitch delete <channel> <message id>`: Delete the specified message. It is supposed to be entered by clicking on a message in-game, since there is no easy way to get the ID of a message.
* `/twitch delete <channel> <message id>`: Delete the specified message. It is supposed to be entered by clicking on a
message in-game, since there is no easy way to get the ID of a message.
* `/twitch clearchat`: Clears the currently selected Twitch Chat. Does not clear the in-game chat; use F3+D to do that.
* `/twitch timeout <user> <duration> [reason]`: Timeouts the given user in the currently selected Twitch Chat. There is no un-timeout command, time the user out for 1 second to do that.
* `/twitch timeout <user> <duration> [reason]`: Timeouts the given user in the currently selected Twitch Chat. There is
no un-timeout command, time the user out for 1 second to do that.
* `/twitch ban <user> [reason]`: Bans the given user in the currently selected Twitch Chat.
* `/twitch unban <user>`: Unbans the given user in the currently selected Twitch Chat.
* Tip 19: *Ban an annoying humanoid from your stream with `/twitch ban <user> [reason]` (ex: `/twitch ban Rajdo Being an annoying humanoid.`)*
* Tip 19: *Ban an annoying humanoid from your stream with `/twitch ban <user> [reason]` (
ex: `/twitch ban Rajdo Being an annoying humanoid.`)*

**Confirmation messages for moderation commands have been recently implemented in commit `45b4be14`**

## Other features

### Emotes

StreamChatMod allows Minecraft to render Twitch, BetterTTV and FrankerFaceZ emotes in your chat, including animated
ones!

Emotes are downloaded & cached mostly during game startup, but will also be updated when restarting the Twitch client or
joining new chats.<br>
Note that loading emotes while playing will cause the game to freeze for a few seconds. (like when reloading a texture
pack)

You can also prevent each type of emotes from being rendered in an image form, using the `/twitch emote` command.

![Emote feature showcase](https://cdn.upload.systems/uploads/0DfjY2HF.png)

### Automatic update checker

The mod automatically checks for updates on startup.

You can also check for updates every 15 minutes, by running `/twitch updatechecker enable` (or `/twitch updatechecker disable` to disable).
This is automatically enabled on prerelease builds.
You can also check for updates every 15 minutes, by running `/twitch updatechecker enable` (
or `/twitch updatechecker disable` to disable). This is automatically enabled on prerelease builds.

### Chat formatting
By default, any formatting codes are "neutralized" (the `§` character is replaced with `&`).
However, this can be changed: you may either allow everyone to use formatting codes, or only subscribers, VIPs and moderators.

By default, any formatting codes are "neutralized" (the `§` character is replaced with `&`). However, this can be
changed: you may either allow everyone to use formatting codes, or only subscribers, VIPs and moderators.

When enabled, the inverse of the "neutralization" happens: the `&` is replaced with `§`, allowing viewers to use Essentials(X)-like color codes.

Expand Down
53 changes: 47 additions & 6 deletions src/main/java/me/mini_bomba/streamchatmod/StreamChatMod.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import com.github.twitch4j.helix.domain.*;
import com.github.twitch4j.tmi.domain.Chatters;
import com.sun.net.httpserver.HttpServer;
import me.mini_bomba.streamchatmod.asm.hooks.FontRendererHook;
import me.mini_bomba.streamchatmod.commands.TwitchChatCommand;
import me.mini_bomba.streamchatmod.commands.TwitchCommand;
import me.mini_bomba.streamchatmod.runnables.TwitchFollowSoundScheduler;
Expand Down Expand Up @@ -43,6 +44,7 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;
import java.util.stream.Collectors;

@SuppressWarnings({"ConstantConditions", "unused"})
@Mod(modid = StreamChatMod.MODID, version = StreamChatMod.VERSION, clientSideOnly = true)
Expand Down Expand Up @@ -80,6 +82,7 @@ public class StreamChatMod {
public ScheduledFuture<?> updateChecker = null;

private final StreamEvents events;
public final StreamEmotes emotes;
protected final TwitchCommand twitchCommand;

// Caches for Twitch clips, users, etc.
Expand All @@ -94,13 +97,14 @@ public class StreamChatMod {

public StreamChatMod() {
events = new StreamEvents(this);
emotes = new StreamEmotes(this);
keybinds = new StreamKeybinds(this);
twitchCommand = new TwitchCommand(this);
}

@EventHandler
public void postInit(FMLPostInitializationEvent event) {
ProgressManager.ProgressBar progress = ProgressManager.push("Starting up", 3);
ProgressManager.ProgressBar progress = ProgressManager.push("Starting up", 4);
progress.step("Checking for updates");
LOGGER.info("Checking for updates...");
latestVersion = StreamUtils.getLatestVersion();
Expand All @@ -112,10 +116,17 @@ public void postInit(FMLPostInitializationEvent event) {
else
LOGGER.info("Mod is up to date!");
progress.step("Starting Twitch client");
startTwitch();
startTwitch(false);
progress.step("Starting async thread");
asyncExecutor = new ScheduledThreadPoolExecutor(1);
if (config.updateCheckerEnabled.getBoolean()) startUpdateChecker();
progress.step("Syncing emote cache");
if (twitch != null) {
ProgressManager.ProgressBar emoteProgress = ProgressManager.push("Syncing emotes", 7);
emotes.syncGlobalEmotes(emoteProgress, false);
emotes.syncAllChannelEmotes(emoteProgress, Arrays.stream(config.twitchChannels.getStringList()).map(this::getTwitchUserByName).map(User::getId).collect(Collectors.toList()), false);
ProgressManager.pop(emoteProgress);
}
ProgressManager.pop(progress);
}

Expand Down Expand Up @@ -146,6 +157,7 @@ public void preInit(FMLPreInitializationEvent event) {
metadata.description += "\n\nYou are running SCM release version " + VERSION + " built from git commit " + GIT_HASH;
}
config = new StreamConfig(event.getSuggestedConfigurationFile());
FontRendererHook.setAllowAnimated(config.allowAnimatedEmotes.getBoolean());
}

@EventHandler
Expand Down Expand Up @@ -216,6 +228,14 @@ public void stopUpdateChecker() {
}
}

protected List<Emote> queryGlobalTwitchEmotes() {
if (twitch == null) {
LOGGER.warn("Could not get global Twitch emotes: Twitch client is disabled");
return Collections.emptyList();
}
return twitch.getHelix().getGlobalEmotes(null).execute().getEmotes();
}

/**
* Schedules an action to be run in another thread.<br>
* <b>This will throw a ConcurrentModificationException if an important action is scheduled</b> (such as Twitch client stopping)<br>
Expand Down Expand Up @@ -294,15 +314,26 @@ public void asyncRevokeTwitchToken() throws ConcurrentModificationException {

public void asyncJoinTwitchChannel(String channel) throws ConcurrentModificationException {
asyncTwitchAction(() -> {
if (twitch == null) { StreamUtils.queueAddMessage(EnumChatFormatting.RED + "Twitch chat is not enabled!"); return; }
if (twitch == null) {
StreamUtils.queueAddMessage(EnumChatFormatting.RED + "Twitch chat is not enabled!");
return;
}
TwitchChat chat = twitch.getChat();
if (chat == null) { StreamUtils.queueAddMessage(EnumChatFormatting.RED + "Twitch chat is not enabled!"); return; }
if (chat == null) {
StreamUtils.queueAddMessage(EnumChatFormatting.RED + "Twitch chat is not enabled!");
return;
}
chat.joinChannel(channel);
if (!chat.isChannelJoined(channel)) { StreamUtils.queueAddMessage(EnumChatFormatting.RED + "Something went wrong: Could not join the channel."); return; }
if (!chat.isChannelJoined(channel)) {
StreamUtils.queueAddMessage(EnumChatFormatting.RED + "Something went wrong: Could not join the channel.");
return;
}
if (config.followEventEnabled.getBoolean()) twitch.getClientHelper().enableFollowEventListener(channel);
config.twitchChannels.set(java.util.stream.Stream.concat(Arrays.stream(config.twitchChannels.getStringList()), java.util.stream.Stream.of(channel)).map(String::toLowerCase).distinct().toArray(String[]::new));
config.saveIfChanged();
StreamUtils.queueAddMessage(EnumChatFormatting.GREEN+"Joined "+channel+"'s chat!");
StreamUtils.queueAddMessage(EnumChatFormatting.GRAY + "Syncing " + channel + "'s channel emotes...");
emotes.syncChannelEmotes(getTwitchUserByName(channel).getId(), true);
StreamUtils.queueAddMessage(EnumChatFormatting.GREEN + "Joined " + channel + "'s chat!");
});
}

Expand Down Expand Up @@ -501,6 +532,10 @@ public void asyncShowTwitchStreamStats() throws ConcurrentModificationException
}

public boolean startTwitch() {
return startTwitch(true);
}

public boolean startTwitch(boolean syncEmotes) {
if (twitch != null || !config.twitchEnabled.getBoolean()) return false;
String token = config.twitchToken.getString();
if (token.equals("")) return false;
Expand All @@ -514,6 +549,12 @@ public boolean startTwitch() {
.withEnableHelix(true)
.withEnableTMI(true)
.build();
if (syncEmotes) {
StreamUtils.queueAddMessage(EnumChatFormatting.GRAY + "Synchronising global emote cache...");
emotes.syncGlobalEmotes(null, true);
StreamUtils.queueAddMessage(EnumChatFormatting.GRAY + "Synchronising channel emote cache...");
emotes.syncAllChannelEmotes(null, Arrays.stream(config.twitchChannels.getStringList()).map(this::getTwitchUserByName).map(User::getId).collect(Collectors.toList()), true);
}
twitch.getEventManager().onEvent(ChannelMessageEvent.class, this::onTwitchMessage);
twitch.getEventManager().onEvent(FollowEvent.class, this::onTwitchFollow);
twitch.getEventManager().onEvent(ChannelNoticeEvent.class, this::onTwitchNotice);
Expand Down
16 changes: 16 additions & 0 deletions src/main/java/me/mini_bomba/streamchatmod/StreamConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ public class StreamConfig {
public final Property eventSoundVolume;
// twitch events
public final Property followEventEnabled;
// emotes
public final Property showTwitchGlobalEmotes;
public final Property showTwitchChannelEmotes;
public final Property showBTTVGlobalEmotes;
public final Property showBTTVChannelEmotes;
public final Property showFFZGlobalEmotes;
public final Property showFFZChannelEmotes;
public final Property allowAnimatedEmotes;

private static final Logger LOGGER = LogManager.getLogger();

Expand All @@ -57,13 +65,21 @@ public StreamConfig(File configFile) {
messageSoundVolume = config.get("sounds", "messageVolume", 1.0d);
eventSoundVolume = config.get("sounds", "eventVolume", 1.0d);
followEventEnabled = config.get("twitchEvents", "followers", true);
showTwitchGlobalEmotes = config.get("emotes", "twitch_globals", true);
showTwitchChannelEmotes = config.get("emotes", "twitch_channel", true);
showBTTVGlobalEmotes = config.get("emotes", "bttv_globals", true);
showBTTVChannelEmotes = config.get("emotes", "bttv_channel", true);
showFFZGlobalEmotes = config.get("emotes", "ffz_globals", true);
showFFZChannelEmotes = config.get("emotes", "ffz_channel", true);
allowAnimatedEmotes = config.get("emotes", "animated", true);
saveIfChanged();
}

public void saveIfChanged() {
if (config.hasChanged()) config.save();
}

@SuppressWarnings("BooleanMethodIsAlwaysInverted")
public boolean isTwitchTokenSet() {
return !twitchToken.getString().equals("");
}
Expand Down
Loading

0 comments on commit 25b13d4

Please sign in to comment.