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

polserver / polserver / 21541532363

31 Jan 2026 08:14AM UTC coverage: 60.532% (+0.03%) from 60.507%
21541532363

push

github

web-flow
Tidy modernize for loops (#862)

* trigger loop convert

* Automated clang-tidy change: modernize-loop-convert

* fixed refactor

* Automated clang-tidy change: modernize-loop-convert

* compile

* first look through

* fixes and start to use a few ranges

* revert autogenerated file

* compilation fix

* second pass

* renamed loop variable

---------

Co-authored-by: Clang Tidy <clang-tidy@users.noreply.github.com>

164 of 447 new or added lines in 61 files covered. (36.69%)

6 existing lines in 5 files now uncovered.

44377 of 73312 relevant lines covered (60.53%)

499857.83 hits per line

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

822
    unsigned int buyprice;
823

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

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

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

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

887
  client->pause();
×
888

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

891
  send_clear_vendorwindow( client, vendor );
×
892

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

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

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

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

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

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

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

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

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

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

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

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

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

1015

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

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

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

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

1061
  pos = msg->offset;
×
1062

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

1069
  u16 numlines = 0;
×
NEW
1070
  for ( auto& data : data_arr->ref_arr )
×
1071
  {
NEW
1072
    BObject* bo = data.get();
×
1073
    if ( bo == nullptr )
×
1074
      continue;
×
1075
    BObjectImp* imp = bo->impptr();
×
1076
    std::string s = imp->getStringRep();
×
1077

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

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

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

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

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

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

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

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

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

1134
  u32 layoutdlen = 0;
1✔
1135

1136
  for ( auto& layout : layout_arr->ref_arr )
3✔
1137
  {
1138
    BObject* bo = layout.get();
2✔
1139
    if ( bo == nullptr )
2✔
1140
      continue;
×
1141
    BObjectImp* imp = bo->impptr();
2✔
1142
    std::string s = imp->getStringRep();
2✔
1143

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

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

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

1179
  bfr->offset = 0;
1✔
1180

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

1184
  for ( auto& data : data_arr->ref_arr )
3✔
1185
  {
1186
    BObject* bo = data.get();
2✔
1187
    if ( bo == nullptr )
2✔
1188
      continue;
×
1189
    BObjectImp* imp = bo->impptr();
2✔
1190
    std::string s = imp->getStringRep();
2✔
1191

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1369
  msg.Send( client );
×
1370

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

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

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

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

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

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

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

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

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

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

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

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

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

1450

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1685
BObjectImp* GetPackageList()
×
1686
{
1687
  std::unique_ptr<ObjArray> arr( new ObjArray );
×
NEW
1688
  for ( auto pkg : Plib::systemstate.packages )
×
1689
  {
1690
    arr->addElement( new String( pkg->name() ) );
×
1691
  }
1692
  return arr.release();
×
1693
}
×
1694

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

1700
BObjectImp* GetRunningScriptList()
×
1701
{
1702
  ObjArray* arr = new ObjArray;
×
1703

1704
  const ExecList& runlist = scriptScheduler.getRunlist();
×
1705
  const ExecList& ranlist = scriptScheduler.getRanlist();
×
1706

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

1718
BObjectImp* GetAllScriptList()
×
1719
{
1720
  ObjArray* arr = new ObjArray;
×
1721

1722
  const ExecList& runlist = scriptScheduler.getRunlist();
×
1723
  const ExecList& ranlist = scriptScheduler.getRanlist();
×
1724
  const HoldList& holdlist = scriptScheduler.getHoldlist();
×
1725
  const NoTimeoutHoldList& notimeoutholdlist = scriptScheduler.getNoTimeoutHoldlist();
×
1726

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

1746
BObjectImp* GetScriptProfiles()
×
1747
{
1748
  std::unique_ptr<ObjArray> arr = std::make_unique<ObjArray>();
×
1749

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

1757
  for ( const auto& src : scriptScheduler.scrstore )
×
1758
  {
1759
    EScriptProgram* eprog = ( ( src ).second ).get();
×
1760

1761

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

1772
    arr->addElement( elem.release() );
×
1773
  }
×
1774
  return arr.release();
×
1775
}
×
1776

1777
BObjectImp* GetIoStatsObj( const IOStats& stats )
×
1778
{
NEW
1779
  auto arr = std::make_unique<BStruct>();
×
1780

1781
  ObjArray* sent = new ObjArray;
×
1782
  arr->addMember( "sent", sent );
×
1783

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

NEW
1787
  for ( const auto& pkt : stats.sent )
×
1788
  {
NEW
1789
    auto elem = std::make_unique<BStruct>();
×
NEW
1790
    elem->addMember( "count", new BLong( pkt.count ) );
×
NEW
1791
    elem->addMember( "bytes", new BLong( pkt.bytes ) );
×
1792
    sent->addElement( elem.release() );
×
1793
  }
×
1794

NEW
1795
  for ( const auto& pkt : stats.received )
×
1796
  {
NEW
1797
    auto elem = std::make_unique<BStruct>();
×
NEW
1798
    elem->addMember( "count", new BLong( pkt.count ) );
×
NEW
1799
    elem->addMember( "bytes", new BLong( pkt.bytes ) );
×
1800
    received->addElement( elem.release() );
×
1801
  }
×
1802

1803
  return arr.release();
×
1804
}
×
1805

1806
BObjectImp* GetIoStats()
×
1807
{
1808
  return GetIoStatsObj( Core::networkManager.iostats );
×
1809
}
1810

1811
BObjectImp* GetQueuedIoStats()
×
1812
{
1813
  return GetIoStatsObj( Core::networkManager.queuedmode_iostats );
×
1814
}
1815

1816
BObjectImp* GetPktStatusObj()
×
1817
{
1818
  using namespace PacketWriterDefs;
1819
  std::unique_ptr<ObjArray> pkts = std::make_unique<ObjArray>();
×
1820
  PacketQueueMap* map = networkManager.packetsSingleton->getPackets();
×
NEW
1821
  for ( const auto& [id, queue] : *map )
×
1822
  {
1823
    std::unique_ptr<BStruct> elem( new BStruct );
×
NEW
1824
    elem->addMember( "pkt", new BLong( id ) );
×
NEW
1825
    elem->addMember( "count", new BLong( static_cast<int>( queue->Count() ) ) );
×
1826
    pkts->addElement( elem.release() );
×
NEW
1827
    if ( queue->HasSubs() )
×
1828
    {
NEW
1829
      PacketInterfaceQueueMap* submap = queue->GetSubs();
×
NEW
1830
      for ( const auto& [subid, subqueue] : *submap )
×
1831
      {
1832
        std::unique_ptr<BStruct> elemsub( new BStruct );
×
NEW
1833
        elemsub->addMember( "pkt", new BLong( id ) );
×
NEW
1834
        elemsub->addMember( "sub", new BLong( subid ) );
×
NEW
1835
        elemsub->addMember( "count", new BLong( static_cast<int>( subqueue.size() ) ) );
×
1836
        pkts->addElement( elemsub.release() );
×
1837
      }
×
1838
    }
1839
  }
×
1840
  return pkts.release();
×
1841
}
×
1842

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

1849
  if ( stricmp( corevar, "itemcount" ) == 0 )
3✔
1850
    return new BLong( get_toplevel_item_count() );
×
1851
  if ( stricmp( corevar, "mobilecount" ) == 0 )
3✔
1852
    return new BLong( get_mobile_count() );
×
1853

1854
  if ( stricmp( corevar, "bytes_sent" ) == 0 )
3✔
1855
    return new Double( static_cast<double>( networkManager.polstats.bytes_sent ) );
×
1856
  if ( stricmp( corevar, "bytes_received" ) == 0 )
3✔
1857
    return new Double( static_cast<double>( networkManager.polstats.bytes_received ) );
×
1858

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

1870
  LONG_COREVAR( last_character_serial, GetCurrentCharSerialNumber() );
3✔
1871
  LONG_COREVAR( last_item_serial, GetCurrentItemSerialNumber() );
3✔
1872

1873
  LONG_COREVAR( tasks_ontime_per_min, GET_PROFILEVAR_PER_MIN( tasks_ontime ) );
3✔
1874
  LONG_COREVAR( tasks_late_per_min, GET_PROFILEVAR_PER_MIN( tasks_late ) );
3✔
1875
  LONG_COREVAR( tasks_late_ticks_per_min, GET_PROFILEVAR_PER_MIN( tasks_late_ticks ) );
3✔
1876

1877
  LONG_COREVAR( scripts_late_per_min, GET_PROFILEVAR_PER_MIN( scripts_late ) );
3✔
1878
  LONG_COREVAR( scripts_ontime_per_min, GET_PROFILEVAR_PER_MIN( scripts_ontime ) );
3✔
1879

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

1909
  return new BError( std::string( "Unknown core variable " ) + corevar );
×
1910
}
1911

1912
BObjectRef PolCore::get_member( const char* membername )
3✔
1913
{
1914
  return BObjectRef( GetCoreVariable( membername ) );
3✔
1915
}
1916

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

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

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

2011
BObjectImp* UOExecutorModule::mf_POLCore()
7✔
2012
{
2013
  return new PolCore;
7✔
2014
}
2015

2016
BObjectImp* MakeMapCacheImp()
×
2017
{
2018
  ObjArray* arr = new ObjArray;
×
2019
  return arr;
×
2020
}
2021

2022

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

2041
    if ( Accounts::find_account( acctname->data() ) )
12✔
2042
    {
2043
      return new BError( "Account already exists" );
×
2044
    }
2045

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

2050
    return new Accounts::AccountObjImp( Accounts::AccountPtrHolder( AccountRef( acct ) ) );
12✔
2051
  }
2052

2053
  return new BError( "Invalid parameter type" );
×
2054
}
2055

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

2067
    return new BError( "Account not found." );
11✔
2068
  }
2069

2070
  return new BError( "Invalid parameter type" );
×
2071
}
2072

2073
BObjectImp* UOExecutorModule::mf_ListAccounts()
×
2074
{
2075
  std::unique_ptr<ObjArray> arr( new ObjArray );
×
NEW
2076
  for ( const auto& account : gamestate.accounts )
×
2077
  {
NEW
2078
    arr->addElement( new String( account->name() ) );
×
2079
  }
2080
  return arr.release();
×
2081
}
×
2082

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

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

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

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

2121
  PktHelper::PacketOut<PktOut_2C> msg;
×
2122
  msg->Write<u8>( RESURRECT_CHOICE_SELECT );
×
2123
  msg.Send( chr->client );
×
2124
  chr->client->gd->resurrect_uoemod = this;
×
2125
  resurrect_chr = chr;
×
2126

2127
  return new BLong( 0 );
×
2128
}
×
2129

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

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

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

2161

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

2175
  PktHelper::PacketOut<PktOut_95> msg;
×
2176
  msg->Write<u32>( item->serial_ext );
×
2177
  msg->offset += 2;  // u16 unk
×
2178
  msg->WriteFlipped<u16>( item->graphic );
×
2179

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

2190
  msg.Send( chr->client );
×
2191

2192
  chr->client->gd->selcolor_uoemod = this;
×
2193
  selcolor_chr = chr;
×
2194
  return new BLong( 0 );
×
2195
}
×
2196

2197
BObjectImp* UOExecutorModule::mf_SendOpenBook()
×
2198
{
2199
  Character* chr;
2200
  Item* book;
2201

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

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

2226
  int npages = ( nlines + 7 ) / 8;
×
2227

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

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

2249
  if ( writable )
×
2250
  {
2251
    PktHelper::PacketOut<PktOut_66> msg;
×
2252
    msg->offset += 2;
×
2253
    msg->Write<u32>( book->serial_ext );
×
2254
    msg->WriteFlipped<u16>( static_cast<u16>( npages ) );
×
2255

2256
    ObjArray* arr = contents_ob.impptr<ObjArray>();
×
2257

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

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

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

2298
            int pagelines;
2299
            for( pagelines = 0; pagelines < 8 && linenum <= nlines; ++pagelines, ++linenum )
2300
            {
2301
            string linetext;
2302

2303
            BObjectVec params;
2304
            params.push_back( BObject(new BLong(linenum)) );
2305
            BObject line_ob = book->call_custom_method( "getline", params );
2306
            linetext = line_ob->getStringRep();
2307

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

2323
  return new BLong( 1 );
×
2324
}
×
2325

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

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

2351
    PktHelper::PacketOut<PktOut_66> msgOut;
×
2352
    msgOut->offset += 2;
×
2353
    msgOut->Write<u32>( book->serial_ext );
×
2354
    msgOut->WriteFlipped<u16>( 1u );
×
2355

2356
    int linenum = ( page - 1 ) * 8 + 1;
×
2357

2358
    msgOut->WriteFlipped<u16>( page );
×
2359
    u16 offset = msgOut->offset;
×
2360
    msgOut->offset += 2;
×
2361

2362
    int pagelines;
2363
    for ( pagelines = 0; pagelines < 8 && linenum <= nlines; ++pagelines, ++linenum )
×
2364
    {
2365
      std::string linetext;
×
2366

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

2372
      if ( msgOut->offset + linetext.size() + 1 > sizeof msgOut->buffer )
×
2373
      {
2374
        return;
×
2375
      }
2376
      msgOut->Write( linetext.c_str(), static_cast<u16>( linetext.size() + 1 ) );
×
2377
    }
×
2378

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

2409
      BObjectImpRefVec params;
×
2410
      params.push_back( ref_ptr<BObjectImp>( new BLong( ( page - 1 ) * 8 + i ) ) );
×
2411
      params.push_back( ref_ptr<BObjectImp>( new String( line ) ) );
×
2412

2413
      BObject page_on = book->call_custom_method( "setline", params );
×
2414
    }
×
2415
  }
