Package micdoodle8.mods.galacticraft.core.dimension

Source Code of micdoodle8.mods.galacticraft.core.dimension.WorldProviderOrbit

package micdoodle8.mods.galacticraft.core.dimension;

import cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import micdoodle8.mods.galacticraft.api.galaxies.CelestialBody;
import micdoodle8.mods.galacticraft.api.prefab.world.gen.WorldProviderSpace;
import micdoodle8.mods.galacticraft.api.vector.BlockVec3;
import micdoodle8.mods.galacticraft.api.vector.Vector3;
import micdoodle8.mods.galacticraft.api.world.IExitHeight;
import micdoodle8.mods.galacticraft.api.world.IOrbitDimension;
import micdoodle8.mods.galacticraft.api.world.ISolarLevel;
import micdoodle8.mods.galacticraft.core.GalacticraftCore;
import micdoodle8.mods.galacticraft.core.blocks.BlockSpinThruster;
import micdoodle8.mods.galacticraft.core.client.SkyProviderOrbit;
import micdoodle8.mods.galacticraft.core.entities.player.GCPlayerStatsClient;
import micdoodle8.mods.galacticraft.core.network.PacketSimple;
import micdoodle8.mods.galacticraft.core.network.PacketSimple.EnumSimplePacket;
import micdoodle8.mods.galacticraft.core.util.ConfigManagerCore;
import micdoodle8.mods.galacticraft.core.world.gen.ChunkProviderOrbit;
import net.minecraft.block.Block;
import net.minecraft.block.BlockAir;
import net.minecraft.block.BlockLiquid;
import net.minecraft.block.material.Material;
import net.minecraft.client.entity.EntityPlayerSP;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityFlying;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.item.EntityFallingBlock;
import net.minecraft.entity.item.EntityItem;
import net.minecraft.entity.item.EntityTNTPrimed;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.init.Blocks;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.AxisAlignedBB;
import net.minecraft.util.MathHelper;
import net.minecraft.world.biome.WorldChunkManager;
import net.minecraft.world.chunk.IChunkProvider;
import net.minecraftforge.client.IRenderHandler;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;

public class WorldProviderOrbit extends WorldProviderSpace implements IOrbitDimension, ISolarLevel, IExitHeight
{
    public int spaceStationDimensionID;

    private static final float GFORCE = 9.81F / 400F; //gravity in metres per tick squared

    private OrbitSpinSaveData savefile;
    public boolean doSpinning = true;
    private float angularVelocityRadians = 0F;
    private float skyAngularVelocity = (float) (this.angularVelocityRadians * 180 / Math.PI);
    private float angularVelocityTarget = 0F;
    private float angularVelocityAccel = 0F;
    private double spinCentreX;
    private double spinCentreZ;
    private float momentOfInertia;
    private float massCentreX;
    private float massCentreZ;
    private int ssBoundsMaxX;
    private int ssBoundsMinX;
    private int ssBoundsMaxY;
    private int ssBoundsMinY;
    private int ssBoundsMaxZ;
    private int ssBoundsMinZ;

    private LinkedList<BlockVec3> thrustersPlus = new LinkedList();
    private LinkedList<BlockVec3> thrustersMinus = new LinkedList();
    private BlockVec3 oneSSBlock;
    //private HashSet<BlockVec3> stationBlocks = new HashSet();

    private HashSet<BlockVec3> checked = new HashSet<BlockVec3>();

    private float artificialG;
    //Used to make continuous particles + thrust sounds at the spin thrusters in this dimension
    //If false, make particles + sounds occasionally in small bursts, just for fun (micro attitude changes)
    //see: BlockSpinThruster.randomDisplayTick()
    public boolean thrustersFiring = false;
    private boolean dataNotLoaded = true;
    private List<Entity> loadedEntities = new LinkedList();
    private double pPrevMotionX = 0D;
    private double pPrevMotionY = 0D;
    private double pPrevMotionZ = 0D;
    private int pjumpticks = 0;
    private boolean pWasOnGround = false;

    @Override
    public void setDimension(int var1)
    {
        this.spaceStationDimensionID = var1;
        super.setDimension(var1);
    }

//  @Override
//  public IChunkProvider createChunkGenerator()
//  {
//    return new ChunkProviderOrbit(this.worldObj, this.worldObj.getSeed(), this.worldObj.getWorldInfo().isMapFeaturesEnabled());
//  }

//  @Override
//  protected void generateLightBrightnessTable()
//  {
//    final float var1 = 0.0F;
//
//    for (int var2 = 0; var2 <= 15; ++var2)
//    {
//      final float var3 = 1.0F - var2 / 15.0F;
//      this.lightBrightnessTable[var2] = (1.0F - var3) / (var3 * 3.0F + 1.0F) * (1.0F - var1) + var1;
//    }
//  }

//  @Override
//  public float[] calcSunriseSunsetColors(float var1, float var2)
//  {
//    return null;
//  }

//  @SideOnly(Side.CLIENT)
//  @Override
//  public Vec3 getFogColor(float var1, float var2)
//  {
//    return Vec3.createVectorHelper((double) 0F / 255F, (double) 0F / 255F, (double) 0F / 255F);
//  }

//  @Override
//  public Vec3 getSkyColor(Entity cameraEntity, float partialTicks)
//  {
//    return Vec3.createVectorHelper(0, 0, 0);
//  }

//  @Override
//  public float calculateCelestialAngle(long par1, float par3)
//  {
//    final int var4 = (int) (par1 % 24000L);
//    float var5 = (var4 + par3) / 24000.0F - 0.25F;
//
//    if (var5 < 0.0F)
//    {
//      ++var5;
//    }
//
//    if (var5 > 1.0F)
//    {
//      --var5;
//    }
//
//    final float var6 = var5;
//    var5 = 1.0F - (float) ((Math.cos(var5 * Math.PI) + 1.0D) / 2.0D);
//    var5 = var6 + (var5 - var6) / 3.0F;
//    return var5;
//  }


    @Override
    public CelestialBody getCelestialBody()
    {
        return GalacticraftCore.satelliteSpaceStation;
    }

    @Override
    public Vector3 getFogColor()
    {
        return new Vector3(0, 0, 0);
    }

    @Override
    public Vector3 getSkyColor()
    {
        return new Vector3(0, 0, 0);
    }

