mirror of
https://github.com/polserver/polserver
synced 2026-08-13 08:23:08 -04:00
misc tests
This commit is contained in:
parent
c80bcebfd5
commit
dfd27eaf26
11 changed files with 1597 additions and 0 deletions
111
testsuite/pol/testpkgs/misc/test_datastore.src
Normal file
111
testsuite/pol/testpkgs/misc/test_datastore.src
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
use uo;
|
||||
use os;
|
||||
use datafile;
|
||||
|
||||
include "testutil";
|
||||
|
||||
const DF_NAME := ":TestMisc:dstest";
|
||||
|
||||
program testdatastore()
|
||||
return 1;
|
||||
endprogram
|
||||
|
||||
// The datafile and element handles are reference objects of their own, each with its own
|
||||
// type name and integer.
|
||||
exported function datafile_refs_report_types( unused resmngr )
|
||||
var df := CreateDataFile( DF_NAME, DF_KEYTYPE_STRING );
|
||||
if ( !df )
|
||||
return ret_error( $"failed to create datafile: {df}" );
|
||||
endif
|
||||
|
||||
var elem := df.CreateElement( "a" );
|
||||
if ( !elem )
|
||||
return ret_error( $"failed to create element: {elem}" );
|
||||
endif
|
||||
|
||||
var res := 1;
|
||||
if ( TypeOf( df ) != "DataFileRef" )
|
||||
res := ret_error( $"unexpected datafile type name: {TypeOf( df )}" );
|
||||
elseif ( TypeOf( elem ) != "DataElemRef" )
|
||||
res := ret_error( $"unexpected element type name: {TypeOf( elem )}" );
|
||||
elseif ( TypeOfInt( df ) == TypeOfInt( elem ) )
|
||||
res := ret_error( "datafile and element share a type integer" );
|
||||
endif
|
||||
|
||||
UnloadDataFile( DF_NAME );
|
||||
return res;
|
||||
endfunction
|
||||
|
||||
// A method neither handle recognises is looked up by name and answers falsy.
|
||||
exported function datafile_unknown_methods( unused resmngr )
|
||||
var df := CreateDataFile( DF_NAME, DF_KEYTYPE_STRING );
|
||||
if ( !df )
|
||||
return ret_error( $"failed to create datafile: {df}" );
|
||||
endif
|
||||
var elem := df.CreateElement( "a" );
|
||||
if ( !elem )
|
||||
return ret_error( $"failed to create element: {elem}" );
|
||||
endif
|
||||
|
||||
var res := 1;
|
||||
if ( df.no_such_method_at_all() )
|
||||
res := ret_error( "datafile answered an unknown method" );
|
||||
elseif ( elem.no_such_method_at_all() )
|
||||
res := ret_error( "element answered an unknown method" );
|
||||
endif
|
||||
|
||||
UnloadDataFile( DF_NAME );
|
||||
return res;
|
||||
endfunction
|
||||
|
||||
// DeleteElement removes a key, and deleting one that was never there is harmless.
|
||||
exported function datafile_delete_element( unused resmngr )
|
||||
var df := CreateDataFile( DF_NAME, DF_KEYTYPE_STRING );
|
||||
if ( !df )
|
||||
return ret_error( $"failed to create datafile: {df}" );
|
||||
endif
|
||||
|
||||
df.CreateElement( "a" );
|
||||
df.CreateElement( "b" );
|
||||
|
||||
var res := 1;
|
||||
if ( df.Keys().size() != 2 )
|
||||
res := ret_error( $"expected two keys, got {df.Keys()}" );
|
||||
else
|
||||
df.DeleteElement( "a" );
|
||||
if ( df.Keys().size() != 1 )
|
||||
res := ret_error( $"expected one key after delete, got {df.Keys()}" );
|
||||
elseif ( df.FindElement( "a" ) )
|
||||
res := ret_error( "the deleted element is still findable" );
|
||||
else
|
||||
df.DeleteElement( "no_such_key" );
|
||||
if ( df.Keys().size() != 1 )
|
||||
res := ret_error( "deleting a missing key changed the key set" );
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
UnloadDataFile( DF_NAME );
|
||||
return res;
|
||||
endfunction
|
||||
|
||||
// ListDataFiles reports what is loaded, and UnloadDataFile takes it back out.
|
||||
exported function datafile_list_and_unload( unused resmngr )
|
||||
var df := CreateDataFile( DF_NAME, DF_KEYTYPE_STRING );
|
||||
if ( !df )
|
||||
return ret_error( $"failed to create datafile: {df}" );
|
||||
endif
|
||||
|
||||
var listed := ListDataFiles();
|
||||
if ( TypeOf( listed ) != "Array" )
|
||||
UnloadDataFile( DF_NAME );
|
||||
return ret_error( $"ListDataFiles did not answer an array: {TypeOf( listed )}" );
|
||||
endif
|
||||
|
||||
var res := UnloadDataFile( DF_NAME );
|
||||
if ( !res )
|
||||
return ret_error( $"failed to unload datafile: {res}" );
|
||||
endif
|
||||
|
||||
return 1;
|
||||
endfunction
|
||||
161
testsuite/pol/testpkgs/misc/test_listing.src
Normal file
161
testsuite/pol/testpkgs/misc/test_listing.src
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
use uo;
|
||||
use os;
|
||||
|
||||
include "testutil";
|
||||
|
||||
// A patch of map reserved for this file so the mobiles it spawns cannot be picked up by
|
||||
// another package's listing test.
|
||||
const LX := 150;
|
||||
const LY := 150;
|
||||
const LZ := 0;
|
||||
|
||||
program testlisting()
|
||||
return 1;
|
||||
endprogram
|
||||
|
||||
function find_serial( list, serial )
|
||||
foreach entry in list
|
||||
if ( entry.serial == serial )
|
||||
return 1;
|
||||
endif
|
||||
endforeach
|
||||
return 0;
|
||||
endfunction
|
||||
|
||||
// ListMobilesNearLocation finds what is standing inside the range and nothing outside it.
|
||||
exported function list_mobiles_near_location( resmngr )
|
||||
var npc := resmngr.CreateNPCFromTemplate( ":TestNPC:probe_npc", LX, LY, LZ, 0,
|
||||
_DEFAULT_REALM, 1 );
|
||||
if ( !npc )
|
||||
return ret_error( $"failed to create npc: {npc}" );
|
||||
endif
|
||||
|
||||
var near := ListMobilesNearLocation( LX, LY, LZ, 2 );
|
||||
if ( !find_serial( near, npc.serial ) )
|
||||
return ret_error( $"the npc was not listed within range: {near.size()} found" );
|
||||
endif
|
||||
|
||||
// A location well away from the npc does not list it.
|
||||
var far := ListMobilesNearLocation( LX + 40, LY + 40, LZ, 2 );
|
||||
if ( find_serial( far, npc.serial ) )
|
||||
return ret_error( "the npc was listed from a location out of range" );
|
||||
endif
|
||||
|
||||
return 1;
|
||||
endfunction
|
||||
|
||||
// The Ex form takes two kinds of flag and needs one of each. The state flags -- NORMAL,
|
||||
// HIDDEN, GHOST, CONCEALED -- decide which mobiles qualify at all, and the ONLY flags then
|
||||
// narrow by kind. A mobile that matches no state flag is never listed, whatever else is asked
|
||||
// for, so an ONLY flag on its own selects nothing at all.
|
||||
exported function list_mobiles_near_ex( resmngr )
|
||||
var npc := resmngr.CreateNPCFromTemplate( ":TestNPC:probe_npc", LX, LY, LZ, 0,
|
||||
_DEFAULT_REALM, 1 );
|
||||
if ( !npc )
|
||||
return ret_error( $"failed to create npc: {npc}" );
|
||||
endif
|
||||
|
||||
var npcs := ListMobilesNearLocationEx( LX, LY, LZ, 2,
|
||||
LISTEX_FLAG_NORMAL + LISTEX_FLAG_NPC_ONLY );
|
||||
if ( !find_serial( npcs, npc.serial ) )
|
||||
return ret_error( "the npc was not listed with NORMAL and NPC_ONLY" );
|
||||
endif
|
||||
|
||||
var players := ListMobilesNearLocationEx( LX, LY, LZ, 2,
|
||||
LISTEX_FLAG_NORMAL + LISTEX_FLAG_PLAYERS_ONLY );
|
||||
if ( find_serial( players, npc.serial ) )
|
||||
return ret_error( "the npc was listed with PLAYERS_ONLY" );
|
||||
endif
|
||||
|
||||
// NPC_ONLY without a state flag qualifies nobody.
|
||||
var neither := ListMobilesNearLocationEx( LX, LY, LZ, 2, LISTEX_FLAG_NPC_ONLY );
|
||||
if ( find_serial( neither, npc.serial ) )
|
||||
return ret_error( "an ONLY flag without a state flag should list nothing" );
|
||||
endif
|
||||
|
||||
return 1;
|
||||
endfunction
|
||||
|
||||
// Line of sight is taken from an object rather than a bare location.
|
||||
exported function list_mobiles_in_line_of_sight( resmngr )
|
||||
var seer := resmngr.CreateNPCFromTemplate( ":TestNPC:probe_npc", LX, LY, LZ, 0,
|
||||
_DEFAULT_REALM, 1 );
|
||||
if ( !seer )
|
||||
return ret_error( $"failed to create npc: {seer}" );
|
||||
endif
|
||||
var other := resmngr.CreateNPCFromTemplate( ":TestNPC:probe_npc", LX + 1, LY, LZ, 0,
|
||||
_DEFAULT_REALM, 1 );
|
||||
if ( !other )
|
||||
return ret_error( $"failed to create second npc: {other}" );
|
||||
endif
|
||||
|
||||
var seen := ListMobilesInLineOfSight( seer, 5 );
|
||||
if ( !find_serial( seen, other.serial ) )
|
||||
return ret_error( "the adjacent npc was not in line of sight" );
|
||||
endif
|
||||
|
||||
return 1;
|
||||
endfunction
|
||||
|
||||
// A box query filters by POL class rather than by object type.
|
||||
exported function list_objects_in_box_of_class( resmngr )
|
||||
var npc := resmngr.CreateNPCFromTemplate( ":TestNPC:probe_npc", LX, LY, LZ, 0,
|
||||
_DEFAULT_REALM, 1 );
|
||||
if ( !npc )
|
||||
return ret_error( $"failed to create npc: {npc}" );
|
||||
endif
|
||||
|
||||
var mobiles := ListObjectsInBoxOfClass( POLCLASS_NPC, LX - 2, LY - 2, -128,
|
||||
LX + 2, LY + 2, 127 );
|
||||
if ( !find_serial( mobiles, npc.serial ) )
|
||||
return ret_error( "the npc was not found in the box query" );
|
||||
endif
|
||||
|
||||
var items := ListObjectsInBoxOfClass( POLCLASS_ITEM, LX - 2, LY - 2, -128,
|
||||
LX + 2, LY + 2, 127 );
|
||||
if ( find_serial( items, npc.serial ) )
|
||||
return ret_error( "the npc was listed as an item" );
|
||||
endif
|
||||
|
||||
return 1;
|
||||
endfunction
|
||||
|
||||
// Nobody is logged in during the test run, so the online list is empty while the offline
|
||||
// list is not.
|
||||
exported function online_and_offline_lists( unused resmngr )
|
||||
var online := EnumerateOnlineCharacters();
|
||||
if ( TypeOf( online ) != "Array" )
|
||||
return ret_error( $"EnumerateOnlineCharacters did not answer an array: {TypeOf( online )}" );
|
||||
endif
|
||||
if ( online.size() != 0 )
|
||||
return ret_error( $"expected nobody online, got {online.size()}" );
|
||||
endif
|
||||
|
||||
var offline := ListOfflineMobilesInRealm( _DEFAULT_REALM );
|
||||
if ( TypeOf( offline ) != "Array" )
|
||||
return ret_error( $"ListOfflineMobilesInRealm did not answer an array: {TypeOf( offline )}" );
|
||||
endif
|
||||
|
||||
return 1;
|
||||
endfunction
|
||||
|
||||
// A circle of radius 0 is a single point, and a larger one is symmetric about its centre.
|
||||
exported function midpoint_circle_coords( unused resmngr )
|
||||
var single := GetMidpointCircleCoords( 100, 100, 0 );
|
||||
if ( TypeOf( single ) != "Array" )
|
||||
return ret_error( $"expected an array, got {TypeOf( single )}" );
|
||||
endif
|
||||
|
||||
var ring := GetMidpointCircleCoords( 100, 100, 3 );
|
||||
if ( ring.size() < 1 )
|
||||
return ret_error( "a radius of 3 produced no coordinates" );
|
||||
endif
|
||||
|
||||
foreach coord in ring
|
||||
if ( coord.x < 97 || coord.x > 103 || coord.y < 97 || coord.y > 103 )
|
||||
return ret_error( $"coordinate outside the circle bounds: {coord.x},{coord.y}" );
|
||||
endif
|
||||
endforeach
|
||||
|
||||
return 1;
|
||||
endfunction
|
||||
144
testsuite/pol/testpkgs/misc/test_osopts.src
Normal file
144
testsuite/pol/testpkgs/misc/test_osopts.src
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
use uo;
|
||||
use os;
|
||||
|
||||
include "testutil";
|
||||
|
||||
program testosopts()
|
||||
return 1;
|
||||
endprogram
|
||||
|
||||
// Set_Script_Option answers the previous value of the option it changed, so each option can
|
||||
// be set and put back. SCRIPTOPT_NO_INTERRUPT is the same flag Is_Critical() reports.
|
||||
exported function script_option_round_trip( unused resmngr )
|
||||
if ( Is_Critical() )
|
||||
return ret_error( "the script should not start out critical" );
|
||||
endif
|
||||
|
||||
var old := Set_Script_Option( SCRIPTOPT_NO_INTERRUPT, 1 );
|
||||
if ( old != 0 )
|
||||
return ret_error( $"expected the previous option value 0, got {old}" );
|
||||
endif
|
||||
if ( !Is_Critical() )
|
||||
Set_Script_Option( SCRIPTOPT_NO_INTERRUPT, 0 );
|
||||
return ret_error( "the script should be critical after setting NO_INTERRUPT" );
|
||||
endif
|
||||
|
||||
old := Set_Script_Option( SCRIPTOPT_NO_INTERRUPT, 0 );
|
||||
if ( old != 1 )
|
||||
return ret_error( $"expected the previous option value 1, got {old}" );
|
||||
endif
|
||||
if ( Is_Critical() )
|
||||
return ret_error( "the script should no longer be critical" );
|
||||
endif
|
||||
|
||||
return 1;
|
||||
endfunction
|
||||
|
||||
// These options each store the value given and report the one they replaced.
|
||||
exported function script_options_each( unused resmngr )
|
||||
var options := { SCRIPTOPT_CAN_ACCESS_OFFLINE_MOBILES, SCRIPTOPT_AUXSVC_ASSUME_STRING,
|
||||
SCRIPTOPT_SURVIVE_ATTACHED_DISCONNECT };
|
||||
|
||||
foreach opt in options
|
||||
var first := Set_Script_Option( opt, 1 );
|
||||
if ( first.errortext )
|
||||
return ret_error( $"option {opt} was refused: {first}" );
|
||||
endif
|
||||
|
||||
var second := Set_Script_Option( opt, 0 );
|
||||
if ( second != 1 )
|
||||
return ret_error( $"option {opt} did not report the value just set, got {second}" );
|
||||
endif
|
||||
|
||||
// Put the option back where it started.
|
||||
Set_Script_Option( opt, first );
|
||||
endforeach
|
||||
|
||||
return 1;
|
||||
endfunction
|
||||
|
||||
// SCRIPTOPT_NO_RUNAWAY is the odd one out. The value it stores is the negation of the value
|
||||
// passed, but the value it reports is the stored flag rather than the option, so setting it
|
||||
// to 1 and reading it back answers 0 instead of 1.
|
||||
exported function script_option_no_runaway( unused resmngr )
|
||||
var original := Set_Script_Option( SCRIPTOPT_NO_RUNAWAY, 1 );
|
||||
if ( original.errortext )
|
||||
return ret_error( $"NO_RUNAWAY was refused: {original}" );
|
||||
endif
|
||||
|
||||
var readback := Set_Script_Option( SCRIPTOPT_NO_RUNAWAY, 0 );
|
||||
if ( readback != 0 )
|
||||
Set_Script_Option( SCRIPTOPT_NO_RUNAWAY, 0 );
|
||||
return ret_error( $"expected the inverted read-back 0, got {readback}" );
|
||||
endif
|
||||
|
||||
// After passing 0 the flag is set, so the next read-back answers 1.
|
||||
var second := Set_Script_Option( SCRIPTOPT_NO_RUNAWAY, 0 );
|
||||
if ( second != 1 )
|
||||
return ret_error( $"expected the flag to read back as 1, got {second}" );
|
||||
endif
|
||||
|
||||
return 1;
|
||||
endfunction
|
||||
|
||||
// An option number outside the known set is refused.
|
||||
exported function script_option_unknown( unused resmngr )
|
||||
var res := Set_Script_Option( 9999, 1 );
|
||||
if ( res || res.errortext != "Unknown Script Option" )
|
||||
return ret_error( $"expected Unknown Script Option, got {res}" );
|
||||
endif
|
||||
|
||||
res := Set_Script_Option( "not_a_number", 1 );
|
||||
if ( res )
|
||||
return ret_error( $"expected a non-integer option number to be refused, got {res}" );
|
||||
endif
|
||||
|
||||
return 1;
|
||||
endfunction
|
||||
|
||||
// Set_Priority answers the previous priority and holds the range 1..255.
|
||||
exported function set_priority_round_trip( unused resmngr )
|
||||
var original := Set_Priority( 100 );
|
||||
if ( original.errortext )
|
||||
return ret_error( $"failed to set priority: {original}" );
|
||||
endif
|
||||
|
||||
var previous := Set_Priority( 50 );
|
||||
if ( previous != 100 )
|
||||
return ret_error( $"expected the previous priority 100, got {previous}" );
|
||||
endif
|
||||
|
||||
var res := Set_Priority( 0 );
|
||||
if ( res )
|
||||
return ret_error( $"a priority below 1 should be refused, got {res}" );
|
||||
endif
|
||||
|
||||
res := Set_Priority( 256 );
|
||||
if ( res )
|
||||
return ret_error( $"a priority above 255 should be refused, got {res}" );
|
||||
endif
|
||||
|
||||
res := Set_Priority( "high" );
|
||||
if ( res )
|
||||
return ret_error( $"a non-integer priority should be refused, got {res}" );
|
||||
endif
|
||||
|
||||
Set_Priority( original );
|
||||
return 1;
|
||||
endfunction
|
||||
|
||||
// Unload_Scripts reports how many it unloaded. A name nobody has loaded unloads nothing.
|
||||
// It is never called with an empty string here: that would unload every script on the shard.
|
||||
exported function unload_scripts_by_name( unused resmngr )
|
||||
var res := Unload_Scripts( ":TestMisc:no_such_script_at_all" );
|
||||
if ( res != 0 )
|
||||
return ret_error( $"expected nothing to unload, got {res}" );
|
||||
endif
|
||||
|
||||
res := Unload_Scripts( 1 );
|
||||
if ( res )
|
||||
return ret_error( $"a non-string script name should be refused, got {res}" );
|
||||
endif
|
||||
|
||||
return 1;
|
||||
endfunction
|
||||
132
testsuite/pol/testpkgs/misc/test_realm.src
Normal file
132
testsuite/pol/testpkgs/misc/test_realm.src
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
use uo;
|
||||
use os;
|
||||
use polsys;
|
||||
|
||||
include "testutil";
|
||||
|
||||
const BASE_REALM := "britannia";
|
||||
|
||||
program testrealm()
|
||||
return 1;
|
||||
endprogram
|
||||
|
||||
// AddRealm needs two strings, an existing base realm, and a name nobody is using yet.
|
||||
exported function add_realm_rejects_bad_arguments( resources )
|
||||
var res := AddRealm( 1, BASE_REALM );
|
||||
if ( res || res.errortext != "Invalid parameter" )
|
||||
return ret_error( $"expected Invalid parameter for a non-string name, got {res}" );
|
||||
endif
|
||||
|
||||
res := AddRealm( "shadow-bad-base", "no_such_realm" );
|
||||
if ( res || res.errortext != "BaseRealm not found." )
|
||||
return ret_error( $"expected BaseRealm not found., got {res}" );
|
||||
endif
|
||||
|
||||
res := AddRealm( BASE_REALM, BASE_REALM );
|
||||
if ( res || res.errortext != "Realmname already defined." )
|
||||
return ret_error( $"expected Realmname already defined., got {res}" );
|
||||
endif
|
||||
|
||||
// A shadow realm cannot itself be used as a base.
|
||||
res := resources.AddRealm( "shadow-as-base", BASE_REALM );
|
||||
if ( !res )
|
||||
return ret_error( $"failed to create shadow realm: {res}" );
|
||||
endif
|
||||
|
||||
res := AddRealm( "shadow-of-shadow", "shadow-as-base" );
|
||||
if ( res || res.errortext != "BaseRealm is a ShadowRealm." )
|
||||
return ret_error( $"expected BaseRealm is a ShadowRealm., got {res}" );
|
||||
endif
|
||||
|
||||
return 1;
|
||||
endfunction
|
||||
|
||||
// DeleteRealm only removes an empty shadow realm.
|
||||
exported function delete_realm_rejects_bad_args( unused resources )
|
||||
var res := DeleteRealm( 1 );
|
||||
if ( res || res.errortext != "Invalid parameter" )
|
||||
return ret_error( $"expected Invalid parameter for a non-string name, got {res}" );
|
||||
endif
|
||||
|
||||
res := DeleteRealm( "no_such_realm" );
|
||||
if ( res || res.errortext != "Realm not found." )
|
||||
return ret_error( $"expected Realm not found., got {res}" );
|
||||
endif
|
||||
|
||||
res := DeleteRealm( BASE_REALM );
|
||||
if ( res || res.errortext != "Realm is not a ShadowRealm." )
|
||||
return ret_error( $"expected Realm is not a ShadowRealm., got {res}" );
|
||||
endif
|
||||
|
||||
return 1;
|
||||
endfunction
|
||||
|
||||
// A shadow realm holding a top-level item refuses to be deleted until the item is gone.
|
||||
exported function delete_realm_refuses_occupied( unused resources )
|
||||
var realm_name := "shadow-occupied";
|
||||
var res := AddRealm( realm_name, BASE_REALM );
|
||||
if ( !res )
|
||||
return ret_error( $"failed to create shadow realm: {res}" );
|
||||
endif
|
||||
|
||||
var item := CreateItemAtLocation( 100, 100, 0, 0x3e3f, 1, realm_name );
|
||||
if ( !item )
|
||||
DeleteRealm( realm_name );
|
||||
return ret_error( $"failed to create item in shadow realm: {item}" );
|
||||
endif
|
||||
|
||||
res := DeleteRealm( realm_name );
|
||||
var failure;
|
||||
if ( res || res.errortext != "Items in Realm." )
|
||||
failure := ret_error( $"expected Items in Realm., got {res}" );
|
||||
endif
|
||||
|
||||
DestroyItem( item );
|
||||
|
||||
res := DeleteRealm( realm_name );
|
||||
if ( !failure && !res )
|
||||
failure := ret_error( $"an emptied shadow realm should delete, got {res}" );
|
||||
endif
|
||||
|
||||
if ( failure )
|
||||
return failure;
|
||||
endif
|
||||
return 1;
|
||||
endfunction
|
||||
|
||||
// GetRealmDecay and SetRealmDecay share the same name lookup.
|
||||
exported function realm_decay_round_trip( resources )
|
||||
var res := GetRealmDecay( 1 );
|
||||
if ( res || res.errortext != "Invalid parameter" )
|
||||
return ret_error( $"expected Invalid parameter, got {res}" );
|
||||
endif
|
||||
|
||||
res := GetRealmDecay( "no_such_realm" );
|
||||
if ( res || res.errortext != "Realm not found." )
|
||||
return ret_error( $"expected Realm not found., got {res}" );
|
||||
endif
|
||||
|
||||
var realm_name := "shadow-decay";
|
||||
res := resources.AddRealm( realm_name, BASE_REALM );
|
||||
if ( !res )
|
||||
return ret_error( $"failed to create shadow realm: {res}" );
|
||||
endif
|
||||
|
||||
res := SetRealmDecay( realm_name, 0 );
|
||||
if ( !res )
|
||||
return ret_error( $"failed to turn decay off: {res}" );
|
||||
endif
|
||||
if ( GetRealmDecay( realm_name ) )
|
||||
return ret_error( "decay should be off" );
|
||||
endif
|
||||
|
||||
res := SetRealmDecay( realm_name, 1 );
|
||||
if ( !res )
|
||||
return ret_error( $"failed to turn decay on: {res}" );
|
||||
endif
|
||||
if ( !GetRealmDecay( realm_name ) )
|
||||
return ret_error( "decay should be on" );
|
||||
endif
|
||||
|
||||
return 1;
|
||||
endfunction
|
||||
104
testsuite/pol/testpkgs/misc/test_resource.src
Normal file
104
testsuite/pol/testpkgs/misc/test_resource.src
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
use uo;
|
||||
use os;
|
||||
|
||||
include "testutil";
|
||||
|
||||
// The shard defines one resource type, testres, over the whole 192x192 map with
|
||||
// UnitsPerArea 50 (testsuite/pol/regions/resource.cfg and testres.cfg).
|
||||
const RES := "testres";
|
||||
|
||||
// Coordinates used only by this file, so the depletion it causes cannot disturb other tests.
|
||||
const FRESH_X := 180;
|
||||
const FRESH_Y := 180;
|
||||
const DRAIN_X := 182;
|
||||
const DRAIN_Y := 182;
|
||||
|
||||
program testresource()
|
||||
return 1;
|
||||
endprogram
|
||||
|
||||
// Every resource entry point looks the type up by name first.
|
||||
exported function resource_unknown_name( unused resmngr )
|
||||
var res := HarvestResource( "no_such_resource", FRESH_X, FRESH_Y, 1, 1 );
|
||||
if ( res || res.errortext != "No resource by that name" )
|
||||
return ret_error( $"HarvestResource: expected No resource by that name, got {res}" );
|
||||
endif
|
||||
|
||||
res := GetHarvestDifficulty( "no_such_resource", FRESH_X, FRESH_Y, 0 );
|
||||
if ( res || res.errortext != "No resource by that name" )
|
||||
return ret_error( $"GetHarvestDifficulty: expected No resource by that name, got {res}" );
|
||||
endif
|
||||
|
||||
res := GetRegionString( "no_such_resource", FRESH_X, FRESH_Y, "UnitsPerArea" );
|
||||
if ( res || res.errortext != "No resource by that name" )
|
||||
return ret_error( $"GetRegionString: expected No resource by that name, got {res}" );
|
||||
endif
|
||||
|
||||
return 1;
|
||||
endfunction
|
||||
|
||||
// A harvest of b units, at most n times, answers b*a. With b=1 and n=1 that is a single unit.
|
||||
exported function harvest_takes_one_unit( unused resmngr )
|
||||
var res := HarvestResource( RES, FRESH_X, FRESH_Y, 1, 1 );
|
||||
if ( res != 1 )
|
||||
return ret_error( $"expected one unit harvested, got {res}" );
|
||||
endif
|
||||
|
||||
res := HarvestResource( RES, FRESH_X, FRESH_Y, -1, 1 );
|
||||
if ( res || res.errortext != "b must be >= 0" )
|
||||
return ret_error( $"expected b must be >= 0, got {res}" );
|
||||
endif
|
||||
|
||||
return 1;
|
||||
endfunction
|
||||
|
||||
// An area holds UnitsPerArea units. Draining it answers everything that was left, and a
|
||||
// further harvest answers nothing.
|
||||
exported function harvest_drains_the_area( unused resmngr )
|
||||
var first := HarvestResource( RES, DRAIN_X, DRAIN_Y, 1, 1000 );
|
||||
if ( first < 1 )
|
||||
return ret_error( $"expected the area to yield its units, got {first}" );
|
||||
endif
|
||||
|
||||
var second := HarvestResource( RES, DRAIN_X, DRAIN_Y, 1, 1000 );
|
||||
if ( second != 0 )
|
||||
return ret_error( $"a drained area should yield nothing, got {second}" );
|
||||
endif
|
||||
|
||||
return 1;
|
||||
endfunction
|
||||
|
||||
// GetHarvestDifficulty additionally needs a resource-bearing landmark of the given tile type at
|
||||
// the location. Grass is not one, so the middle of the map has none.
|
||||
exported function difficulty_needs_a_landmark( unused resmngr )
|
||||
var res := GetHarvestDifficulty( RES, FRESH_X, FRESH_Y, 0 );
|
||||
if ( res || res.errortext != "No resource-bearing landmark there" )
|
||||
return ret_error( $"expected No resource-bearing landmark there, got {res}" );
|
||||
endif
|
||||
|
||||
// A tile type the resource does not list at all is refused the same way, before the map is
|
||||
// ever consulted.
|
||||
res := GetHarvestDifficulty( RES, SAND_X, SAND_Y, 0x3 );
|
||||
if ( res || res.errortext != "No resource-bearing landmark there" )
|
||||
return ret_error( $"expected No resource-bearing landmark there for grass, got {res}" );
|
||||
endif
|
||||
return 1;
|
||||
endfunction
|
||||
|
||||
// testres lists LandTile 0x17, and the generated map lays a sand transition column of exactly
|
||||
// that tile down the inside of its water border, so the landmark lookup succeeds there. It
|
||||
// first asks the static server for a static of that type and only then falls back to the
|
||||
// landtile, which is what makes this the one case that reaches findstatic.
|
||||
const SAND_X := 29;
|
||||
const SAND_Y := 100;
|
||||
|
||||
exported function difficulty_on_a_landmark( unused resmngr )
|
||||
var res := GetHarvestDifficulty( RES, SAND_X, SAND_Y, 0x17 );
|
||||
if ( res.errortext )
|
||||
return ret_error( $"expected a difficulty on the sand column, got {res}" );
|
||||
endif
|
||||
if ( TypeOf( res ) != "Integer" )
|
||||
return ret_error( $"expected an integer difficulty, got {TypeOf( res )} {res}" );
|
||||
endif
|
||||
return 1;
|
||||
endfunction
|
||||
250
testsuite/pol/testpkgs/misc/test_uomenu.src
Normal file
250
testsuite/pol/testpkgs/misc/test_uomenu.src
Normal file
|
|
@ -0,0 +1,250 @@
|
|||
use uo;
|
||||
use os;
|
||||
use cfgfile;
|
||||
|
||||
include "testutil";
|
||||
|
||||
// The fixture read by the constraint cases. Its element keys are integers, which
|
||||
// is what ApplyConstraint matches the objtypes it is handed against.
|
||||
const CONSTRAINT_CFG := "uoconstraint";
|
||||
const CHEAP := 0x900001; // Amount 5
|
||||
const DEAR := 0x900002; // Amount 20
|
||||
const NO_AMOUNT := 0x900003; // has no Amount property at all
|
||||
const AMOUNT_IS_TEXT := 0x900004; // Amount is a string, not a number
|
||||
|
||||
const ATTACH_SCRIPT := ":testmisc:uoattach";
|
||||
const ATTACH_X := 148;
|
||||
const ATTACH_Y := 148;
|
||||
|
||||
program testuomenu()
|
||||
return 1;
|
||||
endprogram
|
||||
|
||||
function list_holds( list, value )
|
||||
foreach entry in list
|
||||
if ( entry == value )
|
||||
return 1;
|
||||
endif
|
||||
endforeach
|
||||
return 0;
|
||||
endfunction
|
||||
|
||||
// A dynamic menu is built up item by item and then read back. Its objtypes come
|
||||
// out in the order they went in, and a submenu-free menu contributes them all.
|
||||
exported function dynamic_menu_objtypes( unused resmngr )
|
||||
var menu := CreateMenu( "test menu" );
|
||||
if ( TypeOf( menu ) != "MenuRef" )
|
||||
return ret_error( $"expected a menu, got {TypeOf( menu )}: {menu}" );
|
||||
endif
|
||||
|
||||
if ( !AddMenuItem( menu, 0x0eed, "a gold coin", 0 ) )
|
||||
return ret_error( "could not add an item by objtype" );
|
||||
endif
|
||||
// An objtype may also be given by the name itemdesc knows it under.
|
||||
if ( !AddMenuItem( menu, "loadtest", "a container", 0x10 ) )
|
||||
return ret_error( "could not add an item by objtype name" );
|
||||
endif
|
||||
|
||||
var objtypes := GetMenuObjTypes( menu );
|
||||
if ( TypeOf( objtypes ) != "Array" )
|
||||
return ret_error( $"expected an array of objtypes, got {objtypes}" );
|
||||
endif
|
||||
if ( objtypes.size() != 2 )
|
||||
return ret_error( $"expected two objtypes, got {objtypes.size()}" );
|
||||
endif
|
||||
if ( !list_holds( objtypes, 0x0eed ) || !list_holds( objtypes, 0x200001 ) )
|
||||
return ret_error( $"the menu holds the wrong objtypes: {objtypes}" );
|
||||
endif
|
||||
|
||||
return 1;
|
||||
endfunction
|
||||
|
||||
exported function menu_rejects_bad_params( unused resmngr )
|
||||
var no_title := CreateMenu( 0 );
|
||||
if ( no_title )
|
||||
return ret_error( $"expected a falsy result for a non-string title, got {no_title}" );
|
||||
endif
|
||||
|
||||
var not_a_menu := AddMenuItem( 0, 0x0eed, "a gold coin", 0 );
|
||||
if ( not_a_menu )
|
||||
return ret_error( $"expected a falsy result adding to a non-menu, got {not_a_menu}" );
|
||||
endif
|
||||
|
||||
// The read-back form also accepts the name of a static menu, and no static
|
||||
// menus are configured on this shard.
|
||||
var unknown := GetMenuObjTypes( "nosuchmenu" );
|
||||
if ( unknown )
|
||||
return ret_error( $"expected a falsy result for an unknown menu, got {unknown}" );
|
||||
endif
|
||||
|
||||
return 1;
|
||||
endfunction
|
||||
|
||||
// Copying an array copies each element, which is the only route to the menu
|
||||
// object's own copy handler. The copy carries the menu's items with it.
|
||||
exported function menu_copies_by_value( unused resmngr )
|
||||
var menu := CreateMenu( "copy me" );
|
||||
if ( !AddMenuItem( menu, 0x0eed, "a gold coin", 0 ) )
|
||||
return ret_error( "could not add an item" );
|
||||
endif
|
||||
|
||||
var holder := { menu };
|
||||
var copied := holder;
|
||||
|
||||
if ( TypeOf( copied[1] ) != "MenuRef" )
|
||||
return ret_error( $"copied element is not a menu: {TypeOf( copied[1] )}" );
|
||||
endif
|
||||
if ( TypeOfInt( copied[1] ) != TypeOfInt( menu ) )
|
||||
return ret_error( "the copy has a different type integer" );
|
||||
endif
|
||||
if ( TypeOfInt( menu ) == TypeOfInt( holder ) )
|
||||
return ret_error( "a menu and an array share a type integer" );
|
||||
endif
|
||||
|
||||
var objtypes := GetMenuObjTypes( copied[1] );
|
||||
if ( objtypes.size() != 1 || objtypes[1] != 0x0eed )
|
||||
return ret_error( $"the copied menu lost its items: {objtypes}" );
|
||||
endif
|
||||
|
||||
return 1;
|
||||
endfunction
|
||||
|
||||
// ApplyConstraint keeps the objtypes a config file says are affordable. An
|
||||
// entry is dropped for any of four reasons: it is not an integer, the file has
|
||||
// no element for it, that element has no such property, or the property is not
|
||||
// a number.
|
||||
exported function apply_constraint_filters( unused resmngr )
|
||||
var cfg := ReadConfigFile( CONSTRAINT_CFG );
|
||||
if ( !cfg )
|
||||
return ret_error( $"could not read {CONSTRAINT_CFG}.cfg: {cfg}" );
|
||||
endif
|
||||
|
||||
var candidates := { CHEAP, DEAR, NO_AMOUNT, AMOUNT_IS_TEXT, 0x900099, "not an objtype" };
|
||||
|
||||
var affordable := ApplyConstraint( candidates, cfg, "Amount", 10 );
|
||||
if ( TypeOf( affordable ) != "Array" )
|
||||
return ret_error( $"expected an array, got {affordable}" );
|
||||
endif
|
||||
if ( affordable.size() != 1 || affordable[1] != CHEAP )
|
||||
return ret_error( $"expected only the cheap objtype, got {affordable}" );
|
||||
endif
|
||||
|
||||
var everything := ApplyConstraint( candidates, cfg, "Amount", 100 );
|
||||
if ( everything.size() != 2 )
|
||||
return ret_error( $"expected both priced objtypes, got {everything}" );
|
||||
endif
|
||||
if ( !list_holds( everything, CHEAP ) || !list_holds( everything, DEAR ) )
|
||||
return ret_error( $"the wrong objtypes were affordable: {everything}" );
|
||||
endif
|
||||
|
||||
var nothing := ApplyConstraint( candidates, cfg, "Amount", 4 );
|
||||
if ( nothing.size() )
|
||||
return ret_error( $"nothing should be affordable, got {nothing}" );
|
||||
endif
|
||||
|
||||
// A property no element carries as a number matches nobody.
|
||||
var by_name := ApplyConstraint( candidates, cfg, "Name", 100 );
|
||||
if ( by_name.size() )
|
||||
return ret_error( $"a non-numeric property should match nobody, got {by_name}" );
|
||||
endif
|
||||
|
||||
return 1;
|
||||
endfunction
|
||||
|
||||
exported function apply_constraint_bad_params( unused resmngr )
|
||||
var cfg := ReadConfigFile( CONSTRAINT_CFG );
|
||||
if ( !cfg )
|
||||
return ret_error( $"could not read {CONSTRAINT_CFG}.cfg: {cfg}" );
|
||||
endif
|
||||
|
||||
var not_a_list := ApplyConstraint( CHEAP, cfg, "Amount", 10 );
|
||||
if ( not_a_list )
|
||||
return ret_error( $"expected a falsy result for a non-array, got {not_a_list}" );
|
||||
endif
|
||||
|
||||
var not_a_cfg := ApplyConstraint( { CHEAP }, 0, "Amount", 10 );
|
||||
if ( not_a_cfg )
|
||||
return ret_error( $"expected a falsy result for a non-config-file, got {not_a_cfg}" );
|
||||
endif
|
||||
|
||||
var no_property := ApplyConstraint( { CHEAP }, cfg, 0, 10 );
|
||||
if ( no_property )
|
||||
return ret_error( $"expected a falsy result for a non-string property, got {no_property}" );
|
||||
endif
|
||||
|
||||
return 1;
|
||||
endfunction
|
||||
|
||||
// A character can be attached to one script at a time, and a script can hold
|
||||
// one character at a time. Both halves of that are refusals rather than
|
||||
// silent replacements.
|
||||
exported function attach_and_detach( resmngr )
|
||||
var npc := resmngr.CreateNPCFromTemplate( ":TestNPC:probe_npc", ATTACH_X, ATTACH_Y, 0, 0,
|
||||
_DEFAULT_REALM, 1 );
|
||||
if ( !npc )
|
||||
return ret_error( $"failed to create npc: {npc}" );
|
||||
endif
|
||||
var other := resmngr.CreateNPCFromTemplate( ":TestNPC:probe_npc", ATTACH_X + 1, ATTACH_Y, 0, 0,
|
||||
_DEFAULT_REALM, 1 );
|
||||
if ( !other )
|
||||
return ret_error( $"failed to create second npc: {other}" );
|
||||
endif
|
||||
|
||||
if ( Detach() )
|
||||
return ret_error( "detaching with nothing attached should answer 0" );
|
||||
endif
|
||||
|
||||
var attached := Attach( npc );
|
||||
if ( !attached )
|
||||
return ret_error( $"could not attach the npc: {attached}" );
|
||||
endif
|
||||
|
||||
var res := 1;
|
||||
var second := Attach( other );
|
||||
if ( second )
|
||||
res := ret_error( $"attaching a second character should be refused, got {second}" );
|
||||
endif
|
||||
|
||||
if ( res )
|
||||
var bad := Attach( 0 );
|
||||
if ( bad )
|
||||
res := ret_error( $"expected an error for a non-character, got {bad}" );
|
||||
endif
|
||||
endif
|
||||
|
||||
if ( !Detach() )
|
||||
res := ret_error( "detaching an attached character should answer 1" );
|
||||
endif
|
||||
|
||||
return res;
|
||||
endfunction
|
||||
|
||||
// The other refusal needs a second script, because the character remembers
|
||||
// which script holds it rather than merely that it is held.
|
||||
exported function attach_refuses_other_script( resmngr )
|
||||
var npc := resmngr.CreateNPCFromTemplate( ":TestNPC:probe_npc", ATTACH_X + 2, ATTACH_Y, 0, 0,
|
||||
_DEFAULT_REALM, 1 );
|
||||
if ( !npc )
|
||||
return ret_error( $"failed to create npc: {npc}" );
|
||||
endif
|
||||
|
||||
var helper := resmngr.Start_Script( ATTACH_SCRIPT, struct{ script := GetProcess(), chr := npc } );
|
||||
if ( !helper )
|
||||
return ret_error( $"could not start {ATTACH_SCRIPT}: {helper}" );
|
||||
endif
|
||||
|
||||
var ready := Wait_For_Event( 5 );
|
||||
if ( !ready )
|
||||
return ret_error( "the helper script never reported back" );
|
||||
endif
|
||||
if ( !ready.attached )
|
||||
return ret_error( $"the helper script could not attach: {ready.attached}" );
|
||||
endif
|
||||
|
||||
var refused := Attach( npc );
|
||||
if ( refused )
|
||||
return ret_error( $"attaching a held character should be refused, got {refused}" );
|
||||
endif
|
||||
|
||||
return 1;
|
||||
endfunction
|
||||
406
testsuite/pol/testpkgs/misc/test_uoregion.src
Normal file
406
testsuite/pol/testpkgs/misc/test_uoregion.src
Normal file
|
|
@ -0,0 +1,406 @@
|
|||
use uo;
|
||||
use os;
|
||||
|
||||
include "testutil";
|
||||
|
||||
// The patch of map painted as the TestJustice region by
|
||||
// testsuite/pol/regions/justice.cfg. It is grass at z 0 and nothing else on the
|
||||
// shard stands here.
|
||||
const JR_X := 134;
|
||||
const JR_Y := 134;
|
||||
const JR_NAME := "TestJustice";
|
||||
|
||||
// Well outside it, where the background region the core builds for itself
|
||||
// applies. That one comes from an empty config element, so its name is the
|
||||
// empty string rather than an error.
|
||||
const BG_X := 90;
|
||||
const BG_Y := 90;
|
||||
|
||||
// The house multis uoconvert generates for the test shard.
|
||||
const MULTIID_HOUSE := 0x6c;
|
||||
const MULTIID_MISSING := 0xfff0;
|
||||
|
||||
// The light regions from testsuite/pol/regions/light.cfg. TestLightDark covers
|
||||
// 108..115 in both axes; the rest of the shard is TestLightBase.
|
||||
const LIGHT_REGION := "TestLightDark";
|
||||
const LIGHT_X := 110;
|
||||
const LIGHT_Y := 110;
|
||||
const LIGHT_DARK := 25;
|
||||
const LIGHT_BASE := 10;
|
||||
const LIGHT_MAX := 30;
|
||||
|
||||
// The weather region from testsuite/pol/regions/weather.cfg, and the patch of
|
||||
// open water next to it that the assignment case moves about. Neither is
|
||||
// readable back from a script -- nothing in uo.em reports a weather region's
|
||||
// type or which region covers a location -- so these cases can only check the
|
||||
// answers the two setters give.
|
||||
const WEATHER_REGION := "TestWeatherStorm";
|
||||
const WEATHER_TYPE_NONE := 255;
|
||||
const WEATHER_RECT_X := 184;
|
||||
const WEATHER_RECT_Y := 184;
|
||||
|
||||
program testuoregion()
|
||||
return 1;
|
||||
endprogram
|
||||
|
||||
function region_pc()
|
||||
var pc := createAccountWithChar( "testuoregion", "pass" );
|
||||
if ( !pc )
|
||||
return pc;
|
||||
endif
|
||||
var moved := MoveObjectToLocation( pc, JR_X, JR_Y + 1, 0, flags := MOVEOBJECT_FORCELOCATION );
|
||||
if ( !moved )
|
||||
return ret_error( $"could not move the region pc: {moved}" );
|
||||
endif
|
||||
return pc;
|
||||
endfunction
|
||||
|
||||
// GetRegionName reaches the justice region three different ways depending on
|
||||
// what it is handed, because UObject::isa is exact class equality: an item is
|
||||
// resolved through its top level owner, a player character through its own
|
||||
// position (or its client's cached region while logged in), and anything else --
|
||||
// an NPC is CLASS_NPC, a container is CLASS_CONTAINER -- straight from where it
|
||||
// stands.
|
||||
exported function region_name_of_objects( resmngr )
|
||||
var npc := resmngr.CreateNPCFromTemplate( ":TestNPC:probe_npc", JR_X, JR_Y, 0, 0, _DEFAULT_REALM,
|
||||
1 );
|
||||
if ( !npc )
|
||||
return ret_error( $"failed to create npc: {npc}" );
|
||||
endif
|
||||
var npc_region := GetRegionName( npc );
|
||||
if ( npc_region != JR_NAME )
|
||||
return ret_error( $"npc region: expected {JR_NAME}, got {npc_region}" );
|
||||
endif
|
||||
|
||||
// An item inside a container answers for the container, which is the top
|
||||
// level owner.
|
||||
var cont := resmngr.CreateItemAtLocation( JR_X + 1, JR_Y, 0, 0x200001 );
|
||||
if ( !cont )
|
||||
return ret_error( $"failed to create container: {cont}" );
|
||||
endif
|
||||
var coin := resmngr.CreateItemInContainer( cont, 0x0eed, 1 );
|
||||
if ( !coin )
|
||||
return ret_error( $"failed to create coin: {coin}" );
|
||||
endif
|
||||
var coin_region := GetRegionName( coin );
|
||||
if ( coin_region != JR_NAME )
|
||||
return ret_error( $"contained item region: expected {JR_NAME}, got {coin_region}" );
|
||||
endif
|
||||
|
||||
var pc := region_pc();
|
||||
if ( !pc )
|
||||
return pc;
|
||||
endif
|
||||
var pc_region := GetRegionName( pc );
|
||||
if ( pc_region != JR_NAME )
|
||||
return ret_error( $"pc region: expected {JR_NAME}, got {pc_region}" );
|
||||
endif
|
||||
|
||||
var bad := GetRegionName( 0 );
|
||||
if ( bad )
|
||||
return ret_error( $"expected an error for a non-object, got {bad}" );
|
||||
endif
|
||||
|
||||
return 1;
|
||||
endfunction
|
||||
|
||||
// The by-location form needs no object at all, and answers the background
|
||||
// region's empty name outside the painted range.
|
||||
exported function region_name_at_location( unused resmngr )
|
||||
var inside := GetRegionNameAtLocation( JR_X, JR_Y );
|
||||
if ( inside != JR_NAME )
|
||||
return ret_error( $"inside the region: expected {JR_NAME}, got {inside}" );
|
||||
endif
|
||||
|
||||
var outside := GetRegionNameAtLocation( BG_X, BG_Y );
|
||||
if ( TypeOf( outside ) != "String" )
|
||||
return ret_error( $"outside the region: expected a string, got {TypeOf( outside )}" );
|
||||
endif
|
||||
if ( outside != "" )
|
||||
return ret_error( $"the background region should be unnamed, got \"{outside}\"" );
|
||||
endif
|
||||
|
||||
var bad_coord := GetRegionNameAtLocation( "not a number", JR_Y );
|
||||
if ( bad_coord )
|
||||
return ret_error( $"expected an error for a non-numeric x, got {bad_coord}" );
|
||||
endif
|
||||
|
||||
var bad_realm := GetRegionNameAtLocation( JR_X, JR_Y, "nosuchrealm" );
|
||||
if ( bad_realm )
|
||||
return ret_error( $"expected an error for an unknown realm, got {bad_realm}" );
|
||||
endif
|
||||
|
||||
return 1;
|
||||
endfunction
|
||||
|
||||
// The light level comes from whichever light region covers the location, and
|
||||
// the two on this shard hold different levels.
|
||||
exported function region_light_level( unused resmngr )
|
||||
var dark := GetRegionLightLevelAtLocation( LIGHT_X, LIGHT_Y );
|
||||
if ( TypeOf( dark ) != "Integer" )
|
||||
return ret_error( $"expected an integer light level, got {TypeOf( dark )}" );
|
||||
endif
|
||||
if ( dark != LIGHT_DARK )
|
||||
return ret_error( $"inside {LIGHT_REGION}: expected {LIGHT_DARK}, got {dark}" );
|
||||
endif
|
||||
|
||||
var base := GetRegionLightLevelAtLocation( BG_X, BG_Y );
|
||||
if ( base != LIGHT_BASE )
|
||||
return ret_error( $"outside {LIGHT_REGION}: expected {LIGHT_BASE}, got {base}" );
|
||||
endif
|
||||
|
||||
// The realm is consulted as well as the coordinates, and the other realm has
|
||||
// no dark patch at all.
|
||||
var other_realm := GetRegionLightLevelAtLocation( LIGHT_X, LIGHT_Y, "britannia2" );
|
||||
if ( other_realm != LIGHT_BASE )
|
||||
return ret_error( $"the same spot in britannia2: expected {LIGHT_BASE}, got {other_realm}" );
|
||||
endif
|
||||
|
||||
var bad_realm := GetRegionLightLevelAtLocation( LIGHT_X, LIGHT_Y, "nosuchrealm" );
|
||||
if ( bad_realm )
|
||||
return ret_error( $"expected an error for an unknown realm, got {bad_realm}" );
|
||||
endif
|
||||
|
||||
return 1;
|
||||
endfunction
|
||||
|
||||
// A light region is set by name rather than by location, and the change is
|
||||
// visible to the by-location read straight away. Only the reserved dark patch
|
||||
// is ever written to -- the base regions cover the whole shard, so moving one
|
||||
// of those would change the light for every client on it.
|
||||
exported function set_region_light_level( unused resmngr )
|
||||
var res := 1;
|
||||
|
||||
if ( !SetRegionLightLevel( LIGHT_REGION, LIGHT_BASE ) )
|
||||
return ret_error( $"could not set the light level of {LIGHT_REGION}" );
|
||||
endif
|
||||
var changed := GetRegionLightLevelAtLocation( LIGHT_X, LIGHT_Y );
|
||||
if ( changed != LIGHT_BASE )
|
||||
res := ret_error( $"after the change: expected {LIGHT_BASE}, got {changed}" );
|
||||
endif
|
||||
|
||||
// Both ends of the accepted range are fine; one past the top is not, and a
|
||||
// negative level is read as unsigned so it is out of range too.
|
||||
if ( res )
|
||||
if ( !SetRegionLightLevel( LIGHT_REGION, 0 ) )
|
||||
res := ret_error( "a light level of 0 should be accepted" );
|
||||
elseif ( !SetRegionLightLevel( LIGHT_REGION, LIGHT_MAX ) )
|
||||
res := ret_error( $"a light level of {LIGHT_MAX} should be accepted" );
|
||||
endif
|
||||
endif
|
||||
|
||||
if ( res )
|
||||
var too_bright := SetRegionLightLevel( LIGHT_REGION, LIGHT_MAX + 1 );
|
||||
if ( too_bright )
|
||||
res := ret_error( $"a light level past {LIGHT_MAX} should be refused, got {too_bright}" );
|
||||
endif
|
||||
endif
|
||||
|
||||
if ( res )
|
||||
var negative := SetRegionLightLevel( LIGHT_REGION, -1 );
|
||||
if ( negative )
|
||||
res := ret_error( $"a negative light level should be refused, got {negative}" );
|
||||
endif
|
||||
endif
|
||||
|
||||
if ( res )
|
||||
var unknown := SetRegionLightLevel( "nosuchlightregion", LIGHT_BASE );
|
||||
if ( unknown )
|
||||
res := ret_error( $"expected an error for an unknown region, got {unknown}" );
|
||||
endif
|
||||
endif
|
||||
|
||||
if ( res )
|
||||
var bad := SetRegionLightLevel( 0, LIGHT_BASE );
|
||||
if ( bad )
|
||||
res := ret_error( $"expected an error for a non-string region name, got {bad}" );
|
||||
endif
|
||||
endif
|
||||
|
||||
var restored := SetRegionLightLevel( LIGHT_REGION, LIGHT_DARK );
|
||||
if ( !restored )
|
||||
return ret_error( $"could not restore the light level of {LIGHT_REGION}: {restored}" );
|
||||
endif
|
||||
|
||||
return res;
|
||||
endfunction
|
||||
|
||||
// Multi dimensions come from the multi definition rather than from any placed
|
||||
// multi, so no house has to exist to ask for them.
|
||||
exported function multi_dimensions( unused resmngr )
|
||||
var dims := GetMultiDimensions( MULTIID_HOUSE );
|
||||
if ( TypeOf( dims ) != "Struct" )
|
||||
return ret_error( $"expected a struct, got {TypeOf( dims )}: {dims}" );
|
||||
endif
|
||||
if ( dims.xmin > dims.xmax )
|
||||
return ret_error( $"xmin {dims.xmin} is past xmax {dims.xmax}" );
|
||||
endif
|
||||
if ( dims.ymin > dims.ymax )
|
||||
return ret_error( $"ymin {dims.ymin} is past ymax {dims.ymax}" );
|
||||
endif
|
||||
|
||||
var missing := GetMultiDimensions( MULTIID_MISSING );
|
||||
if ( missing )
|
||||
return ret_error( $"expected an error for an undefined multi, got {missing}" );
|
||||
endif
|
||||
|
||||
var bad := GetMultiDimensions( "0x6c" );
|
||||
if ( bad )
|
||||
return ret_error( $"expected an error for a non-integer multi id, got {bad}" );
|
||||
endif
|
||||
|
||||
return 1;
|
||||
endfunction
|
||||
|
||||
// Command help is a file lookup down the character's own command level and
|
||||
// every level below it, so the same command answers differently as the level
|
||||
// changes. The shard has two levels, Player and Test, and only the Test one has
|
||||
// a search directory.
|
||||
exported function command_help( unused resmngr )
|
||||
var pc := region_pc();
|
||||
if ( !pc )
|
||||
return pc;
|
||||
endif
|
||||
|
||||
var res := 1;
|
||||
pc.cmdlevel := 0;
|
||||
var unprivileged := GetCommandHelp( pc, "dummycmd" );
|
||||
if ( unprivileged )
|
||||
res := ret_error( $"a player level character should find no help, got {unprivileged}" );
|
||||
endif
|
||||
|
||||
if ( res )
|
||||
pc.cmdlevel := 1;
|
||||
var help := GetCommandHelp( pc, "dummycmd" );
|
||||
if ( TypeOf( help ) != "String" )
|
||||
res := ret_error( $"expected the help text, got {TypeOf( help )}: {help}" );
|
||||
elseif ( !help.find( "dummycmd" ) )
|
||||
res := ret_error( $"the help text is not the fixture's: \"{help}\"" );
|
||||
endif
|
||||
endif
|
||||
|
||||
if ( res )
|
||||
// Reserved device names are refused before any directory is searched.
|
||||
var device := GetCommandHelp( pc, "aux" );
|
||||
if ( device )
|
||||
res := ret_error( $"expected an error for a reserved device name, got {device}" );
|
||||
endif
|
||||
endif
|
||||
|
||||
if ( res )
|
||||
var missing := GetCommandHelp( pc, "nosuchcommand" );
|
||||
if ( missing )
|
||||
res := ret_error( $"expected an error for an unknown command, got {missing}" );
|
||||
endif
|
||||
endif
|
||||
|
||||
if ( res )
|
||||
var bad := GetCommandHelp( 0, "dummycmd" );
|
||||
if ( bad )
|
||||
res := ret_error( $"expected an error for a non-character, got {bad}" );
|
||||
endif
|
||||
endif
|
||||
|
||||
pc.cmdlevel := 0;
|
||||
return res;
|
||||
endfunction
|
||||
|
||||
// A weather region is set by name, and the four levels it carries are written
|
||||
// as one. Nothing reads them back from a script, so what is checked here is
|
||||
// which calls are accepted and which are refused; showing the weather actually
|
||||
// reaching somebody needs a logged-in client.
|
||||
//
|
||||
// The region is put back to "no weather" at the end. TestWeatherStorm is out in
|
||||
// open water where nobody stands, but a client inside a weather region is sent
|
||||
// a weather packet whenever the region changes underneath it.
|
||||
exported function set_region_weather_level( unused resmngr )
|
||||
var res := 1;
|
||||
|
||||
// aux and lightoverride have defaults, so both arities are worth exercising.
|
||||
if ( !SetRegionWeatherLevel( WEATHER_REGION, 2, 30 ) )
|
||||
return ret_error( $"could not set the weather level of {WEATHER_REGION}" );
|
||||
endif
|
||||
if ( !SetRegionWeatherLevel( WEATHER_REGION, 2, 30, 5, LIGHT_BASE ) )
|
||||
res := ret_error( "the five parameter form should be accepted" );
|
||||
endif
|
||||
|
||||
if ( res )
|
||||
var unknown := SetRegionWeatherLevel( "nosuchweatherregion", 2, 30 );
|
||||
if ( unknown )
|
||||
res := ret_error( $"expected an error for an unknown region, got {unknown}" );
|
||||
endif
|
||||
endif
|
||||
|
||||
if ( res )
|
||||
var bad_name := SetRegionWeatherLevel( 0, 2, 30 );
|
||||
if ( bad_name )
|
||||
res := ret_error( $"expected an error for a non-string region name, got {bad_name}" );
|
||||
endif
|
||||
endif
|
||||
|
||||
if ( res )
|
||||
var bad_type := SetRegionWeatherLevel( WEATHER_REGION, "rain", 30 );
|
||||
if ( bad_type )
|
||||
res := ret_error( $"expected an error for a non-numeric type, got {bad_type}" );
|
||||
endif
|
||||
endif
|
||||
|
||||
var restored := SetRegionWeatherLevel( WEATHER_REGION, WEATHER_TYPE_NONE, 0, 0, -1 );
|
||||
if ( !restored )
|
||||
return ret_error( $"could not restore {WEATHER_REGION}: {restored}" );
|
||||
endif
|
||||
|
||||
return res;
|
||||
endfunction
|
||||
|
||||
// Weather regions are the one region group whose painted zones can be moved at
|
||||
// runtime. An empty region name is not an error but the undo: it puts the rect
|
||||
// back to whichever region covered it when the shard loaded.
|
||||
exported function assign_rect_to_weather( unused resmngr )
|
||||
var assigned := AssignRectToWeatherRegion( WEATHER_REGION, WEATHER_RECT_X, WEATHER_RECT_Y,
|
||||
WEATHER_RECT_X + 3, WEATHER_RECT_Y + 3 );
|
||||
if ( !assigned )
|
||||
return ret_error( $"could not assign the rect to {WEATHER_REGION}: {assigned}" );
|
||||
endif
|
||||
|
||||
var res := 1;
|
||||
var unknown := AssignRectToWeatherRegion( "nosuchweatherregion", WEATHER_RECT_X, WEATHER_RECT_Y,
|
||||
WEATHER_RECT_X + 3, WEATHER_RECT_Y + 3 );
|
||||
if ( unknown )
|
||||
res := ret_error( $"expected an error for an unknown region, got {unknown}" );
|
||||
endif
|
||||
|
||||
if ( res )
|
||||
var bad_name := AssignRectToWeatherRegion( 0, WEATHER_RECT_X, WEATHER_RECT_Y, WEATHER_RECT_X + 3,
|
||||
WEATHER_RECT_Y + 3 );
|
||||
if ( bad_name )
|
||||
res := ret_error( $"expected an error for a non-string region name, got {bad_name}" );
|
||||
endif
|
||||
endif
|
||||
|
||||
if ( res )
|
||||
var off_map := AssignRectToWeatherRegion( WEATHER_REGION, WEATHER_RECT_X, WEATHER_RECT_Y, 500,
|
||||
500 );
|
||||
if ( off_map )
|
||||
res := ret_error( $"expected an error for a rect off the map, got {off_map}" );
|
||||
endif
|
||||
endif
|
||||
|
||||
if ( res )
|
||||
var bad_realm := AssignRectToWeatherRegion( WEATHER_REGION, WEATHER_RECT_X, WEATHER_RECT_Y,
|
||||
WEATHER_RECT_X + 3, WEATHER_RECT_Y + 3,
|
||||
"nosuchrealm" );
|
||||
if ( bad_realm )
|
||||
res := ret_error( $"expected an error for an unknown realm, got {bad_realm}" );
|
||||
endif
|
||||
endif
|
||||
|
||||
// The empty name is the documented way back to the load time layout.
|
||||
var restored := AssignRectToWeatherRegion( "", WEATHER_RECT_X, WEATHER_RECT_Y, WEATHER_RECT_X + 3,
|
||||
WEATHER_RECT_Y + 3 );
|
||||
if ( !restored )
|
||||
return ret_error( $"could not put the rect back: {restored}" );
|
||||
endif
|
||||
|
||||
return res;
|
||||
endfunction
|
||||
237
testsuite/pol/testpkgs/misc/test_uowalk.src
Normal file
237
testsuite/pol/testpkgs/misc/test_uowalk.src
Normal file
|
|
@ -0,0 +1,237 @@
|
|||
use uo;
|
||||
use os;
|
||||
use vitals;
|
||||
|
||||
include "testutil";
|
||||
|
||||
// The generated test map is the whole terrain the shard has: grass at z 0
|
||||
// everywhere inside 28..163, water at z -5 outside it. Grass is MoveLand only
|
||||
// and water is MoveSea only, so a movemode decides which of the two a query can
|
||||
// stand on. See FileGenerator::modifyMap in pol-core/poltool/testfiles.cpp.
|
||||
const LAND_X := 140;
|
||||
const LAND_Y := 140;
|
||||
const LAND_Z := 0;
|
||||
|
||||
const SEA_X := 10;
|
||||
const SEA_Y := 100;
|
||||
const SEA_Z := -5;
|
||||
|
||||
// uo.em declares no names for the eight facings, so these are spelled out here.
|
||||
// An odd one is diagonal, which CanWalk treats differently.
|
||||
const DIR_NORTH := 0;
|
||||
const DIR_NORTHEAST := 1;
|
||||
|
||||
// Off the edge of the 192x192 realm.
|
||||
const OFF_X := 500;
|
||||
const OFF_Y := 500;
|
||||
|
||||
program testuowalk()
|
||||
return 1;
|
||||
endprogram
|
||||
|
||||
// A direction is a single step from the starting point, and the answer is the z
|
||||
// the walker would end up at rather than a plain yes. That z is 0 on this map,
|
||||
// which is falsy, so the result has to be told apart from an error by its type.
|
||||
exported function can_walk_on_land( unused resmngr )
|
||||
var north := CanWalk( "L", LAND_X, LAND_Y, LAND_Z, DIR_NORTH );
|
||||
if ( TypeOf( north ) != "Integer" )
|
||||
return ret_error( $"walking north: expected an integer z, got {north}" );
|
||||
endif
|
||||
if ( north != LAND_Z )
|
||||
return ret_error( $"walking north: expected z {LAND_Z}, got {north}" );
|
||||
endif
|
||||
|
||||
// An odd facing is diagonal, which is only allowed if at least one of the two
|
||||
// orthogonals beside it is walkable too.
|
||||
var northeast := CanWalk( "L", LAND_X, LAND_Y, LAND_Z, DIR_NORTHEAST );
|
||||
if ( TypeOf( northeast ) != "Integer" )
|
||||
return ret_error( $"walking northeast: expected an integer z, got {northeast}" );
|
||||
endif
|
||||
|
||||
return 1;
|
||||
endfunction
|
||||
|
||||
// Given a second pair of coordinates instead of a facing, the direction is
|
||||
// worked out from the two positions -- and that pair has to be on the map.
|
||||
exported function can_walk_to_coordinates( unused resmngr )
|
||||
var east := CanWalk( "L", LAND_X, LAND_Y, LAND_Z, LAND_X + 1, LAND_Y );
|
||||
if ( TypeOf( east ) != "Integer" )
|
||||
return ret_error( $"walking east: expected an integer z, got {east}" );
|
||||
endif
|
||||
if ( east != LAND_Z )
|
||||
return ret_error( $"walking east: expected z {LAND_Z}, got {east}" );
|
||||
endif
|
||||
|
||||
var off_map := CanWalk( "L", LAND_X, LAND_Y, LAND_Z, OFF_X, OFF_Y );
|
||||
if ( off_map )
|
||||
return ret_error( $"expected an error for a target off the map, got {off_map}" );
|
||||
endif
|
||||
|
||||
return 1;
|
||||
endfunction
|
||||
|
||||
// Land and sea are exclusive on this map, so each movemode walks on exactly one
|
||||
// of the two.
|
||||
exported function can_walk_movemode_matters( unused resmngr )
|
||||
var sea_on_land := CanWalk( "S", LAND_X, LAND_Y, LAND_Z, DIR_NORTH );
|
||||
if ( sea_on_land )
|
||||
return ret_error( $"sea movemode should not walk on grass, got {sea_on_land}" );
|
||||
endif
|
||||
|
||||
var sea_on_water := CanWalk( "S", SEA_X, SEA_Y, SEA_Z, DIR_NORTH );
|
||||
if ( TypeOf( sea_on_water ) != "Integer" )
|
||||
return ret_error( $"sea movemode on water: expected an integer z, got {sea_on_water}" );
|
||||
endif
|
||||
if ( sea_on_water != SEA_Z )
|
||||
return ret_error( $"sea movemode on water: expected z {SEA_Z}, got {sea_on_water}" );
|
||||
endif
|
||||
|
||||
var land_on_water := CanWalk( "L", SEA_X, SEA_Y, SEA_Z, DIR_NORTH );
|
||||
if ( land_on_water )
|
||||
return ret_error( $"land movemode should not walk on water, got {land_on_water}" );
|
||||
endif
|
||||
|
||||
var bad := CanWalk( 5, LAND_X, LAND_Y, LAND_Z, DIR_NORTH );
|
||||
if ( bad )
|
||||
return ret_error( $"expected an error for a non-string movemode, got {bad}" );
|
||||
endif
|
||||
|
||||
return 1;
|
||||
endfunction
|
||||
|
||||
// A path is the steps after the starting point, so it ends on the goal.
|
||||
exported function find_path_across_land( unused resmngr )
|
||||
var path := FindPath( LAND_X, LAND_Y, LAND_Z, LAND_X + 4, LAND_Y + 4, LAND_Z );
|
||||
if ( TypeOf( path ) != "Array" )
|
||||
return ret_error( $"expected an array of steps, got {path}" );
|
||||
endif
|
||||
if ( !path.size() )
|
||||
return ret_error( "the path across open grass is empty" );
|
||||
endif
|
||||
|
||||
var last := path[path.size()];
|
||||
if ( last.x != LAND_X + 4 || last.y != LAND_Y + 4 )
|
||||
return ret_error( $"the path ends at {last.x},{last.y}, not at the goal" );
|
||||
endif
|
||||
|
||||
// A negative search skirt is clamped to nothing rather than refused, and
|
||||
// ignoring doors takes the other branch of the blocker setup.
|
||||
var skirtless := FindPath( LAND_X, LAND_Y, LAND_Z, LAND_X + 2, LAND_Y + 2, LAND_Z, _DEFAULT_REALM,
|
||||
FP_IGNORE_DOORS, -1 );
|
||||
if ( TypeOf( skirtless ) != "Array" )
|
||||
return ret_error( $"expected an array with a negative skirt, got {skirtless}" );
|
||||
endif
|
||||
|
||||
return 1;
|
||||
endfunction
|
||||
|
||||
exported function find_path_errors( unused resmngr )
|
||||
// The range limit is checked before the coordinates are, so it wins even for
|
||||
// two perfectly good locations.
|
||||
var too_far := FindPath( 40, 40, 0, 120, 120, 0 );
|
||||
if ( too_far )
|
||||
return ret_error( $"expected a range error, got {too_far}" );
|
||||
endif
|
||||
|
||||
var no_movemode := FindPath( LAND_X, LAND_Y, LAND_Z, LAND_X + 2, LAND_Y, LAND_Z, _DEFAULT_REALM,
|
||||
FP_IGNORE_MOBILES, 5, "" );
|
||||
if ( no_movemode )
|
||||
return ret_error( $"expected a movemode error, got {no_movemode}" );
|
||||
endif
|
||||
|
||||
var bad_start := FindPath( OFF_X, 40, 0, OFF_X + 5, 45, 0 );
|
||||
if ( bad_start )
|
||||
return ret_error( $"expected a start coordinate error, got {bad_start}" );
|
||||
endif
|
||||
|
||||
var bad_end := FindPath( 188, 188, 0, 193, 191, 0 );
|
||||
if ( bad_end )
|
||||
return ret_error( $"expected an end coordinate error, got {bad_end}" );
|
||||
endif
|
||||
|
||||
// Grass and water are not connected for a land walker, so this one gets past
|
||||
// every precondition and the search itself runs to exhaustion. The two ends
|
||||
// are close enough together to stay inside the range limit.
|
||||
var unreachable := FindPath( SEA_X + 20, SEA_Y, 0, SEA_X + 10, SEA_Y, SEA_Z );
|
||||
if ( unreachable )
|
||||
return ret_error( $"expected no path from grass to water, got {unreachable}" );
|
||||
endif
|
||||
|
||||
var bad_param := FindPath( "here", 40, 0, 45, 45, 0 );
|
||||
if ( bad_param )
|
||||
return ret_error( $"expected an error for a non-numeric coordinate, got {bad_param}" );
|
||||
endif
|
||||
|
||||
return 1;
|
||||
endfunction
|
||||
|
||||
function ghost_listed( list, serial )
|
||||
foreach entry in list
|
||||
if ( entry.serial == serial )
|
||||
return 1;
|
||||
endif
|
||||
endforeach
|
||||
return 0;
|
||||
endfunction
|
||||
|
||||
// A ghost is a dead player character, and this listing walks the world zones to
|
||||
// find one. A player character is only ever filed in a zone when it logs in --
|
||||
// create.cpp and pol.cpp register one on PlayerEnter, and loading it from
|
||||
// pcs.txt only bumps a counter (uimport.cpp) -- so an offline character is in no
|
||||
// zone at all and no location query can reach it, alive or dead. That is why
|
||||
// ListOfflineMobilesInRealm exists as a separate scan of the object hash.
|
||||
//
|
||||
// The listing therefore cannot be shown finding anybody without a logged-in
|
||||
// client, which belongs to the client tests. What is checked here is the shape
|
||||
// of the answer and that an offline character stays out of it even once it is
|
||||
// dead: this shard works a hit point maximum out through a hook that only runs
|
||||
// for a mobile that is online, so an offline one has a maximum of zero and a
|
||||
// single point of raw damage takes it all the way to dead.
|
||||
exported function list_ghosts_near_location( unused resmngr )
|
||||
var pc := createAccountWithChar( "testuowalk", "pass" );
|
||||
if ( !pc )
|
||||
return pc;
|
||||
endif
|
||||
|
||||
var alive := ListGhostsNearLocation( pc.x, pc.y, pc.z, 2 );
|
||||
if ( TypeOf( alive ) != "Array" )
|
||||
return ret_error( $"expected an array, got {alive}" );
|
||||
endif
|
||||
if ( ghost_listed( alive, pc.serial ) )
|
||||
return ret_error( "a living character was listed as a ghost" );
|
||||
endif
|
||||
|
||||
var damaged := ApplyRawDamage( pc, 1, DAMAGE_NO_REPSYS, DAMAGE_NO_SHOW );
|
||||
if ( damaged == error )
|
||||
return ret_error( $"could not damage the ghost pc: {damaged}" );
|
||||
endif
|
||||
if ( !pc.dead )
|
||||
return ret_error( "one point of raw damage did not kill an offline character" );
|
||||
endif
|
||||
|
||||
var res := 1;
|
||||
var ghosts := ListGhostsNearLocation( pc.x, pc.y, pc.z, 2 );
|
||||
if ( TypeOf( ghosts ) != "Array" )
|
||||
res := ret_error( $"expected an array, got {ghosts}" );
|
||||
elseif ( ghost_listed( ghosts, pc.serial ) )
|
||||
res := ret_error( "an offline character is in no zone and cannot be listed" );
|
||||
endif
|
||||
|
||||
if ( res )
|
||||
var bad := ListGhostsNearLocation( "not a number", pc.y, pc.z, 2 );
|
||||
if ( bad )
|
||||
res := ret_error( $"expected an error for a non-numeric x, got {bad}" );
|
||||
endif
|
||||
endif
|
||||
|
||||
var revived := Resurrect( pc, RESURRECT_FORCELOCATION );
|
||||
if ( !revived )
|
||||
return ret_error( $"could not resurrect the ghost pc: {revived}" );
|
||||
endif
|
||||
var corpse := pc.getcorpse();
|
||||
if ( corpse )
|
||||
DestroyItem( corpse );
|
||||
endif
|
||||
|
||||
return res;
|
||||
endfunction
|
||||
1
testsuite/pol/testpkgs/misc/textcmd/test/dummycmd.txt
Normal file
1
testsuite/pol/testpkgs/misc/textcmd/test/dummycmd.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
dummycmd: the help text GetCommandHelp reads back.
|
||||
17
testsuite/pol/testpkgs/misc/uoattach.src
Normal file
17
testsuite/pol/testpkgs/misc/uoattach.src
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
use os;
|
||||
use uo;
|
||||
|
||||
// Holds a character attached on behalf of test_uomenu.src, so that the caller
|
||||
// can watch Attach refuse a character another script already owns. Attach is
|
||||
// per script, so that refusal can only be reached from a second one.
|
||||
//
|
||||
// The caller's resource manager kills this script at the end of the case; the
|
||||
// timeout is only there so a failing run cannot leave it behind.
|
||||
program uoattach( args )
|
||||
var { script, chr } := args;
|
||||
|
||||
script.sendevent( struct{ attached := Attach( chr ) } );
|
||||
|
||||
Wait_For_Event( 30 );
|
||||
return 1;
|
||||
endprogram
|
||||
34
testsuite/pol/testpkgs/misc/uoconstraint.cfg
Normal file
34
testsuite/pol/testpkgs/misc/uoconstraint.cfg
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
#############################################################################
|
||||
## ApplyConstraint fixture
|
||||
#############################################################################
|
||||
#
|
||||
# Read by testpkgs/misc/test_uomenu.src. ApplyConstraint looks each objtype in
|
||||
# the list it is given up as an *integer* key in a config file, so the element
|
||||
# names here are numbers; the leading 0x is honoured by strtoul.
|
||||
#
|
||||
# The objtypes are made up. ApplyConstraint never consults itemdesc -- it only
|
||||
# matches the numbers it is handed against the keys below -- so nothing here
|
||||
# has to be a real item.
|
||||
#
|
||||
|
||||
Ingredient 0x900001
|
||||
{
|
||||
Amount 5
|
||||
}
|
||||
|
||||
Ingredient 0x900002
|
||||
{
|
||||
Amount 20
|
||||
}
|
||||
|
||||
// No Amount at all: skipped whatever the caller asks for.
|
||||
Ingredient 0x900003
|
||||
{
|
||||
Name nothing
|
||||
}
|
||||
|
||||
// Amount is a string, not an integer, so the value comparison never happens.
|
||||
Ingredient 0x900004
|
||||
{
|
||||
Amount plenty
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue