MCFbric模组开发案例分析:指路石(Waystone)模组代码分析

1.导言

        不是教学!不是教学!不是教学!

        本文是个人用于记录学习的帖子,也就是给自己看的,就像是上课做的随堂笔记。当然,如果你也想开发MOD,看看别人的笔记也肯定有收获。

        本人本科生,并不主修JAVA,但对MC模组开发感兴趣。目前基本是零经验,技术力底下而野心却很大,哈哈。

        介绍一下指路石MOD:

        喜欢玩MC的都知道,这是一个简单却非常实用的MOD,大大节省了我们跑图的时间。通过学习指路石MOD,可以让我们对于模组开发结构有更深层次的认识。

        源码地址:GitHub - TwelveIterationMods/Waystones at 1.20.1

        Mod作者:Blay

        我学习的版本:1.20.1(作者的源码好像把Forge和Fabric混合在一起了,具体为啥我也不知道,我主修Fabric)

2. MOD入口

       指路石的MOD入口文件为Waystone.java,目录在:Waystones/common/.../Waystones.java

        但是需要注意的是,在外面有个包名为Fabric,内部也包含一个名为FabricWaystone.java,大概率是Fabric版本的启动器。可是内部的东西我并不理解,暂且不管,先看common包里的这个。

import ...

public class Waystones {

    public static final Logger logger = LoggerFactory.getLogger(Waystones.class);

    public static final String MOD_ID = "waystones";

    public static void initialize() {
        WaystonesAPI.__internalMethods = new InternalMethodsImpl();
        RequirementRegistry.registerDefaults();

        WaystonesConfig.initialize();
        ModStats.initialize();
        ModEventHandlers.initialize();
        ModNetworking.initialize(Balm.getNetworking());
        ModBlocks.initialize(Balm.getBlocks());
        ModBlockEntities.initialize();
        ModItems.initialize(Balm.getItems());
        ModMenus.initialize();
        ModWorldGen.initialize(Balm.getWorldGen());
        ModCommands.initialize(Balm.getCommands());
        ModComponents.initialize(Balm.getComponents());

        if (WaystonesConfig.getActive().compatibility.blueMap) {
            Balm.initializeIfLoaded("bluemap", "net.blay09.mods.waystones.compat.BlueMapIntegration");
        }

        if (WaystonesConfig.getActive().compatibility.dynmap) {
            Balm.initializeIfLoaded("dynmap", "net.blay09.mods.waystones.compat.DynmapIntegration");
        }

        Balm.initializeIfLoaded(Compat.UNBREAKABLES, "net.blay09.mods.waystones.compat.UnbreakablesIntegration");
    }
}

         可以看出,作者封装了非常多的方法,我能看懂的就有ModBlocks的初始化方法等等,我们需要逐一分析。

public class Waystones {
    public static final Logger logger = LoggerFactory.getLogger(Waystones.class);
    public static final String MOD_ID = "waystones";
}

        定义模组主类,声明模组ID为 waystones,并创建日志记录器 logger,用于输出模组运行时的日志信息,这在mod报错时可以通过分析文档来判断哪个mod出了问题。

        而后面一长串的初始化一会儿再学习,接下来有三个兼容性处理。

    // 兼容性:BlueMap 地图模组
    if (WaystonesConfig.getActive().compatibility.blueMap) {
        Balm.initializeIfLoaded("bluemap", "net.blay09.mods.waystones.compat.BlueMapIntegration");
    }

    // 兼容性:Dynmap 地图模组
    if (WaystonesConfig.getActive().compatibility.dynmap) {
        Balm.initializeIfLoaded("dynmap", "net.blay09.mods.waystones.compat.DynmapIntegration");
    }

    // 兼容性:Unbreakables 模组(防止石碑被破坏)
    Balm.initializeIfLoaded(Compat.UNBREAKABLES, "net.blay09.mods.waystones.compat.UnbreakablesIntegration");

        作者在Waystone的config文件(WayStonesConfig)下面写了一个自定义的方法(getActive,判断模组是否运行),此方法用来对其他的三个模组进行兼容性处理。

        至于具体如何兼容,需要我阅读作者写在WayStonesConfig的代码后才能知道。

3.WaystonesConfig文件

        这是本模组的配置类,作者是如此封装的,可能代表着一种良好的习惯,但这也可能并不是必须的。

        文件包为net.blay09.mods.waystones.config.WaystonesConfig,作者特地写了一个config包,而下面全都是配置文件:

我们先进入最关心的WaystonesConfig文件:

public class WaystonesConfig {

    public static WaystonesConfigData getActive() {
        return Balm.getConfig().getActive(WaystonesConfigData.class);
    }

    public static void initialize() {
        Balm.getConfig().registerConfig(WaystonesConfigData.class, SyncWaystonesConfigMessage::new);
    }
}

哈哈哈,没有想到,作者再一次进行了封装。

getActive()方法用于获取当前生效的配置实例(WaystonesConfigData对象),而这个实例会被用来给模组的其他模块提供配置信息,这就是上文提到的兼容性。