    @Override
    public boolean canRainOrSnow()
    {
        return false;
    }

    @Override
    public boolean hasSunset()
    {
        return false;
    }

    @Override
    public long getDayLength()
    {
        return 24000L;
    }

    @Override
    public boolean shouldForceRespawn()
    {
        return !ConfigManagerCore.forceOverworldRespawn;
    }

    @Override
    public Class<? extends IChunkProvider> getChunkProviderClass()
    {
        return ChunkProviderOrbit.class;
    }

    @Override
    public Class<? extends WorldChunkManager> getWorldChunkManagerClass()
    {
        return null;
    }

    @Override
    @SideOnly(Side.CLIENT)
    public float getStarBrightness(float par1)
    {
        final float var2 = this.worldObj.getCelestialAngle(par1);
        float var3 = 1.0F - (MathHelper.cos(var2 * (float) Math.PI * 2.0F) * 2.0F + 0.25F);

        if (var3 < 0.0F)
        {
            var3 = 0.0F;
        }

        if (var3 > 1.0F)
        {
            var3 = 1.0F;
        }

        return var3 * var3 * 0.5F + 0.3F;
    }

    @Override
    public void updateWeather()
    {
        super.updateWeather();

        if (!this.worldObj.isRemote)
        {
            if (this.dataNotLoaded)
            {
                this.savefile = OrbitSpinSaveData.initWorldData(this.worldObj);
                this.readFromNBT(this.savefile.datacompound);
                if (ConfigManagerCore.enableDebug)
                {
                    System.out.println("Loading data from save: " + this.savefile.datacompound.getFloat("omegaSky"));
                }
                this.dataNotLoaded = false;
            }

            if (this.doSpinning)
            {
                boolean updateNeeded = true;
                if (this.angularVelocityTarget < this.angularVelocityRadians)
                {
                    float newAngle = this.angularVelocityRadians - this.angularVelocityAccel;
                    if (newAngle < this.angularVelocityTarget)
                    {
                        newAngle = this.angularVelocityTarget;
                    }
                    this.setSpinRate(newAngle);
                    this.thrustersFiring = true;
                }
                else if (this.angularVelocityTarget > this.angularVelocityRadians)
                {
                    float newAngle = this.angularVelocityRadians + this.angularVelocityAccel;
                    if (newAngle > this.angularVelocityTarget)
                    {
                        newAngle = this.angularVelocityTarget;
                    }
                    this.setSpinRate(newAngle);
                    this.thrustersFiring = true;
                }
                else if (this.thrustersFiring)
                {
                    this.thrustersFiring = false;
                }
                else
                {
                    updateNeeded = false;
                }

                if (updateNeeded)
                {
                    this.writeToNBT(this.savefile.datacompound);
                    this.savefile.markDirty();
                    List<Object> objList = new ArrayList<Object>();
                    objList.add(Float.valueOf(this.angularVelocityRadians));
                    objList.add(Boolean.valueOf(this.thrustersFiring));
                    GalacticraftCore.packetPipeline.sendToDimension(new PacketSimple(EnumSimplePacket.C_UPDATE_STATION_SPIN, objList), this.spaceStationDimensionID);
                }

                //Update entity positions if in freefall
                this.loadedEntities.clear();
                this.loadedEntities.addAll(this.worldObj.loadedEntityList);
                for (Entity e : this.loadedEntities)
                {
                    if ((e instanceof EntityItem || e instanceof EntityLivingBase && !(e instanceof EntityPlayer) || e instanceof EntityTNTPrimed || e instanceof EntityFallingBlock) && !e.onGround)
                    {
                        boolean freefall = true;
                        if (e.boundingBox.maxX >= this.ssBoundsMinX && e.boundingBox.minX <= this.ssBoundsMaxX && e.boundingBox.maxY >= this.ssBoundsMinY && e.boundingBox.minY <= this.ssBoundsMaxY && e.boundingBox.maxZ >= this.ssBoundsMinZ && e.boundingBox.minZ <= this.ssBoundsMaxZ)
                        {
                            //Entity is somewhere within the space station boundaries

                            //Check if the entity's bounding box is in the same block coordinates as any non-vacuum block (including torches etc)
                            //If so, it's assumed the entity has something close enough to catch onto, so is not in freefall
                            //Note: breatheable air here means the entity is definitely not in freefall
                            int xmx = MathHelper.floor_double(e.boundingBox.maxX + 0.2D);
                            int ym = MathHelper.floor_double(e.boundingBox.minY - 0.1D);
                            int yy = MathHelper.floor_double(e.boundingBox.maxY + 0.1D);
                            int zm = MathHelper.floor_double(e.boundingBox.minZ - 0.2D);
                            int zz = MathHelper.floor_double(e.boundingBox.maxZ + 0.2D);
                            BLOCKCHECK:
                            for (int x = MathHelper.floor_double(e.boundingBox.minX - 0.2D); x <= xmx; x++)
                            {
                                for (int y = ym; y <= yy; y++)
                                {
                                    for (int z = zm; z <= zz; z++)
                                    {
                                        if (this.worldObj.blockExists(x, y, z) && Blocks.air != this.worldObj.getBlock(x, y, z))
                                        {
                                            freefall = false;
                                            break BLOCKCHECK;
                                        }
                                    }
                                }
                            }
                        }

                        if (freefall)
                        {
                            //Do the rotation
                            if (this.angularVelocityRadians != 0F)
                            {
                                float angle;
                                final double xx = e.posX - this.spinCentreX;
                                final double zz = e.posZ - this.spinCentreZ;
                                double arc = Math.sqrt(xx * xx + zz * zz);
                                if (xx == 0D)
                                {
                                    angle = zz > 0 ? 3.1415926535F / 2 : -3.1415926535F / 2;
                                }
                                else
                                {
                                    angle = (float) Math.atan(zz / xx);
                                }
                                if (xx < 0D)
                                {
                                    angle += 3.1415926535F;
                                }
                                angle += this.angularVelocityRadians / 3F;
                                arc = arc * this.angularVelocityRadians;
                                final double offsetX = -arc * MathHelper.sin(angle);
                                final double offsetZ = arc * MathHelper.cos(angle);
                                e.posX += offsetX;
                                e.posZ += offsetZ;
                                e.lastTickPosX += offsetX;
                                e.lastTickPosZ += offsetZ;

                                //Rotated into an unloaded chunk (probably also drifted out to there): byebye
                                if (!this.worldObj.blockExists(MathHelper.floor_double(e.posX), 64, MathHelper.floor_double(e.posZ)))
                                {
                                    e.setDead();
                                }

                                e.boundingBox.offset(offsetX, 0.0D, offsetZ);
                                //TODO check for block collisions here - if so move the entity appropriately and apply fall damage
                                //Moving the entity = slide along / down
                                e.rotationYaw += this.skyAngularVelocity;
                                while (e.rotationYaw > 360F)
                                {
                                    e.rotationYaw -= 360F;
                                }
                            }

                            //Undo deceleration
                            if (e instanceof EntityLivingBase)
                            {
                                e.motionX /= 0.91F;
                                e.motionZ /= 0.91F;
                                if (e instanceof EntityFlying)
                                {
                                    e.motionY /= 0.91F;
                                }
                                else if (e instanceof EntityFallingBlock)
                                {
                                    e.motionY /= 0.9800000190734863D;
                                    //e.motionY += 0.03999999910593033D;
                                    //e.posY += 0.03999999910593033D;
                                    //e.lastTickPosY += 0.03999999910593033D;
                                }
                                else
                                {
                                    e.motionY /= 0.9800000190734863D;
                                }
                            }
                            else
                            {
                                e.motionX /= 0.9800000190734863D;
                                e.motionY /= 0.9800000190734863D;
                                e.motionZ /= 0.9800000190734863D;
                            }
                        }
                    }
                }
            }
        }
    }

