Skip to content

Commit

Permalink
Expose placeholders for stream schedule
Browse files Browse the repository at this point in the history
  • Loading branch information
Gegy committed Oct 14, 2023
1 parent 069aa24 commit 03a372a
Show file tree
Hide file tree
Showing 11 changed files with 383 additions and 1 deletion.
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ dependencies {
}

jarJar(implementation(fg.deobf("com.lovetropics.lib:LTLib:$ltlib_version")))
jarJar(implementation(fg.deobf("eu.pb4:placeholder-api:$placeholder_api_version")))

// runtimeOnly fg.deobf('com.jozufozu.flywheel:Flywheel-Forge:1.18-0.7.0.70')
// runtimeOnly fg.deobf('com.simibubi.create:Create:mc1.18.2_v0.4.1+113')
Expand Down
1 change: 1 addition & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ parchment_version=2023.06.26-1.20.1

registrate_version=MC1.20-1.3.3
ltlib_version=[1.3,1.4)
placeholder_api_version=[2.1,2.2)

# Sets default memory used for gradle commands. Can be overridden by user or command line properties.
# This is required to provide enough memory for the Minecraft decompilation process.
Expand Down
45 changes: 45 additions & 0 deletions src/main/java/com/lovetropics/extras/ExtrasConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.lovetropics.extras;

import net.minecraftforge.common.ForgeConfigSpec;
import net.minecraftforge.common.ForgeConfigSpec.Builder;
import net.minecraftforge.common.ForgeConfigSpec.ConfigValue;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod.EventBusSubscriber;
import net.minecraftforge.fml.common.Mod.EventBusSubscriber.Bus;
import net.minecraftforge.fml.event.config.ModConfigEvent;

@EventBusSubscriber(modid = LTExtras.MODID, bus = Bus.MOD)
public class ExtrasConfig {
private static final Builder COMMON_BUILDER = new Builder();

public static final CategoryTechStack TECH_STACK = new CategoryTechStack();

public static final class CategoryTechStack {
public final ConfigValue<String> authKey;
public final ConfigValue<String> scheduleUrl;

private CategoryTechStack() {
COMMON_BUILDER.comment("Connection to the tech stack").push("techStack");

authKey = COMMON_BUILDER
.comment("API Key used to allow authentication with the tech stack")
.define("authKey", "");

scheduleUrl = COMMON_BUILDER
.comment("API URL to get stream schedule from")
.define("schedule", "http://localhost/schedule");

COMMON_BUILDER.pop();
}
}

public static final ForgeConfigSpec COMMON_CONFIG = COMMON_BUILDER.build();

@SubscribeEvent
public static void configLoad(final ModConfigEvent.Loading event) {
}

@SubscribeEvent
public static void configReload(final ModConfigEvent.Reloading event) {
}
}
5 changes: 5 additions & 0 deletions src/main/java/com/lovetropics/extras/LTExtras.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import com.lovetropics.extras.effect.ExtraEffects;
import com.lovetropics.extras.entity.ExtraEntities;
import com.lovetropics.extras.network.LTExtrasNetwork;
import com.lovetropics.extras.schedule.PlayerTimeZone;
import com.mojang.brigadier.CommandDispatcher;
import com.tterrag.registrate.Registrate;
import com.tterrag.registrate.providers.ProviderType;
Expand Down Expand Up @@ -38,6 +39,7 @@
import net.minecraftforge.fml.ModList;
import net.minecraftforge.fml.ModLoadingContext;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.config.ModConfig;
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import net.minecraftforge.fml.util.ObfuscationReflectionHelper;
Expand All @@ -56,6 +58,7 @@ public class LTExtras {

public static final Capability<CollectibleStore> COLLECTIBLE_STORE = CapabilityManager.get(new CapabilityToken<>() {});
public static final Capability<SpawnItemsStore> SPAWN_ITEMS_STORE = CapabilityManager.get(new CapabilityToken<>() {});
public static final Capability<PlayerTimeZone> PLAYER_TIME_ZONE = CapabilityManager.get(new CapabilityToken<>() {});

public static Registrate registrate() {
return REGISTRATE.get();
Expand Down Expand Up @@ -110,6 +113,8 @@ public LTExtras() {
)
);
});

ModLoadingContext.get().registerConfig(ModConfig.Type.COMMON, ExtrasConfig.COMMON_CONFIG);
}

private static final Pattern QUALIFIER = Pattern.compile("-\\w+\\+\\d+");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ public static CollectibleStore get(final Player player) {
return player.getCapability(LTExtras.COLLECTIBLE_STORE).orElseThrow(IllegalStateException::new);
}

@Nullable public static CollectibleStore getNullable(final Player player) {
@Nullable
public static CollectibleStore getNullable(final Player player) {
return player.getCapability(LTExtras.COLLECTIBLE_STORE).orElse(null);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ public static void register() {
.decoder(ReturnCollectibleItemPacket::new)
.consumerMainThread(ReturnCollectibleItemPacket::handle)
.add();

CHANNEL.messageBuilder(SetTimeZonePacket.class, 3, NetworkDirection.PLAY_TO_SERVER)
.encoder(SetTimeZonePacket::write)
.decoder(SetTimeZonePacket::read)
.consumerMainThread(SetTimeZonePacket::handle)
.add();
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.lovetropics.extras.network;

import com.lovetropics.extras.schedule.PlayerTimeZone;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.server.level.ServerPlayer;
import net.minecraftforge.network.NetworkEvent;

import java.time.DateTimeException;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.util.function.Supplier;

public record SetTimeZonePacket(ZoneId id) {
private static final int MAX_LENGTH = 64;

public static SetTimeZonePacket read(final FriendlyByteBuf input) {
try {
return new SetTimeZonePacket(ZoneId.of(input.readUtf(MAX_LENGTH)));
} catch (final DateTimeException e) {
return new SetTimeZonePacket(ZoneOffset.UTC);
}
}

public void write(final FriendlyByteBuf output) {
output.writeUtf(id.getId());
}

public void handle(final Supplier<NetworkEvent.Context> ctx) {
final ServerPlayer player = ctx.get().getSender();
if (player != null) {
PlayerTimeZone.set(player, id);
}
}
}
68 changes: 68 additions & 0 deletions src/main/java/com/lovetropics/extras/schedule/PlayerTimeZone.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.lovetropics.extras.schedule;

import com.lovetropics.extras.LTExtras;
import net.minecraft.core.Direction;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.capabilities.ICapabilityProvider;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.event.AttachCapabilitiesEvent;
import net.minecraftforge.event.entity.player.PlayerEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;

import javax.annotation.Nullable;
import java.time.ZoneId;
import java.time.ZoneOffset;

@Mod.EventBusSubscriber(modid = LTExtras.MODID)
public class PlayerTimeZone implements ICapabilityProvider {
public static final ResourceLocation ID = new ResourceLocation(LTExtras.MODID, "time_zone");

private final LazyOptional<PlayerTimeZone> instance = LazyOptional.of(() -> this);

private ZoneId zoneId = ZoneOffset.UTC;

@SubscribeEvent
public static void onAttachEntityCapabilities(final AttachCapabilitiesEvent<Entity> event) {
if (event.getObject() instanceof ServerPlayer) {
event.addCapability(ID, new PlayerTimeZone());
}
}

@SubscribeEvent
public static void onPlayerClone(final PlayerEvent.Clone event) {
if (event.isWasDeath()) {
final PlayerTimeZone oldTimeZone = getOrNull(event.getOriginal());
final PlayerTimeZone newTimeZone = getOrNull(event.getEntity());
if (oldTimeZone != null && newTimeZone != null) {
newTimeZone.zoneId = oldTimeZone.zoneId;
}
}
}

public static void set(final ServerPlayer player, final ZoneId zone) {
final PlayerTimeZone capability = getOrNull(player);
if (capability != null) {
capability.zoneId = zone;
}
}

public static ZoneId get(final ServerPlayer player) {
final PlayerTimeZone capability = getOrNull(player);
return capability != null ? capability.zoneId : ZoneOffset.UTC;
}

@Nullable
private static PlayerTimeZone getOrNull(final Player player) {
return player.getCapability(LTExtras.PLAYER_TIME_ZONE).orElse(null);
}

@Override
public <T> LazyOptional<T> getCapability(final Capability<T> cap, @Nullable final Direction side) {
return LTExtras.PLAYER_TIME_ZONE.orEmpty(cap, instance);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package com.lovetropics.extras.schedule;

import com.lovetropics.extras.LTExtras;
import eu.pb4.placeholders.api.PlaceholderContext;
import eu.pb4.placeholders.api.PlaceholderResult;
import eu.pb4.placeholders.api.Placeholders;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.minecraftforge.event.TickEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;

import javax.annotation.Nullable;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiFunction;
import java.util.stream.Collectors;

@Mod.EventBusSubscriber(modid = LTExtras.MODID)
public class SchedulePlaceholders {
private static final PlaceholderResult UNKNOWN = PlaceholderResult.value("?");
private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("EEE HH:mm");

private static final Duration FETCH_INTERVAL = Duration.ofMinutes(5);

private static CompletableFuture<?> fetchFuture = CompletableFuture.completedFuture(null);
@Nullable
private static StreamSchedule schedule;
private static Instant lastFetchTime = Instant.EPOCH;

static {
registerPlaceholder("current/title", (ctx, current, next) -> PlaceholderResult.value(current.shortDescription()));
registerPlaceholder("current/description", (ctx, current, next) -> PlaceholderResult.value(current.longDescription()));
registerPlaceholder("current/hosts", (ctx, current, next) -> formatHosts(current));
registerPlaceholder("current/start", (ctx, current, next) -> formatLocalTime(ctx, current.time()));
registerPlaceholder("current/end", (ctx, current, next) -> next != null ? formatLocalTime(ctx, next.time()) : UNKNOWN);

registerPlaceholderNext("next/title", (ctx, next) -> PlaceholderResult.value(next.shortDescription()));
registerPlaceholderNext("next/description", (ctx, next) -> PlaceholderResult.value(next.longDescription()));
registerPlaceholderNext("next/hosts", (ctx, next) -> formatHosts(next));
registerPlaceholderNext("next/start", (ctx, next) -> formatLocalTime(ctx, next.time()));
registerPlaceholderNext("next/time_until", (ctx, next) -> formatTimeUntil(next));
}

private static void registerPlaceholder(final String id, final PlaceholderFunction function) {
Placeholders.register(new ResourceLocation(LTExtras.MODID, "schedule/" + id), (ctx, arg) -> {
final StreamSchedule schedule = SchedulePlaceholders.schedule;
if (schedule == null) {
return UNKNOWN;
}
final StreamSchedule.State state = schedule.stateAt(Instant.now());
if (state != null) {
return function.get(ctx, state.currentEntry(), state.nextEntry());
}
return UNKNOWN;
});
}

private static void registerPlaceholderNext(final String id, final BiFunction<PlaceholderContext, StreamSchedule.Entry, PlaceholderResult> function) {
registerPlaceholder(id, (ctx, current, next) -> next != null ? function.apply(ctx, next) : UNKNOWN);
}

private static PlaceholderResult formatHosts(final StreamSchedule.Entry entry) {
return PlaceholderResult.value(entry.hosts().stream()
.map(StreamSchedule.Host::name)
.collect(Collectors.joining(", "))
);
}

private static PlaceholderResult formatTimeUntil(final StreamSchedule.Entry entry) {
Duration duration = Duration.between(Instant.now(), entry.time());
if (duration.isNegative()) {
duration = Duration.ZERO;
}
return PlaceholderResult.value(duration.toMinutes() + " minutes");
}

private static PlaceholderResult formatLocalTime(final PlaceholderContext ctx, final Instant time) {
final LocalDateTime localTime = time.atZone(getTimeZone(ctx)).toLocalDateTime();
return PlaceholderResult.value(TIME_FORMATTER.format(localTime));
}

private static ZoneId getTimeZone(final PlaceholderContext ctx) {
final ServerPlayer player = ctx.player();
return player != null ? PlayerTimeZone.get(player) : ZoneOffset.UTC;
}

@SubscribeEvent
public static void onServerTick(final TickEvent.ServerTickEvent event) {
if (event.phase == TickEvent.Phase.END) {
return;
}

if (!fetchFuture.isDone()) {
return;
}

final Instant time = Instant.now();
if (Duration.between(lastFetchTime, time).compareTo(FETCH_INTERVAL) > 0) {
fetchFuture = StreamSchedule.fetch().thenAccept(opt -> opt.ifPresent(s -> schedule = s));
lastFetchTime = time;
}
}

private interface PlaceholderFunction {
PlaceholderResult get(PlaceholderContext ctx, StreamSchedule.Entry current, @Nullable StreamSchedule.Entry next);
}
}
Loading

0 comments on commit 03a372a

Please sign in to comment.