同时,还能确保客户端和服务端使用同一份配置。

但是具体是怎么实现的,还需要分析作者写的Balm库啊。

initialize()方法则是用来初始化配置系统,注册配置类和同步机制,这也是写在入口里的那个方法。

这个方法的流程是Balm加载WaystonesConfigData类定义的配置文件,然后通过SyncWaystonesConfigMessage将配置发送给客户端->客户端收到信息后,会覆盖本地配置,与服务端保持一致。

这是为了保证所有玩家的配置与主机的配置一致,保证了服务端的权威性。

经过这段分析,我们仍没有解开配置文件底层机制的谜底,我们需要继续分析WaystonesConfigData文件和Balm文件。

4.WaystonesConfigData文件

        此文件与上面那个位于同一个包下(config包)。先给出全部代码(代码很长,放这主要是为了以后复制方便,可以不看,后面有分析):

        

@Config(Waystones.MOD_ID)
public class WaystonesConfigData implements BalmConfigData {

    public enum TransportMobs {
        ENABLED,
        SAME_DIMENSION,
        DISABLED
    }

    public enum VillageWaystoneGeneration {
        DISABLED,
        REGULAR,
        FREQUENT
    }

    public General general = new General();
    public Teleports teleports = new Teleports();
    public InventoryButton inventoryButton = new InventoryButton();
    public WorldGen worldGen = new WorldGen();
    public Client client = new Client();
    public Compatibility compatibility = new Compatibility();

    public static class General {

        @Synced
        @Comment("List of waystone origins that should prevent others from editing. PLAYER is special in that it allows only edits by the owner of the waystone.")
        @ExpectedType(WaystoneOrigin.class)
        public Set<WaystoneOrigin> restrictedWaystones = Set.of(WaystoneOrigin.PLAYER);

        @Synced
        @Comment("Set to \"GLOBAL\" to have newly placed or found waystones be global by default.")
        public WaystoneVisibility defaultVisibility = WaystoneVisibility.ACTIVATION;

        @Synced
        @Comment("Add \"GLOBAL\" to allow every player to create global waystones.")
        @ExpectedType(WaystoneVisibility.class)
        public Set<WaystoneVisibility> allowedVisibilities = Set.of();

        @Synced
        @Comment("The time in ticks that it takes to use a warp stone. This is the charge-up time when holding right-click.")
        public int warpStoneUseTime = 32;

        @Synced
        @Comment("The time in ticks that it takes to use a warp plate. This is the time the player has to stand on top for.")
        public int warpPlateUseTime = 15;

        @Synced
        @Comment("The time in ticks it takes to use a scroll. This is the charge-up time when holding right-click.")
        public int scrollUseTime = 32;
    }

    public static class Teleports {
        @Synced
        @Comment("Set to false to simply disable all xp costs. See warpRequirements for more fine-grained control.")
        public boolean enableCosts = true;

        @Synced
        @Comment("Set to false to simply disable all cooldowns. See warpRequirements for more fine-grained control.")
        public boolean enableCooldowns = true;

        @Synced
        @ExpectedType(String.class)
        @Comment("List of warp requirements with comma-separated parameters in parentheses. Conditions can be defined as comma-separated list in square brackets. Will be applied in order.")
        public List<String> warpRequirements = List.of(
                "[is_not_interdimensional] scaled_add_xp_cost(distance, 0.01)",
                "[is_interdimensional] add_xp_cost(27)",
                "[source_is_warp_plate] multiply_xp_cost(0)",
                "[target_is_global] multiply_xp_cost(0)",
                "min_xp_cost(0)",
                "max_xp_cost(27)",
                "[source_is_inventory_button] add_cooldown(inventory_button, 300)");

        @Synced
        @Comment("Set to ENABLED to have nearby pets teleport with you. Set to SAME_DIMENSION to have nearby pets teleport with you only if you're not changing dimensions. Set to DISABLED to disable.")
        public TransportMobs transportPets = TransportMobs.DISABLED;

        @Synced
        @Comment("Set to ENABLED to have leashed mobs teleport with you. Set to SAME_DIMENSION to have leashed mobs teleport with you only if you're not changing dimensions. Set to DISABLED to disable.")
        public TransportMobs transportLeashed = TransportMobs.ENABLED;

        @Comment("List of entities that cannot be teleported, either as pet, leashed, or on warp plates.")
        @ExpectedType(ResourceLocation.class)
        public Set<ResourceLocation> entityDenyList = Set.of(ResourceLocation.withDefaultNamespace("wither"));
    }

    public static class InventoryButton {
        @Synced
        @Comment("Set to 'NONE' for no inventory button. Set to 'NEAREST' for an inventory button that teleports to the nearest waystone. Set to 'ANY' for an inventory button that opens the waystone selection menu. Set to a waystone name for an inventory button that teleports to a specifically named waystone.")
        public String inventoryButton = "";

        @Comment("The x position of the inventory button in the inventory.")
        @Synced
        public int inventoryButtonX = 58;

        @Comment("The y position of the inventory button in the inventory.")
        @Synced
        public int inventoryButtonY = 60;

