Skip to content

Commit

Permalink
feat(events): add dynamic event system for mod compatibility
Browse files Browse the repository at this point in the history
Implement a dynamic event system to support toggleable events and type-tracked events,
improving the mod's adaptability and interaction with other mods. This system includes
the introduction of `Event`, `ToggleableEvent`, and `TypeTrackedEvent` classes, and
facilitates event-driven functionalities across different event sources.

The event system is designed to be flexible and easily extensible, allowing for bettermod integration and compatibility. It also introduces a mechanism to invalidate and
re-initialize events when necessary, ensuring the system remains up-to-date with the
current state of the game and its mods.
  • Loading branch information
cnlimiter committed Aug 11, 2024
1 parent 06e30b1 commit ec5d9f3
Show file tree
Hide file tree
Showing 9 changed files with 402 additions and 1 deletion.
78 changes: 78 additions & 0 deletions common/src/main/java/cn/evole/mods/mcbot/api/event/Event.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package cn.evole.mods.mcbot.api.event;

import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Objects;
import java.util.function.Function;

/**
* @Project: McBot
* @Author: cnlimiter
* @CreateTime: 2024/8/11 20:30
* @Description:
*/
public class Event<T>
{
/**
* The invoker field. This should be updated by the implementation to
* always refer to an instance containing all code that should be
* executed upon event emission.
*/
protected volatile T invoker;

/**
* Returns the invoker instance.
*
* <p>An "invoker" is an object which hides multiple registered
* listeners of type T under one instance of type T, executing
* them and leaving early as necessary.
*
* @return The invoker instance.
*/
public final T invoker() { return invoker; }

private final Function<T[], T> invokerFactory;
protected final Object lock = new Object();
private T[] listeners;

private void addListener(T listener)
{
int oldLength = listeners.length;
listeners = Arrays.copyOf(listeners, oldLength + 1);
listeners[oldLength] = listener;
}

@SuppressWarnings("unchecked")
public Event(Class<? super T> type, Function<T[], T> invokerFactory)
{
this.invokerFactory = invokerFactory;
this.listeners = (T[]) Array.newInstance(type, 0);
update();
}

void update()
{
this.invoker = invokerFactory.apply(listeners);
}

/**
* Register a listener to the event.
*
* @param listener The desired listener.
*/
public void register(T listener)
{
Objects.requireNonNull(listener, "Tried to register a null listener!");

synchronized (lock)
{
addListener(listener);
update();
}
}

public int listenerCount()
{
return listeners.length;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package cn.evole.mods.mcbot.api.event;

import com.google.common.collect.MapMaker;

import java.util.Collections;
import java.util.Set;
import java.util.function.Function;

/**
* @Project: McBot
* @Author: cnlimiter
* @CreateTime: 2024/8/11 20:31
* @Description: from <a href="https://github.com/AHilyard/Iceberg/blob/1.21-multi/common/src/main/java/com/anthonyhilyard/iceberg/events/EventFactory.java">...</a>
*/
public final class EventFactory
{
private static final Set<Event<?>> EVENTS = Collections.newSetFromMap(new MapMaker().weakKeys().makeMap());

private EventFactory() { }

public static void invalidate()
{
EVENTS.forEach(Event::update);
}

public static <T> Event<T> create(Class<? super T> type, Function<T[], T> invokerFactory)
{
Event<T> event = new Event<>(type, invokerFactory);
EVENTS.add(event);
return event;
}

public static <S, T> TypeTrackedEvent<S, T> createTypeTracked(Class<? super T> type, Function<T[], T> invokerFactory)
{
TypeTrackedEvent<S, T> event = new TypeTrackedEvent<>(type, invokerFactory);
EVENTS.add(event);
return event;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package cn.evole.mods.mcbot.api.event;

import java.lang.reflect.Array;
import java.util.function.Function;

/**
* @Project: McBot
* @Author: cnlimiter
* @CreateTime: 2024/8/11 20:33
* @Description: from <a href="https://github.com/AHilyard/Iceberg/blob/1.21-multi/common/src/main/java/com/anthonyhilyard/iceberg/events/ToggleableEvent.java">...</a>
*/
public class ToggleableEvent<T>
{
private T dummyInvoker;
private boolean disabled = false;
private Event<T> event;

@SuppressWarnings("unchecked")
private ToggleableEvent(Class<? super T> type, Function<T[], T> invokerFactory)
{
event = EventFactory.create(type, invokerFactory);
this.dummyInvoker = invokerFactory.apply((T[]) Array.newInstance(type, 0));
}

public static <T> ToggleableEvent<T> create(Class<? super T> type, Function<T[], T> invokerFactory)
{
return new ToggleableEvent<>(type, invokerFactory);
}

public void register(T listener)
{
event.register(listener);
}

public T invoker()
{
if (!disabled)
{
return event.invoker();
}
else
{
return dummyInvoker;
}
}

public boolean disable()
{
if (disabled)
{
return false;
}
else
{
disabled = true;
return true;
}
}

public boolean enable()
{
if (!disabled)
{
return false;
}
else
{
disabled = false;
return true;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package cn.evole.mods.mcbot.api.event;

import com.google.common.collect.Maps;

import java.util.Map;
import java.util.function.Function;

/**
* @Project: McBot
* @Author: cnlimiter
* @CreateTime: 2024/8/11 20:31
* @Description: from <a href="https://github.com/AHilyard/Iceberg/blob/1.21-multi/common/src/main/java/com/anthonyhilyard/iceberg/events/TypeTrackedEvent.java">...</a>
*/
public class TypeTrackedEvent<S, T> extends Event<T>
{
private final Map<Class<? extends S>, T> listenerTypes = Maps.newHashMap();

public TypeTrackedEvent(Class<? super T> type, Function<T[], T> invokerFactory)
{
super(type, invokerFactory);
}

@Override
public void register(T listener)
{
throw new UnsupportedOperationException("Register(listener) unsupported. Use Register(type, listener) instead!");
}

public void register(Class<? extends S> type, T listener)
{
super.register(listener);
listenerTypes.put(type, listener);
}

public Map<Class<? extends S>, T> getListenerTypes()
{
return listenerTypes;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package cn.evole.mods.mcbot.api.event.mod;


import cn.evole.mods.mcbot.api.event.ToggleableEvent;
import net.minecraft.server.level.ServerPlayer;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;

public class McBotEvents {
/**
* 当玩家发送一条消息(并转发到QQ)后触发。
* 包含发送消息的玩家、message_id和消息内容
*/
public static final ToggleableEvent<PlayerChat> ON_CHAT = ToggleableEvent.create(PlayerChat.class, callbacks -> (player, message_id, message) -> {
for (PlayerChat callback : callbacks) {
callback.onChat(player, message_id, message);
}
});

/**
* 当玩家发送一条消息后(并在任何处理之前)触发。
* 如果取消它,消息将不会发送到游戏和QQ。
* 包含发送消息的玩家、消息内容和CallBackInfo。
*/
public static final ToggleableEvent<EarlyPlayerChat> BEFORE_CHAT = ToggleableEvent.create(EarlyPlayerChat.class, callbacks -> (player, message, ci) -> {
for (EarlyPlayerChat callback : callbacks) {
callback.onChat(player, message, ci);
}
});

@FunctionalInterface
public interface PlayerChat {
void onChat(ServerPlayer player, int message_id, String message);
}

@FunctionalInterface
public interface EarlyPlayerChat {
void onChat(ServerPlayer player, String message, CallbackInfo ci);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package cn.evole.mods.mcbot.api.event.server;

import cn.evole.mods.mcbot.api.event.ToggleableEvent;
import net.minecraft.advancements.Advancement;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.state.BlockState;

/**
* Description:
* Author: cnlimiter
* Date: 2022/1/18 9:36
* Version: 1.0
*/
public final class ServerGameEvents {
public static final ToggleableEvent<PlayerTick> PLAYER_TICK = ToggleableEvent.create(PlayerTick.class, callbacks -> (world, player) -> {
for (PlayerTick callback : callbacks) {
callback.onTick(world, player);
}
});

public static final ToggleableEvent<PlayerDeath> PLAYER_DEATH = ToggleableEvent.create(PlayerDeath.class, callbacks -> (source, player) -> {
for (PlayerDeath callback : callbacks) {
callback.onDeath(source, player);
}
});

public static final ToggleableEvent<PlayerChangeDimension> PLAYER_CHANGE_DIMENSION = ToggleableEvent.create(PlayerChangeDimension.class, callbacks -> (world, player) -> {
for (PlayerChangeDimension callback : callbacks) {
callback.onChangeDimension(world, player);
}
});

public static final ToggleableEvent<PlayerDigSpeedCalc> ON_PLAYER_DIG_SPEED_CALC = ToggleableEvent.create(PlayerDigSpeedCalc.class, callbacks -> (world, player, digSpeed, state) -> {
for (PlayerDigSpeedCalc callback : callbacks) {
float newSpeed = callback.onDigSpeedCalc(world, player, digSpeed, state);
if (newSpeed != digSpeed) {
return newSpeed;
}
}

return -1;
});

public static final ToggleableEvent<PlayerLoggedIn> PLAYER_LOGGED_IN = ToggleableEvent.create(PlayerLoggedIn.class, callbacks -> (server, player) -> {
for (PlayerLoggedIn callback : callbacks) {
callback.onPlayerLoggedIn(server, player);
}
});

public static final ToggleableEvent<PlayerLoggedOut> PLAYER_LOGGED_OUT = ToggleableEvent.create(PlayerLoggedOut.class, callbacks -> (server, player) -> {
for (PlayerLoggedOut callback : callbacks) {
callback.onPlayerLoggedOut(server, player);
}
});

public static final ToggleableEvent<PlayerAdvancement> PLAYER_ADVANCEMENT = ToggleableEvent.create(PlayerAdvancement.class, callbacks -> (player, advancement) -> {
for (PlayerAdvancement callback : callbacks) {
callback.onAdvancement(player, advancement);
}
});


public static final ToggleableEvent<ServerChat> SERVER_CHAT = ToggleableEvent.create(ServerChat.class, callbacks -> (player, message) -> {
for (ServerChat callback : callbacks) {
callback.onChat(player, message);
}
});

@FunctionalInterface
public interface PlayerAdvancement {
void onAdvancement(Player player, Advancement advancement);
}

@FunctionalInterface
public interface PlayerTick {
void onTick(ServerLevel world, ServerPlayer player);
}

@FunctionalInterface
public interface PlayerDeath {
void onDeath(DamageSource source, ServerPlayer player);
}

@FunctionalInterface
public interface PlayerChangeDimension {
void onChangeDimension(ServerLevel world, ServerPlayer player);
}

@FunctionalInterface
public interface PlayerDigSpeedCalc {
float onDigSpeedCalc(Level world, Player player, float digSpeed, BlockState state);
}

@FunctionalInterface
public interface PlayerLoggedIn {
void onPlayerLoggedIn(MinecraftServer server, ServerPlayer player);
}

@FunctionalInterface
public interface PlayerLoggedOut {
void onPlayerLoggedOut(MinecraftServer server, ServerPlayer player);
}

@FunctionalInterface
public interface ServerChat {
void onChat(ServerPlayer player, String message);
}
}
11 changes: 11 additions & 0 deletions fabric/src/main/java/cn/evole/mods/mcbot/McBotFabric.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package cn.evole.mods.mcbot;

import net.fabricmc.api.ModInitializer;

public class McBotFabric implements ModInitializer {

@Override
public void onInitialize() {
CommonClass.init();
}
}
Loading

0 comments on commit ec5d9f3

Please sign in to comment.