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

polserver / polserver / 25990848974

17 May 2026 12:27PM UTC coverage: 60.822% (-0.08%) from 60.903%
25990848974

Pull #884

github

turleypol
listhostiles and attack_once accept now Attackable
Pull Request #884: Attackable item

268 of 575 new or added lines in 26 files covered. (46.61%)

17 existing lines in 7 files now uncovered.

44719 of 73524 relevant lines covered (60.82%)

513395.8 hits per line

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

34.09
/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
      {
NEW
776
        if ( obj->ismobile() )
×
777
        {
NEW
778
          Character* targetted_chr = static_cast<Character*>( obj );
×
UNCOV
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
  UObject* uobj;
2535
  u16 range;
2536
  int flags;
NEW
2537
  if ( getUObjectParam( 0, uobj ) && getParam( 1, range ) && getParam( 2, flags ) )
×
2538
  {
NEW
2539
    Attackable att{ uobj };
×
NEW
2540
    if ( !att )
×
NEW
2541
      return new BError( "Invalid parameter" );
×
2542

NEW
2543
    std::unique_ptr<ObjArray> arr( new ObjArray );
×
NEW
2544
    const Character::AttackableSet* hostiles = nullptr;
×
NEW
2545
    if ( auto* item = att.item() )
×
2546
    {
NEW
2547
      if ( item->has_opponent_of() )
×
NEW
2548
        hostiles = item->opponent_of();
×
2549
    }
NEW
2550
    else if ( auto* mob = att.mobile() )
×
NEW
2551
      hostiles = &mob->hostiles();
×
NEW
2552
    if ( !hostiles )
×
NEW
2553
      return arr.release();
×
NEW
2554
    for ( auto& hostile : *hostiles )
×
2555
    {
NEW
2556
      if ( auto* mob = hostile.mobile() )
×
2557
      {
NEW
2558
        if ( mob->concealed() )
×
NEW
2559
          continue;
×
NEW
2560
        if ( ( ~flags & LH_FLAG_INCLUDE_HIDDEN ) && mob->hidden() )
×
NEW
2561
          continue;
×
2562
      }
NEW
2563
      auto* obj = hostile.object();
×
NEW
2564
      if ( ( flags & LH_FLAG_LOS ) && !att.object()->realm()->has_los( *att.object(), *obj ) )
×
2565
        continue;
×
NEW
2566
      if ( !att.object()->in_range( obj, range ) )
×
2567
        continue;
×
NEW
2568
      arr->addElement( obj->make_ref() );
×
2569
    }
2570

2571
    return arr.release();
×
2572
  }
×
2573

2574
  return new BError( "Invalid parameter" );
×
2575
}
2576

2577
BObjectImp* UOExecutorModule::mf_CheckLineOfSight()
1✔
2578
{
2579
  UObject* src;
2580
  UObject* dst;
2581
  if ( getUObjectParam( 0, src ) && getUObjectParam( 1, dst ) )
1✔
2582
  {
2583
    return new BLong( src->realm()->has_los( *src, *dst->toplevel_owner() ) );
1✔
2584
  }
2585

2586
  return new BLong( 0 );
×
2587
}
2588

2589
BObjectImp* UOExecutorModule::mf_CheckLosAt()
1✔
2590
{
2591
  UObject* src;
2592
  Core::Pos3d pos;
1✔
2593
  if ( getUObjectParam( 0, src ) && getPos3dParam( 1, 2, 3, &pos, src->realm() ) )
1✔
2594
  {
2595
    LosObj tgt( Core::Pos4d( pos, src->realm() ) );
1✔
2596
    return new BLong( src->realm()->has_los( *src, tgt ) );
1✔
2597
  }
2598
  return nullptr;
×
2599
}
2600

2601
BObjectImp* UOExecutorModule::mf_CheckLosBetween()
2✔
2602
{
2603
  Core::Pos3d p1, p2;
2✔
2604
  Realms::Realm* realm;
2605
  if ( getRealmParam( 6, &realm ) && getPos3dParam( 0, 1, 2, &p1, realm ) &&
4✔
2606
       getPos3dParam( 3, 4, 5, &p2, realm ) )
2✔
2607
  {
2608
    LosObj att( Core::Pos4d( p1, realm ) );
2✔
2609
    LosObj tgt( Core::Pos4d( p2, realm ) );
2✔
2610
    return new BLong( realm->has_los( att, tgt ) );
2✔
2611
  }
2612
  return nullptr;
×
2613
}
2614

2615
BObjectImp* UOExecutorModule::mf_DestroyItem()
6,190✔
2616
{
2617
  Item* item;
2618
  if ( getItemParam( 0, item ) )
6,190✔
2619
  {
2620
    if ( item->has_gotten_by() )
6,183✔
2621
      item->gotten_by()->clear_gotten_item();
×
2622
    else if ( item->inuse() && !is_reserved_to_me( item ) )
6,183✔
2623
      return new BError( "That item is being used." );
×
2624
    else if ( item->script_isa( POLCLASS_MULTI ) )
6,183✔
2625
      return new BError( "That item is a multi. Use uo::DestroyMulti instead." );
×
2626

2627
    const ItemDesc& id = find_itemdesc( item->objtype_ );
6,183✔
2628
    if ( !id.destroy_script.empty() )
6,183✔
2629
    {
2630
      BObjectImp* res = run_script_to_completion( id.destroy_script, new EItemRefObjImp( item ) );
×
2631
      if ( res->isTrue() )
×
2632
      {  // destruction is okay
2633
        BObject ob( res );
×
2634
      }
×
2635
      else
2636
      {
2637
        // destruction isn't okay!
2638
        return res;
×
2639
      }
2640
    }
2641
    UpdateCharacterOnDestroyItem( item );
6,183✔
2642
    UpdateCharacterWeight( item );
6,183✔
2643
    destroy_item( item );
6,183✔
2644
    return new BLong( 1 );
6,183✔
2645
  }
2646

2647
  return new BError( "Invalid parameter type" );
7✔
2648
}
2649

2650
BObjectImp* UOExecutorModule::mf_SetName()
6✔
2651
{
2652
  UObject* obj;
2653
  const String* name_str;
2654

2655
  if ( getUObjectParam( 0, obj ) && getStringParam( 1, name_str ) )
6✔
2656
  {
2657
    obj->setname( name_str->value() );
6✔
2658
    return new BLong( 1 );
6✔
2659
  }
2660

2661
  return new BLong( 0 );
×
2662
}
2663

2664
BObjectImp* UOExecutorModule::mf_GetPosition()
×
2665
{
2666
  UObject* obj;
2667
  if ( getUObjectParam( 0, obj ) )
×
2668
  {
2669
    std::unique_ptr<BStruct> arr( new BStruct );
×
2670
    arr->addMember( "x", new BLong( obj->x() ) );
×
2671
    arr->addMember( "y", new BLong( obj->y() ) );
×
2672
    arr->addMember( "z", new BLong( obj->z() ) );
×
2673
    return arr.release();
×
2674
  }
×
2675

2676
  return new BLong( 0 );
×
2677
}
2678

2679
BObjectImp* UOExecutorModule::mf_EnumerateItemsInContainer()
6✔
2680
{
2681
  Item* item;
2682
  if ( getItemParam( 0, item ) )
6✔
2683
  {
2684
    int flags = 0;
6✔
2685
    if ( exec.fparams.size() >= 2 )
6✔
2686
    {
2687
      if ( !getParam( 1, flags ) )
6✔
2688
        return new BError( "Invalid parameter type" );
×
2689
    }
2690

2691
    if ( item->isa( UOBJ_CLASS::CLASS_CONTAINER ) )
6✔
2692
    {
2693
      std::unique_ptr<ObjArray> newarr( new ObjArray );
6✔
2694

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

2697
      return newarr.release();
6✔
2698
    }
6✔
2699

2700
    return nullptr;
×
2701
  }
2702
  return new BError( "Invalid parameter type" );
×
2703
}
2704

2705
BObjectImp* UOExecutorModule::mf_EnumerateOnlineCharacters()
×
2706
{
2707
  std::unique_ptr<ObjArray> newarr( new ObjArray );
×
2708

2709
  for ( auto client : networkManager.clients )
×
2710
  {
2711
    if ( client->chr != nullptr )
×
2712
    {
2713
      newarr->addElement( new ECharacterRefObjImp( client->chr ) );
×
2714
    }
2715
  }
2716

2717
  return newarr.release();
×
2718
}
×
2719

2720
BObjectImp* UOExecutorModule::mf_RegisterForSpeechEvents()
1✔
2721
{
2722
  UObject* center;
2723
  int range;
2724
  if ( getUObjectParam( 0, center ) && getParam( 1, range ) )
1✔
2725
  {
2726
    int flags = 0;
1✔
2727
    if ( exec.hasParams( 3 ) )
1✔
2728
    {
2729
      if ( !getParam( 2, flags ) )
1✔
2730
        return new BError( "Invalid parameter type" );
×
2731
    }
2732
    if ( !registered_for_speech_events )
1✔
2733
    {
2734
      ListenPoint::register_for_speech_events( center, &uoexec(), range, flags );
1✔
2735
      registered_for_speech_events = true;
1✔
2736
      return new BLong( 1 );
1✔
2737
    }
2738

2739
    return new BError( "Already registered for speech events" );
×
2740
  }
2741

2742
  return new BError( "Invalid parameter type" );
×
2743
}
2744

2745
BObjectImp* UOExecutorModule::mf_EnableEvents()
5✔
2746
{
2747
  int eventmask;
2748
  if ( getParam( 0, eventmask ) )
5✔
2749
  {
2750
    auto& uoex = uoexec();
5✔
2751
    if ( eventmask & ( EVID_ENTEREDAREA | EVID_LEFTAREA | EVID_SPOKE ) )
5✔
2752
    {
2753
      unsigned short range;
2754
      if ( getParam( 1, range, 0, 32 ) )
5✔
2755
      {
2756
        if ( eventmask & ( EVID_SPOKE ) )
5✔
2757
          uoex.speech_size = range;
2✔
2758
        if ( eventmask & ( EVID_ENTEREDAREA | EVID_LEFTAREA ) )
5✔
2759
          uoex.area_size = range;
3✔
2760
      }
2761
      else
2762
      {
2763
        return nullptr;
×
2764
      }
2765

2766

2767
      unsigned short evopts = 0;
5✔
2768
      if ( exec.hasParams( 2 ) && getParam( 2, evopts ) )
5✔
2769
      {
2770
        if ( eventmask & ( EVID_ENTEREDAREA | EVID_LEFTAREA ) )
5✔
2771
          uoex.area_mask = static_cast<unsigned char>( evopts & 255 );
3✔
2772
      }
2773
    }
2774
    uoex.eventmask |= eventmask;
5✔
2775
    return new BLong( uoex.eventmask );
5✔
2776
  }
2777

2778
  return new BError( "Invalid parameter" );
×
2779
}
2780

2781
BObjectImp* UOExecutorModule::mf_DisableEvents()
×
2782
{
2783
  int eventmask;
2784
  if ( getParam( 0, eventmask ) )
×
2785
  {
2786
    auto& uoex = uoexec();
×
2787
    uoex.eventmask &= ~eventmask;
×
2788

2789
    return new BLong( uoex.eventmask );
×
2790
  }
2791

2792
  return new BError( "Invalid parameter" );
×
2793
}
2794

2795
BObjectImp* UOExecutorModule::mf_Resurrect()
×
2796
{
2797
  Character* chr;
2798
  if ( getCharacterParam( 0, chr ) )
×
2799
  {
2800
    if ( !chr->dead() )
×
2801
      return new BError( "That is not dead" );
×
2802
    int flags = 0;
×
2803
    if ( exec.hasParams( 2 ) )
×
2804
    {
2805
      if ( !getParam( 1, flags ) )
×
2806
        return new BError( "Invalid parameter type" );
×
2807
    }
2808

2809
    if ( ~flags & RESURRECT_FORCELOCATION )
×
2810
    {
2811
      // we want doors to block ghosts in this case.
2812
      bool doors_block = !( chr->graphic == UOBJ_GAMEMASTER ||
×
2813
                            chr->cached_settings.get( PRIV_FLAGS::IGNORE_DOORS ) );
×
2814
      short newz;
2815
      Multi::UMulti* supporting_multi;
2816
      Item* walkon_item;
2817
      if ( !chr->realm()->walkheight( chr->pos2d(), chr->z(), &newz, &supporting_multi,
×
2818
                                      &walkon_item, doors_block, chr->movemode ) )
×
2819
      {
2820
        return new BError( "That location is blocked" );
×
2821
      }
2822
    }
2823

2824
    chr->resurrect();
×
2825
    return new BLong( 1 );
×
2826
  }
2827

2828
  return new BError( "Invalid parameter type" );
×
2829
}
2830

2831
BObjectImp* UOExecutorModule::mf_SystemFindObjectBySerial()
20✔
2832
{
2833
  int serial;
2834
  if ( getParam( 0, serial ) )
20✔
2835
  {
2836
    int sysfind_flags = 0;
20✔
2837
    if ( exec.hasParams( 2 ) )
20✔
2838
    {
2839
      if ( !getParam( 1, sysfind_flags ) )
20✔
2840
        return new BError( "Invalid parameter type" );
×
2841
    }
2842
    if ( IsCharacter( serial ) )
20✔
2843
    {
2844
      Character* chr = system_find_mobile( serial );
3✔
2845
      if ( chr != nullptr )
3✔
2846
      {
2847
        if ( sysfind_flags & SYSFIND_SEARCH_OFFLINE_MOBILES )
2✔
2848
          return new EOfflineCharacterRefObjImp( chr );
×
2849
        return new ECharacterRefObjImp( chr );
2✔
2850
      }
2851

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

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

2859
    if ( item != nullptr )
17✔
2860
    {
2861
      return item->make_ref();
16✔
2862
    }
2863

2864
    return new BError( "Item not found." );
1✔
2865
  }
2866

2867
  return new BError( "Invalid parameter type" );
×
2868
}
2869

2870
BObjectImp* UOExecutorModule::mf_SaveWorldState()
2✔
2871
{
2872
  update_gameclock();
2✔
2873
  cancel_all_trades();
2✔
2874

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

2917
  // non waiting version
2918
  u32 dirty, clean;
2919
  s64 elapsed_ms;
2920
  auto res = write_data( {}, &dirty, &clean, &elapsed_ms );
1✔
2921
  if ( !res )
1✔
2922
    return new BError( "pol.cfg has InhibitSaves=1" );
×
2923
  if ( *res )
1✔
2924
  {
2925
    BStruct* ret = new BStruct();
1✔
2926
    ret->addMember( "DirtyObjects", new BLong( dirty ) );
1✔
2927
    ret->addMember( "CleanObjects", new BLong( clean ) );
1✔
2928
    ret->addMember( "ElapsedMilliseconds", new BLong( Clib::clamp_convert<int>( elapsed_ms ) ) );
1✔
2929
    return ret;
1✔
2930
  }
2931
  return new BError( "Failed to save world" );
×
2932
}
2✔
2933

2934

2935
BObjectImp* UOExecutorModule::mf_SetRegionLightLevel()
×
2936
{
2937
  const String* region_name_str;
2938
  int lightlevel;
2939
  if ( !( getStringParam( 0, region_name_str ) && getParam( 1, lightlevel ) ) )
×
2940
  {
2941
    return new BError( "Invalid Parameter type" );
×
2942
  }
2943

2944
  if ( !VALID_LIGHTLEVEL( lightlevel ) )
×
2945
  {
2946
    return new BError( "Light Level is out of range" );
×
2947
  }
2948

2949
  LightRegion* lightregion = gamestate.lightdef->getregion( region_name_str->value() );
×
2950
  if ( lightregion == nullptr )
×
2951
  {
2952
    return new BError( "Light region not found" );
×
2953
  }
2954

2955
  SetRegionLightLevel( lightregion, lightlevel );
×
2956
  return new BLong( 1 );
×
2957
}
2958

2959
BObjectImp* UOExecutorModule::mf_SetRegionWeatherLevel()
×
2960
{
2961
  const String* region_name_str;
2962
  int type;
2963
  int severity;
2964
  int aux;
2965
  int lightoverride;
2966
  if ( !( getStringParam( 0, region_name_str ) && getParam( 1, type ) && getParam( 2, severity ) &&
×
2967
          getParam( 3, aux ) && getParam( 4, lightoverride ) ) )
×
2968
  {
2969
    return new BError( "Invalid Parameter type" );
×
2970
  }
2971

2972
  WeatherRegion* weatherregion = gamestate.weatherdef->getregion( region_name_str->value() );
×
2973
  if ( weatherregion == nullptr )
×
2974
  {
2975
    return new BError( "Weather region not found" );
×
2976
  }
2977

2978
  SetRegionWeatherLevel( weatherregion, type, severity, aux, lightoverride );
×
2979

2980
  return new BLong( 1 );
×
2981
}
2982
BObjectImp* UOExecutorModule::mf_AssignRectToWeatherRegion()
×
2983
{
2984
  const String* region_name_str;
2985
  Pos2d nw, se;
×
2986
  Realms::Realm* realm;
2987

2988
  if ( !( getStringParam( 0, region_name_str ) && getRealmParam( 5, &realm ) &&
×
2989
          getPos2dParam( 1, 2, &nw, realm ) && getPos2dParam( 3, 4, &se, realm ) ) )
×
2990
  {
2991
    return new BError( "Invalid Parameter type" );
×
2992
  }
2993

2994
  bool res = gamestate.weatherdef->assign_zones_to_region( region_name_str->data(),
×
2995
                                                           Range2d( nw, se, nullptr ), realm );
×
2996
  if ( res )
×
2997
    return new BLong( 1 );
×
2998
  return new BError( "Weather region not found" );
×
2999
}
3000

3001
BObjectImp* UOExecutorModule::mf_Distance()
×
3002
{
3003
  UObject* obj1;
3004
  UObject* obj2;
3005
  if ( getUObjectParam( 0, obj1 ) && getUObjectParam( 1, obj2 ) )
×
3006
    return new BLong( obj1->distance_to( obj2->toplevel_pos() ) );
×
3007
  return new BError( "Invalid parameter type" );
×
3008
}
3009

3010
BObjectImp* UOExecutorModule::mf_DistanceEuclidean()
×
3011
{
3012
  UObject* obj1;
3013
  UObject* obj2;
3014
  if ( getUObjectParam( 0, obj1 ) && getUObjectParam( 1, obj2 ) )
×
3015
  {
3016
    const UObject* tobj1 = obj1->toplevel_owner();
×
3017
    const UObject* tobj2 = obj2->toplevel_owner();
×
3018
    return new Double( sqrt( pow( (double)( tobj1->x() - tobj2->x() ), 2 ) +
×
3019
                             pow( (double)( tobj1->y() - tobj2->y() ), 2 ) ) );
×
3020
  }
3021

3022
  return new BError( "Invalid parameter type" );
×
3023
}
3024

3025
BObjectImp* UOExecutorModule::mf_CoordinateDistance()
×
3026
{
3027
  Pos2d pos1, pos2;
×
3028
  if ( !getPos2dParam( 0, 1, &pos1 ) || !getPos2dParam( 2, 3, &pos2 ) )
×
3029
    return new BError( "Invalid parameter type" );
×
3030
  return new BLong( pos1.pol_distance( pos2 ) );
×
3031
}
3032

3033
BObjectImp* UOExecutorModule::mf_CoordinateDistanceEuclidean()
×
3034
{
3035
  unsigned short x1, y1, x2, y2;
3036
  if ( !( getParam( 0, x1 ) && getParam( 1, y1 ) && getParam( 2, x2 ) && getParam( 3, y2 ) ) )
×
3037
  {
3038
    return new BError( "Invalid parameter type" );
×
3039
  }
3040
  return new Double( sqrt( pow( (double)( x1 - x2 ), 2 ) + pow( (double)( y1 - y2 ), 2 ) ) );
×
3041
}
3042

3043
BObjectImp* UOExecutorModule::mf_GetCoordsInLine()
×
3044
{
3045
  int x1, y1, x2, y2;
3046
  if ( !( getParam( 0, x1 ) && getParam( 1, y1 ) && getParam( 2, x2 ) && getParam( 3, y2 ) ) )
×
3047
  {
3048
    return new BError( "Invalid parameter type" );
×
3049
  }
3050
  if ( x1 == x2 && y1 == y2 )
×
3051
  {  // Same exact coordinates ... just give them the coordinate back!
3052
    ObjArray* coords = new ObjArray;
×
3053
    BStruct* point = new BStruct;
×
3054
    point->addMember( "x", new BLong( x1 ) );
×
3055
    point->addMember( "y", new BLong( y1 ) );
×
3056
    coords->addElement( point );
×
3057
    return coords;
×
3058
  }
3059

3060
  double dx = abs( x2 - x1 ) + 0.5;
×
3061
  double dy = abs( y2 - y1 ) + 0.5;
×
3062
  int vx = 0, vy = 0;
×
3063

3064
  if ( x2 > x1 )
×
3065
    vx = 1;
×
3066
  else if ( x2 < x1 )
×
3067
    vx = -1;
×
3068
  if ( y2 > y1 )
×
3069
    vy = 1;
×
3070
  else if ( y2 < y1 )
×
3071
    vy = -1;
×
3072

3073
  std::unique_ptr<ObjArray> coords( new ObjArray );
×
3074
  if ( dx >= dy )
×
3075
  {
3076
    dy = dy / dx;
×
3077

3078
    for ( int c = 0; c <= dx; c++ )
×
3079
    {
3080
      int point_x = x1 + ( c * vx );
×
3081

3082
      double float_y = double( c ) * double( vy ) * dy;
×
3083
      if ( float_y - floor( float_y ) >= 0.5 )
×
3084
        float_y = ceil( float_y );
×
3085
      int point_y = int( float_y ) + y1;
×
3086

3087
      std::unique_ptr<BStruct> point( new BStruct );
×
3088
      point->addMember( "x", new BLong( point_x ) );
×
3089
      point->addMember( "y", new BLong( point_y ) );
×
3090
      coords->addElement( point.release() );
×
3091
    }
×
3092
  }
3093
  else
3094
  {
3095
    dx = dx / dy;
×
3096
    for ( int c = 0; c <= dy; c++ )
×
3097
    {
3098
      int point_y = y1 + ( c * vy );
×
3099

3100
      double float_x = double( c ) * double( vx ) * dx;
×
3101
      if ( float_x - floor( float_x ) >= 0.5 )
×
3102
        float_x = ceil( float_x );
×
3103
      int point_x = int( float_x ) + x1;
×
3104

3105
      std::unique_ptr<BStruct> point( new BStruct );
×
3106
      point->addMember( "x", new BLong( point_x ) );
×
3107
      point->addMember( "y", new BLong( point_y ) );
×
3108
      coords->addElement( point.release() );
×
3109
    }
×
3110
  }
3111
  return coords.release();
×
3112
}
×
3113

3114
BObjectImp* UOExecutorModule::mf_GetFacing()
×
3115
{
3116
  unsigned short from_x, from_y, to_x, to_y;
3117
  if ( !( getParam( 0, from_x ) && getParam( 1, from_y ) && getParam( 2, to_x ) &&
×
3118
          getParam( 3, to_y ) ) )
×
3119
  {
3120
    return new BError( "Invalid parameter type" );
×
3121
  }
3122

3123
  double x = to_x - from_x;
×
3124
  double y = to_y - from_y;
×
3125
  double pi = acos( double( -1 ) );
×
3126
  double r = sqrt( x * x + y * y );
×
3127

3128
  double angle = ( ( acos( x / r ) * 180.0 ) / pi );
×
3129

3130
  double y_check = ( ( asin( y / r ) * 180.0 ) / pi );
×
3131
  if ( y_check < 0 )
×
3132
  {
3133
    angle = 360 - angle;
×
3134
  }
3135

3136
  unsigned short facing = ( ( short( angle / 40 ) + 10 ) % 8 );
×
3137

3138
  return new BLong( facing );
×
3139
}
3140

3141
// FIXME : Should we do an Orphan check here as well? Ugh.
3142
void true_extricate( Item* item )
30✔
3143
{
3144
  send_remove_object_to_inrange( item );
30✔
3145
  if ( item->container != nullptr )
30✔
3146
  {
3147
    item->extricate();
1✔
3148
  }
3149
  else
3150
  {
3151
    remove_item_from_world( item );
29✔
3152
  }
3153
}
30✔
3154

3155
BObjectImp* UOExecutorModule::mf_MoveItemToContainer()
7✔
3156
{
3157
  Item* item;
3158
  Item* cont_item;
3159
  int px;
3160
  int py;
3161
  int add_to_existing_stack;
3162
  if ( !( getItemParam( 0, item ) && getItemParam( 1, cont_item ) && getParam( 2, px, -1, 65535 ) &&
14✔
3163
          getParam( 3, py, -1, 65535 ) && getParam( 4, add_to_existing_stack, 0, 2 ) ) )
7✔
3164
  {
3165
    return new BError( "Invalid parameter type" );
×
3166
  }
3167

3168
  ItemRef itemref( item );  // dave 1/28/3 prevent item from being destroyed before function ends
7✔
3169
  if ( !item->movable() )
7✔
3170
  {
3171
    Character* chr = controller_.get();
×
3172
    if ( chr == nullptr || !chr->can_move( item ) )
×
3173
      return new BError( "That is immobile" );
×
3174
  }
3175
  if ( item->inuse() && !is_reserved_to_me( item ) )
7✔
3176
  {
3177
    return new BError( "That item is being used." );
×
3178
  }
3179

3180

3181
  if ( !cont_item->isa( UOBJ_CLASS::CLASS_CONTAINER ) )
7✔
3182
  {
3183
    return new BError( "Non-container selected as target" );
×
3184
  }
3185
  UContainer* cont = static_cast<UContainer*>( cont_item );
7✔
3186

3187
  if ( cont->serial == item->serial )
7✔
3188
  {
3189
    return new BError( "Can't put a container into itself" );
×
3190
  }
3191
  if ( is_a_parent( cont, item->serial ) )
7✔
3192
  {
3193
    return new BError( "Can't put a container into an item in itself" );
×
3194
  }
3195
  if ( !cont->can_add( *item ) )
7✔
3196
  {
3197
    return new BError( "Container is too full to add that" );
×
3198
  }
3199
  // DAVE added this 12/04, call can/onInsert & can/onRemove scripts for this container
3200
  Character* chr_owner = cont->GetCharacterOwner();
7✔
3201
  if ( chr_owner == nullptr )
7✔
3202
    if ( controller_.get() != nullptr )
6✔
3203
      chr_owner = controller_.get();
×
3204

3205
  // daved changed order 1/26/3 check canX scripts before onX scripts.
3206
  UContainer* oldcont = item->container;
7✔
3207
  Item* existing_stack = nullptr;
7✔
3208

3209
  if ( ( oldcont != nullptr ) &&
8✔
3210
       ( !oldcont->check_can_remove_script( chr_owner, item, UContainer::MT_CORE_MOVED ) ) )
1✔
3211
    return new BError( "Could not remove item from its container." );
×
3212
  if ( item->orphan() )  // dave added 1/28/3, item might be destroyed in RTC script
7✔
3213
  {
3214
    return new BError( "Item was destroyed in CanRemove script" );
×
3215
  }
3216

3217
  if ( add_to_existing_stack )
7✔
3218
  {
3219
    existing_stack = cont->find_addable_stack( item );
×
3220
    if ( existing_stack != nullptr )
×
3221
    {
3222
      if ( !cont->can_insert_increase_stack( chr_owner, UContainer::MT_CORE_MOVED, existing_stack,
×
3223
                                             item->getamount(), item ) )
×
3224
        return new BError( "Could not add to existing stack" );
×
3225
    }
3226
    else if ( add_to_existing_stack == 2 )
×
3227
      add_to_existing_stack = 0;
×
3228
    else
3229
      return new BError( "There is no existing stack" );
×
3230
  }
3231

3232
  if ( !add_to_existing_stack )
7✔
3233
    if ( !cont->can_insert_add_item( chr_owner, UContainer::MT_CORE_MOVED, item ) )
7✔
3234
      return new BError( "Could not insert item into container." );
×
3235

3236
  if ( item->orphan() )  // dave added 1/28/3, item might be destroyed in RTC script
7✔
3237
  {
3238
    return new BError( "Item was destroyed in CanInsert Script" );
×
3239
  }
3240

3241
  if ( !item->check_unequiptest_scripts() || !item->check_unequip_script() )
7✔
3242
    return new BError( "Item cannot be unequipped" );
×
3243
  if ( item->orphan() )  // dave added 1/28/3, item might be destroyed in RTC script
7✔
3244
  {
3245
    return new BError( "Item was destroyed in Equip Script" );
×
3246
  }
3247

3248
  if ( oldcont != nullptr )
7✔
3249
  {
3250
    oldcont->on_remove( chr_owner, item, UContainer::MT_CORE_MOVED );
1✔
3251
    if ( item->orphan() )  // dave added 1/28/3, item might be destroyed in RTC script
1✔
3252
    {
3253
      return new BError( "Item was destroyed in OnRemove script" );
×
3254
    }
3255
  }
3256

3257
  if ( !add_to_existing_stack )
7✔
3258
  {
3259
    u8 slotIndex = item->slot_index();
7✔
3260
    if ( !cont->can_add_to_slot( slotIndex ) )
7✔
3261
    {
3262
      item->destroy();
×
3263
      return new BError( "No slots available in new container" );
×
3264
    }
3265
    if ( !item->slot_index( slotIndex ) )
7✔
3266
    {
3267
      item->destroy();
×
3268
      return new BError( "Couldn't set slot index on item" );
×
3269
    }
3270

3271
    Core::Pos2d cntpos;
7✔
3272
    if ( px < 0 || py < 0 )
7✔
3273
      cntpos = cont->get_random_location();
7✔
3274
    else
3275
    {
3276
      cntpos.x( static_cast<u16>( px ) ).y( static_cast<u16>( py ) );
×
3277
      if ( !cont->is_legal_posn( cntpos ) )
×
3278
        cntpos = cont->get_random_location();
×
3279
    }
3280

3281
    true_extricate( item );
7✔
3282

3283
    cont->add( item, cntpos );
7✔
3284
    update_item_to_inrange( item );
7✔
3285
    // DAVE added this 11/17: if in a Character's pack, update weight.
3286
    UpdateCharacterWeight( item );
7✔
3287

3288
    cont->on_insert_add_item( chr_owner, UContainer::MT_CORE_MOVED, item );
7✔
3289
    if ( item->orphan() )  // dave added 1/28/3, item might be destroyed in RTC script
7✔
3290
    {
3291
      return new BError( "Item was destroyed in OnInsert script" );
×
3292
    }
3293
  }
3294
  else
3295
  {
3296
    u16 amount = item->getamount();
×
3297
    true_extricate( item );
×
3298
    existing_stack->add_to_self( item );
×
3299
    update_item_to_inrange( existing_stack );
×
3300
    UpdateCharacterWeight( existing_stack );
×
3301

3302
    cont->on_insert_increase_stack( chr_owner, UContainer::MT_CORE_MOVED, existing_stack, amount );
×
3303
  }
3304

3305
  return new BLong( 1 );
7✔
3306
}
7✔
3307

3308

3309
BObjectImp* UOExecutorModule::mf_MoveItemToSecureTradeWin()
×
3310
{
3311
  Item* item;
3312
  Character* chr;
3313
  if ( !( getItemParam( 0, item ) && getCharacterParam( 1, chr ) ) )
×
3314
  {
3315
    return new BError( "Invalid parameter type" );
×
3316
  }
3317

3318
  ItemRef itemref( item );  // dave 1/28/3 prevent item from being destroyed before function ends
×
3319
  if ( !item->movable() )
×
3320
  {
3321
    Character* _chr = controller_.get();
×
3322
    if ( _chr == nullptr || !_chr->can_move( item ) )
×
3323
      return new BError( "That is immobile" );
×
3324
  }
3325
  if ( item->inuse() && !is_reserved_to_me( item ) )
×
3326
  {
3327
    return new BError( "That item is being used." );
×
3328
  }
3329

3330
  // daved changed order 1/26/3 check canX scripts before onX scripts.
3331
  UContainer* oldcont = item->container;
×
3332

3333
  // DAVE added this 12/04, call can/onInsert & can/onRemove scripts for this container
3334
  Character* chr_owner = nullptr;
×
3335
  if ( oldcont != nullptr )
×
3336
    chr_owner = oldcont->GetCharacterOwner();
×
3337
  if ( chr_owner == nullptr )
×
3338
    if ( controller_.get() != nullptr )
×
3339
      chr_owner = controller_.get();
×
3340

3341
  if ( ( oldcont != nullptr ) &&
×
3342
       ( !oldcont->check_can_remove_script( chr_owner, item, UContainer::MT_CORE_MOVED ) ) )
×
3343
    return new BError( "Could not remove item from its container." );
×
3344
  if ( item->orphan() )  // dave added 1/28/3, item might be destroyed in RTC script
×
3345
  {
3346
    return new BError( "Item was destroyed in CanRemove script" );
×
3347
  }
3348

3349
  if ( !item->check_unequiptest_scripts() || !item->check_unequip_script() )
×
3350
    return new BError( "Item cannot be unequipped" );
×
3351
  if ( item->orphan() )  // dave added 1/28/3, item might be destroyed in RTC script
×
3352
  {
3353
    return new BError( "Item was destroyed in Equip Script" );
×
3354
  }
3355

3356
  if ( oldcont != nullptr )
×
3357
  {
3358
    oldcont->on_remove( chr_owner, item, UContainer::MT_CORE_MOVED );
×
3359
    if ( item->orphan() )  // dave added 1/28/3, item might be destroyed in RTC script
×
3360
    {
3361
      return new BError( "Item was destroyed in OnRemove script" );
×
3362
    }
3363
  }
3364

3365
  true_extricate( item );
×
3366

3367
  return place_item_in_secure_trade_container( chr->client, item );
×
3368
}
×
3369

3370
BObjectImp* UOExecutorModule::mf_EquipItem()
23✔
3371
{
3372
  Character* chr;
3373
  Item* item;
3374
  if ( getCharacterParam( 0, chr ) && getItemParam( 1, item ) )
23✔
3375
  {
3376
    ItemRef itemref( item );  // dave 1/28/3 prevent item from being destroyed before function ends
23✔
3377
    if ( !item->movable() )
23✔
3378
    {
3379
      Character* _chr = controller_.get();
×
3380
      if ( _chr == nullptr || !_chr->can_move( item ) )
×
3381
        return new BError( "That is immobile" );
×
3382
    }
3383

3384
    if ( item->inuse() && !is_reserved_to_me( item ) )
23✔
3385
    {
3386
      return new BError( "That item is being used." );
×
3387
    }
3388

3389
    if ( !chr->equippable( item ) || !item->check_equiptest_scripts( chr ) )
23✔
3390
    {
3391
      return new BError( "That item is not equippable by that character" );
×
3392
    }
3393
    if ( item->orphan() )  // dave added 1/28/3, item might be destroyed in RTC script
23✔
3394
    {
3395
      return new BError( "Item was destroyed in EquipTest script" );
×
3396
    }
3397

3398
    item->layer = Plib::tilelayer( item->graphic );
23✔
3399

3400
    if ( item->has_equip_script() )
23✔
3401
    {
3402
      BObjectImp* res = item->run_equip_script( chr, false );
2✔
3403
      if ( !res->isTrue() )
2✔
3404
        return res;
×
3405
      BObject obj( res );
2✔
3406
    }
2✔
3407

3408

3409
    true_extricate( item );
23✔
3410

3411
    // at this point, 'item' is free - doesn't belong to the world, or a container.
3412
    chr->equip( item );
23✔
3413
    send_wornitem_to_inrange( chr, item );
23✔
3414

3415
    return new BLong( 1 );
23✔
3416
  }
23✔
3417

3418
  return new BError( "Invalid parameter type" );
×
3419
}
3420

3421
BObjectImp* UOExecutorModule::mf_RestartScript()
1✔
3422
{
3423
  UObject* obj;
3424
  if ( !getUObjectParam( 0, obj ) )
1✔
3425
    return new BError( "Invalid parameter" );
×
3426

3427
  if ( obj->script_isa( POLCLASS_NPC ) )
1✔
3428
  {
3429
    NPC* npc = static_cast<NPC*>( obj );
×
3430
    npc->restart_script();
×
3431
    return new BLong( 1 );
×
3432
  }
3433
  if ( obj->script_isa( POLCLASS_ITEM ) )
1✔
3434
  {
3435
    Item* item = static_cast<Item*>( obj );
1✔
3436
    item->stop_control_script();
1✔
3437
    return new BLong( item->start_control_script() );
1✔
3438
  }
3439

3440
  return new BError( "RestartScript only operates on NPCs and Items" );
×
3441
}
3442

3443

3444
BObjectImp* UOExecutorModule::mf_GetHarvestDifficulty()
×
3445
{
3446
  const String* resource;
3447
  unsigned short tiletype;
3448
  Pos2d pos;
×
3449
  Realms::Realm* realm;
3450
  if ( getStringParam( 0, resource ) && getRealmParam( 4, &realm ) && getPos2dParam( 1, 2, &pos ) &&
×
3451
       getParam( 3, tiletype ) )
×
3452
  {
3453
    return get_harvest_difficulty( resource->data(), Pos4d( pos, 0, realm ), tiletype );
×
3454
  }
3455

3456
  return new BError( "Invalid parameter" );
×
3457
}
3458

3459
BObjectImp* UOExecutorModule::mf_HarvestResource()
×
3460
{
3461
  Pos2d pos;
×
3462
  Realms::Realm* realm;
3463
  const String* resource;
3464
  int b;
3465
  int n;
3466

3467
  if ( getStringParam( 0, resource ) && getRealmParam( 5, &realm ) &&
×
3468
       getPos2dParam( 1, 2, &pos, realm ) && getParam( 3, b ) && getParam( 4, n ) )
×
3469
  {
3470
    if ( b <= 0 )
×
3471
      return new BError( "b must be >= 0" );
×
3472
    return harvest_resource( resource->data(), Pos4d( pos, 0, realm ), b, n );
×
3473
  }
3474

3475
  return new BError( "Invalid parameter" );
×
3476
}
3477

3478
BObjectImp* UOExecutorModule::mf_GetRegionName( /* objref */ )
×
3479
{
3480
  UObject* obj;
3481

3482
  if ( getUObjectParam( 0, obj ) )
×
3483
  {
3484
    JusticeRegion* justice_region;
3485
    if ( obj->isa( UOBJ_CLASS::CLASS_ITEM ) )
×
3486
      obj = obj->toplevel_owner();
×
3487

3488
    if ( obj->isa( UOBJ_CLASS::CLASS_CHARACTER ) )
×
3489
    {
3490
      Character* chr = static_cast<Character*>( obj );
×
3491

3492
      if ( chr->logged_in() )
×
3493
        justice_region = chr->client->gd->justice_region;
×
3494
      else
3495
        justice_region = gamestate.justicedef->getregion( chr->pos() );
×
3496
    }
3497
    else
3498
      justice_region = gamestate.justicedef->getregion( obj->pos() );
×
3499

3500
    if ( justice_region == nullptr )
×
3501
      return new BError( "No Region defined at this Location" );
×
3502
    return new String( justice_region->region_name() );
×
3503
  }
3504
  return new BError( "Invalid parameter" );
×
3505
}
3506

3507
BObjectImp* UOExecutorModule::mf_GetRegionNameAtLocation( /* x, y, realm */ )
×
3508
{
3509
  Pos2d pos;
×
3510
  Realms::Realm* realm;
3511
  if ( getRealmParam( 2, &realm ) && getPos2dParam( 0, 1, &pos ) )
×
3512
  {
3513
    JusticeRegion* justice_region = gamestate.justicedef->getregion( Pos4d( pos, 0, realm ) );
×
3514
    if ( justice_region == nullptr )
×
3515
      return new BError( "No Region defined at this Location" );
×
3516
    return new String( justice_region->region_name() );
×
3517
  }
3518
  return new BError( "Invalid parameter" );
×
3519
}
3520

3521
BObjectImp* UOExecutorModule::mf_GetRegionString()
×
3522
{
3523
  const String* resource;
3524
  const String* propname;
3525
  Pos2d pos;
×
3526
  Realms::Realm* realm;
3527
  if ( getStringParam( 0, resource ) && getRealmParam( 4, &realm ) && getPos2dParam( 1, 2, &pos ) &&
×
3528
       getStringParam( 3, propname ) )
×
3529
  {
3530
    return get_region_string( resource->data(), Pos4d( pos, 0, realm ), propname->value() );
×
3531
  }
3532

3533
  return new BError( "Invalid parameter" );
×
3534
}
3535

3536
BObjectImp* UOExecutorModule::mf_GetRegionLightLevelAtLocation( /* x, y, realm */ )
×
3537
{
3538
  Pos2d pos;
×
3539
  Realms::Realm* realm;
3540

3541
  if ( getRealmParam( 2, &realm ) && getPos2dParam( 0, 1, &pos ) )
×
3542
  {
3543
    LightRegion* light_region = gamestate.lightdef->getregion( Pos4d( pos, 0, realm ) );
×
3544
    int lightlevel;
3545
    if ( light_region != nullptr )
×
3546
      lightlevel = light_region->lightlevel;
×
3547
    else
3548
      lightlevel = settingsManager.ssopt.default_light_level;
×
3549
    return new BLong( lightlevel );
×
3550
  }
3551

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

3555

3556
BObjectImp* UOExecutorModule::mf_EquipFromTemplate()
1✔
3557
{
3558
  Character* chr;
3559
  const String* template_name;
3560
  if ( getCharacterParam( 0, chr ) && getStringParam( 1, template_name ) )
1✔
3561
  {
3562
    return equip_from_template( chr, template_name->value() );
1✔
3563
  }
3564

3565
  return new BError( "Invalid parameter" );
×
3566
}
3567

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

3579
  return new BError( "Invalid parameter" );
×
3580
}
3581

3582
// FIXME: Use a PrivUpdater here
3583
BObjectImp* UOExecutorModule::mf_RevokePrivilege()
1✔
3584
{
3585
  Character* chr;
3586
  const String* privstr;
3587
  if ( getCharacterParam( 0, chr ) && getStringParam( 1, privstr ) )
1✔
3588
  {
3589
    chr->revoke_privilege( privstr->data() );
1✔
3590
    return new BLong( 1 );
1✔
3591
  }
3592

3593
  return new BError( "Invalid parameter" );
×
3594
}
3595

3596
BObjectImp* UOExecutorModule::mf_ReadGameClock()
14✔
3597
{
3598
  return new BLong( read_gameclock() );
14✔
3599
}
3600

3601
unsigned char decode_xdigit( unsigned char ch )
×
3602
{
3603
  if ( ch >= '0' && ch <= '9' )
×
3604
    ch -= '0';
×
3605
  else if ( ch >= 'A' && ch <= 'F' )
×
3606
    ch = ch - 'A' + 0xa;
×
3607
  else if ( ch >= 'a' && ch <= 'f' )
×
3608
    ch = ch - 'a' + 0xa;
×
3609

3610
  return ch;
×
3611
}
3612

3613
BObjectImp* UOExecutorModule::mf_SendPacket()
×
3614
{
3615
  Character* chr;
3616
  Network::Client* client;
3617
  const String* str;
3618
  if ( getCharacterOrClientParam( 0, chr, client ) && getStringParam( 1, str ) )
×
3619
  {
3620
    if ( str->length() % 2 > 0 )
×
3621
    {
3622
      return new BError( "Invalid packet string length." );
×
3623
    }
3624
    Network::PktHelper::PacketOut<Network::EncryptedPktBuffer>
3625
        buffer;  // encryptedbuffer is the only one without getID buffer[0]
×
3626
    unsigned char* buf = reinterpret_cast<unsigned char*>( buffer->getBuffer() );
×
3627
    const char* s = str->data();
×
3628
    while ( buffer->offset < 2000 && isxdigit( s[0] ) && isxdigit( s[1] ) )
×
3629
    {
3630
      unsigned char ch;
3631
      ch = ( decode_xdigit( s[0] ) << 4 ) | decode_xdigit( s[1] );
×
3632
      *( buf++ ) = ch;
×
3633
      buffer->offset++;
×
3634
      s += 2;
×
3635
    }
3636
    if ( chr != nullptr )
×
3637
    {
3638
      if ( chr->has_active_client() )
×
3639
      {
3640
        buffer.Send( chr->client );
×
3641
        return new BLong( 1 );
×
3642
      }
3643

3644
      return new BError( "No client attached" );
×
3645
    }
3646
    if ( client != nullptr )
×
3647
    {
3648
      if ( client->isConnected() )
×
3649
      {
3650
        buffer.Send( client );
×
3651
        return new BLong( 1 );
×
3652
      }
3653

3654
      return new BError( "Client is disconnected" );
×
3655
    }
3656

3657
    return new BError( "Invalid parameter type" );
×
3658
  }
×
3659

3660
  return new BError( "Invalid parameter type" );
×
3661
}
3662

3663
BObjectImp* UOExecutorModule::mf_SendQuestArrow()
×
3664
{
3665
  Character* chr;
3666
  int x, y;
3667
  u32 arrowid = 0;
×
3668

3669
  if ( getCharacterParam( 0, chr ) && getParam( 1, x, -1, 1000000 ) &&
×
3670
       getParam( 2, y, -1, 1000000 ) )  // max values checked below
×
3671
  {
3672
    if ( !chr->has_active_client() )
×
3673
      return new BError( "No client attached" );
×
3674
    {
3675
      int arrow_id;
3676
      if ( exec.getParam( 3, arrow_id ) )
×
3677
      {
3678
        if ( arrow_id < 1 )
×
3679
          return new BError( "ArrowID out of range" );
×
3680
        arrowid = static_cast<u32>( arrow_id );
×
3681
      }
3682
      else
3683
        arrowid = uoexec().pid();
×
3684
    }
3685
    bool usesNewPktSize = ( chr->client->ClientType & Network::CLIENTTYPE_7090 ) > 0;
×
3686

3687
    Network::PktHelper::PacketOut<Network::PktOut_BA> msg;
×
3688
    if ( x == -1 && y == -1 )
×
3689
    {
3690
      msg->Write<u8>( PKTOUT_BA_ARROW_OFF );
×
3691
      msg->offset += 4;  // u16 x_tgt,y_tgt
×
3692
      if ( usesNewPktSize )
×
3693
      {
3694
        if ( !arrowid )
×
3695
        {
3696
          return new BError( "ArrowID must be supplied for cancelation." );
×
3697
        }
3698

3699
        msg->Write<u32>( static_cast<u32>( arrowid & 0xFFFFFFFF ) );
×
3700
      }
3701
    }
3702
    else
3703
    {
3704
      auto pos = Core::Pos2d( Clib::clamp_convert<u16>( x ), Clib::clamp_convert<u16>( y ) );
×
3705
      if ( !chr->realm()->valid( pos ) )
×
3706
        return new BError( "Invalid Coordinates for Realm" );
×
3707
      msg->Write<u8>( PKTOUT_BA_ARROW_ON );
×
3708
      msg->WriteFlipped<u16>( pos.x() );
×
3709
      msg->WriteFlipped<u16>( pos.y() );
×
3710
      if ( usesNewPktSize )
×
3711
        msg->Write<u32>( static_cast<u32>( arrowid & 0xFFFFFFFF ) );
×
3712
    }
3713
    msg.Send( chr->client );
×
3714
    return new BLong( arrowid );
×
3715
  }
×
3716

3717
  return new BError( "Invalid parameter" );
×
3718
}
3719

3720
BObjectImp* UOExecutorModule::mf_ConsumeReagents()
×
3721
{
3722
  Character* chr;
3723
  int spellid;
3724
  if ( getCharacterParam( 0, chr ) && getParam( 1, spellid ) )
×
3725
  {
3726
    if ( !VALID_SPELL_ID( spellid ) )
×
3727
    {
3728
      return new BError( "Spell ID out of range" );
×
3729
    }
3730
    USpell* spell = gamestate.spells[spellid];
×
3731
    if ( spell == nullptr )
×
3732
    {
3733
      return new BError( "No such spell" );
×
3734
    }
3735

3736
    return new BLong( spell->consume_reagents( chr ) ? 1 : 0 );
×
3737
  }
3738

3739
  return new BError( "Invalid parameter" );
×
3740
}
3741

3742
BObjectImp* UOExecutorModule::mf_StartSpellEffect()
×
3743
{
3744
  Character* chr;
3745
  int spellid;
3746
  if ( getCharacterParam( 0, chr ) && getParam( 1, spellid ) )
×
3747
  {
3748
    if ( !VALID_SPELL_ID( spellid ) )
×
3749
    {
3750
      return new BError( "Spell ID out of range" );
×
3751
    }
3752
    USpell* spell = gamestate.spells[spellid];
×
3753
    if ( spell == nullptr )
×
3754
    {
3755
      return new BError( "No such spell" );
×
3756
    }
3757

3758
    spell->cast( chr );
×
3759
    return new BLong( 1 );
×
3760
  }
3761

3762
  return new BError( "Invalid parameter" );
×
3763
}
3764
BObjectImp* UOExecutorModule::mf_GetSpellDifficulty()
×
3765
{
3766
  int spellid;
3767
  if ( getParam( 0, spellid ) )
×
3768
  {
3769
    if ( !VALID_SPELL_ID( spellid ) )
×
3770
    {
3771
      return new BError( "Spell ID out of range" );
×
3772
    }
3773
    USpell* spell = gamestate.spells[spellid];
×
3774
    if ( spell == nullptr )
×
3775
    {
3776
      return new BError( "No such spell" );
×
3777
    }
3778

3779
    return new BLong( spell->difficulty() );
×
3780
  }
3781

3782
  return new BError( "Invalid parameter" );
×
3783
}
3784
BObjectImp* UOExecutorModule::mf_SpeakPowerWords()
×
3785
{
3786
  Character* chr;
3787
  int spellid;
3788
  unsigned short font;
3789
  unsigned short color;
3790

3791
  if ( getCharacterParam( 0, chr ) && getParam( 1, spellid ) && getParam( 2, font ) &&
×
3792
       getParam( 3, color ) )
×
3793
  {
3794
    if ( !VALID_SPELL_ID( spellid ) )
×
3795
    {
3796
      return new BError( "Spell ID out of range" );
×
3797
    }
3798
    USpell* spell = gamestate.spells[spellid];
×
3799
    if ( spell == nullptr )
×
3800
    {
3801
      return new BError( "No such spell" );
×
3802
    }
3803

3804
    spell->speak_power_words( chr, font, color );
×
3805

3806
    return new BLong( 1 );
×
3807
  }
3808

3809
  return new BError( "Invalid parameter" );
×
3810
}
3811

3812
BObjectImp* UOExecutorModule::mf_ListEquippedItems()
1✔
3813
{
3814
  Character* chr;
3815
  if ( getCharacterParam( 0, chr ) )
1✔
3816
  {
3817
    std::unique_ptr<ObjArray> arr( new ObjArray );
1✔
3818
    for ( int layer = LAYER_EQUIP__LOWEST; layer <= LAYER_EQUIP__HIGHEST; ++layer )
26✔
3819
    {
3820
      Item* item = chr->wornitem( layer );
25✔
3821
      if ( item != nullptr )
25✔
3822
      {
3823
        arr->addElement( new EItemRefObjImp( item ) );
1✔
3824
      }
3825
    }
3826
    return arr.release();
1✔
3827
  }
1✔
3828

3829
  return new BError( "Invalid parameter" );
×
3830
}
3831

3832
BObjectImp* UOExecutorModule::mf_GetEquipmentByLayer()
3✔
3833
{
3834
  Character* chr;
3835
  int layer;
3836
  if ( getCharacterParam( 0, chr ) && getParam( 1, layer ) )
3✔
3837
  {
3838
    if ( layer < LOWEST_LAYER || layer > HIGHEST_LAYER )
3✔
3839
    {
3840
      return new BError( "Invalid layer" );
×
3841
    }
3842

3843
    Item* item = chr->wornitem( layer );
3✔
3844
    if ( item == nullptr )
3✔
3845
    {
3846
      return new BError( "Nothing equipped on that layer." );
1✔
3847
    }
3848

3849
    return new EItemRefObjImp( item );
2✔
3850
  }
3851

3852
  return new BError( "Invalid parameter" );
×
3853
}
3854

3855
BObjectImp* UOExecutorModule::mf_DisconnectClient()
×
3856
{
3857
  Character* chr;
3858
  Network::Client* client;
3859

3860
  if ( getCharacterOrClientParam( 0, chr, client ) )
×
3861
  {
3862
    if ( chr != nullptr )
×
3863
    {
3864
      if ( !chr->has_active_client() )
×
3865
        return new BError( "No client attached" );
×
3866

3867
      client = chr->client;
×
3868
    }
3869

3870
    if ( client != nullptr )
×
3871
    {
3872
      if ( client->isConnected() )
×
3873
      {
3874
        client->Disconnect();
×
3875
        return new BLong( 1 );
×
3876
      }
3877
      return new BError( "Client is disconnected" );
×
3878
    }
3879
    return new BError( "Invalid parameter type" );
×
3880
  }
3881

3882
  return new BError( "Invalid parameter type" );
×
3883
}
3884

3885
BObjectImp* UOExecutorModule::mf_GetMapInfo()
34✔
3886
{
3887
  Core::Pos2d pos;
34✔
3888
  Realms::Realm* realm;
3889

3890
  // note that this uses WORLD_MAX_X, not WORLD_X,
3891
  // because we can't read the outermost edge of the map
3892
  if ( getRealmParam( 2, &realm ) && getPos2dParam( 0, 1, &pos, realm ) )
34✔
3893
  {
3894
    Plib::MAPTILE_CELL cell = realm->getmaptile( pos );
34✔
3895
    std::unique_ptr<BStruct> result( new BStruct );
34✔
3896
    result->addMember( "z", new BLong( cell.z ) );
34✔
3897
    result->addMember( "landtile", new BLong( cell.landtile ) );
34✔
3898

3899
    return result.release();
34✔
3900
  }
34✔
3901

3902
  return new BError( "Invalid parameter" );
×
3903
}
3904
BObjectImp* UOExecutorModule::mf_GetWorldHeight()
×
3905
{
3906
  Core::Pos2d pos;
×
3907
  Realms::Realm* realm;
3908
  if ( getRealmParam( 2, &realm ) && getPos2dParam( 0, 1, &pos, realm ) )
×
3909
  {
3910
    short z = -255;
×
3911
    if ( realm->lowest_standheight( pos, &z ) )
×
3912
      return new BLong( z );
×
3913
    return new BError( "Nowhere" );
×
3914
  }
3915

3916
  return new BError( "Invalid parameter" );
×
3917
}
3918

3919
BObjectImp* UOExecutorModule::mf_GetObjtypeByName()
×
3920
{
3921
  const String* namestr;
3922
  if ( getStringParam( 0, namestr ) )
×
3923
  {
3924
    unsigned int objtype = get_objtype_byname( namestr->data() );
×
3925
    if ( objtype != 0 )
×
3926
      return new BLong( objtype );
×
3927
    return new BError( "No objtype by that name" );
×
3928
  }
3929

3930
  return new BError( "Invalid parameter" );
×
3931
}
3932

3933
BObjectImp* UOExecutorModule::mf_SendEvent()
×
3934
{
3935
  Character* chr;
3936
  if ( getCharacterParam( 0, chr ) )
×
3937
  {
3938
    BObjectImp* event = exec.getParamImp( 1 );
×
3939
    if ( event != nullptr )
×
3940
    {
3941
      if ( chr->isa( UOBJ_CLASS::CLASS_NPC ) )
×
3942
      {
3943
        NPC* npc = static_cast<NPC*>( chr );
×
3944
        // event->add_ref(); // UNTESTED
3945
        return npc->send_event_script( event->copy() );
×
3946
      }
3947

3948
      return new BError( "That mobile is not an NPC" );
×
3949
    }
3950

3951
    return new BError( "Huh?  Not enough parameters" );
×
3952
  }
3953

3954
  return new BError( "Invalid parameter" );
×
3955
}
3956

3957
BObjectImp* UOExecutorModule::mf_DestroyMulti()
32✔
3958
{
3959
  Multi::UMulti* multi;
3960
  if ( getMultiParam( 0, multi ) )
32✔
3961
  {
3962
    const ItemDesc& id = find_itemdesc( multi->objtype_ );
32✔
3963
    if ( !id.destroy_script.empty() )
32✔
3964
    {
3965
      BObjectImp* res = run_script_to_completion( id.destroy_script, new EItemRefObjImp( multi ) );
×
3966
      if ( !res->isTrue() )
×
3967
      {  // destruction is okay
3968
        return res;
×
3969
      }
3970
    }
3971

3972
    Multi::UBoat* boat = multi->as_boat();
32✔
3973
    if ( boat != nullptr )
32✔
3974
    {
3975
      return Multi::destroy_boat( boat );
19✔
3976
    }
3977
    Multi::UHouse* house = multi->as_house();
13✔
3978
    if ( house != nullptr )
13✔
3979
    {
3980
      return Multi::destroy_house( house );
13✔
3981
    }
3982
    return new BError( "WTF!? Don't know what kind of multi that is!" );
×
3983
  }
3984

3985
  return new BError( "Invalid parameter type" );
×
3986
}
3987

3988

3989
BObjectImp* UOExecutorModule::mf_GetMultiDimensions()
×
3990
{
3991
  u16 multiid;
3992
  if ( getParam( 0, multiid ) )
×
3993
  {
3994
    if ( !Multi::MultiDefByMultiIDExists( multiid ) )
×
3995
      return new BError( "MultiID not found" );
×
3996

3997
    const Multi::MultiDef& md = *Multi::MultiDefByMultiID( multiid );
×
3998
    std::unique_ptr<BStruct> ret( new BStruct );
×
3999
    ret->addMember( "xmin", new BLong( md.minrxyz.x() ) );
×
4000
    ret->addMember( "xmax", new BLong( md.maxrxyz.x() ) );
×
4001
    ret->addMember( "ymin", new BLong( md.minrxyz.y() ) );
×
4002
    ret->addMember( "ymax", new BLong( md.maxrxyz.y() ) );
×
4003
    return ret.release();
×
4004
  }
×
4005
  return new BError( "Invalid parameter" );
×
4006
}
4007

4008
BObjectImp* UOExecutorModule::mf_SetScriptController()
×
4009
{
4010
  Character* old_controller = controller_.get();
×
4011
  BObjectImp* param0 = getParamImp( 0 );
×
4012
  bool handled = false;
×
4013

4014
  if ( auto* lng = impptrIf<BLong>( param0 ) )
×
4015
  {
4016
    if ( lng->value() == 0 )
×
4017
    {
4018
      controller_.clear();
×
4019
      handled = true;
×
4020
    }
4021
  }
4022

4023
  if ( !handled )
×
4024
  {
4025
    Character* chr;
4026
    if ( getCharacterParam( 0, chr ) )
×
4027
      controller_.set( chr );
×
4028
    else
4029
      controller_.clear();
×
4030
  }
4031

4032
  if ( old_controller )
×
4033
    return new ECharacterRefObjImp( old_controller );
×
4034
  return new BLong( 0 );
×
4035
}
4036

4037
BObjectImp* UOExecutorModule::mf_GetStandingHeight()
6✔
4038
{
4039
  Core::Pos4d pos;
6✔
4040
  if ( getPos4dParam( 0, 1, 2, 3, &pos ) )
6✔
4041
  {
4042
    short newz;
4043
    Multi::UMulti* multi;
4044
    Item* walkon;
4045
    if ( pos.realm()->lowest_walkheight( pos.xy(), pos.z(), &newz, &multi, &walkon, true,
6✔
4046
                                         Plib::MOVEMODE_LAND ) )
4047
    {
4048
      std::unique_ptr<BStruct> arr( new BStruct );
6✔
4049
      arr->addMember( "z", new BLong( newz ) );
6✔
4050
      if ( multi != nullptr )
6✔
4051
        arr->addMember( "multi", new EMultiRefObjImp( multi ) );
6✔
4052
      return arr.release();
6✔
4053
    }
6✔
4054

4055
    return new BError( "Can't stand there" );
×
4056
  }
4057

4058
  return new BError( "Invalid parameter type" );
×
4059
}
4060

4061
BObjectImp* UOExecutorModule::mf_GetStandingLayers( /* x, y, flags, realm, includeitems */ )
×
4062
{
4063
  Core::Pos2d pos;
×
4064
  int flags;
4065
  Realms::Realm* realm;
4066
  int includeitems;
4067

4068
  if ( getRealmParam( 3, &realm ) && getPos2dParam( 0, 1, &pos, realm ) && getParam( 2, flags ) &&
×
4069
       getParam( 4, includeitems ) )
×
4070
  {
4071
    std::unique_ptr<ObjArray> newarr( new ObjArray );
×
4072

4073
    Plib::MapShapeList mlist;
×
4074
    Core::ItemsVector ivec;
×
4075
    realm->readmultis( mlist, pos, flags );
×
4076
    realm->getmapshapes( mlist, pos, flags );
×
4077
    if ( includeitems )
×
4078
    {
4079
      realm->readdynamics( mlist, pos, ivec, false, flags );
×
4080
    }
4081

4082
    for ( const auto& entry : mlist )
×
4083
    {
4084
      std::unique_ptr<BStruct> arr( new BStruct );
×
4085

4086
      if ( entry.flags & ( Plib::FLAG::MOVELAND | Plib::FLAG::MOVESEA ) )
×
4087
        arr->addMember( "z", new BLong( entry.z + entry.height ) );
×
4088
      else
4089
        arr->addMember( "z", new BLong( entry.z ) );
×
4090

4091
      arr->addMember( "height", new BLong( entry.height ) );
×
4092
      arr->addMember( "flags", new BLong( entry.flags ) );
×
4093
      newarr->addElement( arr.release() );
×
4094
    }
×
4095

4096
    return newarr.release();
×
4097
  }
×
4098
  return new BError( "Invalid parameter type" );
×
4099
}
4100

4101
BObjectImp*
4102
UOExecutorModule::mf_GetStandingCoordinates() /* x, y, radius, minz, maxz, realm := _DEFAULT_REALM,
1✔
4103
                                                 movemode := "L", doors_block = 0 */
4104
{
4105
  s16 r, minz, maxz;
4106
  int doors_block;
4107
  const String* movemodename;
4108
  Core::Pos2d pos;
1✔
4109
  Realms::Realm* realm;
4110

4111
  if ( !getRealmParam( 5, &realm ) )
1✔
4112
    return new BError( "Realm not found" );
×
4113

4114
  if ( !( getPos2dParam( 0, 1, &pos, realm ) && getParam( 2, r ) && getParam( 3, minz ) &&
2✔
4115
          getParam( 4, maxz ) && getStringParam( 6, movemodename ) && getParam( 7, doors_block ) ) )
1✔
4116
  {
4117
    return new BError( "Invalid parameter type" );
×
4118
  }
4119

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

4122
  std::unique_ptr<ObjArray> result( new ObjArray );
1✔
4123

4124
  // Iterate through all tiles in range and populate the return array with valid standing locations
4125
  Core::Vec2d radius( r, r );
1✔
4126
  Core::Pos2d tl = pos - radius;
1✔
4127
  Core::Pos2d br = pos + radius;
1✔
4128
  Core::Range2d range( tl, br, realm );
1✔
4129
  for ( const auto& tile : range )
243✔
4130
  {
4131
    auto layers = realm->get_walkheights( tile, minz, maxz, movemode, doors_block );
121✔
4132
    for ( const auto& layer : layers )
154✔
4133
    {
4134
      std::unique_ptr<BStruct> height_struct( new BStruct );
33✔
4135
      auto z = std::get<0>( layer );
33✔
4136
      auto multi = std::get<1>( layer );
33✔
4137

4138
      // Figure out which members to stick in the struct -- we only add multi it exists
4139
      height_struct->addMember( "x", new BLong( tile.x() ) );
33✔
4140
      height_struct->addMember( "y", new BLong( tile.y() ) );
33✔
4141
      height_struct->addMember( "z", new BLong( z ) );
33✔
4142
      if ( multi != nullptr )
33✔
4143
      {
4144
        height_struct->addMember( "multi", new EMultiRefObjImp( multi ) );
×
4145
      }
4146

4147
      // Add struct to the return array
4148
      result->addElement( height_struct.release() );
33✔
4149
    }
33✔
4150
  }
121✔
4151

4152
  return result.release();
1✔
4153
}
1✔
4154

4155
BObjectImp* UOExecutorModule::mf_ReserveItem()
3✔
4156
{
4157
  Item* item;
4158
  if ( getItemParam( 0, item ) )
3✔
4159
  {
4160
    if ( item->inuse() )
3✔
4161
    {
4162
      if ( is_reserved_to_me( item ) )
2✔
4163
        return new BLong( 2 );
2✔
4164
      return new BError( "That item is already being used." );
×
4165
    }
4166
    item->inuse( true );
1✔
4167
    reserved_items_.emplace_back( item );
1✔
4168
    return new BLong( 1 );
1✔
4169
  }
4170

4171
  return new BError( "Invalid parameter" );
×
4172
}
4173

4174
BObjectImp* UOExecutorModule::mf_ReleaseItem()
3✔
4175
{
4176
  Item* item;
4177
  if ( getItemParam( 0, item ) )
3✔
4178
  {
4179
    if ( item->inuse() )
3✔
4180
    {
4181
      for ( unsigned i = 0; i < reserved_items_.size(); ++i )
3✔
4182
      {
4183
        if ( reserved_items_[i].get() == item )
3✔
4184
        {
4185
          item->inuse( false );
3✔
4186
          reserved_items_[i] = reserved_items_.back();
3✔
4187
          reserved_items_.pop_back();
3✔
4188
          return new BLong( 1 );
3✔
4189
        }
4190
      }
4191
      return new BError( "That item is not reserved by this script." );
×
4192
    }
4193

4194
    return new BError( "That item is not reserved." );
×
4195
  }
4196

4197
  return new BError( "Invalid parameter" );
×
4198
}
4199

4200

4201
BObjectImp* UOExecutorModule::mf_SendSkillWindow()
×
4202
{
4203
  Character *towhom, *forwhom;
4204
  if ( getCharacterParam( 0, towhom ) && getCharacterParam( 1, forwhom ) )
×
4205
  {
4206
    if ( towhom->has_active_client() )
×
4207
    {
4208
      send_skillmsg( towhom->client, forwhom );
×
4209
      return new BLong( 1 );
×
4210
    }
4211

4212
    return new BError( "No client attached" );
×
4213
  }
4214

4215
  return new BError( "Invalid parameter type" );
×
4216
}
4217

4218

4219
BObjectImp* UOExecutorModule::mf_OpenPaperdoll()
×
4220
{
4221
  Character *towhom, *forwhom;
4222
  if ( getCharacterParam( 0, towhom ) && getCharacterParam( 1, forwhom ) )
×
4223
  {
4224
    if ( towhom->has_active_client() )
×
4225
    {
4226
      send_paperdoll( towhom->client, forwhom );
×
4227
      return new BLong( 1 );
×
4228
    }
4229

4230
    return new BError( "No client attached" );
×
4231
  }
4232

4233
  return new BError( "Invalid parameter type" );
×
4234
}
4235

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

4240
BObjectImp* UOExecutorModule::mf_ConsumeSubstance()
×
4241
{
4242
  Item* cont_item;
4243
  unsigned int objtype;
4244
  int amount;
4245
  if ( getItemParam( 0, cont_item ) && getObjtypeParam( 1, objtype ) && getParam( 2, amount ) )
×
4246
  {
4247
    if ( !cont_item->isa( UOBJ_CLASS::CLASS_CONTAINER ) )
×
4248
      return new BError( "That is not a container" );
×
4249
    if ( amount < 0 )
×
4250
      return new BError( "Amount cannot be negative" );
×
4251

4252
    UContainer* cont = static_cast<UContainer*>( cont_item );
×
4253
    int amthave = cont->find_sumof_objtype_noninuse( objtype );
×
4254
    if ( amthave < amount )
×
4255
      return new BError( "Not enough of that substance in container" );
×
4256

4257
    cont->consume_sumof_objtype_noninuse( objtype, amount );
×
4258

4259
    return new BLong( 1 );
×
4260
  }
4261

4262
  return new BError( "Invalid parameter type" );
×
4263
}
4264

4265
bool UOExecutorModule::is_reserved_to_me( Item* item )
2✔
4266
{
4267
  for ( const auto& reserved_item : reserved_items_ )
2✔
4268
  {
4269
    if ( reserved_item.get() == item )
2✔
4270
      return true;
2✔
4271
  }
4272
  return false;
×
4273
}
4274

4275
BObjectImp* UOExecutorModule::mf_Shutdown()
2✔
4276
{
4277
  int exit_code = 0;
2✔
4278

4279
  if ( exec.hasParams( 1 ) )
2✔
4280
  {
4281
    getParam( 0, exit_code );
2✔
4282
  }
4283

4284
  Clib::signal_exit( exit_code );
2✔
4285
#ifndef _WIN32
4286
  // the catch_signals_thread (actually main) sits with sigwait(),
4287
  // so it won't wake up except by being signalled.
4288
  signal_catch_thread();
2✔
4289
#endif
4290
  return new BLong( 1 );
2✔
4291
}
4292

4293

4294
BObjectImp* UOExecutorModule::mf_GetCommandHelp()
×
4295
{
4296
  Character* chr;
4297
  const String* cmd;
4298
  if ( getCharacterParam( 0, chr ) && getStringParam( 1, cmd ) )
×
4299
  {
4300
    std::string help = get_textcmd_help( chr, cmd->value() );
×
4301
    if ( !help.empty() )
×
4302
    {
4303
      return new String( help );
×
4304
    }
4305

4306
    return new BError( "No help for that command found" );
×
4307
  }
×
4308

4309
  return new BError( "Invalid parameter type" );
×
4310
}
4311

4312

4313
BObjectImp* UOExecutorModule::mf_SendStringAsTipWindow()
×
4314
{
4315
  Character* chr;
4316
  const String* str;
4317
  if ( getCharacterParam( 0, chr ) && getStringParam( 1, str ) )
×
4318
  {
4319
    if ( chr->has_active_client() )
×
4320
    {
4321
      send_tip( chr->client, str->value() );
×
4322
      return new BLong( 1 );
×
4323
    }
4324

4325
    return new BError( "No client attached" );
×
4326
  }
4327

4328
  return new BError( "Invalid parameter type" );
×
4329
}
4330

4331
BObjectImp* UOExecutorModule::mf_ListItemsNearLocationWithFlag(
×
4332
    /* x, y, z, range, flags, realm */ )  // DAVE
4333
{
4334
  Pos2d pos;
×
4335
  u16 range;
4336
  int z, flags;
4337
  Realms::Realm* realm;
4338

4339
  if ( getRealmParam( 5, &realm ) && getPos2dParam( 0, 1, &pos, realm ) && getParam( 2, z ) &&
×
4340
       getParam( 3, range ) && getParam( 4, flags ) )
×
4341
  {
4342
    std::unique_ptr<ObjArray> newarr( new ObjArray );
×
4343
    WorldIterator<ItemFilter>::InRange(
×
4344
        pos, realm, range,
4345
        [&]( Item* item )
×
4346
        {
4347
          if ( ( Plib::tile_uoflags( item->graphic ) & flags ) )
×
4348
          {
4349
            if ( item->in_range( pos, range ) )
×
4350
            {
4351
              if ( ( z == LIST_IGNORE_Z ) || ( abs( item->z() - z ) < CONST_DEFAULT_ZRANGE ) )
×
4352
                newarr->addElement( new EItemRefObjImp( item ) );
×
4353
            }
4354
          }
4355
        } );
×
4356

4357
    return newarr.release();
×
4358
  }
×
4359

4360
  return new BError( "Invalid parameter" );
×
4361
}
4362

4363
BObjectImp* UOExecutorModule::mf_ListStaticsAtLocation( /* x, y, z, flags, realm */ )
×
4364
{
4365
  Core::Pos2d pos;
×
4366
  int z, flags;
4367
  Realms::Realm* realm;
4368

4369
  if ( getRealmParam( 4, &realm ) && getPos2dParam( 0, 1, &pos, realm ) && getParam( 2, z ) &&
×
4370
       getParam( 3, flags ) )
×
4371
  {
4372
    std::unique_ptr<ObjArray> newarr( new ObjArray );
×
4373

4374
    if ( !( flags & ITEMS_IGNORE_STATICS ) )
×
4375
    {
4376
      Plib::StaticEntryList slist;
×
4377
      realm->getstatics( slist, pos );
×
4378

4379
      for ( const auto& entry : slist )
×
4380
      {
4381
        if ( ( z == LIST_IGNORE_Z ) || ( entry.z == z ) )
×
4382
        {
4383
          std::unique_ptr<BStruct> arr( new BStruct );
×
4384
          arr->addMember( "x", new BLong( pos.x() ) );
×
4385
          arr->addMember( "y", new BLong( pos.y() ) );
×
4386
          arr->addMember( "z", new BLong( entry.z ) );
×
4387
          arr->addMember( "objtype", new BLong( entry.objtype ) );
×
4388
          arr->addMember( "hue", new BLong( entry.hue ) );
×
4389
          newarr->addElement( arr.release() );
×
4390
        }
×
4391
      }
4392
    }
×
4393

4394
    if ( !( flags & ITEMS_IGNORE_MULTIS ) )
×
4395
    {
4396
      Plib::StaticList mlist;
×
4397
      realm->readmultis( mlist, pos );
×
4398

4399
      for ( const auto& entry : mlist )
×
4400
      {
4401
        if ( ( z == LIST_IGNORE_Z ) || ( entry.z == z ) )
×
4402
        {
4403
          std::unique_ptr<BStruct> arr( new BStruct );
×
4404
          arr->addMember( "x", new BLong( pos.x() ) );
×
4405
          arr->addMember( "y", new BLong( pos.y() ) );
×
4406
          arr->addMember( "z", new BLong( entry.z ) );
×
4407
          arr->addMember( "objtype", new BLong( entry.graphic ) );
×
4408
          newarr->addElement( arr.release() );
×
4409
        }
×
4410
      }
4411
    }
×
4412

4413
    return newarr.release();
×
4414
  }
×
4415
  return new BError( "Invalid parameter" );
×
4416
}
4417

4418
BObjectImp* UOExecutorModule::mf_ListStaticsNearLocation( /* x, y, z, range, flags, realm */ )
×
4419
{
4420
  Core::Pos2d pos;
×
4421
  int z, flags;
4422
  short range;
4423
  Realms::Realm* realm;
4424

4425
  if ( getRealmParam( 5, &realm ) && getPos2dParam( 0, 1, &pos, realm ) && getParam( 2, z ) &&
×
4426
       getParam( 3, range ) && getParam( 4, flags ) )
×
4427
  {
4428
    std::unique_ptr<ObjArray> newarr( new ObjArray );
×
4429
    Core::Vec2d radius( range, range );
×
4430
    Core::Range2d area( pos - radius, pos + radius, realm );
×
4431
    for ( const auto& tile : area )
×
4432
    {
4433
      if ( !( flags & ITEMS_IGNORE_STATICS ) )
×
4434
      {
4435
        Plib::StaticEntryList slist;
×
4436
        realm->getstatics( slist, tile );
×
4437

4438
        for ( const auto& entry : slist )
×
4439
        {
4440
          if ( ( z == LIST_IGNORE_Z ) || ( abs( entry.z - z ) < CONST_DEFAULT_ZRANGE ) )
×
4441
          {
4442
            std::unique_ptr<BStruct> arr( new BStruct );
×
4443
            arr->addMember( "x", new BLong( tile.x() ) );
×
4444
            arr->addMember( "y", new BLong( tile.y() ) );
×
4445
            arr->addMember( "z", new BLong( entry.z ) );
×
4446
            arr->addMember( "objtype", new BLong( entry.objtype ) );
×
4447
            arr->addMember( "hue", new BLong( entry.hue ) );
×
4448
            newarr->addElement( arr.release() );
×
4449
          }
×
4450
        }
4451
      }
×
4452

4453
      if ( !( flags & ITEMS_IGNORE_MULTIS ) )
×
4454
      {
4455
        Plib::StaticList mlist;
×
4456
        realm->readmultis( mlist, tile );
×
4457

4458
        for ( const auto& entry : mlist )
×
4459
        {
4460
          if ( ( z == LIST_IGNORE_Z ) || ( abs( entry.z - z ) < CONST_DEFAULT_ZRANGE ) )
×
4461
          {
4462
            std::unique_ptr<BStruct> arr( new BStruct );
×
4463
            arr->addMember( "x", new BLong( tile.x() ) );
×
4464
            arr->addMember( "y", new BLong( tile.y() ) );
×
4465
            arr->addMember( "z", new BLong( entry.z ) );
×
4466
            arr->addMember( "objtype", new BLong( entry.graphic ) );
×
4467
            newarr->addElement( arr.release() );
×
4468
          }
×
4469
        }
4470
      }
×
4471
    }
4472

4473
    return newarr.release();
×
4474
  }
×
4475
  return new BError( "Invalid parameter" );
×
4476
}
4477

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

4568
using UOSearch = Plib::AStarSearch<UOPathState>;
4569

4570
BObjectImp* UOExecutorModule::mf_FindPath()
7✔
4571
{
4572
  Pos3d pos1, pos2;
7✔
4573
  Realms::Realm* realm;
4574
  const String* movemode_name;
4575

4576
  if ( !getPos3dParam( 0, 1, 2, &pos1 ) || !getPos3dParam( 3, 4, 5, &pos2 ) ||
14✔
4577
       !getRealmParam( 6, &realm ) || !getStringParam( 9, movemode_name ) )
14✔
4578
    return new BError( "Invalid parameter" );
×
4579
  if ( !pos1.in_range( pos2, settingsManager.ssopt.max_pathfind_range ) )
7✔
4580
    return new BError( "Beyond Max Range." );
×
4581

4582
  short theSkirt;
4583
  int flags;
4584

4585
  if ( !getParam( 7, flags ) )
7✔
4586
    flags = FP_IGNORE_MOBILES;
×
4587

4588
  if ( !getParam( 8, theSkirt ) )
7✔
4589
    theSkirt = 5;
×
4590

4591
  Plib::MOVEMODE movemode = Character::decode_movemode( movemode_name->value() );
7✔
4592
  if ( movemode == Plib::MOVEMODE_NONE )
7✔
4593
    return new BError( "Wrong movemode parameter" );
1✔
4594

4595
  if ( theSkirt < 0 )
6✔
4596
    theSkirt = 0;
×
4597

4598
  if ( !realm->valid( pos1.xy() ) )
6✔
4599
    return new BError( "Start Coordinates Invalid for Realm" );
×
4600
  if ( !realm->valid( pos2.xy() ) )
6✔
4601
    return new BError( "End Coordinates Invalid for Realm" );
×
4602

4603
  auto astarsearch = std::make_unique<UOSearch>();
6✔
4604

4605
  Range2d range( pos1.xy().min( pos2.xy() ) - Vec2d( theSkirt, theSkirt ),
6✔
4606
                 pos1.xy().max( pos2.xy() ) + Vec2d( theSkirt, theSkirt ), realm );
12✔
4607

4608
  if ( Plib::systemstate.config.loglevel >= 12 )
6✔
4609
  {
4610
    POLLOGLN( "[FindPath] Calling FindPath({}, {}, {}, {:#x}, {})", pos1, pos2, realm->name(),
×
4611
              flags, theSkirt );
4612
    POLLOGLN( "[FindPath]   search for Blockers inside {}", range );
×
4613
  }
4614

4615
  bool doors_block = ( flags & FP_IGNORE_DOORS ) ? false : true;
6✔
4616
  AStarParams params( range, doors_block, movemode, realm );
6✔
4617

4618
  if ( !( flags & FP_IGNORE_MOBILES ) )
6✔
4619
  {
4620
    WorldIterator<MobileFilter>::InBox( range, realm,
×
4621
                                        [&]( Mobile::Character* chr )
×
4622
                                        {
4623
                                          params.AddBlocker( chr->pos3d() );
×
4624

4625
                                          if ( Plib::systemstate.config.loglevel >= 12 )
×
4626
                                            POLLOGLN( "[FindPath]   add Blocker {} at {}",
×
4627
                                                      chr->name(), chr->pos() );
×
4628
                                        } );
×
4629
  }
4630

4631
  if ( Plib::systemstate.config.loglevel >= 12 )
6✔
4632
  {
4633
    POLLOGLN( "[FindPath]   use StartNode {}", pos1 );
×
4634
    POLLOGLN( "[FindPath]   use EndNode {}", pos2 );
×
4635
  }
4636

4637
  // Create a start state
4638
  UOPathState nodeStart( pos1, &params );
6✔
4639
  // Define the goal state
4640
  UOPathState nodeEnd( pos2, &params );
6✔
4641
  // Set Start and goal states
4642
  astarsearch->SetStartAndGoalStates( nodeStart, nodeEnd );
6✔
4643
  unsigned int SearchState;
4644
  do
4645
  {
4646
    SearchState = astarsearch->SearchStep();
28✔
4647
  } while ( SearchState == UOSearch::SEARCH_STATE_SEARCHING );
28✔
4648
  if ( SearchState == UOSearch::SEARCH_STATE_SUCCEEDED )
6✔
4649
  {
4650
    UOPathState* node = astarsearch->GetSolutionStart();
3✔
4651

4652
    auto nodeArray = std::make_unique<ObjArray>();
3✔
4653
    while ( ( node = astarsearch->GetSolutionNext() ) != nullptr )
12✔
4654
    {
4655
      auto nextStep = std::make_unique<BStruct>();
9✔
4656
      const auto& pos = node->position();
9✔
4657
      nextStep->addMember( "x", new BLong( pos.x() ) );
9✔
4658
      nextStep->addMember( "y", new BLong( pos.y() ) );
9✔
4659
      nextStep->addMember( "z", new BLong( pos.z() ) );
9✔
4660
      nodeArray->addElement( nextStep.release() );
9✔
4661
    }
9✔
4662
    astarsearch->FreeSolutionNodes();
3✔
4663
    return nodeArray.release();
3✔
4664
  }
3✔
4665
  if ( SearchState == UOSearch::SEARCH_STATE_FAILED )
3✔
4666
  {
4667
    return new BError( "Failed to find a path." );
3✔
4668
  }
4669
  if ( SearchState == UOSearch::SEARCH_STATE_OUT_OF_MEMORY )
×
4670
  {
4671
    return new BError( "Out of memory." );
×
4672
  }
4673
  if ( SearchState == UOSearch::SEARCH_STATE_SOLUTION_CORRUPTED )
×
4674
  {
4675
    return new BError( "Solution Corrupted!" );
×
4676
  }
4677

4678
  return new BError( "Pathfind Error." );
×
4679
}
6✔
4680

4681

4682
BObjectImp* UOExecutorModule::mf_UseItem()
×
4683
{
4684
  Item* item;
4685
  Character* chr;
4686

4687
  if ( getItemParam( 0, item ) && getCharacterParam( 1, chr ) )
×
4688
  {
4689
    const ItemDesc& itemdesc = find_itemdesc( item->objtype_ );
×
4690

4691
    if ( itemdesc.requires_attention && ( chr->skill_ex_active() || chr->casting_spell() ) )
×
4692
    {
4693
      if ( chr->client != nullptr )
×
4694
      {
4695
        send_sysmessage( chr->client, "I am already doing something else." );
×
4696
        return new BError( "Character busy." );
×
4697
        ;
4698
      }
4699
    }
4700

4701
    if ( itemdesc.requires_attention && chr->hidden() )
×
4702
      chr->unhide();
×
4703

4704
    ref_ptr<EScriptProgram> prog;
×
4705

4706
    std::string on_use_script = item->get_use_script_name();
×
4707

4708
    if ( !on_use_script.empty() )
×
4709
    {
4710
      ScriptDef sd( on_use_script, nullptr, "" );
×
4711
      prog = find_script2( sd,
×
4712
                           true,  // complain if not found
4713
                           Plib::systemstate.config.cache_interactive_scripts );
×
4714
    }
×
4715
    else if ( !itemdesc.on_use_script.empty() )
×
4716
    {
4717
      prog = find_script2( itemdesc.on_use_script, true,
×
4718
                           Plib::systemstate.config.cache_interactive_scripts );
×
4719
    }
4720

4721
    if ( prog.get() != nullptr )
×
4722
    {
4723
      if ( chr->start_itemuse_script( prog.get(), item, itemdesc.requires_attention ) )
×
4724
        return new BLong( 1 );
×
4725
      return new BError( "Failed to start script!" );
×
4726
      // else log the fact?
4727
    }
4728

4729
    if ( chr->client != nullptr )
×
4730
      item->builtin_on_use( chr->client );
×
4731
    return new BLong( 0 );
×
4732
  }
×
4733

4734
  return new BError( "Invalid parameter" );
×
4735
}
4736

4737
BObjectImp* UOExecutorModule::mf_FindSubstance()
3✔
4738
{
4739
  UContainer::Contents substanceVector;
3✔
4740

4741
  Item* cont_item;
4742
  unsigned int objtype;
4743
  int amount;
4744

4745
  if ( getItemParam( 0, cont_item ) && getObjtypeParam( 1, objtype ) && getParam( 2, amount ) )
3✔
4746
  {
4747
    bool makeInUse;
4748
    int flags;
4749
    if ( !getParam( 3, makeInUse ) )
3✔
4750
      makeInUse = false;
×
4751

4752
    if ( !getParam( 4, flags ) )
3✔
4753
      flags = 0;
×
4754

4755
    if ( !cont_item->isa( UOBJ_CLASS::CLASS_CONTAINER ) )
3✔
4756
      return new BError( "That is not a container" );
×
4757
    if ( amount < 0 )
3✔
4758
      return new BError( "Amount cannot be negative" );
×
4759

4760
    UContainer* cont = static_cast<UContainer*>( cont_item );
3✔
4761
    int amthave = cont->find_sumof_objtype_noninuse( objtype, amount, substanceVector, flags );
3✔
4762
    if ( ( amthave < amount ) && ( !( flags & FINDSUBSTANCE_FIND_ALL ) ) )
3✔
4763
      return new BError( "Not enough of that substance in container" );
1✔
4764

4765
    std::unique_ptr<ObjArray> theArray( new ObjArray() );
2✔
4766
    for ( auto item : substanceVector )
4✔
4767
    {
4768
      if ( item != nullptr )
2✔
4769
      {
4770
        if ( ( makeInUse ) && ( !item->inuse() ) )
2✔
4771
        {
4772
          item->inuse( true );
2✔
4773
          reserved_items_.emplace_back( item );
2✔
4774
        }
4775
        theArray->addElement( new EItemRefObjImp( item ) );
2✔
4776
      }
4777
    }
4778
    return theArray.release();
2✔
4779
  }
2✔
4780

4781
  return new BError( "Invalid parameter type" );
×
4782
}
3✔
4783

4784
BObjectImp* UOExecutorModule::mf_IsStackable()
×
4785
{
4786
  Item* item1;
4787
  Item* item2;
4788

4789
  if ( !( getItemParam( 0, item1 ) && getItemParam( 1, item2 ) ) )
×
4790
  {
4791
    return new BError( "Invalid parameter type" );
×
4792
  }
4793

4794
  if ( item1->objtype_ != item2->objtype_ )
×
4795
    return new BError( "Objtypes differs" );
×
4796
  if ( !item1->stackable() )
×
4797
    return new BError( "That item type is not stackable." );
×
4798

4799
  if ( item1->can_add_to_self( *item2, false ) )
×
4800
    return new BLong( 1 );
×
4801
  return new BError( "Failed to stack" );
×
4802
}
4803

4804
BObjectImp* UOExecutorModule::mf_UpdateMobile()
×
4805
{
4806
  Character* chr;
4807
  int flags;
4808

4809
  if ( getCharacterParam( 0, chr ) && getParam( 1, flags ) )
×
4810
  {
4811
    if ( flags == 1 )
×
4812
    {
4813
      if ( ( !chr->isa( UOBJ_CLASS::CLASS_NPC ) ) && ( chr->client ) )  // no npc and active client
×
4814
        send_owncreate( chr->client, chr );                             // inform self
×
4815
      if ( ( chr->isa( UOBJ_CLASS::CLASS_NPC ) ) || ( chr->client ) )   // npc or active client
×
4816
        send_create_mobile_to_nearby_cansee( chr );                     // inform other
×
4817
      else
4818
        return new BError( "Mobile is offline" );
×
4819
    }
4820
    else
4821
    {
4822
      if ( ( !chr->isa( UOBJ_CLASS::CLASS_NPC ) ) && ( chr->client ) )  // no npc and active client
×
4823
        send_move( chr->client, chr );                                  // inform self
×
4824
      if ( ( chr->isa( UOBJ_CLASS::CLASS_NPC ) ) || ( chr->client ) )   // npc or active client
×
4825
        send_move_mobile_to_nearby_cansee( chr );                       // inform other
×
4826
      else
4827
        return new BError( "Mobile is offline" );
×
4828
    }
4829
    return new BLong( 1 );
×
4830
  }
4831
  return new BError( "Invalid parameter type" );
×
4832
}
4833

4834
BObjectImp* UOExecutorModule::mf_UpdateItem()
×
4835
{
4836
  Item* item;
4837

4838
  if ( getItemParam( 0, item ) )
×
4839
  {
4840
    send_item_to_inrange( item );
×
4841
    return new BLong( 1 );
×
4842
  }
4843

4844
  return new BError( "Invalid parameter type" );
×
4845
}
4846

4847

4848
BObjectImp* UOExecutorModule::mf_CanWalk(
×
4849
    /*movemode, x1, y1, z1, x2_or_dir, y2 := -1, realm := DEF*/ )
4850
{
4851
  Pos4d p;
×
4852
  int x2_or_dir, y2_;
4853
  const String* movemode_name;
4854

4855
  if ( ( getStringParam( 0, movemode_name ) ) && ( getPos4dParam( 1, 2, 3, 6, &p ) ) &&
×
4856
       ( getParam( 4, x2_or_dir ) ) && ( getParam( 5, y2_ ) ) )
×
4857
  {
4858
    Plib::MOVEMODE movemode = Character::decode_movemode( movemode_name->value() );
×
4859

4860
    Core::UFACING dir;
4861
    if ( y2_ == -1 )
×
4862
      dir = static_cast<Core::UFACING>( x2_or_dir & 0x7 );
×
4863
    else
4864
    {
4865
      auto p1 = Pos2d( static_cast<u16>( x2_or_dir ), static_cast<u16>( y2_ ) );
×
4866
      if ( !p.realm()->valid( p1 ) )
×
4867
        return new BError( "Invalid coordinates for realm." );
×
4868

4869
      dir = p.xy().direction_toward( p1 );
×
4870
    }
4871

4872
    if ( dir & 1 )  // check if diagonal movement is allowed
×
4873
    {
4874
      short new_z;
4875
      u8 tmp_facing = ( dir + 1 ) & 0x7;
×
4876
      auto tmp_pos = p.move( static_cast<Core::UFACING>( tmp_facing ) );
×
4877

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

4882
      tmp_facing = ( dir - 1 ) & 0x7;
×
4883
      tmp_pos = p.move( static_cast<Core::UFACING>( tmp_facing ) );
×
4884

4885
      if ( !walk1 && !p.realm()->walkheight( tmp_pos.xy(), tmp_pos.z(), &new_z, nullptr, nullptr,
×
4886
                                             true, movemode, nullptr ) )
4887
        return new BError( "Cannot walk there" );
×
4888
    }
4889

4890
    p.move_to( dir );
×
4891
    short newz;
4892

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

4896
    return new BLong( newz );
×
4897
  }
4898
  return new BError( "Invalid parameter" );
×
4899
}
4900

4901

4902
BObjectImp* UOExecutorModule::mf_SendCharProfile(
×
4903
    /*chr, of_who, title, uneditable_text := array, editable_text := array*/ )
4904
{
4905
  Character *chr, *of_who;
4906
  const String* title;
4907
  const String* uText;
4908
  const String* eText;
4909

4910
  if ( getCharacterParam( 0, chr ) && getCharacterParam( 1, of_who ) &&
×
4911
       getStringParam( 2, title ) && getUnicodeStringParam( 3, uText ) &&
×
4912
       getUnicodeStringParam( 4, eText ) )
×
4913
  {
4914
    if ( chr->logged_in() && of_who->logged_in() )
×
4915
    {
4916
      if ( uText->length() > SPEECH_MAX_LEN || eText->length() > SPEECH_MAX_LEN )
×
4917
        return new BError( "Text exceeds maximum size." );
×
4918

4919
      sendCharProfile( chr, of_who, title->value(), uText->value(), eText->value() );
×
4920
      return new BLong( 1 );
×
4921
    }
4922
    return new BError( "Mobile must be online." );
×
4923
  }
4924
  return new BError( "Invalid parameter type" );
×
4925
}
4926

4927
BObjectImp* UOExecutorModule::mf_SendOverallSeason( /*season_id, playsound := 1*/ )
×
4928
{
4929
  int season_id, playsound;
4930

4931
  if ( getParam( 0, season_id ) && getParam( 1, playsound ) )
×
4932
  {
4933
    if ( season_id < 0 || season_id > 4 )
×
4934
      return new BError( "Invalid season id" );
×
4935

4936
    Network::PktHelper::PacketOut<Network::PktOut_BC> msg;
×
4937
    msg->Write<u8>( static_cast<u16>( season_id ) );
×
4938
    msg->Write<u8>( static_cast<u16>( playsound ) );
×
4939

4940
    for ( auto client : networkManager.clients )
×
4941
    {
4942
      if ( !client->chr || !client->chr->logged_in() || client->getversiondetail().major < 1 )
×
4943
        continue;
×
4944
      msg.Send( client );
×
4945
    }
4946
    return new BLong( 1 );
×
4947
  }
×
4948
  return new BError( "Invalid parameter" );
×
4949
}
4950

4951
// bresenham circle calculates the coords based on center coords and radius
4952
BObjectImp* UOExecutorModule::mf_GetMidpointCircleCoords( /* xcenter, ycenter, radius */ )
×
4953
{
4954
  int xcenter, ycenter, radius;
4955
  if ( !( getParam( 0, xcenter ) && getParam( 1, ycenter ) && getParam( 2, radius ) ) )
×
4956
  {
4957
    return new BError( "Invalid parameter type" );
×
4958
  }
4959
  std::unique_ptr<ObjArray> coords( new ObjArray );
×
4960

4961
  std::vector<std::tuple<int, int>> points;
×
4962
  auto add_point = [&coords]( int x, int y )
×
4963
  {
4964
    std::unique_ptr<BStruct> point( new BStruct );
×
4965
    point->addMember( "x", new BLong( x ) );
×
4966
    point->addMember( "y", new BLong( y ) );
×
4967
    coords->addElement( point.release() );
×
4968
  };
×
4969

4970
  if ( radius == 0 )
×
4971
  {
4972
    add_point( xcenter, ycenter );
×
4973
    return coords.release();
×
4974
  }
4975

4976
  // inside of each quadrant the points are sorted,
4977
  // store the quadrands in seperated vectors and merge them later
4978
  // -> automatically sorted
4979
  std::vector<std::tuple<int, int>> q1, q2, q3, q4;
×
4980
  int x = -radius, y = 0, err = 2 - 2 * radius; /* II. Quadrant */
×
4981
  do
4982
  {
4983
    q1.emplace_back( xcenter - x, ycenter + y ); /*   I. Quadrant */
×
4984
    q2.emplace_back( xcenter - y, ycenter - x ); /*  II. Quadrant */
×
4985
    q3.emplace_back( xcenter + x, ycenter - y ); /* III. Quadrant */
×
4986
    q4.emplace_back( xcenter + y, ycenter + x ); /*  IV. Quadrant */
×
4987
    radius = err;
×
4988
    if ( radius <= y )
×
4989
      err += ++y * 2 + 1;
×
4990
    if ( radius > x || err > y )
×
4991
      err += ++x * 2 + 1;
×
4992
  } while ( x < 0 );
×
4993

4994
  for ( const auto& p : q1 )
×
4995
    add_point( std::get<0>( p ), std::get<1>( p ) );
×
4996
  for ( const auto& p : q2 )
×
4997
    add_point( std::get<0>( p ), std::get<1>( p ) );
×
4998
  for ( const auto& p : q3 )
×
4999
    add_point( std::get<0>( p ), std::get<1>( p ) );
×
5000
  for ( const auto& p : q4 )
×
5001
    add_point( std::get<0>( p ), std::get<1>( p ) );
×
5002

5003
  return coords.release();
×
5004
}
×
5005

5006
size_t UOExecutorModule::sizeEstimate() const
14✔
5007
{
5008
  size_t size = sizeof( *this ) + Clib::memsize( reserved_items_ );
14✔
5009
  return size;
14✔
5010
}
5011
}  // namespace Module
5012
}  // 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