2416
}
2417

2418
char strip_ctrl_chars( char c )
×
2419
{
2420
  if ( c < 0x20 )
×
2421
    return 0x20;
×
2422
  return c;
×
2423
}
2424

2425
void open_book_handler( Client* client, PKTBI_93* msg )
×
2426
{
2427
  // fdump( stdout, msg, sizeof *msg );
2428

2429
  // string title( msg->title, sizeof msg->title );
2430
  // string author( msg->author, sizeof msg->author );
2431

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

2440
  // dave 1/20/3 cheaters insert cntrl chars into books causing severe problems
2441
  std::transform( title.begin(), title.end(), title.begin(), strip_ctrl_chars );
×
2442
  std::transform( author.begin(), author.end(), author.begin(), strip_ctrl_chars );
×
2443

2444

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

2456
  params[0].set( new String( author ) );
×
2457
  book->call_custom_method( "setauthor", params );
×
2458
}
×
2459

2460
BObjectImp* UOExecutorModule::mf_SendHousingTool()
×
2461
{
2462
  Character* chr;
2463
  Multi::UMulti* multi;
2464

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

2472
  if ( multi == nullptr )
×
2473
    return new BError( "House not found." );
×
2474

2475
  Multi::UHouse* house = multi->as_house();
×
2476
  if ( house == nullptr )
×
2477
    return new BError( "Not a House multi." );
×
2478

2479
  if ( !house->IsCustom() )
×
2480
    return new BError( "Not a Custom House." );
×
2481

2482
  if ( house->editing )
×
2483
    return new BError( "House currently being customized." );
×
2484

2485
  if ( house->IsWaitingForAccept() )
×
2486
    return new BError( "House currently being waiting for a commit" );
×
2487

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

2491
  chr->client->gd->custom_house_serial = house->serial;
×
2492
  chr->client->gd->custom_house_chrserial = chr->serial;
×
2493

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

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

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

2533
  house->editing = true;
×
2534
  house->editing_floor_num = 1;
×
2535
  CustomHousesSendFull( house, chr->client, Multi::HOUSE_DESIGN_WORKING );
×
2536

2537
  return new BLong( 1 );
×
2538
}
×
2539

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

2556

2557
void character_race_changer_handler( Client* client, PKTBI_BF* msg )
×
2558
{
2559
  Item* tmpitem;
2560

2561
  if ( msg->msglen == ctBEu16( 5 ) )
×
2562
    return;
×
2563

2564
  if ( ( msg->characterracechanger.result.BodyHue == 0 ) &&
×
2565
       ( msg->characterracechanger.result.HairId == 0 ) &&
×
2566
       ( msg->characterracechanger.result.HairHue == 0 ) &&
×
2567
       ( msg->characterracechanger.result.BeardId == 0 ) &&
×
2568
       ( msg->characterracechanger.result.BeardHue == 0 ) )
×
2569
    return;
×
2570

2571
  client->chr->setcolor( cfBEu16( msg->characterracechanger.result.BodyHue ) | 0x8000 );
×
2572
  client->chr->truecolor = client->chr->color;
×
2573

2574
  client->chr->on_color_changed();
×
2575

2576
  // Create Hair
2577
  if ( client->chr->layer_is_equipped( LAYER_HAIR ) )
×
2578
    destroy_item( client->chr->wornitem( LAYER_HAIR ) );
×
2579

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

2589
  // Create Beard
2590
  if ( client->chr->layer_is_equipped( LAYER_BEARD ) )
×
2591
    destroy_item( client->chr->wornitem( LAYER_BEARD ) );
×
2592

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

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

2609
  Character* chr = client->chr;
×
2610
  if ( chr == nullptr || chr->client->gd->popup_menu_selection_uoemod == nullptr )
×
2611
    return;
×
2612

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

2618
  auto& uoex = chr->client->gd->popup_menu_selection_uoemod->uoexec();
×
2619

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

2630
  uoex.revive();
×
2631
  chr->client->gd->popup_menu_selection_uoemod->popup_menu_selection_chr = nullptr;
×
2632
  chr->client->gd->popup_menu_selection_uoemod->popup_menu_selection_above = nullptr;
×
2633
  chr->client->gd->popup_menu_selection_uoemod = nullptr;
×
2634
}
2635

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

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

