Skip to content

Commit

Permalink
Corpse equipment changes; Fix support of MaxItems 0 for containers (#635
Browse files Browse the repository at this point in the history
)

* Implementation

* Add core-changes

* Fix warnings

* Fix support of MaxItems 0 for "unlimited" in containers

* add initial tests

* Update core-changes
  • Loading branch information
KevinEady committed Mar 24, 2024
1 parent 814f864 commit ac97667
Show file tree
Hide file tree
Showing 13 changed files with 275 additions and 43 deletions.
13 changes: 12 additions & 1 deletion docs/docs.polserver.com/pol100/corechanges.xml
Expand Up @@ -2,9 +2,20 @@
<ESCRIPT>
<header>
<topic>Latest Core Changes</topic>
<datemodified>03-18-2024</datemodified>
<datemodified>03-22-2024</datemodified>
</header>
<version name="POL100.2.0">
<entry>
<date>03-22-2024</date>
<author>Kevin:</author>
<change type="Changed">Equipment shown on a human corpses will now only show what was equipped<br/>
at the time of the character's death. Previously, any equippable item<br/>
on the corpse would show as equipped.</change>
<change type="Fixed">Hair items equipped on a corpse will no longer disappear when opening<br/>
the corpse.</change>
<change type="Fixed">Containers with MaxItems 0 (default if unspecified) will correctly support<br/>
the maximum allowed items (3200.)</change>
</entry>
<entry>
<date>03-18-2024</date>
<author>Kevin:</author>
Expand Down
8 changes: 8 additions & 0 deletions pol-core/doc/core-changes.txt
@@ -1,4 +1,12 @@
-- POL100.2.0 --
03-22-2024 Kevin:
Changed: Equipment shown on a human corpses will now only show what was equipped
at the time of the character's death. Previously, any equippable item
on the corpse would show as equipped.
Fixed: Hair items equipped on a corpse will no longer disappear when opening
the corpse.
Fixed: Containers with MaxItems 0 (default if unspecified) will correctly support
the maximum allowed items (3200.)
03-18-2024 Kevin:
Fixed: Crash when going to certain map edge locations in Tokuno.
Note: This change requires recreating realms via uoconvert.
Expand Down
3 changes: 3 additions & 0 deletions pol-core/pol/containr.cpp
Expand Up @@ -938,6 +938,9 @@ Items::Item* UContainer::clone() const
unsigned short UContainer::max_items() const
{
const auto max_items = desc.max_items + max_items_mod();
if ( max_items == 0 )
return MAX_CONTAINER_ITEMS;

return static_cast<u16>( std::clamp( max_items, 1, MAX_CONTAINER_ITEMS ) );
}

Expand Down
5 changes: 2 additions & 3 deletions pol-core/pol/mobile/charactr.cpp
Expand Up @@ -2210,9 +2210,8 @@ void Character::die()
// small lambdas to reduce the mess inside the loops
auto _copy_item = [&]( Items::Item* _item ) { // copy a item into the corpse
Items::Item* copy = _item->clone();
copy->invisible( true );
copy->movable( false );
corpse->add( copy );
corpse->equip_and_add( copy, copy->layer );
};
auto _drop_item_to_world = [&]( Items::Item* _item ) { // places the item onto the corpse coords
_item->setposition( corpse->pos() );
Expand Down Expand Up @@ -2299,7 +2298,7 @@ void Character::die()
}
else
{
corpse->add_at_random_location( item );
corpse->equip_and_add( item, layer );
}
UPDATE_CHECKPOINT();
}
Expand Down
28 changes: 19 additions & 9 deletions pol-core/pol/mobile/corpse.cpp
Expand Up @@ -31,7 +31,13 @@ UCorpse::UCorpse( const Items::ContainerDesc& descriptor )
: UContainer( descriptor ), corpsetype( 0 ), ownerserial( 0 )
{
movable( false );
layer_list_.resize( HIGHEST_LAYER + 1, nullptr );
can_equip_list_.resize( HIGHEST_LAYER + 1 );
}

const Core::ItemRef& UCorpse::GetItemOnLayer( unsigned idx ) const
{
passert( Items::valid_equip_layer( idx ) );
return can_equip_list_.at( idx );
}