    @Override
    public boolean isSkyColored()
    {
        return false;
    }

    @Override
    public double getHorizon()
    {
        return 44.0D;
    }

    @Override
    public int getAverageGroundLevel()
    {
        return 44;
    }

    @Override
    public boolean isSurfaceWorld()
    {
        return true;
    }


    @Override
    public boolean canCoordinateBeSpawn(int var1, int var2)
    {
        return true;
    }

//  @Override
//  public boolean canRespawnHere()
//  {
//    return !ConfigManagerCore.forceOverworldRespawn;
//  }
//
//  @Override
//  public String getWelcomeMessage()
//  {
//    return "Entering Earth Orbit";
//  }
//
//  @Override
//  public String getDepartMessage()
//  {
//    return "Leaving Earth Orbit";
//  }

    @Override
    public String getDimensionName()
    {
        return "Space Station " + this.spaceStationDimensionID;
    }

    //  @Override
    //  public boolean canSnowAt(int x, int y, int z)
    //  {
    //    return false;
    //  } TODO Fix no snow

//  @Override
//  public boolean canBlockFreeze(int x, int y, int z, boolean byWater)
//  {
//    return false;
//  }
//
//  @Override
//  public boolean canDoLightning(Chunk chunk)
//  {
//    return false;
//  }
//
//  @Override
//  public boolean canDoRainSnowIce(Chunk chunk)
//  {
//    return false;
//  }

    @Override
    public float getGravity()
    {
        return 0.075F;//0.073F;
    }

    @Override
    public boolean hasBreathableAtmosphere()
    {
        return false;
    }

    @Override
    public double getMeteorFrequency()
    {
        return 0;
    }

    @Override
    public double getFuelUsageMultiplier()
    {
        return 0.5D;
    }

    @Override
    public String getPlanetToOrbit()
    {
        return "Overworld";
    }

    @Override
    public int getYCoordToTeleportToPlanet()
    {
        return 30;
    }

    @Override
    public String getSaveFolder()
    {
        return "DIM_SPACESTATION" + this.spaceStationDimensionID;
    }

    @Override
    public double getSolarEnergyMultiplier()
    {
        return ConfigManagerCore.spaceStationEnergyScalar;
    }

    @Override
    public double getYCoordinateToTeleport()
    {
        return 1200;
    }

    @Override
    public boolean canSpaceshipTierPass(int tier)
    {
        return tier > 0;
    }

    @Override
    public float getFallDamageModifier()
    {
        return 0.4F;
    }

    @Override
    public float getSoundVolReductionAmount()
    {
        return 50.0F;
    }

