[FIXED] Bug: Sea creatures can't move in water

Here we stuff all the bugs we've managed to squash/squish/squelch.
Grimson
Developer
Posts: 802
Joined: Sat Jun 04, 2005 1:52 am
Location: Germany
Has thanked: 0
Been thanked: 0

Bug: Sea creatures can't move in water

Post by Grimson »

Well after wondering why none of the sea creatures I added spawns for seem to spawn. So I manually placed some of them in the water and they can't move, but they can move on land. So it seems like UOX3 currently doesn't make a difference between sea and land creatures. I'll try to have a look at the code, but that may take some time (depending on how fast I start to understand the movement and mapstuff code).
Grimson
Developer
Posts: 802
Joined: Sat Jun 04, 2005 1:52 am
Location: Germany
Has thanked: 0
Been thanked: 0

Post by Grimson »

Uh, that part of UOX is really complicated and barely commented. I don't think I can do a fast solution there. :(
User avatar
Xuri
Site Admin
Posts: 3704
Joined: Mon Jun 02, 2003 9:11 am
Location: Norway
Has thanked: 48 times
Been thanked: 8 times
Contact:

Post by Xuri »

There's no difference between sea and land creatures at the moment. There was, once upon a time, and there may still be code lingering about from that time, since a complete rewrite of the walking/npc movement code (which included "swimming", and flying) was later modified to fix other bugs - and the support for swimming/flying was removed.
-= Ho Eyo He Hum =-
giwo
Developer
Posts: 1780
Joined: Fri Jun 18, 2004 4:17 pm
Location: California
Has thanked: 0
Been thanked: 0

Post by giwo »

I am still testing Lingo's modifications to mapstuff.cpp, while it doesn't fix this specific problem, it does make the mapstuff files a bit easier to read/use.

I recall that once apon a time there was code supporting fish moving on water in the Walking code. For whatever reason, it was removed ( If my foggy memory serves me, someone rewrote walking alltogether and never added that support back in).

The tiles support it, and it isn't necesarry to actually flag the NPC object telling it whether it can move on water or not, it will just take some time to figure out how to go about it.
Scott
Grimson
Developer
Posts: 802
Joined: Sat Jun 04, 2005 1:52 am
Location: Germany
Has thanked: 0
Been thanked: 0

Post by Grimson »

giwo wrote:I am still testing Lingo's modifications to mapstuff.cpp, while it doesn't fix this specific problem, it does make the mapstuff files a bit easier to read/use.
Then I'll wait till it is commited to the CVS.
giwo wrote:The tiles support it, and it isn't necesarry to actually flag the NPC object telling it whether it can move on water or not, it will just take some time to figure out how to go about it.
The support to flag something as watercreature is still present in the parsing of the creatures.dfn, so why not use it.
giwo
Developer
Posts: 1780
Joined: Fri Jun 18, 2004 4:17 pm
Location: California
Has thanked: 0
Been thanked: 0

Post by giwo »

This is part of the old code for handling Flying/Swimming creatures. As you can see it is incomplete yet, there was something that called MoveHeightAdjustment() but as far back as I can find, it's unrefrenced.


Code: Select all

#define P_C_IS_GM_BODY		0x01	// Bits for different movement types
#define P_C_IS_PLAYER		0x02
#define P_C_IS_BIRD			0x20
#define P_C_IS_NPC			0x40
#define P_C_IS_FISH			0x80


// check if GM Body
bool cMovement::CanGMWalk( CTileUni &xyb )
{
	UI16 blockid = xyb.ID();

	CTile newTile;
	Map->SeekTile( blockid, &newTile );

	if( Map->IsRoofOrFloorTile( &newTile ) )
		return true;
	
	if( xyb.Type() == 0 )		// map tile
		return true;

	if( xyb.Blocking() )	// blocking
		return true;

	if( xyb.Flag2() & 0x16 )	// climbable, standable, windows/doors
		return true;

	if( xyb.Door() )	// door
		return true;

	return false;
}

bool cMovement::CanNPCWalk( CTileUni &xyb )
{
	UI16 blockid = xyb.ID();

	CTile newTile;
	Map->SeekTile( blockid, &newTile );

	if( Map->IsRoofOrFloorTile( &newTile ) )
		return true;
	
	if( xyb.Type() == 0 )
		return true;

	if( xyb.Standable() || xyb.Climbable() )
		return true;

	return false;
}

bool cMovement::CanPlayerWalk( CTileUni &xyb )
{
	UI16 blockid = xyb.ID();

	CTile newTile;
	Map->SeekTile( blockid, &newTile );

	if( Map->IsRoofOrFloorTile( &newTile ) )
		return true;
	
	if( xyb.Type() == 0 )			// map tile
		return true;

	if( xyb.Standable() || xyb.Climbable() )		// standable, climbable
		return true;

	return false;
}


bool cMovement::CanFishWalk( CTileUni &xyb )
{
	UI16 blockid = xyb.ID();
	
	if( Map->IsTileWet( blockid ) )
		return true;

	// Can they walk/swim on water tiles?
	if( blockid > 0x00A7 && blockid < 0x00AC )
		return true;
	if( blockid > 0x1796 && blockid < 0x179D )
		return true;
	if( blockid > 0x346D && blockid < 0x3486 )
		return true;
	if( blockid > 0x3493 && blockid < 0x34AC )
		return true;
	if( blockid > 0x34B7 && blockid < 0x34CB )
		return true;

	// Can they walk/swim on water ripples and splashes?
	if( blockid > 0x34D0 && blockid < 0x34D6 )
		return true;
	if( blockid > 0x352C && blockid < 0x3531 )
		return true;

	// Can they walk/swim on whirlpools?
//	if( blockid > 0x348F && blockid < 0x3494 )
//		return true;
//	if( blockid > 0x34B4 && blockid < 0x34B8 )
//		return true;

	// Can they walk/swim on/up waterfalls?
	if( blockid > 0x34EC && blockid < 0x3529 )
		return true;

	// Can they walk/swim on the coastlines?
//	if( blockid > 0x179C && blockid < 0x17B3 )
//		return true;
//	if( blockid == 0x1796 )
//		return true;

	return false;
}

// needs testing... not totally accurate, but something to hold place.

bool cMovement::CanBirdWalk( CTileUni &xyb )
{
	return ( CanNPCWalk( xyb ) || CanFishWalk( xyb ) );
}


bool cMovement::MoveHeightAdjustment( int MoveType, CTileUni *thisblock, int &ontype, SI32 &nItemTop, SI32 &nNewZ )
{
	if( ( MoveType & P_C_IS_GM_BODY ) && ( CanGMWalk( *(thisblock) ) ) )
	{
		nNewZ = nItemTop;
		ontype = thisblock->Type();
		return true;
	}
	if( ( MoveType & P_C_IS_PLAYER ) && ( CanPlayerWalk( *(thisblock) ) ) )
	{
		nNewZ = nItemTop;
		ontype = thisblock->Type();
		return true;
	}
	if( ( MoveType & P_C_IS_FISH ) && ( CanFishWalk( *(thisblock) ) ) )
	{
		nNewZ = nItemTop;
		ontype = thisblock->Type();
		return true;
	}
	if( ( MoveType & P_C_IS_NPC ) && ( CanNPCWalk( *(thisblock) ) ) )
	{
		nNewZ = nItemTop;
		ontype = thisblock->Type();
		return true;
	}
	if( ( MoveType & P_C_IS_BIRD ) && ( CanBirdWalk( *(thisblock) ) ) )
	{
		nNewZ = nItemTop;
		ontype = thisblock->Type();
		return true;
	}
	return false;
}
Scott
Grimson
Developer
Posts: 802
Joined: Sat Jun 04, 2005 1:52 am
Location: Germany
Has thanked: 0
Been thanked: 0

Post by Grimson »

I think I got it working. You need to make the following changes to movement.cpp:

add this function:

Code: Select all

bool cMovement::CanFishWalk( CTileUni *tb )
{
	UI16 blockid = tb->ID();

	if( tb->LiquidWet() )
		return true;

	// Can they walk/swim on water tiles?
	if( blockid > 0x00A7 && blockid < 0x00AC )
		return true;
	if( blockid > 0x0135 && blockid < 0x0138 )
		return true;
	if( blockid > 0x1796 && blockid < 0x179D )
		return true;
	if( blockid > 0x346D && blockid < 0x3486 )
		return true;
	if( blockid > 0x3493 && blockid < 0x34AC )
		return true;
	if( blockid > 0x34B7 && blockid < 0x34CB )
		return true;

	// Can they walk/swim on water ripples and splashes?
	if( blockid > 0x34D0 && blockid < 0x34D6 )
		return true;
	if( blockid > 0x352C && blockid < 0x3531 )
		return true;

	// Can they walk/swim on/up waterfalls?
	if( blockid > 0x34EC && blockid < 0x3529 )
		return true;
 
	return false; 
}
and modify the calc_walk() function this way:

Code: Select all

SI08 cMovement::calc_walk( CChar *c, SI16 x, SI16 y, SI16 oldx, SI16 oldy, bool justask )
{
	if( !ValidateObject( c ) )
		return ILLEGAL_Z;
	const SI08 oldz		= c->GetZ();
	bool may_levitate	= c->MayLevitate();
	bool on_ladder		= false;
	SI08 newz			= ILLEGAL_Z;
	bool blocked		= false;
	char ontype			= 0;
	int xycount			= 0;
	UI08 worldNumber	= c->WorldNumber();
	bool isSeaCreature = cwmWorldState->creatures[c->GetID()].IsWater();
	CTileUni xyblock[XYMAX];
	GetBlockingMap( x, y, xyblock, xycount, oldx, oldy, worldNumber );
	GetBlockingStatics( x, y, xyblock, xycount, worldNumber );
	GetBlockingDynamics( x, y, xyblock, xycount, worldNumber );

	// first calculate newZ value
	for( int i = 0; i < xycount; ++i )
	{
		CTileUni *tb = &xyblock[i]; // this is a easy/little tricky, to save a little calculation
		                                 // since the [i] is calclated several times below
			                             // if it doesn't help, it doesn't hurt either.
		SI08 nItemTop = (SI08)(tb->BaseZ() + ((xyblock[i].Type() == 0) ? xyblock[i].Height() : calcTileHeight( xyblock[i].Height() ) )); // Calculate the items total height
		// check if the creature is floating on a static (keeping Z or falling)
		if( nItemTop >= newz && nItemTop <= oldz )
		{
			if ( isSeaCreature )
			{
				if( CanFishWalk( tb ) )
				{ // swimable tile
					newz = nItemTop;
					ontype = tb->Type();
					continue;
				}
			}
			else 
			{
				if( tb->Standable() )
				{ // walkable tile
					newz = nItemTop;
					ontype = tb->Type();
					if( tb->ClimbableBit2() ) // if it was ladder the char is allowed to `levitate´ next move
						on_ladder = true;
					continue;
				}
			}
		}

		// So now comes next step, levitation :o)
		// you can gain Z to a limited amount if yo uwere climbing on last move on a ladder
		if( nItemTop >= newz && may_levitate && nItemTop <= oldz + MAX_Z_LEVITATE && tb->Standable() )
		{
			ontype = tb->Type();
			newz = nItemTop;
			if( tb->ClimbableBit2() ) // if it was ladder the char is allowed to `levitate´ next move
				on_ladder = true;
		}
		// check if the creature is climbing on a climbable Z
		// (gaining Z through stairs, ladders, etc)
		// This form has no height limit, and the tile bottom must start lower or
		// equal current height + levitateable limit
		if( nItemTop >= newz && tb->BaseZ() <= oldz + MAX_Z_LEVITATE )
		{
			if( tb->Climbable() || tb->Type() == 0 ||			// Climbable tile, map tiles are also climbable
			( tb->Flag1() == 0 && tb->Flag2() == 0x22 ) ||		// These are a special kind of tiles where OSI forgot to set the climbable flag
			( (nItemTop >= oldz && nItemTop <= oldz + 3) && tb->Standable() ) )		 // Allow to climb a height of 1 even if the climbable flag is not set
			{                 
				ontype = tb->Type();
				newz = nItemTop;
				if( tb->ClimbableBit2() ) // if it was ladder the char is allowed to `levitate´ next move
					on_ladder = true;
			}
		}
	}

#if DEBUG_WALKING
		Console.Print( "DEBUG: CheckWalkable calculate Z=%d\n", newz );
#endif
    SI08 item_influence = higher( newz + MAX_ITEM_Z_INFLUENCE, oldz );
	// also take care to look on all tiles the creature has fallen through
	// (npc's walking on ocean bug)
	// now the new Z-cordinate of creature is known, 
	// check if it hits it's head against something (blocking in other words)
	bool isGM = IsGMBody( c );
	for( int ii = 0; ii < xycount; ++ii )
	{
		CTileUni *tb = &xyblock[ii]; 
		SI32 nItemTop = tb->BaseZ() + ( ( tb->Type() == 0) ? tb->Height() : calcTileHeight( tb->Height() ) ); // Calculate the items total height
		if( ( ( !isSeaCreature && tb->Blocking() ) || ( isSeaCreature && !CanFishWalk(tb) ) || ( (tb->Standable() || ( isSeaCreature && CanFishWalk( tb ) )) && nItemTop > newz ) ) &&	// Check for blocking tile or stairs
			!( ( isGM || c->IsDead() ) && ( tb->WindowArchDoor() || tb->Door() ) ) )   // ghosts can walk through doors
		{
			// blocking
			if( nItemTop > newz && tb->BaseZ() <= item_influence || ( nItemTop == newz && ontype == 0 ) )
			{ // in effact radius?
				newz = ILLEGAL_Z;
#if DEBUG_WALKING
				Console.Print( "DEBUG: CheckWalkable blocked due to tile=%d at height=%d.\n", xyblock[ii].ID(), xyblock[ii].BaseZ() );
#endif
				blocked = true;
				break;
			}
		}
// knoxos: MAX_ITEM_Z_INFLUENCE is nice, but not truely correct,
//         since the creature height should be the effect radius, if you are i.e.
//         polymorphed to a "slime", you could go through things you normally 
//         wouldn't get under. (Just leaves the question what happens if you
//         unpolymorph in a place where you can't fit, lucky there are no
//         such gaps or tunnels in Britannia). 
//         (Well UO isn't ment to really think in 3d)
	}

#if DEBUG_WALKING
	Console.Print( "DEBUG: CanCharWalk: %dx %dy %dz\n", x, y, z );
#endif
	if( (newz > ILLEGAL_Z) && (!justask) ) // save information if we have climbed on last move.
		c->SetLevitate( on_ladder );
	return newz;
}
You need to add the "WATERCREATURE" tag to all the creatures, that should be able to walk on water instead of land, inside the creatures.dfn.

