-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Expose placeholders for stream schedule
- Loading branch information
Showing
11 changed files
with
383 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) { | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
34 changes: 34 additions & 0 deletions
34
src/main/java/com/lovetropics/extras/network/SetTimeZonePacket.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
68
src/main/java/com/lovetropics/extras/schedule/PlayerTimeZone.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
109 changes: 109 additions & 0 deletions
109
src/main/java/com/lovetropics/extras/schedule/SchedulePlaceholders.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
Oops, something went wrong.