Added offline avatar

This commit is contained in:
Unknown 2020-05-13 23:41:42 +02:00 committed by unknown
parent d7e7e46eaa
commit 37d8fe1c31
12 changed files with 654 additions and 3 deletions

View file

@ -4,6 +4,7 @@ import cr0s.warpdrive.config.WarpDriveConfig;
import cr0s.warpdrive.event.EMPReceiver;
import cr0s.warpdrive.event.ItemHandler;
import cr0s.warpdrive.event.LivingHandler;
import cr0s.warpdrive.event.PlayerHandler;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@ -126,6 +127,7 @@ public class CommonProxy {
// event handlers
MinecraftForge.EVENT_BUS.register(new ItemHandler());
MinecraftForge.EVENT_BUS.register(new LivingHandler());
MinecraftForge.EVENT_BUS.register(new PlayerHandler());
MinecraftForge.EVENT_BUS.register(EMPReceiver.class);
}
}

View file

@ -18,6 +18,7 @@ import net.minecraft.block.Block;
import net.minecraft.block.BlockLiquid;
import net.minecraft.block.material.Material;
import net.minecraft.block.state.IBlockState;
import net.minecraft.client.Minecraft;
import net.minecraft.client.settings.KeyBinding;
import net.minecraft.command.CommandException;
import net.minecraft.command.EntitySelector;
@ -31,6 +32,7 @@ import net.minecraft.item.ItemStack;
import net.minecraft.nbt.CompressedStreamTools;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.server.MinecraftServer;
import net.minecraft.tileentity.TileEntitySkull;
import net.minecraft.util.EnumBlockRenderType;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.AxisAlignedBB;
@ -53,7 +55,6 @@ import net.minecraftforge.common.DimensionManager;
import net.minecraftforge.common.property.IExtendedBlockState;
import net.minecraftforge.common.property.IUnlistedProperty;
import net.minecraftforge.fml.common.FMLCommonHandler;
import net.minecraftforge.fml.common.Optional;
import java.io.File;
import java.io.FileInputStream;
@ -72,7 +73,13 @@ import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;
@ -80,6 +87,8 @@ import org.lwjgl.input.Mouse;
import net.minecraftforge.fluids.Fluid;
import net.minecraftforge.fluids.FluidRegistry;
import com.mojang.authlib.GameProfile;
/**
* Common static methods
*/
@ -93,6 +102,9 @@ public class Commons {
EnumBlockRenderType.MODEL
);
private static final ExecutorService THREAD_POOL = new ThreadPoolExecutor(0, 2, 1L, TimeUnit.MINUTES, new LinkedBlockingQueue<>());
private static final CopyOnWriteArraySet<UUID> uuidGameProfileFilling = new CopyOnWriteArraySet<>();
private static Method methodThrowable_getStackTraceElement;
static {
@ -1023,6 +1035,60 @@ public class Commons {
return server.getPlayerList().getPlayerByUsername(namePlayer);
}
@Nullable
public static EntityPlayerMP getOnlinePlayerByUUID(@Nonnull final UUID uuidPlayer) {
final MinecraftServer server = FMLCommonHandler.instance().getMinecraftServerInstance();
assert server != null;
return server.getPlayerList().getPlayerByUUID(uuidPlayer);
}
public interface ProfilePropertiesAvailableCallback {
void profilePropertiesAvailable(@Nonnull final GameProfile gameProfile);
}
@Nonnull
public static GameProfile getGameProfile(@Nonnull final UUID uuidPlayer, @Nonnull final String namePlayer,
final ProfilePropertiesAvailableCallback profilePropertiesAvailableCallback) {
// validate context
if ( TileEntitySkull.profileCache == null
|| TileEntitySkull.sessionService == null ) {
return new GameProfile(uuidPlayer, namePlayer);
}
// check the cache first
final GameProfile gameProfileCached = TileEntitySkull.profileCache.getProfileByUUID(uuidPlayer);
if ( gameProfileCached != null
&& !gameProfileCached.getProperties().isEmpty() ) {
if (profilePropertiesAvailableCallback != null) {
profilePropertiesAvailableCallback.profilePropertiesAvailable(gameProfileCached);
}
return gameProfileCached;
}
final GameProfile gameProfileEmpty = gameProfileCached != null ? gameProfileCached : new GameProfile(uuidPlayer, namePlayer);
// return directly if properties aren't required or filling is already ongoing
if ( profilePropertiesAvailableCallback == null
|| uuidGameProfileFilling.contains(uuidPlayer) ) {
return gameProfileEmpty;
}
// return an empty profile while a thread will query the server
uuidGameProfileFilling.add(uuidPlayer);
THREAD_POOL.submit(() -> {
final GameProfile gameProfileFilled = TileEntitySkull.sessionService.fillProfileProperties(gameProfileEmpty, true);
// return to main thread before updating our cache and informing caller
Minecraft.getMinecraft().addScheduledTask(() -> {
if (gameProfileEmpty != gameProfileFilled) {// when successful, a new object is created, so we might as well use it
TileEntitySkull.profileCache.addEntry(gameProfileFilled);
}
profilePropertiesAvailableCallback.profilePropertiesAvailable(gameProfileFilled);
uuidGameProfileFilling.remove(uuidPlayer);
});
});
return gameProfileEmpty;
}
public static int colorARGBtoInt(final int alpha, final int red, final int green, final int blue) {
return (clamp(0, 255, alpha) << 24)
+ (clamp(0, 255, red ) << 16)

View file

@ -127,6 +127,7 @@ import cr0s.warpdrive.data.EnumHullPlainType;
import cr0s.warpdrive.data.EnumTier;
import cr0s.warpdrive.entity.EntityLaserExploder;
import cr0s.warpdrive.entity.EntityNPC;
import cr0s.warpdrive.entity.EntityOfflineAvatar;
import cr0s.warpdrive.entity.EntityParticleBunch;
import cr0s.warpdrive.entity.EntitySeat;
import cr0s.warpdrive.event.ChatHandler;
@ -1139,6 +1140,13 @@ public class WarpDrive {
.build();
event.getRegistry().register(entityEntry);
entityEntry = EntityEntryBuilder.create()
.entity(EntityOfflineAvatar.class).factory(EntityOfflineAvatar::new)
.tracker(200, 1, false)
.id("entity_offline_avatar", WarpDriveConfig.G_ENTITY_OFFLINE_AVATAR_ID).name("EntityOfflineAvatar")
.build();
event.getRegistry().register(entityEntry);
entityEntry = EntityEntryBuilder.create()
.entity(EntitySeat.class).factory(EntitySeat::new)
.tracker(200, 1, false)

View file

@ -6,6 +6,7 @@ import cr0s.warpdrive.api.IBlockBase;
import cr0s.warpdrive.api.IItemBase;
import cr0s.warpdrive.block.breathing.BlockColorAirShield;
import cr0s.warpdrive.entity.EntityNPC;
import cr0s.warpdrive.entity.EntityOfflineAvatar;
import cr0s.warpdrive.entity.EntityParticleBunch;
import cr0s.warpdrive.event.ClientHandler;
import cr0s.warpdrive.event.ModelBakeEventHandler;
@ -13,6 +14,7 @@ import cr0s.warpdrive.event.TooltipHandler;
import cr0s.warpdrive.render.ClientCameraHandler;
import cr0s.warpdrive.render.CustomModelLoaderProjector;
import cr0s.warpdrive.render.RenderEntityNPC;
import cr0s.warpdrive.render.RenderEntityOfflineAvatar;
import cr0s.warpdrive.render.RenderEntityParticleBunch;
import cr0s.warpdrive.render.RenderOverlayAir;
import cr0s.warpdrive.render.RenderOverlayCamera;
@ -67,6 +69,13 @@ public class ClientProxy extends CommonProxy {
return new RenderEntityNPC(manager);
}
});
RenderingRegistry.registerEntityRenderingHandler(EntityOfflineAvatar.class, new IRenderFactory<EntityOfflineAvatar>() {
@Nonnull
@Override
public Render<EntityOfflineAvatar> createRenderFor(final RenderManager manager) {
return new RenderEntityOfflineAvatar(manager);
}
});
RenderingRegistry.registerEntityRenderingHandler(EntityParticleBunch.class, new IRenderFactory<EntityParticleBunch>() {
@Nonnull
@Override

View file

@ -0,0 +1,71 @@
package cr0s.warpdrive.client;
import cr0s.warpdrive.Commons;
import cr0s.warpdrive.WarpDrive;
import javax.annotation.Nonnull;
import java.util.HashMap;
import java.util.UUID;
import net.minecraft.client.Minecraft;
import net.minecraft.util.ResourceLocation;
// Collection of texture references supporting offline players, unlike net.minecraft.client.network.NetworkPlayerInfo
public class PlayerTextureManager {
private static final PlayerTextureManager INSTANCE = new PlayerTextureManager();
private static HashMap<UUID, PlayerTextures> mapIdPlayerTextures = new HashMap<>(0);
public static final ResourceLocation RESOURCE_LOCATION_DEFAULT = new ResourceLocation("textures/entity/steve.png");
public static final ResourceLocation RESOURCE_LOCATION_SLIM = new ResourceLocation("textures/entity/alex.png");
private static class PlayerTextures {
public ResourceLocation resourceLocationSkin;
public String skinType = "default";
public ResourceLocation resourceLocationCape;
}
@Nonnull
public static ResourceLocation getPlayerSkin(@Nonnull final UUID uuidPlayer, @Nonnull final String namePlayer) {
{
final PlayerTextures playerTextures = mapIdPlayerTextures.get(uuidPlayer);
if (playerTextures != null) {
if (playerTextures.resourceLocationSkin == null) {
return RESOURCE_LOCATION_DEFAULT;
}
return playerTextures.resourceLocationSkin;
}
}
synchronized (INSTANCE) {
final PlayerTextures playerTextures = new PlayerTextures();
// we clone to avoid synchronization issues without impacting CPU. Hence there's more memory load initially, but less lag during rendering.
// note: clone is being picky, so we do our own cloning
final HashMap<UUID, PlayerTextures> mapIdTypeTextureNew = new HashMap<>(mapIdPlayerTextures.size() + 1);
mapIdTypeTextureNew.putAll(mapIdPlayerTextures);
mapIdTypeTextureNew.put(uuidPlayer, playerTextures);
mapIdPlayerTextures = mapIdTypeTextureNew;
Commons.getGameProfile(uuidPlayer, namePlayer, (gameProfileFilled) ->
Minecraft.getMinecraft().getSkinManager().loadProfileTextures(gameProfileFilled, (type, location, profileTexture) -> {
switch (type) {
case SKIN:
playerTextures.resourceLocationSkin = location;
final String skinType = profileTexture.getMetadata("model");
if (skinType == null) {
playerTextures.skinType = "default";
} else {
playerTextures.skinType = skinType;
}
break;
case CAPE:
playerTextures.resourceLocationCape = location;
break;
}
}, true) );
return RESOURCE_LOCATION_DEFAULT;
}
}
}

View file

@ -169,7 +169,8 @@ public class WarpDriveConfig {
public static int G_ENTITY_PARTICLE_BUNCH_ID = 244;
public static int G_ENTITY_LASER_EXPLODER_ID = 245;
public static int G_ENTITY_NPC_ID = 246;
public static int G_ENTITY_SEAT_ID = 247;
public static int G_ENTITY_OFFLINE_AVATAR_ID = 247;
public static int G_ENTITY_SEAT_ID = 248;
public static final int LUA_SCRIPTS_NONE = 0;
public static final int LUA_SCRIPTS_TEMPLATES = 1;
@ -302,6 +303,15 @@ public class WarpDriveConfig {
public static int BIOMETRIC_SCANNER_DURATION_TICKS = 100;
public static int BIOMETRIC_SCANNER_RANGE_BLOCKS = 3;
// Offline avatar
public static boolean OFFLINE_AVATAR_ENABLE = true;
public static boolean OFFLINE_AVATAR_CREATE_ONLY_ABOARD_SHIPS = true;
public static boolean OFFLINE_AVATAR_FORGET_ON_DEATH = false;
public static float OFFLINE_AVATAR_MODEL_SCALE = 0.5F;
public static boolean OFFLINE_AVATAR_ALWAYS_RENDER_NAME_TAG = false;
public static float OFFLINE_AVATAR_MIN_RANGE_FOR_REMOVAL = 1.0F;
public static float OFFLINE_AVATAR_MAX_RANGE_FOR_REMOVAL = 5.0F;
// Radar
public static int RADAR_MAX_ENERGY_STORED = 100000000; // 100kk eU
public static int RADAR_SCAN_MIN_ENERGY_COST = 10000;
@ -797,6 +807,8 @@ public class WarpDriveConfig {
config.get("general", "entity_laser_exploder_id", G_ENTITY_LASER_EXPLODER_ID, "Entity laser exploder ID").getInt());
G_ENTITY_NPC_ID = Commons.clamp(Integer.MIN_VALUE, Integer.MAX_VALUE,
config.get("general", "entity_NPC_id", G_ENTITY_NPC_ID, "Entity NPC ID").getInt());
G_ENTITY_OFFLINE_AVATAR_ID = Commons.clamp(Integer.MIN_VALUE, Integer.MAX_VALUE,
config.get("general", "entity_offline_avatar_id", G_ENTITY_OFFLINE_AVATAR_ID, "Entity offline avatar ID").getInt());
G_ENTITY_SEAT_ID = Commons.clamp(Integer.MIN_VALUE, Integer.MAX_VALUE,
config.get("general", "entity_seat_id", G_ENTITY_SEAT_ID, "Entity seat ID").getInt());
@ -1034,6 +1046,22 @@ public class WarpDriveConfig {
config.get("jump_gate", "size_max_per_side_by_tier", JUMP_GATE_SIZE_MAX_PER_SIDE_BY_TIER, "Maximum jump gate size on each axis in blocks, for a given tier").getIntList();
clampByTier(1, Integer.MAX_VALUE, JUMP_GATE_SIZE_MAX_PER_SIDE_BY_TIER);
// Offline avatar
OFFLINE_AVATAR_ENABLE =
config.get("offline_avatar", "enable", OFFLINE_AVATAR_ENABLE, "Enable creation of offline avatars to follow ship movements. This only disable creating new ones.").getBoolean(OFFLINE_AVATAR_ENABLE);
OFFLINE_AVATAR_CREATE_ONLY_ABOARD_SHIPS =
config.get("offline_avatar", "create_only_aboard_ships", OFFLINE_AVATAR_CREATE_ONLY_ABOARD_SHIPS, "Only create an offline avatar when player disconnects while inside a ship. Disabling may cause lag in spawn areas...").getBoolean(OFFLINE_AVATAR_CREATE_ONLY_ABOARD_SHIPS);
OFFLINE_AVATAR_FORGET_ON_DEATH =
config.get("offline_avatar", "forget_on_death", OFFLINE_AVATAR_FORGET_ON_DEATH, "Enable to forget current avatar position when it's killed, or disable player teleportation to last known avatar's position").getBoolean(OFFLINE_AVATAR_FORGET_ON_DEATH);
OFFLINE_AVATAR_MODEL_SCALE = (float) Commons.clamp(0.20D, 2.00D,
config.get("offline_avatar", "model_scale", OFFLINE_AVATAR_MODEL_SCALE, "Scale of offline avatar compared to a normal player").getDouble(OFFLINE_AVATAR_MODEL_SCALE));
OFFLINE_AVATAR_ALWAYS_RENDER_NAME_TAG =
config.get("offline_avatar", "always_render_name_tag", OFFLINE_AVATAR_ALWAYS_RENDER_NAME_TAG, "Should avatar name tag always be visible?").getBoolean(OFFLINE_AVATAR_ALWAYS_RENDER_NAME_TAG);
OFFLINE_AVATAR_MIN_RANGE_FOR_REMOVAL = (float) Commons.clamp(0.10D, 10.00D,
config.get("offline_avatar", "min_range_for_removal", OFFLINE_AVATAR_MIN_RANGE_FOR_REMOVAL, "Minimum range between a player and their avatar to consider it for removal (i.e. ensuring connection was successful)").getDouble(OFFLINE_AVATAR_MIN_RANGE_FOR_REMOVAL));
OFFLINE_AVATAR_MAX_RANGE_FOR_REMOVAL = (float) Commons.clamp(Math.max(3.00D, OFFLINE_AVATAR_MIN_RANGE_FOR_REMOVAL), Float.MAX_VALUE,
config.get("offline_avatar", "max_range_for_removal", OFFLINE_AVATAR_MAX_RANGE_FOR_REMOVAL, "Maximum range between a player and his/her avatar to consider it for removal").getDouble(OFFLINE_AVATAR_MAX_RANGE_FOR_REMOVAL));
// Radar
RADAR_MAX_ENERGY_STORED = Commons.clamp(0, Integer.MAX_VALUE,
config.get("radar", "max_energy_stored", RADAR_MAX_ENERGY_STORED, "maximum energy stored").getInt());

View file

@ -0,0 +1,189 @@
package cr0s.warpdrive.data;
import cr0s.warpdrive.Commons;
import cr0s.warpdrive.WarpDrive;
import cr0s.warpdrive.config.WarpDriveConfig;
import cr0s.warpdrive.entity.EntityOfflineAvatar;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.UUID;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.EntityEquipmentSlot;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraftforge.common.util.Constants;
public class OfflineAvatarManager {
private static final HashMap<UUID, GlobalPosition> registry = new HashMap<>(512);
public static void update(@Nonnull final EntityOfflineAvatar entityOfflineAvatar) {
// validate context
if (!Commons.isSafeThread()) {
WarpDrive.logger.error(String.format("Non-threadsafe call to OfflineAvatarManager:update outside main thread, for %s",
entityOfflineAvatar ));
return;
}
if (entityOfflineAvatar.getPlayerUUID() == null) {
WarpDrive.logger.error(String.format("Ignoring update for invalid EntityOfflineAvatar with no UUID %s",
entityOfflineAvatar ));
return;
}
// add new entry
// or update existing entry
GlobalPosition globalPosition = registry.get(entityOfflineAvatar.getPlayerUUID());
if ( globalPosition == null
|| globalPosition.dimensionId != entityOfflineAvatar.world.provider.getDimension()
|| globalPosition.x != (int) Math.floor(entityOfflineAvatar.posX)
|| globalPosition.y != (int) Math.floor(entityOfflineAvatar.posY)
|| globalPosition.z != (int) Math.floor(entityOfflineAvatar.posZ) ) {
globalPosition = new GlobalPosition(entityOfflineAvatar);
registry.put(entityOfflineAvatar.getPlayerUUID(), globalPosition);
}
}
public static void remove(@Nonnull final EntityOfflineAvatar entityOfflineAvatar) {
// validate context
if (!Commons.isSafeThread()) {
WarpDrive.logger.error(String.format("Non-threadsafe call to OfflineAvatarManager:remove outside main thread, for %s",
entityOfflineAvatar ));
return;
}
if (entityOfflineAvatar.getPlayerUUID() == null) {
WarpDrive.logger.error(String.format("Ignoring removal for invalid EntityOfflineAvatar with no UUID %s",
entityOfflineAvatar ));
return;
}
// remove existing entry, if coordinates are matching
final GlobalPosition globalPosition = registry.get(entityOfflineAvatar.getPlayerUUID());
if ( globalPosition != null
&& globalPosition.dimensionId == entityOfflineAvatar.world.provider.getDimension()
&& globalPosition.x == (int) Math.floor(entityOfflineAvatar.posX)
&& globalPosition.y == (int) Math.floor(entityOfflineAvatar.posY)
&& globalPosition.z == (int) Math.floor(entityOfflineAvatar.posZ) ) {
registry.remove(entityOfflineAvatar.getPlayerUUID());
}
}
@Nullable
public static GlobalPosition get(@Nonnull final UUID uuidPlayer) {
// validate context
if (!Commons.isSafeThread()) {
WarpDrive.logger.error(String.format("Non-threadsafe call to OfflineAvatarManager:get outside main thread, for %s",
uuidPlayer ));
return null;
}
// get existing entry
return registry.get(uuidPlayer);
}
public static void readFromNBT(@Nullable final NBTTagCompound tagCompound) {
if ( tagCompound == null
|| !tagCompound.hasKey("offlineAvatars") ) {
registry.clear();
return;
}
// read all entries in a pre-build local collections using known stats to avoid re-allocations
final NBTTagList tagList = tagCompound.getTagList("offlineAvatars", Constants.NBT.TAG_COMPOUND);
final HashMap<UUID, GlobalPosition> registryLocal = new HashMap<>(tagList.tagCount());
for (int index = 0; index < tagList.tagCount(); index++) {
final NBTTagCompound tagCompoundItem = tagList.getCompoundTagAt(index);
final UUID uuid = tagCompoundItem.getUniqueId("");
final GlobalPosition globalPosition = new GlobalPosition(tagCompoundItem);
registryLocal.put(uuid, globalPosition);
}
// transfer to main one
registry.clear();
registry.putAll(registryLocal);
for (final Entry<UUID, GlobalPosition> entry : registryLocal.entrySet()) {
registry.put(entry.getKey(), entry.getValue());
}
}
public static void writeToNBT(@Nonnull final NBTTagCompound tagCompound) {
final NBTTagList tagList = new NBTTagList();
for (final Entry<UUID, GlobalPosition> entry : registry.entrySet()) {
final NBTTagCompound tagCompoundItem = new NBTTagCompound();
tagCompoundItem.setUniqueId("", entry.getKey());
entry.getValue().writeToNBT(tagCompoundItem);
tagList.appendTag(tagCompoundItem);
}
tagCompound.setTag("offlineAvatars", tagList);
}
public static void onPlayerLoggedOut(@Nonnull final EntityPlayer entityPlayer) {
// skip dead players
if (entityPlayer.isDead) {
return;
}
// skip players away from a ship
final World world = entityPlayer.world;
final BlockPos blockPos = entityPlayer.getPosition();
if (WarpDriveConfig.OFFLINE_AVATAR_CREATE_ONLY_ABOARD_SHIPS) {
final GlobalRegion globalRegionNearestShip = GlobalRegionManager.getNearest(EnumGlobalRegionType.SHIP, world, blockPos);
if ( globalRegionNearestShip == null
|| !globalRegionNearestShip.contains(blockPos) ) {
return;
}
}
// spawn an offline avatar entity at location
WarpDrive.logger.debug(String.format("Spawning offline avatar for %s",
entityPlayer ));
final EntityOfflineAvatar entityOfflineAvatar = new EntityOfflineAvatar(world);
entityOfflineAvatar.setPosition(blockPos.getX() + 0.5D, blockPos.getY() + 0.1D, blockPos.getZ() + 0.5D);
entityOfflineAvatar.setCustomNameTag(entityPlayer.getDisplayNameString());
entityOfflineAvatar.setPlayer(entityPlayer.getUniqueID(), entityPlayer.getName());
// copy equipment with a marker to remember those aren't 'legit' items
for (final EntityEquipmentSlot entityEquipmentSlot : EntityEquipmentSlot.values()) {
final ItemStack itemStack = entityPlayer.getItemStackFromSlot(entityEquipmentSlot).copy();
if (!itemStack.isEmpty()) {
if (!itemStack.hasTagCompound()) {
itemStack.setTagCompound(new NBTTagCompound());
}
assert itemStack.getTagCompound() != null;
itemStack.getTagCompound().setBoolean("isFakeItem", true);
entityOfflineAvatar.setItemStackToSlot(entityEquipmentSlot, itemStack);
entityOfflineAvatar.setDropChance(entityEquipmentSlot, 0.0F);
}
}
world.spawnEntity(entityOfflineAvatar);
}
public static void onPlayerLoggedIn(@Nonnull final EntityPlayer entityPlayer) {
assert !entityPlayer.isAddedToWorld();
// skip if we have no record
final GlobalPosition globalPosition = registry.get(entityPlayer.getUniqueID());
if (globalPosition == null) {
return;
}
// skip if player is already close by
if (globalPosition.dimensionId == entityPlayer.dimension) {
final double distance = entityPlayer.getDistance(globalPosition.x + 0.5D, globalPosition.y, globalPosition.z + 0.5D);
if (distance < WarpDriveConfig.OFFLINE_AVATAR_MAX_RANGE_FOR_REMOVAL) {
return;
}
}
// teleport player
// note: this is done before server loads the player's world
entityPlayer.dimension = globalPosition.dimensionId;
entityPlayer.setPosition(globalPosition.x, globalPosition.y, globalPosition.z);
}
}

View file

@ -0,0 +1,177 @@
package cr0s.warpdrive.entity;
import cr0s.warpdrive.Commons;
import cr0s.warpdrive.WarpDrive;
import cr0s.warpdrive.config.WarpDriveConfig;
import cr0s.warpdrive.data.OfflineAvatarManager;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.UUID;
import net.minecraft.entity.EntityLiving;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.network.datasync.DataParameter;
import net.minecraft.network.datasync.DataSerializers;
import net.minecraft.network.datasync.EntityDataManager;
import net.minecraft.world.World;
import com.google.common.base.Optional;
public class EntityOfflineAvatar extends EntityLiving {
// persistent properties
private static final DataParameter<Optional<UUID>> DATA_PLAYER_UUID = EntityDataManager.createKey(EntityOfflineAvatar.class, DataSerializers.OPTIONAL_UNIQUE_ID);
private static final DataParameter<String> DATA_PLAYER_NAME = EntityDataManager.createKey(EntityOfflineAvatar.class, DataSerializers.STRING);
// computed properties
private boolean isDirtyGlobalPosition = true;
private int tickUpdateGlobalPosition = 0;
public EntityOfflineAvatar(@Nonnull final World world) {
super(world);
setCanPickUpLoot(false);
setNoAI(true);
setCustomNameTag("Offline avatar");
setAlwaysRenderNameTag(WarpDriveConfig.OFFLINE_AVATAR_ALWAYS_RENDER_NAME_TAG);
setSize(width * WarpDriveConfig.OFFLINE_AVATAR_MODEL_SCALE, height * WarpDriveConfig.OFFLINE_AVATAR_MODEL_SCALE);
}
@Override
protected void entityInit() {
super.entityInit();
dataManager.register(DATA_PLAYER_UUID, Optional.absent());
dataManager.register(DATA_PLAYER_NAME, "");
}
public void setPlayer(@Nonnull final UUID uuidPlayer, @Nonnull final String namePlayer) {
dataManager.set(DATA_PLAYER_UUID, Optional.of(uuidPlayer));
dataManager.set(DATA_PLAYER_NAME, namePlayer);
}
@Nullable
public UUID getPlayerUUID() {
return dataManager.get(DATA_PLAYER_UUID).orNull();
}
public String getPlayerName() {
return dataManager.get(DATA_PLAYER_NAME);
}
@Override
public void onUpdate() {
super.onUpdate();
if (world.isRemote) {
return;
}
// update registry
if (isDirtyGlobalPosition) {
tickUpdateGlobalPosition = 0;
}
tickUpdateGlobalPosition--;
if (tickUpdateGlobalPosition <= 0) {
tickUpdateGlobalPosition = WarpDriveConfig.G_REGISTRY_UPDATE_INTERVAL_TICKS;
isDirtyGlobalPosition = false;
final UUID uuidPlayer = getPlayerUUID();
if (uuidPlayer == null) {
// cleanup invalid entities
if (ticksExisted > 5) {
WarpDrive.logger.error(String.format("Removing invalid EntityOfflineAvatar with no UUID %s",
this ));
setDead();
}
} else {
// cleanup online players
final EntityPlayer entityPlayer = Commons.getOnlinePlayerByUUID(uuidPlayer);
if ( entityPlayer != null
&& world.provider.getDimension() == entityPlayer.world.provider.getDimension()
&& isInRange(entityPlayer) ) {// (actually online in close proximity)
OfflineAvatarManager.remove(this);
setDead();
} else {// (actually offline or far away)
OfflineAvatarManager.update(this);
}
}
}
}
private boolean isInRange(@Nonnull final EntityPlayer entityPlayer) {
final float distance = entityPlayer.getDistance(this);
return distance >= WarpDriveConfig.OFFLINE_AVATAR_MIN_RANGE_FOR_REMOVAL
&& distance <= WarpDriveConfig.OFFLINE_AVATAR_MAX_RANGE_FOR_REMOVAL;
}
@Override
public void setDead() {
super.setDead();
if (WarpDriveConfig.OFFLINE_AVATAR_FORGET_ON_DEATH) {
OfflineAvatarManager.remove(this);
}
}
@Override
public float getRenderSizeModifier() {
return WarpDriveConfig.OFFLINE_AVATAR_MODEL_SCALE;
}
@Override
public void readEntityFromNBT(@Nonnull final NBTTagCompound tagCompound) {
super.readEntityFromNBT(tagCompound);
final UUID uuidPlayer = tagCompound.getUniqueId("player");
final String namePlayer = tagCompound.getString("playerName");
if ( uuidPlayer == null
|| namePlayer.isEmpty() ) {
WarpDrive.logger.error(String.format("Removing on reading invalid offline avatar in %s",
tagCompound ));
setDead();
return;
}
setPlayer(uuidPlayer, namePlayer);
}
@Override
public void writeEntityToNBT(@Nonnull final NBTTagCompound tagCompound) {
super.writeEntityToNBT(tagCompound);
final UUID uuidPlayer = getPlayerUUID();
if (uuidPlayer == null) {
WarpDrive.logger.error(String.format("Removing on writing invalid offline avatar in %s",
tagCompound ));
setDead();
return;
}
tagCompound.setUniqueId("player", uuidPlayer);
tagCompound.setString("playerName", getPlayerName());
}
@Override
public void onLivingUpdate() {
super.onLivingUpdate();
}
@Override
protected boolean isMovementBlocked() {
return true;
}
@Override
public boolean getAlwaysRenderNameTagForRender() {
return super.getAlwaysRenderNameTagForRender();
}
@Override
public boolean getAlwaysRenderNameTag() {
return super.getAlwaysRenderNameTag();
}
}

View file

@ -8,6 +8,7 @@ import cr0s.warpdrive.config.WarpDriveConfig;
import cr0s.warpdrive.data.CelestialObjectManager;
import cr0s.warpdrive.data.ChunkData;
import cr0s.warpdrive.data.GlobalRegionManager;
import cr0s.warpdrive.data.OfflineAvatarManager;
import cr0s.warpdrive.data.StateAir;
import javax.annotation.Nonnull;
@ -59,6 +60,7 @@ public class ChunkHandler {
final String filename = String.format("%s/%s.dat", event.getWorld().getSaveHandler().getWorldDirectory().getPath(), WarpDrive.MODID);
final NBTTagCompound tagCompound = Commons.readNBTFromFile(filename);
GlobalRegionManager.readFromNBT(tagCompound);
OfflineAvatarManager.readFromNBT(tagCompound);
// enforce vanilla's WorldBorder diameter consistency
final WorldBorder worldBorder = event.getWorld().getWorldBorder();
@ -174,6 +176,7 @@ public class ChunkHandler {
final String filename = String.format("%s/%s.dat", event.getWorld().getSaveHandler().getWorldDirectory().getPath(), WarpDrive.MODID);
final NBTTagCompound tagCompound = new NBTTagCompound();
GlobalRegionManager.writeToNBT(tagCompound);
OfflineAvatarManager.writeToNBT(tagCompound);
Commons.writeNBTToFile(filename, tagCompound);
}

View file

@ -0,0 +1,26 @@
package cr0s.warpdrive.event;
import cr0s.warpdrive.config.WarpDriveConfig;
import cr0s.warpdrive.data.OfflineAvatarManager;
import javax.annotation.Nonnull;
import net.minecraftforge.event.entity.player.PlayerEvent;
import net.minecraftforge.fml.common.eventhandler.EventPriority;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.gameevent.PlayerEvent.PlayerLoggedOutEvent;
public class PlayerHandler {
@SubscribeEvent(priority = EventPriority.LOWEST)
public void onPlayerLoadFromFile(@Nonnull final PlayerEvent.LoadFromFile event) {
OfflineAvatarManager.onPlayerLoggedIn(event.getEntityPlayer());
}
@SubscribeEvent
public void onPlayerLoggedOut(@Nonnull final PlayerLoggedOutEvent event) {
if (WarpDriveConfig.OFFLINE_AVATAR_ENABLE) {
OfflineAvatarManager.onPlayerLoggedOut(event.player);
}
}
}

View file

@ -0,0 +1,68 @@
package cr0s.warpdrive.render;
import cr0s.warpdrive.client.PlayerTextureManager;
import cr0s.warpdrive.config.WarpDriveConfig;
import cr0s.warpdrive.entity.EntityOfflineAvatar;
import javax.annotation.Nonnull;
import java.util.UUID;
import net.minecraft.client.model.ModelBiped;
import net.minecraft.client.model.ModelPlayer;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.entity.RenderLivingBase;
import net.minecraft.client.renderer.entity.RenderManager;
import net.minecraft.client.renderer.entity.layers.LayerArrow;
import net.minecraft.client.renderer.entity.layers.LayerBipedArmor;
import net.minecraft.client.renderer.entity.layers.LayerCustomHead;
import net.minecraft.client.renderer.entity.layers.LayerElytra;
import net.minecraft.client.renderer.entity.layers.LayerHeldItem;
import net.minecraft.util.ResourceLocation;
public class RenderEntityOfflineAvatar extends RenderLivingBase<EntityOfflineAvatar> {
public RenderEntityOfflineAvatar(@Nonnull final RenderManager renderManager) {
super(renderManager, new ModelBiped(), WarpDriveConfig.OFFLINE_AVATAR_MODEL_SCALE);
final boolean useSmallArms = true;
final ModelBiped modelPlayer = new ModelPlayer(0.0F, useSmallArms);
mainModel = modelPlayer;
addLayer(new LayerBipedArmor(this));
addLayer(new LayerHeldItem(this));
addLayer(new LayerArrow(this));
addLayer(new LayerCustomHead(modelPlayer.bipedHead));
addLayer(new LayerElytra(this));
}
@Override
protected void preRenderCallback(@Nonnull final EntityOfflineAvatar entityOfflineAvatar, final float partialTickTime) {
super.preRenderCallback(entityOfflineAvatar, partialTickTime);
GlStateManager.scale(WarpDriveConfig.OFFLINE_AVATAR_MODEL_SCALE,
WarpDriveConfig.OFFLINE_AVATAR_MODEL_SCALE,
WarpDriveConfig.OFFLINE_AVATAR_MODEL_SCALE );
}
@Override
protected ResourceLocation getEntityTexture(@Nonnull final EntityOfflineAvatar entityOfflineAvatar) {
final UUID uuidPlayer = entityOfflineAvatar.getPlayerUUID();
final String namePlayer = entityOfflineAvatar.getPlayerName();
if (uuidPlayer != null) {
return PlayerTextureManager.getPlayerSkin(uuidPlayer, namePlayer);
}
return PlayerTextureManager.RESOURCE_LOCATION_DEFAULT;
}
@Override
protected boolean canRenderName(@Nonnull final EntityOfflineAvatar entityOfflineAvatar) {
return entityOfflineAvatar.getAlwaysRenderNameTagForRender();
}
@Override
public void doRender(@Nonnull final EntityOfflineAvatar entityOfflineAvatar,
final double x, final double y, final double z, final float entityYaw, final float partialTicks) {
super.doRender(entityOfflineAvatar, x, y, z, entityYaw, partialTicks);
}
}

View file

@ -25,4 +25,8 @@ public net.minecraft.world.Explosion field_77283_e # exploder
# WorldProxy
# public-f net.minecraft.world.World *()V # Doesn't matter what it is, we need it public
# public-f net.minecraft.world.WorldServer *()V # Doesn't matter what it is, we need it public
# public-f net.minecraft.world.WorldServer *()V # Doesn't matter what it is, we need it public
# GameProfile caching and filling
public-f net.minecraft.tileentity.TileEntitySkull field_184298_j # profileCache
public-f net.minecraft.tileentity.TileEntitySkull field_184299_k # sessionService