问题
Using Forge 1.8.9 in Eclipse (Mars.1 Release (4.5.1)) in local development environment.
I'm trying to set a player's location every time they join or re-join a world. It always works the first time (e.g. run and join the world. See first screen shot).
After moving around the world a little bit, then logging out of that world and going back in (same session w/out closing down MC), the world fails to appear and in the Console. The location is the same location as in the "all OK" login. Plus there is a Wrong Location! Error.
The error from the console is here:
[05:47:53] [Server thread/INFO]: Player992 joined the game
[05:47:53] [Server thread/WARN]: Wrong location! (9, 9) should be (9, 6), EntityPlayerMP['Player992'/2371, l='world', x=145.00, y=73.00, z=145.00]
[05:48:18] [Server thread/INFO]: Saving and pausing game...
[05:48:18] [Server thread/INFO]: Saving chunks for level 'world'/Overworld
[05:48:18] [Server thread/INFO]: Saving chunks for level 'world'/Nether
[05:48:18] [Server thread/INFO]: Saving chunks for level 'world'/The End
I've tried a few variations of this, including Minecraft Forge: Using correct Join Game listener for setLocationAndAngles but no dice (different behaviour).
Ignore all the 'imports' that aren't relevant. They are artifacts of my multiple attempts.
import net.minecraft.util.ChatComponentText;
import net.minecraft.util.EnumChatFormatting;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.util.EnumChatFormatting;
import net.minecraftforge.event.entity.EntityJoinWorldEvent;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.gameevent.PlayerEvent.PlayerLoggedInEvent;
//import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.client.event.RenderWorldEvent;
import net.minecraftforge.event.world.WorldEvent;
public class JoinGameLocation {
@SubscribeEvent
public void onEntityJoinWorld(EntityJoinWorldEvent event) {
if (event.entity != null && event.entity instanceof EntityPlayer && !event.entity.worldObj.isRemote) {
event.entity.setLocationAndAngles(145, 73, 145, 0, 0);
}
}
}
I've done some reading on the Wrong Location error, but something doesn't seem right, given that I can appear in that location the first time around, so it is not like I am appearing inside a block. I've tried creating a short delay (1-3s), but the error still occurs.
回答1:
"Wrong location!" is used when an entity is added to a chunk that it shouldn't be in from its coordinates.
Here's where (in World.java
) the event is fired (well, actually, there are a few other places, but this is the one used by players among other entities):
/**
* Called when an entity is spawned in the world. This includes players.
*/
public boolean spawnEntityInWorld(Entity p_72838_1_)
{
// do not drop any items while restoring blocksnapshots. Prevents dupes
if (!this.isRemote && (p_72838_1_ == null || (p_72838_1_ instanceof net.minecraft.entity.item.EntityItem && this.restoringBlockSnapshots))) return false;
int i = MathHelper.floor_double(p_72838_1_.posX / 16.0D);
int j = MathHelper.floor_double(p_72838_1_.posZ / 16.0D);
boolean flag = p_72838_1_.forceSpawn;
if (p_72838_1_ instanceof EntityPlayer)
{
flag = true;
}
if (!flag && !this.isChunkLoaded(i, j, true))
{
return false;
}
else
{
if (p_72838_1_ instanceof EntityPlayer)
{
EntityPlayer entityplayer = (EntityPlayer)p_72838_1_;
this.playerEntities.add(entityplayer);
this.updateAllPlayersSleepingFlag();
}
if (net.minecraftforge.common.MinecraftForge.EVENT_BUS.post(new net.minecraftforge.event.entity.EntityJoinWorldEvent(p_72838_1_, this)) && !flag) return false;
this.getChunkFromChunkCoords(i, j).addEntity(p_72838_1_);
this.loadedEntityList.add(p_72838_1_);
this.onEntityAdded(p_72838_1_);
return true;
}
}
Note that i
and j
(chunk coordinates) aren't changed after updating the player's location. So when Chunk.addEntity
(see below) is called, things don't work:
/**
* Adds an entity to the chunk. Args: entity
*/
public void addEntity(Entity entityIn)
{
this.hasEntities = true;
int i = MathHelper.floor_double(entityIn.posX / 16.0D);
int j = MathHelper.floor_double(entityIn.posZ / 16.0D);
if (i != this.xPosition || j != this.zPosition)
{
logger.warn("Wrong location! (" + i + ", " + j + ") should be (" + this.xPosition + ", " + this.zPosition + "), " + entityIn, new Object[] {entityIn});
entityIn.setDead();
}
// ... rest of the method
}
This kills the player.
I'm not entirely sure why it's working the first time. It'll work whenever you log in in the same chunk as the one you'd be teleported to, so if you log out after you're in the wrong location, you'll log in successfully the next time.
Before I get to a fix, here's a few other things to note:
- You don't need to do a null check with instanceof -
null
will never pass an instanceof test. - (At least according to CommandTeleport), you need to teleport
EntityPlayerMP
s differently, usingEntityPlayerMP.playerNetServerHandler.setPlayerLocation
.
To fix it, you'll need to delay the teleportation by 1 tick. I'm not sure quite what the canonical forge method for that is, but something like this should work:
List<Entity> playersToTeleport = new ArrayList<Entity>();
@SubscribeEvent
public void onEntityJoinWorld(EntityJoinWorldEvent event) {
if (event.entity instanceof EntityPlayer && !event.entity.worldObj.isRemote) {
playersToTeleport.add(event.entity);
}
}
@SubscribeEvent
public void teleportEntiesOnWorldTick(TickEvent.WorldTickEvent event) {
// Make sure that this is the type of tick we want.
if (event.phase == TickEvent.Phase.START && event.type == TickEvent.Type.WORLD) {
for (Entity entity : playersToTeleport) {
if (entity.worldObj == event.world) {
if (entity instanceof EntityPlayerMP) {
((EntityPlayerMP) entity).playerNetServerHandler.setPlayerLocation(145, 73, 145, 0, 0);
} else {
entity.setLocationAndAngles(145, 73, 145, 0, 0);
}
}
}
playersToTeleport.clear();
}
}
If you need to be able to change the position the player will be at rather than always going to those specific coordinates, here's one way of doing so:
@SubscribeEvent
public void onEntityJoinWorld(EntityJoinWorldEvent event) {
if (event.entity instanceof EntityPlayer && !event.entity.worldObj.isRemote) {
queueTeleportNextTick(event.entity, Math.random() * 200 - 100, 73,
Math.random() * 200 - 100, 0, 0);
}
}
/**
* List of teleports to perform next tick.
*/
private List<TeleportInfo> queuedTeleports = new ArrayList<TeleportInfo>();
/**
* Stores information about a future teleport.
*/
private static class TeleportInfo {
public TeleportInfo(Entity entity, double x, double y, double z,
float yaw, float pitch) {
this.entity = entity;
this.x = x;
this.y = y;
this.z = z;
this.yaw = yaw;
this.pitch = pitch;
}
public final Entity entity;
public final double x;
public final double y;
public final double z;
public final float yaw;
public final float pitch;
}
/**
* Teleport the given entity to the given coordinates on the next game tick.
*/
public void queueTeleportNextTick(Entity entity, double x, double y,
double z, float yaw, float pitch) {
System.out.printf("Preparing to teleport %s to %f, %f, %f%n", entity, x, y, z);
queuedTeleports.add(new TeleportInfo(entity, x, y, z, yaw, pitch));
}
@SubscribeEvent
public void teleportEntiesOnWorldTick(TickEvent.WorldTickEvent event) {
// Make sure that this is the type of tick we want.
if (event.phase == TickEvent.Phase.START && event.type == TickEvent.Type.WORLD) {
// Perform each teleport
Iterator<TeleportInfo> itr = queuedTeleports.iterator();
while (itr.hasNext()) {
TeleportInfo info = itr.next();
if (info.entity.worldObj == event.world) {
System.out.printf("Teleporting %s to %f, %f, %f%n", info.entity, info.x, info.y, info.z);
if (info.entity instanceof EntityPlayerMP) {
// EntityPlayerMPs are handled somewhat differently.
((EntityPlayerMP) info.entity).playerNetServerHandler
.setPlayerLocation(info.x, info.y, info.z,
info.pitch, info.yaw);
} else {
info.entity.setLocationAndAngles(info.x, info.y, info.z,
info.pitch, info.yaw);
}
itr.remove();
}
}
}
}
Also, note that to use TickEvent
, you need to register to a separate bus than you'd use with EntityJoinWorldEvent
, so to fully register the events used here, you'd do this:
MinecraftForge.EVENT_BUS.register(this);
FMLCommonHandler.instance().bus().register(this);
来源:https://stackoverflow.com/questions/35503721/minecraft-forge-entityjoinworldevent-returns-wrong-location-error