    @SideOnly(Side.CLIENT)
    public void spinUpdate(EntityPlayerSP p, boolean flag)
    {
        boolean freefall = this.testFreefall(p, flag);
        boolean doGravity = true;

        if (freefall)
        {
            doGravity = false;
            this.pjumpticks = 0;
            //Do spinning
            if (this.doSpinning && this.angularVelocityRadians != 0F)
            {
                //TODO maybe need to test to make sure xx and zz are not too large (outside sight range of SS)
                //TODO think about server + network load (loading/unloading chunks) when movement is rapid
                //Maybe reduce chunkloading radius?
                float angle;
                final double xx = p.posX - this.spinCentreX;
                final double zz = p.posZ - this.spinCentreZ;
                double arc = Math.sqrt(xx * xx + zz * zz);
                if (xx == 0D)
                {
                    angle = zz > 0 ? 3.1415926535F / 2 : -3.1415926535F / 2;
                }
                else
                {
                    angle = (float) Math.atan(zz / xx);
                }
                if (xx < 0D)
                {
                    angle += 3.1415926535F;
                }
                angle += this.angularVelocityRadians / 3F;
                arc = arc * this.angularVelocityRadians;
                double offsetX = -arc * MathHelper.sin(angle);
                double offsetZ = arc * MathHelper.cos(angle);

                //Check for block collisions here - if so move the player appropriately
                //First check that there are no existing collisions where the player is now (TODO: bounce the player away)
                if (this.worldObj.getCollidingBoundingBoxes(p, p.boundingBox).size() == 0)
                {
                    //Now check for collisions in the new direction and if there are some, try reducing the movement
                    int collisions = 0;
                    do
                    {
                        List<AxisAlignedBB> list = this.worldObj.getCollidingBoundingBoxes(p, p.boundingBox.addCoord(offsetX, 0.0D, offsetZ));
                        collisions = list.size();
                        if (collisions > 0)
                        {
                            if (!doGravity)
                            {
                                p.motionX += -offsetX;
                                p.motionZ += -offsetZ;
                            }
                            offsetX /= 2D;
                            offsetZ /= 2D;
                            if (offsetX < 0.01D && offsetX > -0.01D)
                            {
                                offsetX = 0D;
                            }
                            if (offsetZ < 0.01D && offsetZ > -0.01D)
                            {
                                offsetZ = 0D;
                            }
                            doGravity = true;

                        }
                    }
                    while (collisions > 0);

                    p.posX += offsetX;
                    p.posZ += offsetZ;
                    p.boundingBox.offset(offsetX, 0.0D, offsetZ);
                }

                p.rotationYaw += this.skyAngularVelocity;
                p.prevRotationYaw += this.skyAngularVelocity;
                while (p.rotationYaw > 360F)
                {
                    p.rotationYaw -= 360F;
                }
                while (p.rotationYaw < 0F)
                {
                    p.rotationYaw += 360F;
                }
                while (p.prevRotationYaw > 360F)
                {
                    p.prevRotationYaw -= 360F;
                }
                while (p.prevRotationYaw < 0F)
                {
                    p.prevRotationYaw += 360F;
                }

        /*        //Just started freefall - give some impulse
                                if (!p.inFreefall && p.inFreefallFirstCheck)
                {
                  p.motionX += offsetX * 0.91F;
                  p.motionZ += offsetZ * 0.91F;
                }*/
            }

            //Reverse effects of deceleration
            p.motionX /= 0.91F;
            p.motionZ /= 0.91F;
            p.motionY /= 0.9800000190734863D;

            //Do freefall motion
            if (!p.capabilities.isCreativeMode)
            {
                double dx = p.motionX - this.pPrevMotionX;
                double dy = p.motionY - this.pPrevMotionY;
                double dz = p.motionZ - this.pPrevMotionZ;
/*        double dyaw = p.rotationYaw - p.prevRotationYaw;
        p.rotationYaw -= dyaw * 0.8D;
        double dyawh = p.rotationYawHead - p.prevRotationYawHead;
        p.rotationYawHead -= dyawh * 0.8D;
        while (p.rotationYaw > 360F)
        {
          p.rotationYaw -= 360F;
        }
        while (p.rotationYaw < 0F)
        {
          p.rotationYaw += 360F;
        }
        while (p.rotationYawHead > 360F)
        {
          p.rotationYawHead -= 360F;
        }
        while (p.rotationYawHead < 0F)
        {
          p.rotationYawHead += 360F;
        }
*/
                //if (p.capabilities.isFlying)
                ///Undo whatever vanilla tried to do to our y motion
                p.motionY -= dy;
                p.motionX -= dx;
                p.motionZ -= dz;

                if (p.movementInput.moveForward != 0)
                {
                    p.motionX -= p.movementInput.moveForward * MathHelper.sin(p.rotationYaw / 57.29578F) / 400F;
                    p.motionZ += p.movementInput.moveForward * MathHelper.cos(p.rotationYaw / 57.29578F) / 400F;
                }

                if (p.movementInput.sneak)
                {
                    p.motionY -= 0.0016D;
                }

                if (p.movementInput.jump)
                {
                    p.motionY += 0.0016D;
                }

                if (p.motionX > 0.7F)
                {
                    p.motionX = 0.7F;
                }
                if (p.motionX < -0.7F)
                {
                    p.motionX = -0.7F;
                }
                if (p.motionY > 0.7F)
                {
                    p.motionY = 0.7F;
                }
                if (p.motionY < -0.7F)
                {
                    p.motionY = -0.7F;
                }
                if (p.motionZ > 0.7F)
                {
                    p.motionZ = 0.7F;
                }
                if (p.motionZ < -0.7F)
                {
                    p.motionZ = -0.7F;
                }
            }
            else
            {
                //Half the normal acceleration in Creative mode
                double dx = p.motionX - this.pPrevMotionX;
                double dy = p.motionY - this.pPrevMotionY;
                double dz = p.motionZ - this.pPrevMotionZ;
                p.motionX -= dx / 2;
                p.motionY -= dy / 2;
                p.motionZ -= dz / 2;

                if (p.motionX > 1.2F)
                {
                    p.motionX = 1.2F;
                }
                if (p.motionX < -1.2F)
                {
                    p.motionX = -1.2F;
                }
                if (p.motionY > 0.7F)
                {
                    p.motionY = 0.7F;
                }
                if (p.motionY < -0.7F)
                {
                    p.motionY = -0.7F;
                }
                if (p.motionZ > 1.2F)
                {
                    p.motionZ = 1.2F;
                }
                if (p.motionZ < -1.2F)
                {
                    p.motionZ = -1.2F;
                }
            }
            //TODO: Think about endless drift?
            //Player may run out of oxygen - that will kill the player eventually if can't get back to SS
            //Maybe player needs a 'suicide' button if floating helplessly in space and with no tether
            //Could auto-kill + respawn the player if floats too far away (config option whether to lose items or not)
            //But we want players to be able to enjoy the view of the spinning space station from the outside
            //Arm and leg movements could start tumbling the player?
        }
        else
        //Not freefall - within arm's length of something or jumping
        {
            double dy = p.motionY - this.pPrevMotionY;
            //if (p.motionY < 0 && this.pPrevMotionY >= 0) p.posY -= p.motionY;
            //if (p.motionY != 0) p.motionY = this.pPrevMotionY;
            if (p.movementInput.jump)
            {
                if (p.onGround || this.pWasOnGround)
                {
                    this.pjumpticks = 20;
                    p.motionY -= 0.015D;
                    p.onGround = false;
                    p.posY -= 0.1D;
                    p.boundingBox.offset(0, -0.1D, 0);
                }
                else
                {
                    p.motionY += 0.003D;
                    if (this.pjumpticks == 0)
                    {
                        p.motionY -= dy;
                    }
                }
            }
            else if (p.movementInput.sneak)
            {
                if (!p.onGround)
                {
                    p.motionY -= 0.003D;
                }
                this.pjumpticks = 0;
            }

            if (this.pjumpticks > 0)
            {
                this.pjumpticks--;
                p.motionY -= dy;
                if (this.pjumpticks >= 17)
                {
                    p.motionY += 0.03D;
                }
            }
        }

        //Artificial gravity
        if (doGravity)
        {
            int quadrant = 0;
            double xd = p.posX - this.spinCentreX;
            double zd = p.posZ - this.spinCentreZ;
            double accel = Math.sqrt(xd * xd + zd * zd) * this.angularVelocityRadians * this.angularVelocityRadians * 4D;

            if (xd < 0)
            {
                if (xd < -Math.abs(zd))
                {
                    quadrant = 2;
                }
                else
                {
                    quadrant = zd < 0 ? 3 : 1;
                }
            }
            else if (xd > Math.abs(zd))
            {
                quadrant = 0;
            }
            else
            {
                quadrant = zd < 0 ? 3 : 1;
            }

            switch (quadrant)
            {
            case 0:
                p.motionX += accel;
                break;
            case 1:
                p.motionZ += accel;
                break;
            case 2:
                p.motionX -= accel;
                break;
            case 3:
            default:
                p.motionZ -= accel;
            }
        }

        GCPlayerStatsClient stats = GCPlayerStatsClient.get(p);
        stats.inFreefall = freefall;
        stats.inFreefallFirstCheck = true;
        this.pPrevMotionX = p.motionX;
        this.pPrevMotionY = p.motionY;
        this.pPrevMotionZ = p.motionZ;
        this.pWasOnGround = p.onGround;
    }

    @SideOnly(Side.CLIENT)
    private boolean testFreefall(EntityPlayerSP p, boolean flag)
    {
        if (this.pjumpticks > 0 || (this.pWasOnGround && p.movementInput.jump))
        {
            return false;
        }

        //This is an "on the ground" check
        if (!flag)
        {
            return false;
        }
        else if (p.boundingBox.maxX >= this.ssBoundsMinX && p.boundingBox.minX <= this.ssBoundsMaxX && p.boundingBox.maxY >= this.ssBoundsMinY && p.boundingBox.minY <= this.ssBoundsMaxY && p.boundingBox.maxZ >= this.ssBoundsMinZ && p.boundingBox.minZ <= this.ssBoundsMaxZ)
        //Player is somewhere within the space station boundaries
        {
            //Check if the player's bounding box is in the same block coordinates as any non-vacuum block (including torches etc)
            //If so, it's assumed the player has something close enough to grab onto, so is not in freefall
            //Note: breatheable air here means the player is definitely not in freefall
            int xmx = MathHelper.floor_double(p.boundingBox.maxX);
            int ym = MathHelper.floor_double(p.boundingBox.minY);
            int yy = MathHelper.floor_double(p.boundingBox.maxY);
            int zm = MathHelper.floor_double(p.boundingBox.minZ);
            int zz = MathHelper.floor_double(p.boundingBox.maxZ);
            for (int x = MathHelper.floor_double(p.boundingBox.minX); x <= xmx; x++)
            {
                for (int y = ym; y <= yy; y++)
                {
                    for (int z = zm; z <= zz; z++)
                    {
                        //Blocks.air is hard vacuum - we want to check for that, here
                        if (Blocks.air != this.worldObj.getBlock(x, y, z))
                        {
                            return false;
                        }
                    }
                }
            }
        }

    /*
    if (freefall)
    {
      //If that check didn't produce a result, see if the player is inside the walls
      //TODO: could apply special weightless movement here like Coriolis force - the player is inside the walls,  not touching them, and in a vacuum
      int quadrant = 0;
      double xd = p.posX - this.spinCentreX;
      double zd = p.posZ - this.spinCentreZ;
      if (xd<0)
      {
        if (xd<-Math.abs(zd))
        {
          quadrant = 2;
        } else
          quadrant = (zd<0) ? 3 : 1;
      } else
        if (xd>Math.abs(zd))
        {
          quadrant = 0;
        } else
          quadrant = (zd<0) ? 3 : 1;
     
      int ymin = MathHelper.floor_double(p.boundingBox.minY)-1;
      int ymax = MathHelper.floor_double(p.boundingBox.maxY);
      int xmin, xmax, zmin, zmax;

      switch (quadrant)
      {
      case 0:
        xmin = MathHelper.floor_double(p.boundingBox.maxX);
        xmax = this.ssBoundsMaxX - 1;
        zmin = MathHelper.floor_double(p.boundingBox.minZ)-1;
        zmax = MathHelper.floor_double(p.boundingBox.maxZ)+1;
        break;
      case 1:
        xmin = MathHelper.floor_double(p.boundingBox.minX)-1;
        xmax = MathHelper.floor_double(p.boundingBox.maxX)+1;
        zmin = MathHelper.floor_double(p.boundingBox.maxZ);
        zmax = this.ssBoundsMaxZ - 1;
        break;
      case 2:
        zmin = MathHelper.floor_double(p.boundingBox.minZ)-1;
        zmax = MathHelper.floor_double(p.boundingBox.maxZ)+1;
        xmin = this.ssBoundsMinX;
        xmax = MathHelper.floor_double(p.boundingBox.minX);
        break;
      case 3:
      default:
        xmin = MathHelper.floor_double(p.boundingBox.minX)-1;
        xmax = MathHelper.floor_double(p.boundingBox.maxX)+1;
        zmin = this.ssBoundsMinZ;
        zmax = MathHelper.floor_double(p.boundingBox.minZ);
        break;
      }
     
      //This block search could cost a lot of CPU (but client side) - maybe optimise later
      BLOCKCHECK0:
      for(int x = xmin; x <= xmax; x++)
        for (int z = zmin; z <= zmax; z++)
          for (int y = ymin; y <= ymax; y++)
            if (Blocks.air != this.worldObj.getBlock(x, y, z))
            {
              freefall = false;
              break BLOCKCHECK0;
            }
    }*/

        return true;
    }

    public float getSpinRate()
    {
        return this.skyAngularVelocity;
    }

    /**
     * Sets the spin rate for the dimension in radians per tick For example,
     * 0.031415 would be 1/200 revolution per tick So that would be 1 revolution
     * every 10 seconds
     */
    public void setSpinRate(float angle)
    {
        this.angularVelocityRadians = angle;
        this.skyAngularVelocity = angle * 180F / 3.1415927F;

        if (FMLCommonHandler.instance().getEffectiveSide() == Side.CLIENT)
        {
            this.updateSkyProviderSpinRate();
        }
    }

    @SideOnly(Side.CLIENT)
    private void updateSkyProviderSpinRate()
    {
        IRenderHandler sky = this.getSkyRenderer();
        if (sky instanceof SkyProviderOrbit)
        {
            ((SkyProviderOrbit) sky).spinDeltaPerTick = this.skyAngularVelocity;
        }
    }