But remember, this is still very experimental.
User avatar
Xuri
Site Admin
Posts: 3704
Joined: Mon Jun 02, 2003 9:11 am
Location: Norway
Has thanked: 48 times
Been thanked: 8 times
Contact:

Post by Xuri »

Looks like it's working Grimson, I've got dolphins and seaserpents swimming on my server right now. Nice work!

As with all experiemental code, it has a few kinks to work out though. For instance, my sea creatures are swimming right through all docks :) I.e. they are not blocked by statics if there's a valid water-tile beneath.

Secondly, there is no way to have creatures that can walk on both water and land. For instance Water Elementals. Add WATERCREATURE to creatures.dfn, and they'll be able to swim, but not walk on land. Remove it, and they can walk on land but not on water.

But it's looking good so far ;)
-= Ho Eyo He Hum =-
Grimson
Developer
Posts: 802
Joined: Sat Jun 04, 2005 1:52 am
Location: Germany
Has thanked: 0
Been thanked: 0

Post by Grimson »

Xuri wrote: As with all experiemental code, it has a few kinks to work out though. For instance, my sea creatures are swimming right through all docks :) I.e. they are not blocked by statics if there's a valid water-tile beneath.
I'll try to find a solution for this.
Xuri wrote:Secondly, there is no way to have creatures that can walk on both water and land. For instance Water Elementals. Add WATERCREATURE to creatures.dfn, and they'll be able to swim, but not walk on land. Remove it, and they can walk on land but not on water.
So it would be best to add another tag "AMPHIBIAN" for example.
punt
VIP
Posts: 244
Joined: Wed Mar 24, 2004 7:46 pm
Has thanked: 0
Been thanked: 9 times

Post by punt »

Personally, I believe many things all ready suffer from tag explosion.

How about a tag that is like:
MOVEMENT LAND
or
MOVEMENT WATER
or
MOVEMENT BOTH ?

It seems to be easer it "key" , and if MOVEMENT is not there, defaults to land?
giwo
Developer
Posts: 1780
Joined: Fri Jun 18, 2004 4:17 pm
Location: California
Has thanked: 0
Been thanked: 0

Post by giwo »

I'm with punt on this one.

Reading in DFN's like creatures become more and more complex with added tags. Obviously there is a need to flag which creatures can move where, but rather than adding a different tag for each one (which people coming along later will have to interpret) one tag with different settings seems the wiser way to go.
Scott
User avatar
Xuri
Site Admin
Posts: 3704
Joined: Mon Jun 02, 2003 9:11 am
Location: Norway
Has thanked: 48 times
Been thanked: 8 times
Contact:

Post by Xuri »

Don't forget "AIR" ;)

And while we're on this topic of swimming NPCs... the regional spawning code - does it allow spawning NPCs on water-tiles? In any case, perhaps we should modify it to only allow spawning creatures on water/land depending on their movement flags?
-= Ho Eyo He Hum =-
giwo
Developer
Posts: 1780
Joined: Fri Jun 18, 2004 4:17 pm
Location: California
Has thanked: 0
Been thanked: 0

Post by giwo »

Xu: That's based off of cMapStuff::CanMonsterMoveHere()

The spawnregion will simply try to place the NPC randomly in the region, if it finds it as a valid tile where the monster can walk, then it will place it, be it on water, land, a hillside, etc.

So basically, it depends on if DoesStaticBlock() allows for "water" creatures.
Scott
Grimson
Developer
Posts: 802
Joined: Sat Jun 04, 2005 1:52 am
Location: Germany
Has thanked: 0
Been thanked: 0

Post by Grimson »

I changed the CanFishWalk function this way:

Code: Select all

bool cMovement::CanFishWalk( CTileUni *tb, SI16 x, SI16 y, UI08 worldNumber )
{
//	UI16 blockid = tb->ID();
	CTile tile;
	MapStaticIterator msi( x, y, worldNumber );
	staticrecord *stat = NULL;
	
	if( tb->LiquidWet() )
	{
		while ( stat = msi.Next() )
		{
			Map->SeekTile(stat->itemid, &tile);
			if( !tile.LiquidWet() )
				return false;
		}
		return true;
	}

	return false; 
}
If I understood the code right this should give me the statics on the x,y coordinate in the world, and check wheter there is a non wet static and then return false, but it doesn't work :(. Well that part of the code is quite complicated and barely commented...
punt
VIP
Posts: 244
Joined: Wed Mar 24, 2004 7:46 pm
Has thanked: 0
Been thanked: 9 times

Post by punt »

Are you only checking the Art tile? What about the terrain tiles? I could build a pier (with Art a.k.a static tiles) but have the base ocean be made with terrain tiles.

Not saying that is your problem here, dind't look into the code, but from just the name of your snippet, it seemd you have to check both terrain and art tiles.
Grimson
Developer
Posts: 802
Joined: Sat Jun 04, 2005 1:52 am
Location: Germany
Has thanked: 0
Been thanked: 0

Post by Grimson »

punt wrote:Are you only checking the Art tile? What about the terrain tiles? I could build a pier (with Art a.k.a static tiles) but have the base ocean be made with terrain tiles.
If I got it right the tile "tb" is the terrain tile, but I'm not sure.
punt
VIP
Posts: 244
Joined: Wed Mar 24, 2004 7:46 pm
Has thanked: 0
Been thanked: 9 times

Post by punt »

Ok, but let me ask the opposite.
If the terrain isn't wet (like shallow water), it will never check the statics.

I realize you are looking for another issue, but still trying to understand the basic setup.
Grimson
Developer
Posts: 802
Joined: Sat Jun 04, 2005 1:52 am
Location: Germany
Has thanked: 0
Been thanked: 0

Post by Grimson »

punt wrote:Ok, but let me ask the opposite.
If the terrain isn't wet (like shallow water), it will never check the statics.
Yes, that's how it is supposed to work. Or are there any statics in which they should be able to swim?
punt
VIP
Posts: 244
Joined: Wed Mar 24, 2004 7:46 pm
Has thanked: 0
Been thanked: 9 times

Post by punt »

So you can never go into shallow water? Or have somthing in a pond in the middle of the map?

That doesn't seem right to me, but that is just me.
User avatar
Xuri
Site Admin
Posts: 3704
Joined: Mon Jun 02, 2003 9:11 am
Location: Norway
Has thanked: 48 times
Been thanked: 8 times
Contact:

Post by Xuri »

Hm. They should be able to swim in the static water tiles, shouldn't they? I mean, all the coastslines and rivers in Britannia are made up of those.
-= Ho Eyo He Hum =-
Locked