// Requirements: // - ✔ Try to not move a lot vertically // - ✔ If territorial: Stay close to the spawn point // - ✔ If air habitat: Don't go above maxHeight blocks above surface // - ✔ If land habitat: Don't walk into water, prefer surface // - ~~If cave habitat: Prefer caves~~ // - ✔ If water habitat: Don't walk onto land // - ✔ Try not to fall from very large heights. Try not to fall from any large heights if entity has FallDamage // - ✔ Prefer preferredLightLevel // - ✔ If land habitat: Must be above a block the entity can stand on // - ✔ if failed searches is high, reduce wander range public Vec3d loadNextWanderTarget() { EnumHabitat habitat = entity.Properties.Habitat; bool canFallDamage = entity.Properties.FallDamage; bool territorial = StayCloseToSpawn; int tries = 9; Vec4d bestTarget = null; Vec4d curTarget = new Vec4d(); BlockPos tmpPos = new BlockPos(); if (FailedConsecutivePathfinds > 10) { WanderRangeMul = Math.Max(0.1f, WanderRangeMul * 0.9f); } else { WanderRangeMul = Math.Min(1, WanderRangeMul * 1.1f); if (rand.NextDouble() < 0.05) { WanderRangeMul = Math.Min(1, WanderRangeMul * 1.5f); } } float wRangeMul = WanderRangeMul; double dx, dy, dz; if (rand.NextDouble() < 0.05) { wRangeMul *= 3; } while (tries-- > 0) { dx = wanderRangeHorizontal.nextFloat() * (rand.Next(2) * 2 - 1) * wRangeMul; dy = wanderRangeVertical.nextFloat() * (rand.Next(2) * 2 - 1) * wRangeMul; dz = wanderRangeHorizontal.nextFloat() * (rand.Next(2) * 2 - 1) * wRangeMul; curTarget.X = entity.ServerPos.X + dx; curTarget.Y = entity.ServerPos.Y + dy; curTarget.Z = entity.ServerPos.Z + dz; curTarget.W = 1; if (StayCloseToSpawn) { double distToEdge = curTarget.SquareDistanceTo(SpawnPosition) / (MaxDistanceToSpawn * MaxDistanceToSpawn); // Prefer staying close to spawn curTarget.W = 1 - distToEdge; } Block block; switch (habitat) { case EnumHabitat.Air: int rainMapY = world.BlockAccessor.GetRainMapHeightAt((int)curTarget.X, (int)curTarget.Z); // Don't fly above max height curTarget.Y = Math.Min(curTarget.Y, rainMapY + maxHeight); // Cannot be in water block = entity.World.BlockAccessor.GetBlock((int)curTarget.X, (int)curTarget.Y, (int)curTarget.Z); if (block.IsLiquid()) { curTarget.W = 0; } break; case EnumHabitat.Land: curTarget.Y = moveDownToFloor((int)curTarget.X, curTarget.Y, (int)curTarget.Z); // No floor found if (curTarget.Y < 0) { curTarget.W = 0; } else { // Does not like water block = entity.World.BlockAccessor.GetBlock((int)curTarget.X, (int)curTarget.Y, (int)curTarget.Z); if (block.IsLiquid()) { curTarget.W /= 2; } // Lets make a straight line plot to see if we would fall off a cliff bool stop = false; bool willFall = false; float angleHor = (float)Math.Atan2(dx, dz) + GameMath.PIHALF; Vec3d target1BlockAhead = curTarget.XYZ.Ahead(1, 0, angleHor); Vec3d startAhead = entity.ServerPos.XYZ.Ahead(1, 0, angleHor); // Otherwise they are forever stuck if they stand over the edge int prevY = (int)startAhead.Y; GameMath.BresenHamPlotLine2d((int)startAhead.X, (int)startAhead.Z, (int)target1BlockAhead.X, (int)target1BlockAhead.Z, (x, z) => { if (stop) { return; } double nowY = moveDownToFloor(x, prevY, z); // Not more than 4 blocks down if (nowY < 0 || prevY - nowY > 4) { willFall = true; stop = true; } // Not more than 2 blocks up if (nowY - prevY > 2) { stop = true; } prevY = (int)nowY; }); if (willFall) { curTarget.W = 0; } } break; case EnumHabitat.Sea: block = entity.World.BlockAccessor.GetBlock((int)curTarget.X, (int)curTarget.Y, (int)curTarget.Z); if (!block.IsLiquid()) { curTarget.W = 0; } break; } if (curTarget.W > 0) { // Try to not hug the wall so much for (int i = 0; i < BlockFacing.HORIZONTALS.Length; i++) { BlockFacing face = BlockFacing.HORIZONTALS[i]; block = entity.World.BlockAccessor.GetBlock((int)curTarget.X + face.Normali.X, (int)curTarget.Y, (int)curTarget.Z + face.Normali.Z); if (block.SideSolid[face.Opposite.Index]) { curTarget.W *= 0.5; } } } if (preferredLightLevel != null) { tmpPos.Set((int)curTarget.X, (int)curTarget.Y, (int)curTarget.Z); int lightdiff = Math.Abs((int)preferredLightLevel - entity.World.BlockAccessor.GetLightLevel(tmpPos, EnumLightLevelType.MaxLight)); curTarget.W /= Math.Max(1, lightdiff); } if (bestTarget == null || curTarget.W > bestTarget.W) { bestTarget = new Vec4d(curTarget.X, curTarget.Y, curTarget.Z, curTarget.W); } } if (bestTarget.W > 0) { //double bla = bestTarget.Y; //bestTarget.Y += 1; //dx = bestTarget.X - entity.ServerPos.X; //dz = bestTarget.Z - entity.ServerPos.Z; //Vec3d sadf = bestTarget.XYZ.Ahead(1, 0, (float)Math.Atan2(dx, dz) + GameMath.PIHALF); /*(entity.Api as ICoreServerAPI).World.HighlightBlocks(world.AllOnlinePlayers[0], 10, new List<BlockPos>() { * new BlockPos((int)bestTarget.X, (int)bestTarget.Y, (int)bestTarget.Z) }, new List<int>() { ColorUtil.ColorFromRgba(0, 255, 0, 80) }, EnumHighlightBlocksMode.Absolute, EnumHighlightShape.Arbitrary); * (entity.Api as ICoreServerAPI).World.HighlightBlocks(world.AllOnlinePlayers[0], 11, new List<BlockPos>() { * new BlockPos((int)sadf.X, (int)sadf.Y, (int)sadf.Z) }, new List<int>() { ColorUtil.ColorFromRgba(0, 255, 255, 180) }, EnumHighlightBlocksMode.Absolute, EnumHighlightShape.Arbitrary);*/ //bestTarget.Y = bla; FailedConsecutivePathfinds = Math.Max(FailedConsecutivePathfinds - 3, 0); return(bestTarget.XYZ); } FailedConsecutivePathfinds++; return(null); }