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

polserver / polserver / 21100551564

17 Jan 2026 08:40PM UTC coverage: 60.504% (+0.01%) from 60.492%
21100551564

Pull #857

github

turleypol
fixed scope
Pull Request #857: ClangTidy readability-else-after-return

837 of 1874 new or added lines in 151 files covered. (44.66%)

48 existing lines in 26 files now uncovered.

44445 of 73458 relevant lines covered (60.5%)

515341.61 hits per line

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

11.15
/pol-core/pol/module/uomod2.cpp
1
/** @file
2
 *
3
 * @par History
4
 * - 2005/03/02 Shinigami: mf_MoveObjectToRealm - fixed item detection and added container handling
5
 * - 2005/06/11 Shinigami: added polcore().internal - just for internal Development (not documented)
6
 * - 2005/06/20 Shinigami: added memory log to polcore().internal (needs defined MEMORYLEAK)
7
 * - 2005/07/25 Shinigami: doubled Msg size in mf_SendGumpMenu to use larger Gumps
8
 * - 2005/10/16 Shinigami: added x- and y-offset to mf_SendGumpMenu
9
 * - 2005/11/26 Shinigami: changed "strcmp" into "stricmp" to suppress Script Errors
10
 * - 2006/05/07 Shinigami: mf_SendBuyWindow & mf_SendSellWindow - added Flags to send Item
11
 * Description using AoS Tooltips
12
 * - 2006/05/24 Shinigami: added mf_SendCharacterRaceChanger - change Hair, Beard and Color
13
 *                         added character_race_changer_handler()
14
 * - 2006/05/30 Shinigami: Changed params of character_race_changer_handler()
15
 *                         Fixed Bug with detection of Gump-Cancel in uo::SendCharacterRaceChanger()
16
 * - 2006/09/23 Shinigami: Script_Cycles, Sleep_Cycles and Script_passes uses 64bit now
17
 * - 2007/04/28 Shinigami: polcore().internal information will be logged in excel-friendly format
18
 * too (leak.log)
19
 * - 2009/07/23 MuadDib:   updates for new Enum::Packet Out ID
20
 * - 2009/08/06 MuadDib:   Removed PasswordOnlyHash support
21
 * - 2009/09/03 MuadDib:   Relocation of account related cpp/h
22
 *                         Relocation of multi related cpp/h
23
 * - 2009/09/10 Turley:    CompressedGump support (Grin)
24
 * - 2009/09/06 Turley:    Changed Version checks to bitfield client->ClientType
25
 * - 2009/12/17 Turley:    CloseWindow( character, type, object ) - Tomi
26
 */
27

28

29
/*
30
    UOEMOD2.CPP - a nice place for the Buy/Sell Interface Functions
31
    */
32

33
#include "pol_global_config.h"
34

35
#include <ctype.h>
36
#include <optional>
37
#include <stddef.h>
38
#include <string>
39

40
#include "../../bscript/berror.h"
41
#include "../../bscript/bobject.h"
42
#include "../../bscript/bstruct.h"
43
#include "../../bscript/eprog.h"
44
#include "../../bscript/executor.h"
45
#include "../../bscript/impstr.h"
46
#include "../../clib/Program/ProgramConfig.h"
47
#include "../../clib/clib.h"
48
#include "../../clib/clib_endian.h"
49
#include "../../clib/logfacility.h"
50
#include "../../clib/passert.h"
51
#include "../../clib/rawtypes.h"
52
#include "../../clib/refptr.h"
53
#include "../../clib/stlutil.h"
54
#include "../../plib/objtype.h"
55
#include "../../plib/pkg.h"
56
#include "../../plib/staticblock.h"
57
#include "../../plib/systemstate.h"
58
#include "../../plib/uconst.h"
59
#include "../accounts/account.h"
60
#include "../accounts/accounts.h"
61
#include "../accounts/acscrobj.h"
62
#include "../containr.h"
63
#include "../core.h"
64
#include "../exscrobj.h"
65
#include "../globals/memoryusage.h"
66
#include "../globals/object_storage.h"
67
#include "../globals/script_internals.h"
68
#include "../globals/state.h"
69
#include "../globals/uvars.h"
70
#include "../item/item.h"
71
#include "../item/itemdesc.h"
72
#include "../layers.h"
73
#include "../mobile/charactr.h"
74
#include "../mobile/npc.h"
75
#include "../multi/customhouses.h"
76
#include "../multi/house.h"
77
#include "../multi/multi.h"
78
#include "../multi/multidef.h"
79
#include "../network/cgdata.h"
80
#include "../network/client.h"
81
#include "../network/packethelper.h"
82
#include "../network/packetinterface.h"
83
#include "../network/packets.h"
84
#include "../network/pktboth.h"
85
#include "../network/pktdef.h"
86
#include "../network/pktin.h"
87
#include "../polclass.h"
88
#include "../polobject.h"
89
#include "../profile.h"
90
#include "../realms/realm.h"
91
#include "../reftypes.h"
92
#include "../savedata.h"
93
#include "../scrstore.h"
94
#include "../sngclick.h"
95
#include "../statmsg.h"
96
#include "../tooltips.h"
97
#include "../ufunc.h"
98
#include "../uobject.h"
99
#include "../uoexec.h"
100
#include "../uoscrobj.h"
101
#include "../uworld.h"
102
#include "base/position.h"
103
#include "systems/suspiciousacts.h"
104
#include "uomod.h"
105

106
#ifdef MEMORYLEAK
107
#include "../../clib/opnew.h"
108
#endif
109

110

111
#include <zlib.h>
112