    public void setSpinRate(float angle, boolean firing)
    {
        this.angularVelocityRadians = angle;
        this.skyAngularVelocity = angle * 180F / 3.1415927F;
        IRenderHandler sky = this.getSkyRenderer();
        if (sky instanceof SkyProviderOrbit)
        {
            ((SkyProviderOrbit) sky).spinDeltaPerTick = this.skyAngularVelocity;
        }
        this.thrustersFiring = firing;
    }

    public void setSpinCentre(double x, double z)
    {
        this.spinCentreX = x;
        this.spinCentreZ = z;
        if (this.worldObj.isRemote)
        {
            if (ConfigManagerCore.enableDebug)
            {
                System.out.println("Clientside update to spin centre: " + x + "," + z);
            }
        }
    }

    public void setSpinBox(int mx, int xx, int my, int yy, int mz, int zz)
    {
        this.ssBoundsMinX = mx;
        this.ssBoundsMaxX = xx;
        this.ssBoundsMinY = my;
        this.ssBoundsMaxY = yy;
        this.ssBoundsMinZ = mz;
        this.ssBoundsMaxZ = zz;
    }

    public void addThruster(BlockVec3 thruster, boolean positive)
    {
        if (positive)
        {
            this.thrustersPlus.add(thruster);
            this.thrustersMinus.remove(thruster);
        }
        else
        {
            this.thrustersPlus.remove(thruster);
            this.thrustersMinus.add(thruster);
        }
    }

    public void removeThruster(BlockVec3 thruster, boolean positive)
    {
        if (positive)
        {
            this.thrustersPlus.remove(thruster);
        }
        else
        {
            this.thrustersMinus.remove(thruster);
        }
    }

