Boat refactor Part 1 of N (#654)

* test for traveller movement due to turn/move
check relative position of a component

* refactor BoatComponents, turn/move
This commit is contained in:
turleypol 2024-07-08 11:35:25 +02:00 committed by GitHub
parent b216a16d36
commit 0d00a675d0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 213 additions and 218 deletions

View file

@ -82,15 +82,13 @@ BoatShape::ComponentShape::ComponentShape( const std::string& str, unsigned char
graphic = static_cast<unsigned short>( strtoul( tmp.c_str(), nullptr, 0 ) );
if ( graphic )
{
unsigned short xd, yd;
s16 xd, yd;
if ( is >> xd >> yd )
{
xdelta = xd;
ydelta = yd;
zdelta = 0;
signed short zd;
delta = Core::Vec3d( xd, yd, 0 );
s16 zd;
if ( is >> zd )
zdelta = zd;
delta.z( zd );
return;
}
}
@ -113,15 +111,13 @@ BoatShape::ComponentShape::ComponentShape( const std::string& str, const std::st
graphic = static_cast<unsigned short>( strtoul( tmp.c_str(), nullptr, 0 ) );
if ( graphic )
{
unsigned short xd, yd;
s16 xd, yd;
if ( is >> xd >> yd )
{
xdelta = xd;
ydelta = yd;
zdelta = 0;
signed short zd;
delta = Core::Vec3d( xd, yd, 0 );
s16 zd;
if ( is >> zd )
zdelta = zd;
delta.z( zd );
ok = true;
}
}
@ -282,13 +278,13 @@ bool BoatShapeExists( u16 multiid )
return Core::gamestate.boatshapes.count( multiid ) != 0;
}
void UBoat::send_smooth_move( Network::Client* client, Core::UFACING move_dir, u8 speed, u16 newx,
u16 newy, bool relative )
// TODO: why not send after actual movement like the other pkts
void UBoat::send_smooth_move( Network::Client* client, Core::UFACING move_dir, u8 speed,
const Core::Pos4d& newpos, bool relative ) const
{
Network::PktHelper::PacketOut<Network::PktOut_F6> msg;
u16 xmod = newx - x();
u16 ymod = newy - y();
auto posdelta = newpos.xyz() - pos3d();
Core::UFACING b_facing = boat_facing();
if ( relative == false )
@ -301,10 +297,10 @@ void UBoat::send_smooth_move( Network::Client* client, Core::UFACING move_dir, u
msg->Write<u8>( move_dir );
msg->Write<u8>( b_facing );
msg->WriteFlipped<u16>( newx );
msg->WriteFlipped<u16>( newy );
msg->WriteFlipped<u16>( ( z() < 0 ) ? static_cast<u16>( 0x10000 + z() )
: static_cast<u16>( z() ) );
msg->WriteFlipped<u16>( newpos.x() );
msg->WriteFlipped<u16>( newpos.y() );
msg->WriteFlipped<u16>( ( newpos.z() < 0 ) ? static_cast<u16>( 0x10000 + newpos.z() )
: static_cast<u16>( newpos.z() ) );
// 0xf000 encoding room, huffman can take more space then the maximum of 0xffff
const u16 max_count = ( 0xf000 - 18 ) / 10;
@ -322,10 +318,11 @@ void UBoat::send_smooth_move( Network::Client* client, Core::UFACING move_dir, u
if ( component == nullptr || component->orphan() )
continue;
msg->Write<u32>( component->serial_ext );
msg->WriteFlipped<u16>( static_cast<u16>( component->x() + xmod ) );
msg->WriteFlipped<u16>( static_cast<u16>( component->y() + ymod ) );
msg->WriteFlipped<u16>( static_cast<u16>( ( component->z() < 0 ) ? ( 0x10000 + component->z() )
: ( component->z() ) ) );
const auto comppos = component->pos() + posdelta;
msg->WriteFlipped<u16>( comppos.x() );
msg->WriteFlipped<u16>( comppos.y() );
msg->WriteFlipped<u16>(
static_cast<u16>( ( comppos.z() < 0 ) ? ( 0x10000 + comppos.z() ) : ( comppos.z() ) ) );
++object_count;
}
for ( auto& travellerRef : travellers_ )
@ -353,10 +350,11 @@ void UBoat::send_smooth_move( Network::Client* client, Core::UFACING move_dir, u
continue;
}
msg->Write<u32>( obj->serial_ext );
msg->WriteFlipped<u16>( static_cast<u16>( obj->x() + xmod ) );
msg->WriteFlipped<u16>( static_cast<u16>( obj->y() + ymod ) );
const auto objpos = obj->pos() + posdelta;
msg->WriteFlipped<u16>( objpos.x() );
msg->WriteFlipped<u16>( objpos.y() );
msg->WriteFlipped<u16>(
static_cast<u16>( ( obj->z() < 0 ) ? ( 0x10000 + obj->z() ) : ( obj->z() ) ) );
static_cast<u16>( ( objpos.z() < 0 ) ? ( 0x10000 + objpos.z() ) : ( objpos.z() ) ) );
++object_count;
}
u16 len = msg->offset;
@ -368,12 +366,11 @@ void UBoat::send_smooth_move( Network::Client* client, Core::UFACING move_dir, u
msg.Send( client, len );
}
void UBoat::send_smooth_move_to_inrange( Core::UFACING move_dir, u8 speed, u16 newx, u16 newy,
bool relative )
void UBoat::send_smooth_move_to_inrange( Core::UFACING move_dir, u8 speed,
const Core::Pos4d& newpos, bool relative ) const
{
const Core::Pos2d newpos = Core::Pos2d( newx, newy );
Core::WorldIterator<Core::OnlinePlayerFilter>::InMaxVisualRange(
newpos, realm(),
newpos,
[&]( Mobile::Character* zonechr )
{
Network::Client* client = zonechr->client;
@ -381,7 +378,7 @@ void UBoat::send_smooth_move_to_inrange( Core::UFACING move_dir, u8 speed, u16 n
if ( zonechr->in_visual_range( this, newpos ) && zonechr->in_visual_range( this ) &&
client->ClientType & Network::CLIENTTYPE_7090 ) // send this only to those who see the
// old location aswell
send_smooth_move( client, move_dir, speed, newx, newy, relative );
send_smooth_move( client, move_dir, speed, newpos, relative );
} );
}
@ -760,7 +757,7 @@ bool UBoat::on_ship( const BoatContext& bc, const UObject* obj )
if ( item->container != nullptr )
return false;
}
Core::Vec2d rxy = obj->pos2d() - Core::Pos2d( bc.x, bc.y );
Core::Vec2d rxy = obj->pos2d() - bc.oldpos.xy();
return bc.mdef.body_contains( rxy );
}
@ -768,6 +765,7 @@ bool UBoat::on_ship( const BoatContext& bc, const UObject* obj )
void UBoat::move_travellers( Core::UFACING move_dir, const BoatContext& oldlocation,
unsigned short newx, unsigned short newy, Realms::Realm* oldrealm )
{
Core::Pos2d newpos2d{ newx, newy };
bool any_orphans = false;
for ( auto& travellerRef : travellers_ )
@ -783,28 +781,27 @@ void UBoat::move_travellers( Core::UFACING move_dir, const BoatContext& oldlocat
}
obj->set_dirty();
auto oldpos = obj->pos();
auto newtravellerpos = obj->pos();
if ( newx != USHRT_MAX &&
newy != USHRT_MAX ) // dave added 3/27/3, if move_xy was used, dont use facing
{
// keeps relative distance from boat mast
auto delta = newtravellerpos.xy() - oldlocation.oldpos.xy();
newtravellerpos.xy( newpos2d + delta );
}
else
{
newtravellerpos.move_to( move_dir );
}
if ( obj->ismobile() )
{
Mobile::Character* chr = static_cast<Mobile::Character*>( obj );
chr->lastpos = oldpos;
chr->setposition( newtravellerpos );
if ( chr->logged_in() )
{
Core::Pos4d oldpos = chr->pos();
chr->lastpos = oldpos;
if ( newx != USHRT_MAX &&
newy != USHRT_MAX ) // dave added 3/27/3, if move_xy was used, dont use facing
{
s16 dx, dy;
dx = chr->x() - oldlocation.x; // keeps relative distance from boat mast
dy = chr->y() - oldlocation.y;
chr->setposition( Core::Pos4d( chr->pos() ).x( newx + dx ).y( newy + dy ) );
}
else
{
chr->setposition( chr->pos().move( move_dir ) );
}
MoveCharacterWorldPosition( oldpos, chr );
chr->position_changed();
if ( chr->client != nullptr )
@ -836,57 +833,16 @@ void UBoat::move_travellers( Core::UFACING move_dir, const BoatContext& oldlocat
}
chr->move_reason = Mobile::Character::MULTIMOVE;
}
else
{
// characters that are logged out move with the boat
// they aren't in the worldzones so this is real easy.
chr->lastpos = chr->pos(); // I think in this case setting last? isn't
// necessary, but I'll do it anyway.
if ( newx != USHRT_MAX &&
newy != USHRT_MAX ) // dave added 3/27/3, if move_xy was used, dont use facing
{
s16 dx, dy;
dx = chr->x() - oldlocation.x; // keeps relative distance from boat mast
dy = chr->y() - oldlocation.y;
chr->setposition( Core::Pos4d( chr->pos() ).x( newx + dx ).y( newy + dy ) );
}
else
{
chr->setposition( chr->pos().move( move_dir ) );
}
}
}
else
{
Items::Item* item = static_cast<Items::Item*>( obj );
Core::Pos4d oldpos = item->pos();
if ( newx != USHRT_MAX &&
newy != USHRT_MAX ) // dave added 4/9/3, if move_xy was used, dont use facing
{
s16 dx, dy;
dx = item->x() - oldlocation.x; // keeps relative distance from boat mast
dy = item->y() - oldlocation.y;
item->setposition( newtravellerpos );
item->set_dirty();
item->setposition( Core::Pos4d( item->pos() ).x( newx + dx ).y( newy + dy ) );
if ( Core::settingsManager.ssopt.refresh_decay_after_boat_moves )
item->restart_decay_timer();
MoveItemWorldPosition( oldpos, item );
}
else
{
item->set_dirty();
item->setposition( item->pos().move( move_dir ) );
if ( Core::settingsManager.ssopt.refresh_decay_after_boat_moves )
item->restart_decay_timer();
MoveItemWorldPosition( oldpos, item );
}
if ( Core::settingsManager.ssopt.refresh_decay_after_boat_moves )
item->restart_decay_timer();
MoveItemWorldPosition( oldpos, item );
Core::WorldIterator<Core::OnlinePlayerFilter>::InMaxVisualRange(
item,
@ -917,30 +873,29 @@ void UBoat::move_travellers( Core::UFACING move_dir, const BoatContext& oldlocat
remove_orphans();
}
void UBoat::turn_traveller_coords( Mobile::Character* chr, RELATIVE_DIR dir )
Core::Pos4d UBoat::turn_coords( const Core::Pos4d& oldpos, RELATIVE_DIR dir ) const
{
chr->lastpos = chr->pos();
s16 xd = chr->x() - x();
s16 yd = chr->y() - y();
Core::Vec2d delta = oldpos.xy() - pos2d();
switch ( dir )
{
case LEFT:
chr->setposition( Core::Pos4d( x() + yd, y() - xd, chr->z(), chr->realm() ) );
chr->facing = static_cast<Core::UFACING>( ( chr->facing + 6 ) & 7 );
delta = Core::Vec2d( delta.y(), -delta.x() );
break;
case AROUND:
chr->setposition( Core::Pos4d( x() - xd, y() - yd, chr->z(), chr->realm() ) );
chr->facing = static_cast<Core::UFACING>( ( chr->facing + 4 ) & 7 );
delta.x( -delta.x() ).y( -delta.y() );
break;
case RIGHT:
chr->setposition( Core::Pos4d( x() - yd, y() + xd, chr->z(), chr->realm() ) );
chr->facing = static_cast<Core::UFACING>( ( chr->facing + 2 ) & 7 );
delta = Core::Vec2d( -delta.y(), delta.x() );
break;
case NO_TURN:
break;
return oldpos;
}
return pos() + delta;
}
u8 UBoat::turn_facing( u8 oldfacing, RELATIVE_DIR dir ) const
{
return ( ( dir * 2 ) + oldfacing ) & 7;
}
void UBoat::turn_travellers( RELATIVE_DIR dir, const BoatContext& oldlocation )
@ -960,17 +915,17 @@ void UBoat::turn_travellers( RELATIVE_DIR dir, const BoatContext& oldlocation )
}
obj->set_dirty();
Core::Pos4d oldpos = obj->pos();
obj->setposition( turn_coords( oldpos, dir ) );
if ( obj->ismobile() )
{
Mobile::Character* chr = static_cast<Mobile::Character*>( obj );
chr->lastpos = oldpos;
chr->setfacing( turn_facing( chr->facing, dir ) );
if ( chr->logged_in() )
{
// send_remove_character_to_nearby( chr );
Core::Pos4d oldpos = chr->pos();
turn_traveller_coords( chr, dir );
Core::MoveCharacterWorldPosition( oldpos, chr );
chr->position_changed();
if ( chr->client != nullptr )
@ -999,41 +954,10 @@ void UBoat::turn_travellers( RELATIVE_DIR dir, const BoatContext& oldlocation )
// deletes.
// chr->lasty = ~ (unsigned short) 0;
}
else
{
turn_traveller_coords( chr, dir );
}
}
else
{
Items::Item* item = static_cast<Items::Item*>( obj );
s16 xd = item->x() - x();
s16 yd = item->y() - y();
u16 newx( 0 );
u16 newy( 0 );
switch ( dir )
{
case NO_TURN:
newx = item->x();
newy = item->y();
break;
case LEFT:
newx = x() + yd;
newy = y() - xd;
break;
case AROUND:
newx = x() - xd;
newy = y() - yd;
break;
case RIGHT:
newx = x() - yd;
newy = y() + xd;
break;
}
item->set_dirty();
Core::Pos4d oldpos = item->pos();
item->setposition( Core::Pos4d( item->pos() ).x( newx ).y( newy ) );
if ( Core::settingsManager.ssopt.refresh_decay_after_boat_moves )
item->restart_decay_timer();
@ -1290,64 +1214,60 @@ bool UBoat::move( Core::UFACING dir, u8 speed, bool relative )
auto newpos = pos().move( move_dir );
if ( navigable( multidef(), newpos ) )
{
BoatContext bc( *this );
send_smooth_move_to_inrange( move_dir, speed, newpos.x(), newpos.y(), relative );
set_dirty();
move_multi_in_world( x(), y(), newpos.x(), newpos.y(), this, realm() );
const Core::Pos4d oldpos = pos();
setposition( newpos );
// NOTE, send_boat_to_inrange pauses those it sends to.
// send_boat_to_inrange( this, oldx, oldy );
move_travellers( move_dir, bc, x(), y(), realm() );
move_components( realm() );
Core::WorldIterator<Core::OnlinePlayerFilter>::InMaxVisualRange(
this,
[&]( Mobile::Character* zonechr )
{
Network::Client* client = zonechr->client;
if ( !zonechr->in_visual_range( this ) )
return;
if ( client->ClientType & Network::CLIENTTYPE_7090 )
{
if ( zonechr->in_visual_range( this, oldpos ) )
return;
else
send_boat_newly_inrange( client ); // send HSA packet only for newly inrange
}
else
{
if ( client->ClientType & Network::CLIENTTYPE_7000 )
send_boat( client ); // Send
else
send_boat_old( client );
}
} );
Core::WorldIterator<Core::OnlinePlayerFilter>::InMaxVisualRange(
oldpos,
[&]( Mobile::Character* zonechr )
{
if ( zonechr->in_visual_range( this, oldpos ) &&
!zonechr->in_visual_range( this ) ) // send remove to chrs only seeing the old loc
send_remove_boat( zonechr->client );
} );
do_tellmoves();
unpause_paused();
return true;
}
else
{
if ( !navigable( multidef(), newpos ) )
return false;
}
BoatContext bc( *this );
send_smooth_move_to_inrange( move_dir, speed, newpos, relative );
set_dirty();
move_multi_in_world( x(), y(), newpos.x(), newpos.y(), this, realm() );
const Core::Pos4d oldpos = pos();
setposition( newpos );
// NOTE, send_boat_to_inrange pauses those it sends to.
// send_boat_to_inrange( this, oldx, oldy );
move_travellers( move_dir, bc, x(), y(), realm() );
move_components( realm() );
Core::WorldIterator<Core::OnlinePlayerFilter>::InMaxVisualRange(
this,
[&]( Mobile::Character* zonechr )
{
Network::Client* client = zonechr->client;
if ( !zonechr->in_visual_range( this ) )
return;
if ( client->ClientType & Network::CLIENTTYPE_7090 )
{
if ( zonechr->in_visual_range( this, oldpos ) )
return; // TODO POS: better place for smooth move
else
send_boat_newly_inrange( client ); // send HSA packet only for newly inrange
}
else
{
if ( client->ClientType & Network::CLIENTTYPE_7000 )
send_boat( client ); // Send
else
send_boat_old( client );
}
} );
Core::WorldIterator<Core::OnlinePlayerFilter>::InMaxVisualRange(
oldpos,
[&]( Mobile::Character* zonechr )
{
if ( zonechr->in_visual_range( this, oldpos ) &&
!zonechr->in_visual_range( this ) ) // send remove to chrs only seeing the old loc
send_remove_boat( zonechr->client );
} );
do_tellmoves();
unpause_paused();
return true;
}
inline unsigned short UBoat::multiid_ifturn( RELATIVE_DIR dir )
@ -1418,8 +1338,7 @@ void UBoat::transform_components( const BoatShape& old_boatshape, Realms::Realm*
Core::Pos4d oldpos = item->pos();
item->setposition(
pos() + Core::Vec3d( itr2->xdelta, itr2->ydelta, static_cast<s8>( itr2->zdelta ) ) );
item->setposition( pos() + itr2->delta );
MoveItemWorldPosition( oldpos, item );
@ -1483,8 +1402,7 @@ void UBoat::move_components( Realms::Realm* /*oldrealm*/ )
item->set_dirty();
Core::Pos4d oldpos = item->pos();
item->setposition(
pos() + Core::Vec3d( itr2->xdelta, itr2->ydelta, static_cast<s8>( itr2->zdelta ) ) );
item->setposition( pos() + itr2->delta );
MoveItemWorldPosition( oldpos, item );
@ -1535,7 +1453,7 @@ bool UBoat::turn( RELATIVE_DIR dir )
send_display_boat_to_inrange( x(), y() );
do_tellmoves();
unpause_paused();
facing = ( ( dir * 2 ) + facing ) & 7;
facing = turn_facing( facing, dir );
result = true;
}
else
@ -1830,8 +1748,7 @@ void UBoat::create_components()
component->graphic = itr->graphic;
// component itemdesc entries generally have graphic=1, so they don't get their height set.
component->height = Plib::tileheight( component->graphic );
component->setposition(
pos() + Core::Vec3d( itr->xdelta, itr->ydelta, static_cast<s8>( itr->zdelta ) ) );
component->setposition( pos() + itr->delta );
component->disable_decay();
component->movable( false );
add_item_to_world( component );

View file

@ -77,9 +77,7 @@ struct BoatShape
unsigned int objtype;
unsigned short graphic;
unsigned short altgraphic;
unsigned short xdelta;
unsigned short ydelta;
signed short zdelta;
Core::Vec3d delta;
ComponentShape( const std::string& str, const std::string& altstr, unsigned char type );
ComponentShape( const std::string& str, unsigned char type );
};
@ -99,10 +97,9 @@ class UBoat final : public UMulti
class BoatContext
{
const MultiDef& mdef;
unsigned short x; // TODO Pos2d
unsigned short y;
Core::Pos4d oldpos;
explicit BoatContext( const UBoat& ub ) : mdef( ub.multidef() ), x( ub.x() ), y( ub.y() ){};
explicit BoatContext( const UBoat& ub ) : mdef( ub.multidef() ), oldpos( ub.pos() ){};
friend class UBoat;
BoatContext& operator=( const BoatContext& ) { return *this; }
};
@ -130,7 +127,7 @@ public:
bool move( Core::UFACING dir, u8 speed, bool relative );
bool move_xy( const Core::Pos2d& newp, int flags, Realms::Realm* oldrealm );
enum RELATIVE_DIR
enum RELATIVE_DIR // order matters! facing = ( ( dir * 2 ) + facing ) & 7;
{
NO_TURN,
RIGHT,
@ -143,10 +140,6 @@ public:
virtual void unregister_object( Core::UObject* obj ) override;
Core::UFACING boat_facing() const;
void send_smooth_move( Network::Client* client, Core::UFACING move_dir, u8 speed, u16 newx,
u16 newy, bool relative );
void send_smooth_move_to_inrange( Core::UFACING move_dir, u8 speed, u16 newx, u16 newy,
bool relative );
void send_display_boat( Network::Client* client );
void send_display_boat_to_inrange( u16 oldx = USHRT_MAX, u16 oldy = USHRT_MAX );
void send_boat( Network::Client* client );
@ -192,7 +185,6 @@ protected:
unsigned short x = USHRT_MAX, unsigned short y = USHRT_MAX,
Realms::Realm* oldrealm = nullptr );
void turn_travellers( RELATIVE_DIR dir, const BoatContext& oldlocation );
void turn_traveller_coords( Mobile::Character* chr, RELATIVE_DIR dir );
static bool on_ship( const BoatContext& bc, const Core::UObject* obj );
void move_offline_mobiles( const Core::Pos4d& newpos );
const MultiDef& multi_ifturn( RELATIVE_DIR dir );
@ -226,6 +218,13 @@ protected:
friend struct BoatMoveGuard;
private:
void send_smooth_move( Network::Client* client, Core::UFACING move_dir, u8 speed,
const Core::Pos4d& newpos, bool relative ) const;
void send_smooth_move_to_inrange( Core::UFACING move_dir, u8 speed, const Core::Pos4d& newpos,
bool relative ) const;
Core::Pos4d turn_coords( const Core::Pos4d& oldpos, RELATIVE_DIR dir ) const;
u8 turn_facing( u8 oldfacing, RELATIVE_DIR dir ) const;
void create_components();
typedef Core::UObjectRef Traveller;
typedef std::vector<Traveller> Travellers;

View file

@ -20,6 +20,12 @@ exported function create()
DestroyMulti(res);
return ret_error(er);
endif
if (res.portplank.x != 10 || res.portplank.y != 48 || res.portplank.z != -4)
var er:="Wrong portplank pos: {},{},{}".format(res.portplank.x, res.portplank.y, res.portplank.z);
DestroyMulti(res);
return ret_error(er);
endif
for i:=1 to 5
sleepms(10);
if (res.getprop("started"))
@ -269,3 +275,76 @@ endfunction
DestroyMulti(boat);
return 1;
endfunction*/
exported function test_boat_turn_travellers()
var boat:=CreateMultiAtLocation(10,50,-4,0x11000, CRMULTI_FACING_EAST);
if (!boat)
return ret_error("Failed to create boat "+boat);
endif
var item:=CreateItemAtLocation(10,51,0,0xf3f);
if (!item)
DestroyMulti(boat);
return ret_error($"Failed to create item {item}");
endif
var npc := CreateNPCFromTemplate( ":testnpc:probe_npc", 9, 50, 0 );
if ( !npc )
DestroyItem(item);
DestroyMulti(boat);
return ret_error( $"Failed to create npc: {npc}" );
endif
var checks:={
{1,4, 9,50, 10,49}, // right
{3,2, 10,51, 9,50}, // left
{2,6, 10,49, 11,50}, // around
{0,6, 10,49, 11,50} // nothing
};
var result:=1;
foreach check in checks
var res:=TurnBoat(boat,check[1]);
if (!res)
result:=ret_error($"Failed to turn {check} {res}");
break;
endif
if (boat.facing != check[2] || item.x != check[3] || item.y != check[4] || npc.x != check[5] || npc.y != check[6])
result := ret_error($"Check failed {check}: facing {boat.facing} item {item.x},{item.y} npc {npc.x},{npc.y}");
break;
endif
endforeach
MoveObjectToLocation( npc, 100, 100, 0, flags := MOVEOBJECT_FORCELOCATION );
npc.kill();
DestroyItem(item);
DestroyMulti(boat);
return result;
endfunction
exported function test_boat_move_travellers()
var boat:=CreateMultiAtLocation(10,50,-4,0x11000, CRMULTI_FACING_EAST);
if (!boat)
return ret_error("Failed to create boat "+boat);
endif
var item:=CreateItemAtLocation(10,51,0,0xf3f);
if (!item)
DestroyMulti(boat);
return ret_error($"Failed to create item {item}");
endif
var npc := CreateNPCFromTemplate( ":testnpc:probe_npc", 9, 50, 0 );
if ( !npc )
DestroyItem(item);
DestroyMulti(boat);
return ret_error( $"Failed to create npc: {npc}" );
endif
var result:=1;
var res:=MoveBoat(boat,0);
if (!res)
result:=ret_error($"Failed to move {res}");
else
if ( item.x != 10 || item.y != 50 || npc.x != 9 || npc.y != 49)
result := ret_error($"Move failed: item {item.x},{item.y} npc {npc.x},{npc.y}");
endif
endif
MoveObjectToLocation( npc, 100, 100, 0, flags := MOVEOBJECT_FORCELOCATION );
npc.kill();
DestroyItem(item);
DestroyMulti(boat);
return result;
endfunction