113
namespace Pol
114
{
115
namespace Core
116
{
117
bool validhair( u16 HairStyle );
118
bool validbeard( u16 BeardStyle );
119
}  // namespace Core
120
namespace Module
121
{
122
using namespace Bscript;
123
using namespace Network;
124
using namespace Mobile;
125
using namespace Items;
126
using namespace Core;
127

128
#define CONST_DEFAULT_ZRANGE 19
129

130
/*
131
0000: 74 02 70 40 29 ca d8 28  00 00 00 03 0b 53 65 77   t.p@)..( .....Sew
132
0010: 69 6e 67 20 6b 69 74 00  00 00 00 0d 09 53 63 69   ing kit. .....Sci
133
0020: 73 73 6f 72 73 00 00 00  00 09 0a 44 79 69 6e 67   ssors... ...Dying
134
0030: 20 74 75 62 00 00 00 00  09 05 44 79 65 73 00 00  tub.... ..Dyes..
135
0040: 00 00 1b 08 44 6f 75 62  6c 65 74 00 00 00 00 1a   ....Doub let.....
136
0050: 0c 53 68 6f 72 74 20 70  61 6e 74 73 00 00 00 00   .Short p ants....
137
0060: 37 0c 46 61 6e 63 79 20  73 68 69 72 74 00 00 00   7.Fancy  shirt...
138
0070: 00 26 0b 4c 6f 6e 67 20  70 61 6e 74 73 00 00 00   .&.Long  pants...
139
0080: 00 19 0c 46 61 6e 63 79  20 64 72 65 73 73 00 00   ...Fancy  dress..
140
0090: 00 00 39 0c 50 6c 61 69  6e 20 64 72 65 73 73 00   ..9.Plai n dress.
141
00a0: 00 00 00 21 06 53 6b 69  72 74 00 00 00 00 1f 05   ...!.Ski rt......
142
00b0: 4b 69 6c 74 00 00 00 00  1a 0b 48 61 6c 66 20 61   Kilt.... ..Half a
143
00c0: 70 72 6f 6e 00 00 00 00  49 05 52 6f 62 65 00 00   pron.... I.Robe..
144
00d0: 00 00 32 06 43 6c 6f 61  6b 00 00 00 00 32 06 43   ..2.Cloa k....2.C
145
00e0: 6c 6f 61 6b 00 00 00 00  1b 08 44 6f 75 62 6c 65   loak.... ..Double
146
00f0: 74 00 00 00 00 21 06 54  75 6e 69 63 00 00 00 00   t....!.T unic....
147
0100: 39 0c 4a 65 73 74 65 72  20 73 75 69 74 00 00 00   9.Jester  suit...
148
0110: 00 1f 0b 4a 65 73 74 65  72 20 68 61 74 00 00 00   ...Jeste r hat...
149
0120: 00 19 0b 46 6c 6f 70 70  79 20 68 61 74 00 00 00   ...Flopp y hat...
150
0130: 00 1a 0e 57 69 64 65 2d  62 72 69 6d 20 68 61 74   ...Wide- brim hat
151
0140: 00 00 00 00 1b 04 43 61  70 00 00 00 00 1a 0f 54   ......Ca p......T
152
0150: 61 6c 6c 20 73 74 72 61  77 20 68 61 74 00 00 00   all stra w hat...
153
0160: 00 19 0a 53 74 72 61 77  20 68 61 74 00 00 00 00   ...Straw  hat....
154
0170: 1e 0d 57 69 7a 61 72 64  27 73 20 68 61 74 00 00   ..Wizard 's hat..
155
0180: 00 00 1a 07 42 6f 6e 6e  65 74 00 00 00 00 1b 0e   ....Bonn et......
156
0190: 46 65 61 74 68 65 72 65  64 20 68 61 74 00 00 00   Feathere d hat...
157
01a0: 00 1a 0d 54 72 69 63 6f  72 6e 65 20 68 61 74 00   ...Trico rne hat.
158
01b0: 00 00 00 0e 08 42 61 6e  64 61 6e 61 00 00 00 00   .....Ban dana....
159
01c0: 0d 09 53 6b 75 6c 6c 63  61 70 00 00 00 00 02 0d   ..Skullc ap......
160
01d0: 46 6f 6c 64 65 64 20 63  6c 6f 74 68 00 00 00 00   Folded c loth....
161
01e0: 02 0d 46 6f 6c 64 65 64  20 63 6c 6f 74 68 00 00   ..Folded  cloth..
162
01f0: 00 00 02 0d 46 6f 6c 64  65 64 20 63 6c 6f 74 68   ....Fold ed cloth
163
0200: 00 00 00 00 02 0d 46 6f  6c 64 65 64 20 63 6c 6f   ......Fo lded clo
164
0210: 74 68 00 00 00 00 4a 0e  42 6f 6c 74 20 6f 66 20   th....J. Bolt of
165
0220: 63 6c 6f 74 68 00 00 00  00 4a 0e 42 6f 6c 74 20   cloth... .J.Bolt
166
0230: 6f 66 20 63 6c 6f 74 68  00 00 00 00 5b 0f 42 61   of cloth ....[.Ba
167
0240: 6c 65 20 6f 66 20 63 6f  74 74 6f 6e 00 00 00 00   le of co tton....
168
0250: 2d 0d 50 69 6c 65 20 6f  66 20 77 6f 6f 6c 00 00   -.Pile o f wool..
169
0260: 00 00 61 0c 46 6c 61 78  20 62 75 6e 64 6c 65 00   ..a.Flax  bundle.
170
*/
171
// msg 3C: be sure to not send the whole structure, just as many 'items' as you insert
172

173
bool send_vendorwindow_contents( Client* client, UContainer* for_sale, bool send_aos_tooltip )
×
174
{
175
  PktHelper::PacketOut<PktOut_74> msg;
×
176
  msg->offset += 2;  // msglen
×
177
  msg->Write<u32>( for_sale->serial_ext );
×
178
  msg->offset++;  // num_items
×
179
  u8 num_items = 0;
×
180
  // FIXME: ick! apparently we need to iterate backwards... WTF?
181
  for ( int i = for_sale->count() - 1; i >= 0; --i )
×
182
  {
183
    Item* item = ( *for_sale )[i];
×
184
    // const ItemDesc& id = find_itemdesc( item->objtype_ );
185
    std::string desc = Clib::strUtf8ToCp1252( item->merchant_description() );
×
186
    size_t addlen = 5 + desc.size();
×
187
    if ( msg->offset + addlen > sizeof msg->buffer )
×
188
    {
189
      return false;
×
190
    }
191
    msg->WriteFlipped<u32>( item->sellprice() );
×
192
    msg->Write<u8>( desc.size() + 1 );  // Don't forget the nullptr
×
193
    msg->Write( desc.c_str(), static_cast<u16>( desc.size() + 1 ) );
×
194
    ++num_items;
×
195

196
    if ( send_aos_tooltip )
×
197
      SendAOSTooltip( client, item, true );
×
198
  }
×
199
  u16 len = msg->offset;
×
200
  msg->offset = 1;
×
201
  msg->WriteFlipped<u16>( len );
×
202
  msg->offset += 4;
×
203
  msg->Write<u8>( num_items );
×
204
  msg.Send( client, len );
×
205
  return true;
×
206
}
×
207

208
BObjectImp* UOExecutorModule::mf_SendBuyWindow( /* character, container, vendor, items, flags */ )
×
209
{
210
  Character *chr, *mrchnt;
211
  Item *item, *item2;
212
  NPC* merchant;
213
  int flags;
214
  UContainer *for_sale, *bought;
215
  unsigned char save_layer_one, save_layer_two;
216

217
  if ( getCharacterParam( 0, chr ) && getItemParam( 1, item ) && getCharacterParam( 2, mrchnt ) &&
×
218
       getItemParam( 3, item2 ) && getParam( 4, flags ) )
×
219
  {
220
    if ( !chr->has_active_client() )
×
221
    {
222
      return new BError( "No client connected to character" );
×
223
    }
224

225
    if ( item->isa( UOBJ_CLASS::CLASS_CONTAINER ) )
×
226
    {
227
      for_sale = static_cast<UContainer*>( item );
×
228
    }
229
    else
230
    {
231
      return new BError( "Parameter 1 invalid" );
×
232
    }
233

234
    if ( mrchnt->isa( UOBJ_CLASS::CLASS_NPC ) )
×
235
    {
236
      merchant = static_cast<NPC*>( mrchnt );
×
237
    }
238
    else
239
    {
240
      return new BError( "Parameter 2 invalid" );
×
241
    }
242

243
    if ( item2->isa( UOBJ_CLASS::CLASS_CONTAINER ) )
×
244
    {
245
      bought = static_cast<UContainer*>( item2 );
×
246
    }
247
    else
248
    {
249
      return new BError( "Parameter 3 invalid" );
×
250
    }
251

252
    //    say_above(merchant, "How may I help you?" );
253
  }
254
  else
255
  {
256
    return new BError( "A parameter was invalid" );
×
257
  }
258

259

260
  // try this
261
  save_layer_one = for_sale->layer;
×
262
  for_sale->layer = LAYER_VENDOR_FOR_SALE;
×
263
  send_wornitem( chr->client, merchant, for_sale );
×
264
  for_sale->layer = save_layer_one;
×
265
  for_sale->setposition( merchant->pos() );
×
266
  // chr->add_additional_legal_item( for_sale );
267

268
  save_layer_two = bought->layer;
×
269
  bought->layer = LAYER_VENDOR_PLAYER_ITEMS;
×
270
  send_wornitem( chr->client, merchant, bought );
×
271
  bought->layer = save_layer_two;
×
272
  bought->setposition( merchant->pos() );
×
273
  // chr->add_additional_legal_item( bought );
274

275
  bool send_aos_tooltip = flags & VENDOR_SEND_AOS_TOOLTIP ? true : false;
×
276

277
  chr->client->gd->vendor.set( merchant );
×
278
  chr->client->gd->vendor_for_sale.set( for_sale );
×
279
  chr->client->gd->vendor_bought.set( bought );
×
280

281
  chr->client->pause(); /* Prevent lag time between messages */
×
282

283
  // Send the 'for sale' container
284
  send_container_contents( chr->client, *for_sale );
×
285

286
  if ( !send_vendorwindow_contents( chr->client, for_sale, send_aos_tooltip ) )
×
287
  {
288
    chr->client->restart();
×
289
    return new BError( "Too much crap in vendor's inventory!" );
×
290
  }
291

292
  // Send the 'bought' container (?) (hmm, I think this is the player-sold items)
293
  send_container_contents( chr->client, *bought );
×
294

295
  if ( !send_vendorwindow_contents( chr->client, bought, send_aos_tooltip ) )
×
296
  {
297
    chr->client->restart();
×
298
    return new BError( "Too much crap in vendor's inventory!" );
×
299
  }
300

301
  // This looks good
302
  PktHelper::PacketOut<PktOut_24> open_window;
×
303
  open_window->Write<u32>( merchant->serial_ext );
×
304
  open_window->WriteFlipped<u16>( 0x0030u );  // FIXME: Serial of buy gump needs #define or enum?
×
305
  if ( chr->client->ClientType & CLIENTTYPE_7090 )
×
306
    open_window->WriteFlipped<u16>( 0x00u );
×
307
  open_window.Send( chr->client );
×
308

309
  // Tell the client how much gold the character has, I guess
310
  send_full_statmsg( chr->client, chr );
×
311

312
  chr->client->restart();
×
313

314
  return new BLong( 1 );
×
315
}
×
316

317

318
void send_clear_vendorwindow( Client* client, Character* vendor )
×
319
{
320
  PktHelper::PacketOut<PktOut_3B> msg;
×
321
  msg->WriteFlipped<u16>( sizeof msg->buffer );
×
322
  msg->Write<u32>( vendor->serial_ext );
×
323
  msg->Write<u8>( PKTBI_3B::STATUS_NOTHING_BOUGHT );
×
324
  msg.Send( client );
×
325
}
×
326
unsigned int calculate_cost( Character* /*vendor*/, UContainer* for_sale, UContainer* bought,
×
327
                             PKTBI_3B* msg )
328
{
329
  uint64_t amt = 0, prev_amt = 0;
×
330

331
  int nitems = ( cfBEu16( msg->msglen ) - offsetof( PKTBI_3B, items ) ) / sizeof msg->items[0];
×
332

333
  for ( int i = 0; i < nitems; ++i )
×
334
  {
335
    u32 serial = cfBEu32( msg->items[i].item_serial );
×
336
    Item* item = for_sale->find( serial );
×
337
    if ( item == nullptr )
×
338
    {
339
      item = bought->find( serial );
×
340
      if ( item == nullptr )
×
341
        continue;
×
342
    }
343
    // const ItemDesc& id = find_itemdesc(item->objtype_);
344
    amt += cfBEu16( msg->items[i].number_bought ) * static_cast<uint64_t>( item->sellprice() );
×
345

346
    if ( amt < prev_amt || amt > INT_MAX )
×
347
    {
348
      return INT_MAX + 1U;
×
349
    }
350

351
    prev_amt = amt;
×
352
  }
353
  return static_cast<uint32_t>( amt );
×
354
}
355

356
void oldBuyHandler( Client* client, PKTBI_3B* msg )
×
357
{
358
  // Close the gump
359

360
  if ( msg->status == PKTBI_3B::STATUS_NOTHING_BOUGHT )
×
361
    return;
×
362

363
  UContainer* backpack = client->chr->backpack();
×
364
  if ( backpack == nullptr )
×
365
    return;
×
366

367
  NPC* vendor = client->gd->vendor.get();
×
368

369
  if ( vendor == nullptr || vendor->orphan() || vendor->serial_ext != msg->vendor_serial )
×
370
  {
371
    return;
×
372
  }
373
  client->gd->vendor.clear();
×
374

375
  UContainer* for_sale = client->gd->vendor_for_sale.get();
×
376
  if ( for_sale == nullptr || for_sale->orphan() )
×
377
  {
378
    return;
×
379
  }
380
  client->gd->vendor_for_sale.clear();
×
381

382
  UContainer* vendor_bought = client->gd->vendor_bought.get();
×
383
  if ( vendor_bought == nullptr || vendor_bought->orphan() )
×
384
  {
385
    return;
×
386
  }
387
  client->gd->vendor_bought.clear();
×
388

389
  unsigned int total_cost = calculate_cost( vendor, for_sale, vendor_bought, msg );
×
390

391
  if ( total_cost > INT_MAX )
×
392
  {
393
    POLLOG_INFO(
×
394
        "\n"
395
        "Warning: Character {:#x} tried to buy items with an overflow from vendor {:#x}.\n",
396
        client->chr->serial, vendor->serial );
×
397
    send_clear_vendorwindow( client, vendor );
×
398
    return;
×
399
  }
400

401
  if ( total_cost > client->chr->gold_carried() )
×
402
  {
403
    send_clear_vendorwindow( client, vendor );
×
404
    return;
×
405
  }
406

407
  unsigned int amount_spent = 0;
×
408

409
  // buy each item individually
410
  // note, we know the buyer can afford it all.
411
  // the question is, can it all fit in his backpack?
412
  int nitems = ( cfBEu16( msg->msglen ) - offsetof( PKTBI_3B, items ) ) / sizeof msg->items[0];
×
413

414
  bool from_bought;
415
  for ( int i = 0; i < nitems; ++i )
×
416
  {
417
    from_bought = false;
×
418
    u32 serial = cfBEu32( msg->items[i].item_serial );
×
419
    Item* fs_item = for_sale->find( serial );
×
420
    if ( fs_item == nullptr )
×
421
    {
422
      fs_item = vendor_bought->find( serial );
×
423
      if ( fs_item == nullptr )
×
424
        continue;
×
425
      from_bought = true;
×
426
    }
427
    unsigned short numleft = cfBEu16( msg->items[i].number_bought );
×
428
    if ( numleft > fs_item->getamount() )
×
429
      numleft = fs_item->getamount();
×
430

431
    // const ItemDesc& id = find_itemdesc( fs_item->objtype_ );
432
    while ( numleft )
×
433
    {
434
      unsigned short num;
435
      if ( fs_item == nullptr )
×
436
        break;
×
437
      if ( fs_item->stackable() )
×
438
      {
439
        num = numleft;
×
440
      }
441
      else
442
      {
443
        num = 1;
×
444
      }
445
      Item* tobuy = nullptr;
×
446
      if ( fs_item->amount_to_remove_is_partial( num ) )
×
447
      {
448
        tobuy = fs_item->remove_part_of_stack( num );
×
449
      }
450
      else
451
      {
452
        if ( from_bought )
×
453
          vendor_bought->remove( fs_item );
×
454
        else
455
          for_sale->remove( fs_item );
×
456
        tobuy = fs_item;
×
457
        fs_item = nullptr;
×
458
      }
459

460
      // move the whole item
461
      ItemRef itemref(
462
          tobuy );  // dave 1/28/3 prevent item from being destroyed before function ends
×
463
      Item* existing_stack;
464
      if ( tobuy->stackable() &&
×
465
           ( existing_stack = backpack->find_addable_stack( tobuy ) ) != nullptr )
×
466
      {
467
        // dave 1-14-3 check backpack's insert scripts before moving.
468
        if ( backpack->can_insert_increase_stack( client->chr, UContainer::MT_CORE_MOVED,
×
469
                                                  existing_stack, tobuy->getamount(), tobuy ) )
×
470
        {
471
          if ( tobuy->orphan() )  // dave added 1/28/3, item might be destroyed in RTC script
×
472
          {
473
            continue;
×
474
          }
475
        }
476
        else  // put the item back just as if the pack had too many/too heavy items.
477
        {
478
          numleft = 0;
×
479
          if ( fs_item )
×
480
            fs_item->add_to_self( tobuy );
×
481
          else
482
            // FIXME : Add Grid Index Default Location Checks here.
483
            // Remember, if index fails, move to the ground.
484
            if ( from_bought )
×
485
              vendor_bought->add( tobuy, tobuy->pos2d() );
×
486
            else
487
              for_sale->add( tobuy, tobuy->pos2d() );
×
488
          continue;
×
489
        }
490
        numleft -= num;
×
491
        amount_spent += tobuy->sellprice() * num;
×
492
        u16 amtadded = tobuy->getamount();
×
493
        existing_stack->add_to_self( tobuy );
×
494
        update_item_to_inrange( existing_stack );
×
495

496
        backpack->on_insert_increase_stack( client->chr, UContainer::MT_CORE_MOVED, existing_stack,
×
497
                                            amtadded );
498
      }
499
      else if ( backpack->can_add( *tobuy ) )
×
500
      {
501
        numleft -= num;
×
502

503
        // dave 12-20 check backpack's insert scripts before moving.
504
        bool canInsert =
505
            backpack->can_insert_add_item( client->chr, UContainer::MT_CORE_MOVED, tobuy );
×
506
        if ( tobuy->orphan() )  // dave added 1/28/3, item might be destroyed in RTC script
×
507
        {
508
          continue;
×
509
        }
510

511
        if ( !canInsert )  // put the item back just as if the pack had too many/too heavy items.
×
512
        {
513
          numleft = 0;
×
514
          if ( fs_item )
×
515
            fs_item->add_to_self( tobuy );
×
516
          else
517
            // FIXME : Add Grid Index Default Location Checks here.
518
            // Remember, if index fails, move to the ground.
519
            if ( from_bought )
×
520
              vendor_bought->add( tobuy, tobuy->pos2d() );
×
521
            else
522
              for_sale->add( tobuy, tobuy->pos2d() );
×
523
          continue;
×
524
        }
525

526
        // FIXME : Add Grid Index Default Location Checks here.
527
        // Remember, if index fails, move to the ground.
528
        backpack->add_at_random_location( tobuy );
×
529
        update_item_to_inrange( tobuy );
×
530
        amount_spent += tobuy->sellprice() * num;
×
531

532
        backpack->on_insert_add_item( client->chr, UContainer::MT_CORE_MOVED, tobuy );
×
533
      }
534
      else
535
      {
536
        numleft = 0;
×
537
        if ( fs_item )
×
538
          fs_item->add_to_self( tobuy );
×
539
        else
540
        {
541
          // FIXME : Add Grid Index Default Location Checks here.
542
          // Remember, if index fails, move to the ground.
543
          if ( from_bought )
×
544
            vendor_bought->add( tobuy, tobuy->pos2d() );
×
545
          else
546
            for_sale->add( tobuy, tobuy->pos2d() );
×
547
        }
548
      }
549
    }
×
550
  }
551

552
  client->chr->spend_gold( amount_spent );
×
553

554
  std::unique_ptr<SourcedEvent> sale_event( new SourcedEvent( EVID_MERCHANT_SOLD, client->chr ) );
×
555
  sale_event->addMember( "amount", new BLong( amount_spent ) );
×
556
  vendor->send_event( sale_event.release() );
×
557

558
  send_clear_vendorwindow( client, vendor );
×
559
}
×
560

561
void buyhandler( Client* client, PKTBI_3B* msg )
×
562
{
563
  if ( !settingsManager.ssopt.scripted_merchant_handlers )
×
564
  {
565
    oldBuyHandler( client, msg );
×
566
    return;
×
567
  }
568
  // Close the gump
569

570
  if ( msg->status == PKTBI_3B::STATUS_NOTHING_BOUGHT )
×
571
    return;
×
572

573
  UContainer* backpack = client->chr->backpack();
×
574
  if ( backpack == nullptr )
×
575
  {
576
    return;
×
577
  }
578

579
  NPC* vendor = client->gd->vendor.get();
×
580
  if ( vendor == nullptr || vendor->orphan() || vendor->serial_ext != msg->vendor_serial )
×
581
  {
582
    return;
×
583
  }
584
  client->gd->vendor.clear();
×
585

586
  UContainer* for_sale = client->gd->vendor_for_sale.get();
×
587
  if ( for_sale == nullptr || for_sale->orphan() )
×
588
  {
589
    return;
×
590
  }
591
  client->gd->vendor_for_sale.clear();
×
592
  UContainer* vendor_bought = client->gd->vendor_bought.get();
×
593
  if ( vendor_bought == nullptr || vendor_bought->orphan() )
×
594
  {
595
    return;
×
596
  }
597
  client->gd->vendor_bought.clear();
×
598

599
  // buy each item individually
600
  // the question is, can it all fit in his backpack?
601
  // But who cares ... let the scripter(s) handle it!
602
  int nitems = ( cfBEu16( msg->msglen ) - offsetof( PKTBI_3B, items ) ) / sizeof msg->items[0];
×
603

604
  std::unique_ptr<ObjArray> items_bought( new ObjArray );
×
605
  for ( int i = 0; i < nitems; ++i )
×
606
  {
607
    Item* fs_item = for_sale->find( cfBEu32( msg->items[i].item_serial ) );
×
608
    if ( fs_item == nullptr )
×
609
    {
610
      fs_item = vendor_bought->find( cfBEu32( msg->items[i].item_serial ) );
×
611
      if ( fs_item == nullptr )
×
612
        continue;
×
613
    }
614
    unsigned short numleft = cfBEu16( msg->items[i].number_bought );
×
615

616
    if ( numleft > fs_item->getamount() )
×
617
      numleft = fs_item->getamount();
×
618

619
    std::unique_ptr<BStruct> entry( new BStruct );
×
620
    entry->addMember( "item", fs_item->make_ref() );
×
621
    entry->addMember( "amount", new BLong( numleft ) );
×
622

623
    items_bought->addElement( entry.release() );
×
624
  }
×
625

626
  std::unique_ptr<SourcedEvent> sale_event( new SourcedEvent( EVID_MERCHANT_SOLD, client->chr ) );
×
627
  sale_event->addMember( "shoppinglist", items_bought.release() );
×
628
  vendor->send_event( sale_event.release() );
×
629

630
  send_clear_vendorwindow( client, vendor );
×
631
}
×
632

633
bool send_vendorsell( Client* client, NPC* merchant, UContainer* sellfrom, UContainer* buyable,
×
634
                      bool send_aos_tooltip )
635
{
636
  unsigned short num_items = 0;
×
637
  PktHelper::PacketOut<PktOut_9E> msg;
×
638
  msg->offset += 2;
×
639
  msg->Write<u32>( merchant->serial_ext );
×
640
  msg->offset += 2;  // numitems
×
641

642
  UContainer::iterator buyable_itr, buyable_end;
×
643
  if ( buyable != nullptr )
×
644
    buyable_end = buyable->end();
×
645

646
  UContainer* cont = sellfrom;
×
647
  while ( cont != nullptr )
×
648
  {
649
    for ( UContainer::iterator itr = cont->begin(), end = cont->end(); itr != end; ++itr )
×
650
    {
651
      Item* item = *itr;
×
652
      if ( item->isa( UOBJ_CLASS::CLASS_CONTAINER ) )
×
653
      {
654
        UContainer* cont2 = static_cast<UContainer*>( item );
×
655
        if ( cont2->count() )
×
656
          continue;
×
657
      }
658
      if ( item->newbie() )
×
659
        continue;
×
660
      unsigned int buyprice;
661
      if ( !item->getbuyprice( buyprice ) )
×
662
        continue;
×
663
      std::string desc = Clib::strUtf8ToCp1252( item->merchant_description() );
×
664
      if ( msg->offset + desc.size() + 14 > sizeof msg->buffer )
×
665
      {
666
        return false;
×
667
      }
668
      if ( buyable != nullptr )
×
669
      {
670
        for ( buyable_itr = buyable->begin(); buyable_itr != buyable_end; ++buyable_itr )
×
671
        {
672
          Item* buyable_item = *buyable_itr;
×
673
          if ( buyable_item->objtype_ == item->objtype_ )
×
674
            break;
×
675
        }
676
        if ( buyable_itr == buyable_end )
×
677
          continue;
×
678
      }
679

680
      msg->Write<u32>( item->serial_ext );
×
681
      msg->WriteFlipped<u16>( item->graphic );
×
682
      msg->WriteFlipped<u16>( item->color );
×
683
      msg->WriteFlipped<u16>( item->getamount() );
×
684
      msg->WriteFlipped<u16>( buyprice );
×
685
      msg->WriteFlipped<u16>( desc.size() );
×
686
      msg->Write( desc.c_str(), static_cast<u16>( desc.size() ), false );  // No null term
×
687
      ++num_items;
×
688

689
      if ( send_aos_tooltip )
×
690
        SendAOSTooltip( client, item, true );
×
691
    }
×
692

693
    cont = nullptr;
×
694
  }
695
  u16 len = msg->offset;
×
696
  msg->offset = 1;
×
697
  msg->WriteFlipped<u16>( len );
×
698
  msg->offset += 4;
×
699
  msg->WriteFlipped<u16>( num_items );
×
700
  msg.Send( client, len );
×
701
  return true;
×
702
}
×
703

704
BObjectImp* UOExecutorModule::mf_SendSellWindow( /* character, vendor, i1, i2, i3, flags */ )
×
705
{
706
  Character *chr, *mrchnt;
707
  NPC* merchant;
708
  Item* wi1a;
709
  Item* wi1b;
710
  Item* wi1c;
711
  int flags;
712
  UContainer* merchant_bought;
713
  UContainer* merchant_buyable = nullptr;
×
714

715
  if ( !( getCharacterParam( 0, chr ) && getCharacterParam( 1, mrchnt ) &&
×
716
          getItemParam( 2, wi1a ) && getItemParam( 3, wi1b ) && getItemParam( 4, wi1c ) &&
×
717
          getParam( 5, flags ) ) )
×
718
  {
719
    return new BError( "A parameter was invalid" );
×
720
  }
721

722
  if ( !chr->has_active_client() )
×
723
  {
724
    return new BError( "No client connected to character" );
×
725
  }
726

727
  if ( mrchnt->isa( UOBJ_CLASS::CLASS_NPC ) )
×
728
  {
729
    merchant = static_cast<NPC*>( mrchnt );
×
730
  }
731
  else
732
  {
733
    return new BError( "Parameter 1 invalid" );
×
734
  }
735
  if ( wi1b->isa( UOBJ_CLASS::CLASS_CONTAINER ) )
×
736
  {
737
    merchant_bought = static_cast<UContainer*>( wi1b );
×
738
  }
739
  else
740
  {
741
    return new BError( "Parameter 2 must be a container" );
×
742
  }
743

744
  if ( flags & VENDOR_BUYABLE_CONTAINER_FILTER )
×
745
  {
746
    if ( wi1c->isa( UOBJ_CLASS::CLASS_CONTAINER ) )
×
747
    {
748
      merchant_buyable = static_cast<UContainer*>( wi1c );
×
749
    }
750
    else
751
    {
752
      return new BError( "Parameter 3 must be a container" );
×
753
    }
754
  }
755

756
  if ( chr->backpack() == nullptr )
×
757
  {
758
    return new BError( "Character has no backpack" );
×
759
  }
760

761
  unsigned char save_layer = wi1a->layer;
×
762
  wi1a->layer = LAYER_VENDOR_FOR_SALE;
×
763
  send_wornitem( chr->client, merchant, wi1a );
×
764
  wi1a->layer = save_layer;
×
765

766
  save_layer = wi1b->layer;
×
767
  wi1b->layer = LAYER_VENDOR_PLAYER_ITEMS;
×
768
  send_wornitem( chr->client, merchant, wi1b );
×
769
  wi1b->layer = save_layer;
×
770

771
  save_layer = wi1c->layer;
×
772
  wi1c->layer = LAYER_VENDOR_BUYABLE_ITEMS;
×
773
  send_wornitem( chr->client, merchant, wi1c );
×
774
  wi1c->layer = save_layer;
×
775

776
  bool send_aos_tooltip = flags & VENDOR_SEND_AOS_TOOLTIP ? true : false;
×
777

778
  send_vendorsell( chr->client, merchant, chr->backpack(), merchant_buyable, send_aos_tooltip );
×
779

780
  chr->client->gd->vendor.set( merchant );
×
781
  chr->client->gd->vendor_bought.set( merchant_bought );
×
782

783
  return new BLong( 1 );
×
784
}
785

786
extern BObjectImp* _create_item_in_container( UContainer* cont, const ItemDesc* descriptor,
787
                                              unsigned short amount, bool force_stacking,
788
                                              std::optional<Core::Pos2d> pos,
789
                                              UOExecutorModule* uoemod );
790
// player selling to vendor
791
void oldSellHandler( Client* client, PKTIN_9F* msg )
×
792
{
793
  UContainer* backpack = client->chr->backpack();
×
794
  if ( backpack == nullptr )
×
795
    return;
×
796

797
  NPC* vendor = client->gd->vendor.get();
×
798

799
  if ( vendor == nullptr || vendor->orphan() || vendor->serial_ext != msg->vendor_serial )
×
800
  {
801
    client->gd->vendor.clear();
×
802
    client->gd->vendor_bought.clear();
×
803
    return;
×
804
  }
805

806
  UContainer* vendor_bought = client->gd->vendor_bought.get();
×
807
  if ( vendor_bought == nullptr || vendor_bought->orphan() )
×
808
  {
809
    client->gd->vendor.clear();
×
810
    client->gd->vendor_bought.clear();
×
811
    return;
×
812
  }
813

814
  unsigned int cost = 0;
×
815
  size_t num_items = cfBEu16( msg->num_items );
×
816
  Clib::sanitize_upperlimit( &num_items, Clib::arsize( msg->items ) );
×
817

818
  for ( size_t i = 0; i < num_items; ++i )
×
819
  {
820
    u32 serial = cfBEu32( msg->items[i].serial );
×
821
    u16 amount = cfBEu16( msg->items[i].amount );
×
822

823
    unsigned int buyprice;
824

825
    Item* item = backpack->find_toplevel( serial );
×
826
    if ( item == nullptr )
×
827
      return;
×
828
    if ( item->newbie() )
×
829
      continue;
×
830
    if ( item->inuse() )
×
831
      continue;
×
832
    if ( !item->getbuyprice( buyprice ) )
×
833
      continue;
×
834
    if ( amount > item->getamount() )
×
835
      amount = item->getamount();
×
836
    Item* remainder_not_sold = nullptr;
×
837
    if ( item->amount_to_remove_is_partial( amount ) )
×
838
      remainder_not_sold = item->slice_stacked_item( amount );
×
839

840
    if ( vendor_bought->can_add( *item ) )
×
841
    {
842
      backpack->remove( item );
×
843
      if ( remainder_not_sold != nullptr )
×
844
      {
845
        // FIXME : Add Grid Index Default Location Checks here.
846
        // Remember, if index fails, move to the ground.
847
        backpack->add( remainder_not_sold, item->pos2d() );
×
848
        update_item_to_inrange( remainder_not_sold );
×
849
        remainder_not_sold = nullptr;
×
850
      }
851
      // FIXME : Add Grid Index Default Location Checks here.
852
      // Remember, if index fails, move to the ground.
853
      vendor_bought->add_at_random_location( item );
×
854
      update_item_to_inrange( item );
×
855
      cost += buyprice * amount;
×
856
    }
857

858
    if ( remainder_not_sold != nullptr )
×
859
    {
860
      item->add_to_self( remainder_not_sold );
×
861
      update_item_to_inrange( item );
×
862
    }
863
  }
864

865
  // dave added 12-19. If no items are sold don't create any gold in the player's pack!
866
  if ( cost > 0 )
×
867
  {
868
    // dave added 12-21, create stacks of 60k gold instead of one huge, invalid stack.
869
    unsigned int temp_cost = cost;
×
870
    while ( temp_cost > 60000 )
×
871
    {
872
      BObject o( _create_item_in_container( backpack, &find_itemdesc( UOBJ_GOLD_COIN ),
×
873
                                            static_cast<unsigned short>( 60000 ), false, {},
874
                                            nullptr ) );
×
875
      temp_cost -= 60000;
×
876
    }
×
877
    if ( temp_cost > 0 )
×
878
    {
879
      BObject o( _create_item_in_container( backpack, &find_itemdesc( UOBJ_GOLD_COIN ),
×
880
                                            static_cast<unsigned short>( temp_cost ), false, {},
881
                                            nullptr ) );
×
882
    }
×
883
  }
884
  std::unique_ptr<SourcedEvent> sale_event( new SourcedEvent( EVID_MERCHANT_BOUGHT, client->chr ) );
×
885
  sale_event->addMember( "amount", new BLong( cost ) );
×
886
  vendor->send_event( sale_event.release() );
×
887

888
  client->pause();
×
889

890
  send_full_statmsg( client, client->chr );
×
891

892
  send_clear_vendorwindow( client, vendor );
×
893

894
  client->gd->vendor_bought.clear();
×
895
  client->gd->vendor.clear();
×
896
  client->restart();
×
897
}
×
898

899
void sellhandler( Client* client, PKTIN_9F* msg )
×
900
{
901
  if ( !settingsManager.ssopt.scripted_merchant_handlers )
×
902
  {
903
    oldSellHandler( client, msg );
×
904
    return;
×
905
  }
906
  UContainer* backpack = client->chr->backpack();
×
907
  if ( backpack == nullptr )
×
908
    return;
×
909

910
  NPC* vendor = client->gd->vendor.get();
×
911
  if ( vendor == nullptr || vendor->orphan() || vendor->serial_ext != msg->vendor_serial )
×
912
  {
913
    client->gd->vendor.clear();
×
914
    client->gd->vendor_bought.clear();
×
915
    return;
×
916
  }
917

918
  UContainer* vendor_bought = client->gd->vendor_bought.get();
×
919
  if ( vendor_bought == nullptr || vendor_bought->orphan() )
×
920
  {
921
    client->gd->vendor.clear();
×
922
    client->gd->vendor_bought.clear();
×
923
    return;
×
924
  }
925

926
  size_t num_items = cfBEu16( msg->num_items );
×
927
  std::unique_ptr<ObjArray> items_sold( new ObjArray );
×
928
  Clib::sanitize_upperlimit( &num_items, Clib::arsize( msg->items ) );
×
929
  for ( size_t i = 0; i < num_items; ++i )
×
930
  {
931
    u32 serial = cfBEu32( msg->items[i].serial );
×
932
    u32 amount = cfBEu16( msg->items[i].amount );
×
933

934
    Item* item = backpack->find_toplevel( serial );
×
935

936
    if ( item == nullptr )
×
937
      return;
×
938
    if ( item->newbie() )
×
939
      continue;
×
940
    if ( item->inuse() )
×
941
      continue;
×
942
    if ( amount > item->getamount() )
×
943
      amount = item->getamount();
×
944

945
    std::unique_ptr<BStruct> entry( new BStruct );
×
946
    entry->addMember( "item", item->make_ref() );
×
947
    entry->addMember( "amount", new BLong( amount ) );
×
948

949
    items_sold->addElement( entry.release() );
×
950
  }
×
951
  std::unique_ptr<SourcedEvent> sale_event( new SourcedEvent( EVID_MERCHANT_BOUGHT, client->chr ) );
×
952
  sale_event->addMember( "shoppinglist", items_sold.release() );
×
953
  vendor->send_event( sale_event.release() );
×
954

955
  send_clear_vendorwindow( client, vendor );
×
956
  client->gd->vendor.clear();
×
957
  client->gd->vendor_bought.clear();
×
958
}
×
959

960
//
961
//  "GUMP" Functions
962
//
963
BObjectImp* UOExecutorModule::mf_SendDialogGump()
1✔
964
{
965
  /*
966
   Client* client,
967
   const char* layout,
968
   const char* strings[]
969
   */
970
  int x, y, flags, gump_id;
971
  Character* chr;
972
  ObjArray* layout_arr;
973
  ObjArray* data_arr;
974
  if ( !( getCharacterParam( 0, chr ) && exec.getObjArrayParam( 1, layout_arr ) &&
2✔
975
          exec.getObjArrayParam( 2, data_arr ) && exec.getParam( 3, x ) && exec.getParam( 4, y ) &&
1✔
976
          exec.getParam( 5, flags ) && exec.getParam( 6, gump_id ) ) )
1✔
977
  {
978
    return new BError( "Invalid parameter" );
×
979
  }
980
  if ( !chr->has_active_client() )
1✔
981
  {
982
    return new BError( "No client attached" );
×
983
  }
984

985
  // Now determine the id to be assigned to the gump
986
  u32 gumpid;
987
  if ( gump_id )
1✔
988
  {
989
    // Validate and use the id requested by the script
990
    if ( gump_id < 1 )
×
991
      return new BError( "GumpID out of range" );
×
992
    gumpid = (u32)gump_id;
×
993
    if ( gumpid >= ScriptScheduler::PID_MIN )
×
994
      return new BError( "GumpID out of range" );
×
995
  }
996
  else
997
  {
998
    // For keeping backward compatibility: we do not know how many scripts do rely
999
    // on the fact that pid == gumpid when autogenerated
1000
    gumpid = this->uoexec().pid();
1✔
1001
  }
1002

1003
  /*
1004
  if (chr->client->gd->gump_uoemod != nullptr)
1005
  {
1006
  return new BError( "Client already has an active gump" );
1007
  }
1008
  */
1009
  if ( ( chr->client->ClientType & CLIENTTYPE_UOSA ) || ( chr->client->IsUOKRClient() ) ||
1✔
1010
       ( ( !( flags & SENDDIALOGMENU_FORCE_OLD ) ) &&
×
1011
         ( chr->client->compareVersion( CLIENT_VER_5000 ) ) ) )
×
1012
    return internal_SendCompressedGumpMenu( chr, layout_arr, data_arr, x, y, gumpid );
1✔
NEW
1013
  return internal_SendUnCompressedGumpMenu( chr, layout_arr, data_arr, x, y, gumpid );
×
1014
}
1015

1016

1017
BObjectImp* UOExecutorModule::internal_SendUnCompressedGumpMenu( Character* chr,
×
1018
                                                                 ObjArray* layout_arr,
1019
                                                                 ObjArray* data_arr, int x, int y,
1020
                                                                 u32 gumpid )
1021
{
1022
  PktHelper::PacketOut<PktOut_B0> msg;
×
1023
  msg->offset += 2;
×
1024
  msg->Write<u32>( chr->serial_ext );
×
1025
  msg->WriteFlipped<u32>( gumpid );
×
1026
  msg->WriteFlipped<u32>( static_cast<u32>( x ) );
×
1027
  msg->WriteFlipped<u32>( static_cast<u32>( y ) );
×
1028
  u16 pos = msg->offset;
×
1029
  msg->offset += 2;  // layoutlen
×
1030
  size_t layoutlen = 0;
×
1031
  for ( unsigned i = 0; i < layout_arr->ref_arr.size(); ++i )
×
1032
  {
1033
    BObject* bo = layout_arr->ref_arr[i].get();
×
1034
    if ( bo == nullptr )
×
1035
      continue;
×
1036
    BObjectImp* imp = bo->impptr();
×
1037
    std::string s = imp->getStringRep();
×
1038

1039
    size_t addlen = 4 + s.length();
×
1040
    layoutlen += addlen;
×
1041
    if ( msg->offset + addlen > sizeof msg->buffer )
×
1042
    {
1043
      return new BError( "Buffer length exceeded" );
×
1044
    }
1045
    msg->Write( "{ ", 2, false );
×
1046
    msg->Write( s.c_str(), static_cast<u16>( s.length() ), false );
×
1047
    msg->Write( " }", 2, false );
×
1048
  }
×
1049

1050
  if ( msg->offset + 1 > static_cast<int>( sizeof msg->buffer ) )
×
1051
  {
1052
    return new BError( "Buffer length exceeded" );
×
1053
  }
1054
  msg->offset++;  // nullterm
×
1055
  layoutlen++;
×
1056

1057
  u16 len = msg->offset;
×
1058
  msg->offset = pos;
×
1059
  msg->WriteFlipped<u16>( layoutlen );
×
1060
  msg->offset = len;
×
1061

1062
  pos = msg->offset;
×
1063

1064
  if ( msg->offset + 2 > static_cast<int>( sizeof msg->buffer ) )
×
1065
  {
1066
    return new BError( "Buffer length exceeded" );
×
1067
  }
1068
  msg->offset += 2;  // numlines
×
1069

1070
  u16 numlines = 0;
×
1071
  for ( unsigned i = 0; i < data_arr->ref_arr.size(); ++i )
×
1072
  {
1073
    BObject* bo = data_arr->ref_arr[i].get();
×
1074
    if ( bo == nullptr )
×
1075
      continue;
×
1076
    BObjectImp* imp = bo->impptr();
×
1077
    std::string s = imp->getStringRep();
×
1078

1079
    ++numlines;
×
1080
    auto utf16 = Bscript::String::toUTF16( s );
×
1081

1082
    if ( msg->offset + 2 + utf16.size() * 2 > sizeof msg->buffer )
×
1083
    {
1084
      return new BError( "Buffer length exceeded" );
×
1085
    }
1086

1087
    msg->WriteFlipped<u16>( utf16.size() );
×
1088
    msg->WriteFlipped( utf16, false );
×
1089
  }
×
1090

1091
  if ( msg->offset + 1 > static_cast<int>( sizeof msg->buffer ) )
×
1092
  {
1093
    return new BError( "Buffer length exceeded" );
×
1094
  }
1095
  msg->offset++;  // nullterm
×
1096

1097
  len = msg->offset;
×
1098
  msg->offset = pos;
×
1099
  msg->WriteFlipped<u16>( numlines );
×
1100
  msg->offset = 1;
×
1101
  msg->WriteFlipped<u16>( len );
×
1102

1103
  if ( !uoexec().suspend() )
×
1104
  {
1105
    DEBUGLOGLN(
×
1106
        "Script Error in '{}' PC={}: \n"
1107
        "\tCall to function UO::SendDialogGump():\n"
1108
        "\tThe execution of this script can't be blocked!",
1109
        scriptname(), exec.PC );
×
1110
    return new Bscript::BError( "Script can't be blocked" );
×
1111
  }
1112

1113
  msg.Send( chr->client, len );
×
1114
  chr->client->gd->add_gumpmod( this, gumpid );
×
1115
  // old_gump_uoemod = this;
1116
  gump_chr = chr;
×
1117

1118
  return new BLong( 0 );
×
1119
}
×
1120

1121
BObjectImp* UOExecutorModule::internal_SendCompressedGumpMenu( Character* chr, ObjArray* layout_arr,
1✔
1122
                                                               ObjArray* data_arr, int x, int y,
1123
                                                               u32 gumpid )
1124
{
1125
  PktHelper::PacketOut<PktOut_DD> msg;
1✔
1126
  PktHelper::PacketOut<PktOut_DD> bfr;  // compress buffer
1✔
1127
  bfr->offset = 0;
1✔
1128
  msg->offset += 2;
1✔
1129
  msg->Write<u32>( chr->serial_ext );
1✔
1130
  msg->WriteFlipped<u32>( gumpid );
1✔
1131
  msg->WriteFlipped<u32>( static_cast<u16>( x ) );
1✔
1132
  msg->WriteFlipped<u32>( static_cast<u16>( y ) );
1✔
1133
  msg->offset += 8;  // u32 layout_clen,layout_dlen
1✔
1134

1135
  u32 layoutdlen = 0;
1✔
1136

1137
  for ( unsigned i = 0; i < layout_arr->ref_arr.size(); ++i )
3✔
1138
  {
1139
    BObject* bo = layout_arr->ref_arr[i].get();
2✔
1140
    if ( bo == nullptr )
2✔
1141
      continue;
×
1142
    BObjectImp* imp = bo->impptr();
2✔
1143
    std::string s = imp->getStringRep();
2✔
1144

1145
    size_t addlen = 4 + s.length();
2✔
1146
    if ( layoutdlen + addlen > sizeof bfr->buffer )
2✔
1147
    {
1148
      return new BError( "Buffer length exceeded" );
×
1149
    }
1150
    layoutdlen += static_cast<u32>( addlen );
2✔
1151
    bfr->Write( "{ ", 2, false );
2✔
1152
    bfr->Write( s.c_str(), static_cast<u16>( s.length() ), false );
2✔
1153
    bfr->Write( " }", 2, false );
2✔
1154
  }
2✔
1155
  if ( layoutdlen + 1 > static_cast<u32>( sizeof bfr->buffer ) )
1✔
1156
  {
1157
    return new BError( "Buffer length exceeded" );
×
1158
  }
1159
  layoutdlen++;
1✔
1160
  bfr->offset++;  // nullterm
1✔
1161

1162
  unsigned long cbuflen =
1✔
1163
      ( ( (unsigned long)( ( (float)( layoutdlen ) ) * 1.001f ) ) + 12 );  // as per zlib spec
1✔
1164
  if ( cbuflen > ( (unsigned long)( 0xFFFF - msg->offset ) ) )
1✔
1165
  {
1166
    return new BError( "Compression error" );
×
1167
  }
1168

1169
  if ( compress2( reinterpret_cast<unsigned char*>( msg->getBuffer() ), &cbuflen,
1✔
1170
                  reinterpret_cast<unsigned char*>( &bfr->buffer ), layoutdlen,
1✔
1171
                  Z_DEFAULT_COMPRESSION ) != Z_OK )
1✔
1172
  {
1173
    return new BError( "Compression error" );
×
1174
  }
1175
  msg->offset -= 8;
1✔
1176
  msg->WriteFlipped<u32>( cbuflen + 4 );
1✔
1177
  msg->WriteFlipped<u32>( layoutdlen );
1✔
1178
  msg->offset += static_cast<u16>( cbuflen );
1✔
1179

1180
  bfr->offset = 0;
1✔
1181

1182
  u32 numlines = 0;
1✔
1183
  u32 datadlen = 0;
1✔
1184

1185
  for ( unsigned i = 0; i < data_arr->ref_arr.size(); ++i )
3✔
1186
  {
1187
    BObject* bo = data_arr->ref_arr[i].get();
2✔
1188
    if ( bo == nullptr )
2✔
1189
      continue;
×
1190
    BObjectImp* imp = bo->impptr();
2✔
1191
    std::string s = imp->getStringRep();
2✔
1192

1193
    auto utf16 = Bscript::String::toUTF16( s );
2✔
1194
    ++numlines;
2✔
1195
    size_t addlen = ( utf16.size() + 1 ) * 2;
2✔
1196
    if ( datadlen + addlen > sizeof bfr->buffer )
2✔
1197
    {
1198
      return new BError( "Buffer length exceeded" );
×
1199
    }
1200
    datadlen += static_cast<u32>( addlen );
2✔
1201
    bfr->WriteFlipped<u16>( utf16.size() );
2✔
1202
    bfr->WriteFlipped( utf16, false );
2✔
1203
  }
2✔
1204
  msg->WriteFlipped<u32>( numlines );
1✔
1205
  if ( numlines != 0 )
1✔
1206
  {
1207
    msg->offset += 8;  // u32 text_clen, text_dlen
1✔
1208

1209
    cbuflen = ( ( (unsigned long)( ( (float)( datadlen ) ) * 1.001f ) ) + 12 );  // as per zlib spec
1✔
1210
    if ( cbuflen > ( (unsigned long)( 0xFFFF - msg->offset ) ) )
1✔
1211
    {
1212
      return new BError( "Compression error" );
×
1213
    }
1214
    if ( compress2( reinterpret_cast<unsigned char*>( msg->getBuffer() ), &cbuflen,
1✔
1215
                    reinterpret_cast<unsigned char*>( &bfr->buffer ), datadlen,
1✔
1216
                    Z_DEFAULT_COMPRESSION ) != Z_OK )
1✔
1217
    {
1218
      return new BError( "Compression error" );
×
1219
    }
1220

1221
    msg->offset -= 8;
1✔
1222
    msg->WriteFlipped<u32>( cbuflen + 4 );
1✔
1223
    msg->WriteFlipped<u32>( datadlen );
1✔
1224
    msg->offset += static_cast<u16>( cbuflen );
1✔
1225
  }
1226
  else
1227
    msg->Write<u32>( 0u );
×
1228
  u16 len = msg->offset;
1✔
1229
  msg->offset = 1;
1✔
1230
  msg->WriteFlipped<u16>( len );
1✔
1231

1232
  if ( !uoexec().suspend() )
1✔
1233
  {
1234
    DEBUGLOGLN(
×
1235
        "Script Error in '{}' PC={}: \n"
1236
        "\tCall to function UO::SendDialogGump():\n"
1237
        "\tThe execution of this script can't be blocked!",
1238
        scriptname(), exec.PC );
×
1239
    return new Bscript::BError( "Script can't be blocked" );
×
1240
  }
1241

1242
  msg.Send( chr->client, len );
1✔
1243
  chr->client->gd->add_gumpmod( this, gumpid );
1✔
1244
  // old_gump_uoemod = this;
1245
  gump_chr = chr;
1✔
1246

1247
  return new BLong( 0 );
1✔
1248
}
1✔
1249

1250
class BIntHash final : public BObjectImp
1251
{
1252
public:
1253
  BIntHash();
1254
  BIntHash( const BIntHash& );
1255
  void add( int key, BObjectImp* value );
1256
  BObjectRef get_member( const char* membername ) override;
1257
  BObjectRef OperSubscript( const BObject& obj ) override;
1258
  BObjectImp* copy() const override;
1259
  std::string getStringRep() const override;
1260
  size_t sizeEstimate() const override;
1261

1262
private:
1263
  using Contents = std::map<int, BObjectRef>;
1264
  Contents contents_;
1265

1266
  // not implemented:
1267
  BIntHash& operator=( const BIntHash& ) = delete;
1268
};
1269

1270
BIntHash::BIntHash() : BObjectImp( OTUnknown ), contents_() {}
×
1271

1272
BIntHash::BIntHash( const BIntHash& ih ) : BObjectImp( OTUnknown ), contents_( ih.contents_ ) {}
×
1273

1274
BObjectImp* BIntHash::copy() const
×
1275
{
1276
  return new BIntHash( *this );
×
1277
}
1278

1279
size_t BIntHash::sizeEstimate() const
×
1280
{
1281
  return sizeof( BIntHash ) +
1282
         Clib::memsize( contents_, []( const auto& v ) { return v.sizeEstimate(); } );
×
1283
}
1284

1285
std::string BIntHash::getStringRep() const
×
1286
{
1287
  return "<inthash>";
×
1288
}
1289

1290
void BIntHash::add( int key, BObjectImp* value )
×
1291
{
1292
  // contents_.insert( Contents::value_type( key, value ) );
1293
  contents_[key].set( new BObject( value ) );
×
1294
}
×
1295

1296
BObjectRef BIntHash::get_member( const char* membername )
×
1297
{
1298
  if ( stricmp( membername, "keys" ) == 0 )
×
1299
  {
1300
    ObjArray* arr = new ObjArray;
×
1301
    BObject obj( arr );
×
1302

1303
    Contents::const_iterator itr, end;
×
1304
    for ( itr = contents_.begin(), end = contents_.end(); itr != end; ++itr )
×
1305
    {
1306
      int key = ( *itr ).first;
×
1307
      arr->addElement( new BLong( key ) );
×
1308
    }
1309

1310
    return BObjectRef( obj.impptr() );
×
1311
  }
×
1312

NEW
1313
  return BObjectRef( new BError( "member not found" ) );
×
1314
}
1315

1316
BObjectRef BIntHash::OperSubscript( const BObject& obj )
×
1317
{
1318
  const BObjectImp& objimp = obj.impref();
×
1319
  if ( objimp.isa( OTLong ) )
×
1320
  {
1321
    const BLong& lng = static_cast<const BLong&>( objimp );
×
1322
    Contents::iterator itr = contents_.find( lng.value() );
×
1323
    if ( itr != contents_.end() )
×
1324
    {
1325
      BObjectRef& oref = ( *itr ).second;
×
1326
      return BObjectRef( oref.get()->impptr() );
×
1327
    }
1328

NEW
1329
    return BObjectRef( new BError( "Key not found in inthash" ) );
×
1330
  }
1331

NEW
1332
  return BObjectRef( new BError( "Incorrect type used as subscript to inthash" ) );
×
1333
}
1334

1335
void clear_gumphandler( Client* client, UOExecutorModule* uoemod )
1✔
1336
{
1337
  uoemod->uoexec().revive();
1✔
1338
  uoemod->gump_chr = nullptr;
1✔
1339
  client->gd->remove_gumpmods( uoemod );
1✔
1340
}
1✔
1341

1342
BObjectImp* UOExecutorModule::mf_CloseGump( /* who, pid, response := 0 */ )
×
1343
{
1344
  Character* chr;
1345
  unsigned int pid;
1346
  BObjectImp* resp;
1347

1348
  if ( !( getCharacterParam( 0, chr ) && exec.getParam( 1, pid ) && ( getParamImp( 2, resp ) ) ) )
×
1349
  {
1350
    return new BError( "Invalid parameter" );
×
1351
  }
1352

1353
  if ( !chr->has_active_client() )
×
1354
    return new BError( "No client attached" );
×
1355

1356
  Client* client = chr->client;
×
1357

1358
  UOExecutorModule* uoemod = client->gd->find_gumpmod( pid );
×
1359
  if ( uoemod == nullptr )
×
1360
  {
1361
    return new BError( "Couldnt find script" );
×
1362
  }
1363

1364
  PktHelper::PacketOut<PktOut_BF_Sub4> msg;
×
1365
  msg->WriteFlipped<u16>( 13u );
×
1366
  msg->offset += 2;
×
1367
  msg->WriteFlipped<u32>( pid );
×
1368
  msg->offset += 4;  // buttonid
×
1369

1370
  msg.Send( client );
×
1371

1372
  uoemod->uoexec().ValueStack.back().set( new BObject( resp ) );
×
1373
  clear_gumphandler( client, uoemod );
×
1374

1375
  return new BLong( 1 );
×
1376
}
×
1377

1378
BObjectImp* UOExecutorModule::mf_CloseWindow( /* chr, type, obj */ )
×
1379
{
1380
  Character* chr;
1381
  unsigned int type;
1382
  UObject* obj;
1383

1384
  if ( !getCharacterParam( 0, chr ) || !getParam( 1, type ) || !getUObjectParam( 2, obj ) )
×
1385
    return new BError( "Invalid parameter" );
×
1386

1387
  if ( !chr->has_active_client() )
×
1388
    return new BError( "No client attached" );
×
1389

1390
  if ( type == PKTBI_BF_16::CONTAINER )
×
1391
  {
1392
    if ( !obj->script_isa( POLCLASS_CONTAINER ) )
×
1393
      return new BError( "Invalid object, has to be a containerRef" );
×
1394
  }
1395
  else if ( type == PKTBI_BF_16::PAPERDOLL || type == PKTBI_BF_16::STATUS ||
×
1396
            type == PKTBI_BF_16::CHARPROFILE )
×
1397
  {
1398
    if ( !obj->script_isa( POLCLASS_MOBILE ) )
×
1399
      return new BError( "Invalid object, has to be a mobRef" );
×
1400
  }
1401
  else
1402
    return new BError( "Invalid type" );
×
1403

1404
  PktHelper::PacketOut<PktOut_BF_Sub16> msg;
×
1405
  msg->WriteFlipped<u16>( 13u );
×
1406
  msg->offset += 2;  // sub
×
1407
  msg->WriteFlipped<u32>( type );
×
1408
  msg->Write<u32>( obj->serial_ext );
×
1409

1410
  msg.Send( chr->client );
×
1411

1412
  return new BLong( 1 );
×
1413
}
×
1414

1415
void gumpbutton_handler( Client* client, PKTIN_B1* msg )
1✔
1416
{
1417
  const u32 VIRTUE_GUMP_ID = 0x1CD;
1✔
1418

1419
  char* msgbuf = reinterpret_cast<char*>( msg );
1✔
1420
  PKTIN_B1::HEADER* hdr = reinterpret_cast<PKTIN_B1::HEADER*>( msg );
1✔
1421
  unsigned short msglen = cfBEu16( hdr->msglen );
1✔
1422

1423
  u32 gumpid = cfBEu32( hdr->dialogid );
1✔
1424
  u32 buttonid = cfBEu32( hdr->buttonid );
1✔
1425

1426
  // When the player double-clicks the virtue pentagram icon in its paperdoll, an unexpected
1427
  // PKTIN_B1 gump reply packet with dialogid == 0x01CD and buttonid == 1 will be sent to us
1428
  if ( gumpid == VIRTUE_GUMP_ID )
1✔
1429
  {
1430
    if ( hdr->serial == client->chr->serial_ext )
×
1431
    {
1432
      if ( buttonid == 1 )
×
1433
      {
1434
        PKTIN_B1::INTS_HEADER* intshdr_ = reinterpret_cast<PKTIN_B1::INTS_HEADER*>( hdr + 1 );
×
1435
        if ( cfBEu32( intshdr_->count ) == 1 )
×
1436
        {
1437
          PKTIN_B1::INT_ENTRY* intentries_ = reinterpret_cast<PKTIN_B1::INT_ENTRY*>( intshdr_ + 1 );
×
1438
          if ( intentries_->value == client->chr->serial_ext )
×
1439
          {
1440
            ref_ptr<EScriptProgram> prog = find_script(
×
1441
                "misc/virtuebutton", true, Plib::systemstate.config.cache_interactive_scripts );
×
1442
            if ( prog.get() != nullptr )
×
1443
              client->chr->start_script( prog.get(), false );
×
1444
            return;
×
1445
          }
×
1446
        }
1447
      }
1448
    }
1449
  }
1450

1451

1452
  UOExecutorModule* uoemod = client->gd->find_gumpmod( gumpid );
1✔
1453
  if ( uoemod == nullptr )
1✔
1454
  {
1455
    SuspiciousActs::GumpResponseWasUnexpected( client, gumpid, buttonid );
×
1456
    return;
×
1457
  }
1458

1459
  auto& uoex = uoemod->uoexec();
1✔
1460
  if ( msglen <=
1✔
1461
       0x0f )  // Using == instead of <= should do the trick, but i think <= is more robust
1462
  {
1463
    // The virtue button packet is 15 bytes long: it will not carry a switchcount/INTS_HEADER,
1464
    // so prevent full processing code to overflow and save some CPU cycles meanwhile.
1465
    // Maybe other packets could be that short too?
1466
    if ( buttonid == 0 )
×
1467
    {
1468
      uoex.ValueStack.back().set( new BObject( new BLong( 0 ) ) );
×
1469
    }
1470
    else
1471
    {
1472
      std::unique_ptr<BIntHash> hash( new BIntHash );
×
1473
      hash->add( 0, new BLong( buttonid ) );
×
1474
      hash->add( buttonid, new BLong( 1 ) );
×
1475
      uoex.ValueStack.back().set( new BObject( hash.release() ) );
×
1476
    }
×
1477
  }
1478
  else
1479
  {
1480
    // Process rest of the packet
1481
    PKTIN_B1::INTS_HEADER* intshdr = reinterpret_cast<PKTIN_B1::INTS_HEADER*>( hdr + 1 );
1✔
1482
    u32 ints_count = cfBEu32( intshdr->count );
1✔
1483
    unsigned stridx = sizeof( PKTIN_B1::HEADER ) + sizeof( PKTIN_B1::INTS_HEADER ) +
1✔
1484
                      sizeof( PKTIN_B1::INT_ENTRY ) * ints_count +
1✔
1485
                      sizeof( PKTIN_B1::STRINGS_HEADER );
1486
    if ( stridx > msglen )
1✔
1487
    {
1488
      SuspiciousActs::GumpResponseHasTooManyInts( client );
×
1489
      clear_gumphandler( client, uoemod );
×
1490
      return;
×
1491
    }
1492
    PKTIN_B1::INT_ENTRY* intentries = reinterpret_cast<PKTIN_B1::INT_ENTRY*>( intshdr + 1 );
1✔
1493
    PKTIN_B1::STRINGS_HEADER* strhdr =
1✔
1494
        reinterpret_cast<PKTIN_B1::STRINGS_HEADER*>( intentries + ints_count );
1✔
1495
    u32 strings_count = cfBEu32( strhdr->count );
1✔
1496
    // even if this is ok, it could still overflow.  Have to check each string.
1497
    // -2 per entry to only count tag+length (data has size of 2 in struct)
1498
    if ( stridx + ( sizeof( PKTIN_B1::STRING_ENTRY ) - 2 ) * strings_count > msglen + 1u )
1✔
1499
    {
1500
      SuspiciousActs::GumpResponseHasTooManyIntsOrStrings( client );
×
1501
      uoex.ValueStack.back().set(
×
1502
          new BObject( new BError( "B1 message specified too many ints and/or strings." ) ) );
×
1503
      clear_gumphandler( client, uoemod );
×
1504
      return;
×
1505
    }
1506

1507
    if ( ints_count == 0 && strings_count == 0 && buttonid == 0 )
1✔
1508
    {
1509
      uoex.ValueStack.back().set( new BObject( new BLong( 0 ) ) );
1✔
1510
    }
1511
    else
1512
    {
1513
      std::unique_ptr<BIntHash> hash( new BIntHash );
×
1514
      hash->add( 0, new BLong( buttonid ) );
×
1515
      hash->add( buttonid, new BLong( 1 ) );
×
1516
      for ( unsigned i = 0; i < ints_count; ++i )
×
1517
      {
1518
        hash->add( cfBEu32( intentries[i].value ), new BLong( 1 ) );
×
1519
      }
1520
      for ( unsigned i = 0; i < strings_count; ++i )
×
1521
      {
1522
        PKTIN_B1::STRING_ENTRY* strentry =
×
1523
            reinterpret_cast<PKTIN_B1::STRING_ENTRY*>( msgbuf + stridx );
×
1524
        unsigned short length = cfBEu16( strentry->length );
×
1525
        stridx += offsetof( PKTIN_B1::STRING_ENTRY, data ) + length * 2;
×
1526
        if ( stridx > msglen )
×
1527
        {
1528
          SuspiciousActs::GumpResponseOverflows( client );
×
1529
          break;
×
1530
        }
1531

1532
        std::string str = Clib::tostring( cfBEu16( strentry->tag ) ) + ": " +
×
1533
                          Bscript::String::fromUTF16( strentry->data, length, true );
×
1534
        // oops we're throwing away tag!
1535
        hash->add( cfBEu16( strentry->tag ), new String( str ) );
×
1536
      }
×
1537
      uoex.ValueStack.back().set( new BObject( hash.release() ) );
×
1538
    }
×
1539
  }
1540

1541
  clear_gumphandler( client, uoemod );
1✔
1542
}
1543

1544
BObjectImp* UOExecutorModule::mf_SendTextEntryGump()
×
1545
{
1546
  Character* chr;
1547
  const String* line1;
1548
  int cancel;
1549
  int style;
1550
  int maximum;
1551
  const String* line2;
1552

1553
  if ( !( getCharacterParam( 0, chr ) && exec.getStringParam( 1, line1 ) &&
×
1554
          exec.getParam( 2, cancel ) && exec.getParam( 3, style ) && exec.getParam( 4, maximum ) &&
×
1555
          exec.getStringParam( 5, line2 ) ) )
×
1556
  {
1557
    return new BError( "Invalid parameter" );
×
1558
  }
1559
  if ( !chr->has_active_client() )
×
1560
  {
1561
    return new BError( "No client attached" );
×
1562
  }
1563

1564
  if ( chr->has_active_textentry() )
×
1565
    return new BError( "Client busy with another textentry dialog" );
×
1566

1567
  PktHelper::PacketOut<PktOut_AB> msg;
×
1568
  msg->offset += 2;
×
1569
  msg->Write<u32>( chr->serial_ext );
×
1570
  msg->offset += 2;  // u8 type,index
×
1571

1572
  std::string convertedString = Clib::strUtf8ToCp1252( line1->value() );
×
1573
  size_t numbytes = convertedString.length() + 1;
×
1574
  if ( numbytes > 256 )
×
1575
    numbytes = 256;
×
1576
  msg->WriteFlipped<u16>( numbytes );
×
1577
  msg->Write( convertedString.c_str(), static_cast<u16>( numbytes ) );  // null-terminated
×
1578

1579
  msg->Write<u8>( static_cast<u8>( cancel ) );
×
1580
  msg->Write<u8>( static_cast<u8>( style ) );
×
1581
  msg->WriteFlipped<s32>( maximum );
×
1582
  convertedString = Clib::strUtf8ToCp1252( line2->value() );
×
1583
  numbytes = convertedString.length() + 1;
×
1584
  if ( numbytes > 256 )
×
1585
    numbytes = 256;
×
1586
  msg->WriteFlipped<u16>( numbytes );
×
1587
  msg->Write( convertedString.c_str(), static_cast<u16>( numbytes ) );  // null-terminated
×
1588
  u16 len = msg->offset;
×
1589
  msg->offset = 1;
×
1590
  msg->WriteFlipped<u16>( len );
×
1591

1592
  if ( !uoexec().suspend() )
×
1593
  {
1594
    DEBUGLOGLN(
×
1595
        "Script Error in '{}' PC={}: \n"
1596
        "\tCall to function UO::SendTextEntryGump():\n"
1597
        "\tThe execution of this script can't be blocked!",
1598
        scriptname(), exec.PC );
×
1599
    return new Bscript::BError( "Script can't be blocked" );
×
1600
  }
1601

1602
  msg.Send( chr->client, len );
×
1603
  chr->client->gd->textentry_uoemod = this;
×
1604
  textentry_chr = chr;
×
1605

1606
  return new BLong( 0 );
×
1607
}
×
1608

1609
void handle_textentry( Client* client, PKTIN_AC* msg )
×
1610
{
1611
  if ( client->gd->textentry_uoemod == nullptr )
×
1612
  {
1613
    ERROR_PRINTLN( "Client (Account {}, Character {}) used out-of-sequence textentry command?",
×
1614
                   client->chr->acct->name(), client->chr->name() );
×
1615
    return;
×
1616
  }
1617
  auto& uoex = client->gd->textentry_uoemod->uoexec();
×
1618
  BObjectImp* resimp = new BLong( 0 );
×
1619
  if ( msg->retcode == PKTIN_AC::RETCODE_OKAY )
×
1620
  {
1621
    unsigned short datalen = cfBEu16( msg->datalen );
×
1622
    if ( datalen >= 1 && datalen <= 256 && msg->data[datalen - 1] == '\0' )
×
1623
    {
1624
      // dave added isprint checking 4/13/3
1625
      bool ok = true;
×
1626
      --datalen;  // don't include null terminator (already checked)
×
1627
      for ( int i = 0; i < datalen; ++i )
×
1628
      {
1629
        if ( !isprint( msg->data[i] ) )
×
1630
        {
1631
          ok = false;
×
1632
          break;
×
1633
        }
1634
      }
1635
      if ( ok )
×
1636
      {
1637
        resimp = new String( msg->data, datalen, String::Tainted::YES );
×
1638
      }
1639
    }
1640
  }
1641

1642
  uoex.ValueStack.back().set( new BObject( resimp ) );
×
1643
  uoex.revive();
×
1644
  client->gd->textentry_uoemod->textentry_chr = nullptr;
×
1645
  client->gd->textentry_uoemod = nullptr;
×
1646
}
1647

1648
class PolCore final : public PolObjectImp
1649
{
1650
public:
1651
  PolCore();
1652
  BObjectRef get_member( const char* membername ) override;
1653
  BObjectImp* call_polmethod( const char* methodname, UOExecutor& ex ) override;
1654
  BObjectImp* copy() const override;
1655
  std::string getStringRep() const override;
1656
  size_t sizeEstimate() const override { return sizeof( PolCore ); }
×
1657
  const char* typeOf() const override;
1658
  u8 typeOfInt() const override;
1659

1660
private:
1661
  // not implemented:
1662
  PolCore& operator=( const PolCore& ) = delete;
1663
};
1664

1665
PolCore::PolCore() : PolObjectImp( OTPolCoreRef ) {}
7✔
1666

1667
BObjectImp* PolCore::copy() const
×
1668
{
1669
  return new PolCore;
×
1670
}
1671

1672
std::string PolCore::getStringRep() const
×
1673
{
1674
  return "<polcore>";
×
1675
}
1676

1677
const char* PolCore::typeOf() const
×
1678
{
1679
  return "PolCoreRef";
×
1680
}
1681
u8 PolCore::typeOfInt() const
×
1682
{
1683
  return OTPolCoreRef;
×
1684
}
1685

1686
BObjectImp* GetPackageList()
×
1687
{
1688
  std::unique_ptr<ObjArray> arr( new ObjArray );
×
1689
  for ( Plib::Packages::iterator itr = Plib::systemstate.packages.begin();
×
1690
        itr != Plib::systemstate.packages.end(); ++itr )
×
1691
  {
1692
    Plib::Package* pkg = ( *itr );
×
1693
    arr->addElement( new String( pkg->name() ) );
×
1694
  }
1695
  return arr.release();
×
1696
}
×
1697

1698
void add_script( ObjArray* arr, UOExecutor* uoexec, const char* /*state*/ )
×
1699
{
1700
  arr->addElement( new ScriptExObjImp( uoexec ) );
×
1701
}
×
1702

1703
BObjectImp* GetRunningScriptList()
×
1704
{
1705
  ObjArray* arr = new ObjArray;
×
1706

1707
  const ExecList& runlist = scriptScheduler.getRunlist();
×
1708
  const ExecList& ranlist = scriptScheduler.getRanlist();
×
1709

1710
  for ( const auto& script : ranlist )
×
1711
  {
1712
    add_script( arr, script, "Running" );
×
1713
  }
1714
  for ( const auto& script : runlist )
×
1715
  {
1716
    add_script( arr, script, "Running" );
×
1717
  }
1718
  return arr;
×
1719
}
1720

1721
BObjectImp* GetAllScriptList()
×
1722
{
1723
  ObjArray* arr = new ObjArray;
×
1724

1725
  const ExecList& runlist = scriptScheduler.getRunlist();
×
1726
  const ExecList& ranlist = scriptScheduler.getRanlist();
×
1727
  const HoldList& holdlist = scriptScheduler.getHoldlist();
×
1728
  const NoTimeoutHoldList& notimeoutholdlist = scriptScheduler.getNoTimeoutHoldlist();
×
1729

1730
  for ( const auto& script : ranlist )
×
1731
  {
1732
    add_script( arr, script, "Running" );
×
1733
  }
1734
  for ( const auto& script : runlist )
×
1735
  {
1736
    add_script( arr, script, "Running" );
×
1737
  }
1738
  for ( const auto& script : holdlist )
×
1739
  {
1740
    add_script( arr, ( script ).second, "Sleeping" );
×
1741
  }
1742
  for ( const auto& script : notimeoutholdlist )
×
1743
  {
1744
    add_script( arr, script, "Sleeping" );
×
1745
  }
1746
  return arr;
×
1747
}
1748

1749
BObjectImp* GetScriptProfiles()
×
1750
{
1751
  std::unique_ptr<ObjArray> arr = std::make_unique<ObjArray>();
×
1752

1753
  u64 total_instr = 0;
×
1754
  for ( const auto& source : scriptScheduler.scrstore )
×
1755
  {
1756
    EScriptProgram* eprog = ( ( source ).second ).get();
×
1757
    total_instr += eprog->instr_cycles;
×
1758
  }
1759

1760
  for ( const auto& src : scriptScheduler.scrstore )
×
1761
  {
1762
    EScriptProgram* eprog = ( ( src ).second ).get();
×
1763

1764

1765
    std::unique_ptr<BStruct> elem = std::make_unique<BStruct>();
×
1766
    elem->addMember( "name", new String( eprog->name ) );
×
1767
    elem->addMember( "instr", new Double( static_cast<double>( eprog->instr_cycles ) ) );
×
1768
    elem->addMember( "invocations", new BLong( eprog->invocations ) );
×
1769
    u64 cycles_per_invoc = eprog->instr_cycles / ( eprog->invocations ? eprog->invocations : 1 );
×
1770
    elem->addMember( "instr_per_invoc", new Double( static_cast<double>( cycles_per_invoc ) ) );
×
1771
    double cycle_percent =
×
1772
        total_instr != 0 ? ( static_cast<double>( eprog->instr_cycles ) / total_instr * 100.0 ) : 0;
×
1773
    elem->addMember( "instr_percent", new Double( cycle_percent ) );
×
1774

1775
    arr->addElement( elem.release() );
×
1776
  }
×
1777
  return arr.release();
×
1778
}
×
1779

1780
BObjectImp* GetIoStatsObj( const IOStats& stats )
×
1781
{
1782
  std::unique_ptr<BStruct> arr( new BStruct );
×
1783

1784
  ObjArray* sent = new ObjArray;
×
1785
  arr->addMember( "sent", sent );
×
1786

1787
  ObjArray* received = new ObjArray;
×
1788
  arr->addMember( "received", received );
×
1789

1790
  for ( unsigned i = 0; i < 256; ++i )
×
1791
  {
1792
    std::unique_ptr<BStruct> elem = std::make_unique<BStruct>();
×
1793
    elem->addMember( "count", new BLong( stats.sent[i].count ) );
×
1794
    elem->addMember( "bytes", new BLong( stats.sent[i].bytes ) );
×
1795
    sent->addElement( elem.release() );
×
1796
  }
×
1797

1798
  for ( unsigned i = 0; i < 256; ++i )
×
1799
  {
1800
    std::unique_ptr<BStruct> elem( new BStruct );
×
1801
    elem->addMember( "count", new BLong( stats.received[i].count ) );
×
1802
    elem->addMember( "bytes", new BLong( stats.received[i].bytes ) );
×
1803
    received->addElement( elem.release() );
×
1804
  }
×
1805

1806
  return arr.release();
×
1807
}
×
1808

1809
BObjectImp* GetIoStats()
×
1810
{
1811
  return GetIoStatsObj( Core::networkManager.iostats );
×
1812
}
1813

1814
BObjectImp* GetQueuedIoStats()
×
1815
{
1816
  return GetIoStatsObj( Core::networkManager.queuedmode_iostats );
×
1817
}
1818

1819
BObjectImp* GetPktStatusObj()
×
1820
{
1821
  using namespace PacketWriterDefs;
1822
  std::unique_ptr<ObjArray> pkts = std::make_unique<ObjArray>();
×
1823
  PacketQueueMap* map = networkManager.packetsSingleton->getPackets();
×
1824
  for ( PacketQueueMap::iterator it = map->begin(); it != map->end(); ++it )
×
1825
  {
1826
    std::unique_ptr<BStruct> elem( new BStruct );
×
1827
    elem->addMember( "pkt", new BLong( it->first ) );
×
1828
    elem->addMember( "count", new BLong( static_cast<int>( it->second->Count() ) ) );
×
1829
    pkts->addElement( elem.release() );
×
1830
    if ( it->second->HasSubs() )
×
1831
    {
1832
      PacketInterfaceQueueMap* submap = it->second->GetSubs();
×
1833
      for ( PacketInterfaceQueueMap::iterator s_it = submap->begin(); s_it != submap->end();
×
1834
            ++s_it )
×
1835
      {
1836
        std::unique_ptr<BStruct> elemsub( new BStruct );
×
1837
        elemsub->addMember( "pkt", new BLong( it->first ) );
×
1838
        elemsub->addMember( "sub", new BLong( s_it->first ) );
×
1839
        elemsub->addMember( "count", new BLong( static_cast<int>( s_it->second.size() ) ) );
×
1840
        pkts->addElement( elemsub.release() );
×
1841
      }
×
1842
    }
1843
  }
×
1844
  return pkts.release();
×
1845
}
×
1846

1847
BObjectImp* GetCoreVariable( const char* corevar )
3✔
1848
{
1849
#define LONG_COREVAR( name, expr )      \
1850
  if ( stricmp( corevar, #name ) == 0 ) \
1851
    return new BLong( static_cast<int>( expr ) );
1852

1853
  if ( stricmp( corevar, "itemcount" ) == 0 )
3✔
1854
    return new BLong( get_toplevel_item_count() );
×
1855
  if ( stricmp( corevar, "mobilecount" ) == 0 )
3✔
1856
    return new BLong( get_mobile_count() );
×
1857

1858
  if ( stricmp( corevar, "bytes_sent" ) == 0 )
3✔
1859
    return new Double( static_cast<double>( networkManager.polstats.bytes_sent ) );
×
1860
  if ( stricmp( corevar, "bytes_received" ) == 0 )
3✔
1861
    return new Double( static_cast<double>( networkManager.polstats.bytes_received ) );
×
1862

1863
  LONG_COREVAR( uptime, polclock() / POLCLOCKS_PER_SEC );
3✔
1864
  LONG_COREVAR( sysload, stateManager.profilevars.last_sysload );
3✔
1865
  LONG_COREVAR( sysload_severity, stateManager.profilevars.last_sysload_nprocs );
3✔
1866
  //  LONG_COREVAR( bytes_sent, polstats.bytes_sent );
1867
  //  LONG_COREVAR( bytes_received, polstats.bytes_received );
1868
  LONG_COREVAR( systime, time( nullptr ) );
3✔
1869
  LONG_COREVAR( events_per_min, GET_PROFILEVAR_PER_MIN( events ) );
3✔
1870
  LONG_COREVAR( skill_checks_per_min, GET_PROFILEVAR_PER_MIN( skill_checks ) );
3✔
1871
  LONG_COREVAR( combat_operations_per_min, GET_PROFILEVAR_PER_MIN( combat_operations ) );
3✔
1872
  LONG_COREVAR( error_creations_per_min, GET_PROFILEVAR_PER_MIN( error_creations ) );
3✔
1873

1874
  LONG_COREVAR( last_character_serial, GetCurrentCharSerialNumber() );
3✔
1875
  LONG_COREVAR( last_item_serial, GetCurrentItemSerialNumber() );
3✔
1876

1877
  LONG_COREVAR( tasks_ontime_per_min, GET_PROFILEVAR_PER_MIN( tasks_ontime ) );
3✔
1878
  LONG_COREVAR( tasks_late_per_min, GET_PROFILEVAR_PER_MIN( tasks_late ) );
3✔
1879
  LONG_COREVAR( tasks_late_ticks_per_min, GET_PROFILEVAR_PER_MIN( tasks_late_ticks ) );
3✔
1880

1881
  LONG_COREVAR( scripts_late_per_min, GET_PROFILEVAR_PER_MIN( scripts_late ) );
3✔
1882
  LONG_COREVAR( scripts_ontime_per_min, GET_PROFILEVAR_PER_MIN( scripts_ontime ) );
3✔
1883

1884
  LONG_COREVAR( instr_per_min, stateManager.profilevars.last_instructions_pm );
3✔
1885
  LONG_COREVAR( priority_divide, scriptScheduler.priority_divide );
3✔
1886
  LONG_COREVAR( update_range, gamestate.max_update_range );
3✔
1887
  LONG_COREVAR( worldsaved_at, SaveContext::last_worldsave_success );
2✔
1888
  if ( stricmp( corevar, "version" ) == 0 )
×
1889
    return new String( POL_VERSION_STR );
×
1890
  if ( stricmp( corevar, "verstr" ) == 0 )
×
1891
    return new String( POL_VERSION_ID );
×
1892
  if ( stricmp( corevar, "compiledatetime" ) == 0 )
×
1893
    return new String( Clib::ProgramConfig::build_datetime() );
×
1894
  if ( stricmp( corevar, "packages" ) == 0 )
×
1895
    return GetPackageList();
×
1896
  if ( stricmp( corevar, "running_scripts" ) == 0 )
×
1897
    return GetRunningScriptList();
×
1898
  if ( stricmp( corevar, "all_scripts" ) == 0 )
×
1899
    return GetAllScriptList();
×
1900
  if ( stricmp( corevar, "script_profiles" ) == 0 )
×
1901
    return GetScriptProfiles();
×
1902
  if ( stricmp( corevar, "iostats" ) == 0 )
×
1903
    return GetIoStats();
×
1904
  if ( stricmp( corevar, "queued_iostats" ) == 0 )
×
1905
    return GetQueuedIoStats();
×
1906
  if ( stricmp( corevar, "pkt_status" ) == 0 )
×
1907
    return GetPktStatusObj();
×
1908
  if ( stricmp( corevar, "memory_usage" ) == 0 )
×
1909
    return new BLong( static_cast<int>( Clib::getCurrentMemoryUsage() / 1024 ) );
×
1910
  if ( stricmp( corevar, "poldir" ) == 0 )
×
1911
    return new String( Clib::ProgramConfig::programDir() );
×
1912

1913
  return new BError( std::string( "Unknown core variable " ) + corevar );
×
1914
}
1915

1916
BObjectRef PolCore::get_member( const char* membername )
3✔
1917
{
1918
  return BObjectRef( GetCoreVariable( membername ) );
3✔
1919
}
1920

1921
BObjectImp* PolCore::call_polmethod( const char* methodname, UOExecutor& ex )
4✔
1922
{
1923
  if ( stricmp( methodname, "log_profile" ) == 0 )
4✔
1924
  {
1925
    if ( ex.numParams() != 1 )
×
1926
      return new BError( "polcore.log_profile(clear) requires 1 parameter." );
×
1927
    int clear;
1928
    if ( ex.getParam( 0, clear ) )
×
1929
    {
1930
      log_all_script_cycle_counts( clear ? true : false );
×
1931
      return new BLong( 1 );
×
1932
    }
1933
  }
1934
  else if ( stricmp( methodname, "set_priority_divide" ) == 0 )
4✔
1935
  {
1936
    if ( ex.numParams() != 1 )
×
1937
      return new BError( "polcore.set_priority_divide(value) requires 1 parameter." );
×
1938
    int div;
1939
    if ( ex.getParam( 0, div, 1, 1000 ) )
×
1940
    {
1941
      scriptScheduler.priority_divide = div;
×
1942
      return new BLong( 1 );
×
1943
    }
1944

NEW
1945
    return nullptr;
×
1946
  }
1947
  else if ( stricmp( methodname, "clear_script_profile_counters" ) == 0 )
4✔
1948
  {
1949
    if ( ex.numParams() > 0 )
×
1950
      return new BError( "polcore.clear_script_profile_counters() doesn't take parameters." );
×
1951
    clear_script_profile_counters();
×
1952
    return new BLong( 1 );
×
1953
  }
1954
  else if ( stricmp( methodname, "internal" ) == 0 )  // Just for internal Development...
4✔
1955
  {
1956
    int type;
1957
    if ( ex.getParam( 0, type ) )
4✔
1958
    {
1959
#ifdef MEMORYLEAK
1960
      if ( type == 1 )
1961
      {
1962
        auto time_tm = Clib::localtime( time( nullptr ) );
1963
        DEBUGLOGLN( "[{:%m/%d %T}] polcore().internal", time_tm );
1964
        LEAKLOG( "{:%m/%d %T};", time_tm );
1965

1966
        bobject_alloc.log_stuff( "bobject" );
1967
        uninit_alloc.log_stuff( "uninit" );
1968
        blong_alloc.log_stuff( "blong" );
1969
        double_alloc.log_stuff( "double" );
1970
        ConfigFiles_log_stuff();
1971
        Clib::PrintHeapData();  // Will print endl in llog
1972
      }
1973
#endif
1974
#ifdef ESCRIPT_PROFILE
1975
      DEBUGLOGLN( EscriptProfiler::result() );
1976
#endif
1977
      if ( type == 2 )
4✔
1978
      {
1979
        Core::MemoryUsage::log();
1✔
1980
      }
1981
      else if ( type == 3 )
3✔
1982
      {
1983
        POLLOG_ERRORLN( "Forcing crash" );
×
1984
        int* i = nullptr;
×
1985
        *i = 1;
×
1986
      }
1987
      else if ( type == 4 )
3✔
1988
      {
1989
        POLLOG_ERRORLN( "Forcing assert crash" );
×
1990
        passert_always( false );
×
1991
      }
1992
      else if ( type == 5 )
3✔
1993
      {
1994
        Core::scriptScheduler.estimateSize( true );
1✔
1995
      }
1996
      else if ( type == 6 )
2✔
1997
      {
1998
        const String* script;
1999
        if ( !ex.getStringParam( 1, script ) )
1✔
2000
          return new BLong( 0 );
×
2001
        Core::scriptScheduler.logScriptVariables( script->data() );
1✔
2002
        return new BLong( 1 );
1✔
2003
      }
2004
      else if ( type == 7 )
1✔
2005
      {
2006
        Core::objStorageManager.objecthash.Clear( false );
1✔
2007
      }
2008
      return new BLong( 1 );
3✔
2009
    }
NEW
2010
    return new BError( "polcore.internal(value) requires 1 parameter." );
×
2011
  }
2012
  return nullptr;
×
2013
}
2014

2015
BObjectImp* UOExecutorModule::mf_POLCore()
7✔
2016
{
2017
  return new PolCore;
7✔
2018
}
2019

2020
BObjectImp* MakeMapCacheImp()
×
2021
{
2022
  ObjArray* arr = new ObjArray;
×
2023
  return arr;
×
2024
}
2025

2026

2027
BObjectImp* UOExecutorModule::mf_CreateAccount()
12✔
2028
{
2029
  const String* acctname;
2030
  const String* password;
2031
  int enabled;
2032
  if ( getStringParam( 0, acctname ) && getStringParam( 1, password ) && getParam( 2, enabled ) )
12✔
2033
  {
2034
    if ( acctname->SafeCharAmt() < acctname->length() )
12✔
2035
    {
2036
      return new BError(
2037
          "Attempted to use username in account creation with non-allowed characters." );
×
2038
    }
2039
    if ( password->SafeCharAmt() < password->length() )
12✔
2040
    {
2041
      return new BError(
2042
          "Attempted to use password in account creation with non-allowed characters." );
×
2043
    }
2044

2045
    if ( Accounts::find_account( acctname->data() ) )
12✔
2046
    {
2047
      return new BError( "Account already exists" );
×
2048
    }
2049

2050
    // Dave 6/5/3 let this function handle the hashing (Account ctor does it)
2051
    Accounts::Account* acct = Accounts::create_new_account( acctname->value(), password->value(),
12✔
2052
                                                            enabled ? true : false );  // MD5
2053

2054
    return new Accounts::AccountObjImp( Accounts::AccountPtrHolder( AccountRef( acct ) ) );
12✔
2055
  }
2056

NEW
2057
  return new BError( "Invalid parameter type" );
×
2058
}
2059

2060
BObjectImp* UOExecutorModule::mf_FindAccount()
43✔
2061
{
2062
  const String* acctname;
2063
  if ( getStringParam( 0, acctname ) )
43✔
2064
  {
2065
    Accounts::Account* acct = Accounts::find_account( acctname->data() );
43✔
2066
    if ( acct != nullptr )
43✔
2067
    {
2068
      return new Accounts::AccountObjImp( Accounts::AccountPtrHolder( AccountRef( acct ) ) );
32✔
2069
    }
2070

2071
    return new BError( "Account not found." );
11✔
2072
  }
2073

NEW
2074
  return new BError( "Invalid parameter type" );
×
2075
}
2076

2077
BObjectImp* UOExecutorModule::mf_ListAccounts()
×
2078
{
2079
  std::unique_ptr<ObjArray> arr( new ObjArray );
×
2080
  for ( unsigned idx = 0; idx < gamestate.accounts.size(); idx++ )
×
2081
  {
2082
    arr->addElement( new String( gamestate.accounts[idx]->name() ) );
×
2083
  }
2084
  return arr.release();
×
2085
}
×
2086

2087
void handle_resurrect_menu( Client* client, PKTBI_2C* msg )
×
2088
{
2089
  if ( msg->choice )
×
2090
  {
2091
    INFO_PRINTLN( "Resurrect Menu Choice: {}", int( msg->choice ) );
×
2092
    // transmit( client, msg, sizeof *msg );
2093
  }
2094

2095
  if ( client->chr != nullptr && client->gd != nullptr && client->gd->resurrect_uoemod != nullptr )
×
2096
  {
2097
    auto& uoex = client->gd->resurrect_uoemod->uoexec();
×
2098
    uoex.ValueStack.back().set( new BObject( new BLong( msg->choice ) ) );
×
2099
    uoex.revive();
×
2100
    client->gd->resurrect_uoemod->resurrect_chr = nullptr;
×
2101
    client->gd->resurrect_uoemod = nullptr;
×
2102
  }
2103
}
×
2104

2105
BObjectImp* UOExecutorModule::mf_SendInstaResDialog()
×
2106
{
2107
  Character* chr;
2108
  if ( !getCharacterParam( 0, chr ) )
×
2109
    return new BError( "Invalid parameter type" );
×
2110
  if ( !chr->has_active_client() )
×
2111
    return new BError( "No client attached" );
×
2112
  if ( chr->client->gd->resurrect_uoemod != nullptr )
×
2113
    return new BError( "Client busy with another instares dialog" );
×
2114

2115
  if ( !uoexec().suspend() )
×
2116
  {
2117
    DEBUGLOGLN(
×
2118
        "Script Error in '{}' PC={}: \n"
2119
        "\tCall to function UO::SendInstaResDialog():\n"
2120
        "\tThe execution of this script can't be blocked!",
2121
        scriptname(), exec.PC );
×
2122
    return new Bscript::BError( "Script can't be blocked" );
×
2123
  }
2124

2125
  PktHelper::PacketOut<PktOut_2C> msg;
×
2126
  msg->Write<u8>( RESURRECT_CHOICE_SELECT );
×
2127
  msg.Send( chr->client );
×
2128
  chr->client->gd->resurrect_uoemod = this;
×
2129
  resurrect_chr = chr;
×
2130

2131
  return new BLong( 0 );
×
2132
}
×
2133

2134
void handle_selcolor( Client* client, PKTBI_95* msg )
×
2135
{
2136
  if ( client->chr != nullptr && client->gd != nullptr && client->gd->selcolor_uoemod != nullptr )
×
2137
  {
2138
    auto& uoex = client->gd->selcolor_uoemod->uoexec();
×
2139
    unsigned short color = cfBEu16( msg->graphic_or_color ) & Plib::VALID_ITEM_COLOR_MASK;
×
2140
    BObject* valstack;
2141
    if ( color >= 2 && color <= 1001 )
×
2142
    {
2143
      valstack = new BObject( new BLong( color ) );
×
2144
    }
2145
    else
2146
    {
2147
      valstack = new BObject( new BError( "Client selected an out-of-range color" ) );
×
2148

2149
      // unsigned short newcolor = ((color - 2) % 1000) + 2;
2150
      POLLOG_ERRORLN( "Client #{:d} (account {}) selected an out-of-range color {:#x}",
×
2151
                      static_cast<unsigned long>( client->instance_ ),
×
2152
                      ( ( client->acct != nullptr ) ? client->acct->name() : "unknown" ), color );
×
2153
    }
2154

2155
    // client->gd->selcolor_uoemod->uoexec.ValueStack.back().set( new BObject( new BLong( color )
2156
    // )
2157
    // );
2158
    uoex.ValueStack.back().set( valstack );
×
2159
    uoex.revive();
×
2160
    client->gd->selcolor_uoemod->selcolor_chr = nullptr;
×
2161
    client->gd->selcolor_uoemod = nullptr;
×
2162
  }
2163
}
×
2164

2165

2166
BObjectImp* UOExecutorModule::mf_SelectColor()
×
2167
{
2168
  Character* chr;
2169
  Item* item;
2170
  if ( !getCharacterParam( 0, chr ) || !getItemParam( 1, item ) )
×
2171
  {
2172
    return new BError( "Invalid parameter type" );
×
2173
  }
2174
  if ( !chr->has_active_client() )
×
2175
    return new BError( "No client attached" );
×
2176
  if ( chr->client->gd->resurrect_uoemod != nullptr )
×
2177
    return new BError( "Client is already selecting a color" );
×
2178

2179
  PktHelper::PacketOut<PktOut_95> msg;
×
2180
  msg->Write<u32>( item->serial_ext );
×
2181
  msg->offset += 2;  // u16 unk
×
2182
  msg->WriteFlipped<u16>( item->graphic );
×
2183

2184
  if ( !uoexec().suspend() )
×
2185
  {
2186
    DEBUGLOGLN(
×
2187
        "Script Error in '{}' PC={}: \n"
2188
        "\tCall to function UO::SelectColor():\n"
2189
        "\tThe execution of this script can't be blocked!",
2190
        scriptname(), exec.PC );
×
2191
    return new Bscript::BError( "Script can't be blocked" );
×
2192
  }
2193

2194
  msg.Send( chr->client );
×
2195

2196
  chr->client->gd->selcolor_uoemod = this;
×
2197
  selcolor_chr = chr;
×
2198
  return new BLong( 0 );
×
2199
}
×
2200

2201
BObjectImp* UOExecutorModule::mf_SendOpenBook()
×
2202
{
2203
  Character* chr;
2204
  Item* book;
2205

2206
  if ( !( getCharacterParam( 0, chr ) && getItemParam( 1, book ) ) )
×
2207
  {
2208
    return new BError( "Invalid parameter type" );
×
2209
  }
2210
  if ( !chr->has_active_client() )
×
2211
  {
2212
    return new BError( "No active client" );
×
2213
  }
2214

2215
  bool writable = book->call_custom_method( "iswritable" )->isTrue();
×
2216
  BObject nlines_ob( book->call_custom_method( "getnumlines" ) );
×
2217
  int nlines;
2218
  if ( nlines_ob.isa( BObjectImp::OTLong ) )
×
2219
  {
2220
    BLong* blong = nlines_ob.impptr<BLong>();
×
2221
    nlines = blong->value();
×
2222
  }
2223
  else
2224
  {
2225
    return new BError( "book.GetNumLines() did not return an Integer" );
×
2226
  }
2227
  std::string title = book->call_custom_method( "gettitle" )->getStringRep();
×
2228
  std::string author = book->call_custom_method( "getauthor" )->getStringRep();
×
2229

2230
  int npages = ( nlines + 7 ) / 8;
×
2231

2232
  BObject contents_ob( UninitObject::create() );
×
2233
  if ( writable )
×
2234
  {
2235
    contents_ob.setimp( book->call_custom_method( "getcontents" ).impptr() );
×
2236
    if ( !contents_ob.isa( BObjectImp::OTArray ) )
×
2237
    {
2238
      if ( contents_ob.isa( BObjectImp::OTError ) )
×
2239
        return contents_ob->copy();
×
NEW
2240
      return new BError( "book.GetContents() must return an array" );
×
2241
    }
2242
  }
2243

2244
  PktHelper::PacketOut<PktOut_93> msg93;
×
2245
  msg93->Write<u32>( book->serial_ext );
×
2246
  msg93->Write<u8>( writable ? 1u : 0u );
×
2247
  msg93->Write<u8>( 1u );
×
2248
  msg93->WriteFlipped<u16>( static_cast<u16>( npages ) );
×
2249
  msg93->Write( title.c_str(), 60, false );
×
2250
  msg93->Write( author.c_str(), 30, false );
×
2251
  msg93.Send( chr->client );
×
2252

2253
  if ( writable )
×
2254
  {
2255
    PktHelper::PacketOut<PktOut_66> msg;
×
2256
    msg->offset += 2;
×
2257
    msg->Write<u32>( book->serial_ext );
×
2258
    msg->WriteFlipped<u16>( static_cast<u16>( npages ) );
×
2259

2260
    ObjArray* arr = contents_ob.impptr<ObjArray>();
×
2261

2262
    int linenum = 1;
×
2263
    for ( int page = 1; page <= npages; ++page )
×
2264
    {
2265
      if ( msg->offset + 4 > static_cast<int>( sizeof msg->buffer ) )
×
2266
      {
2267
        return new BError( "Buffer overflow" );
×
2268
      }
2269
      msg->WriteFlipped<u16>( static_cast<u16>( page ) );
×
2270
      u16 offset = msg->offset;
×
2271
      msg->offset += 2;
×
2272

2273
      int pagelines;
2274
      for ( pagelines = 0; pagelines < 8 && linenum <= nlines; ++pagelines, ++linenum )
×
2275
      {
2276
        const BObjectImp* line_imp = arr->imp_at( linenum );
×
2277
        std::string linetext;
×
2278
        if ( line_imp )
×
2279
          linetext = line_imp->getStringRep();  // This is UTF-8
×
2280
        if ( msg->offset + linetext.size() + 1 > sizeof msg->buffer )
×
2281
        {
2282
          return new BError( "Buffer overflow" );
×
2283
        }
2284
        msg->Write( linetext.c_str(), static_cast<u16>( linetext.size() + 1 ) );
×
2285
      }
×
2286
      u16 len = msg->offset;
×
2287
      msg->offset = offset;
×
2288
      msg->WriteFlipped<u16>( static_cast<u16>( pagelines ) );
×
2289
      msg->offset = len;
×
2290
    }
2291

2292
    /*
2293
            int linenum = 1;
2294
            for( int page = 1; page <= npages; ++page )
2295
            {
2296
            PKTBI_66_CONTENTS* ppage = reinterpret_cast<PKTBI_66_CONTENTS*>(&buffer[msglen]);
2297
            msglen += sizeof(*ppage);
2298
            if (msglen > sizeof buffer)
2299
            return new BError( "Buffer overflow" );
2300
            ppage->page = ctBEu16( page );
2301

2302
            int pagelines;
2303
            for( pagelines = 0; pagelines < 8 && linenum <= nlines; ++pagelines, ++linenum )
2304
            {
2305
            string linetext;
2306

2307
            BObjectVec params;
2308
            params.push_back( BObject(new BLong(linenum)) );
2309
            BObject line_ob = book->call_custom_method( "getline", params );
2310
            linetext = line_ob->getStringRep();
2311

2312
            char* linebuf = reinterpret_cast<char*>(&buffer[msglen]);
2313
            msglen += linetext.size()+1;
2314
            if (msglen > sizeof buffer)
2315
            return new BError( "Buffer overflow" );
2316
            memcpy( linebuf, linetext.c_str(), linetext.size()+1 );
2317
            }
2318
            ppage->lines = ctBEu16( pagelines );
2319
            }
2320
            */
2321
    u16 len = msg->offset;
×
2322
    msg->offset = 1;
×
2323
    msg->WriteFlipped<u16>( len );
×
2324
    msg.Send( chr->client, len );
×
2325
  }
×
2326

2327
  return new BLong( 1 );
×
2328
}
×
2329

2330
void read_book_page_handler( Client* client, PKTBI_66* msg )
×
2331
{
2332
  unsigned int book_serial = cfBEu32( msg->book_serial );
×
2333
  u16 page = cfBEu16( msg->page );
×
2334
  Item* book = find_legal_item( client->chr, book_serial );
×
2335
  if ( book == nullptr )
×
2336
  {
2337
    POLLOGLN( "Unable to find book {:#x} for character {:#x}", book_serial, client->chr->serial );
×
2338
    return;
×
2339
  }
2340

2341
  if ( msg->lines == 0xFFFF )
×
2342
  {
2343
    BObject nlines_ob( book->call_custom_method( "getnumlines" ) );
×
2344
    int nlines;
2345
    if ( nlines_ob.isa( BObjectImp::OTLong ) )
×
2346
    {
2347
      BLong* blong = nlines_ob.impptr<BLong>();
×
2348
      nlines = blong->value();
×
2349
    }
2350
    else
2351
    {
2352
      return;
×
2353
    }
2354

2355
    PktHelper::PacketOut<PktOut_66> msgOut;
×
2356
    msgOut->offset += 2;
×
2357
    msgOut->Write<u32>( book->serial_ext );
×
2358
    msgOut->WriteFlipped<u16>( 1u );
×
2359

2360
    int linenum = ( page - 1 ) * 8 + 1;
×
2361

2362
    msgOut->WriteFlipped<u16>( page );
×
2363
    u16 offset = msgOut->offset;
×
2364
    msgOut->offset += 2;
×
2365

2366
    int pagelines;
2367
    for ( pagelines = 0; pagelines < 8 && linenum <= nlines; ++pagelines, ++linenum )
×
2368
    {
2369
      std::string linetext;
×
2370

2371
      BObjectImpRefVec params;
×
2372
      params.push_back( ref_ptr<BObjectImp>( new BLong( linenum ) ) );
×
2373
      BObject line_ob = book->call_custom_method( "getline", params );
×
2374
      linetext = line_ob->getStringRep();  // this is UTF-8
×
2375

2376
      if ( msgOut->offset + linetext.size() + 1 > sizeof msgOut->buffer )
×
2377
      {
2378
        return;
×
2379
      }
2380
      msgOut->Write( linetext.c_str(), static_cast<u16>( linetext.size() + 1 ) );
×
2381
    }
×
2382

2383
    u16 len = msgOut->offset;
×
2384
    msgOut->offset = offset;
×
2385
    msgOut->WriteFlipped<u16>( static_cast<u16>( pagelines ) );
×
2386
    msgOut->offset = 1;
×
2387
    msgOut->WriteFlipped<u16>( len );
×
2388
    msgOut.Send( client, len );
×
2389
  }
×
2390
  else
2391
  {
2392
    size_t msglen = cfBEu16( msg->msglen );
×
2393
    const char* ptext = msg->text;
×
2394
    size_t bytesleft = msglen - offsetof( PKTBI_66, text );
×
2395
    for ( int i = 1; i <= 8; ++i )
×
2396
    {
2397
      std::string line;
×
2398
      while ( bytesleft )
×
2399
      {
2400
        --bytesleft;
×
2401
        if ( *ptext )
×
2402
        {
2403
          line.append( 1, *ptext );
×
2404
          ++ptext;
×
2405
        }
2406
        else
2407
        {
2408
          ++ptext;  // skip null terminator
×
2409
          break;
×
2410
        }
2411
      }
2412

2413
      BObjectImpRefVec params;
×
2414
      params.push_back( ref_ptr<BObjectImp>( new BLong( ( page - 1 ) * 8 + i ) ) );
×
2415
      params.push_back( ref_ptr<BObjectImp>( new String( line ) ) );
×
2416

2417
      BObject page_on = book->call_custom_method( "setline", params );
×
2418
    }
×
2419
  }
2420
}
2421

2422
char strip_ctrl_chars( char c )
×
2423
{
2424
  if ( c < 0x20 )
×
2425
    return 0x20;
×
NEW
2426
  return c;
×
2427
}
2428

2429
void open_book_handler( Client* client, PKTBI_93* msg )
×
2430
{
2431
  // fdump( stdout, msg, sizeof *msg );
2432

2433
  // string title( msg->title, sizeof msg->title );
2434
  // string author( msg->author, sizeof msg->author );
2435

2436
  // Dave changed this 12/19 from sizeof msg->title. The protocol defines garbage after the
2437
  // terminator for
2438
  // the title and author strings, so we were writing this garbage into save files. This caused
2439
  // some
2440
  //"No SERIAL property" bugs, because the parser barfed on the bad characters.
2441
  std::string title( msg->title, strlen( msg->title ) );
×
2442
  std::string author( msg->author, strlen( msg->author ) );
×
2443

2444
  // dave 1/20/3 cheaters insert cntrl chars into books causing severe problems
2445
  std::transform( title.begin(), title.end(), title.begin(), strip_ctrl_chars );
×
2446
  std::transform( author.begin(), author.end(), author.begin(), strip_ctrl_chars );
×
2447

2448

2449
  unsigned int book_serial = cfBEu32( msg->serial );
×
2450
  Item* book = find_legal_item( client->chr, book_serial );
×
2451
  if ( book == nullptr )
×
2452
  {
2453
    POLLOGLN( "Unable to find book {:#x} for character {:#x}", book_serial, client->chr->serial );
×
2454
    return;
×
2455
  }
2456
  BObjectImpRefVec params;
×
2457
  params.push_back( ref_ptr<BObjectImp>( new String( title ) ) );
×
2458
  book->call_custom_method( "settitle", params );
×
2459

2460
  params[0].set( new String( author ) );
×
2461
  book->call_custom_method( "setauthor", params );
×
2462
}
×
2463

2464
BObjectImp* UOExecutorModule::mf_SendHousingTool()
×
2465
{
2466
  Character* chr;
2467
  Multi::UMulti* multi;
2468

2469
  if ( !( getCharacterParam( 0, chr ) && getMultiParam( 1, multi ) ) )
×
2470
  {
2471
    return new BError( "Invalid parameter type" );
×
2472
  }
2473
  if ( !chr->client->acctSupports( Plib::ExpansionVersion::AOS ) )
×
2474
    return new BError( "Charater does not have AOS enabled." );
×
2475

2476
  if ( multi == nullptr )
×
2477
    return new BError( "House not found." );
×
2478

2479
  Multi::UHouse* house = multi->as_house();
×
2480
  if ( house == nullptr )
×
2481
    return new BError( "Not a House multi." );
×
2482

2483
  if ( !house->IsCustom() )
×
2484
    return new BError( "Not a Custom House." );
×
2485

2486
  if ( house->editing )
×
2487
    return new BError( "House currently being customized." );
×
2488

2489
  if ( house->IsWaitingForAccept() )
×
2490
    return new BError( "House currently being waiting for a commit" );
×
2491

2492
  if ( chr->realm()->find_supporting_multi( chr->pos3d() ) != house )
×
2493
    return new BError( "You must be inside the house to customize it." );
×
2494

2495
  chr->client->gd->custom_house_serial = house->serial;
×
2496
  chr->client->gd->custom_house_chrserial = chr->serial;
×
2497

2498
  {
2499
    PktHelper::PacketOut<PktOut_BF_Sub20> msg;
×
2500
    msg->WriteFlipped<u16>( 17u );
×
2501
    msg->offset += 2;  // sub
×
2502
    msg->Write<u32>( house->serial_ext );
×
2503
    msg->Write<u8>( 0x4u );          // begin
×
2504
    msg->offset += 2;                // u16 unk2 FIXME what's the meaning
×
2505
    msg->Write<u32>( 0xFFFFFFFFu );  // fixme
×
2506
    msg->Write<u8>( 0xFFu );         // fixme
×
2507
    msg.Send( chr->client );
×
2508
  }
×
2509
  Core::Pos4d newpos = house->pos() + Core::Vec3d( 0, 0, 7 );
×
2510
  move_character_to( chr, newpos, MOVEITEM_FORCELOCATION );
×
2511

2512
  house->WorkingDesign.AddComponents( house );
×
2513
  house->CurrentDesign.AddComponents( house );
×
2514
  house->WorkingDesign.ClearComponents( house );
×
2515
  Multi::ItemList itemlist;
×
2516
  Multi::MobileList moblist;
×
2517
  Multi::UHouse::list_contents( house, itemlist, moblist );
×
2518
  const Multi::MultiDef& def = house->multidef();
×
2519
  while ( !itemlist.empty() )
×
2520
  {
2521
    Item* item = itemlist.front();
×
2522
    send_remove_object_if_inrange( chr->client, item );
×
2523
    itemlist.pop_front();
×
2524
  }
2525

2526
  while ( !moblist.empty() )
×
2527
  {
2528
    Character* multichr = moblist.back();
×
2529
    if ( multichr != chr )
×
2530
    {
2531
      Core::Pos4d pos = house->pos() + Core::Vec2d( def.minrxyz.x(), def.maxrxyz.y() + 1 );
×
2532
      move_character_to( multichr, pos, MOVEITEM_FORCELOCATION );
×
2533
    }
2534
    moblist.pop_back();
×
2535
  }
2536

2537
  house->editing = true;
×
2538
  house->editing_floor_num = 1;
×
2539
  CustomHousesSendFull( house, chr->client, Multi::HOUSE_DESIGN_WORKING );
×
2540

2541
  return new BLong( 1 );
×
2542
}
×
2543

2544
BObjectImp* UOExecutorModule::mf_SendCharacterRaceChanger( /* Character */ )
×
2545
{
2546
  Character* chr;
2547
  if ( getCharacterParam( 0, chr ) )
×
2548
  {
2549
    PktHelper::PacketOut<PktOut_BF_Sub2A> msg;
×
2550
    msg->WriteFlipped<u16>( 7u );
×
2551
    msg->offset += 2;  // sub
×
2552
    msg->Write<u8>( chr->gender );
×
2553
    msg->Write<u8>( chr->race + 1u );
×
2554
    msg.Send( chr->client );
×
2555
    return new BLong( 1 );
×
2556
  }
×
NEW
2557
  return new BError( "Invalid parameter" );
×
2558
}
2559

2560

2561
void character_race_changer_handler( Client* client, PKTBI_BF* msg )
×
2562
{
2563
  Item* tmpitem;
2564

2565
  if ( msg->msglen == ctBEu16( 5 ) )
×
2566
    return;
×
2567

2568
  if ( ( msg->characterracechanger.result.BodyHue == 0 ) &&
×
2569
       ( msg->characterracechanger.result.HairId == 0 ) &&
×
2570
       ( msg->characterracechanger.result.HairHue == 0 ) &&
×
2571
       ( msg->characterracechanger.result.BeardId == 0 ) &&
×
2572
       ( msg->characterracechanger.result.BeardHue == 0 ) )
×
2573
    return;
×
2574

2575
  client->chr->setcolor( cfBEu16( msg->characterracechanger.result.BodyHue ) | 0x8000 );
×
2576
  client->chr->truecolor = client->chr->color;
×
2577

2578
  client->chr->on_color_changed();
×
2579

2580
  // Create Hair
2581
  if ( client->chr->layer_is_equipped( LAYER_HAIR ) )
×
2582
    destroy_item( client->chr->wornitem( LAYER_HAIR ) );
×
2583

2584
  if ( validhair( cfBEu16( msg->characterracechanger.result.HairId ) ) )
×
2585
  {
2586
    tmpitem = Item::create( cfBEu16( msg->characterracechanger.result.HairId ) );
×
2587
    tmpitem->layer = LAYER_HAIR;
×
2588
    tmpitem->color = cfBEu16( msg->characterracechanger.result.HairHue );
×
2589
    client->chr->equip( tmpitem );
×
2590
    send_wornitem_to_inrange( client->chr, tmpitem );
×
2591
  }
2592

2593
  // Create Beard
2594
  if ( client->chr->layer_is_equipped( LAYER_BEARD ) )
×
2595
    destroy_item( client->chr->wornitem( LAYER_BEARD ) );
×
2596

2597
  if ( validbeard( cfBEu16( msg->characterracechanger.result.BeardId ) ) )
×
2598
  {
2599
    tmpitem = Item::create( cfBEu16( msg->characterracechanger.result.BeardId ) );
×
2600
    tmpitem->layer = LAYER_BEARD;
×
2601
    tmpitem->color = cfBEu16( msg->characterracechanger.result.BeardHue );
×
2602
    client->chr->equip( tmpitem );
×
2603
    send_wornitem_to_inrange( client->chr, tmpitem );
×
2604
  }
2605
}
2606

2607
// Called when selection made or when selection canceled with nullptr parameters
2608
void popup_menu_selection_made( Network::Client* client, u32 serial, u16 id )
×
2609
{
2610
  if ( client == nullptr )
×
2611
    return;
×
2612

2613
  Character* chr = client->chr;
×
2614
  if ( chr == nullptr || chr->client->gd->popup_menu_selection_uoemod == nullptr )
×
2615
    return;
×
2616

2617
  // The function sending the PopUp menu is responsible to set this
2618
  passert_always_r(
×
2619
      chr->client->gd->popup_menu_selection_uoemod->popup_menu_selection_above != nullptr,
2620
      "Bug in handling PopUp menu selection. Please report this on the forums." );
2621

2622
  auto& uoex = chr->client->gd->popup_menu_selection_uoemod->uoexec();
×
2623

2624
  if ( id && serial )
×
2625
  {
2626
    if ( chr->client->gd->popup_menu_selection_uoemod->popup_menu_selection_above->serial ==
×
2627
         serial )
2628
      uoex.ValueStack.back().set( new BObject( new BLong( id ) ) );
×
2629
    else
2630
      POLLOG_INFO( "{}/{} send an unexpected popup reply for {:#x}.\n", client->acct->name(),
×
2631
                   client->chr->name(), serial );
×
2632
  }
2633

2634
  uoex.revive();
×
2635
  chr->client->gd->popup_menu_selection_uoemod->popup_menu_selection_chr = nullptr;
×
2636
  chr->client->gd->popup_menu_selection_uoemod->popup_menu_selection_above = nullptr;
×
2637
  chr->client->gd->popup_menu_selection_uoemod = nullptr;
×
2638
}
2639

2640
/// Sends a PopUp/Context menu
2641
BObjectImp* UOExecutorModule::mf_SendPopUpMenu()
×
2642
{
2643
  Character* chr;
2644
  UObject* above;
2645
  ObjArray* menu_arr;
2646
  if ( !( getCharacterParam( 0, chr ) && getUObjectParam( 1, above ) &&
×
2647
          exec.getObjArrayParam( 2, menu_arr ) ) )
×
2648
  {
2649
    return new BError( "Invalid parameter" );
×
2650
  }
2651
  if ( !chr->has_active_client() )
×
2652
    return new BError( "No client attached" );
×
2653
  if ( menu_arr->ref_arr.empty() )
×
2654
    return new BError( "Can't send empty menu" );
×
2655
  if ( menu_arr->ref_arr.size() > 0xfffe )
×
2656
    return new BError( "Too many entries in menu" );
×
2657

2658
  // Prepare packet
2659
  struct Entry
2660
  {
2661
    u32 cliloc = 0;
2662
    u16 flags = 0;
2663
    u16 color = 0;
2664
  };
2665
  std::vector<Entry> entries;
×
2666
  bool newformat = false;
×
2667
  if ( chr->client->ClientType & CLIENTTYPE_UOKR )
×
2668
    newformat = true;
×
2669

2670
  for ( u16 i = 0; i < menu_arr->ref_arr.size(); ++i )
×
2671
  {
2672
    BObject* bo = menu_arr->ref_arr[i].get();
×
2673
    if ( bo == nullptr )
×
2674
      continue;
×
2675
    BObjectImp* imp = bo->impptr();
×
2676

2677
    if ( entries.size() >= 255 )  // overflow
×
2678
      return new BError( "Too many entries in menu" );
×
2679
    Entry entry;
×
2680
    if ( auto* lng = impptrIf<BLong>( imp ) )
×
2681
    {
2682
      // Short form: menu is just an int
2683
      entry.cliloc = lng->value();
×
2684
    }
2685
    else if ( auto* elem = impptrIf<BStruct>( imp ) )
×
2686
    {
2687
      // Full form: menu is a struct
2688

2689
      BObjectImp* cl = const_cast<BObjectImp*>( elem->FindMember( "cliloc" ) );
×
2690
      if ( cl == nullptr )
×
2691
        return new BError( "Missing cliloc for menu element" );
×
2692
      if ( auto* lngcliloc = impptrIf<BLong>( cl ) )
×
2693
        entry.cliloc = lngcliloc->value();
×
2694
      else
2695
        return new BError( "Invalid cliloc for menu element" );
×
2696

2697
      const BObjectImp* ds = elem->FindMember( "disabled" );
×
2698
      if ( ds != nullptr && ds->isTrue() )
×
2699
        entry.flags |= PKTBI_BF_14_ENTRIES::POPUP_MENU_LOCKED;
×
2700

2701
      const BObjectImp* ar = elem->FindMember( "arrow" );
×
2702
      if ( ar != nullptr && ar->isTrue() )
×
2703
        entry.flags |= PKTBI_BF_14_ENTRIES::POPUP_MENU_ARROW;
×
2704

2705
      BObjectImp* co = const_cast<BObjectImp*>( elem->FindMember( "color" ) );
×
2706
      if ( auto* colng = impptrIf<BLong>( co ) )
×
2707
      {
2708
        entry.color = static_cast<u16>( colng->value() );
×
2709
        entry.flags |= PKTBI_BF_14_ENTRIES::POPUP_MENU_COLOR;
×
2710
      }
2711
    }
2712
    else
2713
      return new BError( "Menu elements must be int or struct" );
×
2714

2715
    if ( !newformat && ( entry.cliloc < 3000000 || entry.cliloc > 3065535 ) )
×
2716
    {
2717
      if ( chr->client->ClientType & CLIENTTYPE_6017 )
×
2718
        newformat = true;
×
2719
      else
2720
        return new BError( "Cliloc out of range in menu" );
×
2721
    }
2722
    entries.push_back( entry );
×
2723
  }
2724

2725
  PktHelper::PacketOut<PktOut_BF_Sub14> msg;
×
2726
  msg->offset += 4;
×
2727
  msg->Write<u8>( 0u );                   // unknown
×
2728
  msg->Write<u8>( newformat ? 2u : 1u );  // 1=2D, 2=KR
×
2729
  msg->Write<u32>( above->serial_ext );   // Above serial
×
2730
  msg->Write<u8>( static_cast<u8>( entries.size() ) );
×
2731
  for ( u16 i = 0; i < entries.size(); ++i )
×
2732
  {
2733
    auto& e = entries[i];
×
2734
    if ( newformat )
×
2735
    {
2736
      msg->WriteFlipped<u32>( e.cliloc );
×
2737
      msg->WriteFlipped<u16>( i + 1u );
×
2738
      if ( e.flags & PKTBI_BF_14_ENTRIES::POPUP_MENU_COLOR )  // new format does not support color
×
2739
        e.flags &= ~PKTBI_BF_14_ENTRIES::POPUP_MENU_COLOR;
×
2740
      msg->WriteFlipped<u16>( e.flags );
×
2741
    }
2742
    else
2743
    {
2744
      msg->WriteFlipped<u16>( i + 1u );                                  // Menu element ID
×
2745
      msg->WriteFlipped<u16>( static_cast<u16>( e.cliloc - 3000000 ) );  // Cliloc ID, adjusted
×
2746
      msg->WriteFlipped<u16>( e.flags );                                 // Flags
×
2747
      if ( e.flags & PKTBI_BF_14_ENTRIES::POPUP_MENU_COLOR )
×
2748
        msg->WriteFlipped<u16>( e.color );
×
2749
    }
2750
  }
2751

2752
  // Add lengths and send
2753
  u16 len = msg->offset;
×
2754
  msg->offset = 1;
×
2755
  msg->WriteFlipped<u16>( len );
×
2756
  msg.Send( chr->client, len );
×
2757

2758
  // Cancel any previously waiting popup response
2759
  if ( auto& old = chr->client->gd->popup_menu_selection_uoemod; old != nullptr )
×
2760
  {
2761
    // reset uomod members otherwise the deconstructor will reset the next request
2762
    old->popup_menu_selection_chr = nullptr;
×
2763
    old->popup_menu_selection_above = nullptr;
×
2764
    old->uoexec().revive();
×
2765

2766
    old = nullptr;
×
2767
    chr->client->gd->on_popup_menu_selection = nullptr;
×
2768
  }
2769

2770
  // Suspend the script first
2771
  if ( !uoexec().suspend() )
×
2772
  {
2773
    DEBUGLOGLN(
×
2774
        "Script Error in '{}' PC={}: \n"
2775
        "\tCall to function UO::SendPopupMenu():\n"
2776
        "\tThe execution of this script can't be blocked!",
2777
        scriptname(), exec.PC );
×
2778
    return new Bscript::BError( "Script can't be blocked" );
×
2779
  }
2780

2781
  // Prepare to restart the script once the response arrives
2782
  chr->client->gd->on_popup_menu_selection = popup_menu_selection_made;
×
2783
  chr->client->gd->popup_menu_selection_uoemod = this;
×
2784
  popup_menu_selection_chr = chr;
×
2785
  popup_menu_selection_above = above;
×
2786

2787
  return new BLong( 0 );
×
2788
}
×
2789

2790
BObjectImp* UOExecutorModule::mf_SingleClick()
×
2791
{
2792
  Character* chr = nullptr;
×
2793
  UObject* what = nullptr;
×
2794

2795
  if ( !getCharacterParam( 0, chr ) || !getUObjectParam( 1, what ) )
×
2796
    return new BError( "Invalid parameter" );
×
2797

2798
  if ( !chr->has_active_client() )
×
2799
    return new BError( "Mobile has no active client" );
×
2800

2801
  // If it got here, clear any errors from getUObjectParam/getCharacterParam
2802
  exec.setFunctionResult( nullptr );
×
2803

2804
  singleclick( chr->client, what->serial );
×
2805
  return new BLong( 1 );
×
2806
}
2807

2808
BObjectImp* UOExecutorModule::mf_ListStaticsNearLocationOfType(
×
2809
    /* x, y, z, range, objtype, flags, realm */ )
2810
{
2811
  Core::Pos2d pos;
×
2812
  int z, flags;
2813
  short range;
2814
  unsigned int objtype;
2815
  Realms::Realm* realm;
2816

2817
  if ( getRealmParam( 6, &realm ) && getPos2dParam( 0, 1, &pos, realm ) && getParam( 2, z ) &&
×
2818
       getParam( 3, range ) && getObjtypeParam( 4, objtype ) && getParam( 5, flags ) )
×
2819
  {
2820
    std::unique_ptr<ObjArray> newarr( new ObjArray );
×
2821
    Core::Vec2d radius( range, range );
×
2822
    Core::Range2d area( pos - radius, pos + radius, realm );
×
2823
    for ( const auto& tile : area )
×
2824
    {
2825
      if ( !( flags & ITEMS_IGNORE_STATICS ) )
×
2826
      {
2827
        Plib::StaticEntryList slist;
×
2828
        realm->getstatics( slist, tile );
×
2829

2830
        for ( unsigned i = 0; i < slist.size(); ++i )
×
2831
        {
2832
          if ( slist[i].objtype != objtype )
×
2833
            continue;
×
2834
          if ( ( z == LIST_IGNORE_Z ) || ( abs( slist[i].z - z ) < CONST_DEFAULT_ZRANGE ) )
×
2835
          {
2836
            std::unique_ptr<BStruct> arr( new BStruct );
×
2837
            arr->addMember( "x", new BLong( tile.x() ) );
×
2838
            arr->addMember( "y", new BLong( tile.y() ) );
×
2839
            arr->addMember( "z", new BLong( slist[i].z ) );
×
2840
            arr->addMember( "objtype", new BLong( slist[i].objtype ) );
×
2841
            arr->addMember( "hue", new BLong( slist[i].hue ) );
×
2842
            newarr->addElement( arr.release() );
×
2843
          }
×
2844
        }
2845
      }
×
2846

2847
      if ( !( flags & ITEMS_IGNORE_MULTIS ) )
×
2848
      {
2849
        Plib::StaticList mlist;
×
2850
        realm->readmultis( mlist, tile );
×
2851
        for ( unsigned i = 0; i < mlist.size(); ++i )
×
2852
        {
2853
          if ( mlist[i].graphic != objtype )
×
2854
            continue;
×
2855
          if ( ( z == LIST_IGNORE_Z ) || ( abs( mlist[i].z - z ) < CONST_DEFAULT_ZRANGE ) )
×
2856
          {
2857
            std::unique_ptr<BStruct> arr( new BStruct );
×
2858
            arr->addMember( "x", new BLong( tile.x() ) );
×
2859
            arr->addMember( "y", new BLong( tile.y() ) );
×
2860
            arr->addMember( "z", new BLong( mlist[i].z ) );
×
2861
            arr->addMember( "objtype", new BLong( mlist[i].graphic ) );
×
2862
            newarr->addElement( arr.release() );
×
2863
          }
×
2864
        }
2865
      }
×
2866
    }
2867

2868
    return newarr.release();
×
2869
  }
×
NEW
2870
  return new BError( "Invalid parameter" );
×
2871
}
2872

2873

2874
BObjectImp* UOExecutorModule::mf_ListStaticsNearLocationWithFlag(
×
2875
    /* x, y, z, range, flags, realm */ )
2876
{
2877
  Core::Pos2d pos;
×
2878
  int z, flags;
2879
  short range;
2880
  Realms::Realm* realm;
2881

2882
  if ( getRealmParam( 5, &realm ) && getPos2dParam( 0, 1, &pos, realm ) && getParam( 2, z ) &&
×
2883
       getParam( 3, range ) && getParam( 4, flags ) )
×
2884
  {
2885
    std::unique_ptr<ObjArray> newarr( new ObjArray );
×
2886
    Core::Vec2d radius( range, range );
×
2887
    Core::Range2d area( pos - radius, pos + radius, realm );
×
2888
    for ( const auto& tile : area )
×
2889
    {
2890
      if ( !( flags & ITEMS_IGNORE_STATICS ) )
×
2891
      {
2892
        Plib::StaticEntryList slist;
×
2893
        realm->getstatics( slist, tile );
×
2894

2895
        for ( unsigned i = 0; i < slist.size(); ++i )
×
2896
        {
2897
          if ( ( Plib::tile_uoflags( slist[i].objtype ) & flags ) )
×
2898
          {
2899
            if ( ( z == LIST_IGNORE_Z ) || ( abs( slist[i].z - z ) < CONST_DEFAULT_ZRANGE ) )
×
2900
            {
2901
              std::unique_ptr<BStruct> arr( new BStruct );
×
2902
              arr->addMember( "x", new BLong( tile.x() ) );
×
2903
              arr->addMember( "y", new BLong( tile.y() ) );
×
2904
              arr->addMember( "z", new BLong( slist[i].z ) );
×
2905
              arr->addMember( "objtype", new BLong( slist[i].objtype ) );
×
2906
              arr->addMember( "hue", new BLong( slist[i].hue ) );
×
2907
              newarr->addElement( arr.release() );
×
2908
            }
×
2909
          }
2910
        }
2911
      }
×
2912

2913
      if ( !( flags & ITEMS_IGNORE_MULTIS ) )
×
2914
      {
2915
        Plib::StaticList mlist;
×
2916
        realm->readmultis( mlist, tile );
×
2917
        for ( unsigned i = 0; i < mlist.size(); ++i )
×
2918
        {
2919
          if ( ( Plib::tile_uoflags( mlist[i].graphic ) & flags ) )
×
2920
          {
2921
            if ( ( z == LIST_IGNORE_Z ) || ( abs( mlist[i].z - z ) < CONST_DEFAULT_ZRANGE ) )
×
2922
            {
2923
              std::unique_ptr<BStruct> arr( new BStruct );
×
2924
              arr->addMember( "x", new BLong( tile.x() ) );
×
2925
              arr->addMember( "y", new BLong( tile.y() ) );
×
2926
              arr->addMember( "z", new BLong( mlist[i].z ) );
×
2927
              arr->addMember( "objtype", new BLong( mlist[i].graphic ) );
×
2928
              newarr->addElement( arr.release() );
×
2929
            }
×
2930
          }
2931
        }
2932
      }
×
2933
    }
2934

2935
    return newarr.release();
×
2936
  }
×
NEW
2937
  return new BError( "Invalid parameter" );
×
2938
}
2939
}  // namespace Module
2940
}  // 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