NEW
2666
  for ( auto& mentry : menu_arr->ref_arr )
×
2667
  {
NEW
2668
    BObject* bo = mentry.get();
×
2669
    if ( bo == nullptr )
×
2670
      continue;
×
2671
    BObjectImp* imp = bo->impptr();
×
2672

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

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

2693
      const BObjectImp* ds = elem->FindMember( "disabled" );
×
2694
      if ( ds != nullptr && ds->isTrue() )
×
2695
        entry.flags |= PKTBI_BF_14_ENTRIES::POPUP_MENU_LOCKED;
×
2696

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

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

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

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

2748
  // Add lengths and send
2749
  u16 len = msg->offset;
×
2750
  msg->offset = 1;
×
2751
  msg->WriteFlipped<u16>( len );
×
2752
  msg.Send( chr->client, len );
×
2753

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

2762
    old = nullptr;
×
2763
    chr->client->gd->on_popup_menu_selection = nullptr;
×
2764
  }
2765

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

2777
  // Prepare to restart the script once the response arrives
2778
  chr->client->gd->on_popup_menu_selection = popup_menu_selection_made;
×
2779
  chr->client->gd->popup_menu_selection_uoemod = this;
×
2780
  popup_menu_selection_chr = chr;
×
2781
  popup_menu_selection_above = above;
×
2782

2783
  return new BLong( 0 );
×
2784
}
×
2785

2786
BObjectImp* UOExecutorModule::mf_SingleClick()
×
2787
{
2788
  Character* chr = nullptr;
×
2789
  UObject* what = nullptr;
×
2790

2791
  if ( !getCharacterParam( 0, chr ) || !getUObjectParam( 1, what ) )
×
2792
    return new BError( "Invalid parameter" );
×
2793

2794
  if ( !chr->has_active_client() )
×
2795
    return new BError( "Mobile has no active client" );
×
2796

2797
  // If it got here, clear any errors from getUObjectParam/getCharacterParam
2798
  exec.setFunctionResult( nullptr );
×
2799

2800
  singleclick( chr->client, what->serial );
×
2801
  return new BLong( 1 );
×
2802
}
2803

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

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

NEW
2826
        for ( const auto& entry : slist )
×
2827
        {
NEW
2828
          if ( entry.objtype != objtype )
×
2829
            continue;
×
NEW
2830
          if ( ( z == LIST_IGNORE_Z ) || ( abs( entry.z - z ) < CONST_DEFAULT_ZRANGE ) )
×
2831
          {
2832
            std::unique_ptr<BStruct> arr( new BStruct );
×
2833
            arr->addMember( "x", new BLong( tile.x() ) );
×
2834
            arr->addMember( "y", new BLong( tile.y() ) );
×
NEW
2835
            arr->addMember( "z", new BLong( entry.z ) );
×
NEW
2836
            arr->addMember( "objtype", new BLong( entry.objtype ) );
×
NEW
2837
            arr->addMember( "hue", new BLong( entry.hue ) );
×
2838
            newarr->addElement( arr.release() );
×
2839
          }
×
2840
        }