        @Comment("The y position of the inventory button in the creative menu.")
        @Synced
        public int creativeInventoryButtonX = 88;

        @Comment("The y position of the inventory button in the creative menu.")
        @Synced
        public int creativeInventoryButtonY = 33;
    }

    public static class WorldGen {
        @Comment("Set to 'DEFAULT' to only generate the normally textured waystones. Set to 'MOSSY' or 'SANDY' to generate all as that variant. Set to 'BIOME' to make the style depend on the biome it is generated in.")
        public WorldGenStyle wildWaystoneStyle = WorldGenStyle.BIOME;

        @Comment("Approximate chunk distance between wild waystones being generated. Set to 0 to disable generation.")
        public int chunksBetweenWildWaystones = 25;

        @Comment("List of dimensions that wild waystones are allowed to spawn in. If left empty, all dimensions except those in wildWaystonesDimensionDenyList are used.")
        @ExpectedType(ResourceLocation.class)
        public Set<ResourceLocation> wildWaystonesDimensionAllowList = Set.of(ResourceLocation.withDefaultNamespace("overworld"), ResourceLocation.withDefaultNamespace("the_nether"), ResourceLocation.withDefaultNamespace("the_end"));

        @Comment("List of dimensions that wild waystones are not allowed to spawn in. Only used if wildWaystonesDimensionAllowList is empty.")
        @ExpectedType(ResourceLocation.class)
        public Set<ResourceLocation> wildWaystonesDimensionDenyList = Set.of();

        @Comment("Set to 'PRESET_FIRST' to first use names from the nameGenerationPresets. Set to 'PRESET_ONLY' to use only those custom names. Set to 'MIXED' to have some waystones use custom names, and others random names.")
        public NameGenerationMode nameGenerationMode = NameGenerationMode.PRESET_FIRST;

        @Comment("The template to use when generating new names. Supported placeholders are {Biome} (english biome name) and {MrPork} (the default name generator).")
        public String nameGenerationTemplate = "{MrPork}";

        @Comment("These names will be used for the PRESET name generation mode. See the nameGenerationMode option for more info.")
        @ExpectedType(String.class)
        public List<String> nameGenerationPresets = List.of();

        @Comment("Set to REGULAR to have waystones spawn in some villages. Set to FREQUENT to have waystones spawn in most villages. Set to DISABLED to disable waystone generation in villages. Waystones will only spawn in vanilla or supported villages.")
        public VillageWaystoneGeneration spawnInVillages = VillageWaystoneGeneration.REGULAR;
    }

    public static class Compatibility {
        @Comment("If enabled, JourneyMap waypoints will be created for each activated waystone.")
        public boolean journeyMap = true;

        @Comment("If enabled, JourneyMap waypoints will only be created if the mod 'JourneyMap Integration' is not installed")
        public boolean preferJourneyMapIntegrationMod = true;

        @Comment("If enabled, Waystones will add markers for waystones and sharestones to BlueMap.")
        public boolean blueMap = true;

        @Comment("If enabled, Waystones will add markers for waystones and sharestones to Dynmap.")
        public boolean dynmap = true;
    }

    public static class Client {
        @Comment("If enabled, the text overlay on waystones will no longer always render at full brightness.")
        public boolean disableTextGlow = false;
    }

    public InventoryButtonMode getInventoryButtonMode() {
        return new InventoryButtonMode(inventoryButton.inventoryButton);
    }
}
TransportMobs
public enum TransportMobs {
    ENABLED,        // 允许传送宠物/拴绳生物
    SAME_DIMENSION, // 仅同维度传送
    DISABLED        // 完全禁止传送
}

        看起来这个枚举类是用来限定传送生物实体的,玩家可以设置这些值。

VillageWaystoneGeneration
public enum VillageWaystoneGeneration {
    DISABLED,       // 村庄不生成石碑
    REGULAR,        // 常规频率生成
    FREQUENT        // 高频生成
}

        这是村庄是否生成石碑的设置了,这个玩过RLCraft的应该就明白,作者很有可能就是把村庄生成设置为100%,所以每个村庄都有石碑(我记得是这样的,但好像有些没有?)

        至于后面的内容,大抵也都是一些配置选项,我技术力比较低看的不是很懂,就先不说了

        而且作者的源代码一直在改,代码之间不断归并。

5.接下来?

        还记得MOD入口的那一堆初始化吗?

        WaystonesConfig.initialize();
        ModStats.initialize();
        ModEventHandlers.initialize();
        ModNetworking.initialize(Balm.getNetworking());
        ModBlocks.initialize(Balm.getBlocks());
        ModBlockEntities.initialize(Balm.getBlockEntities());
        ModItems.initialize(Balm.getItems());
        ModMenus.initialize(Balm.getMenus());
        ModWorldGen.initialize(Balm.getWorldGen());
        ModRecipes.initialize(Balm.getRecipes());

        每一个都有聊头,我需要继续逐步研读,不断分析,知道把MOD的结构整理清楚。

        这个日志将持续更新。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值