Minecraft源码分析(1) - 刷怪逻辑

Entity种类

生物大致划分为四种:攻击型,被动型,水生型(也就是鱿鱼)和环境型(也就是蝙蝠)。攻击型生物有一个每游戏刻(1/20秒)一次的生成周期。被动型和水生型生物只有每400刻(20秒)一次的生成周期。因为这一点,攻击型生物可以在任何时候生成,而动物生成则非常少。另外,大部分动物在建立世界时产生的区块中生成。源码中对应的类是

EnumCreatureType.java

public enum EnumCreatureType
{
    MONSTER(IMob.class, 70, Material.air, false, false),
    CREATURE(EntityAnimal.class, 10, Material.air, true, true),
    AMBIENT(EntityAmbientCreature.class, 15, Material.air, true, false),
    WATER_CREATURE(EntityWaterMob.class, 5, Material.water, true, false);
    /**
     * The root class of creatures associated with this EnumCreatureType (IMobs for aggressive creatures, EntityAnimals
     * for friendly ones)
     */
    private final Class creatureClass;
    private final int maxNumberOfCreature;
    private final Material creatureMaterial;
    /** A flag indicating whether this creature type is peaceful. */
    private final boolean isPeacefulCreature;
    /** Whether this creature type is an animal. */
    private final boolean isAnimal;

    private EnumCreatureType(Class class, int _maxNumberOfCreature, Material _creatureMaterial, boolean _isPeacefulCreature, boolean _isAnimal)
    {
        this.creatureClass = class;
        this.maxNumberOfCreature = _maxNumberOfCreature;
        this.creatureMaterial = _creatureMaterial;
        this.isPeacefulCreature = _isPeacefulCreature;
        this.isAnimal = _isAnimal;
    }

    public Class getCreatureClass()
    {
        return this.creatureClass;
    }

    public int getMaxNumberOfCreature()
    {
        return this.maxNumberOfCreature;
    }

    /**
     * Gets whether or not this creature type is peaceful.
     */
    public boolean getPeacefulCreature()
    {
        return this.isPeacefulCreature;
    }

    /**
     * Return whether this creature type is an animal.
     */
    public boolean getAnimal()
    {
        return this.isAnimal;
    }
}

怪物容量(可理解为人口)与适合生成的区块总数直接成比例。要计算容量的话,生成区域在每一方向上均扩展一个区块,所以有17*17区块的大小,然后总的区块数被代入到下式中:

容量=常量*区块数/289
每一种生物均具有自己的容量计算和公式中不同的常量值:

攻击型 = 70
被动型 = 10
环境型(蝙蝠) = 15
水生型 = 5

服务器架构

图1. MC服务器架构

MinecraftServer作为服务器,主要负责服务端的更新,里面可以包含多个WorldServer,WorldClent作为服务端,当玩家加入一个服务器的时候,就会创建一个在本地。SpawnerAnimals作为刷怪的工具类,主要用来处理刷怪逻辑。

首先来看WorldServer的Tick

注:如无特别说明,所有tick都是一秒20次。

图2. WorldServer Tick 流程

很清晰的逻辑,这里主要看一下findChunksForSpawning的实现。

在单人游戏模式下,区块计数总为17*17=289,那么各种生物的容量也就是上面列出的数值。在多人游戏中,在多个玩家范围内的区块只被计算一次,所以玩家越分散,更多地区块会被覆盖且会有更高的生物容量。

在每次生成周期的开始都会检查一次容量。如果存活的生物数量超过它的容量,整个生成周期就会被跳过。

在每一生成周期中,会在每一个合适的区块中进行一次生成一组生物的尝试。该区块内选择一个随机地点作为这组生物的中心点。为生成这组生物,中心方块对水生生物而言必须是水方块,对所有其它生物来说则必须是空气方块。注意在后面的情形中,它一定得是空气方块。任何其它方块,哪怕是一个透明方块都会阻止整组生物的生成。

图3. 陆地怪物的生成条件

如果该组位置合适,会在以中心方块为原点41*1*41的范围(就是41*41格大小的方型,有1格高的区域)内进行12次尝试以生成多至4个的生物(狼是8个,恶魂是1个)。生物将会在这一区域生成其身体的最下部分。在每次生成尝试中,会在这一区域中随机选择一个方块的地点。尽管生成区域能扩展到中心21格之外,但是随机选出的地点强烈地向该组的中心集中。大约有85%的生成将会在该组中心的5格以内,99%会落在10格以内

组内所有的生物都是相同的种类。在该组第一次生成尝试时从该地区所适合生成的种类中随机挑选一种以决定整组的种类。

具体的种类可以参考要Minecraft的Wiki。

findChunksForSpawning函数实现的就是上面描述的逻辑。看一下SpawnerAnimals.java这个类。

public final class SpawnerAnimals
{
    private static final int MOB_COUNT_DIV = (int)Math.pow(17.0D, 2.0D);
    /** The 17x17 area around the player where mobs can spawn */
    private final Set eligibleChunksForSpawning = Sets.newHashSet();
    private static final String __OBFID = "CL_00000152";

    /**
     * adds all chunks within the spawn radius of the players to eligibleChunksForSpawning. pars: the world,
     * hostileCreatures, passiveCreatures. returns number of eligible chunks.
     */

    public int findChunksForSpawning(WorldServer server, boolean spawnHostileMobs, boolean spawnPeacefulMobs, boolean isSpecialSpawnTick)
    {
        if (!spawnHostileMobs && !spawnPeacefulMobs)
        {
            return 0;
        }
        else
        {
            this.eligibleChunksForSpawning.clear();
            int chunkCount = 0;
            Iterator iterator = server.playerEntities.iterator();
            int k;
            int creatureCount;

            while (iterator.hasNext())
            {
                EntityPlayer entityplayer = (EntityPlayer)iterator.next();

                if (!entityplayer.isSpectator())
                {
                    int j = MathHelper.floor_double(entityplayer.posX / 16.0D);
                    k = MathHelper.floor_double(entityplayer.posZ / 16.0D);
                    byte b0 = 8;

                    for (int l = -b0; l <= b0; ++l)
                    {
                        for (creatureCount = -b0; creatureCount <= b0; ++creatureCount)
                        {
                            boolean flag3 = l == -b0 || l == b0 || creatureCount == -b0 || creatureCount == b0;
                            ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(l + j, creatureCount + k);

                            if (!this.eligibleChunksForSpawning.contains(chunkcoordintpair))
                            {
                                ++chunkCount;

                                if (!flag3 && server.getWorldBorder().contains(chunkcoordintpair))
                                {
                                    this.eligibleChunksForSpawning.add(chunkcoordintpair);
                                }
                            }
                        }
                    }
                }
            }

            int totalEntityCount = 0;
            BlockPos blockpos2 = server.getSpawnPoint();
            EnumCreatureType[] aenumcreaturetype = EnumCreatureType.values();
            k = aenumcreaturetype.length;

            for (int i = 0; i < k; ++i)
            {
                EnumCreatureType enumcreaturetype = aenumcreaturetype[i];

                if ((!enumcreaturetype.getPeacefulCreature() || spawnPeacefulMobs) && (enumcreaturetype.getPeacefulCreature() || spawnHostileMobs) && (!enumcreaturetype.getAnimal() || isSpecialSpawnTick))
                {
                    creatureCount = server.countEntities(enumcreaturetype, true);
                    int maxCreatureCount = enumcreaturetype.getMaxNumberOfCreature() * chunkCount / MOB_COUNT_DIV;

                    if (creatureCount <= maxCreatureCount)
                    {
                        Iterator iterator1 = this.eligibleChunksForSpawning.iterator();
                        ArrayList<ChunkCoordIntPair> tmp = new ArrayList(eligibleChunksForSpawning);
                        Collections.shuffle(tmp);
                        iterator1 = tmp.iterator();
                        label115:

                        while (iterator1.hasNext())
                        {
                            ChunkCoordIntPair chunkcoordintpair1 = (ChunkCoordIntPair)iterator1.next();
                            BlockPos blockpos = getRandomChunkPosition(server, chunkcoordintpair1.chunkXPos, chunkcoordintpair1.chunkZPos);
                            int j1 = blockpos.getX();
                            int k1 = blockpos.getY();
                            int l1 = blockpos.getZ();
                            Block block = server.getBlockState(blockpos).getBlock();

                            if (!block.isNormalCube())
                            {
                                int entityCountOnChunk = 0;
                                int j2 = 0;

                                while (j2 < 3)
                                {
                                    int k2 = j1;
                                    int l2 = k1;
                                    int i3 = l1;
                                    byte b1 = 6;
                                    BiomeGenBase.SpawnListEntry spawnlistentry = null;
                                    IEntityLivingData ientitylivingdata = null;
                                    int j3 = 0;

                                    while (true)
                                    {
                                        if (j3 < 4)
                                        {
                                            label108:
                                            {
                                                k2 += server.rand.nextInt(b1) - server.rand.nextInt(b1);
                                                l2 += server.rand.nextInt(1) - server.rand.nextInt(1);
                                                i3 += server.rand.nextInt(b1) - server.rand.nextInt(b1);
                                                BlockPos blockpos1 = new BlockPos(k2, l2, i3);
                                                float f = (float)k2 + 0.5F;
                                                float f1 = (float)i3 + 0.5F;
                                                //Check must be away from Player by 24 block, and away from player spawn point. 576 = 24 * 24
                                                if (!server.CheckCanSpawnHere((double)f, (double)l2, (double)f1, 24.0D) && blockpos2.distanceSq((double)f, (double)l2, (double)f1) >= 576.0D)
                                                {
                                                    if (spawnlistentry == null)
                                                    {
                                                        spawnlistentry = server.GetSpawnListEntry(enumcreaturetype, blockpos1);

                                                        if (spawnlistentry == null)
                                                        {
                                                            break label108;
                                                        }
                                                    }

                                                    if (server.CheckChunkHasSpawnEntry(enumcreaturetype, spawnlistentry, blockpos1) && canCreatureTypeSpawnAtLocation(EntitySpawnPlacementRegistry.GetSpawnPointType(spawnlistentry.entityClass), server, blockpos1))
                                                    {
                                                        EntityLiving entityliving;

                                                        try
                                                        {
                                                            entityliving = (EntityLiving)spawnlistentry.entityClass.getConstructor(new Class[] {World.class}).newInstance(new Object[] {server});
                                                        }
                                                        catch (Exception exception)
                                                        {
                                                            exception.printStackTrace();
                                                            return totalEntityCount;
                                                        }

                                                        entityliving.setLocationAndAngles((double)f, (double)l2, (double)f1, server.rand.nextFloat() * 360.0F, 0.0F);

                                                        Result canSpawn = ForgeEventFactory.canEntitySpawn(entityliving, server, f, l2, f1);
                                                        if (canSpawn == Result.ALLOW || (canSpawn == Result.DEFAULT && (entityliving.getCanSpawnHere() && entityliving.handleLavaMovement())))
                                                        {
                                                            if (!ForgeEventFactory.doSpecialSpawn(entityliving, server, f1, l2, f1))
                                                            ientitylivingdata = entityliving.getEntityData(server.getDifficultyForLocation(new BlockPos(entityliving)), ientitylivingdata);

                                                            if (entityliving.handleLavaMovement())
                                                            {
                                                                ++entityCountOnChunk;
                                                                server.spawnEntityInWorld(entityliving);
                                                            }

                                                            if (entityCountOnChunk >= ForgeEventFactory.getMaxSpawnPackSize(entityliving))
                                                            {
                                                                continue label115;
                                                            }
                                                        }

                                                        totalEntityCount += entityCountOnChunk;
                                                    }
                                                }

                                                ++j3;
                                                continue;
                                            }
                                        }

                                        ++j2;
                                        break;
                                    }
                                }
                            }
                        }
                    }
                }
            }

            return totalEntityCount;
        }
    }

    protected static BlockPos getRandomChunkPosition(World worldIn, int x, int z)
    {
        Chunk chunk = worldIn.getChunkFromChunkCoords(x, z);
        int k = x * 16 + worldIn.rand.nextInt(16);
        int l = z * 16 + worldIn.rand.nextInt(16);
        int creatureCount = MathHelper.Ceiling(chunk.getHeight(new BlockPos(k, 0, l)) + 1, 16);
        int j1 = worldIn.rand.nextInt(creatureCount > 0 ? creatureCount : chunk.getTopFilledSegment() + 16 - 1);
        return new BlockPos(k, j1, l);
    }

    public static boolean canCreatureTypeSpawnAtLocation(EntityLiving.SpawnPlacementType placeType, World worldIn, BlockPos pos)
    {
        if (!worldIn.getWorldBorder().contains(pos))
        {
            return false;
        }
        else
        {
            Block block = worldIn.getBlockState(pos).getBlock();

            if (placeType == EntityLiving.SpawnPlacementType.IN_WATER)
            {
                return block.getMaterial().isLiquid() && worldIn.getBlockState(pos.down()).getBlock().getMaterial().isLiquid() && !worldIn.getBlockState(pos.up()).getBlock().isNormalCube();
            }
            else
            {
                BlockPos blockpos1 = pos.down();

                if (!worldIn.getBlockState(blockpos1).getBlock().canCreatureSpawn(worldIn, blockpos1, placeType))
                {
                    return false;
                }
                else
                {
                    Block block1 = worldIn.getBlockState(blockpos1).getBlock();
                    boolean flag = block1 != Blocks.bedrock && block1 != Blocks.barrier;
                    return flag && !block.isNormalCube() && !block.getMaterial().isLiquid() && !worldIn.getBlockState(pos.up()).getBlock().isNormalCube();
                }
            }
        }
    }

