GlowstoneMC / Glowstone

A fast, customizable and compatible open source server for Minecraft: Java Edition
https://glowstone.net
Other
1.88k stars 270 forks source link

Fire PlayerInitialSpawnEvent (#922) #1051

Closed clabe45 closed 4 years ago

clabe45 commented 4 years ago

A PlayerInitialSpawnEvent is now fired the first time spawnAt is called for a player.

CLAassistant commented 4 years ago

CLA assistant check
All committers have signed the CLA.

SHADOWDANCH commented 4 years ago

I think PlayerInitialSpawnEvent should be called not on first join but just before spawning player entity in world. Paper original patch here (removed)

aramperes commented 4 years ago

@SHADOWDANCH Contributions based on source code (patches or otherwise) from CraftBukkit or any of its forks are prohibited by our CLA. I've removed the link in your comment as a precaution.

If the documentation is insufficient for this particular event, I suggest contacting the PaperMC team on Discord for clarification and/or suggesting they update their docs.

SHADOWDANCH commented 4 years ago

@momothereal if i myself show how this event works on paper (screenshots or something) it be correct?

aramperes commented 4 years ago

@SHADOWDANCH Yes, that is considered reverse-engineering and it is encouraged by the project :)

SHADOWDANCH commented 4 years ago

So i created simple test plugin and here is source code. And here results i get Output on first spawn:

playerName: SHADOWDAN_
hasPlayedBefore: false
spawnLocation: Location{world=CraftWorld{name=world},x=-169.5,y=64.0,z=256.5,pitch=0.0,yaw=0.0}

Output after second spawn:

playerName: SHADOWDAN_
hasPlayedBefore: true
spawnLocation: Location{world=CraftWorld{name=world},x=-169.5,y=64.0,z=256.5,pitch=-3.45,yaw=5.3270264}

Ss you can see event called on second spawn too. So Initial in current scope means not player first join to world but first spawning player entity. Changing location in PlayerInitialSpawnEvent changes location where player entity be spawned and in if change location in PlayerSpawnLocationEvent player be "teleported" to new location and not initially spawned on it

clabe45 commented 4 years ago

@SHADOWDANCH so you're saying it should be fired every time the player joins?

SHADOWDANCH commented 4 years ago

@clabe45 yes every time before player spawning in world

clabe45 commented 4 years ago

@SHADOWDANCH so before spawnAt is called?

SHADOWDANCH commented 4 years ago

@clabe45 yes, i think

mastercoms commented 4 years ago

I would do it in GlowPlayer#join. Sorry for not doing my research first.

clabe45 commented 4 years ago

@momothereal what do you think?

aramperes commented 4 years ago

This is tricky because the event has a setter, setSpawnLocation. In my opinion, the result of the setter should be the initial location of the player entity, which is currently being set in the constructor: https://github.com/GlowstoneMC/Glowstone/blob/760bff1c4f4fc19ea8507d69924bb7cacb6a9d80/src/main/java/net/glowstone/entity/GlowPlayer.java#L579-L580

This is due to our Entity implementation which requires a Location in the constructor. I think this has been a limitation in other parts of Glowstone before which should be revamped.

So I looked for where PlayerSpawnLocationEvent was being called, and I found this in GlowEntity's constructor:

https://github.com/GlowstoneMC/Glowstone/blob/760bff1c4f4fc19ea8507d69924bb7cacb6a9d80/src/main/java/net/glowstone/entity/GlowEntity.java#L319-L325

It seems like the PlayerInitialSpawnEvent call should be identical to this one, potentially right before this event. Again, I would confirm the difference between these two events with the Paper team.

@SHADOWDANCH Maybe you could test the ordering of these events in your plugin?

SHADOWDANCH commented 4 years ago

@momothereal yes. Updated source code here Output i get:

UUID of player SHADOWDAN_ is 8648480e-b14a-383f-8242-1f3a0b2f4082
PlayerInitialSpawnEvent called! playerName: SHADOWDAN_
hasPlayedBefore: true
spawnLocation: Location{world=CraftWorld{name=world},x=-155.5272637568838,y=64.0,z=250.0834669566757,pitch=9.300014,yaw=52.427734}
PlayerSpawnLocationEvent called! playerName: SHADOWDAN_
SHADOWDAN_[/127.0.0.1:3195] logged in with entity id 79 at ([world]-155.5272637568838, 64.0, 250.0834669566757)

So PlayerInitialSpawnEvent called before PlayerSpawnLocationEvent

aramperes commented 4 years ago

@SHADOWDANCH Cool. Could you test that setting the PlayerInitialSpawnEvent#setSpawnLocation value carries over to the PlayerSpawnLocationEvent#getSpawnLocation in the event handler?

If it does carry over, then I think the correct implementation is to copy the firing of PlayerSpawnLocationEvent right before it in the GlowEntity constructor.

SHADOWDANCH commented 4 years ago

@momothereal yes. Updated source code here Output:

UUID of player SHADOWDAN_ is 8648480e-b14a-383f-8242-1f3a0b2f4082
PlayerInitialSpawnEvent called! playerName: SHADOWDAN_
hasPlayedBefore: true
spawnLocation: Location{world=CraftWorld{name=world},x=-156.70885792222927,y=74.0,z=255.04264081124023,pitch=12.450013,yaw=20.777733}
PlayerSpawnLocationEvent called! playerName: SHADOWDAN_
spawnLocation: Location{world=CraftWorld{name=world},x=-156.70885792222927,y=74.0,z=255.04264081124023,pitch=12.450013,yaw=20.777733}
SHADOWDAN_[/127.0.0.1:3933] logged in with entity id 106 at ([world]-156.70885792222927, 74.0, 255.04264081124023)

SpawnLocation same in this events so it passes from PlayerInitialSpawnEvent to PlayerSpawnLocationEvent

clabe45 commented 4 years ago

Ok, points noted. Could we move that player-specific code in GlowEntity constructor to GlowPlayer? I think that would make more sense and it would make my job easier, unless if it would break something.

aramperes commented 4 years ago

Unfortunately, moving this code to GlowPlayer is not possible since it would be fired after the GlowEntity, GlowLivingEntity, and GlowHumanEntity constructors are done executing. The GlowEntity constructor depends on the spawnLocation to determine which world to allocate the entity to.

The revamp I mentioned would move the allocation portion out of the GlowEntity constructor, but that is not in the scope of this PR. If you want to simply implement the event with our current architecture, it has to be fired in the GlowEntity constructor just like PlayerSpawnLocationEvent.

Also, looking at the GlowEntity constructor totally shows a bug with the world being chosen before the event is fired. Maybe you could fix this by declaring the world variable after the if-statement? https://github.com/GlowstoneMC/Glowstone/blob/760bff1c4f4fc19ea8507d69924bb7cacb6a9d80/src/main/java/net/glowstone/entity/GlowEntity.java#L317-L327

clabe45 commented 4 years ago

Ahh ok, so I will fire the PlayerInitialSpawnEvent before PlayerSpawnLocationEvent in the GlowEntity constructor. The location for PlayerSpawnLocationEvent and the player's location itself will depend on the result from PlayerInitialSpawnEvent. However, what's the good of setting the local variable location in the constructor of GlowEntity after it is cloned in the beginning of the constructor? I don't know how to work with that.

aramperes commented 4 years ago

Hm that's interesting. The field is set at the beginning so that calling event.getPlayer().getLocation() still works, but it seems like the output of the event is never set. Something like:

public GlowEntity(Location location) {
    // Set initial locations for event calls
    this.origin = location.clone();
    this.previousLocation = location.clone();
    this.location = location.clone();

    // this is so dirty I washed my hands after writing it.
    if (this instanceof GlowPlayer) {
        // TODO: Fire PlayerInitialSpawnEvent

        // spawn location event
        location = EventFactory.getInstance()
            .callEvent(new PlayerSpawnLocationEvent((Player) this, location.clone()))
            .getSpawnLocation().clone();

        // Update locations again
        this.origin = location.clone();
        this.previousLocation = location.clone();
        this.location = location.clone();
    }

    // ID allocation
    world = (GlowWorld) location.getWorld();
    world.getServer().getEntityIdManager().allocate(this);
    world.getEntityManager().register(this);
}
clabe45 commented 4 years ago

Ok noted.

clabe45 commented 4 years ago

I tried that, but origin, previousLocation and location are all final. Idk how to set a location to another location without setting it.

aramperes commented 4 years ago

You can use Position.copyLocation(location, this.location) etc. to copy the contents of the first location to the second one.

clabe45 commented 4 years ago

Awesome! This will be the first PR of many :D

mastercoms commented 4 years ago

Thank you for your contribution to Glowstone!

clabe45 commented 4 years ago

My pleasure