Pol  Revision:cb584c9
uoexhelp.cpp
Go to the documentation of this file.
1 
9 #include "uoexhelp.h"
10 
11 #include <ctype.h>
12 #include <exception>
13 #include <stdlib.h>
14 #include <string>
15 
16 #include "../bscript/berror.h"
17 #include "../bscript/bobject.h"
18 #include "../bscript/executor.h"
19 #include "../bscript/fmodule.h"
20 #include "../bscript/impstr.h"
21 #include "../clib/logfacility.h"
22 #include "../clib/strutil.h"
23 #include "../plib/systemstate.h"
24 #include "fnsearch.h"
25 #include "globals/network.h"
26 #include "globals/uvars.h"
27 #include "item/itemdesc.h"
28 #include "mobile/attribute.h"
29 #include "mobile/charactr.h"
30 #include "multi/multi.h"
31 #include "network/client.h"
32 #include "uobject.h"
33 #include "uoscrobj.h"
34 #include "vital.h"
35 
36 namespace Pol
37 {
38 namespace Core
39 {
40 using namespace Bscript;
41 using namespace Module;
42 
43 bool getCharacterOrClientParam( Executor& exec, unsigned param, Mobile::Character*& chrptr,
44  Network::Client*& clientptr )
45 {
46  BObjectImp* imp = exec.getParamImp( param );
47  if ( imp == nullptr )
48  {
49  exec.setFunctionResult( new BError( "Missing parameter " + Clib::decint( param ) ) );
50  return false;
51  }
52  else if ( imp->isa( BObjectImp::OTApplicObj ) )
53  {
55 
56  if ( ( aob != nullptr ) && ( aob->object_type() == &echaracterrefobjimp_type ) )
57  {
58  ECharacterRefObjImp* chrref_imp =
59  Clib::explicit_cast<ECharacterRefObjImp*, BApplicObjBase*>( aob );
60 
61  chrptr = chrref_imp->value().get();
62 
63  if ( chrptr->orphan() )
64  {
65  exec.setFunctionResult( new BError( "Mobile has been destroyed" ) );
66  return false;
67  }
68 
69  if ( chrptr->logged_in() || chrref_imp->offline_access_ok() )
70  {
71  return true;
72  }
73  else
74  {
75  exec.setFunctionResult( new BError( "Mobile is offline" ) );
76  return false;
77  }
78  }
79  else if ( ( aob != nullptr ) && ( aob->object_type() == &eclientrefobjimp_type ) )
80  {
81  EClientRefObjImp* clientref_imp =
82  Clib::explicit_cast<EClientRefObjImp*, BApplicObjBase*>( aob );
83 
84  clientptr = clientref_imp->value().exists() ? clientref_imp->value().get_weakptr() : nullptr;
85 
86  if ( ( clientptr != nullptr ) && clientptr->isConnected() )
87  {
88  return true;
89  }
90  else
91  {
92  exec.setFunctionResult( new BError( "Client is disconnected" ) );
93  return false;
94  }
95  }
96  else
97  {
98  // FIXME: log error
99  return false;
100  }
101  }
102  else if ( imp->isa( BObjectImp::OTLong ) )
103  {
104  BLong* pchar_serial = Clib::explicit_cast<BLong*, BObjectImp*>( imp );
105 
106  unsigned int serial = pchar_serial->value();
107  if ( IsItem( serial ) || serial == 0 )
108  {
109  exec.setFunctionResult( new BError( "Serial refers to an Item, or is zero" ) );
110  return false;
111  }
112 
113  chrptr = system_find_mobile( serial );
114 
115  if ( chrptr != nullptr )
116  {
117  if ( chrptr->logged_in() )
118  {
119  return true;
120  }
121  else
122  {
123  exec.setFunctionResult( new BError( "Mobile is offline" ) );
124  return false;
125  }
126  }
127  else
128  {
129  exec.setFunctionResult( new BError( "Mobile does not exist" ) );
130  return false;
131  }
132  }
133  else
134  {
135  // FIXME: log error
136  return false;
137  }
138 }
139 
140 bool getCharacterParam( Bscript::Executor& exec, unsigned param, Mobile::Character*& chrptr )
141 {
142  BObjectImp* imp = exec.getParamImp( param );
143  if ( imp == nullptr )
144  {
145  exec.setFunctionResult( new BError( "Missing parameter " + Clib::decint( param ) ) );
146  return false;
147  }
148  else if ( imp->isa( BObjectImp::OTApplicObj ) )
149  {
151 
152  if ( ( aob != nullptr ) && ( aob->object_type() == &echaracterrefobjimp_type ) )
153  {
154  ECharacterRefObjImp* chrref_imp =
155  Clib::explicit_cast<ECharacterRefObjImp*, BApplicObjBase*>( aob );
156 
157  chrptr = chrref_imp->value().get();
158 
159  if ( chrptr->orphan() )
160  {
161  exec.setFunctionResult( new BError( "Mobile has been destroyed" ) );
162  return false;
163  }
164 
165  if ( chrptr->logged_in() || chrref_imp->offline_access_ok() )
166  {
167  return true;
168  }
169  else
170  {
171  exec.setFunctionResult( new BError( "Mobile is offline" ) );
172  return false;
173  }
174  }
175  else
176  {
177  // FIXME: log error
178  return false;
179  }
180  }
181  else if ( imp->isa( BObjectImp::OTLong ) )
182  {
183  BLong* pchar_serial = Clib::explicit_cast<BLong*, BObjectImp*>( imp );
184 
185  unsigned int serial = pchar_serial->value();
186  if ( IsItem( serial ) || serial == 0 )
187  {
188  exec.setFunctionResult( new BError( "Serial refers to an Item, or is zero" ) );
189  return false;
190  }
191 
192  chrptr = system_find_mobile( serial );
193 
194  if ( chrptr != nullptr )
195  {
196  if ( chrptr->logged_in() )
197  {
198  return true;
199  }
200  else
201  {
202  exec.setFunctionResult( new BError( "Mobile is offline" ) );
203  return false;
204  }
205  }
206  else
207  {
208  exec.setFunctionResult( new BError( "Mobile does not exist" ) );
209  return false;
210  }
211  }
212  else
213  {
214  // FIXME: log error
215  return false;
216  }
217 }
218 
219 bool getItemParam( Executor& exec, unsigned param, Items::Item*& itemptr )
220 {
221  BObjectImp* imp = exec.getParamImp( param );
222  if ( imp == nullptr )
223  {
224  return false;
225  }
226  else if ( imp->isa( BObjectImp::OTApplicObj ) )
227  {
229 
230  if ( ( aob != nullptr ) && ( aob->object_type() == &eitemrefobjimp_type ) )
231  {
232  EItemRefObjImp* itemref_imp = Clib::explicit_cast<EItemRefObjImp*, BApplicObjBase*>( aob );
233 
234  itemptr = itemref_imp->value().get();
235  return ( !itemptr->orphan() );
236  }
237  else
238  {
239  // FIXME: log error
240  return false;
241  }
242  }
243  else if ( imp->isa( BObjectImp::OTLong ) )
244  {
245  BLong* pitem_serial = Clib::explicit_cast<BLong*, BObjectImp*>( imp );
246  unsigned int serial = pitem_serial->value();
247 
248  if ( IsCharacter( serial ) || serial == 0 )
249  return false;
250 
251  itemptr = system_find_item( serial );
252 
253  return ( itemptr != nullptr );
254  }
255  else
256  {
257  // FIXME: log error
258  return false;
259  }
260 }
261 
262 bool getUBoatParam( Executor& exec, unsigned param, Multi::UBoat*& boatptr )
263 {
264  BObjectImp* imp = exec.getParamImp( param );
265  if ( imp == nullptr )
266  {
267  return false;
268  }
269  else if ( imp->isa( BObjectImp::OTApplicObj ) )
270  {
272 
273  if ( aob->object_type() == &euboatrefobjimp_type )
274  {
275  EUBoatRefObjImp* boatref_imp = Clib::explicit_cast<EUBoatRefObjImp*, BApplicObjBase*>( aob );
276 
277  boatptr = boatref_imp->value().get();
278  return ( !boatptr->orphan() );
279  }
280  else if ( aob->object_type() == &eitemrefobjimp_type )
281  {
282  EItemRefObjImp* itemref_imp = Clib::explicit_cast<EItemRefObjImp*, BApplicObjBase*>( aob );
283 
284  Items::Item* item = itemref_imp->value().get();
285  if ( item->isa( UOBJ_CLASS::CLASS_MULTI ) )
286  {
287  Multi::UMulti* multi = static_cast<Multi::UMulti*>( item );
288  boatptr = multi->as_boat();
289  if ( boatptr == nullptr )
290  return false;
291  else
292  return ( !boatptr->orphan() );
293  }
294  else
295  {
296  return false;
297  }
298  }
299  else
300  {
301  // FIXME: log error
302  return false;
303  }
304  }
305  else if ( imp->isa( BObjectImp::OTLong ) )
306  {
307  BLong* pitem_serial = Clib::explicit_cast<BLong*, BObjectImp*>( imp );
308 
309  Multi::UMulti* multi = system_find_multi( pitem_serial->value() );
310  if ( multi )
311  boatptr = multi->as_boat();
312 
313  return ( boatptr != nullptr );
314  }
315  else
316  {
317  // FIXME: log error
318  return false;
319  }
320 }
321 
322 
323 bool getMultiParam( Executor& exec, unsigned param, Multi::UMulti*& multiptr )
324 {
325  BObjectImp* imp = exec.getParamImp( param );
326  if ( imp == nullptr )
327  {
328  return false;
329  }
330  else if ( imp->isa( BObjectImp::OTApplicObj ) )
331  {
333 
334  if ( aob->object_type() == &emultirefobjimp_type )
335  {
336  EMultiRefObjImp* multiref_imp = Clib::explicit_cast<EMultiRefObjImp*, BApplicObjBase*>( aob );
337 
338  multiptr = multiref_imp->value().get();
339  return ( !multiptr->orphan() );
340  }
341  else if ( aob->object_type() == &euboatrefobjimp_type )
342  {
343  EUBoatRefObjImp* boatref_imp = Clib::explicit_cast<EUBoatRefObjImp*, BApplicObjBase*>( aob );
344 
345  multiptr = boatref_imp->value().get();
346  return ( !multiptr->orphan() );
347  }
348  else
349  {
350  // FIXME: log error
351  return false;
352  }
353  }
354  else if ( imp->isa( BObjectImp::OTLong ) )
355  {
356  BLong* pitem_serial = Clib::explicit_cast<BLong*, BObjectImp*>( imp );
357 
358  multiptr = system_find_multi( pitem_serial->value() );
359 
360  return ( multiptr != nullptr );
361  }
362  else
363  {
364  // FIXME: log error
365  return false;
366  }
367 }
368 
369 bool getUObjectParam( Executor& exec, unsigned param, UObject*& objptr )
370 {
371  Items::Item* item = nullptr;
372  Mobile::Character* chr = nullptr;
373  Multi::UMulti* multi = nullptr;
374 
375  // This function is a kludge because the individual functions will all test for
376  // a serial independently and may set errors.
377  // TODO: Refactor this function to test whether the parameter is a BLong in a single place.
378 
379  if ( getCharacterParam( exec, param, chr ) )
380  {
381  objptr = chr;
382  return true;
383  }
384  else if ( getItemParam( exec, param, item ) )
385  {
386  objptr = item;
387  return true;
388  }
389  else if ( getMultiParam( exec, param, multi ) )
390  {
391  objptr = multi;
392  return true;
393  }
394  else
395  {
396  return false;
397  }
398 }
399 
400 bool getObjtypeParam( Executor& exec, unsigned param, unsigned int& objtype )
401 {
402  BObjectImp* imp = exec.getParamImp( param );
403  if ( imp == nullptr )
404  {
405  return false;
406  }
407  unsigned int objtype_long = 0;
408 
409  if ( imp->isa( BObjectImp::OTLong ) )
410  {
411  BLong* plong = Clib::explicit_cast<BLong*, BObjectImp*>( imp );
412  objtype_long = plong->value();
413  }
414  else if ( imp->isa( BObjectImp::OTString ) )
415  {
416  // this could be an objtypename, or an objtype in string form. Cope with either.
417  String* pstring = Clib::explicit_cast<String*, BObjectImp*>( imp );
418  const char* ot_str = pstring->data();
419  if ( !isdigit( ot_str[0] ) )
420  {
421  objtype = Items::get_objtype_byname( pstring->data() );
422  if ( objtype != 0 )
423  {
424  return true;
425  }
426  else
427  {
428  exec.setFunctionResult(
429  new BError( std::string( "Objtype not defined: " ) + pstring->data() ) );
430 
431  return false;
432  }
433  }
434  else
435  {
436  // a number passed...process below as if passed as a BLong
437  objtype_long = strtol( ot_str, nullptr, 0 );
438  }
439  }
440  else
441  {
442  DEBUGLOG << "Script Error in '" << exec.scriptname() << "' PC=" << exec.PC << ": \n"
443  << "\tCall to function " << exec.current_module_function->name.get() << ":\n"
444  << "\tParameter " << param << ": Expected Long or String, got datatype "
445  << BObjectImp::typestr( imp->type() ) << "\n";
446  return false;
447  }
448 
449  if ( ( objtype_long > Plib::systemstate.config.max_tile_id ) &&
450  ( objtype_long <= Plib::systemstate.config.max_objtype ) )
451  {
452  objtype = objtype_long;
453  if ( Items::has_itemdesc( objtype ) )
454  {
455  return true;
456  }
457  else
458  {
459  exec.setFunctionResult(
460  new BError( "Objtype " + Clib::hexint( objtype_long ) + " is not defined." ) );
461  return false;
462  }
463  }
464  else if ( objtype_long <= Plib::systemstate.config.max_tile_id )
465  {
466  objtype = objtype_long;
467  return true;
468  }
469  else
470  {
471  DEBUGLOG << "Script Error in '" << exec.scriptname() << "' PC=" << exec.PC << ": \n"
472  << "\tCall to function " << exec.current_module_function->name.get() << ":\n"
473  << "\tParameter " << param << ": Value " << objtype_long
474  << " is out of range for an objtype\n";
475  exec.setFunctionResult( new BError( "Objtype is out of range ( acceptable: 0 - " +
476  Clib::hexint( Plib::systemstate.config.max_objtype ) +
477  " )" ) );
478  return false;
479  }
480 }
481 
482 bool getObjtypeParam( Executor& exec, unsigned param, const Items::ItemDesc*& itemdesc_out )
483 {
484  BObjectImp* imp = exec.getParamImp( param );
485  if ( imp == nullptr )
486  {
487  return false;
488  }
489  unsigned int objtype_long = 0;
490 
491  if ( imp->isa( BObjectImp::OTLong ) )
492  {
493  BLong* plong = Clib::explicit_cast<BLong*, BObjectImp*>( imp );
494  objtype_long = plong->value();
495  }
496  else if ( imp->isa( BObjectImp::OTString ) )
497  {
498  // this could be an objtypename, or an objtype in string form. Cope with either.
499  String* pstring = Clib::explicit_cast<String*, BObjectImp*>( imp );
500  const char* ot_str = pstring->data();
501  if ( !isdigit( ot_str[0] ) )
502  {
503  unsigned int objtype = Items::get_objtype_byname( pstring->data() );
504  if ( objtype != 0 )
505  {
506  itemdesc_out = &Items::find_itemdesc( objtype );
507  return true;
508  }
509  else
510  {
511  exec.setFunctionResult(
512  new BError( std::string( "Objtype not defined: " ) + pstring->data() ) );
513 
514  return false;
515  }
516  }
517  else
518  {
519  // a number passed...process below as if passed as a BLong
520  objtype_long = strtol( ot_str, nullptr, 0 );
521  }
522  }
523  else if ( imp->isa( BObjectImp::OTStruct ) )
524  {
525  BStruct* itemdesc_struct = Clib::explicit_cast<BStruct*, BObjectImp*>( imp );
526  try
527  {
528  itemdesc_out = Items::CreateItemDescriptor( itemdesc_struct );
529  return true;
530  }
531  catch ( std::exception& ex )
532  {
533  std::string message = std::string( "Unable to create item descriptor: " ) + ex.what();
534  exec.setFunctionResult( new BError( message ) );
535  return false;
536  }
537  }
538  else
539  {
540  DEBUGLOG << "Script Error in '" << exec.scriptname() << "' PC=" << exec.PC << ": \n"
541  << "\tCall to function " << exec.current_module_function->name.get() << ":\n"
542  << "\tParameter " << param << ": Expected Long, String, or Struct, got datatype "
543  << BObjectImp::typestr( imp->type() ) << "\n";
544  return false;
545  }
546 
547  // we get here if the value passed was an integer - either a BLong, or a String containing a
548  // number.
549  if ( ( objtype_long > Plib::systemstate.config.max_tile_id ) &&
550  ( objtype_long <= Plib::systemstate.config.max_objtype ) )
551  {
552  const Items::ItemDesc* itemdesc = &Items::find_itemdesc( objtype_long );
553 
554  if ( itemdesc != Core::gamestate.empty_itemdesc.get() )
555  {
556  itemdesc_out = itemdesc;
557  return true;
558  }
559  else
560  {
561  exec.setFunctionResult(
562  new BError( "Objtype " + Clib::hexint( objtype_long ) + " is not defined." ) );
563  return false;
564  }
565  }
566  else if ( objtype_long <= Plib::systemstate.config.max_tile_id )
567  {
568  unsigned int objtype = objtype_long;
569  itemdesc_out = &Items::find_itemdesc( objtype );
570  if ( itemdesc_out == Core::gamestate.empty_itemdesc.get() )
571  {
572  // return a temporary item descriptor initialized with the objtype and graphic.
573  itemdesc_out = Core::gamestate.temp_itemdesc.get();
574  Core::gamestate.temp_itemdesc->objtype = objtype;
575  Core::gamestate.temp_itemdesc->graphic = static_cast<u16>( objtype );
577  Core::gamestate.temp_itemdesc->doubleclick_range =
579  }
580 
581  return true;
582  }
583  else
584  {
585  DEBUGLOG << "Script Error in '" << exec.scriptname() << "' PC=" << exec.PC << ": \n"
586  << "\tCall to function " << exec.current_module_function->name.get() << ":\n"
587  << "\tParameter " << param << ": Value " << objtype_long
588  << " is out of range for an objtype\n";
589  exec.setFunctionResult( new BError( "Objtype is out of range (acceptable: 0-0x20000)" ) );
590  return false;
591  }
592 }
593 
594 bool getSkillIdParam( Executor& exec, unsigned param, USKILLID& skillid )
595 {
596  int skillval;
597  if ( exec.getParam( param, skillval, SKILLID__LOWEST,
599  {
600  skillid = static_cast<USKILLID>( skillval );
601  return true;
602  }
603  else
604  {
605  return false;
606  }
607 }
608 
609 
610 bool getAttributeParam( Executor& exec, unsigned param, const Mobile::Attribute*& attr )
611 {
612  const String* attrname;
613  if ( !exec.getStringParam( param, attrname ) )
614  return false;
615 
616  attr = Mobile::Attribute::FindAttribute( attrname->value() );
617  if ( !attr )
618  {
619  exec.setFunctionResult( new BError( "Attribute not defined: " + attrname->value() ) );
620  return false; // new BError( "Attribute not found" );
621  }
622 
623  return true;
624 }
625 
626 
627 bool getVitalParam( Executor& exec, unsigned param, const Vital*& vital )
628 {
629  const String* vitalname;
630  if ( !exec.getStringParam( param, vitalname ) )
631  return false;
632 
633  vital = FindVital( vitalname->value() );
634  if ( !vital )
635  {
636  exec.setFunctionResult( new BError( "Vital not defined: " + vitalname->value() ) );
637  return false;
638  }
639 
640  return true;
641 }
642 }
643 }
void setFunctionResult(BObjectImp *imp)
Definition: executor.cpp:374
unsigned short default_doubleclick_range
Definition: ssopt.h:46
BApplicObjType echaracterrefobjimp_type
Definition: uoscrobj.cpp:126
const std::string & value() const
Definition: impstr.h:67
Vital * FindVital(const std::string &str)
Definition: vital.cpp:83
unsigned int get_objtype_byname(const char *str)
Definition: itemdesc.cpp:58
BObjectType type() const
Definition: bobject.h:358
int value() const
Definition: bobject.h:592
BApplicObjType eitemrefobjimp_type
Definition: uoscrobj.cpp:125
virtual bool offline_access_ok() const
Definition: uoscrobj.h:99
SystemState systemstate
Definition: systemstate.cpp:12
bool isa(BObjectType type) const
Definition: bobject.h:353
Core::PolConfig config
Definition: systemstate.h:43
T * get() const
Definition: refptr.h:176
std::string decint(unsigned short v)
Definition: strutil.cpp:64
virtual class UBoat * as_boat()
Definition: multis.cpp:51
T * get_weakptr() const
Definition: weakptr.h:110
bool logged_in() const
Definition: charactr.cpp:428
const ItemDesc & find_itemdesc(unsigned int objtype)
Definition: itemdesc.cpp:933
BApplicObjType emultirefobjimp_type
Definition: uoscrobj.cpp:124
std::unique_ptr< Items::ItemDesc > temp_itemdesc
Definition: uvars.h:209
bool getAttributeParam(Executor &exec, unsigned param, const Mobile::Attribute *&attr)
Definition: uoexhelp.cpp:610
std::string hexint(unsigned short v)
Definition: strutil.cpp:23
const BApplicObjType * object_type() const
Definition: bobject.h:897
unsigned short u16
Definition: rawtypes.h:26
bool getVitalParam(Executor &exec, unsigned param, const Vital *&vital)
Definition: uoexhelp.cpp:627
bool getCharacterParam(Bscript::Executor &exec, unsigned param, Mobile::Character *&chrptr)
Definition: uoexhelp.cpp:140
boost_utils::function_name_flystring name
Definition: fmodule.h:27
BApplicObjType eclientrefobjimp_type
Definition: uoscrobj.cpp:130
bool IsItem(u32 serial)
Definition: uobject.h:316
Multi::UMulti * system_find_multi(u32 serial)
Definition: fnsearch.cpp:50
unsigned short maxskills
Definition: uoclient.h:55
#define DEBUGLOG
Definition: logfacility.h:237
bool getCharacterOrClientParam(Executor &exec, unsigned param, Mobile::Character *&chrptr, Network::Client *&clientptr)
Definition: uoexhelp.cpp:43
NetworkManager networkManager
Definition: network.cpp:28
bool exists() const
Definition: weakptr.h:115
bool orphan() const
Definition: baseobject.h:119
BApplicObjType euboatrefobjimp_type
Definition: uoscrobj.cpp:123
bool getItemParam(Executor &exec, unsigned param, Items::Item *&itemptr)
Definition: uoexhelp.cpp:219
GameState gamestate
Definition: uvars.cpp:74
SettingsManager settingsManager
Definition: settings.cpp:14
Mobile::Character * system_find_mobile(u32 serial)
Definition: fnsearch.cpp:32
bool getObjtypeParam(Executor &exec, unsigned param, unsigned int &objtype)
Definition: uoexhelp.cpp:400
unsigned int default_decay_time
Definition: ssopt.h:45
bool IsCharacter(u32 serial)
Definition: uobject.h:311
Items::Item * system_find_item(u32 serial)
Definition: fnsearch.cpp:41
const std::string & scriptname() const
Definition: executor.h:413
virtual UBoat * as_boat() POL_OVERRIDE
Definition: boat.cpp:602
bool getUObjectParam(Executor &exec, unsigned param, UObject *&objptr)
Definition: uoexhelp.cpp:369
const String * getStringParam(unsigned param)
Definition: executor.cpp:347
const ItemDesc * CreateItemDescriptor(Bscript::BStruct *itemdesc_struct)
Definition: itemdesc.cpp:965
bool getMultiParam(Executor &exec, unsigned param, Multi::UMulti *&multiptr)
Definition: uoexhelp.cpp:323
D explicit_cast(const S &s)
Definition: stlutil.h:40
bool getSkillIdParam(Executor &exec, unsigned param, USKILLID &skillid)
Definition: uoexhelp.cpp:594
bool has_itemdesc(u32 objtype)
Definition: itemdesc.cpp:895
const char * data() const
Definition: impstr.h:66
Definition: berror.cpp:12
static const char * typestr(BObjectType typ)
Definition: object.cpp:218
BObjectImp * getParamImp(unsigned param)
Definition: executor.cpp:266
ModuleFunction * current_module_function
Definition: executor.h:218
unsigned int max_objtype
Definition: polcfg.h:62
bool getUBoatParam(Executor &exec, unsigned param, Multi::UBoat *&boatptr)
Definition: uoexhelp.cpp:262
static Attribute * FindAttribute(const std::string &str)
Definition: attribute.cpp:22
UoClientGeneral uoclient_general
Definition: network.h:67
bool getParam(unsigned param, int &value)
Definition: executor.cpp:363