Repository navigation
Developer Getting Started
本文只说明“如何在自己的 Forge 模组中依赖 FPSMatch,并注册一个最小地图类型”。如果你只是使用 FPSMatch 做玩法扩展,只需要关注依赖配置、地图注册和运行时扩展点。
当前源码基线为 Minecraft 1.20.1、Forge 47.4.10、FPSMatch 1.3.0 snapshot,并使用 Gradle Wrapper 8.14.5。模组运行和 Java 源码目标为 Java 17;执行完整源码构建时,createMinecraftArtifacts 还会通过 Gradle toolchain 调用 Java 21,因此本机需要让 Gradle 同时找到 Java 17 和 Java 21。
FPSMatch 可以从 Modrinth Maven 或 CurseForge Maven 引入。二选一即可。
repositories {
maven {
name = "Modrinth"
url = "https://api.modrinth.com/maven"
}
}
dependencies {
modImplementation "maven.modrinth:fpsmatch:<published-version>"
}<published-version> 必须替换为目标平台上实际发布、且与目标 BlockOffensive 版本匹配的 FPSMatch 文件版本。当前工作区的 1.3.0 snapshot 不代表 Modrinth 上一定存在同名制品。
repositories {
maven {
name = "CurseMaven"
url = "https://www.cursemaven.com"
}
}
dependencies {
modImplementation "curse.maven:fpsmatch-1331710:7109977"
}如果你把 FPSMatch 发布到了自己的 Maven,可使用它的 Gradle 坐标:
dependencies {
modImplementation "com.phasetranscrystal:fpsmatch:<version>"
}<version> 替换为你实际发布的 FPSMatch 版本。
FPSMatch 的玩法核心是 BaseMap。每个可运行对局都应该是一个 BaseMap 子类。
最小地图类需要做三件事:
- 调用
super(serverLevel, mapName, areaData)初始化地图基础数据。 - 添加至少一个普通队伍。
- 实现地图类型 ID 和胜利条件。
import com.ptcrys.fpsmatch.core.data.AreaData;
import com.ptcrys.fpsmatch.core.map.BaseMap;
import com.ptcrys.fpsmatch.core.team.TeamData;
import net.minecraft.server.level.ServerLevel;
public class ExampleArenaMap extends BaseMap {
public static final String TYPE = "example_arena";
public ExampleArenaMap(ServerLevel serverLevel, String mapName, AreaData areaData) {
super(serverLevel, mapName, areaData);
// 创建两个普通队伍。第二个参数是队伍人数上限。
this.addTeam(TeamData.of("red", 5));
this.addTeam(TeamData.of("blue", 5));
}
@Override
public String getGameType() {
// 这个字符串就是 /fpsm map create 使用的 gameType。
return TYPE;
}
@Override
public boolean victoryGoal() {
// 返回 true 时,BaseMap.mapTick() 会触发 victory()。
// 最小示例先不设置胜利条件。
return false;
}
}地图类型需要通过 RegisterFPSMapEvent 注册。注册后,FPSMatch 才知道如何根据 gameType 创建你的地图实例。
import com.ptcrys.fpsmatch.common.event.register.RegisterFPSMapEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
@Mod.EventBusSubscriber(modid = MODID, bus = Mod.EventBusSubscriber.Bus.FORGE)
public class ExampleMapRegister {
@SubscribeEvent
public static void onMapRegister(RegisterFPSMapEvent event) {
// TYPE 对应地图类型 ID,ExampleArenaMap::new 对应地图构造器。
event.registerGameType(ExampleArenaMap.TYPE, ExampleArenaMap::new);
}
}这里的 MODID 应替换为你的模组 ID 常量。
进入游戏后,用 /fpsm map create 创建地图实例:
/fpsm map create example_arena test_map ~ ~ ~ ~10 ~10 ~10
参数说明:
-
example_arena:地图类型 ID,对应ExampleArenaMap.TYPE。 -
test_map:地图实例名称,同一地图类型下不能重复。 - 第一个坐标:地图区域的第一个角点。
- 第二个坐标:地图区域的第二个角点。
地图区域由 AreaData 保存。FPSMatch 会用这个区域判断玩家、实体或方块是否位于地图内。
让当前玩家加入地图:
/fpsm map modify example_arena test_map team join @s
启动地图:
/fpsm map modify example_arena test_map debug start
debug start 会调用 BaseMap.start()。如果没有事件取消,地图会进入开始状态。
victoryGoal() 每 tick 都会被 BaseMap.mapTick() 调用。你可以在这里检查队伍分数、存活人数、时间限制或目标状态。
@Override
public boolean victoryGoal() {
// 示例:任意队伍达到 10 分后结束地图。
return this.getMapTeams().getNormalTeams().stream()
.anyMatch(team -> team.getScores() >= 10);
}如果 victoryGoal() 返回 true,FPSMatch 会调用 victory() 并发布 FPSMapEvent.VictoryEvent。
FPSMatch 会把对局内死亡信息包装成 DeathContext。自定义玩法通常需要覆盖 handleDeath 来处理分数、回合状态或复活逻辑。
@Override
public void handleDeath(DeathContext context) {
super.handleDeath(context);
// 死亡玩家一定存在。
ServerPlayer deadPlayer = context.getDeadPlayer();
// 攻击者可能不存在,例如摔落、虚空或环境伤害。
context.getAttackerOptional().ifPresent(attacker -> {
this.getMapTeams().getTeamByPlayer(attacker).ifPresent(attackerTeam ->
attackerTeam.setScores(attackerTeam.getScores() + 1));
});
}DeathContext 还会携带枪杀、爆头、穿墙、穿烟、开镜击杀等信息。是否有这些信息取决于实际伤害来源和兼容事件。
如果你的地图需要可调参数,可以使用 Setting<T>。配置项可以通过命令读取和修改。
private final Setting<Integer> scoreLimit;
public ExampleArenaMap(ServerLevel serverLevel, String mapName, AreaData areaData) {
super(serverLevel, mapName, areaData);
this.addTeam(TeamData.of("red", 5));
this.addTeam(TeamData.of("blue", 5));
// 分类、配置名和默认值;注册后即可通过 settings 命令操作。
this.scoreLimit = this.addSetting("match", "scoreLimit", 10);
}
@Override
public boolean victoryGoal() {
return this.getMapTeams().getNormalTeams().stream()
.anyMatch(team -> team.getScores() >= scoreLimit.get());
}修改配置:
/fpsm map modify example_arena test_map settings set scoreLimit 15
最小地图跑通后,再继续接入:
-
MapCapability:地图级可复用功能。 -
TeamCapability:队伍级可复用功能。 -
FPSMShop:经济和购买系统。 -
FPSMapEvent:对局事件监听。 - 自定义 packet:同步额外客户端数据。