    /**
     * This will check all blocks which are in contact with each other to find
     * the shape of the spacestation. It also finds the centre of mass (to
     * rotate around) and the moment of inertia (how easy/hard this is to
     * rotate).
     * <p/>
     * If placingThruster is true, it will return false if the thruster (at
     * baseBlock) is not in contact with the "existing" spacestation - so the
     * player cannot place thrusters on outlying disconnected blocks and expect
     * them to have an effect.
     * <p/>
     * Note: this check will briefly load, server-side, all chunks which have
     * spacestation blocks in them or 1 block adjacent to those.
     *
     * @param baseBlock
     * @return
     */
    public boolean checkSS(BlockVec3 baseBlock, boolean placingThruster)
    {
        if (this.oneSSBlock == null || this.oneSSBlock.getBlockID(this.worldObj) instanceof BlockAir)
        {
            if (baseBlock != null)
            {
                this.oneSSBlock = baseBlock.clone();
            }
            else
            {
                this.oneSSBlock = new BlockVec3(0, 64, 0);
            }
        }

        // Find contiguous blocks using an algorithm like the oxygen sealer one
        List<BlockVec3> currentLayer = new LinkedList<BlockVec3>();
        List<BlockVec3> nextLayer = new LinkedList<BlockVec3>();
        final List<BlockVec3> foundThrusters = new LinkedList<BlockVec3>();

        this.checked.clear();
        currentLayer.add(this.oneSSBlock.clone());
        this.checked.add(this.oneSSBlock.clone());
        Block bStart = this.oneSSBlock.getBlockID(this.worldObj);
        if (bStart instanceof BlockSpinThruster)
        {
            foundThrusters.add(this.oneSSBlock);
        }

        float thismass = 0.1F; //Mass of a thruster
        float thismassCentreX = 0.1F * this.oneSSBlock.x;
        float thismassCentreY = 0.1F * this.oneSSBlock.y;
        float thismassCentreZ = 0.1F * this.oneSSBlock.z;
        float thismoment = 0F;
        int thisssBoundsMaxX = this.oneSSBlock.x;
        int thisssBoundsMinX = this.oneSSBlock.x;
        int thisssBoundsMaxY = this.oneSSBlock.y;
        int thisssBoundsMinY = this.oneSSBlock.y;
        int thisssBoundsMaxZ = this.oneSSBlock.z;
        int thisssBoundsMinZ = this.oneSSBlock.z;

        while (currentLayer.size() > 0)
        {
            for (BlockVec3 vec : currentLayer)
            {
                if (vec.x < thisssBoundsMinX)
                {
                    thisssBoundsMinX = vec.x;
                }
                if (vec.y < thisssBoundsMinY)
                {
                    thisssBoundsMinY = vec.y;
                }
                if (vec.z < thisssBoundsMinZ)
                {
                    thisssBoundsMinZ = vec.z;
                }
                if (vec.x > thisssBoundsMaxX)
                {
                    thisssBoundsMaxX = vec.x;
                }
                if (vec.y > thisssBoundsMaxY)
                {
                    thisssBoundsMaxY = vec.y;
                }
                if (vec.z > thisssBoundsMaxZ)
                {
                    thisssBoundsMaxZ = vec.z;
                }

                for (int side = 0; side < 6; side++)
                {
                    if (vec.sideDone[side])
                    {
                        continue;
                    }
                    BlockVec3 sideVec = vec.newVecSide(side);

                    if (!this.checked.contains(sideVec))
                    {
                        this.checked.add(sideVec);
                        Block b = sideVec.getBlockID(this.worldObj);
                        if (!(b instanceof BlockAir) && b != null)
                        {
                            nextLayer.add(sideVec);
                            if (bStart instanceof BlockAir)
                            {
                                this.oneSSBlock = sideVec.clone();
                                bStart = b;
                            }
                            float m = 1.0F;
                            //Liquids have a mass of 1, stone, metal blocks etc will be heavier
                            if (!(b instanceof BlockLiquid))
                            {
                                //For most blocks, hardness gives a good idea of mass
                                m = b.getBlockHardness(this.worldObj, sideVec.x, sideVec.y, sideVec.z);
                                if (m < 0.1F)
                                {
                                    m = 0.1F;
                                }
                                else if (m > 30F)
                                {
                                    m = 30F;
                                }
                                //Wood items have a high hardness compared with their presumed mass
                                if (b.getMaterial() == Material.wood)
                                {
                                    m /= 4;
                                }

                                //TODO: higher mass for future Galacticraft hi-density item like neutronium
                                //Maybe also check for things in other mods by name: lead, uranium blocks?
                            }
                            thismassCentreX += m * sideVec.x;
                            thismassCentreY += m * sideVec.y;
                            thismassCentreZ += m * sideVec.z;
                            thismass += m;
                            thismoment += m * (sideVec.x * sideVec.x + sideVec.z * sideVec.z);
                            if (b instanceof BlockSpinThruster)
                            {
                                foundThrusters.add(sideVec);
                            }
                        }
                    }
                }
            }

            currentLayer = nextLayer;
            nextLayer = new LinkedList<BlockVec3>();
        }

        if (placingThruster && !this.checked.contains(baseBlock))
        {
            if (foundThrusters.size() > 0)
            {
                //The thruster was not placed on the existing contiguous space station: it must be.
                if (ConfigManagerCore.enableDebug)
                {
                    System.out.println("Returning false: oneSSBlock was " + this.oneSSBlock.x + "," + this.oneSSBlock.y + "," + this.oneSSBlock.z + " - baseBlock was " + baseBlock.x + "," + baseBlock.y + "," + baseBlock.z + " - found " + foundThrusters.size());
                }
                return false;
            }

            //No thruster on the original space station - so assume the player made new station and start check again
            //This offers players a reset option: just remove all thrusters from original station then starting adding to new one
            //(This first check prevents an infinite loop)
            if (!this.oneSSBlock.equals(baseBlock))
            {
                this.oneSSBlock = baseBlock.clone();
                if (this.oneSSBlock.getBlockID(this.worldObj).getMaterial() != Material.air)
                {
                    return this.checkSS(baseBlock, true);
                }
            }

            return false;

        }

        // Update thruster lists based on what was found
        this.thrustersPlus.clear();
        this.thrustersMinus.clear();
        for (BlockVec3 thruster : foundThrusters)
        {
            int facing = thruster.getBlockMetadata(this.worldObj) & 8;
            if (facing == 0)
            {
                this.thrustersPlus.add(thruster.clone());
            }
            else
            {
                this.thrustersMinus.add(thruster.clone());
            }
        }

        // Calculate centre of mass
        float mass = thismass;

        this.massCentreX = thismassCentreX / thismass + 0.5F;
        float massCentreY = thismassCentreY / thismass + 0.5F;
        this.massCentreZ = thismassCentreZ / thismass + 0.5F;
        //System.out.println("(X,Z) = "+this.massCentreX+","+this.massCentreZ);

        this.setSpinCentre(this.massCentreX, this.massCentreZ);

        //The boundary is at the outer edges of the blocks
        this.ssBoundsMaxX = thisssBoundsMaxX + 1;
        this.ssBoundsMinX = thisssBoundsMinX;
        this.ssBoundsMaxY = thisssBoundsMaxY + 1;
        this.ssBoundsMinY = thisssBoundsMinY;
        this.ssBoundsMaxZ = thisssBoundsMaxZ + 1;
        this.ssBoundsMinZ = thisssBoundsMinZ;

        // Calculate momentOfInertia
        thismoment -= this.massCentreX * this.massCentreX * mass;
        thismoment -= this.massCentreZ * this.massCentreZ * mass;
        this.momentOfInertia = thismoment;

        //TODO
        // TODO defy gravity
        // TODO break blocks which are outside SS (not in checked)
        // TODO prevent spin if there is a huge number of blocks outside SS

        if (ConfigManagerCore.enableDebug)
        {
            System.out.println("MoI = " + this.momentOfInertia + " CoMx = " + this.massCentreX + " CoMz = " + this.massCentreZ);
        }

        //Send packets to clients in this dimension
        List<Object> objList = new ArrayList<Object>();
        objList.add(Double.valueOf(this.spinCentreX));
        objList.add(Double.valueOf(this.spinCentreZ));
        GalacticraftCore.packetPipeline.sendToDimension(new PacketSimple(EnumSimplePacket.C_UPDATE_STATION_DATA, objList), this.spaceStationDimensionID);

        objList = new ArrayList<Object>();
        objList.add(Integer.valueOf(this.ssBoundsMinX));
        objList.add(Integer.valueOf(this.ssBoundsMaxX));
        objList.add(Integer.valueOf(this.ssBoundsMinY));
        objList.add(Integer.valueOf(this.ssBoundsMaxY));
        objList.add(Integer.valueOf(this.ssBoundsMinZ));
        objList.add(Integer.valueOf(this.ssBoundsMaxZ));
        GalacticraftCore.packetPipeline.sendToDimension(new PacketSimple(EnumSimplePacket.C_UPDATE_STATION_BOX, objList), this.spaceStationDimensionID);

        this.updateSpinSpeed();

        return true;
    }

    public void updateSpinSpeed()
    {
        if (this.momentOfInertia > 0F)
        {
            float netTorque = 0F;
            int countThrusters = 0;
            int countThrustersReverse = 0;

            for (BlockVec3 thruster : this.thrustersPlus)
            {
                float xx = thruster.x - this.massCentreX;
                float zz = thruster.z - this.massCentreZ;
                netTorque += MathHelper.sqrt_float(xx * xx + zz * zz);
                countThrusters++;
            }
            for (BlockVec3 thruster : this.thrustersMinus)
            {
                float xx = thruster.x - this.massCentreX;
                float zz = thruster.z - this.massCentreZ;
                netTorque -= MathHelper.sqrt_float(xx * xx + zz * zz);
                countThrustersReverse++;
            }

            if (countThrusters == countThrustersReverse)
            {
                this.angularVelocityAccel = 0.000004F;
                this.angularVelocityTarget = 0F;
            }
            else
            {
                countThrusters += countThrustersReverse;
                if (countThrusters > 4)
                {
                    countThrusters = 4;
                }

                float maxRx = Math.max(this.ssBoundsMaxX - this.massCentreX, this.massCentreX - this.ssBoundsMinX);
                float maxRz = Math.max(this.ssBoundsMaxZ - this.massCentreZ, this.massCentreZ - this.ssBoundsMinZ);
                float maxR = Math.max(maxRx, maxRz);
                this.angularVelocityTarget = MathHelper.sqrt_float(WorldProviderOrbit.GFORCE / maxR) / 2;
                //The divide by 2 is not scientific but is a Minecraft factor as everything happens more quickly
                float spinCap = 0.00125F * countThrusters;

                //TODO: increase this above 20F in release versions so everything happens more slowly
                this.angularVelocityAccel = netTorque / this.momentOfInertia / 20F;
                if (this.angularVelocityAccel < 0)
                {
                    this.angularVelocityAccel = -this.angularVelocityAccel;
                    this.angularVelocityTarget = -this.angularVelocityTarget;
                    if (this.angularVelocityTarget < -spinCap)
                    {
                        this.angularVelocityTarget = -spinCap;
                    }
                }
                else
                    //Do not make it spin too fast or players might get dizzy
                    //Also make it so players need minimum 4 thrusters for best spin
                    if (this.angularVelocityTarget > spinCap)
                    {
                        this.angularVelocityTarget = spinCap;
                    }

                if (ConfigManagerCore.enableDebug)
                {
                    System.out.println("MaxR = " + maxR + " Angular vel = " + this.angularVelocityTarget + " Angular accel = " + this.angularVelocityAccel);
                }
            }
        }

        if (!this.worldObj.isRemote)
        {
            //Save the updated data for the world
            if (this.savefile == null)
            {
                this.savefile = OrbitSpinSaveData.initWorldData(this.worldObj);
                this.dataNotLoaded = false;
            }
            else
            {
                this.writeToNBT(this.savefile.datacompound);
                this.savefile.markDirty();
            }
        }
    }

    public void readFromNBT(NBTTagCompound nbt)
    {
        this.doSpinning = true;//nbt.getBoolean("doSpinning");
        this.angularVelocityRadians = nbt.getFloat("omegaRad");
        this.skyAngularVelocity = nbt.getFloat("omegaSky");
        this.angularVelocityTarget = nbt.getFloat("omegaTarget");
        this.angularVelocityAccel = nbt.getFloat("omegaAcc");

        NBTTagCompound oneBlock = (NBTTagCompound) nbt.getTag("oneBlock");
        if (oneBlock != null)
        {
            this.oneSSBlock = BlockVec3.readFromNBT(oneBlock);
        }
        else
        {
            this.oneSSBlock = null;
        }

        //A lot of the data can be refreshed by checkSS
        this.checkSS(this.oneSSBlock, false);

        //Send packets to clients in this dimension
        List<Object> objList = new ArrayList<Object>();
        objList.add(Float.valueOf(this.angularVelocityRadians));
        objList.add(Boolean.valueOf(this.thrustersFiring));
        GalacticraftCore.packetPipeline.sendToDimension(new PacketSimple(EnumSimplePacket.C_UPDATE_STATION_SPIN, objList), this.spaceStationDimensionID);

        objList = new ArrayList<Object>();
        objList.add(Double.valueOf(this.spinCentreX));
        objList.add(Double.valueOf(this.spinCentreZ));
        GalacticraftCore.packetPipeline.sendToDimension(new PacketSimple(EnumSimplePacket.C_UPDATE_STATION_DATA, objList), this.spaceStationDimensionID);

        objList = new ArrayList<Object>();
        objList.add(Integer.valueOf(this.ssBoundsMinX));
        objList.add(Integer.valueOf(this.ssBoundsMaxX));
        objList.add(Integer.valueOf(this.ssBoundsMinY));
        objList.add(Integer.valueOf(this.ssBoundsMaxY));
        objList.add(Integer.valueOf(this.ssBoundsMinZ));
        objList.add(Integer.valueOf(this.ssBoundsMaxZ));
        GalacticraftCore.packetPipeline.sendToDimension(new PacketSimple(EnumSimplePacket.C_UPDATE_STATION_BOX, objList), this.spaceStationDimensionID);
    }

    public void writeToNBT(NBTTagCompound nbt)
    {
        nbt.setBoolean("doSpinning", this.doSpinning);
        nbt.setFloat("omegaRad", this.angularVelocityRadians);
        nbt.setFloat("omegaSky", this.skyAngularVelocity);
        nbt.setFloat("omegaTarget", this.angularVelocityTarget);
        nbt.setFloat("omegaAcc", this.angularVelocityAccel);
        if (this.oneSSBlock != null)
        {
            NBTTagCompound oneBlock = new NBTTagCompound();
            this.oneSSBlock.writeToNBT(oneBlock);
            nbt.setTag("oneBlock", oneBlock);
        }
    }

    /**
     * Call this when player first login/transfer to this dimension
     * <p/>
     * TODO how can this code be called by other mods / plugins with teleports
     * (e.g. Bukkit)? See WorldUtil.teleportEntity()
     *
     * @param player
     */
    public void sendPacketsToClient(EntityPlayerMP player)
    {
        List<Object> objList = new ArrayList<Object>();
        objList.add(Float.valueOf(this.angularVelocityRadians));
        objList.add(Boolean.valueOf(this.thrustersFiring));
        GalacticraftCore.packetPipeline.sendTo(new PacketSimple(EnumSimplePacket.C_UPDATE_STATION_SPIN, objList), player);

        objList = new ArrayList<Object>();
        objList.add(Double.valueOf(this.spinCentreX));
        objList.add(Double.valueOf(this.spinCentreZ));
        GalacticraftCore.packetPipeline.sendTo(new PacketSimple(EnumSimplePacket.C_UPDATE_STATION_DATA, objList), player);

        objList = new ArrayList<Object>();
        objList.add(Integer.valueOf(this.ssBoundsMinX));
        objList.add(Integer.valueOf(this.ssBoundsMaxX));
        objList.add(Integer.valueOf(this.ssBoundsMinY));
        objList.add(Integer.valueOf(this.ssBoundsMaxY));
        objList.add(Integer.valueOf(this.ssBoundsMinZ));
        objList.add(Integer.valueOf(this.ssBoundsMaxZ));
        GalacticraftCore.packetPipeline.sendTo(new PacketSimple(EnumSimplePacket.C_UPDATE_STATION_BOX, objList), player);
    }

    @Override
    public float getThermalLevelModifier()
    {
        return 0;
    }

    @Override
    public float getWindLevel()
    {
        return 0.1F;
    }

    //TODO Occasional call to checkSS to update centre of mass etc (in case the player has been building)
}
TOP

Related Classes of micdoodle8.mods.galacticraft.core.dimension.WorldProviderOrbit

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.