
不給糖就搗蛋!
這不是崇洋媚外的心態,在遊戲內純粹就是好玩的心理。為了應景一下,在我們目前的模組我想要做到以下的功能:
好了,讓我們一步一步地完成上面的工作吧!
這個功能我們在[Day24]已經有提過了,我想要達成這個目的很簡單:在進入遊戲後,直接在Mods設定中的將功能全部移除:
這個功能有一點麻煩,但我們先想想要怎麼做以及思考方向:
有了以上的想法後,我們可以透過在講指令時提過,任何指令類別一定要繼承CommandBase;透過Ctrl + H查找CommandBase的子類別,就會找到下面兩個與我們提到要變動時間的工作有關的指令類別:
這兩個類別裡面就已經很清楚的說明如何用指令改時間了,所以我們就將對應的功能放到ClientProxy內的FMLServerStartingEvent事件:
ClientProxy.java
...(略)
@Override
public void serverStarting(FMLServerStartingEvent event) {
    // 0 means the OverWorld (主世界)
    // -1 means the Nether (地獄)
    // 1 means the Ent (終界)
    WorldServer world = event.getServer().worldServerForDimension(0);
    // 更改時間為半夜0時 (18000 = 第15分鐘 * 60秒 * 20刻)
    world.setWorldTime(18000);
    // 將gamerule中的日夜交替動作取消
    world.getGameRules().setOrCreateGameRule("doDaylightCycle", "false");
}
..(略)
這個時候存檔進入遊戲會看到的是月亮在正上方(午夜),並且時間不會變化。
這個想法出現後我馬上就後悔了...因為實在是太花我的時間去思考要如何達成。
但是,最後還是有了一個我想要的效果出來了
我們這邊只以苦力怕作為範例。
Minecraft要自定義不存在的效果是非常困難的,因此首先一定是先想:是什麼現有的東西也會發光,讓我們可以去參考呢?
我想到的是:火把
當然你也有可能想到紅石火把、營火等等,這些東西都有一個共通的特性:都是方塊(Block)
我們要改的可是生物(Entity / Mobs)的亮度阿!
所以這裡我找了一下資料,大概的作法都是:
LivingUpdateEvent處理我們的邏輯。相關的作法只要找一下都可以參考,我這邊只引用我需要的部分:
com.ithome.mymod.events套件目錄下新增LivingUpdate類別LivingUpdateEvent事件的方法:
public class LivingUpdate {
    @SubscribeEvent
    public void livingUpdate(LivingEvent.LivingUpdateEvent event) {
        if(event.entity instanceof HalloweenCreeper) {
            BlockPos pos = event.entity.getPosition();
            addLight(event.entity.worldObj, pos.getX(), pos.getY(), pos.getZ(), 10);
        }
    }
    private void addLight(World world, int posX, int posY, int posZ, int lightLevel) {
        world.setLightFor(EnumSkyBlock.BLOCK, new BlockPos(posX, posY, posZ), lightLevel);
        world.markBlockRangeForRenderUpdate( posX, posY, posZ, 12, 12, 12);
        world.updateBlockTick(new BlockPos( posX,  posY, posZ),
                world.getBlockState(new BlockPos( posX,  posY, posZ)).getBlock(), 1, 0);
        world.checkLightFor(EnumSkyBlock.BLOCK, new BlockPos( posX, posY + 1, posZ));
        world.checkLightFor(EnumSkyBlock.BLOCK, new BlockPos( posX, posY - 1, posZ));
        world.checkLightFor(EnumSkyBlock.BLOCK, new BlockPos(posX + 1, posY, posZ));
        world.checkLightFor(EnumSkyBlock.BLOCK, new BlockPos(posX - 1, posY, posZ));
        world.checkLightFor(EnumSkyBlock.BLOCK, new BlockPos(posX, posY, posZ + 1));
        world.checkLightFor(EnumSkyBlock.BLOCK, new BlockPos(posX, posY, posZ - 1));
    }
}
MyMod.java主程式內的eventTriggerMap物件內,新增我們的事件註冊:
MyMod.eventTriggerMap.put(new LivingUpdate(), true);
我們這邊只以苦力怕作為範例。
這個功能與我們在[Day27]提到的"孤僻貓"很像;要做的只是將所有的AI工作移除後,只保留會"追"玩家的部分就好:
HalloweenCreeper類別到com.ithome.mymod.entities套件下,繼承EntityCreeper並且把所有AI工作先移除:
public class HalloweenCreeper extends EntityCreeper {
    public static String name = "HalloweenCreeper";
    public HalloweenCreeper(World worldIn) {
        super(worldIn);
        setupUI();
    }
    private void setupUI() {
        this.tasks.taskEntries.clear();
        this.targetTasks.taskEntries.clear();
    }
}
MyMod.java主程式,註冊新的HalloweenCreeper實體,在preInit方法內加上:
// 註冊HalloweenCreeper
while (EntityList.getClassFromID(availableId) != null) {
    availableId++;
}
EntityRegistry.registerGlobalEntityID(HalloweenCreeper.class, HalloweenCreeper.name, availableId);
丟訊息的AI工作就比較簡單了:
EntityAIHalloween類別在com.ithome.mymod.ai下:public class EntityAIHalloween extends EntityAIBase {
    private final EntityMob entityMob;
    // 判斷碰撞的時間,避免太頻繁執行
    public static long timer;
    public EntityAIHalloween(EntityMob entityMob) {
        this.entityMob = entityMob;
        // 7可以與大部分的AI工作同時發生
        setMutexBits(7);
    }
    @Override
    public boolean shouldExecute() {
        if(entityMob.getAttackTarget() != null) {
            EntityLivingBase target = entityMob.getAttackTarget();
            float distance = entityMob.getDistanceToEntity(target);
            if(distance < 2.0F) {
                return true;
            }
        }
        return false;
    }
    @Override
    public void startExecuting() {
        EntityPlayer player = (EntityPlayer)entityMob.getAttackTarget();
        // 往碰撞的玩家送出訊息
        player.addChatComponentMessage(new ChatComponentText(EnumChatFormatting.LIGHT_PURPLE + "Trick or treat!"));
        // 設定執行時間
        timer = System.currentTimeMillis();
    }
    @Override
    public boolean continueExecuting() {
        // 若當前時間與執行時間相差在2秒內,就繼續這個AI工作來避免下一輪的觸發 (避免太頻繁)
        return System.currentTimeMillis() - timer < 2 * 1000;
    }
}
HalloweenCreeper類別,加上必要的AI工作:
private void setupUI() {
    this.tasks.taskEntries.clear();
    this.targetTasks.taskEntries.clear();
    // 送訊息給玩家的工作
    this.tasks.addTask(0, new EntityAIHalloween(this));
    // 閒晃
    this.tasks.addTask(1, new EntityAIWander(this, 0.8D));
    // 尋找玩家與靠近
    this.tasks.addTask(2, new EntityAIWatchClosest(this, EntityPlayer.class, 8.0F));
    this.tasks.addTask(3, new EntityAIAttackOnCollide(this, 1.0D, false));
    // Idle狀態
    this.tasks.addTask(4, new EntityAILookIdle(this));
    // 設定目標的工作
    this.targetTasks.addTask(0, new EntityAINearestAttackableTarget(this, EntityPlayer.class, true));
}
後續還有很多的功能可以自行發揮,這裡就完成一個簡單版的Halloween!