/*
* This file is part of Sponge, licensed under the MIT License (MIT).
*
* Copyright (c) SpongePowered <https://www.spongepowered.org>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.spongepowered.server.mixin.core.entity.player;
import com.flowpowered.math.vector.Vector3d;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
import it.unimi.dsi.fastutil.objects.ObjectIterator;
import net.minecraft.block.BlockBed;
import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.InventoryPlayer;
import net.minecraft.init.Blocks;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.util.math.BlockPos;
import org.spongepowered.api.Sponge;
import org.spongepowered.api.block.BlockSnapshot;
import org.spongepowered.api.entity.Transform;
import org.spongepowered.api.event.SpongeEventFactory;
import org.spongepowered.api.event.action.SleepingEvent;
import org.spongepowered.api.event.cause.Cause;
import org.spongepowered.api.event.cause.NamedCause;
import org.spongepowered.api.world.World;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import org.spongepowered.common.SpongeImpl;
import org.spongepowered.common.data.util.NbtDataUtil;
import org.spongepowered.common.interfaces.entity.player.IMixinEntityPlayer;
import org.spongepowered.common.mixin.core.entity.MixinEntityLivingBase;
import org.spongepowered.common.util.VecHelper;
import java.util.Optional;
import javax.annotation.Nullable;
@Mixin(EntityPlayer.class)
public abstract class MixinEntityPlayer extends MixinEntityLivingBase implements IMixinEntityPlayer {
@Shadow public InventoryPlayer inventory;
@Shadow protected boolean sleeping;
@Shadow @Nullable public BlockPos bedLocation;
@Shadow private int sleepTimer;
@Shadow @Nullable private BlockPos spawnChunk;
@Shadow private boolean spawnForced;
@Shadow public abstract void setSpawnPoint(BlockPos pos, boolean forced);
private static final String PERSISTED_NBT_TAG = "PlayerPersisted";
private Int2ObjectOpenHashMap<BlockPos> spawnChunkMap = new Int2ObjectOpenHashMap<>();
private IntSet spawnForcedSet = new IntOpenHashSet();
/**
* @author Minecrell
* @reason Return the appropriate bed location for the current dimension
*/
@Overwrite @Nullable
public BlockPos getBedLocation() { // getBedLocation
return getBedLocation(this.dimension);
}
@Override
public BlockPos getBedLocation(int dimension) {
return dimension == 0 ? this.spawnChunk : this.spawnChunkMap.get(dimension);
}
/**
* @author Minecrell
* @reason Return the appropriate spawn forced flag for the current dimension
*/
@Overwrite
public boolean isSpawnForced() { // isSpawnForced
return isSpawnForced(this.dimension);
}
@Override
public boolean isSpawnForced(int dimension) {
return dimension == 0 ? this.spawnForced : this.spawnForcedSet.contains(dimension);
}
@Inject(method = "setSpawnPoint", at = @At("HEAD"), cancellable = true)
private void onSetSpawnPoint(BlockPos pos, boolean forced, CallbackInfo ci) {
if (this.dimension != 0) {
setSpawnChunk(pos, forced, this.dimension);
ci.cancel();
}
}
public void setSpawnChunk(@Nullable BlockPos pos, boolean forced, int dimension) {
if (dimension == 0) {
if (pos != null) {
this.spawnChunk = pos;
this.spawnForced = forced;
} else {
this.spawnChunk = null;
this.spawnForced = false;
}
} else if (pos != null) {
this.spawnChunkMap.put(dimension, pos);
if (forced) {
this.spawnForcedSet.add(dimension);
} else {
this.spawnForcedSet.remove(dimension);
}
} else {
this.spawnChunkMap.remove(dimension);
this.spawnForcedSet.remove(dimension);
}
}
@Inject(method = "clonePlayer", at = @At("RETURN"))
private void onClonePlayerReturn(EntityPlayer oldPlayer, boolean respawnFromEnd, CallbackInfo ci) {
this.spawnChunkMap = ((MixinEntityPlayer) (Object) oldPlayer).spawnChunkMap;
this.spawnForcedSet = ((MixinEntityPlayer) (Object) oldPlayer).spawnForcedSet;
final NBTTagCompound old = ((MixinEntityPlayer) (Object) oldPlayer).getEntityData();
if (old.hasKey(PERSISTED_NBT_TAG)) {
this.getEntityData().setTag(PERSISTED_NBT_TAG, old.getCompoundTag(PERSISTED_NBT_TAG));
}
}
@Inject(method = "readEntityFromNBT", at = @At("RETURN"))
private void onReadEntityFromNBT(NBTTagCompound tagCompound, CallbackInfo ci) {
final NBTTagList spawnList = tagCompound.getTagList("Spawns", NbtDataUtil.TAG_COMPOUND);
for (int i = 0; i < spawnList.tagCount(); i++) {
final NBTTagCompound spawnData = spawnList.getCompoundTagAt(i);
int spawnDim = spawnData.getInteger("Dim");
this.spawnChunkMap.put(spawnDim,
new BlockPos(spawnData.getInteger("SpawnX"), spawnData.getInteger("SpawnY"), spawnData.getInteger("SpawnZ")));
if (spawnData.getBoolean("SpawnForced")) {
this.spawnForcedSet.add(spawnDim);
}
}
}
@Inject(method = "writeEntityToNBT", at = @At("RETURN"))
private void onWriteEntityToNBT(NBTTagCompound tagCompound, CallbackInfo ci) {
final NBTTagList spawnList = new NBTTagList();
ObjectIterator<Int2ObjectMap.Entry<BlockPos>> itr = this.spawnChunkMap.int2ObjectEntrySet().fastIterator();
while (itr.hasNext()) {
Int2ObjectMap.Entry<BlockPos> entry = itr.next();
int dim = entry.getIntKey();
BlockPos spawn = entry.getValue();
NBTTagCompound spawnData = new NBTTagCompound();
spawnData.setInteger("Dim", dim);
spawnData.setInteger("SpawnX", spawn.getX());
spawnData.setInteger("SpawnY", spawn.getY());
spawnData.setInteger("SpawnZ", spawn.getZ());
spawnData.setBoolean("SpawnForced", this.spawnForcedSet.contains(dim));
spawnList.appendTag(spawnData);
}
tagCompound.setTag("Spawns", spawnList);
}
// Event injectors
@Inject(method = "trySleep", at = @At("HEAD"), cancellable = true)
private void onTrySleep(BlockPos bedPos, CallbackInfoReturnable<EntityPlayer.SleepResult> ci) {
SleepingEvent.Pre event = SpongeEventFactory.createSleepingEventPre(Cause.of(NamedCause.source(this)),
((org.spongepowered.api.world.World) this.world).createSnapshot(bedPos.getX(), bedPos.getY(), bedPos.getZ()), this);
if (SpongeImpl.postEvent(event)) {
ci.setReturnValue(EntityPlayer.SleepResult.OTHER_PROBLEM);
}
}
/**
* @author Minecrell
* @reason Implements the post sleeping events.
*/
@Overwrite
public void wakeUpPlayer(boolean immediately, boolean updateWorldFlag, boolean setSpawn) {
// Sponge start (Set size after event call)
//this.setSize(0.6F, 1.8F);
Transform<org.spongepowered.api.world.World> newLocation = null;
// Sponge end
IBlockState iblockstate = this.world.getBlockState(this.bedLocation);
if (this.bedLocation != null && iblockstate.getBlock() == Blocks.BED) {
// Sponge start (Change block state after event call)
//this.world.setBlockState(this.playerLocation, iblockstate.withProperty(BlockBed.OCCUPIED, Boolean.valueOf(false)), 4);
// Sponge end
BlockPos blockpos = BlockBed.getSafeExitLocation(this.world, this.bedLocation, 0);
if (blockpos == null) {
blockpos = this.bedLocation.up();
}
// Sponge start (Store position for later)
/*this.setPosition((double) ((float) blockpos.getX() + 0.5F), (double) ((float) blockpos.getY() + 0.1F),
(double) ((float) blockpos.getZ() + 0.5F));*/
newLocation = getTransform().setPosition(new Vector3d(blockpos.getX() + 0.5F, blockpos.getY() + 0.1F, blockpos.getZ() + 0.5F));
// Sponge end
}
// Sponge start
BlockSnapshot bed = getWorld().createSnapshot(VecHelper.toVector3i(this.bedLocation));
SleepingEvent.Post event = SpongeEventFactory.createSleepingEventPost(Cause.of(NamedCause.source(this)), bed,
Optional.ofNullable(newLocation), this, setSpawn);
if (SpongeImpl.postEvent(event)) {
return;
}
// Moved from above
this.setSize(0.6F, 1.8F);
if (newLocation != null) {
// Set property only if bed still exists
this.world.setBlockState(this.bedLocation, iblockstate.withProperty(BlockBed.OCCUPIED, false), 4);
}
// Teleport player
event.getSpawnTransform().ifPresent(this::setLocationAndAngles);
// Sponge end
this.sleeping = false;
if (!this.world.isRemote && updateWorldFlag) {
this.world.updateAllPlayersSleepingFlag();
}
this.sleepTimer = immediately ? 0 : 100;
if (setSpawn) {
this.setSpawnPoint(this.bedLocation, false);
}
// Sponge start
SpongeImpl.postEvent(SpongeEventFactory.createSleepingEventFinish(event.getCause(), bed, this));
// Sponge end
}
}