2841
      }
×
2842

2843
      if ( !( flags & ITEMS_IGNORE_MULTIS ) )
×
2844
      {
2845
        Plib::StaticList mlist;
×
2846
        realm->readmultis( mlist, tile );
×
NEW
2847
        for ( const auto& entry : mlist )
×
2848
        {
NEW
2849
          if ( entry.graphic != objtype )
×
2850
            continue;
×
NEW
2851
          if ( ( z == LIST_IGNORE_Z ) || ( abs( entry.z - z ) < CONST_DEFAULT_ZRANGE ) )
×
2852
          {
2853
            std::unique_ptr<BStruct> arr( new BStruct );
×
2854
            arr->addMember( "x", new BLong( tile.x() ) );
×
2855
            arr->addMember( "y", new BLong( tile.y() ) );
×
NEW
2856
            arr->addMember( "z", new BLong( entry.z ) );
×
NEW
2857
            arr->addMember( "objtype", new BLong( entry.graphic ) );
×
2858
            newarr->addElement( arr.release() );
×
2859
          }
×
2860
        }
2861
      }
×
2862
    }
2863

2864
    return newarr.release();
×
2865
  }
×
2866
  return new BError( "Invalid parameter" );
×
2867
}
2868

2869

2870
BObjectImp* UOExecutorModule::mf_ListStaticsNearLocationWithFlag(
×
2871
    /* x, y, z, range, flags, realm */ )
2872
{
2873
  Core::Pos2d pos;
×
2874
  int z, flags;
2875
  short range;
2876
  Realms::Realm* realm;
2877

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

NEW
2891
        for ( const auto& entry : slist )
×
2892
        {
NEW
2893
          if ( ( Plib::tile_uoflags( entry.objtype ) & flags ) )
×
2894
          {
NEW
2895
            if ( ( z == LIST_IGNORE_Z ) || ( abs( entry.z - z ) < CONST_DEFAULT_ZRANGE ) )
×
2896
            {
2897
              std::unique_ptr<BStruct> arr( new BStruct );
×
2898
              arr->addMember( "x", new BLong( tile.x() ) );
×
2899
              arr->addMember( "y", new BLong( tile.y() ) );
×
NEW
2900
              arr->addMember( "z", new BLong( entry.z ) );
×
NEW
2901
              arr->addMember( "objtype", new BLong( entry.objtype ) );
×
NEW
2902
              arr->addMember( "hue", new BLong( entry.hue ) );
×
2903
              newarr->addElement( arr.release() );
×
2904
            }
×
2905
          }
2906
        }
2907
      }
×
2908

2909
      if ( !( flags & ITEMS_IGNORE_MULTIS ) )
×
2910
      {
2911
        Plib::StaticList mlist;
×
2912
        realm->readmultis( mlist, tile );
×
NEW
2913
        for ( const auto& entry : mlist )
×
2914
        {
NEW
2915
          if ( ( Plib::tile_uoflags( entry.graphic ) & flags ) )
×
2916
          {
NEW
2917
            if ( ( z == LIST_IGNORE_Z ) || ( abs( entry.z - z ) < CONST_DEFAULT_ZRANGE ) )
×
2918
            {
2919
              std::unique_ptr<BStruct> arr( new BStruct );
×
2920
              arr->addMember( "x", new BLong( tile.x() ) );
×
2921
              arr->addMember( "y", new BLong( tile.y() ) );
×
NEW
2922
              arr->addMember( "z", new BLong( entry.z ) );
×
NEW
2923
              arr->addMember( "objtype", new BLong( entry.graphic ) );
×
2924
              newarr->addElement( arr.release() );
×
2925
            }
×
2926
          }
2927
        }
2928
      }
×
2929
    }
2930

2931
    return newarr.release();
×
2932
  }
×
2933
  return new BError( "Invalid parameter" );
×
2934
}
2935
}  // namespace Module
2936
}  // 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