Jump to content

[1.7.10] How to create custom spawning without modifying bed/world spawn points


Recommended Posts

Posted

Hello!

 

New to Forge. I know my way around Java, so I hope I'll be able to provide enough info for anyone willing to help me. Been pulling my hair out over this for the past 2 days. :P

 

So, I've extended player properties via

IExtendedEntityProperties

and I've been attempting to control player spawning based on their properties that they have stored.

 

My mod has tile entity blocks that give players its coords on activation. These tile entities store two ints: 1 that tracks the number of spawns it has facilitated since its instantiation, and how many "resource points" it has left. When the player attempts to spawn at a tile entity they are subscribed, a resource point is deducted. If the tile entity is out of points, the player defaults to world spawn.

 

I've ran into several issues:

 

[*]When using the

EntityJoinWorldEvent

, I discovered that chunk coords are calculated before the event fires in

World.addEntityToWorld()

, making it (seemingly) impossible to accomplish my goal this way, as I can only modify the

EntityPlayer

's position, not it's chunk. Modifying the position will throw a "Wrong location!" warning

[*]The strange thing is it does in fact work, but only on first spawn. When a player first joins the world, it spawns on the tile entity block initially just fine, as its pulling the data from NBT that I set in

IExtendedEntityProperties

, but any subsequent respawn makes the player just spawn at respawn, even though I've written working persistent properties, so I know my properties are reattaching to the player just fine at respawn.

 

I'm sure the way I'm going about this is BEYOND hacky, so if anyone has any suggestion, by all means, let me know. I can provide code when I get home, but I was hoping I wouldn't have to do that, as some one might have an infinitely more elegant way of doing this. :)

Posted

I don't know the answers for sure, but you're generally thinking the right way about the debug but in modding you also have to figure out exactly what the heck Minecraft is doing.

 

I think one issue is that Minecraft was sort of made to consider the 0, 0 (x, z) position to be the origin for generation. So it isn't quite like you generate the world and then place the player, but rather you generate the world from the player outwards. They add a bit of randomness, but only around that initial position as you can see with the

 

Anyway, it still might be possible if you catch it at the right time. I think in the worst case you can handle the player tick which I think will be late enough that you could simply set the player position.

 

Some of the things you should consider about the spawn points:

- It gets saved in WorldInfo instance, which has methods to set and get the spawnX, spawnY, and spawnZ and I think these are world positions, not just in the chunk.

- There is a getSpawnPoint() method in WorldProvider class, but I think that give point within the chunk.

- There is a setSpawnLocation() method in the WorldServer class, that that is for point within a chunk.

 

Also, in modding it is usually instructive to find something that works similar to what you want and figure out how Minecraft does it. So I'd look at beds and see how they manage changes to the spawn position.

 

But again in worst case, handle the PlayerTickEvent instead and on the first tick (use boolean flag to prevent executing the code on all subsequent ticks) move the player. There might be a slight visual glitch, but at that point in the game the chunk loading isn't that pretty anyway.

 

Check out my tutorials here: http://jabelarminecraft.blogspot.com/

Posted

Thanks for the reply! A lot of good stuff. +1

 

I don't know the answers for sure, but you're generally thinking the right way about the debug but in modding you also have to figure out exactly what the heck Minecraft is doing.

 

I think one issue is that Minecraft was sort of made to consider the 0, 0 (x, z) position to be the origin for generation. So it isn't quite like you generate the world and then place the player, but rather you generate the world from the player outwards. They add a bit of randomness, but only around that initial position as you can see with the

 

I spent some time with the stack trace the warning was giving me which gave me some insight.

 

[*]

World.addEntityToWorld(Entity entity)

is called after the entity is finished constructing.

[*]Vars

i

and

j

are calculated using

(entity.posX / 16.0D)

and

(entity.posZ / 16.0D)

respectively, to be used later as chunk coordinates.

[*]

EntityJoinWorldEvent

is fired, at this point allowing entity modification (I don't remember the method sig, but I know the entity gets passed)

[*]World.getChunkFromChunkCoords(i, j).addEntity(entity) is called, meaning chunk coordinates are calculated before any position editing can be done in

EntityJoinWorldEvent

, making it is impossible to modify the chunk coordinates from the event.

 

This is what made me conclude that somehow I was going about this all wrong.

 

Anyway, it still might be possible if you catch it at the right time. I think in the worst case you can handle the player tick which I think will be late enough that you could simply set the player position.

 

Some of the things you should consider about the spawn points:

- It gets saved in WorldInfo instance, which has methods to set and get the spawnX, spawnY, and spawnZ and I think these are world positions, not just in the chunk.

- There is a getSpawnPoint() method in WorldProvider class, but I think that give point within the chunk.

- There is a setSpawnLocation() method in the WorldServer class, that that is for point within a chunk.

 

Also, in modding it is usually instructive to find something that works similar to what you want and figure out how Minecraft does it. So I'd look at beds and see how they manage changes to the spawn position.

 

I was actually reviewing the Perfect Spawn mod to see how the author handled multiple spawn points. There was some mention of WorldProvider, but I otherwise didn't spend enough time with it to fully grasp it.

 

I'll look into beds, though. Thanks!

 

But again in worst case, handle the PlayerTickEvent instead and on the first tick (use boolean flag to prevent executing the code on all subsequent ticks) move the player. There might be a slight visual glitch, but at that point in the game the chunk loading isn't that pretty anyway.

 

This might be viable, from what I can tell, since my main goal is high compatibility with other mods. Any visual side-effects aren't much of a concern to me. At this point, I'm just trying to prototype, and if it's really ugly, I'll change it. Thanks for this!

 

If anyone has any other suggestions, I'd love to hear them. Otherwise, I'll post code snippets as soon as I get home so I can hopefully paint a better picture of what I'm trying to do.

Posted

If you could provide code, it would be nice.

As to stuff - there are several events that would allow you do thing you are doing. Problem is that Minecraft won't (not full clean).

ServerConfigurationManager#recreatePlayerEntity - recreates EntityPlayer after death, there are also set all positions bed spawns, everything.

Sadly - there are no hooks that are launched before server sends data packets about new EntityPlayer (look code).

Your best shot is to go with:

@SubscribeEvent
public void onPlayerRespawn(PlayerEvent.PlayerRespawnEvent event)
{
	event.player.setPositionAndUpdate(100, 100, 100); // And use values from IEEP.
}

This event is launched from FML only on server, after reconstrucing and entity joining world - you are safe to use IEEP and any kind of new data.

 

This is for respawn ofc., For normal spawning (joining) you can use bunch of other events:

PlayerEvent.PlayerLoggedInEvent

PlayerEvent.PlayerChangedDimensionEvent

etc.

 

Disclaimer - I might have totally missed the point of what are you doing. I was more or less basing on thread title.

 

 

EDIT

Btw. if you really would like to go hardcore - you can make "nice" trick with data-swapping.

Bed-spawn position is accessible and changeable - the moment of player's death you can check if he should use your respawn system, if yes, copy position from your IEEP and swap bed-spawn loaction with it, that way the vanilla code will actually do everything on it's own, but using your swapped data. Then, again in PlayerRespawnEvent, you can swap data back to make it "normal".

 

This will save you bunch of (internal) packets and fix the problem with loading unnecessary chunks.

1.7.10 is no longer supported by forge, you are on your own.

Posted

Thanks guys for all the help. I was able to get something up and running!

 

EDIT

Btw. if you really would like to go hardcore - you can make "nice" trick with data-swapping.

Bed-spawn position is accessible and changeable - the moment of player's death you can check if he should use your respawn system, if yes, copy position from your IEEP and swap bed-spawn loaction with it, that way the vanilla code will actually do everything on it's own, but using your swapped data. Then, again in PlayerRespawnEvent, you can swap data back to make it "normal".

 

This will save you bunch of (internal) packets and fix the problem with loading unnecessary chunks.

 

This was the first thing I tried, but I kept getting invalid spawn point warnings in chat about a lack of a bed within the chunk, even though I was passing the

forced

flag. I gave up after moving it between different events still without luck.

 

But again in worst case, handle the PlayerTickEvent instead and on the first tick (use boolean flag to prevent executing the code on all subsequent ticks) move the player. There might be a slight visual glitch, but at that point in the game the chunk loading isn't that pretty anyway.

 

This did it! It's not particularly elegant, but it works as expected! Can definitely use this for prototyping, plus the visual shenanigans are negligible, even when moving great distances.

 

Here's my code, in case you were curious.

 

My tick handler:

public class PlayerTickEventHandler {

static boolean isFirstTick;

public PlayerTickEventHandler() {
	this.isFirstTick = true;
}

@SubscribeEvent
public void onPlayerTick(PlayerTickEvent event) {
	if (isFirstTick) {
		if (SupplyPlayer.get(event.player).hasSpawn){
			ChatUtils.sendServerMsg("Tick hit!");
			event.player.setPositionAndUpdate(SupplyPlayer.get(event.player).supplyX, 
                                                                                         SupplyPlayer.get(event.player).supplyY, 
								                 SupplyPlayer.get(event.player).supplyZ);
		}
	this.isFirstTick = false;
	}
}
}

 

My spawn event handler (I rolled the two events into a single class, but I'm not sure if this is best practice):

public class PlayerSpawnEventsHandler {

PlayerSpawnEventsHandler(){};

@SubscribeEvent 
public void onEntityJoinWorldEvent(EntityJoinWorldEvent event) {
	if (!event.entity.worldObj.isRemote && event.entity instanceof EntityPlayer) {
		PlayerTickEventHandler.isFirstTick = true;
		NBTTagCompound playerData = CommonProxy.getPlayerNBT(((EntityPlayer) event.entity).getDisplayName());
		if (playerData != null) {
			SupplyPlayer.getFromEvent(event).loadNBTData(playerData);
		}

	}
}

@SubscribeEvent
public void onLivingDeathEvent(LivingDeathEvent event) {
	if (!event.entity.worldObj.isRemote && event.entity instanceof EntityPlayer) {
		ChatUtils.sendServerMsg("Player successfully detected on death!");
		NBTTagCompound playerData = new NBTTagCompound();
		SupplyPlayer.getFromEvent(event).saveNBTData(playerData);
		CommonProxy.storePlayerNBT(((EntityPlayer) event.entity).getDisplayName(), playerData);
	}
}
}

 

And finally, my IEEP class (pretty much entirely lifted from coolAlias's awesome IEEP tutorial):

public class SupplyPlayer implements IExtendedEntityProperties {

public final static String EXT_PROP_NAME = "SupplyPlayer";

private EntityPlayer player;
public double supplyX, supplyY, supplyZ;
public boolean hasSpawn;

public SupplyPlayer(EntityPlayer player) {
	this.player = player;
	this.supplyX = 0;
	this.supplyY = 0;
	this.supplyZ = 0;
	this.hasSpawn = false;
}

public static void register(EntityPlayer player) {
	player.registerExtendedProperties(EXT_PROP_NAME, new SupplyPlayer(player));
}

public static SupplyPlayer get(EntityPlayer player) {
	return (SupplyPlayer) player.getExtendedProperties(EXT_PROP_NAME);
}

public static SupplyPlayer getFromEvent(EntityEvent event) {
	return SupplyPlayer.get((EntityPlayer) event.entity);
}

private static String getSaveKey(EntityPlayer player) {
	return player.getDisplayName() + ":" + EXT_PROP_NAME;
}

public static void saveProxyData(EntityPlayer player) {
	SupplyPlayer playerData = SupplyPlayer.get(player);

	NBTTagCompound savedData = new NBTTagCompound();

	playerData.saveNBTData(savedData);

	CommonProxy.storePlayerNBT(getSaveKey(player), savedData);
}

public static void loadProxyData(EntityPlayer player) {
	SupplyPlayer playerData = SupplyPlayer.get(player);
	NBTTagCompound savedData = CommonProxy.getPlayerNBT(getSaveKey(player));

	if(savedData != null) {
		playerData.loadNBTData(savedData);
	}
}

public void updateCoords(int supplyX, int supplyY, int supplyZ) {
	this.supplyX = supplyX;
	this.supplyY = supplyY;
	this.supplyZ = supplyZ;
	this.hasSpawn = true;
}

@Override
public void saveNBTData(NBTTagCompound compound) {
	NBTTagCompound props = new NBTTagCompound();

	props.setDouble("supplyX", this.supplyX);
	props.setDouble("supplyY", this.supplyY);
	props.setDouble("supplyZ", this.supplyZ);
	props.setBoolean("hasSpawn", this.hasSpawn);

	compound.setTag(EXT_PROP_NAME, props);
}


// Loads NBT data
@Override
public void loadNBTData(NBTTagCompound compound) {
	NBTTagCompound props = (NBTTagCompound) compound.getTag(EXT_PROP_NAME);

	this.supplyX = props.getDouble("supplyX");
	this.supplyY = props.getDouble("supplyY");
	this.supplyZ = props.getDouble("supplyZ");
	this.hasSpawn = props.getBoolean("hasSpawn");
}


// Basically useless for now
@Override
public void init(Entity entity, World world) {
}
}

 

Thanks again for the help. Hugely appreciated! Btw, if either of you have suggestions regarding my code, let me know. It's unfinished and I'm prone to making mistakes, but I'm also not the best programmer in the world, so I'm bound to overlook things.

Posted

Cool. Your code looks good to me, you have similar coding style. Regarding whether your event handling methods are in single class or separate classes, I personally throw them all into one class per event bus. Then I don't have to do the extra step of registering the class on the bus each time I decide to add some event handling. But it there is nothing wrong with having them separate.

Check out my tutorials here: http://jabelarminecraft.blogspot.com/

Posted

Err, about that 'awesome' tutorial, the part about persisting data across death by storing in the proxy is outdated - I made a post about a better method here, but haven't gotten around to updating the actual thread due to the 'new and improved and almost completely unusable' forum editor on MCF.

 

The gist of it is subscribe to PlayerEvent.Clone and use the previous player instance to copy the old IEEP data to the new player instance. Way cleaner than the old method.

Posted

Err, about that 'awesome' tutorial, the part about persisting data across death by storing in the proxy is outdated - I made a post about a better method here, but haven't gotten around to updating the actual thread due to the 'new and improved and almost completely unusable' forum editor on MCF.

 

The gist of it is subscribe to PlayerEvent.Clone and use the previous player instance to copy the old IEEP data to the new player instance. Way cleaner than the old method.

 

Thanks for popping in! If that's the case, I'll probably update it when I migrate everything to 1.8. I began this mod with the intention of making it compatible with another mod, but the mod in question is no longer maintained, and at this point, I'm mainly making a proof-of-concept. I'll add this to the list though, since so far, testing has gone smoothly, and everything is starting to get a little messy.

 

But hey, it was awesome to me. It got the results I wanted, and premature optimization is the devil's right hand. :)

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Announcements



×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.