// server map info

#define getmaplayoutid(x, y) (clamp((int)(x), 2, (1 << maplayout_factor) - 2) + (clamp((int)(y), 2, (1 << maplayout_factor) - 2) << maplayout_factor))

// server map geometry tools
ssqr &getsblock(int id)
{
    if(!maplayout || id < 2 || id >= ((1 << (maplayout_factor * 2)) - 2))
    {
        static ssqr dummy;
        dummy.type = SPACE;
        dummy.ceil = 16;
        dummy.floor = 0;
        dummy.vdelta = 0;
        return dummy;
    }
    return maplayout[id];
}

inline char cornertype(int x, int y)
{
    if(!maplayout) return 0;
    ssqr &me = getsblock(getmaplayoutid(x, y));
    if(me.type != CORNER) return 0;
    ssqr &up = getsblock(getmaplayoutid(x, y-1));
    ssqr &left = getsblock(getmaplayoutid(x-1, y));
    ssqr &right = getsblock(getmaplayoutid(x+1, y));
    ssqr &down = getsblock(getmaplayoutid(x, y+1));
    const uchar mes = me.ceil - me.floor;
    const bool
        u = up.type == SOLID || uchar(up.ceil - up.floor) < mes,
        l = left.type == SOLID || uchar(left.ceil - left.floor) < mes,
        r = right.type == SOLID || uchar(right.ceil - right.floor) < mes,
        d = down.type == SOLID || uchar(down.ceil - down.floor) < mes;
    if((u && d) || (l && r)) return 0; // more than 2 cubes, or two adjecent ones
    if((u && !l) || (l && !u)) return 2; // topright
    return 1; // topleft
}

inline uchar maxvdelta(int id)
{
    if(!maplayout) return 0;
    ssqr *s = &getsblock(id);
    uchar vdelta = s++->vdelta;
    if(uchar(s->vdelta) > vdelta) vdelta = s->vdelta;
    s += (1 << maplayout_factor) - 1; // new row, left one
    if(uchar(s->vdelta) > vdelta) vdelta = s->vdelta;
    ++s;
    if(uchar(s->vdelta) > vdelta) vdelta = s->vdelta;
    return vdelta;
}

float getblockfloor(int id, bool check_vdelta = true)
{
    if (!maplayout) return 127;
    ssqr &s = getsblock(id);
    if (s.type == SOLID)
        return 127;
    else if (check_vdelta && s.type == FHF)
        return s.floor - maxvdelta(id) / 4.f;
    else
        return s.floor;
}

float getblockceil(int id)
{
    if (!maplayout) return -128;
    ssqr &s = getsblock(id);
    if (s.type == SOLID)
        return -128;
    else if (s.type == CHF)
        return s.ceil + maxvdelta(id) / 4.f;
    else
        return s.ceil;
}

bool outofborder(const vec &p)
{
    loopi(2)
        if(p[i] < 2 || p[i] > (1 << maplayout_factor) - 2)
            return true;
    return false;
}

bool checkpos(vec &p, bool alter = true)
{
    bool ret = false;
    vec fix = p;
    const float epsilon = .1f; // the real value is much smaller than that
    // xy
    loopi(2)
    {
        if(fix[i] <= 2)
        {
            fix[i] = 2 + epsilon;
            ret = true;
        }
        else if((1 << maplayout_factor) - 2 <= fix[i])
        {
            fix[i] = (1 << maplayout_factor) - 2 - epsilon;
            ret = true;
        }
    }
    if(!ret)
    {
        // z
        const int mapi = getmaplayoutid(fix.x, fix.y);
        const char ceil = getblockceil(mapi), floor = getblockfloor(mapi);
        if(fix.z >= ceil)
        {
            fix.z = ceil - epsilon;
            ret = true;
        }
        else if(floor >= fix.z)
        {
            fix.z = floor + epsilon;
            ret = true;
        }
    }
    if(alter)
        p = fix;
    return ret;
}

void snewmap(int factor)
{
    sents.shrink(0);
    maplayout_factor = clamp(factor, SMALLEST_FACTOR, LARGEST_FACTOR);
    smapstats.hdr.waterlevel = -100000;
    const int layoutsize = 1 << (maplayout_factor * 2);
    ssqr defaultblock;
    defaultblock.type = SPACE;
    defaultblock.floor = 0;
    defaultblock.ceil = 16;
    defaultblock.vdelta = 0;
    defaultblock.wtex = DEFAULT_WALL;
    defaultblock.ctex = DEFAULT_WALL;
    maplayout = new ssqr[layoutsize + 256];
    loopi(layoutsize)
        memcpy(maplayout + i, &defaultblock, sizeof(ssqr));
}