    /**
     * Called during chunk generation to spawn initial creatures.
     */
    public static void performWorldGenSpawning(World worldIn, BiomeGenBase biomeGenBase, int chunkCenterX, int chunkCenterY, int rangeX, int rangeY, Random rand)
    {
        List list = biomeGenBase.getSpawnableList(EnumCreatureType.CREATURE);

        if (!list.isEmpty())
        {
            while (rand.nextFloat() < biomeGenBase.getSpawningChance())
            {
                BiomeGenBase.SpawnListEntry spawnlistentry = (BiomeGenBase.SpawnListEntry)WeightedRandom.getRandomItem(worldIn.rand, list);
                int creatureCount = spawnlistentry.minGroupCount + rand.nextInt(1 + spawnlistentry.maxGroupCount - spawnlistentry.minGroupCount);
                IEntityLivingData ientitylivingdata = null;
                int j1 = chunkCenterX + rand.nextInt(rangeX);
                int k1 = chunkCenterY + rand.nextInt(rangeY);
                int l1 = j1;
                int entityCountOnChunk = k1;

                for (int j2 = 0; j2 < creatureCount; ++j2)
                {
                    boolean flag = false;

                    for (int k2 = 0; !flag && k2 < 4; ++k2)
                    {
                        BlockPos blockpos = worldIn.getTopSolidOrLiquidBlock(new BlockPos(j1, 0, k1));

                        if (canCreatureTypeSpawnAtLocation(EntityLiving.SpawnPlacementType.ON_GROUND, worldIn, blockpos))
                        {
                            EntityLiving entityliving;

                            try
                            {
                                entityliving = (EntityLiving)spawnlistentry.entityClass.getConstructor(new Class[] {World.class}).newInstance(new Object[] {worldIn});
                            }
                            catch (Exception exception)
                            {
                                exception.printStackTrace();
                                continue;
                            }

                            entityliving.setLocationAndAngles((double)((float)j1 + 0.5F), (double)blockpos.getY(), (double)((float)k1 + 0.5F), rand.nextFloat() * 360.0F, 0.0F);
                            worldIn.spawnEntityInWorld(entityliving);
                            ientitylivingdata = entityliving.func_180482_a(worldIn.getDifficultyForLocation(new BlockPos(entityliving)), ientitylivingdata);
                            flag = true;
                        }

                        j1 += rand.nextInt(5) - rand.nextInt(5);

                        for (k1 += rand.nextInt(5) - rand.nextInt(5); j1 < chunkCenterX || j1 >= chunkCenterX + rangeX || k1 < chunkCenterY || k1 >= chunkCenterY + rangeX; k1 = entityCountOnChunk + rand.nextInt(5) - rand.nextInt(5))
                        {
                            j1 = l1 + rand.nextInt(5) - rand.nextInt(5);
                        }
                    }
                }
            }
        }
    }
}

怪物的生命周期

待续。

特殊刷怪机制

刷怪箱逻辑

待续。

参考

Minecraft Wiki

Minecraft Forge

时间: 2024-10-12 15:32:40

Minecraft源码分析(1) - 刷怪逻辑的相关文章

Minecraft源码分析(3) - 刷怪笼(MobSpawner)逻辑

Minecraft刷怪笼顾名思义就是刷怪的笼子,遍布Minecraft各地,除了在水中.浮空和末地不存在外,一般来说刷怪笼都存在于低于地平线的位置,但是也有一些刷怪笼生成于高于地面的山中,有时会出现多个刷怪笼密集生成于一片区域的情况,比如蜘蛛刷怪笼.每种刷怪笼只会刷出一种怪,比如骷髅刷怪笼只会刷出骷髅弓箭手.当周围有刷怪笼时,可以听到独特的嘶嘶声,并且如果感觉某种怪物数量骤增,前赴后继地来攻击你,那周围很可能有刷怪笼存在.另外大部分刷怪笼都处于一个特制的房间中,其特点就是地板和墙壁是苔石铺成的,

传奇源码分析-客户端(游戏逻辑处理源分析四)

现在假设玩家开始操作游戏:传奇的客户端源代码工程WindHorn一.CWHApp派生CWHWindow和CWHDXGraphicWindow.二.CWHDefProcess派生出CloginProcess.CcharacterProcess.CgameProcess客户端WinMain调用CWHDXGraphicWindow g_xMainWnd;创建一个窗口.客户端CWHDXGraphicWindow在自己的Create函数中调用了CWHWindow的Create来创建窗口,然后再调用自己的C

传奇源码分析-客户端(游戏逻辑处理源分析五 服务器端响应)

器执行流程:(玩家走动) GameSrv服务器ProcessUserHuman线程处理玩家消息:遍历UserInfoList列表,依次调用每个UserInfo的Operate来处理命令队列中的所有操作; pUserInfo->Operate()调用m_pxPlayerObject->Operate()调用.判断玩家if (!m_fIsDead),如果已死,则发送_MSG_FAIL消息.我们在前面看到过,该消息是被优先处理的.否则则调用WalkTo,并发送_MSG_GOOD消息给客户端.Walk

传奇源码分析-客户端(游戏逻辑处理源分析三)

6. 接收怪物,商人,其它玩家的消息:ProcessUserHuman:(其它玩家-服务器处理)CPlayerObject->SearchViewRange();CPlayerObject->Operate();遍历UserInfoList列表,依次调用每个UserInfo的Operate来处理命令队列中的所有操作; pUserInfo->Operate()调用m_pxPlayerObject->Operate()调用.根据分发消息(RM_TURN)向客户端发送SM_TURN消息.

传奇源码分析-客户端(游戏逻辑处理源分析二)

5.接受登录成功后,接收GameSrv服务器发送的消息:接收GameGate发送的消息:CClientSocket::OnSocketMessage的FD_READ事件中,PacketQ.PushQ((BYTE*)pszPacket);把接收到的消息,压入PacketQ队列中.处理PacketQ队列数据是由CGameProcess::Load()时调用OnTimer在CGameProcess::OnTimer中处理的, 处理过程为:OnMessageReceive; ProcessPacket(

gomoblie flappy 源码分析:游戏逻辑

本文主要讨论游戏规则逻辑,具体绘制技术请参看相关文章: gomoblie flappy 源码分析:图片素材和大小的处理 http://www.cnblogs.com/ghj1976/p/5222289.html 绘制时间间隔控制 绘制是按照 60 FPS 的节奏绘制的(即每秒钟 60 帧),  FPS : frames per second(帧率) 代码中的控制注意是通过 golang.org/x/mobile/exp/sprite/clock 下的 Time 控制的.  Time实际是 int

【朝花夕拾】Android自定义View篇之(六)Android事件分发机制(中)从源码分析事件分发逻辑及经常遇到的一些“诡异”现象

前言 转载请注明,转自[https://www.cnblogs.com/andy-songwei/p/11039252.html]谢谢! 在上一篇文章[[朝花夕拾]Android自定义View篇之(五)Android事件分发机制(上)Touch三个重要方法的处理逻辑][下文简称(五),请先阅读完(五)再阅读本文],我们通过示例和log来分析了Android的事件分发机制.这些,我们只是看到了现象,如果要进一步了解事件分发机制,这是不够的,我们还需要透过现象看本质,去研究研究源码.本文将从源码(基

vscode源码分析【三】程序的启动逻辑,性能问题的追踪

第一篇: vscode源码分析[一]从源码运行vscode 第二篇:vscode源码分析[二]程序的启动逻辑,第一个窗口是如何创建的 启动追踪 代码文件:src\main.js 如果指定了特定的启动参数:trace vscode会在启动之初,执行下面的代码: const contentTracing = require('electron').contentTracing; const traceOptions = { categoryFilter: args['trace-category-f

Android触摸屏事件派发机制详解与源码分析二(ViewGroup篇)

1 背景 还记得前一篇<Android触摸屏事件派发机制详解与源码分析一(View篇)>中关于透过源码继续进阶实例验证模块中存在的点击Button却触发了LinearLayout的事件疑惑吗?当时说了,在那一篇咱们只讨论View的触摸事件派发机制,这个疑惑留在了这一篇解释,也就是ViewGroup的事件派发机制. PS:阅读本篇前建议先查看前一篇<Android触摸屏事件派发机制详解与源码分析一(View篇)>,这一篇承接上一篇. 关于View与ViewGroup的区别在前一篇的A