• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

polserver / polserver / 25964000893

16 May 2026 02:08PM UTC coverage: 60.833% (-0.02%) from 60.856%
25964000893

push

github

turleypol
items need to keep track of the opponents to be able to cleanup on
destroy

6 of 38 new or added lines in 6 files covered. (15.79%)

6 existing lines in 4 files now uncovered.

44706 of 73490 relevant lines covered (60.83%)

495390.42 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

34.25
/pol-core/pol/module/uomod.cpp
1
/** @file
2
 *
3
 * @par History
4
 * - 2005/03/02 Shinigami: internal_MoveItem - item took from container don't had the correct realm
5
 * - 2005/04/02 Shinigami: mf_CreateItemCopyAtLocation - added realm param
6
 * - 2005/04/28 Shinigami: mf_EnumerateItemsInContainer/enumerate_contents - added flag to list
7
 * content of locked container too
8
 * - 2005/04/28 Shinigami: added mf_SecureTradeWin - to init secure trade via script over long
9
 * distances
10
 *                         added mf_MoveItemToSecureTradeWin - to move item to secure trade window
11
 * via script
12
 * - 2005/04/31 Shinigami: mf_EnumerateItemsInContainer - error message added
13
 * - 2005/06/01 Shinigami: added mf_Attach - to attach a Script to a Character
14
 *                         mf_MoveItem - stupid non-realm anti-crash bugfix (e.g. for intrinsic
15
 * weapons without realm)
16
 *                         added mf_ListStaticsAtLocation - list static Items at location
17
 *                         added mf_ListStaticsNearLocation - list static Items around location
18
 *                         added mf_GetStandingLayers - get layer a mobile can stand
19
 * - 2005/06/06 Shinigami: mf_ListStatics* will return multi Items too / flags added
20
 * - 2005/07/04 Shinigami: mf_ListStatics* constants renamed
21
 *                         added Z to mf_ListStatics*Location - better specify what u want to get
22
 *                         modified Z handling of mf_ListItems*Location* and
23
 * mf_ListMobilesNearLocation*
24
 *                         better specify what u want to get
25
 *                         added realm-based coord check to mf_ListItems*Location*,
26
 *                         mf_List*InBox and mf_ListMobilesNearLocation*
27
 *                         added mf_ListStaticsInBox - list static items in box
28
 * - 2005/07/07 Shinigami: realm-based coord check in mf_List*InBox disabled
29
 * - 2005/09/02 Shinigami: mf_Attach - smaller bug which allowed u to attach more than one Script to
30
 * a Character
31
 * - 2005/09/03 Shinigami: mf_FindPath - added Flags to support non-blocking doors
32
 * - 2005/09/23 Shinigami: added mf_SendStatus - to send full status packet to support extensions
33
 * - 2005/12/06 MuadDib:   Rewrote realm handling for Move* and internal_move_item. New realm is
34
 *                         to be passed to function, and oldrealm handling is done within the
35
 * function.
36
 *                         Container's still save and check oldrealm as before to update clients.
37
 * - 2006/01/18 Shinigami: added attached_npc_ - to get attached NPC from AI-Script-Process Obj
38
 * - 2006/05/10 Shinigami: mf_ListMobilesNearLocationEx - just a logical mismatch
39
 * - 2006/05/24 Shinigami: added mf_SendCharacterRaceChanger - change Hair, Beard and Color
40
 * - 2006/06/08 Shinigami: started to add FindPath logging
41
 * - 2006/09/17 Shinigami: mf_SendEvent() will return error "Event queue is full, discarding event"
42
 * - 2006/11/25 Shinigami: mf_GetWorldHeight() fixed bug because z was'n initialized
43
 * - 2007/05/03 Turley:    added mf_GetRegionNameAtLocation - get name of justice region
44
 * - 2007/05/04 Shinigami: added mf_GetRegionName - get name of justice region by objref
45
 * - 2007/05/07 Shinigami: fixed a crash in mf_GetRegionName using NPCs; added TopLevelOwner
46
 * - 2008/07/08 Turley:    Added mf_IsStackable - Is item stackable?
47
 * - 2008/07/21 Mehdorn:   mf_ReserveItem() will return 2 if Item is already reserved by me (instead
48
 * of 1)
49
 * - 2009/07/27 MuadDib:   Packet Struct Refactoring
50
 * - 2009/08/08 MuadDib:   mf_SetRawSkill(),  mf_GetRawSkill(),  mf_ApplyRawDamage(), mf_GameStat(),
51
 *                         mf_AwardRawPoints(), old replace_properties(), mf_GetSkill() cleaned out.
52
 * - 2009/08/25 Shinigami: STLport-5.2.1 fix: additional parentheses in mf_ListMultisInBox
53
 * - 2009/09/03 MuadDib:   Changes for account related source file relocation
54
 *                         Changes for multi related source file relocation
55
 * - 2009/09/14 MuadDib:   Slot support added to creation/move to container.
56
 * - 2009/09/15 MuadDib:   Multi registration/unregistration support added.
57
 * - 2009/10/22 Turley:    added CanWalk()
58
 * - 2009/11/19 Turley:    added flag param to UpdateMobile controls if packet 0x78 or 0x77 should
59
 * be send - Tomi
60
 * - 2009/12/02 Turley:    added config.max_tile_id - Tomi
61
 * - 2009/12/17 Turley:    CloseWindow( character, type, object ) - Tomi
62
 * - 2010/01/15 Turley:    (Tomi) season stuff
63
 *                         (Tomi) SpeakPowerWords font and color params
64
 * - 2011/11/12 Tomi:      added extobj.mount
65
 */
66

67

68
#include "pol_global_config.h"
69

70
#include "uomod.h"
71
#include <cmath>
72
#include <cstddef>
73
#include <exception>
74
#include <memory>
75
#include <optional>
76
#include <stdlib.h>
77
#include <string>
78

79
#include "../../bscript/berror.h"
80
#include "../../bscript/bobject.h"
81
#include "../../bscript/bstruct.h"
82
#include "../../bscript/dict.h"
83
#include "../../bscript/impstr.h"
84
#include "../../clib/cfgelem.h"
85
#include "../../clib/clib.h"
86
#include "../../clib/clib_endian.h"
87
#include "../../clib/esignal.h"
88
#include "../../clib/logfacility.h"
89
#include "../../clib/passert.h"
90
#include "../../clib/refptr.h"
91
#include "../../clib/stlutil.h"
92
#include "../../clib/timer.h"
93
#include "../../plib/clidata.h"
94
#include "../../plib/mapcell.h"
95
#include "../../plib/mapshape.h"
96
#include "../../plib/maptile.h"
97
#include "../../plib/objtype.h"
98
#include "../../plib/staticblock.h"
99
#include "../../plib/stlastar.h"
100
#include "../../plib/systemstate.h"
101
#include "../../plib/uconst.h"
102
#include "../../plib/udatfile.h"
103
#include "../action.h"
104
#include "../cfgrepos.h"
105
#include "../containr.h"
106
#include "../core.h"
107
#include "../eventid.h"
108
#include "../fnsearch.h"
109
#include "../gameclck.h"
110
#include "../globals/object_storage.h"
111
#include "../globals/uvars.h"
112
#include "../item/item.h"
113
#include "../item/itemdesc.h"
114
#include "../layers.h"
115
#include "../lightlvl.h"
116
#include "../listenpt.h"
117
#include "../los.h"
118
#include "../menu.h"
119
#include "../mobile/charactr.h"
120
#include "../mobile/npc.h"
121
#include "../mobile/ufacing.h"
122
#include "../multi/boat.h"
123
#include "../multi/house.h"
124
#include "../multi/multi.h"
125
#include "../multi/multidef.h"
126
#include "../network/cgdata.h"
127
#include "../network/client.h"
128
#include "../network/packethelper.h"
129
#include "../network/packets.h"
130
#include "../network/pktboth.h"
131
#include "../network/pktdef.h"
132
#include "../npctmpl.h"
133
#include "../polclass.h"
134
#include "../polclock.h"
135
#include "../polobject.h"
136
#include "../polsem.h"
137
#include "../polsig.h"
138
#include "../profile.h"
139
#include "../savedata.h"
140
#include "../scrdef.h"
141
#include "../scrsched.h"
142
#include "../scrstore.h"
143
#include "../spells.h"
144
#include "../target.h"
145
#include "../ufunc.h"
146
#include "../uimport.h"
147
#include "../umanip.h"
148
#include "../uobject.h"
149
#include "../uoexec.h"
150
#include "../uopathnode.h"
151
#include "../uoscrobj.h"
152
#include "../uworld.h"
153
#include "../wthrtype.h"
154
#include "cfgmod.h"
155
#include "realms/realm.h"
156
#include "realms/realms.h"
157
#include "regions/guardrgn.h"
158
#include "regions/miscrgn.h"
159
#include "regions/resource.h"
160

161
#include <module_defs/uo.h>
162