float sraycube(const vec &o, const vec &ray, vec *surface = NULL)
{ // server counterpart of raycube
    if(surface) *surface = vec(0, 0, 0);
    if(ray.iszero()) return 0;

    vec v = o;
    float dist = 0, dx = 0, dy = 0, dz = 0;

    const int maxtraces = (1 << maplayout_factor << 2);
    for(int numtraces = 0; numtraces < maxtraces; numtraces++)
    {
        int x = int(v.x), y = int(v.y);
        if(x < 0 || y < 0 || x >= (1 << maplayout_factor) || y >= (1 << maplayout_factor)) return dist;
        const int mapid = getmaplayoutid(x, y);
        ssqr s = getsblock(getmaplayoutid(x, y));
        float floor = getblockfloor(mapid), ceil = getblockceil(mapid);
        if(s.type == SOLID || v.z < floor || v.z > ceil)
        {
            if((!dx && !dy) || s.wtex==DEFAULT_SKY || (s.type != SOLID && v.z > ceil && s.ctex==DEFAULT_SKY)) return dist;
            if(surface)
            {
                int cornert = 0;
                if(s.type == CORNER && (cornert = cornertype(x, y)))
                {
                    float angle = atan2(v.y - o.y, v.x - o.x) / RAD;
                    while(angle < 0) angle += 360;
                    while(angle > 360) angle -= 360;
                    // maybe there is a faster way?

                    // topleft
                    if(cornert == 1)
                        surface->x = surface->y = (angle >= 135 && angle <= 315) ? -.7071f : .7071f;
                    // topright
                    else if(cornert == 2)
                    {
                        surface->x = (angle >= 45 && angle <= 225) ? -.7071f : .7071f;
                        surface->y = -surface->x;
                    }
                }
                else
                { // make one for heightfields?
                    if(dx<dy) surface->x = ray.x>0 ? -1 : 1;
                    else surface->y = ray.y>0 ? -1 : 1;
                    ssqr n = getsblock(getmaplayoutid(x+surface->x, y+surface->y));
                    if(n.type == SOLID || (v.z < floor && v.z < n.floor) || (v.z > ceil && v.z > n.ceil))
                    {
                        *surface = dx<dy ? vec(0, ray.y>0 ? -1 : 1, 0) : vec(ray.x>0 ? -1 : 1, 0, 0);
                        n = getsblock(getmaplayoutid(x+surface->x, y+surface->y));
                        if(n.type == SOLID || (v.z < floor && v.z < n.floor) || (v.z > ceil && v.z > n.ceil))
                            *surface = vec(0, 0, ray.z>0 ? -1 : 1);
                    }
                }
            }
            dist = max(dist-0.1f, 0.0f);
            break;
        }
        dx = ray.x ? (x + (ray.x > 0 ? 1 : 0) - v.x)/ray.x : 1e16f;
        dy = ray.y ? (y + (ray.y > 0 ? 1 : 0) - v.y)/ray.y : 1e16f;
        dz = ray.z ? ((ray.z > 0 ? ceil : floor) - v.z)/ray.z : 1e16f;
        if(dz < dx && dz < dy)
        {
            if(surface && (s.ctex!=DEFAULT_SKY || ray.z<0))
            {
                if(s.type != ((ray.z > 0) ? CHF : FHF)) // flat part
                    surface->z = ray.z>0 ? -1 : 1;
                else
                { // use top left surface
                    // of a plane, n = (b - a) x (c - a)
                    const float f = (ray.z > 0) ? .25f : -.25f;
                    *surface = vec(0, 0, s.vdelta * f); // as a
                    vec b(1, 0, getsblock(getmaplayoutid(x+1, y)).vdelta * f), c(0, 1, getsblock(getmaplayoutid(x, y+1)).vdelta * f);
                    /*
                    conoutf("a %.2f %.2f %.2f", surface->x, surface->y, surface->z);
                    conoutf("b %.2f %.2f %.2f", b.x, b.y, b.z);
                    conoutf("c %.2f %.2f %.2f", c.x, c.y, c.z);
                    /*/
                    b.sub(*surface);
                    c.sub(*surface);
                    dz *= surface->cross(b, c).normalize().z;
                    /*
                    conoutf("n %.2f %.2f %.2f", surface->x, surface->y, surface->z);
                    //*/
                }
            /*
            (getsblock(getmaplayoutid(x, y)).vdelta ==
            getsblock(getmaplayoutid(x+1, y)).vdelta &&
            getsblock(getmaplayoutid(x, y)).vdelta ==
            getsblock(getmaplayoutid(x, y+1)).vdelta &&
            getsblock(getmaplayoutid(x, y)).vdelta ==
            getsblock(getmaplayoutid(x+1, y+1)).vdelta)
            */
            }
            dist += dz;
            break;
        }
        float disttonext = 0.1f + min(dx, dy);
        v.add(vec(ray).mul(disttonext));
        dist += disttonext;
    }
    return dist;
}