bool UCorpse::take_contents_to_grave() const
Expand All @@ -47,7 +53,7 @@ void UCorpse::take_contents_to_grave( bool newvalue )
void UCorpse::add( Item* item )
{
// When an item is added, check if it's equippable and add to the appropriate layer
if ( Items::valid_equip_layer( item ) && GetItemOnLayer( item->tile_layer ) == nullptr )
if ( Items::valid_equip_layer( item ) && GetItemOnLayer( item->tile_layer ) == item )
{
PutItemOnLayer( item );
}
Expand All @@ -56,13 +62,19 @@ void UCorpse::add( Item* item )
base::add( item );
}

void UCorpse::equip_and_add( Item* item, unsigned idx )
{
can_equip_list_[idx].set( item );
add_at_random_location( item );
}

void UCorpse::remove( iterator itr )
{
Item* item = *itr;

if ( Items::valid_equip_layer( item ) )
{
Item* item_on_layer = GetItemOnLayer( item->tile_layer );
auto& item_on_layer = GetItemOnLayer( item->tile_layer );

if ( item_on_layer != nullptr && item_on_layer->serial == item->serial )
{
Expand Down Expand Up @@ -109,8 +121,6 @@ void UCorpse::PutItemOnLayer( Item* item )
item->set_dirty();
set_dirty();
item->layer = item->tile_layer;
layer_list_[item->tile_layer] = Contents::value_type( item );
add_bulk( item );
}

void UCorpse::RemoveItemFromLayer( Item* item )
Expand All @@ -120,7 +130,6 @@ void UCorpse::RemoveItemFromLayer( Item* item )

item->set_dirty();
set_dirty();
layer_list_[item->tile_layer] = nullptr;
item->layer = 0;
}

Expand Down Expand Up @@ -152,7 +161,7 @@ size_t UCorpse::estimatedSize() const
size_t size = base::estimatedSize() + sizeof( u16 ) /*corpsetype*/
+ sizeof( u32 ) /*ownerserial*/
// no estimateSize here element is in objhash
+ 3 * sizeof( Items::Item** ) + layer_list_.capacity() * sizeof( Items::Item* );
+ 3 * sizeof( Items::Item** ) + can_equip_list_.capacity() * sizeof( Items::Item* );
return size;
}

Expand All @@ -163,8 +172,9 @@ void UCorpse::on_insert_add_item( Mobile::Character* mob, MoveType move, Items::
if ( Items::valid_equip_layer( new_item ) )
{
UCorpse* corpse = static_cast<UCorpse*>( this );
Item* item_on_layer = corpse->GetItemOnLayer( new_item->tile_layer );
if ( item_on_layer != nullptr && item_on_layer->serial == new_item->serial )
auto& item_on_layer = corpse->GetItemOnLayer( new_item->tile_layer );
if ( item_on_layer != nullptr && !item_on_layer->orphan() &&
item_on_layer->serial == new_item->serial )
{
send_corpse_equip_inrange( corpse );
}
Expand Down
15 changes: 4 additions & 11 deletions pol-core/pol/mobile/corpse.h
Expand Up @@ -4,6 +4,7 @@
#include "../../clib/rawtypes.h"
#include "../containr.h"
#include "../item/item.h"
#include "../reftypes.h"

namespace Pol
{
Expand Down Expand Up @@ -50,6 +51,7 @@ class UCorpse final : public UContainer
virtual u16 get_senditem_amount() const override;

virtual void add( Item* item ) override;
void equip_and_add( Item* item, unsigned idx );
virtual void remove( iterator itr ) override;

virtual void on_insert_add_item( Mobile::Character* mob, MoveType move,
Expand All @@ -58,7 +60,7 @@ class UCorpse final : public UContainer
void take_contents_to_grave( bool newvalue );
u16 corpsetype;
u32 ownerserial; // NPCs get deleted on death, so serial is used.
Items::Item* GetItemOnLayer( unsigned idx ) const;
const Core::ItemRef& GetItemOnLayer( unsigned idx ) const;

virtual bool get_method_hook( const char* methodname, Bscript::Executor* ex, ExportScript** hook,
unsigned int* PC ) const override;
Expand All @@ -79,17 +81,8 @@ class UCorpse final : public UContainer
// value );
// virtual Bscript::BObjectImp* set_script_member( const char *membername, int value );
virtual bool script_isa( unsigned isatype ) const override;
Contents layer_list_;
std::vector<Core::ItemRef> can_equip_list_;
};

inline Items::Item* UCorpse::GetItemOnLayer( unsigned idx ) const
{
// Checks if the requested layer is valid
if ( Items::valid_equip_layer( idx ) )
return layer_list_[idx];

return nullptr;
}
} // namespace Core
} // namespace Pol

Expand Down
16 changes: 7 additions & 9 deletions pol-core/pol/ufunc.cpp
Expand Up @@ -497,12 +497,10 @@ void send_put_in_container_to_inrange( const Item* item )
// An item is visible on a corpse if:
// - it's visible
// - or the chr has seeinvisitems() privilege
// - it's hair or beard
bool can_see_on_corpse( const Client* client, const Item* item )
// (note: hair items are not invisible on corpses)
bool can_see_on_corpse( const Client* client, const Core::ItemRef& item )
{
bool invisible =
( item->invisible() && !client->chr->can_seeinvisitems() && item->layer != Core::LAYER_HAIR &&
item->layer != Core::LAYER_BEARD && item->layer != Core::LAYER_FACE );
bool invisible = ( item->invisible() && !client->chr->can_seeinvisitems() );

return !invisible;
}
Expand All @@ -517,9 +515,9 @@ void send_corpse_equip( Client* client, const UCorpse* corpse )

for ( unsigned layer = Core::LOWEST_LAYER; layer <= Core::HIGHEST_LAYER; ++layer )
{
Item* item2 = corpse->GetItemOnLayer( layer );
const auto& item2 = corpse->GetItemOnLayer( layer );

if ( !item2 )
if ( !item2 || item2->orphan() || item2->container != corpse )
continue;

if ( !can_see_on_corpse( client, item2 ) )
Expand Down Expand Up @@ -547,9 +545,9 @@ void send_corpse_contents( Client* client, const UCorpse* corpse )

for ( unsigned layer = Core::LOWEST_LAYER; layer <= Core::HIGHEST_LAYER; ++layer )
{
const Items::Item* item = corpse->GetItemOnLayer( layer );
const Core::ItemRef item = corpse->GetItemOnLayer( layer );

if ( !item )
if ( !item || item->orphan() || item->container != corpse )
continue;

if ( !can_see_on_corpse( client, item ) )
Expand Down
8 changes: 8 additions & 0 deletions pol-core/poltool/testfiles.cpp
Expand Up @@ -78,6 +78,14 @@ void FileGenerator::modifyTiledata( std::vector<T>& land, std::vector<U>& item )
additem( &item[0xe75], 0x00600002, 3, 21, 1, "backpack" );
additem( &item[0xf3f], 0x04000800, 0, 0, 1, "arrow" );
additem( &item[0xeed], 0x04000801, 0, 0, 0, "gold coin" );

additem( &item[0x2006], 0x00214000, 255, 0, 0, "corpse" );

// equipment
additem( &item[0x1517], 0x00404002, 1, 5, 1, "shirt" );
additem( &item[0x152e], 0x08400000, 2, 4, 1, "short pants" );
additem( &item[0x203b], 0x00400002, 0, 11, 0, "short hair" );

// house 0x6b
additem( &item[0x6], 0x00002050, 255, 0, 20, "wooden wall" );
additem( &item[0x7], 0x00002050, 255, 0, 20, "wooden wall" );
Expand Down
14 changes: 14 additions & 0 deletions testsuite/pol/config/equip.cfg
@@ -0,0 +1,14 @@
Equipment StartingEquipment
{
# shirt, 5399
Equip 0x1517

# short pants, 5422
Equip 0x152e

# short hair, 8251
Equip 0x203b

# backpack, 3701
Equip 0xe75
}
125 changes: 125 additions & 0 deletions testsuite/pol/testpkgs/client/test_client_corpse.src
@@ -0,0 +1,125 @@
use os;
use uo;

include "testutil";
include "communication";

var char;
var char2;
var npc;
var corpse;

var clientcon := getClientConnection();

program chartests()
var a := FindAccount( "testclient0" );
char := a.getcharacter( 1 );
if ( !char )
return ret_error( "Could not find char at slot 1" );
endif
a := FindAccount( "testclient1" );
char2 := a.getcharacter( 1 );
if ( !char2 )
return ret_error( "Could not find char2 at slot 1" );
endif
endprogram

function cleanup()
if ( npc )
npc.kill();
npc := 0;
endif
if ( corpse )
DestroyItem( corpse );
corpse := 0;
endif
endfunction

function cleanup_ret_error( err )
cleanup();
return ret_error( err );
endfunction

exported function test_corpse_equipment()
var res, corpse_equipment, corpse_content, goldcoin;

if ( !( res := MoveObjectToLocation( char, 100, 100, 0, flags := MOVEOBJECT_FORCELOCATION ) ) )
return cleanup_ret_error( $"Could not move player: {res}" );
endif

if ( !( npc := CreateNPCFromTemplate( ":testnpc:probe_npc", 100, 100, 0 ) ) )
return cleanup_ret_error( $"Could not create npc: {npc}" );
endif

if ( !( res := EquipFromTemplate( npc, "StartingEquipment" ) ) )
return cleanup_ret_error( $"Could not equip template: {res}" );
endif

if ( !( goldcoin := CreateItemInContainer( npc.backpack, "goldcoin", 1000 ) ) )
return cleanup_ret_error( $"Could not create goldcoin in backpack: {goldcoin}" );
endif

npc.kill();
npc := 0;
Sleep( 1 );

if ( !( corpse := ListItemsNearLocationOfType( 100, 100, 0, 1, 0x2006 )[1] ) )
return cleanup_ret_error( $"Could not find corpse" );
endif

//
// The corpse_content should only contain the equipped items (and not the goldcoin),
// as the container has not been opened.
//
Clear_Event_Queue();
clientcon.sendevent( struct{ todo := "list_equipped_items", arg := corpse.serial, id := 0 } );
if ( !( corpse_equipment := waitForClient( 0, { EVT_LIST_EQUIPPED_ITEMS } ) ) )
return cleanup_ret_error( $"Could not list corpse equipment: {corpse_equipment}" );
endif

Clear_Event_Queue();
clientcon.sendevent( struct{ todo := "list_objects", arg := corpse.serial, id := 0 } );
if ( !( corpse_content := waitForClient( 0, { EVT_LIST_OBJS } ) ) )
return cleanup_ret_error( $"Could not get first corpse content: {corpse_content}" );
endif

var corpse_equipment_serials := array{};
foreach equipment in ( corpse_equipment.objs )
corpse_equipment_serials.append( equipment.serial );
endforeach

if ( Len( corpse_content.objs ) != Len( corpse_equipment.objs ) )
return cleanup_ret_error( $"corpse_content and corpse_equipment not equal: {corpse_content.objs} != {corpse_equipment.objs}" );
endif

foreach content in ( corpse_content.objs )
if ( !( content.serial in corpse_equipment_serials ) )
return cleanup_ret_error( $"Content {content.serial:x} not in corpse equipment" );
endif
endforeach

//
// After opening the corpse, goldcoin should now be in the corpse contents.
//
Clear_Event_Queue();
clientcon.sendevent( struct{ todo := "double_click", arg := corpse.serial, id := 0 } );
if ( !( res := waitForClient( 0, { EVT_DOUBLE_CLICK } ) ) )
return cleanup_ret_error( $"Could not double-click corpse: {res}" );
endif

Clear_Event_Queue();
clientcon.sendevent( struct{ todo := "list_objects", arg := corpse.serial, id := 0 } );
if ( !( corpse_content := waitForClient( 0, { EVT_LIST_OBJS } ) ) )
return cleanup_ret_error( $"Could not get second corpse content: {corpse_content}" );
endif

res := ret_error( $"goldcoin {goldcoin.serial:x} not found in corpse_content: {corpse_content.objs}" );
foreach content in ( corpse_content.objs )
if ( content.serial == goldcoin.serial )
res := 1;
endif
endforeach

cleanup();
return res;
endfunction

0 comments on commit ac97667

Please sign in to comment.