Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: Teleport to players' last locations in worlds #1794

Closed
wants to merge 6 commits into from

Conversation

qwylz
Copy link

@qwylz qwylz commented Feb 1, 2017

First, thank you for the great plugin. Recently I tested several plugins for managing multiple worlds and Multiverse-Core handily came out on top. Thank you!

Secondly, this is not actually a request to merge code at this time--this code is definitely not ready to be merged. I've submitted this PR more as an early request for comments and guidance as I work to get the code ready for a real pull request. Will you please read through this submission and the accompanying code and provide any suggestions and guidance necessary for me to get this ready for a true pull request?

This code is intended to add a new, non-default feature to Multiverse-Core. When enabled, the feature makes it so that when a player leaves a world, his/her last position in the world is saved. Then, when teleporting back to the world using the teleport syntax of just the bare world name (i.e. no "w:" or "e:" prefix), the player is teleported to his/her previously saved location in the world rather than to the world's spawn point. If the player has no saved location for the world, or if the save cannot be read/parsed for some reason, the player is teleported to the world's spawn point as a fallback measure. The behavior when using the "w:" or "e:" syntaxes is unchanged (i.e. always teleports to the world's spawn location or an exact location, respectively).

In it's current form, the code works, but is in an hackish, "can I even get this to work" kind of state. I need to take care of the following items before it's really ready for a true pull request:

  1. Currently, it's not possible to disable the feature. I still need to add a configuration property for the plugin that will control whether the feature is enabled or disabled. I plan to have it default to being disabled, so that, by default, there is no change in behavior.

  2. The code is currently embedded in several different classes. Very ugly. I need to refactor the code into it's own class(es) (and an exception) which will be utilized by the various listener and command classes.

  3. I haven't done anything about permissions, yet. There should probably be separate permissions for teleporting to a world at the player's saved location versus teleporting to the world's spawn point or exact locations.

  4. The code currently saves a players location in a world when leaving a world either via teleporting or the player quitting. It does not save players' locations when the server is stopped. I'm not sure how to listen for that. Is there a bukkit event? A player's location is also not saved when he/she is kicked from a world. I figure that if someone is misbehaving enough to be kicked, his/her location doesn't need to be save (however, if there was a previously saved location for the player, that saved information is not deleted).

  5. While my day job is software development, my experience is in other languages. I've never written a line of Java until writing the code for this feature yesterday. Hopefully, I haven't abused Java too badly, and there's likely more Java-like ways to do some of what this code does. Suggestions are welcome, please.

One final note: I have developed this code under Oracle's official Java 8 JDK. Hopefully, I haven't used any constructs or patterns that cause problems on earlier versions or other flavors of Java.

Again, thank you for the great plugin. I hope that I can get the code for this feature in order so that it can be merged. Please let me know of any guidance and suggestions that you have.

@dumptruckman
Copy link
Member

dumptruckman commented Feb 1, 2017

  1. Rather than a global config option, perhaps consider just using a single simple default: false permission for whether they should teleport to the world last location. This way the option can be per player and is still off by default.

  2. I'll have to look over the code and make comments there.

  3. See 1

  4. PlayerQuitEvent should cover all cases of the player leaving the server. PlayerChangeWorldEvent should cover all cases of players changing world. I would suggest making a simple yaml data file for this information. Just be sure to associate the location data with player UUID.

}
if (yc != null) {
try {
if (! yc.isSet("schema"))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

YamlConfiguration should have like getInteger() for this.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah ha! Yes, I see that now. When I first looked at the docs for YamlConfiguration, I didn't realize that the list of methods was not in alphabetic order. I stopped when I saw the .get and .isSet methods and no other .get or .is methods. I've refactored to use the .isInt, .isDouble, .getInt, and .getDouble methods. Thanks for the tip!

yc = null;
// fall through to use world spawn location
}
if (yc != null) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's been a while since I've looked at MV's code but I think we have a simpler way to serialize/deserialize location data.

*
* @return A non-null MVDestination
*/
public MVDestination getDestination(String destination) {
public MVDestination getDestination(String destination, Player player) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably better you leave the old method in just add this as a new method. This way we can preserve backwards compatibility.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you look a little later in the diff, you'll see that's what I already did. I didn't want to break the existing API. The original method is just a shim that supports the original API. It simply calls the new API with null for player and the behavior is the same as the old behavior and still keeps the code DRY.

@qwylz
Copy link
Author

qwylz commented Feb 7, 2017

Having the ability to enable/disable this on a per-player basis is a good idea. It allows for more use cases than my simple case which I had not thought of. I think that using the permissions system might be a good way to handle how to enable/disable for each player. I think that's what you were suggesting, as well (yes?).

I still think there will need to be a global default enable/disable item, otherwise I don't think my use case is achievable (unless I misunderstand what you're meaning). My use case is to specifically allow this behavior for all users without having to enable it for each new player that comes along. Having the global setting and then per-user permissions allows for both whitelisting and blacklisting use cases. Out of the box, it would be in whitelisting mode so as to retain backward compatibility.

@dumptruckman
Copy link
Member

If you use only permissions (white list as you say) rather than also having a configuration value you can just give your default user the permission in your permission manager. Even better, if you want it for everyone no questions you can set it to default: true in permissions.yml in your server root.

@qwylz
Copy link
Author

qwylz commented Feb 7, 2017

I think my confusion comes from not having studied MV's permissions model, thus far. Now that I've started to look at it, I think it's starting to make sense, but I need to do so more exploration on how the permissions all work together.

To that end, I have a question:

If a player has multiverse.teleport.self.e, it enables them to use /mv tp e: in/into any and all worlds for which they have multiverse.access.WORLDNAME, right? In other words, if a player has the .e permission they can teleport to exact destinations in all worlds to which they have the corresponding .access permission and there is no way to say that a player can exact teleport in/to worlds A and C, but not B (but still allow the player access to B via other teleport methods), right?

@dumptruckman
Copy link
Member

Yes, you have that right.

@qwylz
Copy link
Author

qwylz commented Feb 11, 2017

Now that I understand how the permissions all work, I understand what you meant about using permissions rather than needing a global configuration item. It's a great idea! I've got that all sorted out and implemented now. I think I've now taken care of all the outstanding items.

There are three new permissions used:

multiverse.teleport.self.l
multiverse.teleport.other.l
multiverse.save.lastlocations

The first two are used to control who can use the new last location destination functionality. The /mv tp command, when given a destination that does not specify a location type identifier, will intelligently figure out whether to use a last location destination or just the original world spawn destination based on permissions.

If a player has either multiverse.teleport.self.l or multiverse.save.lastlocations his/her last location in a world will be saved (otherwise it will not be saved). This makes it possible to make it so a user's last locations are saved without allowing them to teleport to their last locations (by just giving the user multiverse.save.lastlocations).

I think I have this all ready to be a real PR. Can this code be merged?

P.S.
Sorry, I see that I spelled refactor wrong in my last commit. :-(

Copy link
Member

@dumptruckman dumptruckman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice overall. There's still a few things that need addressing before we can merge.

@@ -258,6 +260,8 @@ public void onLoad() {
// Setup our SafeTTeleporter
this.safeTTeleporter = new SimpleSafeTTeleporter(this);
this.unsafeCallWrapper = new UnsafeCallWrapper(this);
// Set up MVPlayerLocation
MVPlayerLocation.init(this);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any way we can avoid static initialization like this?

}

player = (Player) e;
this.location = MVPlayerLocation.getPlayerLastLocation(player, world);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this location be set in setDestination using a non static method? Such as plugin.getPlayerLocationManager().getPlayerLastLocation... or something. Just trying to avoid static stuff like this.

this.plugin.removePlayerSession(event.getPlayer());
Player player = event.getPlayer();
if (this.plugin.getMVPerms().hasPermission(player, "multiverse.teleport.self."
+ LastLocationDestination.getID(), true)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Theoretically the third arg here should be false, but it isn't used anymore so I guess it doesn't matter.

* This method is called when a player teleports anywhere.
* @param event The Event that was fired.
*/
@EventHandler(priority = EventPriority.MONITOR)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should add an ignoreCancelled = true here.

@@ -315,7 +339,9 @@ private void sendPlayerToDefaultWorld(final Player player) {
new Runnable() {
@Override
public void run() {
player.teleport(plugin.getMVWorldManager().getFirstSpawnWorld().getSpawnLocation());
LastLocationDestination destination = new LastLocationDestination();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are there no permission checks here?

}

try {
yc.save(new File(dir, playerID + ".yaml"));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extension should be .yml. Again, I think you could cut down on files by just having 1 file per player.

playerID = player.getUniqueId().toString();

file = new File(plugin.getDataFolder(), PLAYER_LOCATION_DATA
+ File.separator + world.getName() + File.separator + playerID + ".yaml");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.yml

}

public static void savePlayerLocation(Player player, Location location, String action) {
String world = location.getWorld().getName();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should use World UUID rather than name. Also, I might suggest just having 1 file for each player (again using UUID) with all world data stored in it.

plugin = p;
}

private static class InvalidData extends Exception {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exceptions should be follow the naming scheme SuchAndSuchException. Also, make sure there's not an existing exception in Java that might cover this case well enough.

try {
if (! yc.isSet("schema"))
throw new InvalidData("missing schema node");
if (! yc.isInt("schema"))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isInt being true implies isSet is true. No need for both.

@dumptruckman
Copy link
Member

Also, consider squashing your commits.

@Tzzzt123
Copy link

I vote for this Pull request!

@ghost
Copy link

ghost commented Apr 16, 2017

@Tzzzt123 Same here. Been trying to find a fix here on GH and see this. Would love for this to be a feature. Would help a lot.

@fernferret
Copy link
Member

fernferret commented Apr 19, 2017 via email

@mattysweeps
Copy link

3 years later

@M4cs
Copy link

M4cs commented Mar 10, 2020

So how exactly do we enable this then?

@mattysweeps
Copy link

https://github.com/Taronyuu/StayPut is almost what you'd want, except it doesn't choose the default spawn location when there's no previous location in the database.

@LoneDev6
Copy link

Any news?

@edcous
Copy link

edcous commented Jun 14, 2020

Stay put

@pixelblob
Copy link

Anything happening?

@LoneDev6
Copy link

Anything happening?

I don't think so

@phemmer
Copy link

phemmer commented Aug 21, 2020

Stumbled across this PR while looking for how to preserve last location.
The functionality exists in the Multiverse-Inventories plugin.

Add all the worlds to a "share" with the last_location property set. Or add last_location to the use_optionals list in the config.

@benwoo1110
Copy link
Member

Didn't read fully, but this looks like a feature of Multiverse-Inventories.

@benwoo1110 benwoo1110 added PR: Enhancement Pull requests to implement a feature or improvement in code. State: On Hold Issues/PRs that need more to discuss and assess. Other MV Plugin labels Oct 9, 2020
@nicegamer7 nicegamer7 changed the base branch from master to main November 25, 2020 18:33
@benwoo1110
Copy link
Member

outdated PR

@benwoo1110 benwoo1110 closed this Feb 3, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
PR: Enhancement Pull requests to implement a feature or improvement in code. State: On Hold Issues/PRs that need more to discuss and assess.
Projects
None yet
Development

Successfully merging this pull request may close these issues.