163
namespace Pol
164
{
165
namespace Core
166
{
167
void cancel_trade( Mobile::Character* chr1 );
168
void cancel_all_trades();
169
Bscript::BObjectImp* place_item_in_secure_trade_container( Network::Client* client,
170
                                                           Items::Item* item );
171
Bscript::BObjectImp* open_trade_window( Network::Client* client, Mobile::Character* dropon );
172
void send_tip( Network::Client* client, const std::string& tiptext );
173
std::string get_textcmd_help( Mobile::Character* chr, const std::string& cmd );
174
void send_paperdoll( Network::Client* client, Mobile::Character* chr );
175
void send_skillmsg( Network::Client* client, const Mobile::Character* chr );
176
Bscript::BObjectImp* equip_from_template( Mobile::Character* chr,
177
                                          const std::string& template_name );
178
}  // namespace Core
179
namespace Module
180
{
181
using namespace Bscript;
182
using namespace Items;
183
using namespace Mobile;
184
using namespace Core;
185

186
#define CONST_DEFAULT_ZRANGE 19
187

188
class EMenuObjImp final : public PolApplicObj<Menu>
189
{
190
public:
191
  EMenuObjImp( const Menu& m ) : PolApplicObj<Menu>( &menu_type, m ) {}
×
192
  const char* typeOf() const override { return "MenuRef"; }
×
193
  u8 typeOfInt() const override { return OTMenuRef; }
×
194
  BObjectImp* copy() const override { return new EMenuObjImp( value() ); }
×
195
};
196

197

198
UOExecutorModule::UOExecutorModule( UOExecutor& exec )
289✔
199
    : TmplExecutorModule<UOExecutorModule, Core::PolModule>( exec ),
200
      target_cursor_chr( nullptr ),
289✔
201
      menu_selection_chr( nullptr ),
289✔
202
      popup_menu_selection_chr( nullptr ),
289✔
203
      popup_menu_selection_above( nullptr ),
289✔
204
      prompt_chr( nullptr ),
289✔
205
      gump_chrs(),
289✔
206
      textentry_chr( nullptr ),
289✔
207
      resurrect_chr( nullptr ),
289✔
208
      selcolor_chr( nullptr ),
289✔
209
      attached_chr_( nullptr ),
289✔
210
      attached_npc_( nullptr ),
289✔
211
      attached_item_( nullptr ),
289✔
212
      controller_( nullptr ),
289✔
213
      reserved_items_(),
289✔
214
      target_options( 0 ),
289✔
215
      registered_for_speech_events( false )
289✔
216
{
217
}
289✔
218

219
UOExecutorModule::~UOExecutorModule()
578✔
220
{
221
  auto& uoex = uoexec();
289✔
222
  while ( !reserved_items_.empty() )
289✔
223
  {
224
    Item* item = reserved_items_.back().get();
×
225
    item->inuse( false );
×
226
    reserved_items_.pop_back();
×
227
  }
228

229
  if ( target_cursor_chr )
289✔
230
  {
231
    // CHECKME can we cancel the cursor request?
232
    if ( auto* client = target_cursor_chr->client; client && client->gd )
×
233
      client->gd->target_cursor_uoemod = nullptr;
×
234
    target_cursor_chr = nullptr;
×
235
  }
236
  if ( menu_selection_chr )
289✔
237
  {
238
    if ( auto* client = menu_selection_chr->client; client && client->gd )
×
239
      client->gd->menu_selection_uoemod = nullptr;
×
240
    menu_selection_chr = nullptr;
×
241
  }
242
  if ( popup_menu_selection_chr )
289✔
243
  {
244
    if ( auto* client = popup_menu_selection_chr->client; client && client->gd )
×
245
      client->gd->popup_menu_selection_uoemod = nullptr;
×
246
    popup_menu_selection_chr = nullptr;
×
247
    popup_menu_selection_above = nullptr;
×
248
  }
249
  if ( prompt_chr )
289✔
250
  {
251
    if ( auto* client = prompt_chr->client; client && client->gd )
×
252
      client->gd->prompt_uoemod = nullptr;
×
253
    prompt_chr = nullptr;
×
254
  }
255
  for ( auto& [chr, _] : gump_chrs )
289✔
256
  {
257
    if ( auto* client = chr->client; client && client->gd )
×
258
      client->gd->remove_gumpmods( this );
×
259
  }
260
  if ( textentry_chr )
289✔
261
  {
262
    if ( auto* client = textentry_chr->client; client && client->gd )
×
263
      client->gd->textentry_uoemod = nullptr;
×
264
    textentry_chr = nullptr;
×
265
  }
266
  if ( resurrect_chr )
289✔
267
  {
268
    if ( auto* client = resurrect_chr->client; client && client->gd )
×
269
      client->gd->resurrect_uoemod = nullptr;
×
270
    resurrect_chr = nullptr;
×
271
  }
272
  if ( selcolor_chr )
289✔
273
  {
274
    if ( auto* client = selcolor_chr->client; client && client->gd )
×
275
      client->gd->selcolor_uoemod = nullptr;
×
276
    selcolor_chr = nullptr;
×
277
  }
278
  if ( attached_chr_ )
289✔
279
  {
280
    passert( attached_chr_->script_ex == &uoex );
×
281
    attached_chr_->script_ex = nullptr;
×
282
    attached_chr_ = nullptr;
×
283
  }
284
  if ( attached_item_.get() )
289✔
285
  {
286
    attached_item_->process( nullptr );
23✔
287
    attached_item_.clear();
23✔
288
  }
289
  if ( registered_for_speech_events )
289✔
290
  {
291
    ListenPoint::deregister_from_speech_events( &uoex );
1✔
292
  }
293
}
578✔
294

295
BObjectImp* UOExecutorModule::mf_Attach( /* Character */ )
×
296
{
297
  Character* chr;
298
  if ( getCharacterParam( 0, chr ) )
×
299
  {
300
    if ( attached_chr_ == nullptr )
×
301
    {
302
      if ( chr->script_ex == nullptr )
×
303
      {
304
        attached_chr_ = chr;
×
305
        attached_chr_->script_ex = &uoexec();
×
306

307
        return new BLong( 1 );
×
308
      }
309
      return new BError( "Another script still attached." );
×
310
    }
311
    return new BError( "Another character still attached." );
×
312
  }
313
  return new BError( "Invalid parameter" );
×
314
}
315

316
BObjectImp* UOExecutorModule::mf_Detach()
×
317
{
318
  if ( attached_chr_ != nullptr )
×
319
  {
320
    passert( attached_chr_->script_ex == &uoexec() );
×
321
    attached_chr_->script_ex = nullptr;
×
322
    attached_chr_ = nullptr;
×
323
    return new BLong( 1 );
×
324
  }
325

326
  return new BLong( 0 );
×
327
}
328

329
static bool item_create_params_ok( u32 objtype, int amount )
6,169✔
330
{
331
  return ( objtype >= UOBJ_ITEM__LOWEST && objtype <= Plib::systemstate.config.max_objtype ) &&
6,169✔
332
         amount > 0 && amount <= 60000L;
12,338✔
333
}
334

335
BObjectImp* _create_item_in_container( UContainer* cont, const ItemDesc* descriptor,
45✔
336
                                       unsigned short amount, bool force_stacking,
337
                                       std::optional<Core::Pos2d> pos, UOExecutorModule* uoemod )
338
{
339
  if ( ( Plib::tile_flags( descriptor->graphic ) & Plib::FLAG::STACKABLE ) || force_stacking )
45✔
340
  {
341
    for ( UContainer::const_iterator itr = cont->begin(); itr != cont->end(); ++itr )
42✔
342
    {
343
      Item* item = *itr;
16✔
344
      ItemRef itemref( item );  // dave 1/28/3 prevent item from being destroyed before function
16✔
345
                                // ends
346

347
      if ( item->objtype_ == descriptor->objtype && !item->newbie() &&
10✔
348
           item->insured() == descriptor->insured && item->cursed() == descriptor->cursed &&
10✔
349
           item->color ==
10✔
350
               descriptor
351
                   ->color &&  // dave added 5/11/3, only add to existing stack if is default color
10✔
352
           item->has_only_default_cprops(
10✔
353
               descriptor ) &&  // dave added 5/11/3, only add to existing stack if default cprops
6✔
354
           ( !item->inuse() || ( uoemod && uoemod->is_reserved_to_me( item ) ) ) &&
32✔
355
           item->can_add_to_self( amount, force_stacking ) )
6✔
356
      {
357
        // DAVE added this 11/17, call can/onInsert scripts for this container
358
        Character* chr_owner = cont->GetCharacterOwner();
6✔
359
        if ( chr_owner == nullptr )
6✔
360
          if ( uoemod != nullptr )
5✔
361
            chr_owner = uoemod->controller_.get();
5✔
362

363
        // If the can insert script fails for combining a stack, we'll let the create new item code
364
        // below handle it
365
        // what if a cannInsert script modifies (adds to) the container we're iterating over? (they
366
        // shouldn't do that)
367
        // FIXME oh my, this makes no sense.  'item' in this case is already in the container.
368
        if ( !cont->can_insert_increase_stack( chr_owner, UContainer::MT_CORE_CREATED, item, amount,
6✔
369
                                               nullptr ) )
370
          continue;
×
371
        if ( item->orphan() )  // dave added 1/28/3, item might be destroyed in RTC script
6✔
372
        {
373
          return new BError( "Item was destroyed in CanInsert Script" );
×
374
        }
375
#ifdef PERGON
376
        item->ct_merge_stacks_pergon(
377
            amount );  // Pergon: Re-Calculate Property CreateTime after Adding Items to a Stack
378
#endif
379

380
        int newamount = item->getamount();
6✔
381
        newamount += amount;
6✔
382
        item->setamount( static_cast<unsigned short>( newamount ) );
6✔
383

384
        update_item_to_inrange( item );
6✔
385
        UpdateCharacterWeight( item );
6✔
386

387
        // FIXME again, this makes no sense, item is already in the container.
388
        cont->on_insert_increase_stack( chr_owner, UContainer::MT_CORE_CREATED, item, amount );
6✔
389
        if ( item->orphan() )  // dave added 1/28/3, item might be destroyed in RTC script
6✔
390
        {
391
          return new BError( "Item was destroyed in OnInsert Script" );
×
392
        }
393

394

395
        return new EItemRefObjImp( item );
6✔
396
      }
397
    }
16✔
398
  }
399
  else if ( amount != 1 && !force_stacking )
13✔
400
  {
401
    return new BError( "That item is not stackable.  Create one at a time." );
×
402
  }
403

404
  Item* item = Item::create( *descriptor );
39✔
405
  if ( item != nullptr )
39✔
406
  {
407
    ItemRef itemref( item );  // dave 1/28/3 prevent item from being destroyed before function ends
39✔
408
    item->setposition( cont->pos() );
39✔
409
    item->setamount( amount );
39✔
410

411
    if ( cont->can_add( *item ) )
39✔
412
    {
413
      if ( !descriptor->create_script.empty() )
38✔
414
      {
415
        BObjectImp* res =
416
            run_script_to_completion( descriptor->create_script, new EItemRefObjImp( item ) );
×
417
        if ( !res->isTrue() )
×
418
        {
419
          item->destroy();
×
420
          return res;
×
421
        }
422
        BObject ob( res );
×
423
        if ( !cont->can_add( *item ) )
×
424
        {  // UNTESTED
425
          item->destroy();
×
426
          return new BError( "Couldn't add item to container after create script ran" );
×
427
        }
428
      }
×
429

430
      // run before owner is found. No need to find owner if not even able to use slots.
431
      u8 slotIndex = item->slot_index();
38✔
432
      if ( !cont->can_add_to_slot( slotIndex ) )
38✔
433
      {
434
        item->destroy();
×
435
        return new BError( "No slots available in this container" );
×
436
      }
437
      if ( !item->slot_index( slotIndex ) )
38✔
438
      {
439
        item->destroy();
×
440
        return new BError( "Couldn't set slot index on item" );
×
441
      }
442

443
      // DAVE added this 11/17, call can/onInsert scripts for this container
444
      Character* chr_owner = cont->GetCharacterOwner();
38✔
445
      if ( chr_owner == nullptr )
38✔
446
        if ( uoemod != nullptr )
25✔
447
          chr_owner = uoemod->controller_.get();
25✔
448

449
      if ( !cont->can_insert_add_item( chr_owner, UContainer::MT_CORE_CREATED, item ) )
38✔
450
      {
451
        item->destroy();
×
452
        // FIXME: try to propogate error from returned result of the canInsert script
453
        return new BError( "Could not insert item into container." );
×
454
      }
455
      if ( item->orphan() )  // dave added 1/28/3, item might be destroyed in RTC script
38✔
456
      {
457
        return new BError( "Item was destroyed in CanInsert Script" );
×
458
      }
459

460
      if ( !pos || !cont->is_legal_posn( pos.value() ) )
38✔
461
        pos = cont->get_random_location();
34✔
462
      cont->add( item, pos.value() );
38✔
463

464
      update_item_to_inrange( item );
38✔
465
      // DAVE added this 11/17, refresh owner's weight on item insert
466
      UpdateCharacterWeight( item );
38✔
467

468
      cont->on_insert_add_item( chr_owner, UContainer::MT_CORE_CREATED, item );
38✔
469
      if ( item->orphan() )  // dave added 1/28/3, item might be destroyed in RTC script
38✔
470
      {
471
        return new BError( "Item was destroyed in OnInsert Script" );
×
472
      }
473

474
      return new EItemRefObjImp( item );
38✔
475
    }
476

477
    item->destroy();
1✔
478
    return new BError( "That container is full" );
1✔
479
  }
39✔
480

481
  return new BError( "Failed to create that item type" );
×
482
}
483

484
BObjectImp* UOExecutorModule::mf_CreateItemInContainer()
41✔
485
{
486
  Item* item;
487
  const ItemDesc* descriptor;
488
  int amount;
489
  int px;
490
  int py;
491

492
  if ( getItemParam( 0, item ) && getObjtypeParam( 1, descriptor ) && getParam( 2, amount ) &&
82✔
493
       getParam( 3, px, -1, 65535 ) && getParam( 4, py, -1, 65535 ) &&
123✔
494
       item_create_params_ok( descriptor->objtype, amount ) )
41✔
495
  {
496
    if ( item->isa( UOBJ_CLASS::CLASS_CONTAINER ) )
41✔
497
    {
498
      std::optional<Core::Pos2d> pos;
41✔
499
      if ( px >= 0 && py >= 0 )
41✔
500
        pos = Core::Pos2d( static_cast<u16>( px ), static_cast<u16>( py ) );
2✔
501
      return _create_item_in_container( static_cast<UContainer*>( item ), descriptor,
41✔
502
                                        static_cast<unsigned short>( amount ), false, pos, this );
41✔
503
    }
504

505
    return new BError( "That is not a container" );
×
506
  }
507

508
  return new BError( "A parameter was invalid" );
×
509
}
510

511
BObjectImp* UOExecutorModule::mf_CreateItemInInventory()
1✔
512
{
513
  Item* item;
514
  const ItemDesc* descriptor;
515
  int amount;
516
  int px;
517
  int py;
518

519
  if ( getItemParam( 0, item ) && getObjtypeParam( 1, descriptor ) && getParam( 2, amount ) &&
2✔
520
       getParam( 3, px, -1, 65535 ) && getParam( 4, py, -1, 65535 ) &&
3✔
521
       item_create_params_ok( descriptor->objtype, amount ) )
1✔
522
  {
523
    if ( item->isa( UOBJ_CLASS::CLASS_CONTAINER ) )
1✔
524
    {
525
      std::optional<Core::Pos2d> pos;
1✔
526
      if ( px >= 0 && py >= 0 )
1✔
527
        pos = Core::Pos2d( static_cast<u16>( px ), static_cast<u16>( py ) );
1✔
528
      return _create_item_in_container( static_cast<UContainer*>( item ), descriptor,
1✔
529
                                        static_cast<unsigned short>( amount ), true, pos, this );
1✔
530
    }
531

532
    return new BError( "That is not a container" );
×
533
  }
534

535
  return new BError( "A parameter was invalid" );
×
536
}
537

538

539
BObjectImp* UOExecutorModule::mf_Broadcast()
×
540
{
541
  const char* text;
542
  unsigned short font;
543
  unsigned short color;
544
  unsigned short requiredCmdLevel;
545
  text = exec.paramAsString( 0 );
×
546
  if ( text && getParam( 1, font ) &&     // todo: getFontParam
×
547
       getParam( 2, color ) &&            // todo: getColorParam
×
548
       getParam( 3, requiredCmdLevel ) )  // todo: getRequiredCmdLevelParam
×
549
  {
550
    if ( !Bscript::String::hasUTF8Characters( text ) )
×
551
      Core::broadcast( text, font, color, requiredCmdLevel );
×
552
    else
553
      Core::broadcast_unicode( text, "ENU", font, color, requiredCmdLevel );
×
554
    return new BLong( 1 );
×
555
  }
556

557
  return nullptr;
×
558
}
559

560
/* Special containers (specifically, bankboxes, but probably any other
561
   "invisible" accessible container) seem to work thus:
562
   They sit in layer 0x1D.  The player is told to "wear_item" the item,
563
   then the gump and container contents are sent.
564
   We'll put a reference to this item in the character's additional_legal_items
565
   container, which is flushed whenever he moves.
566
   It might be better to actually put it in that layer, because that way
567
   it implicitly is at the same location (location of the wornitems container)
568
   */
569
BObjectImp* UOExecutorModule::mf_SendOpenSpecialContainer()
×
570
{
571
  Character* chr;
572
  Item* item;
573
  if ( !getCharacterParam( 0, chr ) || !getItemParam( 1, item ) )
×
574
  {
575
    return new BError( "Invalid parameter type" );
×
576
  }
577

578
  if ( !chr->has_active_client() )
×
579
  {
580
    return new BError( "No client attached" );
×
581
  }
582
  if ( !item->isa( UOBJ_CLASS::CLASS_CONTAINER ) )
×
583
  {
584
    return new BError( "That isn't a container" );
×
585
  }
586

587
  u8 save_layer = item->layer;
×
588
  item->layer = LAYER_BANKBOX;
×
589
  send_wornitem( chr->client, chr, item );
×
590
  item->layer = save_layer;
×
591
  item->setposition( chr->pos() );
×
592
  item->double_click( chr->client );  // open the container on the client's screen
×
593
  chr->add_remote_container( item );
×
594

595
  return new BLong( 1 );
×
596
}
597

598

599
BObjectImp* UOExecutorModule::mf_SecureTradeWin()
×
600
{
601
  Character* chr;
602
  Character* chr2;
603
  if ( !getCharacterParam( 0, chr ) || !getCharacterParam( 1, chr2 ) )
×
604
  {
605
    return new BError( "Invalid parameter type." );
×
606
  }
607

608
  if ( chr == chr2 )
×
609
  {
610
    return new BError( "You can't trade with yourself." );
×
611
  }
612

613
  if ( !chr->has_active_client() )
×
614
  {
615
    return new BError( "No client attached." );
×
616
  }
617
  if ( !chr2->has_active_client() )
×
618
  {
619
    return new BError( "No client attached." );
×
620
  }
621

622
  return Core::open_trade_window( chr->client, chr2 );
×
623
}
624

625
BObjectImp* UOExecutorModule::mf_CloseTradeWindow()
×
626
{
627
  Character* chr;
628
  if ( !getCharacterParam( 0, chr ) )
×
629
  {
630
    return new BError( "Invalid parameter type." );
×
631
  }
632
  if ( !chr->trading_with )
×
633
    return new BError( "Mobile is not currently trading with anyone." );
×
634

635
  Core::cancel_trade( chr );
×
636
  return new BLong( 1 );
×
637
}
638

639
BObjectImp* UOExecutorModule::mf_SendViewContainer()
7✔
640
{
641
  Character* chr;
642
  Item* item;
643
  if ( !getCharacterParam( 0, chr ) || !getItemParam( 1, item ) )
7✔
644
  {
645
    return new BError( "Invalid parameter type" );
×
646
  }
647

648
  if ( !chr->has_active_client() )
7✔
649
  {
650
    return new BError( "No client attached" );
×
651
  }
652
  if ( !item->isa( UOBJ_CLASS::CLASS_CONTAINER ) )
7✔
653
  {
654
    return new BError( "That isn't a container" );
×
655
  }
656
  UContainer* cont = static_cast<UContainer*>( item );
7✔
657
  chr->client->pause();
7✔
658
  send_open_gump( chr->client, *cont );
7✔
659
  send_container_contents( chr->client, *cont );
7✔
660
  chr->client->restart();
7✔
661
  return new BLong( 1 );
7✔
662
}
663

664
BObjectImp* UOExecutorModule::mf_FindObjtypeInContainer()
×
665
{
666
  Item* item;
667
  unsigned int objtype;
668
  int flags;
669
  if ( !getItemParam( 0, item ) || !getObjtypeParam( 1, objtype ) )
×
670
  {
671
    return new BError( "Invalid parameter type" );
×
672
  }
673
  if ( !item->isa( UOBJ_CLASS::CLASS_CONTAINER ) )
×
674
  {
675
    return new BError( "That is not a container" );
×
676
  }
677
  if ( !getParam( 2, flags ) )
×
678
    flags = 0;
×
679

680
  UContainer* cont = static_cast<UContainer*>( item );
×
681
  Item* found = cont->find_objtype( objtype, flags );
×
682
  if ( found == nullptr )
×
683
    return new BError( "No items were found" );
×
684
  return new EItemRefObjImp( found );
×
685
}
686

687

688
BObjectImp* UOExecutorModule::mf_SendSysMessage()
×
689
{
690
  Character* chr;
691
  const String* ptext;
692
  unsigned short font;
693
  unsigned short color;
694

695
  if ( getCharacterParam( 0, chr ) && ( ( ptext = getStringParam( 1 ) ) != nullptr ) &&
×
696
       getParam( 2, font ) && getParam( 3, color ) )
×
697
  {
698
    if ( chr->has_active_client() )
×
699
    {
700
      if ( !ptext->hasUTF8Characters() )
×
701
        send_sysmessage( chr->client, ptext->data(), font, color );
×
702
      else
703
        Core::send_sysmessage_unicode( chr->client, ptext->value(), "ENU", font, color );
×
704
      return new BLong( 1 );
×
705
    }
706

707
    return new BError( "Mobile has no active client" );
×
708
  }
709

710
  return new BError( "Invalid parameter type" );
×
711
}
712

713
BObjectImp* UOExecutorModule::mf_PrintTextAbove()
1✔
714
{
715
  UObject* obj;
716
  const String* ptext;
717
  unsigned short font;
718
  unsigned short color;
719
  int journal_print;
720

721
  if ( getUObjectParam( 0, obj ) && getStringParam( 1, ptext ) && getParam( 2, font ) &&
2✔
722
       getParam( 3, color ) && getParam( 4, journal_print ) )
2✔
723
  {
724
    if ( !ptext->hasUTF8Characters() )
1✔
725
      return new BLong( say_above( obj, ptext->data(), font, color, journal_print ) );
1✔
726
    return new BLong( say_above_unicode( obj, ptext->value(), "ENU", font, color, journal_print ) );
×
727
  }
728

729
  return new BError( "A parameter was invalid" );
×
730
}
731
BObjectImp* UOExecutorModule::mf_PrintTextAbovePrivate()
×
732
{
733
  Character* chr;
734
  UObject* obj;
735
  const String* ptext;
736
  unsigned short font;
737
  unsigned short color;
738
  int journal_print;
739

740
  if ( getUObjectParam( 0, obj ) && getStringParam( 1, ptext ) && getCharacterParam( 2, chr ) &&
×
741
       getParam( 3, font ) && getParam( 4, color ) && getParam( 5, journal_print ) )
×
742
  {
743
    if ( !ptext->hasUTF8Characters() )
×
744
      return new BLong( private_say_above( chr, obj, ptext->data(), font, color, journal_print ) );
×
745
    return new BLong( private_say_above_unicode( chr, obj, ptext->value(), "ENU", font, color ) );
×
746
  }
747

748
  return new BError( "A parameter was invalid" );
×
749
}
750

751
// const int TGTOPT_NOCHECK_LOS = 0x0000; // currently unused
752
const int TGTOPT_CHECK_LOS = 0x0001;
753
const int TGTOPT_HARMFUL = 0x0002;
754
const int TGTOPT_HELPFUL = 0x0004;
755
const int TGTOPT_ALLOW_NONLOCAL = 0x0008;
756

757
void handle_script_cursor( Character* chr, UObject* obj )
3✔
758
{
759
  if ( chr != nullptr && chr->client->gd->target_cursor_uoemod != nullptr )
3✔
760
  {
761
    auto& uoex = chr->client->gd->target_cursor_uoemod->uoexec();
3✔
762
    if ( obj != nullptr )
3✔
763
    {
764
      if ( chr->client->gd->target_cursor_uoemod->target_options & TGTOPT_HARMFUL )
2✔
765
      {
766
        auto attackable = Attackable{ obj };
1✔
767
        if ( attackable )
1✔
768
        {
769
          attackable.inform_engaged( Attackable{ chr } );
1✔
770
          if ( auto* mob = attackable.mobile() )
1✔
771
            chr->repsys_on_attack( mob );
1✔
772
        }
773
      }
774
      else if ( chr->client->gd->target_cursor_uoemod->target_options & TGTOPT_HELPFUL )
1✔
775
      {
776
        if ( obj->ismobile() )
×
777
        {
778
          Character* targetted_chr = static_cast<Character*>( obj );
×
779
          chr->repsys_on_help( targetted_chr );
×
780
        }
781
      }
782
      uoex.ValueStack.back().set( new BObject( obj->make_ref() ) );
2✔
783
    }
784
    // even on cancel, we wake the script up.
785
    uoex.revive();
3✔
786
    chr->client->gd->target_cursor_uoemod->target_cursor_chr = nullptr;
3✔
787
    chr->client->gd->target_cursor_uoemod = nullptr;
3✔
788
  }
789
}
3✔
790

791

792
BObjectImp* UOExecutorModule::mf_Target()
3✔
793
{
794
  Character* chr;
795
  if ( !getCharacterParam( 0, chr ) )
3✔
796
  {
797
    return new BError( "Invalid parameter type" );
×
798
  }
799
  if ( !chr->has_active_client() )
3✔
800
  {
801
    return new BError( "No client connected" );
×
802
  }
803
  if ( chr->target_cursor_busy() )
3✔
804
  {
805
    return new BError( "Client busy with another target cursor" );
×
806
  }
807

808
  if ( !getParam( 1, target_options ) )
3✔
809
    target_options = TGTOPT_CHECK_LOS;
×
810

811
  PKTBI_6C::CURSOR_TYPE crstype;
812

813
  if ( target_options & TGTOPT_HARMFUL )
3✔
814
    crstype = PKTBI_6C::CURSOR_TYPE_HARMFUL;
1✔
815
  else if ( target_options & TGTOPT_HELPFUL )
2✔
816
    crstype = PKTBI_6C::CURSOR_TYPE_HELPFUL;
×
817
  else
818
    crstype = PKTBI_6C::CURSOR_TYPE_NEUTRAL;
2✔
819

820
  if ( !uoexec().suspend() )
3✔
821
  {
822
    DEBUGLOGLN(
×
823
        "Script Error in '{}' PC={}: \n"
824
        "\tCall to function UO::Target():\n"
825
        "\tThe execution of this script can't be blocked!",
826
        scriptname(), exec.PC );
×
827
    return new Bscript::BError( "Script can't be blocked" );
×
828
  }
829

830
  TargetCursor* tgt_cursor = nullptr;
3✔
831

832
  bool is_los_checked = ( target_options & TGTOPT_CHECK_LOS ) && !chr->ignores_line_of_sight();
3✔
833
  bool allow_nonlocal = ( target_options & TGTOPT_ALLOW_NONLOCAL );
3✔
834
  if ( is_los_checked )
3✔
835
  {
836
    if ( allow_nonlocal )
1✔
837
    {
838
      tgt_cursor = &gamestate.target_cursors.los_checked_allow_nonlocal_script_cursor;
1✔
839
    }
840
    else
841
    {
842
      tgt_cursor = &gamestate.target_cursors.los_checked_script_cursor;
×
843
    }
844
  }
845
  else
846
  {
847
    if ( allow_nonlocal )
2✔
848
    {
849
      tgt_cursor = &gamestate.target_cursors.nolos_checked_allow_nonlocal_script_cursor;
1✔
850
    }
851
    else
852
    {
853
      tgt_cursor = &gamestate.target_cursors.nolos_checked_script_cursor;
1✔
854
    }
855
  }
856

857
  tgt_cursor->send_object_cursor( chr->client, crstype );
3✔
858

859
  chr->client->gd->target_cursor_uoemod = this;
3✔
860
  target_cursor_chr = chr;
3✔
861

862
  return new BLong( 0 );
3✔
863
}
864

865
BObjectImp* UOExecutorModule::mf_CancelTarget()
×
866
{
867
  Character* chr;
868
  if ( getCharacterParam( 0, chr ) )
×
869
  {
870
    if ( chr->has_active_client() )
×
871
    {
872
      if ( chr->target_cursor_busy() )
×
873
      {
874
        Network::PktHelper::PacketOut<Network::PktOut_6C> msg;
×
875
        msg->Write<u8>( PKTBI_6C::UNK1_00 );
×
876
        msg->offset += 4;  // u32 target_cursor_serial
×
877
        msg->Write<u8>( 0x3u );
×
878
        // rest 0
879
        msg.Send( chr->client, sizeof msg->buffer );
×
880
        return new BLong( 0 );
×
881
      }
×
882

883
      return new BError( "Client does not have an active target cursor" );
×
884
    }
885

886
    return new BError( "No client connected" );
×
887
  }
888

889
  return new BError( "Invalid parameter type" );
×
890
}
891

892
void handle_coord_cursor( Character* chr, PKTBI_6C* msg )
1✔
893
{
894
  if ( chr != nullptr && chr->client->gd->target_cursor_uoemod != nullptr )
1✔
895
  {
896
    auto& uoex = chr->client->gd->target_cursor_uoemod->uoexec();
1✔
897
    if ( msg != nullptr )
1✔
898
    {
899
      auto pos = Core::Pos3d( cfBEu16( msg->x ), cfBEu16( msg->y ), msg->z );
1✔
900
      BStruct* arr = new BStruct;
1✔
901
      arr->addMember( "x", new BLong( pos.x() ) );
1✔
902
      arr->addMember( "y", new BLong( pos.y() ) );
1✔
903
      arr->addMember( "z", new BLong( pos.z() ) );
1✔
904
      //      FIXME: Objtype CANNOT be trusted! Scripts must validate this, or, we must
905
      //      validate right here. Should we check map/static, or let that reside
906
      //      for scripts to run ListStatics? In theory, using Injection or similar,
907
      //      you could mine where no mineable tiles are by faking objtype in packet
908
      //      and still get the resources?
909
      arr->addMember( "objtype", new BLong( cfBEu16( msg->graphic ) ) );
1✔
910

911
      u32 selected_serial = cfBEu32( msg->selected_serial );
1✔
912
      if ( selected_serial )
1✔
913
      {
914
        UObject* obj = system_find_object( selected_serial );
×
915
        if ( obj )
×
916
        {
917
          arr->addMember( obj->target_tag(), obj->make_ref() );
×
918
        }
919
      }
920

921
      //      Never trust packet's objtype is the reason here. They should never
922
      //      target on other realms. DUH!
923
      arr->addMember( "realm", new String( chr->realm()->name() ) );
1✔
924

925
      Multi::UMulti* multi = chr->realm()->find_supporting_multi( pos );
1✔
926
      if ( multi != nullptr )
1✔
927
        arr->addMember( "multi", multi->make_ref() );
×
928

929
      uoex.ValueStack.back().set( new BObject( arr ) );
1✔
930
    }
931

932
    uoex.revive();
1✔
933
    chr->client->gd->target_cursor_uoemod->target_cursor_chr = nullptr;
1✔
934
    chr->client->gd->target_cursor_uoemod = nullptr;
1✔
935
  }
936
}
1✔
937

938
BObjectImp* UOExecutorModule::mf_TargetCoordinates()
1✔
939
{
940
  Character* chr;
941
  if ( !getCharacterParam( 0, chr ) )
1✔
942
  {
943
    return new BError( "Invalid parameter type" );
×
944
  }
945
  if ( !chr->has_active_client() )
1✔
946
  {
947
    return new BError( "Mobile has no active client" );
×
948
  }
949
  if ( chr->target_cursor_busy() )
1✔
950
  {
951
    return new BError( "Client has an active target cursor" );
×
952
  }
953

954
  if ( !uoexec().suspend() )
1✔
955
  {
956
    DEBUGLOGLN(
×
957
        "Script Error in '{}' PC={}: \n"
958
        "\tCall to function UO::TargetCoordinates():\n"
959
        "\tThe execution of this script can't be blocked!",
960
        scriptname(), exec.PC );
×
961
    return new Bscript::BError( "Script can't be blocked" );
×
962
  }
963

964
  gamestate.target_cursors.script_cursor2.send_coord_cursor( chr->client );
1✔
965
  chr->client->gd->target_cursor_uoemod = this;
1✔
966
  target_cursor_chr = chr;
1✔
967
  return new BLong( 0 );
1✔
968
}
969

970
BObjectImp* UOExecutorModule::mf_TargetMultiPlacement()
×
971
{
972
  Character* chr;
973
  unsigned int objtype, hue;
974
  int flags;
975
  int xoffset, yoffset;
976
  if ( !( getCharacterParam( 0, chr ) && getObjtypeParam( 1, objtype ) && getParam( 2, flags ) &&
×
977
          getParam( 3, xoffset ) && getParam( 4, yoffset ) && getParam( 5, hue ) ) )
×
978
  {
979
    return new BError( "Invalid parameter type" );
×
980
  }
981

982

983
  if ( !chr->has_active_client() )
×
984
  {
985
    return new BError( "No client attached" );
×
986
  }
987

988
  if ( chr->target_cursor_busy() )
×
989
  {
990
    return new BError( "Client busy with another target cursor" );
×
991
  }
992

993
  if ( find_itemdesc( objtype ).type != ItemDesc::BOATDESC &&
×
994
       find_itemdesc( objtype ).type != ItemDesc::HOUSEDESC )
×
995
  {
996
    return new BError( "Object Type is out of range for Multis" );
×
997
  }
998

999
  if ( !uoexec().suspend() )
×
1000
  {
1001
    DEBUGLOGLN(
×
1002
        "Script Error in '{}' PC={}: \n"
1003
        "\tCall to function UO::TargetMultiPlacement():\n"
1004
        "\tThe execution of this script can't be blocked!",
1005
        scriptname(), exec.PC );
×
1006
    return new Bscript::BError( "Script can't be blocked" );
×
1007
  }
1008

1009
  chr->client->gd->target_cursor_uoemod = this;
×
1010
  target_cursor_chr = chr;
×
1011

1012
  gamestate.target_cursors.multi_placement_cursor.send_placemulti(
×
1013
      chr->client, objtype, flags, (s16)xoffset, (s16)yoffset, hue );
1014

1015
  return new BLong( 0 );
×
1016
}
1017

1018
BObjectImp* UOExecutorModule::mf_GetObjType()
×
1019
{
1020
  Item* item;
1021
  Character* chr;
1022

1023
  if ( getItemParam( 0, item ) )
×
1024
  {
1025
    return new BLong( item->objtype_ );
×
1026
  }
1027
  if ( getCharacterParam( 0, chr ) )
×
1028
  {
1029
    return new BLong( chr->objtype_ );
×
1030
  }
1031

1032
  return new BLong( 0 );
×
1033
}
1034

1035
// FIXME character needs an Accessible that takes an Item*
1036
BObjectImp* UOExecutorModule::mf_Accessible()
×
1037
{
1038
  Character* chr;
1039
  Item* item;
1040
  int range;
1041

1042
  if ( getCharacterParam( 0, chr ) && getItemParam( 1, item ) )
×
1043
  {
1044
    // Range defaults to -1 if it's not defined in the .em file (old scripts)
1045
    // or the user provides a weird object.
1046
    if ( exec.numParams() < 3 || !getParam( 2, range ) )
×
1047
      range = -1;
×
1048

1049
    if ( chr->can_access( item, range ) )
×
1050
      return new BLong( 1 );
×
1051
    return new BLong( 0 );
×
1052
  }
1053
  return new BLong( 0 );
×
1054
}
1055

1056
BObjectImp* UOExecutorModule::mf_GetAmount()
×
1057
{
1058
  Item* item;
1059

1060
  if ( getItemParam( 0, item ) )
×
1061
  {
1062
    return new BLong( item->getamount() );
×
1063
  }
1064

1065
  return new BLong( 0 );
×
1066
}
1067

1068

1069
// FIXME, copy-pasted from NPC, copy-pasted to CreateItemInContainer
1070
BObjectImp* UOExecutorModule::mf_CreateItemInBackpack()
3✔
1071
{
1072
  Character* chr;
1073
  const ItemDesc* descriptor;
1074
  unsigned short amount;
1075
  int px;
1076
  int py;
1077

1078
  if ( getCharacterParam( 0, chr ) && getObjtypeParam( 1, descriptor ) && getParam( 2, amount ) &&
6✔
1079
       getParam( 3, px, -1, 65535 ) && getParam( 4, py, -1, 65535 ) &&
9✔
1080
       item_create_params_ok( descriptor->objtype, amount ) )
3✔
1081
  {
1082
    UContainer* backpack = chr->backpack();
3✔
1083
    if ( backpack != nullptr )
3✔
1084
    {
1085
      std::optional<Core::Pos2d> pos;
3✔
1086
      if ( px >= 0 && py >= 0 )
3✔
1087
        pos = Core::Pos2d( static_cast<u16>( px ), static_cast<u16>( py ) );
1✔
1088
      return _create_item_in_container( backpack, descriptor, amount, false, pos, this );
3✔
1089
    }
1090

1091
    return new BError( "Character has no backpack." );
×
1092
  }
1093

1094
  return new BError( "A parameter was invalid." );
×
1095
}
1096

1097
BObjectImp* _complete_create_item_at_location( Item* item, const Core::Pos4d& pos )
6,124✔
1098
{
1099
  item->setposition( pos );
6,124✔
1100

1101
  // ITEMDESCTODO: use the original descriptor
1102
  const ItemDesc& id = find_itemdesc( item->objtype_ );
6,124✔
1103
  if ( !id.create_script.empty() )
6,124✔
1104
  {
1105
    BObjectImp* res = run_script_to_completion( id.create_script, new EItemRefObjImp( item ) );
×
1106
    if ( !res->isTrue() )
×
1107
    {
1108
      item->destroy();
×
1109
      return res;
×
1110
    }
1111

1112
    BObject ob( res );
×
1113
  }
×
1114

1115
  update_item_to_inrange( item );
6,124✔
1116
  add_item_to_world( item );
6,124✔
1117
  register_with_supporting_multi( item );
6,124✔
1118
  return new EItemRefObjImp( item );
6,124✔
1119
}
1120

1121
BObjectImp* UOExecutorModule::mf_CreateItemAtLocation( /* x,y,z,objtype,amount,realm */ )
6,124✔
1122
{
1123
  Core::Pos4d pos;
6,124✔
1124
  const ItemDesc* itemdesc;
1125
  unsigned short amount;
1126
  if ( getPos4dParam( 0, 1, 2, 5, &pos ) && getObjtypeParam( 3, itemdesc ) &&
12,248✔
1127
       getParam( 4, amount, 1, 60000 ) && item_create_params_ok( itemdesc->objtype, amount ) )
12,248✔
1128
  {
1129
    if ( !( Plib::tile_flags( itemdesc->graphic ) & Plib::FLAG::STACKABLE ) && ( amount != 1 ) )
6,124✔
1130
    {
1131
      return new BError( "That item is not stackable.  Create one at a time." );
×
1132
    }
1133

1134
    Item* item = Item::create( *itemdesc );
6,124✔
1135
    if ( item != nullptr )
6,124✔
1136
    {
1137
      item->setamount( amount );
6,124✔
1138
      return _complete_create_item_at_location( item, pos );
6,124✔
1139
    }
1140

1141
    return new BError( "Unable to create item of objtype " + Clib::hexint( itemdesc->objtype ) );
×
1142
  }
1143
  return new BError( "Invalid parameter type" );
×
1144
}
1145

1146
BObjectImp* UOExecutorModule::mf_CreateItemCopyAtLocation( /* x,y,z,item,realm */ )
×
1147
{
1148
  Core::Pos4d pos;
×
1149
  Item* origitem;
1150
  if ( getPos4dParam( 0, 1, 2, 4, &pos ) && getItemParam( 3, origitem ) )
×
1151
  {
1152
    if ( origitem->script_isa( POLCLASS_MULTI ) )
×
1153
      return new BError( "This function does not work with Multi objects." );
×
1154

1155
    Item* item = origitem->clone();
×
1156
    if ( item != nullptr )
×
1157
    {
1158
      return _complete_create_item_at_location( item, pos );
×
1159
    }
1160

1161
    return new BError( "Unable to clone item" );
×
1162
  }
1163

1164
  return new BError( "Invalid parameter type" );
×
1165
}
1166

1167
BObjectImp* UOExecutorModule::mf_CreateMultiAtLocation( /* x,y,z,objtype,flags,realm */ )
37✔
1168
{
1169
  Core::Pos4d pos;
37✔
1170
  const ItemDesc* descriptor;
1171
  int flags = 0;
37✔
1172
  if ( !( getPos4dParam( 0, 1, 2, 5, &pos ) && getParam( 4, flags ) &&
74✔
1173
          getObjtypeParam( 3, descriptor ) ) )
37✔
1174
  {
1175
    return new BError( "Invalid parameter type" );
×
1176
  }
1177
  if ( descriptor->type != ItemDesc::BOATDESC && descriptor->type != ItemDesc::HOUSEDESC )
37✔
1178
  {
1179
    return new BError( "That objtype is not a Multi" );
×
1180
  }
1181

1182
  return Multi::UMulti::scripted_create( *descriptor, pos, flags );
37✔
1183
}
1184

1185
void replace_properties( Clib::ConfigElem& elem, BStruct* custom )
7✔
1186
{
1187
  for ( const auto& [name, refobj] : custom->contents() )
75✔
1188
  {
1189
    BObjectImp* ref = refobj->impptr();
68✔
1190

1191
    if ( name == "CProps" )
68✔
1192
    {
1193
      if ( auto* cpropdict = impptrIf<BDictionary>( ref ) )
5✔
1194
      {
1195
        const BDictionary::Contents& cprop_cont = cpropdict->contents();
5✔
1196
        for ( const auto& [key, valueobj] : cprop_cont )
10✔
1197
        {
1198
          elem.add_prop( "cprop", ( key->getStringRep() + "\t" + valueobj->impptr()->pack() ) );
5✔
1199
        }
1200
      }
1201
      else
1202
      {
1203
        throw std::runtime_error( "NPC override_properties: CProps must be a dictionary, but is: " +
×
1204
                                  std::string( ref->typeOf() ) );
×
1205
      }
1206
    }
1207
    else
1208
    {
1209
      elem.clear_prop( name.c_str() );
63✔
1210
      elem.add_prop( name, ref->getStringRep() );
63✔
1211
    }
1212
  }
1213
}
7✔
1214

1215
BObjectImp* UOExecutorModule::mf_CreateNpcFromTemplate()
76✔
1216
{
1217
  const String* tmplname;
1218
  Core::Pos4d pos;
76✔
1219
  int forceInt;
1220
  bool forceLocation;
1221
  if ( !( getStringParam( 0, tmplname ) && getPos4dParam( 1, 2, 3, 5, &pos ) ) )
76✔
1222
  {
1223
    return new BError( "Invalid parameter type" );
×
1224
  }
1225
  BObjectImp* imp = getParamImp( 4 );
76✔
1226
  BStruct* custom_struct = nullptr;
76✔
1227
  if ( imp->isa( BObjectImp::OTLong ) )
76✔
1228
  {
1229
    custom_struct = nullptr;
69✔
1230
  }
1231
  else if ( imp->isa( BObjectImp::OTStruct ) )
7✔
1232
  {
1233
    custom_struct = static_cast<BStruct*>( imp );
7✔
1234
  }
1235
  else
1236
  {
1237
    return new BError( std::string( "Parameter 4 must be a Struct or Integer(0), got " ) +
×
1238
                       BObjectImp::typestr( imp->type() ) );
×
1239
  }
1240
  if ( !getParam( 6, forceInt ) )
76✔
1241
  {
1242
    forceLocation = false;
×
1243
  }
1244
  else
1245
  {
1246
    forceLocation = forceInt ? true : false;
76✔
1247
  }
1248

1249
  Clib::ConfigElem elem;
76✔
1250
  bool found = FindNpcTemplate( tmplname->data(), elem );
76✔
1251

1252
  if ( !found )
76✔
1253
  {
1254
    return new BError( "NPC template '" + tmplname->value() + "' not found" );
×
1255
  }
1256
  Plib::MOVEMODE movemode = Character::decode_movemode( elem.read_string( "MoveMode", "L" ) );
76✔
1257

1258
  short newz;
1259
  Multi::UMulti* dummy_multi = nullptr;
76✔
1260
  Item* dummy_walkon = nullptr;
76✔
1261
  if ( !pos.realm()->walkheight( pos.xy(), pos.z(), &newz, &dummy_multi, &dummy_walkon, true,
76✔
1262
                                 movemode ) )
1263
  {
1264
    if ( !forceLocation )
2✔
1265
      return new BError( "Not a valid location for an NPC!" );
×
1266
  }
1267
  if ( !forceLocation )
76✔
1268
    pos.z( Clib::clamp_convert<s8>( newz ) );
72✔
1269

1270

1271
  NpcRef npc;
76✔
1272
  // readProperties can throw, if stuff is missing.
1273
  try
1274
  {
1275
    npc.set( new NPC( elem.remove_ushort( "OBJTYPE" ), elem ) );
76✔
1276
    npc->set_dirty();
76✔
1277
    elem.clear_prop( "Serial" );
76✔
1278
    elem.clear_prop( "X" );
76✔
1279
    elem.clear_prop( "Y" );
76✔
1280
    elem.clear_prop( "Z" );
76✔
1281

1282
    elem.add_prop( "Serial", GetNextSerialNumber() );
76✔
1283
    // FIXME sanity check
1284
    elem.add_prop( "X", pos.x() );
76✔
1285
    elem.add_prop( "Y", pos.y() );
76✔
1286
    elem.add_prop( "Z", (s16)pos.z() );
76✔
1287
    elem.add_prop( "Realm", pos.realm()->name() );
76✔
1288
    if ( custom_struct != nullptr )
76✔
1289
      replace_properties( elem, custom_struct );
7✔
1290
    npc->readPropertiesForNewNPC( elem );
76✔
1291
    ////HASH
1292
    objStorageManager.objecthash.Insert( npc.get() );
76✔
1293
    ////
1294

1295

1296
    // characters.push_back( npc.get() );
1297
    SetCharacterWorldPosition( npc.get(), Realms::WorldChangeReason::NpcCreate );
76✔
1298
    WorldIterator<OnlinePlayerFilter>::InMaxVisualRange(
76✔
1299
        npc.get(),
76✔
1300
        [&]( Character* zonechr )
76✔
1301
        {
1302
          if ( zonechr->in_visual_range( npc.get() ) )
24✔
1303
            send_char_data( zonechr->client, npc.get() );
21✔
1304
        } );
24✔
1305
    pos.realm()->notify_entered( *npc );
76✔
1306
    if ( dummy_multi )
76✔
1307
    {
1308
      dummy_multi->register_object( npc.get() );
9✔
1309
      if ( npc->registered_multi == 0 )
9✔
1310
      {
1311
        npc->registered_multi = dummy_multi->serial;
9✔
1312
        dummy_multi->walk_on( npc.get() );
9✔
1313
      }
1314
    }
1315
    else
1316
    {
1317
      if ( npc->registered_multi > 0 )
67✔
1318
      {
1319
        Multi::UMulti* multi = system_find_multi( npc->registered_multi );
×
1320
        if ( multi != nullptr )
×
1321
        {
1322
          multi->unregister_object( npc.get() );
×
1323
        }
1324
        npc->registered_multi = 0;
×
1325
      }
1326
    }
1327
    return new ECharacterRefObjImp( npc.get() );
76✔
1328
  }
1329
  catch ( std::exception& ex )
×
1330
  {
1331
    if ( npc.get() != nullptr )
×
1332
      npc->destroy();
×
1333
    return new BError( "Exception detected trying to create npc from template '" +
×
1334
                       tmplname->value() + "': " + ex.what() );
×
1335
  }
×
1336
}
76✔
1337

1338

1339
BObjectImp* UOExecutorModule::mf_SubtractAmount()
×
1340
{
1341
  Item* item;
1342
  unsigned short amount;
1343

1344
  if ( getItemParam( 0, item ) && getParam( 1, amount, 1, item->itemdesc().stack_limit ) )
×
1345
  {
1346
    if ( item->has_gotten_by() )
×
1347
      item->gotten_by()->clear_gotten_item();
×
1348
    else if ( item->inuse() && !is_reserved_to_me( item ) )
×
1349
      return new BError( "That item is being used." );
×
1350
    subtract_amount_from_item( item, amount );
×
1351
    return new BLong( 1 );
×
1352
  }
1353

1354
  return new BError( "Invalid parameter type" );
×
1355
}
1356

1357
BObjectImp* UOExecutorModule::mf_AddAmount()
×
1358
{
1359
  Item* item;
1360
  unsigned short amount;
1361

1362
  if ( getItemParam( 0, item ) && getParam( 1, amount, 1, MAX_STACK_ITEMS ) )
×
1363
  {
1364
    if ( item->inuse() && !is_reserved_to_me( item ) )
×
1365
    {
1366
      return new BError( "That item is being used." );
×
1367
    }
1368
    if ( ~Plib::tile_flags( item->graphic ) & Plib::FLAG::STACKABLE )
×
1369
    {
1370
      return new BError( "That item type is not stackable." );
×
1371
    }
1372
    if ( !item->can_add_to_self( amount, false ) )
×
1373
    {
1374
      return new BError( "Can't add that much to that stack" );
×
1375
    }
1376

1377
    unsigned short newamount = item->getamount();
×
1378
    newamount += amount;
×
1379
    item->setamount( newamount );
×
1380
    update_item_to_inrange( item );
×
1381

1382
    // DAVE added this 12/05: if in a Character's pack, update weight.
1383
    UpdateCharacterWeight( item );
×
1384

1385
    return new EItemRefObjImp( item );
×
1386
  }
1387

1388
  return new BError( "Invalid parameter type" );
×
1389
}
1390

1391
BObjectImp* UOExecutorModule::mf_PerformAction()
×
1392
{
1393
  Character* chr;
1394
  unsigned short actionval;
1395
  unsigned short framecount, repeatcount;
1396
  unsigned short backward, repeatflag, delay;
1397
  if ( getCharacterParam( 0, chr ) && getParam( 1, actionval ) && getParam( 2, framecount ) &&
×
1398
       getParam( 3, repeatcount ) && getParam( 4, backward ) && getParam( 5, repeatflag ) &&
×
1399
       getParam( 6, delay ) )
×
1400
  {
1401
    if ( !UACTION_IS_VALID( actionval ) )
×
1402
    {
1403
      return new BError( "Invalid parameter 'action'" );
×
1404
    }
1405

1406
    UACTION action = static_cast<UACTION>( actionval );
×
1407
    send_action_to_inrange(
×
1408
        chr, action, framecount, repeatcount, static_cast<DIRECTION_FLAG_OLD>( backward ),
1409
        static_cast<REPEAT_FLAG_OLD>( repeatflag ), static_cast<unsigned char>( delay ) );
1410
    return new BLong( 1 );
×
1411
  }
1412

1413
  return new BError( "Invalid parameter" );
×
1414
}
1415

1416
BObjectImp* UOExecutorModule::mf_PlaySoundEffect()
×
1417
{
1418
  UObject* chr;
1419
  int effect;
1420
  if ( getUObjectParam( 0, chr ) && getParam( 1, effect ) )
×
1421
  {
1422
    play_sound_effect( chr, static_cast<u16>( effect ) );
×
1423
    return new BLong( 1 );
×
1424
  }
1425

1426
  return new BError( "Invalid parameter" );
×
1427
}
1428

1429
BObjectImp* UOExecutorModule::mf_PlaySoundEffectPrivate()
×
1430
{
1431
  UObject* center;
1432
  int effect;
1433
  Character* forchr;
1434
  if ( getUObjectParam( 0, center ) && getParam( 1, effect ) && getCharacterParam( 2, forchr ) )
×
1435
  {
1436
    play_sound_effect_private( center, static_cast<u16>( effect ), forchr );
×
1437
    return new BLong( 1 );
×
1438
  }
1439

1440
  return new BError( "Invalid parameter" );
×
1441
}
1442

1443
BObjectImp* UOExecutorModule::mf_PlaySoundEffectXYZ()
×
1444
{
1445
  Core::Pos4d center;
×
1446
  int effect;
1447
  if ( getPos4dParam( 0, 1, 2, 4, &center ) && getParam( 3, effect ) )
×
1448
  {
1449
    play_sound_effect_xyz( center, static_cast<u16>( effect ) );
×
1450
    return new BLong( 1 );
×
1451
  }
1452

1453
  return new BError( "Invalid parameter" );
×
1454
}
1455

1456
BObjectImp* UOExecutorModule::mf_PlayMusic( /* char, music_id */ )
×
1457
{
1458
  Character* chr;
1459
  int musicid;
1460
  if ( getCharacterParam( 0, chr ) && getParam( 1, musicid ) )
×
1461
  {
1462
    send_midi( chr->client, static_cast<u16>( musicid ) );
×
1463
    return new BLong( 1 );
×
1464
  }
1465

1466
  return new BError( "Invalid parameter" );
×
1467
}
1468

1469
void menu_selection_made( Network::Client* client, MenuItem* mi, PKTIN_7D* msg )
×
1470
{
1471
  if ( client != nullptr )
×
1472
  {
1473
    Character* chr = client->chr;
×
1474
    if ( chr != nullptr && chr->client->gd->menu_selection_uoemod != nullptr )
×
1475
    {
1476
      auto& uoex = chr->client->gd->menu_selection_uoemod->uoexec();
×
1477
      if ( mi != nullptr && msg != nullptr )
×
1478
      {
1479
        BStruct* selection = new BStruct;
×
1480
        // FIXME should make sure objtype and choice are within valid range.
1481
        selection->addMember( "objtype", new BLong( mi->objtype_ ) );
×
1482
        selection->addMember( "graphic", new BLong( mi->graphic_ ) );
×
1483
        selection->addMember( "index",
×
1484
                              new BLong( cfBEu16( msg->choice ) ) );  // this has been validated
×
1485
        selection->addMember( "color", new BLong( mi->color_ ) );
×
1486
        uoex.ValueStack.back().set( new BObject( selection ) );
×
1487
      }
1488
      // 0 is already on the value stack, for the case of cancellation.
1489
      uoex.revive();
×
1490
      chr->client->gd->menu_selection_uoemod->menu_selection_chr = nullptr;
×
1491
      chr->client->gd->menu_selection_uoemod = nullptr;
×
1492
    }
1493
  }
1494
}
×
1495

1496
bool UOExecutorModule::getDynamicMenuParam( unsigned param, Menu*& menu )
×
1497
{
1498
  BApplicObjBase* aob = getApplicObjParam( param, &menu_type );
×
1499
  if ( aob != nullptr )
×
1500
  {
1501
    EMenuObjImp* menu_imp = static_cast<EMenuObjImp*>( aob );
×
1502
    menu = &menu_imp->value();
×
1503
    return true;
×
1504
  }
1505

1506
  return false;
×
1507
}
1508

1509
bool UOExecutorModule::getStaticOrDynamicMenuParam( unsigned param, Menu*& menu )
×
1510
{
1511
  BObjectImp* imp = getParamImp( param );
×
1512
  if ( imp->isa( BObjectImp::OTString ) )
×
1513
  {
1514
    String* pmenuname = static_cast<String*>( imp );
×
1515
    menu = Menu::find_menu( pmenuname->data() );
×
1516
    return ( menu != nullptr );
×
1517
  }
1518
  if ( imp->isa( BObjectImp::OTApplicObj ) )
×
1519
  {
1520
    BApplicObjBase* aob = static_cast<BApplicObjBase*>( imp );
×
1521
    if ( aob->object_type() == &menu_type )
×
1522
    {
1523
      EMenuObjImp* menu_imp = static_cast<EMenuObjImp*>( aob );
×
1524
      menu = &menu_imp->value();
×
1525
      return true;
×
1526
    }
1527
  }
1528
  DEBUGLOGLN( "SelectMenuItem: expected a menu name (static menu) or a CreateMenu() dynamic menu" );
×
1529
  return false;
×
1530
}
1531

1532
BObjectImp* UOExecutorModule::mf_SelectMenuItem2()
×
1533
{
1534
  Character* chr;
1535
  Menu* menu;
1536

1537
  if ( !getCharacterParam( 0, chr ) || !getStaticOrDynamicMenuParam( 1, menu ) ||
×
1538
       ( chr->client->gd->menu_selection_uoemod != nullptr ) )
×
1539
  {
1540
    return new BError( "Invalid parameter" );
×
1541
  }
1542

1543
  if ( menu == nullptr || !chr->has_active_client() || menu->menuitems_.empty() )
×
1544
  {
1545
    return new BError( "Client is busy, or menu is empty" );
×
1546
  }
1547

1548
  if ( !send_menu( chr->client, menu ) )
×
1549
  {
1550
    return new BError( "Menu too large" );
×
1551
  }
1552

1553
  if ( !uoexec().suspend() )
×
1554
  {
1555
    DEBUGLOGLN(
×
1556
        "Script Error in '{}' PC={}: \n"
1557
        "\tCall to function UO::SelectMenuItem():\n"
1558
        "\tThe execution of this script can't be blocked!",
1559
        scriptname(), exec.PC );
×
1560
    return new Bscript::BError( "Script can't be blocked" );
×
1561
  }
1562

1563
  chr->client->gd->menu = menu->getWeakPtr();
×
1564
  chr->client->gd->on_menu_selection = menu_selection_made;
×
1565
  chr->client->gd->menu_selection_uoemod = this;
×
1566
  menu_selection_chr = chr;
×
1567

1568
  return new BLong( 0 );
×
1569
}
1570

1571
void append_objtypes( ObjArray* objarr, Menu* menu )
×
1572
{
1573
  for ( const auto& menuitem : menu->menuitems_ )
×
1574
  {
1575
    if ( menuitem.submenu_id )
×
1576
    {
1577
      // Code Analyze: Commented out and replaced with tmp_menu due to hiding
1578
      // menu passed to function.
1579
      //      Menu* menu = find_menu( mi->submenu_id );
1580
      Menu* tmp_menu = Menu::find_menu( menuitem.submenu_id );
×
1581
      if ( tmp_menu != nullptr )
×
1582
        append_objtypes( objarr, tmp_menu );
×
1583
    }
1584
    else
1585
    {
1586
      objarr->addElement( new BLong( menuitem.objtype_ ) );
×
1587
    }
1588
  }
1589
}
×
1590

1591
BObjectImp* UOExecutorModule::mf_GetMenuObjTypes()
×
1592
{
1593
  Menu* menu;
1594
  if ( getStaticOrDynamicMenuParam( 0, menu ) )
×
1595
  {
1596
    std::unique_ptr<ObjArray> objarr( new ObjArray );
×
1597

1598
    append_objtypes( objarr.get(), menu );
×
1599

1600
    return objarr.release();
×
1601
  }
×
1602
  return new BLong( 0 );
×
1603
}
1604

1605
BObjectImp* UOExecutorModule::mf_ApplyConstraint()
×
1606
{
1607
  ObjArray* arr;
1608
  StoredConfigFile* cfile;
1609
  const String* propname_str;
1610
  int amthave;
1611

1612
  if ( getObjArrayParam( 0, arr ) && getStoredConfigFileParam( *this, 1, cfile ) &&
×
1613
       getStringParam( 2, propname_str ) && getParam( 3, amthave ) )
×
1614
  {
1615
    std::unique_ptr<ObjArray> newarr( new ObjArray );
×
1616

1617
    for ( auto& ref : arr->ref_arr )
×
1618
    {
1619
      BObject* bo = ref.get();
×
1620
      if ( bo == nullptr )
×
1621
        continue;
×
1622
      if ( !bo->isa( BObjectImp::OTLong ) )
×
1623
        continue;
×
1624
      BLong* blong = bo->impptr<BLong>();
×
1625
      unsigned int objtype = static_cast<u32>( blong->value() );
×
1626

1627
      ref_ptr<StoredConfigElem> celem = cfile->findelem( objtype );
×
1628
      if ( celem.get() == nullptr )
×
1629
        continue;
×
1630

1631
      BObjectImp* propval = celem->getimp( propname_str->value() );
×
1632
      if ( propval == nullptr )
×
1633
        continue;
×
1634
      if ( !propval->isa( BObjectImp::OTLong ) )
×
1635
        continue;
×
1636
      BLong* amtreq = static_cast<BLong*>( propval );
×
1637
      if ( amtreq->value() > amthave )
×
1638
        continue;
×
1639
      newarr->addElement( new BLong( objtype ) );
×
1640
    }
×
1641

1642
    return newarr.release();
×
1643
  }
×
1644

1645
  return new UninitObject;
×
1646
}
1647

1648
BObjectImp* UOExecutorModule::mf_CreateMenu()
×
1649
{
1650
  const String* title;
1651
  if ( getStringParam( 0, title ) )
×
1652
  {
1653
    Menu temp;
×
1654
    temp.menu_id = 0;
×
1655
    Clib::stracpy( temp.title, title->data(), sizeof temp.title );
×
1656
    return new EMenuObjImp( temp );
×
1657
  }
×
1658
  return new BLong( 0 );
×
1659
}
1660

1661
BObjectImp* UOExecutorModule::mf_AddMenuItem()
×
1662
{
1663
  Menu* menu;
1664
  unsigned int objtype;
1665
  const String* text;
1666
  unsigned short color;
1667

1668
  if ( getDynamicMenuParam( 0, menu ) && getObjtypeParam( 1, objtype ) &&
×
1669
       getStringParam( 2, text ) && getParam( 3, color ) )
×
1670
  {
1671
    menu->menuitems_.emplace_back();
×
1672
    MenuItem* mi = &menu->menuitems_.back();
×
1673
    mi->objtype_ = objtype;
×
1674
    mi->graphic_ = getgraphic( objtype );
×
1675
    Clib::stracpy( mi->title, text->data(), sizeof mi->title );
×
1676
    mi->color_ = color & settingsManager.ssopt.item_color_mask;
×
1677
    return new BLong( 1 );
×
1678
  }
1679

1680
  return new BLong( 0 );
×
1681
}
1682

1683
BObjectImp* UOExecutorModule::mf_GetObjProperty()
80✔
1684
{
1685
  UObject* uobj;
1686
  const String* propname_str;
1687
  if ( getUObjectParam( 0, uobj ) && getStringParam( 1, propname_str ) )
80✔
1688
  {
1689
    std::string val;
80✔
1690
    if ( uobj->getprop( propname_str->value(), val ) )
80✔
1691
    {
1692
      return BObjectImp::unpack( val.c_str() );
34✔
1693
    }
1694

1695
    return new BError( "Property not found" );
46✔
1696
  }
80✔
1697

1698
  return new BError( "Invalid parameter type" );
×
1699
}
1700

1701
BObjectImp* UOExecutorModule::mf_SetObjProperty()
9✔
1702
{
1703
  UObject* uobj;
1704
  const String* propname_str;
1705
  if ( getUObjectParam( 0, uobj ) && getStringParam( 1, propname_str ) )
9✔
1706
  {
1707
    BObjectImp* propval = getParamImp( 2 );
9✔
1708
    uobj->setprop( propname_str->value(), propval->pack() );
9✔
1709
    return new BLong( 1 );
9✔
1710
  }
1711

1712
  return new BError( "Invalid parameter type" );
×
1713
}
1714

1715
BObjectImp* UOExecutorModule::mf_EraseObjProperty()
×
1716
{
1717
  UObject* uobj;
1718
  const String* propname_str;
1719
  if ( getUObjectParam( 0, uobj ) && getStringParam( 1, propname_str ) )
×
1720
  {
1721
    uobj->eraseprop( propname_str->value() );
×
1722
    return new BLong( 1 );
×
1723
  }
1724

1725
  return new BError( "Invalid parameter type" );
×
1726
}
1727
BObjectImp* UOExecutorModule::mf_GetObjPropertyNames()
×
1728
{
1729
  UObject* uobj;
1730
  if ( getUObjectParam( 0, uobj ) )
×
1731
  {
1732
    std::vector<std::string> propnames;
×
1733
    uobj->getpropnames( propnames );
×
1734
    std::unique_ptr<ObjArray> arr( new ObjArray );
×
1735
    for ( const auto& propname : propnames )
×
1736
    {
1737
      arr->addElement( new String( propname ) );
×
1738
    }
1739
    return arr.release();
×
1740
  }
×
1741

1742
  return new BError( "Invalid parameter type" );
×
1743
}
1744

1745

1746
BObjectImp* UOExecutorModule::mf_GetGlobalProperty()
49✔
1747
{
1748
  const String* propname_str;
1749
  if ( getStringParam( 0, propname_str ) )
49✔
1750
  {
1751
    std::string val;
49✔
1752
    if ( gamestate.global_properties->getprop( propname_str->value(), val ) )
49✔
1753
    {
1754
      return BObjectImp::unpack( val.c_str() );
43✔
1755
    }
1756

1757
    return new BError( "Property not found" );
6✔
1758
  }
49✔
1759

1760
  return new BError( "Invalid parameter type" );
×
1761
}
1762

1763
BObjectImp* UOExecutorModule::mf_SetGlobalProperty()
24✔
1764
{
1765
  const String* propname_str;
1766
  if ( exec.getStringParam( 0, propname_str ) )
24✔
1767
  {
1768
    BObjectImp* propval = exec.getParamImp( 1 );
24✔
1769
    gamestate.global_properties->setprop( propname_str->value(), propval->pack() );
24✔
1770
    return new BLong( 1 );
24✔
1771
  }
1772

1773
  return new BError( "Invalid parameter type" );
×
1774
}
1775

1776
BObjectImp* UOExecutorModule::mf_EraseGlobalProperty()
×
1777
{
1778
  const String* propname_str;
1779
  if ( getStringParam( 0, propname_str ) )
×
1780
  {
1781
    gamestate.global_properties->eraseprop( propname_str->value() );
×
1782
    return new BLong( 1 );
×
1783
  }
1784

1785
  return new BError( "Invalid parameter type" );
×
1786
}
1787

1788
BObjectImp* UOExecutorModule::mf_GetGlobalPropertyNames()
×
1789
{
1790
  std::vector<std::string> propnames;
×
1791
  gamestate.global_properties->getpropnames( propnames );
×
1792
  std::unique_ptr<ObjArray> arr( new ObjArray );
×
1793
  for ( const auto& propname : propnames )
×
1794
  {
1795
    arr->addElement( new String( propname ) );
×
1796
  }
1797
  return arr.release();
×
1798
}
×
1799

1800
BObjectImp* UOExecutorModule::mf_PlayMovingEffect()
×
1801
{
1802
  UObject* src;
1803
  UObject* dst;
1804
  unsigned short effect;
1805
  int speed;
1806
  int loop;
1807
  int explode;
1808
  if ( getUObjectParam( 0, src ) && getUObjectParam( 1, dst ) && getParam( 2, effect ) &&
×
1809
       getParam( 3, speed, UCHAR_MAX ) && getParam( 4, loop, UCHAR_MAX ) &&
×
1810
       getParam( 5, explode, UCHAR_MAX ) )
×
1811
  {
1812
    if ( src->realm() != dst->realm() )
×
1813
      return new BError( "Realms must match" );
×
1814
    play_moving_effect( src, dst, effect, static_cast<unsigned char>( speed ),
×
1815
                        static_cast<unsigned char>( loop ), static_cast<unsigned char>( explode ) );
1816
    return new BLong( 1 );
×
1817
  }
1818

1819
  return new BLong( 0 );
×
1820
}
1821

1822
BObjectImp* UOExecutorModule::mf_PlayMovingEffectXYZ()
×
1823
{
1824
  unsigned short effect;
1825
  int speed;
1826
  int loop;
1827
  int explode;
1828
  Realms::Realm* realm;
1829
  Pos3d src, dst;
×
1830
  if ( getRealmParam( 10, &realm ) && getPos3dParam( 0, 1, 2, &src, realm ) &&
×
1831
       getPos3dParam( 3, 4, 5, &dst, realm ) && getParam( 6, effect ) &&
×
1832
       getParam( 7, speed, UCHAR_MAX ) && getParam( 8, loop, UCHAR_MAX ) &&
×
1833
       getParam( 9, explode, UCHAR_MAX ) )
×
1834
  {
1835
    play_moving_effect2( src, dst, effect, static_cast<signed char>( speed ),
×
1836
                         static_cast<signed char>( loop ), static_cast<signed char>( explode ),
×
1837
                         realm );
1838
    return new BLong( 1 );
×
1839
  }
1840

1841
  return nullptr;
×
1842
}
1843

1844
// FIXME add/test:stationary
1845

1846
BObjectImp* UOExecutorModule::mf_PlayObjectCenteredEffect()
×
1847
{
1848
  UObject* src;
1849
  unsigned short effect;
1850
  int speed;
1851
  int loop;
1852
  if ( getUObjectParam( 0, src ) && getParam( 1, effect ) && getParam( 2, speed, UCHAR_MAX ) &&
×
1853
       getParam( 3, loop, UCHAR_MAX ) )
×
1854
  {
1855
    play_object_centered_effect( src, effect, static_cast<unsigned char>( speed ),
×
1856
                                 static_cast<unsigned char>( loop ) );
1857
    return new BLong( 1 );
×
1858
  }
1859

1860
  return nullptr;
×
1861
}
1862

1863
BObjectImp* UOExecutorModule::mf_PlayStationaryEffect()
×
1864
{
1865
  Pos4d pos;
×
1866
  unsigned short effect;
1867
  int speed, loop, explode;
1868
  if ( getPos4dParam( 0, 1, 2, 7, &pos ) && getParam( 3, effect ) &&
×
1869
       getParam( 4, speed, UCHAR_MAX ) && getParam( 5, loop, UCHAR_MAX ) &&
×
1870
       getParam( 6, explode, UCHAR_MAX ) )
×
1871
  {
1872
    play_stationary_effect( pos, effect, static_cast<unsigned char>( speed ),
×
1873
                            static_cast<unsigned char>( loop ),
1874
                            static_cast<unsigned char>( explode ) );
1875
    return new BLong( 1 );
×
1876
  }
1877

1878
  return new BError( "Invalid parameter" );
×
1879
}
1880

1881

1882
BObjectImp* UOExecutorModule::mf_PlayMovingEffectEx()
×
1883
{
1884
  UObject* src;
1885
  UObject* dst;
1886
  unsigned short effect;
1887
  int speed;
1888
  int duration;
1889
  int hue;
1890
  int render;
1891
  int direction;
1892
  int explode;
1893
  unsigned short effect3d;
1894
  unsigned short effect3dexplode;
1895
  unsigned short effect3dsound;
1896

1897
  if ( getUObjectParam( 0, src ) && getUObjectParam( 1, dst ) && getParam( 2, effect ) &&
×
1898
       getParam( 3, speed, UCHAR_MAX ) && getParam( 4, duration, UCHAR_MAX ) &&
×
1899
       getParam( 5, hue, INT_MAX ) && getParam( 6, render, INT_MAX ) &&
×
1900
       getParam( 7, direction, UCHAR_MAX ) && getParam( 8, explode, UCHAR_MAX ) &&
×
1901
       getParam( 9, effect3d ) && getParam( 10, effect3dexplode ) && getParam( 11, effect3dsound ) )
×
1902
  {
1903
    if ( src->realm() != dst->realm() )
×
1904
      return new BError( "Realms must match" );
×
1905
    play_moving_effect_ex(
×
1906
        src, dst, effect, static_cast<unsigned char>( speed ),
1907
        static_cast<unsigned char>( duration ), static_cast<unsigned int>( hue ),
1908
        static_cast<unsigned int>( render ), static_cast<unsigned char>( direction ),
1909
        static_cast<unsigned char>( explode ), effect3d, effect3dexplode, effect3dsound );
1910
    return new BLong( 1 );
×
1911
  }
1912

1913
  return new BLong( 0 );
×
1914
}
1915

1916
BObjectImp* UOExecutorModule::mf_PlayMovingEffectXYZEx()
×
1917
{
1918
  Pos3d src, dst;
×
1919
  Realms::Realm* realm;
1920

1921
  unsigned short effect;
1922
  int speed;
1923
  int duration;
1924
  int hue;
1925
  int render;
1926
  int direction;
1927
  int explode;
1928
  unsigned short effect3d;
1929
  unsigned short effect3dexplode;
1930
  unsigned short effect3dsound;
1931

1932
  if ( getRealmParam( 6, &realm ) && getPos3dParam( 0, 1, 2, &src, realm ) &&
×
1933
       getPos3dParam( 3, 4, 5, &dst, realm ) && getParam( 7, effect ) &&
×
1934
       getParam( 8, speed, UCHAR_MAX ) && getParam( 9, duration, UCHAR_MAX ) &&
×
1935
       getParam( 10, hue, INT_MAX ) && getParam( 11, render, INT_MAX ) &&
×
1936
       getParam( 12, direction, UCHAR_MAX ) && getParam( 13, explode, UCHAR_MAX ) &&
×
1937
       getParam( 14, effect3d ) && getParam( 15, effect3dexplode ) &&
×
1938
       getParam( 16, effect3dsound ) )
×
1939
  {
1940
    play_moving_effect2_ex(
×
1941
        src, dst, realm, effect, static_cast<unsigned char>( speed ),
1942
        static_cast<unsigned char>( duration ), static_cast<unsigned int>( hue ),
1943
        static_cast<unsigned int>( render ), static_cast<unsigned char>( direction ),
1944
        static_cast<unsigned char>( explode ), effect3d, effect3dexplode, effect3dsound );
1945
    return new BLong( 1 );
×
1946
  }
1947

1948
  return nullptr;
×
1949
}
1950

1951
BObjectImp* UOExecutorModule::mf_PlayObjectCenteredEffectEx()
×
1952
{
1953
  UObject* src;
1954
  unsigned short effect;
1955
  int speed;
1956
  int duration;
1957
  int hue;
1958
  int render;
1959
  int layer;
1960
  unsigned short effect3d;
1961

1962
  if ( getUObjectParam( 0, src ) && getParam( 1, effect ) && getParam( 2, speed, UCHAR_MAX ) &&
×
1963
       getParam( 3, duration, UCHAR_MAX ) && getParam( 4, hue, INT_MAX ) &&
×
1964
       getParam( 5, render, INT_MAX ) && getParam( 6, layer, UCHAR_MAX ) &&
×
1965
       getParam( 7, effect3d ) )
×
1966
  {
1967
    play_object_centered_effect_ex(
×
1968
        src, effect, static_cast<unsigned char>( speed ), static_cast<unsigned char>( duration ),
1969
        static_cast<unsigned int>( hue ), static_cast<unsigned int>( render ),
1970
        static_cast<unsigned char>( layer ), effect3d );
1971
    return new BLong( 1 );
×
1972
  }
1973

1974
  return nullptr;
×
1975
}
1976

1977
BObjectImp* UOExecutorModule::mf_PlayStationaryEffectEx()
×
1978
{
1979
  Pos4d pos;
×
1980
  unsigned short effect;
1981
  int speed;
1982
  int duration;
1983
  int hue;
1984
  int render;
1985
  unsigned short effect3d;
1986

1987
  if ( getPos4dParam( 0, 1, 2, 3, &pos ) && getParam( 4, effect ) &&
×
1988
       getParam( 5, speed, UCHAR_MAX ) && getParam( 6, duration, UCHAR_MAX ) &&
×
1989
       getParam( 7, hue, INT_MAX ) && getParam( 8, render, INT_MAX ) && getParam( 9, effect3d ) )
×
1990
  {
1991
    play_stationary_effect_ex(
×
1992
        pos, effect, static_cast<unsigned char>( speed ), static_cast<unsigned char>( duration ),
1993
        static_cast<unsigned int>( hue ), static_cast<unsigned int>( render ), effect3d );
1994
    return new BLong( 1 );
×
1995
  }
1996

1997
  return new BError( "Invalid parameter" );
×
1998
}
1999

2000
BObjectImp* UOExecutorModule::mf_PlayLightningBoltEffect()
×
2001
{
2002
  UObject* center;
2003
  if ( getUObjectParam( 0, center ) )
×
2004
  {
2005
    play_lightning_bolt_effect( center );
×
2006
    return new BLong( 1 );
×
2007
  }
2008

2009
  return new BLong( 0 );
×
2010
}
2011

2012
BObjectImp* UOExecutorModule::mf_ListItemsNearLocation( /* x, y, z, range, realm */ )
×
2013
{
2014
  Core::Pos2d pos;
×
2015
  int z;
2016
  u16 range;
2017
  Realms::Realm* realm;
2018

2019
  if ( getRealmParam( 4, &realm ) && getPos2dParam( 0, 1, &pos, realm ) && getParam( 2, z ) &&
×
2020
       getParam( 3, range ) )
×
2021
  {
2022
    std::unique_ptr<ObjArray> newarr( new ObjArray );
×
2023
    WorldIterator<ItemFilter>::InRange(
×
2024
        pos, realm, range,
2025
        [&]( Item* item )
×
2026
        {
2027
          if ( item->in_range( pos, range ) )
×
2028
          {
2029
            if ( ( z == LIST_IGNORE_Z ) || ( abs( item->z() - z ) < CONST_DEFAULT_ZRANGE ) )
×
2030
              newarr->addElement( item->make_ref() );
×
2031
          }
2032
        } );
×
2033

2034
    return newarr.release();
×
2035
  }
×
2036

2037
  return nullptr;
×
2038
}
2039

2040
Range3d UOExecutorModule::internal_InBoxAreaChecks( const Pos2d& p1, int z1, const Pos2d& p2,
10✔
2041
                                                    int z2, Realms::Realm* realm )
2042
{
2043
  // no error checks for out of realm range on purpose
2044
  if ( ( z1 > z2 ) && z1 != LIST_IGNORE_Z && z2 != LIST_IGNORE_Z )
10✔
2045
    std::swap( z1, z2 );
×
2046
  if ( z1 == LIST_IGNORE_Z )
10✔
2047
    z1 = ZCOORD_MIN;
×
2048
  if ( z2 == LIST_IGNORE_Z )
10✔
2049
    z2 = ZCOORD_MAX;
×
2050
  return Range3d( Pos3d( p1, Clib::clamp_convert<s8>( z1 ) ),
10✔
2051
                  Pos3d( p2, Clib::clamp_convert<s8>( z2 ) ), realm );
20✔
2052
}
2053

2054
BObjectImp* UOExecutorModule::mf_ListObjectsInBox( /* x1, y1, z1, x2, y2, z2, realm */ )
×
2055
{
2056
  int z1, z2;
2057
  Pos2d p1, p2;
×
2058
  Realms::Realm* realm;
2059

2060
  if ( !( getPos2dParam( 0, 1, &p1 ) && getParam( 2, z1 ) && getPos2dParam( 3, 4, &p2 ) &&
×
2061
          getParam( 5, z2 ) && getRealmParam( 6, &realm ) ) )
×
2062
  {
2063
    return new BError( "Invalid parameter" );
×
2064
  }
2065
  Range3d box = internal_InBoxAreaChecks( p1, z1, p2, z2, realm );
×
2066
  z1 = box.nw_b().z();
×
2067
  z2 = box.se_t().z();
×
2068

2069
  std::unique_ptr<ObjArray> newarr( new ObjArray );
×
2070
  WorldIterator<MobileFilter>::InBox( box.range(), realm,
×
2071
                                      [&]( Mobile::Character* chr )
×
2072
                                      {
2073
                                        if ( chr->z() >= z1 && chr->z() <= z2 )
×
2074
                                        {
2075
                                          newarr->addElement( chr->make_ref() );
×
2076
                                        }
2077
                                      } );
×
2078
  WorldIterator<ItemFilter>::InBox( box.range(), realm,
×
2079
                                    [&]( Items::Item* item )
×
2080
                                    {
2081
                                      if ( item->z() >= z1 && item->z() <= z2 )
×
2082
                                      {
2083
                                        newarr->addElement( item->make_ref() );
×
2084
                                      }
2085
                                    } );
×
2086

2087
  return newarr.release();
×
2088
}
×
2089

2090
BObjectImp* UOExecutorModule::mf_ListItemsInBoxOfObjType(
2✔
2091
    /* objtype, x1, y1, z1, x2, y2, z2, realm */ )
2092
{
2093
  unsigned int objtype;
2094
  int z1, z2;
2095
  Pos2d p1, p2;
2✔
2096
  Realms::Realm* realm;
2097

2098
  if ( !( getParam( 0, objtype ) && getPos2dParam( 1, 2, &p1 ) && getParam( 3, z1 ) &&
4✔
2099
          getPos2dParam( 4, 5, &p2 ) && getParam( 6, z2 ) && getRealmParam( 7, &realm ) ) )
2✔
2100
  {
2101
    return new BError( "Invalid parameter" );
×
2102
  }
2103
  Range3d box = internal_InBoxAreaChecks( p1, z1, p2, z2, realm );
2✔
2104
  z1 = box.nw_b().z();
2✔
2105
  z2 = box.se_t().z();
2✔
2106

2107
  std::unique_ptr<ObjArray> newarr( new ObjArray );
2✔
2108
  WorldIterator<ItemFilter>::InBox(
4✔
2109
      box.range(), realm,
2✔
2110
      [&]( Items::Item* item )
2✔
2111
      {
2112
        if ( item->z() >= z1 && item->z() <= z2 && item->objtype_ == objtype )
40✔
2113
        {
2114
          newarr->addElement( item->make_ref() );
39✔
2115
        }
2116
      } );
40✔
2117

2118
  return newarr.release();
2✔
2119
}
2✔
2120

2121
BObjectImp* UOExecutorModule::mf_ListObjectsInBoxOfClass(
1✔
2122
    /* POL_Class, x1, y1, z1, x2, y2, z2, realm */ )
2123
{
2124
  unsigned int POL_Class;
2125
  int z1, z2;
2126
  Pos2d p1, p2;
1✔
2127
  Realms::Realm* realm;
2128

2129
  if ( !( getParam( 0, POL_Class ) && getPos2dParam( 1, 2, &p1 ) && getParam( 3, z1 ) &&
2✔
2130
          getPos2dParam( 4, 5, &p2 ) && getParam( 6, z2 ) && getRealmParam( 7, &realm ) ) )
1✔
2131
  {
2132
    return new BError( "Invalid parameter" );
×
2133
  }
2134
  Range3d box = internal_InBoxAreaChecks( p1, z1, p2, z2, realm );
1✔
2135
  z1 = box.nw_b().z();
1✔
2136
  z2 = box.se_t().z();
1✔
2137

2138
  std::unique_ptr<ObjArray> newarr( new ObjArray );
1✔
2139
  WorldIterator<MobileFilter>::InBox(
2✔
2140
      box.range(), realm,
1✔
2141
      [&]( Mobile::Character* chr )
1✔
2142
      {
2143
        if ( chr->z() >= z1 && chr->z() <= z2 && chr->script_isa( POL_Class ) )
×
2144
        {
2145
          newarr->addElement( chr->make_ref() );
×
2146
        }
2147
      } );
×
2148
  WorldIterator<ItemFilter>::InBox(
2✔
2149
      box.range(), realm,
1✔
2150
      [&]( Items::Item* item )
1✔
2151
      {
2152
        if ( item->z() >= z1 && item->z() <= z2 && item->script_isa( POL_Class ) )
1✔
2153
        {
2154
          newarr->addElement( item->make_ref() );
1✔
2155
        }
2156
      } );
1✔
2157

2158
  return newarr.release();
1✔
2159
}
1✔
2160

2161
BObjectImp* UOExecutorModule::mf_ListMobilesInBox( /* x1, y1, z1, x2, y2, z2, realm */ )
2✔
2162
{
2163
  int z1, z2;
2164
  Pos2d p1, p2;
2✔
2165
  Realms::Realm* realm;
2166

2167
  if ( !( getPos2dParam( 0, 1, &p1 ) && getParam( 2, z1 ) && getPos2dParam( 3, 4, &p2 ) &&
4✔
2168
          getParam( 5, z2 ) && getRealmParam( 6, &realm ) ) )
2✔
2169
  {
2170
    return new BError( "Invalid parameter" );
×
2171
  }
2172
  Range3d box = internal_InBoxAreaChecks( p1, z1, p2, z2, realm );
2✔
2173
  z1 = box.nw_b().z();
2✔
2174
  z2 = box.se_t().z();
2✔
2175
  std::unique_ptr<ObjArray> newarr( new ObjArray );
2✔
2176
  WorldIterator<MobileFilter>::InBox( box.range(), realm,
2✔
2177
                                      [&]( Mobile::Character* chr )
2✔
2178
                                      {
2179
                                        if ( chr->z() >= z1 && chr->z() <= z2 )
2✔
2180
                                        {
2181
                                          newarr->addElement( chr->make_ref() );
2✔
2182
                                        }
2183
                                      } );
2✔
2184

2185
  return newarr.release();
2✔
2186
}
2✔
2187

2188
BObjectImp* UOExecutorModule::mf_ListMultisInBox( /* x1, y1, z1, x2, y2, z2, realm */ )
4✔
2189
{
2190
  int z1, z2;
2191
  Pos2d p1, p2;
4✔
2192
  Realms::Realm* realm;
2193

2194
  if ( !( getPos2dParam( 0, 1, &p1 ) && getParam( 2, z1 ) && getPos2dParam( 3, 4, &p2 ) &&
8✔
2195
          getParam( 5, z2 ) && getRealmParam( 6, &realm ) ) )
4✔
2196
  {
2197
    return new BError( "Invalid parameter" );
×
2198
  }
2199

2200
  Range3d box = internal_InBoxAreaChecks( p1, z1, p2, z2, realm );
4✔
2201
  z1 = box.nw_b().z();
4✔
2202
  z2 = box.se_t().z();
4✔
2203

2204
  std::unique_ptr<ObjArray> newarr( new ObjArray );
4✔
2205

2206
  // extend the coords to find the center item
2207
  // but only as parameter for the filter function
2208
  Vec2d urange{ Clib::clamp_convert<s16>( gamestate.max_update_range ),
4✔
2209
                Clib::clamp_convert<s16>( gamestate.max_update_range ) };
4✔
2210
  Range2d boxrange( box.nw() - urange, box.se() + urange, realm );
4✔
2211

2212
  // search for multis.  this is tricky, since the center might lie outside the box
2213
  WorldIterator<MultiFilter>::InBox(
4✔
2214
      boxrange, realm,
2215
      [&]( Multi::UMulti* multi )
4✔
2216
      {
2217
        if ( !box.intersect( multi->current_box() ) )
4✔
2218
          return;
×
2219
        const Multi::MultiDef& md = multi->multidef();
4✔
2220
        // some part of it is contained in the box.  Look at the
2221
        // individual statics, to see if any of them lie within.
2222
        for ( const auto& citr : md.components )
34✔
2223
        {
2224
          const Multi::MULTI_ELEM* elem = citr.second;
34✔
2225
          Pos3d absp = multi->pos3d() + elem->relpos;
34✔
2226
          if ( box.contains( absp.xy() ) )
34✔
2227
          {
2228
            // do Z checking
2229
            int height = Plib::tileheight( getgraphic( elem->objtype ) );
4✔
2230
            int top = absp.z() + height;
4✔
2231

2232
            if ( ( z1 <= absp.z() && absp.z() <= z2 ) ||  // bottom point lies between
8✔
2233
                 ( z1 <= top && top <= z2 ) ||            // top lies between
8✔
2234
                 ( top >= z2 && absp.z() <= z1 ) )        // spans
×
2235
            {
2236
              newarr->addElement( multi->make_ref() );
4✔
2237
              break;  // out of for
4✔
2238
            }
2239
          }
2240
        }
2241
      } );
2242

2243
  return newarr.release();
4✔
2244
}
4✔
2245

2246
BObjectImp* UOExecutorModule::mf_ListStaticsInBox( /* x1, y1, z1, x2, y2, z2, flags, realm */ )
1✔
2247
{
2248
  int z1, z2;
2249
  Pos2d p1, p2;
1✔
2250
  int flags;
2251
  Realms::Realm* realm;
2252

2253
  if ( !( getPos2dParam( 0, 1, &p1 ) && getParam( 2, z1 ) && getPos2dParam( 3, 4, &p2 ) &&
2✔
2254
          getParam( 5, z2 ) && getParam( 6, flags ) && getRealmParam( 7, &realm ) ) )
1✔
2255
  {
2256
    return new BError( "Invalid parameter" );
×
2257
  }
2258

2259
  Range3d box = internal_InBoxAreaChecks( p1, z1, p2, z2, realm );
1✔
2260
  z1 = box.nw_b().z();
1✔
2261
  z2 = box.se_t().z();
1✔
2262

2263
  std::unique_ptr<ObjArray> newarr( new ObjArray );
1✔
2264

2265
  for ( const auto& pos : box.range() )
5✔
2266
  {
2267
    if ( !( flags & ITEMS_IGNORE_STATICS ) )
2✔
2268
    {
2269
      Plib::StaticEntryList slist;
2✔
2270
      realm->getstatics( slist, pos );
2✔
2271

2272
      for ( const auto& entry : slist )
4✔
2273
      {
2274
        if ( ( z1 <= entry.z ) && ( entry.z <= z2 ) )
2✔
2275
        {
2276
          std::unique_ptr<BStruct> arr( new BStruct );
2✔
2277
          arr->addMember( "x", new BLong( pos.x() ) );
2✔
2278
          arr->addMember( "y", new BLong( pos.y() ) );
2✔
2279
          arr->addMember( "z", new BLong( entry.z ) );
2✔
2280
          arr->addMember( "objtype", new BLong( entry.objtype ) );
2✔
2281
          arr->addMember( "hue", new BLong( entry.hue ) );
2✔
2282
          newarr->addElement( arr.release() );
2✔
2283
        }
2✔
2284
      }
2285
    }
2✔
2286

2287
    if ( !( flags & ITEMS_IGNORE_MULTIS ) )
2✔
2288
    {
2289
      Plib::StaticList mlist;
2✔
2290
      realm->readmultis( mlist, pos );
2✔
2291

2292
      for ( const auto& entry : mlist )
2✔
2293
      {
2294
        if ( ( z1 <= entry.z ) && ( entry.z <= z2 ) )
×
2295
        {
2296
          std::unique_ptr<BStruct> arr( new BStruct );
×
2297
          arr->addMember( "x", new BLong( pos.x() ) );
×
2298
          arr->addMember( "y", new BLong( pos.y() ) );
×
2299
          arr->addMember( "z", new BLong( entry.z ) );
×
2300
          arr->addMember( "objtype", new BLong( entry.graphic ) );
×
2301
          newarr->addElement( arr.release() );
×
2302
        }
×
2303
      }
2304
    }
2✔
2305
  }
2306
  return newarr.release();
1✔
2307
}
1✔
2308

2309
BObjectImp* UOExecutorModule::mf_ListItemsNearLocationOfType( /* x, y, z, range, objtype, realm */ )
29✔
2310
{
2311
  Core::Pos2d pos;
29✔
2312
  int z;
2313
  u16 range;
2314
  unsigned int objtype;
2315
  Realms::Realm* realm;
2316

2317
  if ( getRealmParam( 5, &realm ) && getPos2dParam( 0, 1, &pos, realm ) && getParam( 2, z ) &&
58✔
2318
       getParam( 3, range ) && getObjtypeParam( 4, objtype ) )
58✔
2319
  {
2320
    std::unique_ptr<ObjArray> newarr( new ObjArray );
29✔
2321
    WorldIterator<ItemFilter>::InRange(
29✔
2322
        pos, realm, range,
2323
        [&]( Items::Item* item )
29✔
2324
        {
2325
          if ( item->objtype_ == objtype && item->in_range( pos, range ) )
57✔
2326
          {
2327
            if ( ( z == LIST_IGNORE_Z ) || ( abs( item->z() - z ) < CONST_DEFAULT_ZRANGE ) )
44✔
2328
              newarr->addElement( item->make_ref() );
44✔
2329
          }
2330
        } );
57✔
2331

2332
    return newarr.release();
29✔
2333
  }
29✔
2334

2335
  return nullptr;
×
2336
}
2337

2338

2339
BObjectImp* UOExecutorModule::mf_ListItemsAtLocation( /* x, y, z, realm */ )
×
2340
{
2341
  Pos2d pos;
×
2342
  int z;
2343
  Realms::Realm* realm;
2344

2345
  if ( getRealmParam( 3, &realm ) && getPos2dParam( 0, 1, &pos, realm ) && getParam( 2, z ) )
×
2346
  {
2347
    std::unique_ptr<ObjArray> newarr( new ObjArray );
×
2348
    WorldIterator<ItemFilter>::InRange( pos, realm, 0,
×
2349
                                        [&]( Items::Item* item )
×
2350
                                        {
2351
                                          if ( item->pos2d() == pos )
×
2352
                                          {
2353
                                            if ( ( z == LIST_IGNORE_Z ) || ( item->z() == z ) )
×
2354
                                              newarr->addElement( item->make_ref() );
×
2355
                                          }
2356
                                        } );
×
2357

2358
    return newarr.release();
×
2359
  }
×
2360

2361
  return nullptr;
×
2362
}
2363

2364
BObjectImp* UOExecutorModule::mf_ListGhostsNearLocation()
×
2365
{
2366
  Pos2d pos;
×
2367
  int z;
2368
  u16 range;
2369
  Realms::Realm* realm;
2370

2371
  if ( getRealmParam( 4, &realm ) && getPos2dParam( 0, 1, &pos, realm ) && getParam( 2, z ) &&
×
2372
       getParam( 3, range ) )
×
2373
  {
2374
    std::unique_ptr<ObjArray> newarr( new ObjArray );
×
2375
    WorldIterator<PlayerFilter>::InRange(
×
2376
        pos, realm, range,
2377
        [&]( Mobile::Character* chr )
×
2378
        {
2379
          if ( chr->dead() && ( abs( chr->z() - z ) < CONST_DEFAULT_ZRANGE ) )
×
2380
          {
2381
            newarr->addElement( chr->make_ref() );
×
2382
          }
2383
        } );
×
2384

2385
    return newarr.release();
×
2386
  }
×
2387

2388
  return new BError( "Invalid parameter" );
×
2389
}
2390

2391
const unsigned LMBLEX_FLAG_NORMAL = 0x01;
2392
const unsigned LMBLEX_FLAG_HIDDEN = 0x02;
2393
const unsigned LMBLEX_FLAG_DEAD = 0x04;
2394
const unsigned LMBLEX_FLAG_CONCEALED = 0x8;
2395
const unsigned LMBLEX_FLAG_PLAYERS_ONLY = 0x10;
2396
const unsigned LMBLEX_FLAG_NPC_ONLY = 0x20;
2397

2398
BObjectImp* UOExecutorModule::mf_ListMobilesNearLocationEx( /* x, y, z, range, flags, realm */ )
×
2399
{
2400
  Pos2d pos;
×
2401
  Realms::Realm* realm;
2402
  int z, flags;
2403
  u16 range;
2404

2405
  if ( getRealmParam( 5, &realm ) && getPos2dParam( 0, 1, &pos, realm ) && getParam( 2, z ) &&
×
2406
       getParam( 3, range ) && getParam( 4, flags ) )
×
2407
  {
2408
    bool inc_normal = ( flags & LMBLEX_FLAG_NORMAL ) ? true : false;
×
2409
    bool inc_hidden = ( flags & LMBLEX_FLAG_HIDDEN ) ? true : false;
×
2410
    bool inc_dead = ( flags & LMBLEX_FLAG_DEAD ) ? true : false;
×
2411
    bool inc_concealed = ( flags & LMBLEX_FLAG_CONCEALED ) ? true : false;
×
2412
    bool inc_players_only = ( flags & LMBLEX_FLAG_PLAYERS_ONLY ) ? true : false;
×
2413
    bool inc_npc_only = ( flags & LMBLEX_FLAG_NPC_ONLY ) ? true : false;
×
2414

2415
    std::unique_ptr<ObjArray> newarr( new ObjArray );
×
2416

2417
    auto fill_mobs = [&]( Mobile::Character* _chr )
×
2418
    {
2419
      if ( ( inc_hidden && _chr->hidden() ) || ( inc_dead && _chr->dead() ) ||
×
2420
           ( inc_concealed && _chr->concealed() ) ||
×
2421
           ( inc_normal && !( _chr->hidden() || _chr->dead() || _chr->concealed() ) ) )
×
2422
      {
2423
        if ( ( z == LIST_IGNORE_Z ) || ( abs( _chr->z() - z ) < CONST_DEFAULT_ZRANGE ) )
×
2424
        {
2425
          if ( !inc_players_only && !inc_npc_only )
×
2426
            newarr->addElement( _chr->make_ref() );
×
2427
          else if ( inc_players_only && _chr->client )
×
2428
            newarr->addElement( _chr->make_ref() );
×
2429
          else if ( inc_npc_only && _chr->isa( UOBJ_CLASS::CLASS_NPC ) )
×
2430
            newarr->addElement( _chr->make_ref() );
×
2431
        }
2432
      }
2433
    };
×
2434
    if ( inc_players_only )
×
2435
      WorldIterator<PlayerFilter>::InRange( pos, realm, range, fill_mobs );
×
2436
    else if ( inc_npc_only )
×
2437
      WorldIterator<NPCFilter>::InRange( pos, realm, range, fill_mobs );
×
2438
    else
2439
      WorldIterator<MobileFilter>::InRange( pos, realm, range, fill_mobs );
×
2440

2441
    return newarr.release();
×
2442
  }
×
2443

2444
  return new BError( "Invalid parameter" );
×
2445
}
2446

2447
BObjectImp* UOExecutorModule::mf_ListMobilesNearLocation( /* x, y, z, range, realm */ )
×
2448
{
2449
  Pos2d pos;
×
2450
  int z;
2451
  u16 range;
2452
  Realms::Realm* realm;
2453

2454
  if ( getRealmParam( 4, &realm ) && getPos2dParam( 0, 1, &pos, realm ) && getParam( 2, z ) &&
×
2455
       getParam( 3, range ) )
×
2456
  {
2457
    std::unique_ptr<ObjArray> newarr( new ObjArray );
×
2458
    WorldIterator<MobileFilter>::InRange(
×
2459
        pos, realm, range,
2460
        [&]( Mobile::Character* chr )
×
2461
        {
2462
          if ( ( !chr->concealed() ) && ( !chr->hidden() ) && ( !chr->dead() ) )
×
2463
            if ( ( z == LIST_IGNORE_Z ) || ( abs( chr->z() - z ) < CONST_DEFAULT_ZRANGE ) )
×
2464
              newarr->addElement( chr->make_ref() );
×
2465
        } );
×
2466
    return newarr.release();
×
2467
  }
×
2468

2469
  return new BError( "Invalid parameter" );
×
2470
}
2471

2472
BObjectImp* UOExecutorModule::mf_ListMobilesInLineOfSight()
×
2473
{
2474
  UObject* obj;
2475
  int range;
2476
  if ( getUObjectParam( 0, obj ) && getParam( 1, range ) )
×
2477
  {
2478
    obj = obj->toplevel_owner();
×
2479
    std::unique_ptr<ObjArray> newarr( new ObjArray );
×
2480
    WorldIterator<MobileFilter>::InRange(
×
2481
        obj, range,
2482
        [&]( Mobile::Character* chr )
×
2483
        {
2484
          if ( chr->dead() || chr->hidden() || chr->concealed() )
×
2485
            return;
×
2486
          if ( chr == obj )
×
2487
            return;
×
2488
          if ( ( abs( chr->z() - obj->z() ) < CONST_DEFAULT_ZRANGE ) )
×
2489
          {
2490
            if ( obj->realm()->has_los( *obj, *chr ) )
×
2491
            {
2492
              newarr->addElement( chr->make_ref() );
×
2493
            }
2494
          }
2495
        } );
2496

2497
    return newarr.release();
×
2498
  }
×
2499

2500
  return new BError( "Invalid parameter" );
×
2501
}
2502

2503
BObjectImp* UOExecutorModule::mf_ListOfflineMobilesInRealm( /*realm*/ )
×
2504
{
2505
  Realms::Realm* realm = nullptr;
×
2506

2507
  if ( getRealmParam( 0, &realm ) )
×
2508
  {
2509
    std::unique_ptr<ObjArray> newarr( new ObjArray() );
×
2510

2511
    for ( const auto& objitr : Pol::Core::objStorageManager.objecthash )
×
2512
    {
2513
      UObject* obj = objitr.second.get();
×
2514
      if ( !obj->ismobile() || obj->isa( UOBJ_CLASS::CLASS_NPC ) )
×
2515
        continue;
×
2516

2517
      Character* chr = static_cast<Character*>( obj );
×
2518
      if ( chr->logged_in() || chr->realm() != realm || chr->orphan() )
×
2519
        continue;
×
2520

2521
      newarr->addElement( new EOfflineCharacterRefObjImp( chr ) );
×
2522
    }
2523
    return newarr.release();
×
2524
  }
×
2525

2526
  return new BError( "Invalid parameter" );
×
2527
}
2528

2529
// keep this in sync with UO.EM
2530
const int LH_FLAG_LOS = 1;             // only include those in LOS
2531
const int LH_FLAG_INCLUDE_HIDDEN = 2;  // include hidden characters
2532
BObjectImp* UOExecutorModule::mf_ListHostiles()
×
2533
{
2534
  Character* chr;
2535
  u16 range;
2536
  int flags;
2537
  // TODO Attackable allow items
UNCOV
2538
  if ( getCharacterParam( 0, chr ) && getParam( 1, range ) && getParam( 2, flags ) )
×
2539
  {
2540
    std::unique_ptr<ObjArray> arr( new ObjArray );
×
2541

2542
    for ( auto& hostile : chr->hostiles() )
×
2543
    {
NEW
2544
      if ( auto* mob = hostile.mobile() )
×
2545
      {
2546
        if ( mob->concealed() )
×
2547
          continue;
×
2548
        if ( ( ~flags & LH_FLAG_INCLUDE_HIDDEN ) && mob->hidden() )
×
2549
          continue;
×
2550
      }
NEW
2551
      auto* obj = hostile.object();
×
NEW
2552
      if ( ( flags & LH_FLAG_LOS ) && !chr->realm()->has_los( *chr, *obj ) )
×
NEW
2553
        continue;
×
NEW
2554
      if ( !chr->in_range( obj, range ) )
×
NEW
2555
        continue;
×
NEW
2556
      arr->addElement( obj->make_ref() );
×
2557
    }
2558

2559
    return arr.release();
×
2560
  }
×
2561

2562
  return new BError( "Invalid parameter" );
×
2563
}
2564

2565
BObjectImp* UOExecutorModule::mf_CheckLineOfSight()
1✔
2566
{
2567
  UObject* src;
2568
  UObject* dst;
2569
  if ( getUObjectParam( 0, src ) && getUObjectParam( 1, dst ) )
1✔
2570
  {
2571
    return new BLong( src->realm()->has_los( *src, *dst->toplevel_owner() ) );
1✔
2572
  }
2573

2574
  return new BLong( 0 );
×
2575
}
2576

2577
BObjectImp* UOExecutorModule::mf_CheckLosAt()
1✔
2578
{
2579
  UObject* src;
2580
  Core::Pos3d pos;
1✔
2581
  if ( getUObjectParam( 0, src ) && getPos3dParam( 1, 2, 3, &pos, src->realm() ) )
1✔
2582
  {
2583
    LosObj tgt( Core::Pos4d( pos, src->realm() ) );
1✔
2584
    return new BLong( src->realm()->has_los( *src, tgt ) );
1✔
2585
  }
2586
  return nullptr;
×
2587
}
2588

2589
BObjectImp* UOExecutorModule::mf_CheckLosBetween()
2✔
2590
{
2591
  Core::Pos3d p1, p2;
2✔
2592
  Realms::Realm* realm;
2593
  if ( getRealmParam( 6, &realm ) && getPos3dParam( 0, 1, 2, &p1, realm ) &&
4✔
2594
       getPos3dParam( 3, 4, 5, &p2, realm ) )
2✔
2595
  {
2596
    LosObj att( Core::Pos4d( p1, realm ) );
2✔
2597
    LosObj tgt( Core::Pos4d( p2, realm ) );
2✔
2598
    return new BLong( realm->has_los( att, tgt ) );
2✔
2599
  }
2600
  return nullptr;
×
2601
}
2602

2603
BObjectImp* UOExecutorModule::mf_DestroyItem()
6,190✔
2604
{
2605
  Item* item;
2606
  if ( getItemParam( 0, item ) )
6,190✔
2607
  {
2608
    if ( item->has_gotten_by() )
6,183✔
2609
      item->gotten_by()->clear_gotten_item();
×
2610
    else if ( item->inuse() && !is_reserved_to_me( item ) )
6,183✔
2611
      return new BError( "That item is being used." );
×
2612
    else if ( item->script_isa( POLCLASS_MULTI ) )
6,183✔
2613
      return new BError( "That item is a multi. Use uo::DestroyMulti instead." );
×
2614

2615
    const ItemDesc& id = find_itemdesc( item->objtype_ );
6,183✔
2616
    if ( !id.destroy_script.empty() )
6,183✔
2617
    {
2618
      BObjectImp* res = run_script_to_completion( id.destroy_script, new EItemRefObjImp( item ) );
×
2619
      if ( res->isTrue() )
×
2620
      {  // destruction is okay
2621
        BObject ob( res );
×
2622
      }
×
2623
      else
2624
      {
2625
        // destruction isn't okay!
2626
        return res;
×
2627
      }
2628
    }
2629
    UpdateCharacterOnDestroyItem( item );
6,183✔
2630
    UpdateCharacterWeight( item );
6,183✔
2631
    destroy_item( item );
6,183✔
2632
    return new BLong( 1 );
6,183✔
2633
  }
2634

2635
  return new BError( "Invalid parameter type" );
7✔
2636
}
2637

2638
BObjectImp* UOExecutorModule::mf_SetName()
6✔
2639
{
2640
  UObject* obj;
2641
  const String* name_str;
2642

2643
  if ( getUObjectParam( 0, obj ) && getStringParam( 1, name_str ) )
6✔
2644
  {
2645
    obj->setname( name_str->value() );
6✔
2646
    return new BLong( 1 );
6✔
2647
  }
2648

2649
  return new BLong( 0 );
×
2650
}
2651

2652
BObjectImp* UOExecutorModule::mf_GetPosition()
×
2653
{
2654
  UObject* obj;
2655
  if ( getUObjectParam( 0, obj ) )
×
2656
  {
2657
    std::unique_ptr<BStruct> arr( new BStruct );
×
2658
    arr->addMember( "x", new BLong( obj->x() ) );
×
2659
    arr->addMember( "y", new BLong( obj->y() ) );
×
2660
    arr->addMember( "z", new BLong( obj->z() ) );
×
2661
    return arr.release();
×
2662
  }
×
2663

2664
  return new BLong( 0 );
×
2665
}
2666

2667
BObjectImp* UOExecutorModule::mf_EnumerateItemsInContainer()
6✔
2668
{
2669
  Item* item;
2670
  if ( getItemParam( 0, item ) )
6✔
2671
  {
2672
    int flags = 0;
6✔
2673
    if ( exec.fparams.size() >= 2 )
6✔
2674
    {
2675
      if ( !getParam( 1, flags ) )
6✔
2676
        return new BError( "Invalid parameter type" );
×
2677
    }
2678

2679
    if ( item->isa( UOBJ_CLASS::CLASS_CONTAINER ) )
6✔
2680
    {
2681
      std::unique_ptr<ObjArray> newarr( new ObjArray );
6✔
2682

2683
      static_cast<UContainer*>( item )->enumerate_contents( newarr.get(), flags );
6✔
2684

2685
      return newarr.release();
6✔
2686
    }
6✔
2687

2688
    return nullptr;
×
2689
  }
2690
  return new BError( "Invalid parameter type" );
×
2691
}
2692

2693
BObjectImp* UOExecutorModule::mf_EnumerateOnlineCharacters()
×
2694
{
2695
  std::unique_ptr<ObjArray> newarr( new ObjArray );
×
2696

2697
  for ( auto client : networkManager.clients )
×
2698
  {
2699
    if ( client->chr != nullptr )
×
2700
    {
2701
      newarr->addElement( new ECharacterRefObjImp( client->chr ) );
×
2702
    }
2703
  }
2704

2705
  return newarr.release();
×
2706
}
×
2707

2708
BObjectImp* UOExecutorModule::mf_RegisterForSpeechEvents()
1✔
2709
{
2710
  UObject* center;
2711
  int range;
2712
  if ( getUObjectParam( 0, center ) && getParam( 1, range ) )
1✔
2713
  {
2714
    int flags = 0;
1✔
2715
    if ( exec.hasParams( 3 ) )
1✔
2716
    {
2717
      if ( !getParam( 2, flags ) )
1✔
2718
        return new BError( "Invalid parameter type" );
×
2719
    }
2720
    if ( !registered_for_speech_events )
1✔
2721
    {
2722
      ListenPoint::register_for_speech_events( center, &uoexec(), range, flags );
1✔
2723
      registered_for_speech_events = true;
1✔
2724
      return new BLong( 1 );
1✔
2725
    }
2726

2727
    return new BError( "Already registered for speech events" );
×
2728
  }
2729

2730
  return new BError( "Invalid parameter type" );
×
2731
}
2732

2733
BObjectImp* UOExecutorModule::mf_EnableEvents()
5✔
2734
{
2735
  int eventmask;
2736
  if ( getParam( 0, eventmask ) )
5✔
2737
  {
2738
    auto& uoex = uoexec();
5✔
2739
    if ( eventmask & ( EVID_ENTEREDAREA | EVID_LEFTAREA | EVID_SPOKE ) )
5✔
2740
    {
2741
      unsigned short range;
2742
      if ( getParam( 1, range, 0, 32 ) )
5✔
2743
      {
2744
        if ( eventmask & ( EVID_SPOKE ) )
5✔
2745
          uoex.speech_size = range;
2✔
2746
        if ( eventmask & ( EVID_ENTEREDAREA | EVID_LEFTAREA ) )
5✔
2747
          uoex.area_size = range;
3✔
2748
      }
2749
      else
2750
      {
2751
        return nullptr;
×
2752
      }
2753

2754

2755
      unsigned short evopts = 0;
5✔
2756
      if ( exec.hasParams( 2 ) && getParam( 2, evopts ) )
5✔
2757
      {
2758
        if ( eventmask & ( EVID_ENTEREDAREA | EVID_LEFTAREA ) )
5✔
2759
          uoex.area_mask = static_cast<unsigned char>( evopts & 255 );
3✔
2760
      }
2761
    }
2762
    uoex.eventmask |= eventmask;
5✔
2763
    return new BLong( uoex.eventmask );
5✔
2764
  }
2765

2766
  return new BError( "Invalid parameter" );
×
2767
}
2768

2769
BObjectImp* UOExecutorModule::mf_DisableEvents()
×
2770
{
2771
  int eventmask;
2772
  if ( getParam( 0, eventmask ) )
×
2773
  {
2774
    auto& uoex = uoexec();
×
2775
    uoex.eventmask &= ~eventmask;
×
2776

2777
    return new BLong( uoex.eventmask );
×
2778
  }
2779

2780
  return new BError( "Invalid parameter" );
×
2781
}
2782

2783
BObjectImp* UOExecutorModule::mf_Resurrect()
×
2784
{
2785
  Character* chr;
2786
  if ( getCharacterParam( 0, chr ) )
×
2787
  {
2788
    if ( !chr->dead() )
×
2789
      return new BError( "That is not dead" );
×
2790
    int flags = 0;
×
2791
    if ( exec.hasParams( 2 ) )
×
2792
    {
2793
      if ( !getParam( 1, flags ) )
×
2794
        return new BError( "Invalid parameter type" );
×
2795
    }
2796

2797
    if ( ~flags & RESURRECT_FORCELOCATION )
×
2798
    {
2799
      // we want doors to block ghosts in this case.
2800
      bool doors_block = !( chr->graphic == UOBJ_GAMEMASTER ||
×
2801
                            chr->cached_settings.get( PRIV_FLAGS::IGNORE_DOORS ) );
×
2802
      short newz;
2803
      Multi::UMulti* supporting_multi;
2804
      Item* walkon_item;
2805
      if ( !chr->realm()->walkheight( chr->pos2d(), chr->z(), &newz, &supporting_multi,
×
2806
                                      &walkon_item, doors_block, chr->movemode ) )
×
2807
      {
2808
        return new BError( "That location is blocked" );
×
2809
      }
2810
    }
2811

2812
    chr->resurrect();
×
2813
    return new BLong( 1 );
×
2814
  }
2815

2816
  return new BError( "Invalid parameter type" );
×
2817
}
2818

2819
BObjectImp* UOExecutorModule::mf_SystemFindObjectBySerial()
20✔
2820
{
2821
  int serial;
2822
  if ( getParam( 0, serial ) )
20✔
2823
  {
2824
    int sysfind_flags = 0;
20✔
2825
    if ( exec.hasParams( 2 ) )
20✔
2826
    {
2827
      if ( !getParam( 1, sysfind_flags ) )
20✔
2828
        return new BError( "Invalid parameter type" );
×
2829
    }
2830
    if ( IsCharacter( serial ) )
20✔
2831
    {
2832
      Character* chr = system_find_mobile( serial );
3✔
2833
      if ( chr != nullptr )
3✔
2834
      {
2835
        if ( sysfind_flags & SYSFIND_SEARCH_OFFLINE_MOBILES )
2✔
2836
          return new EOfflineCharacterRefObjImp( chr );
×
2837
        return new ECharacterRefObjImp( chr );
2✔
2838
      }
2839

2840
      return new BError( "Character not found" );
1✔
2841
    }
2842

2843
    // dave changed this 3/8/3: objecthash (via system_find_item) will find any kind of item, so
2844
    // don't need system_find_multi here.
2845
    Item* item = system_find_item( serial );
17✔
2846

2847
    if ( item != nullptr )
17✔
2848
    {
2849
      return item->make_ref();
16✔
2850
    }
2851

2852
    return new BError( "Item not found." );
1✔
2853
  }
2854

2855
  return new BError( "Invalid parameter type" );
×
2856
}
2857

2858
BObjectImp* UOExecutorModule::mf_SaveWorldState()
2✔
2859
{
2860
  update_gameclock();
2✔
2861
  cancel_all_trades();
2✔
2862

2863
  PolClockPauser pauser;
2✔
2864
  // do not suspend when critical, to keep defined state of a critical block
2865
  if ( !uoexec().critical() && uoexec().suspend() )
2✔
2866
  {
2867
    Tools::Timer<> total_timer;
1✔
2868
    auto res = write_data(
1✔
2869
        [uoexec = uoexec().weakptr.non_owning(), total_timer = std::move( total_timer )](
2✔
2870
            bool result, u32 clean_writes, u32 dirty_writes, s64 ellapsed ) mutable
2871
        {
2872
          Core::PolLock lck;
1✔
2873
          if ( !uoexec.exists() )
1✔
2874
            return;
×
2875
          if ( result )
1✔
2876
          {
2877
            auto* ret = new Bscript::BStruct();
1✔
2878
            ret->addMember( "DirtyObjects", new Bscript::BLong( dirty_writes ) );
1✔
2879
            ret->addMember( "CleanObjects", new Bscript::BLong( clean_writes ) );
1✔
2880
            ret->addMember( "ElapsedMilliseconds",
1✔
2881
                            new Bscript::BLong( Clib::clamp_convert<int>( ellapsed ) ) );
1✔
2882
            ret->addMember(
1✔
2883
                "ElapsedMillisecondsTotal",
2884
                new Bscript::BLong( Clib::clamp_convert<int>( total_timer.ellapsed() ) ) );
1✔
2885
            uoexec.get_weakptr()->ValueStack.back().set( new Bscript::BObject( ret ) );
1✔
2886
          }
2887
          else
2888
          {
2889
            uoexec.get_weakptr()->ValueStack.back().set(
×
2890
                new Bscript::BObject( new Bscript::BError( "Failed to save world" ) ) );
×
2891
          }
2892
          uoexec.get_weakptr()->revive();
1✔
2893
        } );
1✔
2894
    if ( !res )
1✔
2895
    {
2896
      uoexec().revive();
×
2897
      return new BError( "pol.cfg has InhibitSaves=1" );
×
2898
    }
2899
    if ( *res )
1✔
2900
      return new BLong( 0 );  // callback will be called
1✔
2901
    uoexec().revive();
×
2902
    return new BError( "Failed to save world" );
×
2903
  }
1✔
2904

2905
  // non waiting version
2906
  u32 dirty, clean;
2907
  s64 elapsed_ms;
2908
  auto res = write_data( {}, &dirty, &clean, &elapsed_ms );
1✔
2909
  if ( !res )
1✔
2910
    return new BError( "pol.cfg has InhibitSaves=1" );
×
2911
  if ( *res )
1✔
2912
  {
2913
    BStruct* ret = new BStruct();
1✔
2914
    ret->addMember( "DirtyObjects", new BLong( dirty ) );
1✔
2915
    ret->addMember( "CleanObjects", new BLong( clean ) );
1✔
2916
    ret->addMember( "ElapsedMilliseconds", new BLong( Clib::clamp_convert<int>( elapsed_ms ) ) );
1✔
2917
    return ret;
1✔
2918
  }
2919
  return new BError( "Failed to save world" );
×
2920
}
2✔
2921

2922

2923
BObjectImp* UOExecutorModule::mf_SetRegionLightLevel()
×
2924
{
2925
  const String* region_name_str;
2926
  int lightlevel;
2927
  if ( !( getStringParam( 0, region_name_str ) && getParam( 1, lightlevel ) ) )
×
2928
  {
2929
    return new BError( "Invalid Parameter type" );
×
2930
  }
2931

2932
  if ( !VALID_LIGHTLEVEL( lightlevel ) )
×
2933
  {
2934
    return new BError( "Light Level is out of range" );
×
2935
  }
2936

2937
  LightRegion* lightregion = gamestate.lightdef->getregion( region_name_str->value() );
×
2938
  if ( lightregion == nullptr )
×
2939
  {
2940
    return new BError( "Light region not found" );
×
2941
  }
2942

2943
  SetRegionLightLevel( lightregion, lightlevel );
×
2944
  return new BLong( 1 );
×
2945
}
2946

2947
BObjectImp* UOExecutorModule::mf_SetRegionWeatherLevel()
×
2948
{
2949
  const String* region_name_str;
2950
  int type;
2951
  int severity;
2952
  int aux;
2953
  int lightoverride;
2954
  if ( !( getStringParam( 0, region_name_str ) && getParam( 1, type ) && getParam( 2, severity ) &&
×
2955
          getParam( 3, aux ) && getParam( 4, lightoverride ) ) )
×
2956
  {
2957
    return new BError( "Invalid Parameter type" );
×
2958
  }
2959

2960
  WeatherRegion* weatherregion = gamestate.weatherdef->getregion( region_name_str->value() );
×
2961
  if ( weatherregion == nullptr )
×
2962
  {
2963
    return new BError( "Weather region not found" );
×
2964
  }
2965

2966
  SetRegionWeatherLevel( weatherregion, type, severity, aux, lightoverride );
×
2967

2968
  return new BLong( 1 );
×
2969
}
2970
BObjectImp* UOExecutorModule::mf_AssignRectToWeatherRegion()
×
2971
{
2972
  const String* region_name_str;
2973
  Pos2d nw, se;
×
2974
  Realms::Realm* realm;
2975

2976
  if ( !( getStringParam( 0, region_name_str ) && getRealmParam( 5, &realm ) &&
×
2977
          getPos2dParam( 1, 2, &nw, realm ) && getPos2dParam( 3, 4, &se, realm ) ) )
×
2978
  {
2979
    return new BError( "Invalid Parameter type" );
×
2980
  }
2981

2982
  bool res = gamestate.weatherdef->assign_zones_to_region( region_name_str->data(),
×
2983
                                                           Range2d( nw, se, nullptr ), realm );
×
2984
  if ( res )
×
2985
    return new BLong( 1 );
×
2986
  return new BError( "Weather region not found" );
×
2987
}
2988

2989
BObjectImp* UOExecutorModule::mf_Distance()
×
2990
{
2991
  UObject* obj1;
2992
  UObject* obj2;
2993
  if ( getUObjectParam( 0, obj1 ) && getUObjectParam( 1, obj2 ) )
×
2994
    return new BLong( obj1->distance_to( obj2->toplevel_pos() ) );
×
2995
  return new BError( "Invalid parameter type" );
×
2996
}
2997

2998
BObjectImp* UOExecutorModule::mf_DistanceEuclidean()
×
2999
{
3000
  UObject* obj1;
3001
  UObject* obj2;
3002
  if ( getUObjectParam( 0, obj1 ) && getUObjectParam( 1, obj2 ) )
×
3003
  {
3004
    const UObject* tobj1 = obj1->toplevel_owner();
×
3005
    const UObject* tobj2 = obj2->toplevel_owner();
×
3006
    return new Double( sqrt( pow( (double)( tobj1->x() - tobj2->x() ), 2 ) +
×
3007
                             pow( (double)( tobj1->y() - tobj2->y() ), 2 ) ) );
×
3008
  }
3009

3010
  return new BError( "Invalid parameter type" );
×
3011
}
3012

3013
BObjectImp* UOExecutorModule::mf_CoordinateDistance()
×
3014
{
3015
  Pos2d pos1, pos2;
×
3016
  if ( !getPos2dParam( 0, 1, &pos1 ) || !getPos2dParam( 2, 3, &pos2 ) )
×
3017
    return new BError( "Invalid parameter type" );
×
3018
  return new BLong( pos1.pol_distance( pos2 ) );
×
3019
}
3020

3021
BObjectImp* UOExecutorModule::mf_CoordinateDistanceEuclidean()
×
3022
{
3023
  unsigned short x1, y1, x2, y2;
3024
  if ( !( getParam( 0, x1 ) && getParam( 1, y1 ) && getParam( 2, x2 ) && getParam( 3, y2 ) ) )
×
3025
  {
3026
    return new BError( "Invalid parameter type" );
×
3027
  }
3028
  return new Double( sqrt( pow( (double)( x1 - x2 ), 2 ) + pow( (double)( y1 - y2 ), 2 ) ) );
×
3029
}
3030

3031
BObjectImp* UOExecutorModule::mf_GetCoordsInLine()
×
3032
{
3033
  int x1, y1, x2, y2;
3034
  if ( !( getParam( 0, x1 ) && getParam( 1, y1 ) && getParam( 2, x2 ) && getParam( 3, y2 ) ) )
×
3035
  {
3036
    return new BError( "Invalid parameter type" );
×
3037
  }
3038
  if ( x1 == x2 && y1 == y2 )
×
3039
  {  // Same exact coordinates ... just give them the coordinate back!
3040
    ObjArray* coords = new ObjArray;
×
3041
    BStruct* point = new BStruct;
×
3042
    point->addMember( "x", new BLong( x1 ) );
×
3043
    point->addMember( "y", new BLong( y1 ) );
×
3044
    coords->addElement( point );
×
3045
    return coords;
×
3046
  }
3047

3048
  double dx = abs( x2 - x1 ) + 0.5;
×
3049
  double dy = abs( y2 - y1 ) + 0.5;
×
3050
  int vx = 0, vy = 0;
×
3051

3052
  if ( x2 > x1 )
×
3053
    vx = 1;
×
3054
  else if ( x2 < x1 )
×
3055
    vx = -1;
×
3056
  if ( y2 > y1 )
×
3057
    vy = 1;
×
3058
  else if ( y2 < y1 )
×
3059
    vy = -1;
×
3060

3061
  std::unique_ptr<ObjArray> coords( new ObjArray );
×
3062
  if ( dx >= dy )
×
3063
  {
3064
    dy = dy / dx;
×
3065

3066
    for ( int c = 0; c <= dx; c++ )
×
3067
    {
3068
      int point_x = x1 + ( c * vx );
×
3069

3070
      double float_y = double( c ) * double( vy ) * dy;
×
3071
      if ( float_y - floor( float_y ) >= 0.5 )
×
3072
        float_y = ceil( float_y );
×
3073
      int point_y = int( float_y ) + y1;
×
3074

3075
      std::unique_ptr<BStruct> point( new BStruct );
×
3076
      point->addMember( "x", new BLong( point_x ) );
×
3077
      point->addMember( "y", new BLong( point_y ) );
×
3078
      coords->addElement( point.release() );
×
3079
    }
×
3080
  }
3081
  else
3082
  {
3083
    dx = dx / dy;
×
3084
    for ( int c = 0; c <= dy; c++ )
×
3085
    {
3086
      int point_y = y1 + ( c * vy );
×
3087

3088
      double float_x = double( c ) * double( vx ) * dx;
×
3089
      if ( float_x - floor( float_x ) >= 0.5 )
×
3090
        float_x = ceil( float_x );
×
3091
      int point_x = int( float_x ) + x1;
×
3092

3093
      std::unique_ptr<BStruct> point( new BStruct );
×
3094
      point->addMember( "x", new BLong( point_x ) );
×
3095
      point->addMember( "y", new BLong( point_y ) );
×
3096
      coords->addElement( point.release() );
×
3097
    }
×
3098
  }
3099
  return coords.release();
×
3100
}
×
3101

3102
BObjectImp* UOExecutorModule::mf_GetFacing()
×
3103
{
3104
  unsigned short from_x, from_y, to_x, to_y;
3105
  if ( !( getParam( 0, from_x ) && getParam( 1, from_y ) && getParam( 2, to_x ) &&
×
3106
          getParam( 3, to_y ) ) )
×
3107
  {
3108
    return new BError( "Invalid parameter type" );
×
3109
  }
3110

3111
  double x = to_x - from_x;
×
3112
  double y = to_y - from_y;
×
3113
  double pi = acos( double( -1 ) );
×
3114
  double r = sqrt( x * x + y * y );
×
3115

3116
  double angle = ( ( acos( x / r ) * 180.0 ) / pi );
×
3117

3118
  double y_check = ( ( asin( y / r ) * 180.0 ) / pi );
×
3119
  if ( y_check < 0 )
×
3120
  {
3121
    angle = 360 - angle;
×
3122
  }
3123

3124
  unsigned short facing = ( ( short( angle / 40 ) + 10 ) % 8 );
×
3125

3126
  return new BLong( facing );
×
3127
}
3128

3129
// FIXME : Should we do an Orphan check here as well? Ugh.
3130
void true_extricate( Item* item )
30✔
3131
{
3132
  send_remove_object_to_inrange( item );
30✔
3133
  if ( item->container != nullptr )
30✔
3134
  {
3135
    item->extricate();
1✔
3136
  }
3137
  else
3138
  {
3139
    remove_item_from_world( item );
29✔
3140
  }
3141
}
30✔
3142

3143
BObjectImp* UOExecutorModule::mf_MoveItemToContainer()
7✔
3144
{
3145
  Item* item;
3146
  Item* cont_item;
3147
  int px;
3148
  int py;
3149
  int add_to_existing_stack;
3150
  if ( !( getItemParam( 0, item ) && getItemParam( 1, cont_item ) && getParam( 2, px, -1, 65535 ) &&
14✔
3151
          getParam( 3, py, -1, 65535 ) && getParam( 4, add_to_existing_stack, 0, 2 ) ) )
7✔
3152
  {
3153
    return new BError( "Invalid parameter type" );
×
3154
  }
3155

3156
  ItemRef itemref( item );  // dave 1/28/3 prevent item from being destroyed before function ends
7✔
3157
  if ( !item->movable() )
7✔
3158
  {
3159
    Character* chr = controller_.get();
×
3160
    if ( chr == nullptr || !chr->can_move( item ) )
×
3161
      return new BError( "That is immobile" );
×
3162
  }
3163
  if ( item->inuse() && !is_reserved_to_me( item ) )
7✔
3164
  {
3165
    return new BError( "That item is being used." );
×
3166
  }
3167

3168

3169
  if ( !cont_item->isa( UOBJ_CLASS::CLASS_CONTAINER ) )
7✔
3170
  {
3171
    return new BError( "Non-container selected as target" );
×
3172
  }
3173
  UContainer* cont = static_cast<UContainer*>( cont_item );
7✔
3174

3175
  if ( cont->serial == item->serial )
7✔
3176
  {
3177
    return new BError( "Can't put a container into itself" );
×
3178
  }
3179
  if ( is_a_parent( cont, item->serial ) )
7✔
3180
  {
3181
    return new BError( "Can't put a container into an item in itself" );
×
3182
  }
3183
  if ( !cont->can_add( *item ) )
7✔
3184
  {
3185
    return new BError( "Container is too full to add that" );
×
3186
  }
3187
  // DAVE added this 12/04, call can/onInsert & can/onRemove scripts for this container
3188
  Character* chr_owner = cont->GetCharacterOwner();
7✔
3189
  if ( chr_owner == nullptr )
7✔
3190
    if ( controller_.get() != nullptr )
6✔
3191
      chr_owner = controller_.get();
×
3192

3193
  // daved changed order 1/26/3 check canX scripts before onX scripts.
3194
  UContainer* oldcont = item->container;
7✔
3195
  Item* existing_stack = nullptr;
7✔
3196

3197
  if ( ( oldcont != nullptr ) &&
8✔
3198
       ( !oldcont->check_can_remove_script( chr_owner, item, UContainer::MT_CORE_MOVED ) ) )
1✔
3199
    return new BError( "Could not remove item from its container." );
×
3200
  if ( item->orphan() )  // dave added 1/28/3, item might be destroyed in RTC script
7✔
3201
  {
3202
    return new BError( "Item was destroyed in CanRemove script" );
×
3203
  }
3204

3205
  if ( add_to_existing_stack )
7✔
3206
  {
3207
    existing_stack = cont->find_addable_stack( item );
×
3208
    if ( existing_stack != nullptr )
×
3209
    {
3210
      if ( !cont->can_insert_increase_stack( chr_owner, UContainer::MT_CORE_MOVED, existing_stack,
×
3211
                                             item->getamount(), item ) )
×
3212
        return new BError( "Could not add to existing stack" );
×
3213
    }
3214
    else if ( add_to_existing_stack == 2 )
×
3215
      add_to_existing_stack = 0;
×
3216
    else
3217
      return new BError( "There is no existing stack" );
×
3218
  }
3219

3220
  if ( !add_to_existing_stack )
7✔
3221
    if ( !cont->can_insert_add_item( chr_owner, UContainer::MT_CORE_MOVED, item ) )
7✔
3222
      return new BError( "Could not insert item into container." );
×
3223

3224
  if ( item->orphan() )  // dave added 1/28/3, item might be destroyed in RTC script
7✔
3225
  {
3226
    return new BError( "Item was destroyed in CanInsert Script" );
×
3227
  }
3228

3229
  if ( !item->check_unequiptest_scripts() || !item->check_unequip_script() )
7✔
3230
    return new BError( "Item cannot be unequipped" );
×
3231
  if ( item->orphan() )  // dave added 1/28/3, item might be destroyed in RTC script
7✔
3232
  {
3233
    return new BError( "Item was destroyed in Equip Script" );
×
3234
  }
3235

3236
  if ( oldcont != nullptr )
7✔
3237
  {
3238
    oldcont->on_remove( chr_owner, item, UContainer::MT_CORE_MOVED );
1✔
3239
    if ( item->orphan() )  // dave added 1/28/3, item might be destroyed in RTC script
1✔
3240
    {
3241
      return new BError( "Item was destroyed in OnRemove script" );
×
3242
    }
3243
  }
3244

3245
  if ( !add_to_existing_stack )
7✔
3246
  {
3247
    u8 slotIndex = item->slot_index();
7✔
3248
    if ( !cont->can_add_to_slot( slotIndex ) )
7✔
3249
    {
3250
      item->destroy();
×
3251
      return new BError( "No slots available in new container" );
×
3252
    }
3253
    if ( !item->slot_index( slotIndex ) )
7✔
3254
    {
3255
      item->destroy();
×
3256
      return new BError( "Couldn't set slot index on item" );
×
3257
    }
3258

3259
    Core::Pos2d cntpos;
7✔
3260
    if ( px < 0 || py < 0 )
7✔
3261
      cntpos = cont->get_random_location();
7✔
3262
    else
3263
    {
3264
      cntpos.x( static_cast<u16>( px ) ).y( static_cast<u16>( py ) );
×
3265
      if ( !cont->is_legal_posn( cntpos ) )
×
3266
        cntpos = cont->get_random_location();
×
3267
    }
3268

3269
    true_extricate( item );
7✔
3270

3271
    cont->add( item, cntpos );
7✔
3272
    update_item_to_inrange( item );
7✔
3273
    // DAVE added this 11/17: if in a Character's pack, update weight.
3274
    UpdateCharacterWeight( item );
7✔
3275

3276
    cont->on_insert_add_item( chr_owner, UContainer::MT_CORE_MOVED, item );
7✔
3277
    if ( item->orphan() )  // dave added 1/28/3, item might be destroyed in RTC script
7✔
3278
    {
3279
      return new BError( "Item was destroyed in OnInsert script" );
×
3280
    }
3281
  }
3282
  else
3283
  {
3284
    u16 amount = item->getamount();
×
3285
    true_extricate( item );
×
3286
    existing_stack->add_to_self( item );
×
3287
    update_item_to_inrange( existing_stack );
×
3288
    UpdateCharacterWeight( existing_stack );
×
3289

3290
    cont->on_insert_increase_stack( chr_owner, UContainer::MT_CORE_MOVED, existing_stack, amount );
×
3291
  }
3292

3293
  return new BLong( 1 );
7✔
3294
}
7✔
3295

3296

3297
BObjectImp* UOExecutorModule::mf_MoveItemToSecureTradeWin()
×
3298
{
3299
  Item* item;
3300
  Character* chr;
3301
  if ( !( getItemParam( 0, item ) && getCharacterParam( 1, chr ) ) )
×
3302
  {
3303
    return new BError( "Invalid parameter type" );
×
3304
  }
3305

3306
  ItemRef itemref( item );  // dave 1/28/3 prevent item from being destroyed before function ends
×
3307
  if ( !item->movable() )
×
3308
  {
3309
    Character* _chr = controller_.get();
×
3310
    if ( _chr == nullptr || !_chr->can_move( item ) )
×
3311
      return new BError( "That is immobile" );
×
3312
  }
3313
  if ( item->inuse() && !is_reserved_to_me( item ) )
×
3314
  {
3315
    return new BError( "That item is being used." );
×
3316
  }
3317

3318
  // daved changed order 1/26/3 check canX scripts before onX scripts.
3319
  UContainer* oldcont = item->container;
×
3320

3321
  // DAVE added this 12/04, call can/onInsert & can/onRemove scripts for this container
3322
  Character* chr_owner = nullptr;
×
3323
  if ( oldcont != nullptr )
×
3324
    chr_owner = oldcont->GetCharacterOwner();
×
3325
  if ( chr_owner == nullptr )
×
3326
    if ( controller_.get() != nullptr )
×
3327
      chr_owner = controller_.get();
×
3328

3329
  if ( ( oldcont != nullptr ) &&
×
3330
       ( !oldcont->check_can_remove_script( chr_owner, item, UContainer::MT_CORE_MOVED ) ) )
×
3331
    return new BError( "Could not remove item from its container." );
×
3332
  if ( item->orphan() )  // dave added 1/28/3, item might be destroyed in RTC script
×
3333
  {
3334
    return new BError( "Item was destroyed in CanRemove script" );
×
3335
  }
3336

3337
  if ( !item->check_unequiptest_scripts() || !item->check_unequip_script() )
×
3338
    return new BError( "Item cannot be unequipped" );
×
3339
  if ( item->orphan() )  // dave added 1/28/3, item might be destroyed in RTC script
×
3340
  {
3341
    return new BError( "Item was destroyed in Equip Script" );
×
3342
  }
3343

3344
  if ( oldcont != nullptr )
×
3345
  {
3346
    oldcont->on_remove( chr_owner, item, UContainer::MT_CORE_MOVED );
×
3347
    if ( item->orphan() )  // dave added 1/28/3, item might be destroyed in RTC script
×
3348
    {
3349
      return new BError( "Item was destroyed in OnRemove script" );
×
3350
    }
3351
  }
3352

3353
  true_extricate( item );
×
3354

3355
  return place_item_in_secure_trade_container( chr->client, item );
×
3356
}
×
3357

3358
BObjectImp* UOExecutorModule::mf_EquipItem()
23✔
3359
{
3360
  Character* chr;
3361
  Item* item;
3362
  if ( getCharacterParam( 0, chr ) && getItemParam( 1, item ) )
23✔
3363
  {
3364
    ItemRef itemref( item );  // dave 1/28/3 prevent item from being destroyed before function ends
23✔
3365
    if ( !item->movable() )
23✔
3366
    {
3367
      Character* _chr = controller_.get();
×
3368
      if ( _chr == nullptr || !_chr->can_move( item ) )
×
3369
        return new BError( "That is immobile" );
×
3370
    }
3371

3372
    if ( item->inuse() && !is_reserved_to_me( item ) )
23✔
3373
    {
3374
      return new BError( "That item is being used." );
×
3375
    }
3376

3377
    if ( !chr->equippable( item ) || !item->check_equiptest_scripts( chr ) )
23✔
3378
    {
3379
      return new BError( "That item is not equippable by that character" );
×
3380
    }
3381
    if ( item->orphan() )  // dave added 1/28/3, item might be destroyed in RTC script
23✔
3382
    {
3383
      return new BError( "Item was destroyed in EquipTest script" );
×
3384
    }
3385

3386
    item->layer = Plib::tilelayer( item->graphic );
23✔
3387

3388
    if ( item->has_equip_script() )
23✔
3389
    {
3390
      BObjectImp* res = item->run_equip_script( chr, false );
2✔
3391
      if ( !res->isTrue() )
2✔
3392
        return res;
×
3393
      BObject obj( res );
2✔
3394
    }
2✔
3395

3396

3397
    true_extricate( item );
23✔
3398

3399
    // at this point, 'item' is free - doesn't belong to the world, or a container.
3400
    chr->equip( item );
23✔
3401
    send_wornitem_to_inrange( chr, item );
23✔
3402

3403
    return new BLong( 1 );
23✔
3404
  }
23✔
3405

3406
  return new BError( "Invalid parameter type" );
×
3407
}
3408

3409
BObjectImp* UOExecutorModule::mf_RestartScript()
1✔
3410
{
3411
  UObject* obj;
3412
  if ( !getUObjectParam( 0, obj ) )
1✔
3413
    return new BError( "Invalid parameter" );
×
3414

3415
  if ( obj->script_isa( POLCLASS_NPC ) )
1✔
3416
  {
3417
    NPC* npc = static_cast<NPC*>( obj );
×
3418
    npc->restart_script();
×
3419
    return new BLong( 1 );
×
3420
  }
3421
  if ( obj->script_isa( POLCLASS_ITEM ) )
1✔
3422
  {
3423
    Item* item = static_cast<Item*>( obj );
1✔
3424
    item->stop_control_script();
1✔
3425
    return new BLong( item->start_control_script() );
1✔
3426
  }
3427

3428
  return new BError( "RestartScript only operates on NPCs and Items" );
×
3429
}
3430

3431

3432
BObjectImp* UOExecutorModule::mf_GetHarvestDifficulty()
×
3433
{
3434
  const String* resource;
3435
  unsigned short tiletype;
3436
  Pos2d pos;
×
3437
  Realms::Realm* realm;
3438
  if ( getStringParam( 0, resource ) && getRealmParam( 4, &realm ) && getPos2dParam( 1, 2, &pos ) &&
×
3439
       getParam( 3, tiletype ) )
×
3440
  {
3441
    return get_harvest_difficulty( resource->data(), Pos4d( pos, 0, realm ), tiletype );
×
3442
  }
3443

3444
  return new BError( "Invalid parameter" );
×
3445
}
3446

3447
BObjectImp* UOExecutorModule::mf_HarvestResource()
×
3448
{
3449
  Pos2d pos;
×
3450
  Realms::Realm* realm;
3451
  const String* resource;
3452
  int b;
3453
  int n;
3454

3455
  if ( getStringParam( 0, resource ) && getRealmParam( 5, &realm ) &&
×
3456
       getPos2dParam( 1, 2, &pos, realm ) && getParam( 3, b ) && getParam( 4, n ) )
×
3457
  {
3458
    if ( b <= 0 )
×
3459
      return new BError( "b must be >= 0" );
×
3460
    return harvest_resource( resource->data(), Pos4d( pos, 0, realm ), b, n );
×
3461
  }
3462

3463
  return new BError( "Invalid parameter" );
×
3464
}
3465

3466
BObjectImp* UOExecutorModule::mf_GetRegionName( /* objref */ )
×
3467
{
3468
  UObject* obj;
3469

3470
  if ( getUObjectParam( 0, obj ) )
×
3471
  {
3472
    JusticeRegion* justice_region;
3473
    if ( obj->isa( UOBJ_CLASS::CLASS_ITEM ) )
×
3474
      obj = obj->toplevel_owner();
×
3475

3476
    if ( obj->isa( UOBJ_CLASS::CLASS_CHARACTER ) )
×
3477
    {
3478
      Character* chr = static_cast<Character*>( obj );
×
3479

3480
      if ( chr->logged_in() )
×
3481
        justice_region = chr->client->gd->justice_region;
×
3482
      else
3483
        justice_region = gamestate.justicedef->getregion( chr->pos() );
×
3484
    }
3485
    else
3486
      justice_region = gamestate.justicedef->getregion( obj->pos() );
×
3487

3488
    if ( justice_region == nullptr )
×
3489
      return new BError( "No Region defined at this Location" );
×
3490
    return new String( justice_region->region_name() );
×
3491
  }
3492
  return new BError( "Invalid parameter" );
×
3493
}
3494

3495
BObjectImp* UOExecutorModule::mf_GetRegionNameAtLocation( /* x, y, realm */ )
×
3496
{
3497
  Pos2d pos;
×
3498
  Realms::Realm* realm;
3499
  if ( getRealmParam( 2, &realm ) && getPos2dParam( 0, 1, &pos ) )
×
3500
  {
3501
    JusticeRegion* justice_region = gamestate.justicedef->getregion( Pos4d( pos, 0, realm ) );
×
3502
    if ( justice_region == nullptr )
×
3503
      return new BError( "No Region defined at this Location" );
×
3504
    return new String( justice_region->region_name() );
×
3505
  }
3506
  return new BError( "Invalid parameter" );
×
3507
}
3508

3509
BObjectImp* UOExecutorModule::mf_GetRegionString()
×
3510
{
3511
  const String* resource;
3512
  const String* propname;
3513
  Pos2d pos;
×
3514
  Realms::Realm* realm;
3515
  if ( getStringParam( 0, resource ) && getRealmParam( 4, &realm ) && getPos2dParam( 1, 2, &pos ) &&
×
3516
       getStringParam( 3, propname ) )
×
3517
  {
3518
    return get_region_string( resource->data(), Pos4d( pos, 0, realm ), propname->value() );
×
3519
  }
3520

3521
  return new BError( "Invalid parameter" );
×
3522
}
3523

3524
BObjectImp* UOExecutorModule::mf_GetRegionLightLevelAtLocation( /* x, y, realm */ )
×
3525
{
3526
  Pos2d pos;
×
3527
  Realms::Realm* realm;
3528

3529
  if ( getRealmParam( 2, &realm ) && getPos2dParam( 0, 1, &pos ) )
×
3530
  {
3531
    LightRegion* light_region = gamestate.lightdef->getregion( Pos4d( pos, 0, realm ) );
×
3532
    int lightlevel;
3533
    if ( light_region != nullptr )
×
3534
      lightlevel = light_region->lightlevel;
×
3535
    else
3536
      lightlevel = settingsManager.ssopt.default_light_level;
×
3537
    return new BLong( lightlevel );
×
3538
  }
3539

3540
  return new BError( "Invalid parameter" );
×
3541
}
3542

3543

3544
BObjectImp* UOExecutorModule::mf_EquipFromTemplate()
1✔
3545
{
3546
  Character* chr;
3547
  const String* template_name;
3548
  if ( getCharacterParam( 0, chr ) && getStringParam( 1, template_name ) )
1✔
3549
  {
3550
    return equip_from_template( chr, template_name->value() );
1✔
3551
  }
3552

3553
  return new BError( "Invalid parameter" );
×
3554
}
3555

3556
// FIXME: Use a PrivUpdater here
3557
BObjectImp* UOExecutorModule::mf_GrantPrivilege()
4✔
3558
{
3559
  Character* chr;
3560
  const String* privstr;
3561
  if ( getCharacterParam( 0, chr ) && getStringParam( 1, privstr ) )
4✔
3562
  {
3563
    chr->grant_privilege( privstr->data() );
4✔
3564
    return new BLong( 1 );
4✔
3565
  }
3566

3567
  return new BError( "Invalid parameter" );
×
3568
}
3569

3570
// FIXME: Use a PrivUpdater here
3571
BObjectImp* UOExecutorModule::mf_RevokePrivilege()
1✔
3572
{
3573
  Character* chr;
3574
  const String* privstr;
3575
  if ( getCharacterParam( 0, chr ) && getStringParam( 1, privstr ) )
1✔
3576
  {
3577
    chr->revoke_privilege( privstr->data() );
1✔
3578
    return new BLong( 1 );
1✔
3579
  }
3580

3581
  return new BError( "Invalid parameter" );
×
3582
}
3583

3584
BObjectImp* UOExecutorModule::mf_ReadGameClock()
14✔
3585
{
3586
  return new BLong( read_gameclock() );
14✔
3587
}
3588

3589
unsigned char decode_xdigit( unsigned char ch )
×
3590
{
3591
  if ( ch >= '0' && ch <= '9' )
×
3592
    ch -= '0';
×
3593
  else if ( ch >= 'A' && ch <= 'F' )
×
3594
    ch = ch - 'A' + 0xa;
×
3595
  else if ( ch >= 'a' && ch <= 'f' )
×
3596
    ch = ch - 'a' + 0xa;
×
3597

3598
  return ch;
×
3599
}
3600

3601
BObjectImp* UOExecutorModule::mf_SendPacket()
×
3602
{
3603
  Character* chr;
3604
  Network::Client* client;
3605
  const String* str;
3606
  if ( getCharacterOrClientParam( 0, chr, client ) && getStringParam( 1, str ) )
×
3607
  {
3608
    if ( str->length() % 2 > 0 )
×
3609
    {
3610
      return new BError( "Invalid packet string length." );
×
3611
    }
3612
    Network::PktHelper::PacketOut<Network::EncryptedPktBuffer>
3613
        buffer;  // encryptedbuffer is the only one without getID buffer[0]
×
3614
    unsigned char* buf = reinterpret_cast<unsigned char*>( buffer->getBuffer() );
×
3615
    const char* s = str->data();
×
3616
    while ( buffer->offset < 2000 && isxdigit( s[0] ) && isxdigit( s[1] ) )
×
3617
    {
3618
      unsigned char ch;
3619
      ch = ( decode_xdigit( s[0] ) << 4 ) | decode_xdigit( s[1] );
×
3620
      *( buf++ ) = ch;
×
3621
      buffer->offset++;
×
3622
      s += 2;
×
3623
    }
3624
    if ( chr != nullptr )
×
3625
    {
3626
      if ( chr->has_active_client() )
×
3627
      {
3628
        buffer.Send( chr->client );
×
3629
        return new BLong( 1 );
×
3630
      }
3631

3632
      return new BError( "No client attached" );
×
3633
    }
3634
    if ( client != nullptr )
×
3635
    {
3636
      if ( client->isConnected() )
×
3637
      {
3638
        buffer.Send( client );
×
3639
        return new BLong( 1 );
×
3640
      }
3641

3642
      return new BError( "Client is disconnected" );
×
3643
    }
3644

3645
    return new BError( "Invalid parameter type" );
×
3646
  }
×
3647

3648
  return new BError( "Invalid parameter type" );
×
3649
}
3650

3651
BObjectImp* UOExecutorModule::mf_SendQuestArrow()
×
3652
{
3653
  Character* chr;
3654
  int x, y;
3655
  u32 arrowid = 0;
×
3656

3657
  if ( getCharacterParam( 0, chr ) && getParam( 1, x, -1, 1000000 ) &&
×
3658
       getParam( 2, y, -1, 1000000 ) )  // max values checked below
×
3659
  {
3660
    if ( !chr->has_active_client() )
×
3661
      return new BError( "No client attached" );
×
3662
    {
3663
      int arrow_id;
3664
      if ( exec.getParam( 3, arrow_id ) )
×
3665
      {
3666
        if ( arrow_id < 1 )
×
3667
          return new BError( "ArrowID out of range" );
×
3668
        arrowid = static_cast<u32>( arrow_id );
×
3669
      }
3670
      else
3671
        arrowid = uoexec().pid();
×
3672
    }
3673
    bool usesNewPktSize = ( chr->client->ClientType & Network::CLIENTTYPE_7090 ) > 0;
×
3674

3675
    Network::PktHelper::PacketOut<Network::PktOut_BA> msg;
×
3676
    if ( x == -1 && y == -1 )
×
3677
    {
3678
      msg->Write<u8>( PKTOUT_BA_ARROW_OFF );
×
3679
      msg->offset += 4;  // u16 x_tgt,y_tgt
×
3680
      if ( usesNewPktSize )
×
3681
      {
3682
        if ( !arrowid )
×
3683
        {
3684
          return new BError( "ArrowID must be supplied for cancelation." );
×
3685
        }
3686

3687
        msg->Write<u32>( static_cast<u32>( arrowid & 0xFFFFFFFF ) );
×
3688
      }
3689
    }
3690
    else
3691
    {
3692
      auto pos = Core::Pos2d( Clib::clamp_convert<u16>( x ), Clib::clamp_convert<u16>( y ) );
×
3693
      if ( !chr->realm()->valid( pos ) )
×
3694
        return new BError( "Invalid Coordinates for Realm" );
×
3695
      msg->Write<u8>( PKTOUT_BA_ARROW_ON );
×
3696
      msg->WriteFlipped<u16>( pos.x() );
×
3697
      msg->WriteFlipped<u16>( pos.y() );
×
3698
      if ( usesNewPktSize )
×
3699
        msg->Write<u32>( static_cast<u32>( arrowid & 0xFFFFFFFF ) );
×
3700
    }
3701
    msg.Send( chr->client );
×
3702
    return new BLong( arrowid );
×
3703
  }
×
3704

3705
  return new BError( "Invalid parameter" );
×
3706
}
3707

3708
BObjectImp* UOExecutorModule::mf_ConsumeReagents()
×
3709
{
3710
  Character* chr;
3711
  int spellid;
3712
  if ( getCharacterParam( 0, chr ) && getParam( 1, spellid ) )
×
3713
  {
3714
    if ( !VALID_SPELL_ID( spellid ) )
×
3715
    {
3716
      return new BError( "Spell ID out of range" );
×
3717
    }
3718
    USpell* spell = gamestate.spells[spellid];
×
3719
    if ( spell == nullptr )
×
3720
    {
3721
      return new BError( "No such spell" );
×
3722
    }
3723

3724
    return new BLong( spell->consume_reagents( chr ) ? 1 : 0 );
×
3725
  }
3726

3727
  return new BError( "Invalid parameter" );
×
3728
}
3729

3730
BObjectImp* UOExecutorModule::mf_StartSpellEffect()
×
3731
{
3732
  Character* chr;
3733
  int spellid;
3734
  if ( getCharacterParam( 0, chr ) && getParam( 1, spellid ) )
×
3735
  {
3736
    if ( !VALID_SPELL_ID( spellid ) )
×
3737
    {
3738
      return new BError( "Spell ID out of range" );
×
3739
    }
3740
    USpell* spell = gamestate.spells[spellid];
×
3741
    if ( spell == nullptr )
×
3742
    {
3743
      return new BError( "No such spell" );
×
3744
    }
3745

3746
    spell->cast( chr );
×
3747
    return new BLong( 1 );
×
3748
  }
3749

3750
  return new BError( "Invalid parameter" );
×
3751
}
3752
BObjectImp* UOExecutorModule::mf_GetSpellDifficulty()
×
3753
{
3754
  int spellid;
3755
  if ( getParam( 0, spellid ) )
×
3756
  {
3757
    if ( !VALID_SPELL_ID( spellid ) )
×
3758
    {
3759
      return new BError( "Spell ID out of range" );
×
3760
    }
3761
    USpell* spell = gamestate.spells[spellid];
×
3762
    if ( spell == nullptr )
×
3763
    {
3764
      return new BError( "No such spell" );
×
3765
    }
3766

3767
    return new BLong( spell->difficulty() );
×
3768
  }
3769

3770
  return new BError( "Invalid parameter" );
×
3771
}
3772
BObjectImp* UOExecutorModule::mf_SpeakPowerWords()
×
3773
{
3774
  Character* chr;
3775
  int spellid;
3776
  unsigned short font;
3777
  unsigned short color;
3778

3779
  if ( getCharacterParam( 0, chr ) && getParam( 1, spellid ) && getParam( 2, font ) &&
×
3780
       getParam( 3, color ) )
×
3781
  {
3782
    if ( !VALID_SPELL_ID( spellid ) )
×
3783
    {
3784
      return new BError( "Spell ID out of range" );
×
3785
    }
3786
    USpell* spell = gamestate.spells[spellid];
×
3787
    if ( spell == nullptr )
×
3788
    {
3789
      return new BError( "No such spell" );
×
3790
    }
3791

3792
    spell->speak_power_words( chr, font, color );
×
3793

3794
    return new BLong( 1 );
×
3795
  }
3796

3797
  return new BError( "Invalid parameter" );
×
3798
}
3799

3800
BObjectImp* UOExecutorModule::mf_ListEquippedItems()
1✔
3801
{
3802
  Character* chr;
3803
  if ( getCharacterParam( 0, chr ) )
1✔
3804
  {
3805
    std::unique_ptr<ObjArray> arr( new ObjArray );
1✔
3806
    for ( int layer = LAYER_EQUIP__LOWEST; layer <= LAYER_EQUIP__HIGHEST; ++layer )
26✔
3807
    {
3808
      Item* item = chr->wornitem( layer );
25✔
3809
      if ( item != nullptr )
25✔
3810
      {
3811
        arr->addElement( new EItemRefObjImp( item ) );
1✔
3812
      }
3813
    }
3814
    return arr.release();
1✔
3815
  }
1✔
3816

3817
  return new BError( "Invalid parameter" );
×
3818
}
3819

3820
BObjectImp* UOExecutorModule::mf_GetEquipmentByLayer()
3✔
3821
{
3822
  Character* chr;
3823
  int layer;
3824
  if ( getCharacterParam( 0, chr ) && getParam( 1, layer ) )
3✔
3825
  {
3826
    if ( layer < LOWEST_LAYER || layer > HIGHEST_LAYER )
3✔
3827
    {
3828
      return new BError( "Invalid layer" );
×
3829
    }
3830

3831
    Item* item = chr->wornitem( layer );
3✔
3832
    if ( item == nullptr )
3✔
3833
    {
3834
      return new BError( "Nothing equipped on that layer." );
1✔
3835
    }
3836

3837
    return new EItemRefObjImp( item );
2✔
3838
  }
3839

3840
  return new BError( "Invalid parameter" );
×
3841
}
3842

3843
BObjectImp* UOExecutorModule::mf_DisconnectClient()
×
3844
{
3845
  Character* chr;
3846
  Network::Client* client;
3847

3848
  if ( getCharacterOrClientParam( 0, chr, client ) )
×
3849
  {
3850
    if ( chr != nullptr )
×
3851
    {
3852
      if ( !chr->has_active_client() )
×
3853
        return new BError( "No client attached" );
×
3854

3855
      client = chr->client;
×
3856
    }
3857

3858
    if ( client != nullptr )
×
3859
    {
3860
      if ( client->isConnected() )
×
3861
      {
3862
        client->Disconnect();
×
3863
        return new BLong( 1 );
×
3864
      }
3865
      return new BError( "Client is disconnected" );
×
3866
    }
3867
    return new BError( "Invalid parameter type" );
×
3868
  }
3869

3870
  return new BError( "Invalid parameter type" );
×
3871
}
3872

3873
BObjectImp* UOExecutorModule::mf_GetMapInfo()
34✔
3874
{
3875
  Core::Pos2d pos;
34✔
3876
  Realms::Realm* realm;
3877

3878
  // note that this uses WORLD_MAX_X, not WORLD_X,
3879
  // because we can't read the outermost edge of the map
3880
  if ( getRealmParam( 2, &realm ) && getPos2dParam( 0, 1, &pos, realm ) )
34✔
3881
  {
3882
    Plib::MAPTILE_CELL cell = realm->getmaptile( pos );
34✔
3883
    std::unique_ptr<BStruct> result( new BStruct );
34✔
3884
    result->addMember( "z", new BLong( cell.z ) );
34✔
3885
    result->addMember( "landtile", new BLong( cell.landtile ) );
34✔
3886

3887
    return result.release();
34✔
3888
  }
34✔
3889

3890
  return new BError( "Invalid parameter" );
×
3891
}
3892
BObjectImp* UOExecutorModule::mf_GetWorldHeight()
×
3893
{
3894
  Core::Pos2d pos;
×
3895
  Realms::Realm* realm;
3896
  if ( getRealmParam( 2, &realm ) && getPos2dParam( 0, 1, &pos, realm ) )
×
3897
  {
3898
    short z = -255;
×
3899
    if ( realm->lowest_standheight( pos, &z ) )
×
3900
      return new BLong( z );
×
3901
    return new BError( "Nowhere" );
×
3902
  }
3903

3904
  return new BError( "Invalid parameter" );
×
3905
}
3906

3907
BObjectImp* UOExecutorModule::mf_GetObjtypeByName()
×
3908
{
3909
  const String* namestr;
3910
  if ( getStringParam( 0, namestr ) )
×
3911
  {
3912
    unsigned int objtype = get_objtype_byname( namestr->data() );
×
3913
    if ( objtype != 0 )
×
3914
      return new BLong( objtype );
×
3915
    return new BError( "No objtype by that name" );
×
3916
  }
3917

3918
  return new BError( "Invalid parameter" );
×
3919
}
3920

3921
BObjectImp* UOExecutorModule::mf_SendEvent()
×
3922
{
3923
  Character* chr;
3924
  if ( getCharacterParam( 0, chr ) )
×
3925
  {
3926
    BObjectImp* event = exec.getParamImp( 1 );
×
3927
    if ( event != nullptr )
×
3928
    {
3929
      if ( chr->isa( UOBJ_CLASS::CLASS_NPC ) )
×
3930
      {
3931
        NPC* npc = static_cast<NPC*>( chr );
×
3932
        // event->add_ref(); // UNTESTED
3933
        return npc->send_event_script( event->copy() );
×
3934
      }
3935

3936
      return new BError( "That mobile is not an NPC" );
×
3937
    }
3938

3939
    return new BError( "Huh?  Not enough parameters" );
×
3940
  }
3941

3942
  return new BError( "Invalid parameter" );
×
3943
}
3944

3945
BObjectImp* UOExecutorModule::mf_DestroyMulti()
32✔
3946
{
3947
  Multi::UMulti* multi;
3948
  if ( getMultiParam( 0, multi ) )
32✔
3949
  {
3950
    const ItemDesc& id = find_itemdesc( multi->objtype_ );
32✔
3951
    if ( !id.destroy_script.empty() )
32✔
3952
    {
3953
      BObjectImp* res = run_script_to_completion( id.destroy_script, new EItemRefObjImp( multi ) );
×
3954
      if ( !res->isTrue() )
×
3955
      {  // destruction is okay
3956
        return res;
×
3957
      }
3958
    }
3959

3960
    Multi::UBoat* boat = multi->as_boat();
32✔
3961
    if ( boat != nullptr )
32✔
3962
    {
3963
      return Multi::destroy_boat( boat );
19✔
3964
    }
3965
    Multi::UHouse* house = multi->as_house();
13✔
3966
    if ( house != nullptr )
13✔
3967
    {
3968
      return Multi::destroy_house( house );
13✔
3969
    }
3970
    return new BError( "WTF!? Don't know what kind of multi that is!" );
×
3971
  }
3972

3973
  return new BError( "Invalid parameter type" );
×
3974
}
3975

3976

3977
BObjectImp* UOExecutorModule::mf_GetMultiDimensions()
×
3978
{
3979
  u16 multiid;
3980
  if ( getParam( 0, multiid ) )
×
3981
  {
3982
    if ( !Multi::MultiDefByMultiIDExists( multiid ) )
×
3983
      return new BError( "MultiID not found" );
×
3984

3985
    const Multi::MultiDef& md = *Multi::MultiDefByMultiID( multiid );
×
3986
    std::unique_ptr<BStruct> ret( new BStruct );
×
3987
    ret->addMember( "xmin", new BLong( md.minrxyz.x() ) );
×
3988
    ret->addMember( "xmax", new BLong( md.maxrxyz.x() ) );
×
3989
    ret->addMember( "ymin", new BLong( md.minrxyz.y() ) );
×
3990
    ret->addMember( "ymax", new BLong( md.maxrxyz.y() ) );
×
3991
    return ret.release();
×
3992
  }
×
3993
  return new BError( "Invalid parameter" );
×
3994
}
3995

3996
BObjectImp* UOExecutorModule::mf_SetScriptController()
×
3997
{
3998
  Character* old_controller = controller_.get();
×
3999
  BObjectImp* param0 = getParamImp( 0 );
×
4000
  bool handled = false;
×
4001

4002
  if ( auto* lng = impptrIf<BLong>( param0 ) )
×
4003
  {
4004
    if ( lng->value() == 0 )
×
4005
    {
4006
      controller_.clear();
×
4007
      handled = true;
×
4008
    }
4009
  }
4010

4011
  if ( !handled )
×
4012
  {
4013
    Character* chr;
4014
    if ( getCharacterParam( 0, chr ) )
×
4015
      controller_.set( chr );
×
4016
    else
4017
      controller_.clear();
×
4018
  }
4019

4020
  if ( old_controller )
×
4021
    return new ECharacterRefObjImp( old_controller );
×
4022
  return new BLong( 0 );
×
4023
}
4024

4025
BObjectImp* UOExecutorModule::mf_GetStandingHeight()
6✔
4026
{
4027
  Core::Pos4d pos;
6✔
4028
  if ( getPos4dParam( 0, 1, 2, 3, &pos ) )
6✔
4029
  {
4030
    short newz;
4031
    Multi::UMulti* multi;
4032
    Item* walkon;
4033
    if ( pos.realm()->lowest_walkheight( pos.xy(), pos.z(), &newz, &multi, &walkon, true,
6✔
4034
                                         Plib::MOVEMODE_LAND ) )
4035
    {
4036
      std::unique_ptr<BStruct> arr( new BStruct );
6✔
4037
      arr->addMember( "z", new BLong( newz ) );
6✔
4038
      if ( multi != nullptr )
6✔
4039
        arr->addMember( "multi", new EMultiRefObjImp( multi ) );
6✔
4040
      return arr.release();
6✔
4041
    }
6✔
4042

4043
    return new BError( "Can't stand there" );
×
4044
  }
4045

4046
  return new BError( "Invalid parameter type" );
×
4047
}
4048

4049
BObjectImp* UOExecutorModule::mf_GetStandingLayers( /* x, y, flags, realm, includeitems */ )
×
4050
{
4051
  Core::Pos2d pos;
×
4052
  int flags;
4053
  Realms::Realm* realm;
4054
  int includeitems;
4055

4056
  if ( getRealmParam( 3, &realm ) && getPos2dParam( 0, 1, &pos, realm ) && getParam( 2, flags ) &&
×
4057
       getParam( 4, includeitems ) )
×
4058
  {
4059
    std::unique_ptr<ObjArray> newarr( new ObjArray );
×
4060

4061
    Plib::MapShapeList mlist;
×
4062
    Core::ItemsVector ivec;
×
4063
    realm->readmultis( mlist, pos, flags );
×
4064
    realm->getmapshapes( mlist, pos, flags );
×
4065
    if ( includeitems )
×
4066
    {
4067
      realm->readdynamics( mlist, pos, ivec, false, flags );
×
4068
    }
4069

4070
    for ( const auto& entry : mlist )
×
4071
    {
4072
      std::unique_ptr<BStruct> arr( new BStruct );
×
4073

4074
      if ( entry.flags & ( Plib::FLAG::MOVELAND | Plib::FLAG::MOVESEA ) )
×
4075
        arr->addMember( "z", new BLong( entry.z + entry.height ) );
×
4076
      else
4077
        arr->addMember( "z", new BLong( entry.z ) );
×
4078

4079
      arr->addMember( "height", new BLong( entry.height ) );
×
4080
      arr->addMember( "flags", new BLong( entry.flags ) );
×
4081
      newarr->addElement( arr.release() );
×
4082
    }
×
4083

4084
    return newarr.release();
×
4085
  }
×
4086
  return new BError( "Invalid parameter type" );
×
4087
}
4088

4089
BObjectImp*
4090
UOExecutorModule::mf_GetStandingCoordinates() /* x, y, radius, minz, maxz, realm := _DEFAULT_REALM,
1✔
4091
                                                 movemode := "L", doors_block = 0 */
4092
{
4093
  s16 r, minz, maxz;
4094
  int doors_block;
4095
  const String* movemodename;
4096
  Core::Pos2d pos;
1✔
4097
  Realms::Realm* realm;
4098

4099
  if ( !getRealmParam( 5, &realm ) )
1✔
4100
    return new BError( "Realm not found" );
×
4101

4102
  if ( !( getPos2dParam( 0, 1, &pos, realm ) && getParam( 2, r ) && getParam( 3, minz ) &&
2✔
4103
          getParam( 4, maxz ) && getStringParam( 6, movemodename ) && getParam( 7, doors_block ) ) )
1✔
4104
  {
4105
    return new BError( "Invalid parameter type" );
×
4106
  }
4107

4108
  Plib::MOVEMODE movemode = Character::decode_movemode( movemodename->value() );
1✔
4109

4110
  std::unique_ptr<ObjArray> result( new ObjArray );
1✔
4111

4112
  // Iterate through all tiles in range and populate the return array with valid standing locations
4113
  Core::Vec2d radius( r, r );
1✔
4114
  Core::Pos2d tl = pos - radius;
1✔
4115
  Core::Pos2d br = pos + radius;
1✔
4116
  Core::Range2d range( tl, br, realm );
1✔
4117
  for ( const auto& tile : range )
243✔
4118
  {
4119
    auto layers = realm->get_walkheights( tile, minz, maxz, movemode, doors_block );
121✔
4120
    for ( const auto& layer : layers )
154✔
4121
    {
4122
      std::unique_ptr<BStruct> height_struct( new BStruct );
33✔
4123
      auto z = std::get<0>( layer );
33✔
4124
      auto multi = std::get<1>( layer );
33✔
4125

4126
      // Figure out which members to stick in the struct -- we only add multi it exists
4127
      height_struct->addMember( "x", new BLong( tile.x() ) );
33✔
4128
      height_struct->addMember( "y", new BLong( tile.y() ) );
33✔
4129
      height_struct->addMember( "z", new BLong( z ) );
33✔
4130
      if ( multi != nullptr )
33✔
4131
      {
4132
        height_struct->addMember( "multi", new EMultiRefObjImp( multi ) );
×
4133
      }
4134

4135
      // Add struct to the return array
4136
      result->addElement( height_struct.release() );
33✔
4137
    }
33✔
4138
  }
121✔
4139

4140
  return result.release();
1✔
4141
}
1✔
4142

4143
BObjectImp* UOExecutorModule::mf_ReserveItem()
3✔
4144
{
4145
  Item* item;
4146
  if ( getItemParam( 0, item ) )
3✔
4147
  {
4148
    if ( item->inuse() )
3✔
4149
    {
4150
      if ( is_reserved_to_me( item ) )
2✔
4151
        return new BLong( 2 );
2✔
4152
      return new BError( "That item is already being used." );
×
4153
    }
4154
    item->inuse( true );
1✔
4155
    reserved_items_.emplace_back( item );
1✔
4156
    return new BLong( 1 );
1✔
4157
  }
4158

4159
  return new BError( "Invalid parameter" );
×
4160
}
4161

4162
BObjectImp* UOExecutorModule::mf_ReleaseItem()
3✔
4163
{
4164
  Item* item;
4165
  if ( getItemParam( 0, item ) )
3✔
4166
  {
4167
    if ( item->inuse() )
3✔
4168
    {
4169
      for ( unsigned i = 0; i < reserved_items_.size(); ++i )
3✔
4170
      {
4171
        if ( reserved_items_[i].get() == item )
3✔
4172
        {
4173
          item->inuse( false );
3✔
4174
          reserved_items_[i] = reserved_items_.back();
3✔
4175
          reserved_items_.pop_back();
3✔
4176
          return new BLong( 1 );
3✔
4177
        }
4178
      }
4179
      return new BError( "That item is not reserved by this script." );
×
4180
    }
4181

4182
    return new BError( "That item is not reserved." );
×
4183
  }
4184

4185
  return new BError( "Invalid parameter" );
×
4186
}
4187

4188

4189
BObjectImp* UOExecutorModule::mf_SendSkillWindow()
×
4190
{
4191
  Character *towhom, *forwhom;
4192
  if ( getCharacterParam( 0, towhom ) && getCharacterParam( 1, forwhom ) )
×
4193
  {
4194
    if ( towhom->has_active_client() )
×
4195
    {
4196
      send_skillmsg( towhom->client, forwhom );
×
4197
      return new BLong( 1 );
×
4198
    }
4199

4200
    return new BError( "No client attached" );
×
4201
  }
4202

4203
  return new BError( "Invalid parameter type" );
×
4204
}
4205

4206

4207
BObjectImp* UOExecutorModule::mf_OpenPaperdoll()
×
4208
{
4209
  Character *towhom, *forwhom;
4210
  if ( getCharacterParam( 0, towhom ) && getCharacterParam( 1, forwhom ) )
×
4211
  {
4212
    if ( towhom->has_active_client() )
×
4213
    {
4214
      send_paperdoll( towhom->client, forwhom );
×
4215
      return new BLong( 1 );
×
4216
    }
4217

4218
    return new BError( "No client attached" );
×
4219
  }
4220

4221
  return new BError( "Invalid parameter type" );
×
4222
}
4223

4224
// TODO: a FindSubstance call that does the 'find' portion below, but returns an
4225
// array of the stacks.  All returned items would be marked 'inuse'.
4226
// Then, make ConsumeSubstance able to use such an array of owned items.
4227

4228
BObjectImp* UOExecutorModule::mf_ConsumeSubstance()
×
4229
{
4230
  Item* cont_item;
4231
  unsigned int objtype;
4232
  int amount;
4233
  if ( getItemParam( 0, cont_item ) && getObjtypeParam( 1, objtype ) && getParam( 2, amount ) )
×
4234
  {
4235
    if ( !cont_item->isa( UOBJ_CLASS::CLASS_CONTAINER ) )
×
4236
      return new BError( "That is not a container" );
×
4237
    if ( amount < 0 )
×
4238
      return new BError( "Amount cannot be negative" );
×
4239

4240
    UContainer* cont = static_cast<UContainer*>( cont_item );
×
4241
    int amthave = cont->find_sumof_objtype_noninuse( objtype );
×
4242
    if ( amthave < amount )
×
4243
      return new BError( "Not enough of that substance in container" );
×
4244

4245
    cont->consume_sumof_objtype_noninuse( objtype, amount );
×
4246

4247
    return new BLong( 1 );
×
4248
  }
4249

4250
  return new BError( "Invalid parameter type" );
×
4251
}
4252

4253
bool UOExecutorModule::is_reserved_to_me( Item* item )
2✔
4254
{
4255
  for ( const auto& reserved_item : reserved_items_ )
2✔
4256
  {
4257
    if ( reserved_item.get() == item )
2✔
4258
      return true;
2✔
4259
  }
4260
  return false;
×
4261
}
4262

4263
BObjectImp* UOExecutorModule::mf_Shutdown()
2✔
4264
{
4265
  int exit_code = 0;
2✔
4266

4267
  if ( exec.hasParams( 1 ) )
2✔
4268
  {
4269
    getParam( 0, exit_code );
2✔
4270
  }
4271

4272
  Clib::signal_exit( exit_code );
2✔
4273
#ifndef _WIN32
4274
  // the catch_signals_thread (actually main) sits with sigwait(),
4275
  // so it won't wake up except by being signalled.
4276
  signal_catch_thread();
2✔
4277
#endif
4278
  return new BLong( 1 );
2✔
4279
}
4280

4281

4282
BObjectImp* UOExecutorModule::mf_GetCommandHelp()
×
4283
{
4284
  Character* chr;
4285
  const String* cmd;
4286
  if ( getCharacterParam( 0, chr ) && getStringParam( 1, cmd ) )
×
4287
  {
4288
    std::string help = get_textcmd_help( chr, cmd->value() );
×
4289
    if ( !help.empty() )
×
4290
    {
4291
      return new String( help );
×
4292
    }
4293

4294
    return new BError( "No help for that command found" );
×
4295
  }
×
4296

4297
  return new BError( "Invalid parameter type" );
×
4298
}
4299

4300

4301
BObjectImp* UOExecutorModule::mf_SendStringAsTipWindow()
×
4302
{
4303
  Character* chr;
4304
  const String* str;
4305
  if ( getCharacterParam( 0, chr ) && getStringParam( 1, str ) )
×
4306
  {
4307
    if ( chr->has_active_client() )
×
4308
    {
4309
      send_tip( chr->client, str->value() );
×
4310
      return new BLong( 1 );
×
4311
    }
4312

4313
    return new BError( "No client attached" );
×
4314
  }
4315

4316
  return new BError( "Invalid parameter type" );
×
4317
}
4318

4319
BObjectImp* UOExecutorModule::mf_ListItemsNearLocationWithFlag(
×
4320
    /* x, y, z, range, flags, realm */ )  // DAVE
4321
{
4322
  Pos2d pos;
×
4323
  u16 range;
4324
  int z, flags;
4325
  Realms::Realm* realm;
4326

4327
  if ( getRealmParam( 5, &realm ) && getPos2dParam( 0, 1, &pos, realm ) && getParam( 2, z ) &&
×
4328
       getParam( 3, range ) && getParam( 4, flags ) )
×
4329
  {
4330
    std::unique_ptr<ObjArray> newarr( new ObjArray );
×
4331
    WorldIterator<ItemFilter>::InRange(
×
4332
        pos, realm, range,
4333
        [&]( Item* item )
×
4334
        {
4335
          if ( ( Plib::tile_uoflags( item->graphic ) & flags ) )
×
4336
          {
4337
            if ( item->in_range( pos, range ) )
×
4338
            {
4339
              if ( ( z == LIST_IGNORE_Z ) || ( abs( item->z() - z ) < CONST_DEFAULT_ZRANGE ) )
×
4340
                newarr->addElement( new EItemRefObjImp( item ) );
×
4341
            }
4342
          }
4343
        } );
×
4344

4345
    return newarr.release();
×
4346
  }
×
4347

4348
  return new BError( "Invalid parameter" );
×
4349
}
4350

4351
BObjectImp* UOExecutorModule::mf_ListStaticsAtLocation( /* x, y, z, flags, realm */ )
×
4352
{
4353
  Core::Pos2d pos;
×
4354
  int z, flags;
4355
  Realms::Realm* realm;
4356

4357
  if ( getRealmParam( 4, &realm ) && getPos2dParam( 0, 1, &pos, realm ) && getParam( 2, z ) &&
×
4358
       getParam( 3, flags ) )
×
4359
  {
4360
    std::unique_ptr<ObjArray> newarr( new ObjArray );
×
4361

4362
    if ( !( flags & ITEMS_IGNORE_STATICS ) )
×
4363
    {
4364
      Plib::StaticEntryList slist;
×
4365
      realm->getstatics( slist, pos );
×
4366

4367
      for ( const auto& entry : slist )
×
4368
      {
4369
        if ( ( z == LIST_IGNORE_Z ) || ( entry.z == z ) )
×
4370
        {
4371
          std::unique_ptr<BStruct> arr( new BStruct );
×
4372
          arr->addMember( "x", new BLong( pos.x() ) );
×
4373
          arr->addMember( "y", new BLong( pos.y() ) );
×
4374
          arr->addMember( "z", new BLong( entry.z ) );
×
4375
          arr->addMember( "objtype", new BLong( entry.objtype ) );
×
4376
          arr->addMember( "hue", new BLong( entry.hue ) );
×
4377
          newarr->addElement( arr.release() );
×
4378
        }
×
4379
      }
4380
    }
×
4381

4382
    if ( !( flags & ITEMS_IGNORE_MULTIS ) )
×
4383
    {
4384
      Plib::StaticList mlist;
×
4385
      realm->readmultis( mlist, pos );
×
4386

4387
      for ( const auto& entry : mlist )
×
4388
      {
4389
        if ( ( z == LIST_IGNORE_Z ) || ( entry.z == z ) )
×
4390
        {
4391
          std::unique_ptr<BStruct> arr( new BStruct );
×
4392
          arr->addMember( "x", new BLong( pos.x() ) );
×
4393
          arr->addMember( "y", new BLong( pos.y() ) );
×
4394
          arr->addMember( "z", new BLong( entry.z ) );
×
4395
          arr->addMember( "objtype", new BLong( entry.graphic ) );
×
4396
          newarr->addElement( arr.release() );
×
4397
        }
×
4398
      }
4399
    }
×
4400

4401
    return newarr.release();
×
4402
  }
×
4403
  return new BError( "Invalid parameter" );
×
4404
}
4405

4406
BObjectImp* UOExecutorModule::mf_ListStaticsNearLocation( /* x, y, z, range, flags, realm */ )
×
4407
{
4408
  Core::Pos2d pos;
×
4409
  int z, flags;
4410
  short range;
4411
  Realms::Realm* realm;
4412

4413
  if ( getRealmParam( 5, &realm ) && getPos2dParam( 0, 1, &pos, realm ) && getParam( 2, z ) &&
×
4414
       getParam( 3, range ) && getParam( 4, flags ) )
×
4415
  {
4416
    std::unique_ptr<ObjArray> newarr( new ObjArray );
×
4417
    Core::Vec2d radius( range, range );
×
4418
    Core::Range2d area( pos - radius, pos + radius, realm );
×
4419
    for ( const auto& tile : area )
×
4420
    {
4421
      if ( !( flags & ITEMS_IGNORE_STATICS ) )
×
4422
      {
4423
        Plib::StaticEntryList slist;
×
4424
        realm->getstatics( slist, tile );
×
4425

4426
        for ( const auto& entry : slist )
×
4427
        {
4428
          if ( ( z == LIST_IGNORE_Z ) || ( abs( entry.z - z ) < CONST_DEFAULT_ZRANGE ) )
×
4429
          {
4430
            std::unique_ptr<BStruct> arr( new BStruct );
×
4431
            arr->addMember( "x", new BLong( tile.x() ) );
×
4432
            arr->addMember( "y", new BLong( tile.y() ) );
×
4433
            arr->addMember( "z", new BLong( entry.z ) );
×
4434
            arr->addMember( "objtype", new BLong( entry.objtype ) );
×
4435
            arr->addMember( "hue", new BLong( entry.hue ) );
×
4436
            newarr->addElement( arr.release() );
×
4437
          }
×
4438
        }
4439
      }
×
4440

4441
      if ( !( flags & ITEMS_IGNORE_MULTIS ) )
×
4442
      {
4443
        Plib::StaticList mlist;
×
4444
        realm->readmultis( mlist, tile );
×
4445

4446
        for ( const auto& entry : mlist )
×
4447
        {
4448
          if ( ( z == LIST_IGNORE_Z ) || ( abs( entry.z - z ) < CONST_DEFAULT_ZRANGE ) )
×
4449
          {
4450
            std::unique_ptr<BStruct> arr( new BStruct );
×
4451
            arr->addMember( "x", new BLong( tile.x() ) );
×
4452
            arr->addMember( "y", new BLong( tile.y() ) );
×
4453
            arr->addMember( "z", new BLong( entry.z ) );
×
4454
            arr->addMember( "objtype", new BLong( entry.graphic ) );
×
4455
            newarr->addElement( arr.release() );
×
4456
          }
×
4457
        }
4458
      }
×
4459
    }
4460

4461
    return newarr.release();
×
4462
  }
×
4463
  return new BError( "Invalid parameter" );
×
4464
}
4465

4466
//  Birdy :  (Notes on Pathfinding)
4467
//
4468
//  This implimentation of pathfinding is actually from a very nice stl based A*
4469
//  implimentation.  It will, within reason, succeed in finding a path in a wide
4470
//  variety of circumstance, including traversing stairs and multiple level
4471
//  structures.
4472
//
4473
//  Unfortunately, it, or my POL support classes implimenting it, seems to at times
4474
//  generate corrupted results in finding a path.  These corruptions take the form
4475
//  of basically looping within the result list, or linking in non-closed list nodes
4476
//  which may be on the open list and generate more looping or which may have even
4477
//  been deleted by the algorithm, generally causing an illegal reference and crash.
4478
//
4479
//  Thus far, the above senerio has only been replicated, though fairly reliably, in
4480
//  cases where there are many many(about 100) npc's trying to pathfind at the same time,
4481
//  in close proximity to one another.
4482
//
4483
//  "Looping" will, of course, cause the shard to hang while the pathfinding routine
4484
//  tries to build the child list for the solution set.  An illegal reference will
4485
//  cause the shard to crash most likely.  Neither is tollerable of course, so I have
4486
//  put in two safeguards against this.
4487
//
4488
//  Safeguard #1) As we go through the nodes in the solution, I make sure each of them
4489
//        is on the Closed List.  If not, the pathfind errors out with a
4490
//        "Solution Corrupt!" error.  This is an attempt to short circuit any
4491
//        solution containing erroneous nodes in it.
4492
//
4493
//  Safeguard #2) I keep a vector of the nodes in the solution as we go through them.
4494
//        Before adding each node to the vector, I search that vector for that
4495
//        node.  If that vector already contains the node, the pathfind errors
4496
//        out with a "Solution Corrupt!" error.  THis is an attempt to catch
4497
//        the cases where Closed List nodes have looped for some reason.
4498
//
4499
//  These two safeguards should not truly be necessary for this algorithm, and take up
4500
//  space and time to guard against.  Thus, finding out why these problems are occurring
4501
//  in the first place would be great, as we could fix that instead and remove these
4502
//  checks.  If necessary to live with long term, then the solution vector should probably
4503
//  be made into a hash table for quick lookups.
4504
//
4505
//  I have also implimented a sort blocking list that is supposed to represent those
4506
//  spots that mobiles occupy and are thus not walkable on.  This is only maintained if
4507
//  the mobilesBlock parameter passed from escript is true.  It would probably be good
4508
//  to make this a hash table.  It may be said that it is a good idea to pre-process all
4509
//  blocking spots on the map, but this would hinder the ability to manage 3d shifts, or
4510
//  probably be prohibitive if an attempt were made to check for traversal at all possible
4511
//  various z's.  So it may be that checking such at the time of the search for the path
4512
//  is ultimately the best means of handling this.
4513
//
4514
//  Other details that may be nice to have here might be a method for interacting with
4515
//  escript, letting escript tell the pathfinder to ignore doors, and then the pathfinder
4516
//  putting in the solution list an indication to escript that at the given node a door
4517
//  was encountered, so that AI can open the door before attempting to traverse that
4518
//  path.  Providing the user the ability to define an array of objects which are to be
4519
//  considered as blocking, or an array of objects which are to be ignored even if they
4520
//  are blocking might be nice as well.  The former could be easily implimented by
4521
//  adding to the mobile's blocking list.  The latter may be more difficult to do
4522
//  considering if the item blocks, the walk function will not let you traverse it, and
4523
//  it is the walk function that we use to accurately replicate the ability of the
4524
//  mobile to traverse from one place to the next.  Additional functionality of some
4525
//  sort may be necessary to do this, or making a copy of the walk function and putting
4526
//  in a special walk function strictly for pathfinding which can take this array into
4527
//  account.
4528
//
4529
//  For now, however, these concerns are all unaddressed.  I wish to see if this
4530
//  function works and is useful, and also would prefer to find some way to track down
4531
//  the corruption of the solution nodes before adding more complexity to the problem.
4532
//
4533
//  Other than the below function, the following files will be added to CVS to support
4534
//  it in the src module.  They are :
4535
//
4536
//  stlastar.h  --  virtually untouched by me really, it is the original author's
4537
//          implimentation, pretty nicely done and documented if you are
4538
//          interested in learning about A*.
4539
//
4540
//  fsa.h     --  a "fixed size allocation" module which I toy with enabling and
4541
//          disabling.  Using it is supposed to get you some speed, but it
4542
//          limits your maps because it will only allocate a certain # of
4543
//          nodes at once, and after that, it will return out of memory.
4544
//          Presently, it is disabled, but will be submitted to CVS for
4545
//          completion in case we wish to use it later.
4546
//
4547
//  uopathnode.h -- this is stricly my work, love it or hate it, it's pretty quick
4548
//          and dirty, and can stand to be cleaned up, optimized, and so forth.
4549
//          I have the name field and function in there to allow for some
4550
//          debugging, though the character array could probably be removed to
4551
//          make the nodes smaller.  I didn't find it mattered particularly, since
4552
//          these puppies are created and destroyed at a pretty good clip.
4553
//          It is this class that encapsulates the necessary functionality to
4554
//          make the otherwise fairly generic stlastar class work.
4555

4556
using UOSearch = Plib::AStarSearch<UOPathState>;
4557

4558
BObjectImp* UOExecutorModule::mf_FindPath()
7✔
4559
{
4560
  Pos3d pos1, pos2;
7✔
4561
  Realms::Realm* realm;
4562
  const String* movemode_name;
4563

4564
  if ( !getPos3dParam( 0, 1, 2, &pos1 ) || !getPos3dParam( 3, 4, 5, &pos2 ) ||
14✔
4565
       !getRealmParam( 6, &realm ) || !getStringParam( 9, movemode_name ) )
14✔
4566
    return new BError( "Invalid parameter" );
×
4567
  if ( !pos1.in_range( pos2, settingsManager.ssopt.max_pathfind_range ) )
7✔
4568
    return new BError( "Beyond Max Range." );
×
4569

4570
  short theSkirt;
4571
  int flags;
4572

4573
  if ( !getParam( 7, flags ) )
7✔
4574
    flags = FP_IGNORE_MOBILES;
×
4575

4576
  if ( !getParam( 8, theSkirt ) )
7✔
4577
    theSkirt = 5;
×
4578

4579
  Plib::MOVEMODE movemode = Character::decode_movemode( movemode_name->value() );
7✔
4580
  if ( movemode == Plib::MOVEMODE_NONE )
7✔
4581
    return new BError( "Wrong movemode parameter" );
1✔
4582

4583
  if ( theSkirt < 0 )
6✔
4584
    theSkirt = 0;
×
4585

4586
  if ( !realm->valid( pos1.xy() ) )
6✔
4587
    return new BError( "Start Coordinates Invalid for Realm" );
×
4588
  if ( !realm->valid( pos2.xy() ) )
6✔
4589
    return new BError( "End Coordinates Invalid for Realm" );
×
4590

4591
  auto astarsearch = std::make_unique<UOSearch>();
6✔
4592

4593
  Range2d range( pos1.xy().min( pos2.xy() ) - Vec2d( theSkirt, theSkirt ),
6✔
4594
                 pos1.xy().max( pos2.xy() ) + Vec2d( theSkirt, theSkirt ), realm );
12✔
4595

4596
  if ( Plib::systemstate.config.loglevel >= 12 )
6✔
4597
  {
4598
    POLLOGLN( "[FindPath] Calling FindPath({}, {}, {}, {:#x}, {})", pos1, pos2, realm->name(),
×
4599
              flags, theSkirt );
4600
    POLLOGLN( "[FindPath]   search for Blockers inside {}", range );
×
4601
  }
4602

4603
  bool doors_block = ( flags & FP_IGNORE_DOORS ) ? false : true;
6✔
4604
  AStarParams params( range, doors_block, movemode, realm );
6✔
4605

4606
  if ( !( flags & FP_IGNORE_MOBILES ) )
6✔
4607
  {
4608
    WorldIterator<MobileFilter>::InBox( range, realm,
×
4609
                                        [&]( Mobile::Character* chr )
×
4610
                                        {
4611
                                          params.AddBlocker( chr->pos3d() );
×
4612

4613
                                          if ( Plib::systemstate.config.loglevel >= 12 )
×
4614
                                            POLLOGLN( "[FindPath]   add Blocker {} at {}",
×
4615
                                                      chr->name(), chr->pos() );
×
4616
                                        } );
×
4617
  }
4618

4619
  if ( Plib::systemstate.config.loglevel >= 12 )
6✔
4620
  {
4621
    POLLOGLN( "[FindPath]   use StartNode {}", pos1 );
×
4622
    POLLOGLN( "[FindPath]   use EndNode {}", pos2 );
×
4623
  }
4624

4625
  // Create a start state
4626
  UOPathState nodeStart( pos1, &params );
6✔
4627
  // Define the goal state
4628
  UOPathState nodeEnd( pos2, &params );
6✔
4629
  // Set Start and goal states
4630
  astarsearch->SetStartAndGoalStates( nodeStart, nodeEnd );
6✔
4631
  unsigned int SearchState;
4632
  do
4633
  {
4634
    SearchState = astarsearch->SearchStep();
28✔
4635
  } while ( SearchState == UOSearch::SEARCH_STATE_SEARCHING );
28✔
4636
  if ( SearchState == UOSearch::SEARCH_STATE_SUCCEEDED )
6✔
4637
  {
4638
    UOPathState* node = astarsearch->GetSolutionStart();
3✔
4639

4640
    auto nodeArray = std::make_unique<ObjArray>();
3✔
4641
    while ( ( node = astarsearch->GetSolutionNext() ) != nullptr )
12✔
4642
    {
4643
      auto nextStep = std::make_unique<BStruct>();
9✔
4644
      const auto& pos = node->position();
9✔
4645
      nextStep->addMember( "x", new BLong( pos.x() ) );
9✔
4646
      nextStep->addMember( "y", new BLong( pos.y() ) );
9✔
4647
      nextStep->addMember( "z", new BLong( pos.z() ) );
9✔
4648
      nodeArray->addElement( nextStep.release() );
9✔
4649
    }
9✔
4650
    astarsearch->FreeSolutionNodes();
3✔
4651
    return nodeArray.release();
3✔
4652
  }
3✔
4653
  if ( SearchState == UOSearch::SEARCH_STATE_FAILED )
3✔
4654
  {
4655
    return new BError( "Failed to find a path." );
3✔
4656
  }
4657
  if ( SearchState == UOSearch::SEARCH_STATE_OUT_OF_MEMORY )
×
4658
  {
4659
    return new BError( "Out of memory." );
×
4660
  }
4661
  if ( SearchState == UOSearch::SEARCH_STATE_SOLUTION_CORRUPTED )
×
4662
  {
4663
    return new BError( "Solution Corrupted!" );
×
4664
  }
4665

4666
  return new BError( "Pathfind Error." );
×
4667
}
6✔
4668

4669

4670
BObjectImp* UOExecutorModule::mf_UseItem()
×
4671
{
4672
  Item* item;
4673
  Character* chr;
4674

4675
  if ( getItemParam( 0, item ) && getCharacterParam( 1, chr ) )
×
4676
  {
4677
    const ItemDesc& itemdesc = find_itemdesc( item->objtype_ );
×
4678

4679
    if ( itemdesc.requires_attention && ( chr->skill_ex_active() || chr->casting_spell() ) )
×
4680
    {
4681
      if ( chr->client != nullptr )
×
4682
      {
4683
        send_sysmessage( chr->client, "I am already doing something else." );
×
4684
        return new BError( "Character busy." );
×
4685
        ;
4686
      }
4687
    }
4688

4689
    if ( itemdesc.requires_attention && chr->hidden() )
×
4690
      chr->unhide();
×
4691

4692
    ref_ptr<EScriptProgram> prog;
×
4693

4694
    std::string on_use_script = item->get_use_script_name();
×
4695

4696
    if ( !on_use_script.empty() )
×
4697
    {
4698
      ScriptDef sd( on_use_script, nullptr, "" );
×
4699
      prog = find_script2( sd,
×
4700
                           true,  // complain if not found
4701
                           Plib::systemstate.config.cache_interactive_scripts );
×
4702
    }
×
4703
    else if ( !itemdesc.on_use_script.empty() )
×
4704
    {
4705
      prog = find_script2( itemdesc.on_use_script, true,
×
4706
                           Plib::systemstate.config.cache_interactive_scripts );
×
4707
    }
4708

4709
    if ( prog.get() != nullptr )
×
4710
    {
4711
      if ( chr->start_itemuse_script( prog.get(), item, itemdesc.requires_attention ) )
×
4712
        return new BLong( 1 );
×
4713
      return new BError( "Failed to start script!" );
×
4714
      // else log the fact?
4715
    }
4716

4717
    if ( chr->client != nullptr )
×
4718
      item->builtin_on_use( chr->client );
×
4719
    return new BLong( 0 );
×
4720
  }
×
4721

4722
  return new BError( "Invalid parameter" );
×
4723
}
4724

4725
BObjectImp* UOExecutorModule::mf_FindSubstance()
3✔
4726
{
4727
  UContainer::Contents substanceVector;
3✔
4728

4729
  Item* cont_item;
4730
  unsigned int objtype;
4731
  int amount;
4732

4733
  if ( getItemParam( 0, cont_item ) && getObjtypeParam( 1, objtype ) && getParam( 2, amount ) )
3✔
4734
  {
4735
    bool makeInUse;
4736
    int flags;
4737
    if ( !getParam( 3, makeInUse ) )
3✔
4738
      makeInUse = false;
×
4739

4740
    if ( !getParam( 4, flags ) )
3✔
4741
      flags = 0;
×
4742

4743
    if ( !cont_item->isa( UOBJ_CLASS::CLASS_CONTAINER ) )
3✔
4744
      return new BError( "That is not a container" );
×
4745
    if ( amount < 0 )
3✔
4746
      return new BError( "Amount cannot be negative" );
×
4747

4748
    UContainer* cont = static_cast<UContainer*>( cont_item );
3✔
4749
    int amthave = cont->find_sumof_objtype_noninuse( objtype, amount, substanceVector, flags );
3✔
4750
    if ( ( amthave < amount ) && ( !( flags & FINDSUBSTANCE_FIND_ALL ) ) )
3✔
4751
      return new BError( "Not enough of that substance in container" );
1✔
4752

4753
    std::unique_ptr<ObjArray> theArray( new ObjArray() );
2✔
4754
    for ( auto item : substanceVector )
4✔
4755
    {
4756
      if ( item != nullptr )
2✔
4757
      {
4758
        if ( ( makeInUse ) && ( !item->inuse() ) )
2✔
4759
        {
4760
          item->inuse( true );
2✔
4761
          reserved_items_.emplace_back( item );
2✔
4762
        }
4763
        theArray->addElement( new EItemRefObjImp( item ) );
2✔
4764
      }
4765
    }
4766
    return theArray.release();
2✔
4767
  }
2✔
4768

4769
  return new BError( "Invalid parameter type" );
×
4770
}
3✔
4771

4772
BObjectImp* UOExecutorModule::mf_IsStackable()
×
4773
{
4774
  Item* item1;
4775
  Item* item2;
4776

4777
  if ( !( getItemParam( 0, item1 ) && getItemParam( 1, item2 ) ) )
×
4778
  {
4779
    return new BError( "Invalid parameter type" );
×
4780
  }
4781

4782
  if ( item1->objtype_ != item2->objtype_ )
×
4783
    return new BError( "Objtypes differs" );
×
4784
  if ( !item1->stackable() )
×
4785
    return new BError( "That item type is not stackable." );
×
4786

4787
  if ( item1->can_add_to_self( *item2, false ) )
×
4788
    return new BLong( 1 );
×
4789
  return new BError( "Failed to stack" );
×
4790
}
4791

4792
BObjectImp* UOExecutorModule::mf_UpdateMobile()
×
4793
{
4794
  Character* chr;
4795
  int flags;
4796

4797
  if ( getCharacterParam( 0, chr ) && getParam( 1, flags ) )
×
4798
  {
4799
    if ( flags == 1 )
×
4800
    {
4801
      if ( ( !chr->isa( UOBJ_CLASS::CLASS_NPC ) ) && ( chr->client ) )  // no npc and active client
×
4802
        send_owncreate( chr->client, chr );                             // inform self
×
4803
      if ( ( chr->isa( UOBJ_CLASS::CLASS_NPC ) ) || ( chr->client ) )   // npc or active client
×
4804
        send_create_mobile_to_nearby_cansee( chr );                     // inform other
×
4805
      else
4806
        return new BError( "Mobile is offline" );
×
4807
    }
4808
    else
4809
    {
4810
      if ( ( !chr->isa( UOBJ_CLASS::CLASS_NPC ) ) && ( chr->client ) )  // no npc and active client
×
4811
        send_move( chr->client, chr );                                  // inform self
×
4812
      if ( ( chr->isa( UOBJ_CLASS::CLASS_NPC ) ) || ( chr->client ) )   // npc or active client
×
4813
        send_move_mobile_to_nearby_cansee( chr );                       // inform other
×
4814
      else
4815
        return new BError( "Mobile is offline" );
×
4816
    }
4817
    return new BLong( 1 );
×
4818
  }
4819
  return new BError( "Invalid parameter type" );
×
4820
}
4821

4822
BObjectImp* UOExecutorModule::mf_UpdateItem()
×
4823
{
4824
  Item* item;
4825

4826
  if ( getItemParam( 0, item ) )
×
4827
  {
4828
    send_item_to_inrange( item );
×
4829
    return new BLong( 1 );
×
4830
  }
4831

4832
  return new BError( "Invalid parameter type" );
×
4833
}
4834

4835

4836
BObjectImp* UOExecutorModule::mf_CanWalk(
×
4837
    /*movemode, x1, y1, z1, x2_or_dir, y2 := -1, realm := DEF*/ )
4838
{
4839
  Pos4d p;
×
4840
  int x2_or_dir, y2_;
4841
  const String* movemode_name;
4842

4843
  if ( ( getStringParam( 0, movemode_name ) ) && ( getPos4dParam( 1, 2, 3, 6, &p ) ) &&
×
4844
       ( getParam( 4, x2_or_dir ) ) && ( getParam( 5, y2_ ) ) )
×
4845
  {
4846
    Plib::MOVEMODE movemode = Character::decode_movemode( movemode_name->value() );
×
4847

4848
    Core::UFACING dir;
4849
    if ( y2_ == -1 )
×
4850
      dir = static_cast<Core::UFACING>( x2_or_dir & 0x7 );
×
4851
    else
4852
    {
4853
      auto p1 = Pos2d( static_cast<u16>( x2_or_dir ), static_cast<u16>( y2_ ) );
×
4854
      if ( !p.realm()->valid( p1 ) )
×
4855
        return new BError( "Invalid coordinates for realm." );
×
4856

4857
      dir = p.xy().direction_toward( p1 );
×
4858
    }
4859

4860
    if ( dir & 1 )  // check if diagonal movement is allowed
×
4861
    {
4862
      short new_z;
4863
      u8 tmp_facing = ( dir + 1 ) & 0x7;
×
4864
      auto tmp_pos = p.move( static_cast<Core::UFACING>( tmp_facing ) );
×
4865

4866
      // needs to save because if only one direction is blocked, it shouldn't block ;)
4867
      bool walk1 = p.realm()->walkheight( tmp_pos.xy(), tmp_pos.z(), &new_z, nullptr, nullptr, true,
×
4868
                                          movemode, nullptr );
4869

4870
      tmp_facing = ( dir - 1 ) & 0x7;
×
4871
      tmp_pos = p.move( static_cast<Core::UFACING>( tmp_facing ) );
×
4872

4873
      if ( !walk1 && !p.realm()->walkheight( tmp_pos.xy(), tmp_pos.z(), &new_z, nullptr, nullptr,
×
4874
                                             true, movemode, nullptr ) )
4875
        return new BError( "Cannot walk there" );
×
4876
    }
4877

4878
    p.move_to( dir );
×
4879
    short newz;
4880

4881
    if ( !p.realm()->walkheight( p.xy(), p.z(), &newz, nullptr, nullptr, true, movemode, nullptr ) )
×
4882
      return new BError( "Cannot walk there" );
×
4883

4884
    return new BLong( newz );
×
4885
  }
4886
  return new BError( "Invalid parameter" );
×
4887
}
4888

4889

4890
BObjectImp* UOExecutorModule::mf_SendCharProfile(
×
4891
    /*chr, of_who, title, uneditable_text := array, editable_text := array*/ )
4892
{
4893
  Character *chr, *of_who;
4894
  const String* title;
4895
  const String* uText;
4896
  const String* eText;
4897

4898
  if ( getCharacterParam( 0, chr ) && getCharacterParam( 1, of_who ) &&
×
4899
       getStringParam( 2, title ) && getUnicodeStringParam( 3, uText ) &&
×
4900
       getUnicodeStringParam( 4, eText ) )
×
4901
  {
4902
    if ( chr->logged_in() && of_who->logged_in() )
×
4903
    {
4904
      if ( uText->length() > SPEECH_MAX_LEN || eText->length() > SPEECH_MAX_LEN )
×
4905
        return new BError( "Text exceeds maximum size." );
×
4906

4907
      sendCharProfile( chr, of_who, title->value(), uText->value(), eText->value() );
×
4908
      return new BLong( 1 );
×
4909
    }
4910
    return new BError( "Mobile must be online." );
×
4911
  }
4912
  return new BError( "Invalid parameter type" );
×
4913
}
4914

4915
BObjectImp* UOExecutorModule::mf_SendOverallSeason( /*season_id, playsound := 1*/ )
×
4916
{
4917
  int season_id, playsound;
4918

4919
  if ( getParam( 0, season_id ) && getParam( 1, playsound ) )
×
4920
  {
4921
    if ( season_id < 0 || season_id > 4 )
×
4922
      return new BError( "Invalid season id" );
×
4923

4924
    Network::PktHelper::PacketOut<Network::PktOut_BC> msg;
×
4925
    msg->Write<u8>( static_cast<u16>( season_id ) );
×
4926
    msg->Write<u8>( static_cast<u16>( playsound ) );
×
4927

4928
    for ( auto client : networkManager.clients )
×
4929
    {
4930
      if ( !client->chr || !client->chr->logged_in() || client->getversiondetail().major < 1 )
×
4931
        continue;
×
4932
      msg.Send( client );
×
4933
    }
4934
    return new BLong( 1 );
×
4935
  }
×
4936
  return new BError( "Invalid parameter" );
×
4937
}
4938

4939
// bresenham circle calculates the coords based on center coords and radius
4940
BObjectImp* UOExecutorModule::mf_GetMidpointCircleCoords( /* xcenter, ycenter, radius */ )
×
4941
{
4942
  int xcenter, ycenter, radius;
4943
  if ( !( getParam( 0, xcenter ) && getParam( 1, ycenter ) && getParam( 2, radius ) ) )
×
4944
  {
4945
    return new BError( "Invalid parameter type" );
×
4946
  }
4947
  std::unique_ptr<ObjArray> coords( new ObjArray );
×
4948

4949
  std::vector<std::tuple<int, int>> points;
×
4950
  auto add_point = [&coords]( int x, int y )
×
4951
  {
4952
    std::unique_ptr<BStruct> point( new BStruct );
×
4953
    point->addMember( "x", new BLong( x ) );
×
4954
    point->addMember( "y", new BLong( y ) );
×
4955
    coords->addElement( point.release() );
×
4956
  };
×
4957

4958
  if ( radius == 0 )
×
4959
  {
4960
    add_point( xcenter, ycenter );
×
4961
    return coords.release();
×
4962
  }
4963

4964
  // inside of each quadrant the points are sorted,
4965
  // store the quadrands in seperated vectors and merge them later
4966
  // -> automatically sorted
4967
  std::vector<std::tuple<int, int>> q1, q2, q3, q4;
×
4968
  int x = -radius, y = 0, err = 2 - 2 * radius; /* II. Quadrant */
×
4969
  do
4970
  {
4971
    q1.emplace_back( xcenter - x, ycenter + y ); /*   I. Quadrant */
×
4972
    q2.emplace_back( xcenter - y, ycenter - x ); /*  II. Quadrant */
×
4973
    q3.emplace_back( xcenter + x, ycenter - y ); /* III. Quadrant */
×
4974
    q4.emplace_back( xcenter + y, ycenter + x ); /*  IV. Quadrant */
×
4975
    radius = err;
×
4976
    if ( radius <= y )
×
4977
      err += ++y * 2 + 1;
×
4978
    if ( radius > x || err > y )
×
4979
      err += ++x * 2 + 1;
×
4980
  } while ( x < 0 );
×
4981

4982
  for ( const auto& p : q1 )
×
4983
    add_point( std::get<0>( p ), std::get<1>( p ) );
×
4984
  for ( const auto& p : q2 )
×
4985
    add_point( std::get<0>( p ), std::get<1>( p ) );
×
4986
  for ( const auto& p : q3 )
×
4987
    add_point( std::get<0>( p ), std::get<1>( p ) );
×
4988
  for ( const auto& p : q4 )
×
4989
    add_point( std::get<0>( p ), std::get<1>( p ) );
×
4990

4991
  return coords.release();
×
4992
}
×
4993

4994
size_t UOExecutorModule::sizeEstimate() const
14✔
4995
{
4996
  size_t size = sizeof( *this ) + Clib::memsize( reserved_items_ );
14✔
4997
  return size;
14✔
4998
}
4999
}  // namespace Module
5000
}  // namespace Pol
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc