• Vote in May Mapping Competition to select a winner!
  • There is NO official Otland's Discord server and NO official Otland's server list. The Otland's Staff does not manage any Discord server or server list. Moderators or administrator of any Discord server or server lists have NO connection to the Otland's Staff. Do not get scammed!

[C++] Ground-Underground Mapping and Sunshine

Dries390

Member
Joined
Sep 8, 2007
Messages
42
Best answers
3
Reaction score
17
Hello everyone. I recently ran into some problems tring to create more complex landscapes using the forgotten server engine and set out to try and bypass, if not fix, these following issues

1a)
The z =< 7 and z > 7 floors of the map don't communicate with each other. You can map something very nice below z = 7, but you will not be able to see it from above.
1b) Underground (z > 7) you can only see, at most, two floors down.
2) Remere's map editor has the same problem as 1a. When you map something below the ground, it's a pain to make sure everything lines up correctly on the surface.
3) The natural light cycle does not extend underground; even when it's light outside going under z = 7 will make it appear like you're in a cave.

A lot of smart people on the forums said a lot of smart things and so I was able to piece together how to perform all these changes. I've been sifting through a lot of random posts to try and piece everything together so I'll try to give credit to people where I remember who they were. Baxnie's post here:
showed me how to enable transparant water through otclient.



Requirements:

- You'll need to be able to re-compile Remere's map editor, your source of server (I use TFS 1.3 downgraded to 8.60 by Nekiro) and the OTClient. How to do this is explained at their respective github wiki's.

Disclaimer:

Please back up your files before messing with the source code. Things might go wrong and while I -can- usually fix my own code I can't guarantee I'll understand what's wrong with yours. Please use your head and try to learn -what- you're doing while applying these changes, my goal is really to write down as much as I can to help people figure out how to implement these changes rather than do everything for you. As far as I've tested, these changes work on my server and if anything goes really wrong I'll try to come back and update this.

Known Issues:

- I didn't put in the effort (yet) to fix how Remere offsets lower levels with respect to higher levels. This means your underground map will 'float' away when you go to z < 7, the lower levels keep being offset while the higher levels don't. This does -not- effect your map but it just looks really weird and might be a bit confusing.

Example:
post_1.PNG

This is how we want things to look when we're done (maybe mapped a lot better).

Server-side and OTClient-side
I based my modifications on Erexo's post here, all credits go to him:

TFS Modification
protocolgame.cpp l. 538 - l. 561
C++:
void ProtocolGame::GetMapDescription(int32_t x, int32_t y, int32_t z, int32_t width, int32_t height, NetworkMessage& msg)
{
    int32_t skip = -1;
    int32_t startz, endz, zstep;

    if (z > 7) {
        startz = 0;            //floors z < 7 retrieve map description from z = 0
        endz = 15;           //floors z < 7 look for map until z = 15
        zstep = 1;
    } else {
        startz = 15;          //floors z >= 7 look for map until z = 15
        endz = 0;             //floors z >= 7 retrieve map description from z = 0
        zstep = -1;
    }

    for (int32_t nz = startz; nz != endz + zstep; nz += zstep) {
        GetFloorDescription(msg, x, y, nz, width, height, z - nz, skip);
    }

    if (skip >= 0) {
        msg.addByte(skip);
        msg.addByte(0xFF);
    }
}
protocolgame.cpp l. 538 - l. 561
C++:
bool ProtocolGame::canSee(int32_t x, int32_t y, int32_t z) const
{
    if (!player) {
        return false;
    }

    const Position& myPos = player->getPosition();
    if (myPos.z <= 7) {
        //we are on ground level or above (7 -> 0)
        //view is from current floor to floor 15.
        if (z > 15) {
            return false;
        }
    } else if (myPos.z >= 8) {
        //we are underground (8 -> 15)
        //view is from current floor to floor 15.
        if (z > 15) {
            return false;
        }
    }

    //negative offset means that the action taken place is on a lower floor than ourself
    int32_t offsetz = myPos.getZ() - z;
    if ((x >= myPos.getX() - 8 + offsetz) && (x <= myPos.getX() + 9 + offsetz) &&
            (y >= myPos.getY() - 6 + offsetz) && (y <= myPos.getY() + 7 + offsetz)) {
        return true;
    }
    return false;
}
OTClient Modification

protocolgameparse.cpp l. 2044 - l. 2064
C++:
void ProtocolGame::setMapDescription(const InputMessagePtr& msg, int x, int y, int z, int width, int height)
{
    int startz, endz, zstep;

    if(z > Otc::SEA_FLOOR) {
        startz = 0;
        endz = 15;
        zstep = 1;
    }
    else {
        startz = 15;
        endz = 0;
        zstep = -1;
    }

    int skip = 0;
    for(int nz = startz; nz != endz + zstep; nz += zstep)
        skip = setFloorDescription(msg, x, y, nz, width, height, z - nz, skip);
}
map.cpp l. 707 - l. 710
C++:
int Map::getLastAwareFloor()
{
    return Otc::SEA_FLOOR + 8;
}
mapview.cpp l. 696 - l. 719
C++:
int MapView::calcLastVisibleFloor()
{
    if(!m_multifloor)
        return calcFirstVisibleFloor();

    int z = 7;

    Position cameraPosition = getCameraPosition();
    // this could happens if the player is not known yet
    if(cameraPosition.isValid()) {
        // view only underground floors when below sea level
        if(cameraPosition.z > Otc::SEA_FLOOR)
            z = 15;
        else
            z = 15;
    }

    if(m_lockedFirstVisibleFloor != -1)
        z = std::max<int>(m_lockedFirstVisibleFloor, z);

    // just ensure the that the floor is in the valid range
    z = stdext::clamp<int>(z, 0, (int)Otc::MAX_Z);
    return z;
}
These changes should allow your server, and client, to communicate floors above z = 7 with those below z = 7. Mapping this way is, however, a huge pain in the butt because Remere can't see below z = 7. The following piece of code "fixes" this:

map_drawer.cpp l. 100 - l. 134
C++:
void MapDrawer::SetupVars()
{
    canvas->MouseToMap(&mouse_map_x, &mouse_map_y);
    canvas->GetViewBox(&view_scroll_x, &view_scroll_y, &screensize_x, &screensize_y);

    dragging = canvas->dragging;
    dragging_draw = canvas->dragging_draw;

    zoom = (float)canvas->GetZoom();
    tile_size = int(TILE_SIZE / zoom); // after zoom
    floor = canvas->GetFloor();

    if(options.show_all_floors) {
        if(floor < 8)
            start_z = MAP_MAX_LAYER; // These two values for start_z allow the editor to get lower floors.
        else
            start_z = MAP_MAX_LAYER;  //start_z = std::min(MAP_MAX_LAYER, floor + 2);
    }
    else
        start_z = floor;

    end_z = floor;
    superend_z = (floor > GROUND_LAYER ? 8 : 0);

    start_x = view_scroll_x / TILE_SIZE;
    start_y = view_scroll_y / TILE_SIZE;

    if(floor > GROUND_LAYER) {
        start_x -= 2;
        start_y -= 2;
    }

    end_x = start_x + screensize_x / tile_size + 2;
    end_y = start_y + screensize_y / tile_size + 2;
}
Post automatically merged:

With the underground open, a new problem arises. Anyone going to z > 7 will be plunged into the depths of darkness because of how the day-night cycle is draw in the client, regardless of the fact that there is (maybe) no ground above it.

post_2.png
Here's a very crude illustration of the problem. I didn't like how "non-realistic" this was so I added the following code to my OTClient
mapview.cpp l. 117- l. 138
void MapView::draw(const Rect& rect)
C++:
                Light ambientLight;
                if(cameraPosition.z <= Otc::SEA_FLOOR) {
                    ambientLight = g_map.getLight();
                } else {
                    int floorCounter = 0;
                    for (int iz = cameraPosition.z; iz > 0; iz--) {
                        Position dPos= cameraPosition;
                        dPos.z = dPos.z - iz;
                        TilePtr dTile = g_map.getTile(dPos);
                        if (dTile) {
                            floorCounter = floorCounter + 1;
                        }
                    }
                    if (floorCounter> 0) {
                        ambientLight.color = 215;
                        ambientLight.intensity = 0;
                    } else {
                        ambientLight = g_map.getLight();
                    }
                }
                ambientLight.intensity = std::max<int>(m_minimumAmbientLight*255, ambientLight.intensity);
                m_lightView->setGlobalLight(ambientLight);
This little snipper of code essentially checks if there are any tiles above the current position and, if not, changes the light intensity to the "above-ground" light-intensity i.e. the current level of light corresponding to the time of day 'shines' down into the pit. Here's an example I just tested.

post_2.png

I'm still working on testing and improving the lighting. One thing I've (just) noticed is that there's a small bit of ambient light tied to the character underground such that the two brightnesses don't -quite- match up, but it's quite minor and I'll probably find a way to change it.

Edit: IMPORTANT! Don't forget to check "show all floors" or hit ctrl+W!

Enjoy!
 
Last edited:

Lessaire

Omniscient Hypervisor
Premium User
Joined
Dec 29, 2009
Messages
1,241
Best answers
44
Reaction score
371
Location
Oregon
Thanks. This will save me a lot of time later when I go 48 floors. :cool:
 

Lessaire

Omniscient Hypervisor
Premium User
Joined
Dec 29, 2009
Messages
1,241
Best answers
44
Reaction score
371
Location
Oregon
For my purposes I kinda have to...

The og Frozenhell representation of the Inferno was insufficient for my tastes. I'd need 9 uninterrupted floors just to even start to do it right, but some levels would need 2-3 floors to really do justice, but honestly my vision is gonna take 14 floors. It should be pretty awesome. And don't even get me started on what comes after that.

Teleporter tricks are not a compromise. They are a cop out.

OP ran into a lot of problems because the current 0-15 floors are already so well defined in so many places. I think by leaving those alone, and creating a new range 16-47, one would preserve backwards compatibility and just have to worry about how the interface between 15 and 16 would work, since it's ocean floor to the top of a new sky. And then redoing light and visibility for the new range. It also makes a very clear distinction in that files utilizing this would choke the existing software making it clear they are different.
 
OP
Dries390

Dries390

Member
Joined
Sep 8, 2007
Messages
42
Best answers
3
Reaction score
17
I ran into some more problems trying to create a tent in the pit I was working on. The light would drop to underground levels if I tried to enter the tent on the picture

1587140723380.png

because my script modification would detect the tiles and turn off the light. You can't just change the tiles to be non-ground because they won't pan the camera down when you walk under them. By changing line 10 of the last code above from

C++:
if (dTile) {
to

C++:
if (dTile && dTile->isFullGround()) {
You can now use Objectbuilder to make tiles which act like ground (camera pans down when you walk under them, you can examine them from a lower/higher level) but allow light to flow through.

1587140651605.png

Depending on what you're building it might be annoying to go through this process a lot (AND you can't walk on them as far as I know) but for smaller objects it's a really nice solution.

Edit: The observant reader might have noticed I forgot to unflag unpassability in the picture above. Uncheck that and update items.otb and you'll have normal-behaving floors you -can- walk on but which let sun flow down.
 
Last edited:
Top