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

polserver / polserver / 21066490217

16 Jan 2026 12:22PM UTC coverage: 60.507%. Remained the same
21066490217

push

github

web-flow
misc clang-tidy (#853)

* trigger tidy

* Automated clang-tidy change: modernize-use-equals-delete,modernize-make-shared,modernize-make-unique,modernize-use-constraints,readability-container-size-empty,modernize-redundant-void-arg,modernize-use-emplace

* removed non needed macros

* missed to disable tidy build

---------

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

174 of 248 new or added lines in 74 files covered. (70.16%)

2 existing lines in 2 files now uncovered.

44459 of 73477 relevant lines covered (60.51%)

515895.79 hits per line

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

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

28

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

33
#include "pol_global_config.h"
34

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

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

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

110

111
#include <zlib.h>
112

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

128
#define CONST_DEFAULT_ZRANGE 19
129

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

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

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

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

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

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

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

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

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

259

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

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

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

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

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

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

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

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

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

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

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

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

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

317

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

407
  unsigned int amount_spent = 0;
×
408

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

823
    unsigned int buyprice;
824

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

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

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

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

888
  client->pause();
×
889

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

892
  send_clear_vendorwindow( client, vendor );
×
893

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

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

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

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

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

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

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

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

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

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

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

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

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

1017

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

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

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

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

1063
  pos = msg->offset;
×
1064

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

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

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

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

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

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

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

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

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

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

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

1136
  u32 layoutdlen = 0;
1✔
1137

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

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

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

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

1181
  bfr->offset = 0;
1✔
1182

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

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

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

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

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

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

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

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

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

1263
private:
1264
  typedef std::map<int, BObjectRef> Contents;
1265
  Contents contents_;
1266

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

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

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

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

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

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

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

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

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

1311
    return BObjectRef( obj.impptr() );
×
1312
  }
×
1313
  else
1314
  {
1315
    return BObjectRef( new BError( "member not found" ) );
×
1316
  }
1317
}
1318

1319
BObjectRef BIntHash::OperSubscript( const BObject& obj )
×
1320
{
1321
  const BObjectImp& objimp = obj.impref();
×
1322
  if ( objimp.isa( OTLong ) )
×
1323
  {
1324
    const BLong& lng = static_cast<const BLong&>( objimp );
×
1325
    Contents::iterator itr = contents_.find( lng.value() );
×
1326
    if ( itr != contents_.end() )
×
1327
    {
1328
      BObjectRef& oref = ( *itr ).second;
×
1329
      return BObjectRef( oref.get()->impptr() );
×
1330
    }
1331
    else
1332
    {
1333
      return BObjectRef( new BError( "Key not found in inthash" ) );
×
1334
    }
1335
  }
1336
  else
1337
  {
1338
    return BObjectRef( new BError( "Incorrect type used as subscript to inthash" ) );
×
1339
  }
1340
}
1341

1342
void clear_gumphandler( Client* client, UOExecutorModule* uoemod )
1✔
1343
{
1344
  uoemod->uoexec().revive();
1✔
1345
  uoemod->gump_chr = nullptr;
1✔
1346
  client->gd->remove_gumpmods( uoemod );
1✔
1347
}
1✔
1348

1349
BObjectImp* UOExecutorModule::mf_CloseGump( /* who, pid, response := 0 */ )
×
1350
{
1351
  Character* chr;
1352
  unsigned int pid;
1353
  BObjectImp* resp;
1354

1355
  if ( !( getCharacterParam( 0, chr ) && exec.getParam( 1, pid ) && ( getParamImp( 2, resp ) ) ) )
×
1356
  {
1357
    return new BError( "Invalid parameter" );
×
1358
  }
1359

1360
  if ( !chr->has_active_client() )
×
1361
    return new BError( "No client attached" );
×
1362

1363
  Client* client = chr->client;
×
1364

1365
  UOExecutorModule* uoemod = client->gd->find_gumpmod( pid );
×
1366
  if ( uoemod == nullptr )
×
1367
  {
1368
    return new BError( "Couldnt find script" );
×
1369
  }
1370

1371
  PktHelper::PacketOut<PktOut_BF_Sub4> msg;
×
1372
  msg->WriteFlipped<u16>( 13u );
×
1373
  msg->offset += 2;
×
1374
  msg->WriteFlipped<u32>( pid );
×
1375
  msg->offset += 4;  // buttonid
×
1376

1377
  msg.Send( client );
×
1378

1379
  uoemod->uoexec().ValueStack.back().set( new BObject( resp ) );
×
1380
  clear_gumphandler( client, uoemod );
×
1381

1382
  return new BLong( 1 );
×
1383
}
×
1384

1385
BObjectImp* UOExecutorModule::mf_CloseWindow( /* chr, type, obj */ )
×
1386
{
1387
  Character* chr;
1388
  unsigned int type;
1389
  UObject* obj;
1390

1391
  if ( !getCharacterParam( 0, chr ) || !getParam( 1, type ) || !getUObjectParam( 2, obj ) )
×
1392
    return new BError( "Invalid parameter" );
×
1393

1394
  if ( !chr->has_active_client() )
×
1395
    return new BError( "No client attached" );
×
1396

1397
  if ( type == PKTBI_BF_16::CONTAINER )
×
1398
  {
1399
    if ( !obj->script_isa( POLCLASS_CONTAINER ) )
×
1400
      return new BError( "Invalid object, has to be a containerRef" );
×
1401
  }
1402
  else if ( type == PKTBI_BF_16::PAPERDOLL || type == PKTBI_BF_16::STATUS ||
×
1403
            type == PKTBI_BF_16::CHARPROFILE )
×
1404
  {
1405
    if ( !obj->script_isa( POLCLASS_MOBILE ) )
×
1406
      return new BError( "Invalid object, has to be a mobRef" );
×
1407
  }
1408
  else
1409
    return new BError( "Invalid type" );
×
1410

1411
  PktHelper::PacketOut<PktOut_BF_Sub16> msg;
×
1412
  msg->WriteFlipped<u16>( 13u );
×
1413
  msg->offset += 2;  // sub
×
1414
  msg->WriteFlipped<u32>( type );
×
1415
  msg->Write<u32>( obj->serial_ext );
×
1416

1417
  msg.Send( chr->client );
×
1418

1419
  return new BLong( 1 );
×
1420
}
×
1421

1422
void gumpbutton_handler( Client* client, PKTIN_B1* msg )
1✔
1423
{
1424
  const u32 VIRTUE_GUMP_ID = 0x1CD;
1✔
1425

1426
  char* msgbuf = reinterpret_cast<char*>( msg );
1✔
1427
  PKTIN_B1::HEADER* hdr = reinterpret_cast<PKTIN_B1::HEADER*>( msg );
1✔
1428
  unsigned short msglen = cfBEu16( hdr->msglen );
1✔
1429

1430
  u32 gumpid = cfBEu32( hdr->dialogid );
1✔
1431
  u32 buttonid = cfBEu32( hdr->buttonid );
1✔
1432

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

1458

1459
  UOExecutorModule* uoemod = client->gd->find_gumpmod( gumpid );
1✔
1460
  if ( uoemod == nullptr )
1✔
1461
  {
1462
    SuspiciousActs::GumpResponseWasUnexpected( client, gumpid, buttonid );
×
1463
    return;
×
1464
  }
1465

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

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

1539
        std::string str = Clib::tostring( cfBEu16( strentry->tag ) ) + ": " +
×
1540
                          Bscript::String::fromUTF16( strentry->data, length, true );
×
1541
        // oops we're throwing away tag!
1542
        hash->add( cfBEu16( strentry->tag ), new String( str ) );
×
1543
      }
×
1544
      uoex.ValueStack.back().set( new BObject( hash.release() ) );
×
1545
    }
×
1546
  }
1547

1548
  clear_gumphandler( client, uoemod );
1✔
1549
}
1550

1551
BObjectImp* UOExecutorModule::mf_SendTextEntryGump()
×
1552
{
1553
  Character* chr;
1554
  const String* line1;
1555
  int cancel;
1556
  int style;
1557
  int maximum;
1558
  const String* line2;
1559

1560
  if ( !( getCharacterParam( 0, chr ) && exec.getStringParam( 1, line1 ) &&
×
1561
          exec.getParam( 2, cancel ) && exec.getParam( 3, style ) && exec.getParam( 4, maximum ) &&
×
1562
          exec.getStringParam( 5, line2 ) ) )
×
1563
  {
1564
    return new BError( "Invalid parameter" );
×
1565
  }
1566
  if ( !chr->has_active_client() )
×
1567
  {
1568
    return new BError( "No client attached" );
×
1569
  }
1570

1571
  if ( chr->has_active_textentry() )
×
1572
    return new BError( "Client busy with another textentry dialog" );
×
1573

1574
  PktHelper::PacketOut<PktOut_AB> msg;
×
1575
  msg->offset += 2;
×
1576
  msg->Write<u32>( chr->serial_ext );
×
1577
  msg->offset += 2;  // u8 type,index
×
1578

1579
  std::string convertedString = Clib::strUtf8ToCp1252( line1->value() );
×
1580
  size_t numbytes = convertedString.length() + 1;
×
1581
  if ( numbytes > 256 )
×
1582
    numbytes = 256;
×
1583
  msg->WriteFlipped<u16>( numbytes );
×
1584
  msg->Write( convertedString.c_str(), static_cast<u16>( numbytes ) );  // null-terminated
×
1585

1586
  msg->Write<u8>( static_cast<u8>( cancel ) );
×
1587
  msg->Write<u8>( static_cast<u8>( style ) );
×
1588
  msg->WriteFlipped<s32>( maximum );
×
1589
  convertedString = Clib::strUtf8ToCp1252( line2->value() );
×
1590
  numbytes = convertedString.length() + 1;
×
1591
  if ( numbytes > 256 )
×
1592
    numbytes = 256;
×
1593
  msg->WriteFlipped<u16>( numbytes );
×
1594
  msg->Write( convertedString.c_str(), static_cast<u16>( numbytes ) );  // null-terminated
×
1595
  u16 len = msg->offset;
×
1596
  msg->offset = 1;
×
1597
  msg->WriteFlipped<u16>( len );
×
1598

1599
  if ( !uoexec().suspend() )
×
1600
  {
1601
    DEBUGLOGLN(
×
1602
        "Script Error in '{}' PC={}: \n"
1603
        "\tCall to function UO::SendTextEntryGump():\n"
1604
        "\tThe execution of this script can't be blocked!",
1605
        scriptname(), exec.PC );
×
1606
    return new Bscript::BError( "Script can't be blocked" );
×
1607
  }
1608

1609
  msg.Send( chr->client, len );
×
1610
  chr->client->gd->textentry_uoemod = this;
×
1611
  textentry_chr = chr;
×
1612

1613
  return new BLong( 0 );
×
1614
}
×
1615

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

1649
  uoex.ValueStack.back().set( new BObject( resimp ) );
×
1650
  uoex.revive();
×
1651
  client->gd->textentry_uoemod->textentry_chr = nullptr;
×
1652
  client->gd->textentry_uoemod = nullptr;
×
1653
}
1654

1655
class PolCore final : public PolObjectImp
1656
{
1657
public:
1658
  PolCore();
1659
  BObjectRef get_member( const char* membername ) override;
1660
  BObjectImp* call_polmethod( const char* methodname, UOExecutor& ex ) override;
1661
  BObjectImp* copy() const override;
1662
  std::string getStringRep() const override;
1663
  size_t sizeEstimate() const override { return sizeof( PolCore ); }
×
1664
  const char* typeOf() const override;
1665
  u8 typeOfInt() const override;
1666

1667
private:
1668
  // not implemented:
1669
  PolCore& operator=( const PolCore& ) = delete;
1670
};
1671

1672
PolCore::PolCore() : PolObjectImp( OTPolCoreRef ) {}
7✔
1673

1674
BObjectImp* PolCore::copy() const
×
1675
{
1676
  return new PolCore;
×
1677
}
1678

1679
std::string PolCore::getStringRep() const
×
1680
{
1681
  return "<polcore>";
×
1682
}
1683

1684
const char* PolCore::typeOf() const
×
1685
{
1686
  return "PolCoreRef";
×
1687
}
1688
u8 PolCore::typeOfInt() const
×
1689
{
1690
  return OTPolCoreRef;
×
1691
}
1692

1693
BObjectImp* GetPackageList()
×
1694
{
1695
  std::unique_ptr<ObjArray> arr( new ObjArray );
×
1696
  for ( Plib::Packages::iterator itr = Plib::systemstate.packages.begin();
×
1697
        itr != Plib::systemstate.packages.end(); ++itr )
×
1698
  {
1699
    Plib::Package* pkg = ( *itr );
×
1700
    arr->addElement( new String( pkg->name() ) );
×
1701
  }
1702
  return arr.release();
×
1703
}
×
1704

1705
void add_script( ObjArray* arr, UOExecutor* uoexec, const char* /*state*/ )
×
1706
{
1707
  arr->addElement( new ScriptExObjImp( uoexec ) );
×
1708
}
×
1709

1710
BObjectImp* GetRunningScriptList()
×
1711
{
1712
  ObjArray* arr = new ObjArray;
×
1713

1714
  const ExecList& runlist = scriptScheduler.getRunlist();
×
1715
  const ExecList& ranlist = scriptScheduler.getRanlist();
×
1716

1717
  for ( const auto& script : ranlist )
×
1718
  {
1719
    add_script( arr, script, "Running" );
×
1720
  }
1721
  for ( const auto& script : runlist )
×
1722
  {
1723
    add_script( arr, script, "Running" );
×
1724
  }
1725
  return arr;
×
1726
}
1727

1728
BObjectImp* GetAllScriptList()
×
1729
{
1730
  ObjArray* arr = new ObjArray;
×
1731

1732
  const ExecList& runlist = scriptScheduler.getRunlist();
×
1733
  const ExecList& ranlist = scriptScheduler.getRanlist();
×
1734
  const HoldList& holdlist = scriptScheduler.getHoldlist();
×
1735
  const NoTimeoutHoldList& notimeoutholdlist = scriptScheduler.getNoTimeoutHoldlist();
×
1736

1737
  for ( const auto& script : ranlist )
×
1738
  {
1739
    add_script( arr, script, "Running" );
×
1740
  }
1741
  for ( const auto& script : runlist )
×
1742
  {
1743
    add_script( arr, script, "Running" );
×
1744
  }
1745
  for ( const auto& script : holdlist )
×
1746
  {
1747
    add_script( arr, ( script ).second, "Sleeping" );
×
1748
  }
1749
  for ( const auto& script : notimeoutholdlist )
×
1750
  {
1751
    add_script( arr, script, "Sleeping" );
×
1752
  }
1753
  return arr;
×
1754
}
1755

1756
BObjectImp* GetScriptProfiles()
×
1757
{
1758
  std::unique_ptr<ObjArray> arr = std::make_unique<ObjArray>();
×
1759

1760
  u64 total_instr = 0;
×
1761
  for ( const auto& source : scriptScheduler.scrstore )
×
1762
  {
1763
    EScriptProgram* eprog = ( ( source ).second ).get();
×
1764
    total_instr += eprog->instr_cycles;
×
1765
  }
1766

1767
  for ( const auto& src : scriptScheduler.scrstore )
×
1768
  {
1769
    EScriptProgram* eprog = ( ( src ).second ).get();
×
1770

1771

1772
    std::unique_ptr<BStruct> elem = std::make_unique<BStruct>();
×
1773
    elem->addMember( "name", new String( eprog->name ) );
×
1774
    elem->addMember( "instr", new Double( static_cast<double>( eprog->instr_cycles ) ) );
×
1775
    elem->addMember( "invocations", new BLong( eprog->invocations ) );
×
1776
    u64 cycles_per_invoc = eprog->instr_cycles / ( eprog->invocations ? eprog->invocations : 1 );
×
1777
    elem->addMember( "instr_per_invoc", new Double( static_cast<double>( cycles_per_invoc ) ) );
×
1778
    double cycle_percent =
×
1779
        total_instr != 0 ? ( static_cast<double>( eprog->instr_cycles ) / total_instr * 100.0 ) : 0;
×
1780
    elem->addMember( "instr_percent", new Double( cycle_percent ) );
×
1781

1782
    arr->addElement( elem.release() );
×
1783
  }
×
1784
  return arr.release();
×
1785
}
×
1786

1787
BObjectImp* GetIoStatsObj( const IOStats& stats )
×
1788
{
1789
  std::unique_ptr<BStruct> arr( new BStruct );
×
1790

1791
  ObjArray* sent = new ObjArray;
×
1792
  arr->addMember( "sent", sent );
×
1793

1794
  ObjArray* received = new ObjArray;
×
1795
  arr->addMember( "received", received );
×
1796

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

1805
  for ( unsigned i = 0; i < 256; ++i )
×
1806
  {
1807
    std::unique_ptr<BStruct> elem( new BStruct );
×
1808
    elem->addMember( "count", new BLong( stats.received[i].count ) );
×
1809
    elem->addMember( "bytes", new BLong( stats.received[i].bytes ) );
×
1810
    received->addElement( elem.release() );
×
1811
  }
×
1812

1813
  return arr.release();
×
1814
}
×
1815

1816
BObjectImp* GetIoStats()
×
1817
{
1818
  return GetIoStatsObj( Core::networkManager.iostats );
×
1819
}
1820

1821
BObjectImp* GetQueuedIoStats()
×
1822
{
1823
  return GetIoStatsObj( Core::networkManager.queuedmode_iostats );
×
1824
}
1825

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

1854
BObjectImp* GetCoreVariable( const char* corevar )
3✔
1855
{
1856
#define LONG_COREVAR( name, expr )      \
1857
  if ( stricmp( corevar, #name ) == 0 ) \
1858
    return new BLong( static_cast<int>( expr ) );
1859

1860
  if ( stricmp( corevar, "itemcount" ) == 0 )
3✔
1861
    return new BLong( get_toplevel_item_count() );
×
1862
  if ( stricmp( corevar, "mobilecount" ) == 0 )
3✔
1863
    return new BLong( get_mobile_count() );
×
1864

1865
  if ( stricmp( corevar, "bytes_sent" ) == 0 )
3✔
1866
    return new Double( static_cast<double>( networkManager.polstats.bytes_sent ) );
×
1867
  if ( stricmp( corevar, "bytes_received" ) == 0 )
3✔
1868
    return new Double( static_cast<double>( networkManager.polstats.bytes_received ) );
×
1869

1870
  LONG_COREVAR( uptime, polclock() / POLCLOCKS_PER_SEC );
3✔
1871
  LONG_COREVAR( sysload, stateManager.profilevars.last_sysload );
3✔
1872
  LONG_COREVAR( sysload_severity, stateManager.profilevars.last_sysload_nprocs );
3✔
1873
  //  LONG_COREVAR( bytes_sent, polstats.bytes_sent );
1874
  //  LONG_COREVAR( bytes_received, polstats.bytes_received );
1875
  LONG_COREVAR( systime, time( nullptr ) );
3✔
1876
  LONG_COREVAR( events_per_min, GET_PROFILEVAR_PER_MIN( events ) );
3✔
1877
  LONG_COREVAR( skill_checks_per_min, GET_PROFILEVAR_PER_MIN( skill_checks ) );
3✔
1878
  LONG_COREVAR( combat_operations_per_min, GET_PROFILEVAR_PER_MIN( combat_operations ) );
3✔
1879
  LONG_COREVAR( error_creations_per_min, GET_PROFILEVAR_PER_MIN( error_creations ) );
3✔
1880

1881
  LONG_COREVAR( last_character_serial, GetCurrentCharSerialNumber() );
3✔
1882
  LONG_COREVAR( last_item_serial, GetCurrentItemSerialNumber() );
3✔
1883

1884
  LONG_COREVAR( tasks_ontime_per_min, GET_PROFILEVAR_PER_MIN( tasks_ontime ) );
3✔
1885
  LONG_COREVAR( tasks_late_per_min, GET_PROFILEVAR_PER_MIN( tasks_late ) );
3✔
1886
  LONG_COREVAR( tasks_late_ticks_per_min, GET_PROFILEVAR_PER_MIN( tasks_late_ticks ) );
3✔
1887

1888
  LONG_COREVAR( scripts_late_per_min, GET_PROFILEVAR_PER_MIN( scripts_late ) );
3✔
1889
  LONG_COREVAR( scripts_ontime_per_min, GET_PROFILEVAR_PER_MIN( scripts_ontime ) );
3✔
1890

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

1920
  return new BError( std::string( "Unknown core variable " ) + corevar );
×
1921
}
1922

1923
BObjectRef PolCore::get_member( const char* membername )
3✔
1924
{
1925
  return BObjectRef( GetCoreVariable( membername ) );
3✔
1926
}
1927

1928
BObjectImp* PolCore::call_polmethod( const char* methodname, UOExecutor& ex )
4✔
1929
{
1930
  if ( stricmp( methodname, "log_profile" ) == 0 )
4✔
1931
  {
1932
    if ( ex.numParams() != 1 )
×
1933
      return new BError( "polcore.log_profile(clear) requires 1 parameter." );
×
1934
    int clear;
1935
    if ( ex.getParam( 0, clear ) )
×
1936
    {
1937
      log_all_script_cycle_counts( clear ? true : false );
×
1938
      return new BLong( 1 );
×
1939
    }
1940
  }
1941
  else if ( stricmp( methodname, "set_priority_divide" ) == 0 )
4✔
1942
  {
1943
    if ( ex.numParams() != 1 )
×
1944
      return new BError( "polcore.set_priority_divide(value) requires 1 parameter." );
×
1945
    int div;
1946
    if ( ex.getParam( 0, div, 1, 1000 ) )
×
1947
    {
1948
      scriptScheduler.priority_divide = div;
×
1949
      return new BLong( 1 );
×
1950
    }
1951
    else
1952
    {
1953
      return nullptr;
×
1954
    }
1955
  }
1956
  else if ( stricmp( methodname, "clear_script_profile_counters" ) == 0 )
4✔
1957
  {
1958
    if ( ex.numParams() > 0 )
×
1959
      return new BError( "polcore.clear_script_profile_counters() doesn't take parameters." );
×
1960
    clear_script_profile_counters();
×
1961
    return new BLong( 1 );
×
1962
  }
1963
  else if ( stricmp( methodname, "internal" ) == 0 )  // Just for internal Development...
4✔
1964
  {
1965
    int type;
1966
    if ( ex.getParam( 0, type ) )
4✔
1967
    {
1968
#ifdef MEMORYLEAK
1969
      if ( type == 1 )
1970
      {
1971
        auto time_tm = Clib::localtime( time( nullptr ) );
1972
        DEBUGLOGLN( "[{:%m/%d %T}] polcore().internal", time_tm );
1973
        LEAKLOG( "{:%m/%d %T};", time_tm );
1974

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

2025
BObjectImp* UOExecutorModule::mf_POLCore()
7✔
2026
{
2027
  return new PolCore;
7✔
2028
}
2029

2030
BObjectImp* MakeMapCacheImp()
×
2031
{
2032
  ObjArray* arr = new ObjArray;
×
2033
  return arr;
×
2034
}
2035

2036

2037
BObjectImp* UOExecutorModule::mf_CreateAccount()
12✔
2038
{
2039
  const String* acctname;
2040
  const String* password;
2041
  int enabled;
2042
  if ( getStringParam( 0, acctname ) && getStringParam( 1, password ) && getParam( 2, enabled ) )
12✔
2043
  {
2044
    if ( acctname->SafeCharAmt() < acctname->length() )
12✔
2045
    {
2046
      return new BError(
2047
          "Attempted to use username in account creation with non-allowed characters." );
×
2048
    }
2049
    if ( password->SafeCharAmt() < password->length() )
12✔
2050
    {
2051
      return new BError(
2052
          "Attempted to use password in account creation with non-allowed characters." );
×
2053
    }
2054

2055
    if ( Accounts::find_account( acctname->data() ) )
12✔
2056
    {
2057
      return new BError( "Account already exists" );
×
2058
    }
2059

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

2064
    return new Accounts::AccountObjImp( Accounts::AccountPtrHolder( AccountRef( acct ) ) );
12✔
2065
  }
2066
  else
2067
  {
2068
    return new BError( "Invalid parameter type" );
×
2069
  }
2070
}
2071

2072
BObjectImp* UOExecutorModule::mf_FindAccount()
43✔
2073
{
2074
  const String* acctname;
2075
  if ( getStringParam( 0, acctname ) )
43✔
2076
  {
2077
    Accounts::Account* acct = Accounts::find_account( acctname->data() );
43✔
2078
    if ( acct != nullptr )
43✔
2079
    {
2080
      return new Accounts::AccountObjImp( Accounts::AccountPtrHolder( AccountRef( acct ) ) );
32✔
2081
    }
2082
    else
2083
    {
2084
      return new BError( "Account not found." );
11✔
2085
    }
2086
  }
2087
  else
2088
  {
2089
    return new BError( "Invalid parameter type" );
×
2090
  }
2091
}
2092

2093
BObjectImp* UOExecutorModule::mf_ListAccounts()
×
2094
{
2095
  std::unique_ptr<ObjArray> arr( new ObjArray );
×
2096
  for ( unsigned idx = 0; idx < gamestate.accounts.size(); idx++ )
×
2097
  {
2098
    arr->addElement( new String( gamestate.accounts[idx]->name() ) );
×
2099
  }
2100
  return arr.release();
×
2101
}
×
2102

2103
void handle_resurrect_menu( Client* client, PKTBI_2C* msg )
×
2104
{
2105
  if ( msg->choice )
×
2106
  {
2107
    INFO_PRINTLN( "Resurrect Menu Choice: {}", int( msg->choice ) );
×
2108
    // transmit( client, msg, sizeof *msg );
2109
  }
2110

2111
  if ( client->chr != nullptr && client->gd != nullptr && client->gd->resurrect_uoemod != nullptr )
×
2112
  {
2113
    auto& uoex = client->gd->resurrect_uoemod->uoexec();
×
2114
    uoex.ValueStack.back().set( new BObject( new BLong( msg->choice ) ) );
×
2115
    uoex.revive();
×
2116
    client->gd->resurrect_uoemod->resurrect_chr = nullptr;
×
2117
    client->gd->resurrect_uoemod = nullptr;
×
2118
  }
2119
}
×
2120

2121
BObjectImp* UOExecutorModule::mf_SendInstaResDialog()
×
2122
{
2123
  Character* chr;
2124
  if ( !getCharacterParam( 0, chr ) )
×
2125
    return new BError( "Invalid parameter type" );
×
2126
  if ( !chr->has_active_client() )
×
2127
    return new BError( "No client attached" );
×
2128
  if ( chr->client->gd->resurrect_uoemod != nullptr )
×
2129
    return new BError( "Client busy with another instares dialog" );
×
2130

2131
  if ( !uoexec().suspend() )
×
2132
  {
2133
    DEBUGLOGLN(
×
2134
        "Script Error in '{}' PC={}: \n"
2135
        "\tCall to function UO::SendInstaResDialog():\n"
2136
        "\tThe execution of this script can't be blocked!",
2137
        scriptname(), exec.PC );
×
2138
    return new Bscript::BError( "Script can't be blocked" );
×
2139
  }
2140

2141
  PktHelper::PacketOut<PktOut_2C> msg;
×
2142
  msg->Write<u8>( RESURRECT_CHOICE_SELECT );
×
2143
  msg.Send( chr->client );
×
2144
  chr->client->gd->resurrect_uoemod = this;
×
2145
  resurrect_chr = chr;
×
2146

2147
  return new BLong( 0 );
×
2148
}
×
2149

2150
void handle_selcolor( Client* client, PKTBI_95* msg )
×
2151
{
2152
  if ( client->chr != nullptr && client->gd != nullptr && client->gd->selcolor_uoemod != nullptr )
×
2153
  {
2154
    auto& uoex = client->gd->selcolor_uoemod->uoexec();
×
2155
    unsigned short color = cfBEu16( msg->graphic_or_color ) & Plib::VALID_ITEM_COLOR_MASK;
×
2156
    BObject* valstack;
2157
    if ( color >= 2 && color <= 1001 )
×
2158
    {
2159
      valstack = new BObject( new BLong( color ) );
×
2160
    }
2161
    else
2162
    {
2163
      valstack = new BObject( new BError( "Client selected an out-of-range color" ) );
×
2164

2165
      // unsigned short newcolor = ((color - 2) % 1000) + 2;
2166
      POLLOG_ERRORLN( "Client #{:d} (account {}) selected an out-of-range color {:#x}",
×
2167
                      static_cast<unsigned long>( client->instance_ ),
×
2168
                      ( ( client->acct != nullptr ) ? client->acct->name() : "unknown" ), color );
×
2169
    }
2170

2171
    // client->gd->selcolor_uoemod->uoexec.ValueStack.back().set( new BObject( new BLong( color )
2172
    // )
2173
    // );
2174
    uoex.ValueStack.back().set( valstack );
×
2175
    uoex.revive();
×
2176
    client->gd->selcolor_uoemod->selcolor_chr = nullptr;
×
2177
    client->gd->selcolor_uoemod = nullptr;
×
2178
  }
2179
}
×
2180

2181

2182
BObjectImp* UOExecutorModule::mf_SelectColor()
×
2183
{
2184
  Character* chr;
2185
  Item* item;
2186
  if ( !getCharacterParam( 0, chr ) || !getItemParam( 1, item ) )
×
2187
  {
2188
    return new BError( "Invalid parameter type" );
×
2189
  }
2190
  if ( !chr->has_active_client() )
×
2191
    return new BError( "No client attached" );
×
2192
  if ( chr->client->gd->resurrect_uoemod != nullptr )
×
2193
    return new BError( "Client is already selecting a color" );
×
2194

2195
  PktHelper::PacketOut<PktOut_95> msg;
×
2196
  msg->Write<u32>( item->serial_ext );
×
2197
  msg->offset += 2;  // u16 unk
×
2198
  msg->WriteFlipped<u16>( item->graphic );
×
2199

2200
  if ( !uoexec().suspend() )
×
2201
  {
2202
    DEBUGLOGLN(
×
2203
        "Script Error in '{}' PC={}: \n"
2204
        "\tCall to function UO::SelectColor():\n"
2205
        "\tThe execution of this script can't be blocked!",
2206
        scriptname(), exec.PC );
×
2207
    return new Bscript::BError( "Script can't be blocked" );
×
2208
  }
2209

2210
  msg.Send( chr->client );
×
2211

2212
  chr->client->gd->selcolor_uoemod = this;
×
2213
  selcolor_chr = chr;
×
2214
  return new BLong( 0 );
×
2215
}
×
2216

2217
BObjectImp* UOExecutorModule::mf_SendOpenBook()
×
2218
{
2219
  Character* chr;
2220
  Item* book;
2221

2222
  if ( !( getCharacterParam( 0, chr ) && getItemParam( 1, book ) ) )
×
2223
  {
2224
    return new BError( "Invalid parameter type" );
×
2225
  }
2226
  if ( !chr->has_active_client() )
×
2227
  {
2228
    return new BError( "No active client" );
×
2229
  }
2230

2231
  bool writable = book->call_custom_method( "iswritable" )->isTrue();
×
2232
  BObject nlines_ob( book->call_custom_method( "getnumlines" ) );
×
2233
  int nlines;
2234
  if ( nlines_ob.isa( BObjectImp::OTLong ) )
×
2235
  {
2236
    BLong* blong = nlines_ob.impptr<BLong>();
×
2237
    nlines = blong->value();
×
2238
  }
2239
  else
2240
  {
2241
    return new BError( "book.GetNumLines() did not return an Integer" );
×
2242
  }
2243
  std::string title = book->call_custom_method( "gettitle" )->getStringRep();
×
2244
  std::string author = book->call_custom_method( "getauthor" )->getStringRep();
×
2245

2246
  int npages = ( nlines + 7 ) / 8;
×
2247

2248
  BObject contents_ob( UninitObject::create() );
×
2249
  if ( writable )
×
2250
  {
2251
    contents_ob.setimp( book->call_custom_method( "getcontents" ).impptr() );
×
2252
    if ( !contents_ob.isa( BObjectImp::OTArray ) )
×
2253
    {
2254
      if ( contents_ob.isa( BObjectImp::OTError ) )
×
2255
        return contents_ob->copy();
×
2256
      else
2257
        return new BError( "book.GetContents() must return an array" );
×
2258
    }
2259
  }
2260

2261
  PktHelper::PacketOut<PktOut_93> msg93;
×
2262
  msg93->Write<u32>( book->serial_ext );
×
2263
  msg93->Write<u8>( writable ? 1u : 0u );
×
2264
  msg93->Write<u8>( 1u );
×
2265
  msg93->WriteFlipped<u16>( static_cast<u16>( npages ) );
×
2266
  msg93->Write( title.c_str(), 60, false );
×
2267
  msg93->Write( author.c_str(), 30, false );
×
2268
  msg93.Send( chr->client );
×
2269

2270
  if ( writable )
×
2271
  {
2272
    PktHelper::PacketOut<PktOut_66> msg;
×
2273
    msg->offset += 2;
×
2274
    msg->Write<u32>( book->serial_ext );
×
2275
    msg->WriteFlipped<u16>( static_cast<u16>( npages ) );
×
2276

2277
    ObjArray* arr = contents_ob.impptr<ObjArray>();
×
2278

2279
    int linenum = 1;
×
2280
    for ( int page = 1; page <= npages; ++page )
×
2281
    {
2282
      if ( msg->offset + 4 > static_cast<int>( sizeof msg->buffer ) )
×
2283
      {
2284
        return new BError( "Buffer overflow" );
×
2285
      }
2286
      msg->WriteFlipped<u16>( static_cast<u16>( page ) );
×
2287
      u16 offset = msg->offset;
×
2288
      msg->offset += 2;
×
2289

2290
      int pagelines;
2291
      for ( pagelines = 0; pagelines < 8 && linenum <= nlines; ++pagelines, ++linenum )
×
2292
      {
2293
        const BObjectImp* line_imp = arr->imp_at( linenum );
×
2294
        std::string linetext;
×
2295
        if ( line_imp )
×
2296
          linetext = line_imp->getStringRep();  // This is UTF-8
×
2297
        if ( msg->offset + linetext.size() + 1 > sizeof msg->buffer )
×
2298
        {
2299
          return new BError( "Buffer overflow" );
×
2300
        }
2301
        msg->Write( linetext.c_str(), static_cast<u16>( linetext.size() + 1 ) );
×
2302
      }
×
2303
      u16 len = msg->offset;
×
2304
      msg->offset = offset;
×
2305
      msg->WriteFlipped<u16>( static_cast<u16>( pagelines ) );
×
2306
      msg->offset = len;
×
2307
    }
2308

2309
    /*
2310
            int linenum = 1;
2311
            for( int page = 1; page <= npages; ++page )
2312
            {
2313
            PKTBI_66_CONTENTS* ppage = reinterpret_cast<PKTBI_66_CONTENTS*>(&buffer[msglen]);
2314
            msglen += sizeof(*ppage);
2315
            if (msglen > sizeof buffer)
2316
            return new BError( "Buffer overflow" );
2317
            ppage->page = ctBEu16( page );
2318

2319
            int pagelines;
2320
            for( pagelines = 0; pagelines < 8 && linenum <= nlines; ++pagelines, ++linenum )
2321
            {
2322
            string linetext;
2323

2324
            BObjectVec params;
2325
            params.push_back( BObject(new BLong(linenum)) );
2326
            BObject line_ob = book->call_custom_method( "getline", params );
2327
            linetext = line_ob->getStringRep();
2328

2329
            char* linebuf = reinterpret_cast<char*>(&buffer[msglen]);
2330
            msglen += linetext.size()+1;
2331
            if (msglen > sizeof buffer)
2332
            return new BError( "Buffer overflow" );
2333
            memcpy( linebuf, linetext.c_str(), linetext.size()+1 );
2334
            }
2335
            ppage->lines = ctBEu16( pagelines );
2336
            }
2337
            */
2338
    u16 len = msg->offset;
×
2339
    msg->offset = 1;
×
2340
    msg->WriteFlipped<u16>( len );
×
2341
    msg.Send( chr->client, len );
×
2342
  }
×
2343

2344
  return new BLong( 1 );
×
2345
}
×
2346

2347
void read_book_page_handler( Client* client, PKTBI_66* msg )
×
2348
{
2349
  unsigned int book_serial = cfBEu32( msg->book_serial );
×
2350
  u16 page = cfBEu16( msg->page );
×
2351
  Item* book = find_legal_item( client->chr, book_serial );
×
2352
  if ( book == nullptr )
×
2353
  {
2354
    POLLOGLN( "Unable to find book {:#x} for character {:#x}", book_serial, client->chr->serial );
×
2355
    return;
×
2356
  }
2357

2358
  if ( msg->lines == 0xFFFF )
×
2359
  {
2360
    BObject nlines_ob( book->call_custom_method( "getnumlines" ) );
×
2361
    int nlines;
2362
    if ( nlines_ob.isa( BObjectImp::OTLong ) )
×
2363
    {
2364
      BLong* blong = nlines_ob.impptr<BLong>();
×
2365
      nlines = blong->value();
×
2366
    }
2367
    else
2368
    {
2369
      return;
×
2370
    }
2371

2372
    PktHelper::PacketOut<PktOut_66> msgOut;
×
2373
    msgOut->offset += 2;
×
2374
    msgOut->Write<u32>( book->serial_ext );
×
2375
    msgOut->WriteFlipped<u16>( 1u );
×
2376

2377
    int linenum = ( page - 1 ) * 8 + 1;
×
2378

2379
    msgOut->WriteFlipped<u16>( page );
×
2380
    u16 offset = msgOut->offset;
×
2381
    msgOut->offset += 2;
×
2382

2383
    int pagelines;
2384
    for ( pagelines = 0; pagelines < 8 && linenum <= nlines; ++pagelines, ++linenum )
×
2385
    {
2386
      std::string linetext;
×
2387

2388
      BObjectImpRefVec params;
×
2389
      params.push_back( ref_ptr<BObjectImp>( new BLong( linenum ) ) );
×
2390
      BObject line_ob = book->call_custom_method( "getline", params );
×
2391
      linetext = line_ob->getStringRep();  // this is UTF-8
×
2392

2393
      if ( msgOut->offset + linetext.size() + 1 > sizeof msgOut->buffer )
×
2394
      {
2395
        return;
×
2396
      }
2397
      msgOut->Write( linetext.c_str(), static_cast<u16>( linetext.size() + 1 ) );
×
2398
    }
×
2399

2400
    u16 len = msgOut->offset;
×
2401
    msgOut->offset = offset;
×
2402
    msgOut->WriteFlipped<u16>( static_cast<u16>( pagelines ) );
×
2403
    msgOut->offset = 1;
×
2404
    msgOut->WriteFlipped<u16>( len );
×
2405
    msgOut.Send( client, len );
×
2406
  }
×
2407
  else
2408
  {
2409
    size_t msglen = cfBEu16( msg->msglen );
×
2410
    const char* ptext = msg->text;
×
2411
    size_t bytesleft = msglen - offsetof( PKTBI_66, text );
×
2412
    for ( int i = 1; i <= 8; ++i )
×
2413
    {
2414
      std::string line;
×
2415
      while ( bytesleft )
×
2416
      {
2417
        --bytesleft;
×
2418
        if ( *ptext )
×
2419
        {
2420
          line.append( 1, *ptext );
×
2421
          ++ptext;
×
2422
        }
2423
        else
2424
        {
2425
          ++ptext;  // skip null terminator
×
2426
          break;
×
2427
        }
2428
      }
2429

2430
      BObjectImpRefVec params;
×
2431
      params.push_back( ref_ptr<BObjectImp>( new BLong( ( page - 1 ) * 8 + i ) ) );
×
2432
      params.push_back( ref_ptr<BObjectImp>( new String( line ) ) );
×
2433

2434
      BObject page_on = book->call_custom_method( "setline", params );
×
2435
    }
×
2436
  }
2437
}
2438

2439
char strip_ctrl_chars( char c )
×
2440
{
2441
  if ( c < 0x20 )
×
2442
    return 0x20;
×
2443
  else
2444
    return c;
×
2445
}
2446

2447
void open_book_handler( Client* client, PKTBI_93* msg )
×
2448
{
2449
  // fdump( stdout, msg, sizeof *msg );
2450

2451
  // string title( msg->title, sizeof msg->title );
2452
  // string author( msg->author, sizeof msg->author );
2453

2454
  // Dave changed this 12/19 from sizeof msg->title. The protocol defines garbage after the
2455
  // terminator for
2456
  // the title and author strings, so we were writing this garbage into save files. This caused
2457
  // some
2458
  //"No SERIAL property" bugs, because the parser barfed on the bad characters.
2459
  std::string title( msg->title, strlen( msg->title ) );
×
2460
  std::string author( msg->author, strlen( msg->author ) );
×
2461

2462
  // dave 1/20/3 cheaters insert cntrl chars into books causing severe problems
2463
  std::transform( title.begin(), title.end(), title.begin(), strip_ctrl_chars );
×
2464
  std::transform( author.begin(), author.end(), author.begin(), strip_ctrl_chars );
×
2465

2466

2467
  unsigned int book_serial = cfBEu32( msg->serial );
×
2468
  Item* book = find_legal_item( client->chr, book_serial );
×
2469
  if ( book == nullptr )
×
2470
  {
2471
    POLLOGLN( "Unable to find book {:#x} for character {:#x}", book_serial, client->chr->serial );
×
2472
    return;
×
2473
  }
2474
  BObjectImpRefVec params;
×
2475
  params.push_back( ref_ptr<BObjectImp>( new String( title ) ) );
×
2476
  book->call_custom_method( "settitle", params );
×
2477

2478
  params[0].set( new String( author ) );
×
2479
  book->call_custom_method( "setauthor", params );
×
2480
}
×
2481

2482
BObjectImp* UOExecutorModule::mf_SendHousingTool()
×
2483
{
2484
  Character* chr;
2485
  Multi::UMulti* multi;
2486

2487
  if ( !( getCharacterParam( 0, chr ) && getMultiParam( 1, multi ) ) )
×
2488
  {
2489
    return new BError( "Invalid parameter type" );
×
2490
  }
2491
  if ( !chr->client->acctSupports( Plib::ExpansionVersion::AOS ) )
×
2492
    return new BError( "Charater does not have AOS enabled." );
×
2493

2494
  if ( multi == nullptr )
×
2495
    return new BError( "House not found." );
×
2496

2497
  Multi::UHouse* house = multi->as_house();
×
2498
  if ( house == nullptr )
×
2499
    return new BError( "Not a House multi." );
×
2500

2501
  if ( !house->IsCustom() )
×
2502
    return new BError( "Not a Custom House." );
×
2503

2504
  if ( house->editing )
×
2505
    return new BError( "House currently being customized." );
×
2506

2507
  if ( house->IsWaitingForAccept() )
×
2508
    return new BError( "House currently being waiting for a commit" );
×
2509

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

2513
  chr->client->gd->custom_house_serial = house->serial;
×
2514
  chr->client->gd->custom_house_chrserial = chr->serial;
×
2515

2516
  {
2517
    PktHelper::PacketOut<PktOut_BF_Sub20> msg;
×
2518
    msg->WriteFlipped<u16>( 17u );
×
2519
    msg->offset += 2;  // sub
×
2520
    msg->Write<u32>( house->serial_ext );
×
2521
    msg->Write<u8>( 0x4u );          // begin
×
2522
    msg->offset += 2;                // u16 unk2 FIXME what's the meaning
×
2523
    msg->Write<u32>( 0xFFFFFFFFu );  // fixme
×
2524
    msg->Write<u8>( 0xFFu );         // fixme
×
2525
    msg.Send( chr->client );
×
2526
  }
×
2527
  Core::Pos4d newpos = house->pos() + Core::Vec3d( 0, 0, 7 );
×
2528
  move_character_to( chr, newpos, MOVEITEM_FORCELOCATION );
×
2529

2530
  house->WorkingDesign.AddComponents( house );
×
2531
  house->CurrentDesign.AddComponents( house );
×
2532
  house->WorkingDesign.ClearComponents( house );
×
2533
  Multi::ItemList itemlist;
×
2534
  Multi::MobileList moblist;
×
2535
  Multi::UHouse::list_contents( house, itemlist, moblist );
×
2536
  const Multi::MultiDef& def = house->multidef();
×
2537
  while ( !itemlist.empty() )
×
2538
  {
2539
    Item* item = itemlist.front();
×
2540
    send_remove_object_if_inrange( chr->client, item );
×
2541
    itemlist.pop_front();
×
2542
  }
2543

2544
  while ( !moblist.empty() )
×
2545
  {
2546
    Character* multichr = moblist.back();
×
2547
    if ( multichr != chr )
×
2548
    {
2549
      Core::Pos4d pos = house->pos() + Core::Vec2d( def.minrxyz.x(), def.maxrxyz.y() + 1 );
×
2550
      move_character_to( multichr, pos, MOVEITEM_FORCELOCATION );
×
2551
    }
2552
    moblist.pop_back();
×
2553
  }
2554

2555
  house->editing = true;
×
2556
  house->editing_floor_num = 1;
×
2557
  CustomHousesSendFull( house, chr->client, Multi::HOUSE_DESIGN_WORKING );
×
2558

2559
  return new BLong( 1 );
×
2560
}
×
2561

2562
BObjectImp* UOExecutorModule::mf_SendCharacterRaceChanger( /* Character */ )
×
2563
{
2564
  Character* chr;
2565
  if ( getCharacterParam( 0, chr ) )
×
2566
  {
2567
    PktHelper::PacketOut<PktOut_BF_Sub2A> msg;
×
2568
    msg->WriteFlipped<u16>( 7u );
×
2569
    msg->offset += 2;  // sub
×
2570
    msg->Write<u8>( chr->gender );
×
2571
    msg->Write<u8>( chr->race + 1u );
×
2572
    msg.Send( chr->client );
×
2573
    return new BLong( 1 );
×
2574
  }
×
2575
  else
2576
    return new BError( "Invalid parameter" );
×
2577
}
2578

2579

2580
void character_race_changer_handler( Client* client, PKTBI_BF* msg )
×
2581
{
2582
  Item* tmpitem;
2583

2584
  if ( msg->msglen == ctBEu16( 5 ) )
×
2585
    return;
×
2586

2587
  if ( ( msg->characterracechanger.result.BodyHue == 0 ) &&
×
2588
       ( msg->characterracechanger.result.HairId == 0 ) &&
×
2589
       ( msg->characterracechanger.result.HairHue == 0 ) &&
×
2590
       ( msg->characterracechanger.result.BeardId == 0 ) &&
×
2591
       ( msg->characterracechanger.result.BeardHue == 0 ) )
×
2592
    return;
×
2593

2594
  client->chr->setcolor( cfBEu16( msg->characterracechanger.result.BodyHue ) | 0x8000 );
×
2595
  client->chr->truecolor = client->chr->color;
×
2596

2597
  client->chr->on_color_changed();
×
2598

2599
  // Create Hair
2600
  if ( client->chr->layer_is_equipped( LAYER_HAIR ) )
×
2601
    destroy_item( client->chr->wornitem( LAYER_HAIR ) );
×
2602

2603
  if ( validhair( cfBEu16( msg->characterracechanger.result.HairId ) ) )
×
2604
  {
2605
    tmpitem = Item::create( cfBEu16( msg->characterracechanger.result.HairId ) );
×
2606
    tmpitem->layer = LAYER_HAIR;
×
2607
    tmpitem->color = cfBEu16( msg->characterracechanger.result.HairHue );
×
2608
    client->chr->equip( tmpitem );
×
2609
    send_wornitem_to_inrange( client->chr, tmpitem );
×
2610
  }
2611

2612
  // Create Beard
2613
  if ( client->chr->layer_is_equipped( LAYER_BEARD ) )
×
2614
    destroy_item( client->chr->wornitem( LAYER_BEARD ) );
×
2615

2616
  if ( validbeard( cfBEu16( msg->characterracechanger.result.BeardId ) ) )
×
2617
  {
2618
    tmpitem = Item::create( cfBEu16( msg->characterracechanger.result.BeardId ) );
×
2619
    tmpitem->layer = LAYER_BEARD;
×
2620
    tmpitem->color = cfBEu16( msg->characterracechanger.result.BeardHue );
×
2621
    client->chr->equip( tmpitem );
×
2622
    send_wornitem_to_inrange( client->chr, tmpitem );
×
2623
  }
2624
}
2625

2626
// Called when selection made or when selection canceled with nullptr parameters
2627
void popup_menu_selection_made( Network::Client* client, u32 serial, u16 id )
×
2628
{
2629
  if ( client == nullptr )
×
2630
    return;
×
2631

2632
  Character* chr = client->chr;
×
2633
  if ( chr == nullptr || chr->client->gd->popup_menu_selection_uoemod == nullptr )
×
2634
    return;
×
2635

2636
  // The function sending the PopUp menu is responsible to set this
2637
  passert_always_r(
×
2638
      chr->client->gd->popup_menu_selection_uoemod->popup_menu_selection_above != nullptr,
2639
      "Bug in handling PopUp menu selection. Please report this on the forums." );
2640

2641
  auto& uoex = chr->client->gd->popup_menu_selection_uoemod->uoexec();
×
2642

2643
  if ( id && serial )
×
2644
  {
2645
    if ( chr->client->gd->popup_menu_selection_uoemod->popup_menu_selection_above->serial ==
×
2646
         serial )
2647
      uoex.ValueStack.back().set( new BObject( new BLong( id ) ) );
×
2648
    else
2649
      POLLOG_INFO( "{}/{} send an unexpected popup reply for {:#x}.\n", client->acct->name(),
×
2650
                   client->chr->name(), serial );
×
2651
  }
2652

2653
  uoex.revive();
×
2654
  chr->client->gd->popup_menu_selection_uoemod->popup_menu_selection_chr = nullptr;
×
2655
  chr->client->gd->popup_menu_selection_uoemod->popup_menu_selection_above = nullptr;
×
2656
  chr->client->gd->popup_menu_selection_uoemod = nullptr;
×
2657
}
2658

2659
/// Sends a PopUp/Context menu
2660
BObjectImp* UOExecutorModule::mf_SendPopUpMenu()
×
2661
{
2662
  Character* chr;
2663
  UObject* above;
2664
  ObjArray* menu_arr;
2665
  if ( !( getCharacterParam( 0, chr ) && getUObjectParam( 1, above ) &&
×
2666
          exec.getObjArrayParam( 2, menu_arr ) ) )
×
2667
  {
2668
    return new BError( "Invalid parameter" );
×
2669
  }
2670
  if ( !chr->has_active_client() )
×
2671
    return new BError( "No client attached" );
×
NEW
2672
  if ( menu_arr->ref_arr.empty() )
×
2673
    return new BError( "Can't send empty menu" );
×
2674
  if ( menu_arr->ref_arr.size() > 0xfffe )
×
2675
    return new BError( "Too many entries in menu" );
×
2676

2677
  // Prepare packet
2678
  struct Entry
2679
  {
2680
    u32 cliloc = 0;
2681
    u16 flags = 0;
2682
    u16 color = 0;
2683
  };
2684
  std::vector<Entry> entries;
×
2685
  bool newformat = false;
×
2686
  if ( chr->client->ClientType & CLIENTTYPE_UOKR )
×
2687
    newformat = true;
×
2688

2689
  for ( u16 i = 0; i < menu_arr->ref_arr.size(); ++i )
×
2690
  {
2691
    BObject* bo = menu_arr->ref_arr[i].get();
×
2692
    if ( bo == nullptr )
×
2693
      continue;
×
2694
    BObjectImp* imp = bo->impptr();
×
2695

2696
    if ( entries.size() >= 255 )  // overflow
×
2697
      return new BError( "Too many entries in menu" );
×
2698
    Entry entry;
×
2699
    if ( auto* lng = impptrIf<BLong>( imp ) )
×
2700
    {
2701
      // Short form: menu is just an int
2702
      entry.cliloc = lng->value();
×
2703
    }
2704
    else if ( auto* elem = impptrIf<BStruct>( imp ) )
×
2705
    {
2706
      // Full form: menu is a struct
2707

2708
      BObjectImp* cl = const_cast<BObjectImp*>( elem->FindMember( "cliloc" ) );
×
2709
      if ( cl == nullptr )
×
2710
        return new BError( "Missing cliloc for menu element" );
×
2711
      if ( auto* lngcliloc = impptrIf<BLong>( cl ) )
×
2712
        entry.cliloc = lngcliloc->value();
×
2713
      else
2714
        return new BError( "Invalid cliloc for menu element" );
×
2715

2716
      const BObjectImp* ds = elem->FindMember( "disabled" );
×
2717
      if ( ds != nullptr && ds->isTrue() )
×
2718
        entry.flags |= PKTBI_BF_14_ENTRIES::POPUP_MENU_LOCKED;
×
2719

2720
      const BObjectImp* ar = elem->FindMember( "arrow" );
×
2721
      if ( ar != nullptr && ar->isTrue() )
×
2722
        entry.flags |= PKTBI_BF_14_ENTRIES::POPUP_MENU_ARROW;
×
2723

2724
      BObjectImp* co = const_cast<BObjectImp*>( elem->FindMember( "color" ) );
×
2725
      if ( auto* colng = impptrIf<BLong>( co ) )
×
2726
      {
2727
        entry.color = static_cast<u16>( colng->value() );
×
2728
        entry.flags |= PKTBI_BF_14_ENTRIES::POPUP_MENU_COLOR;
×
2729
      }
2730
    }
2731
    else
2732
      return new BError( "Menu elements must be int or struct" );
×
2733

2734
    if ( !newformat && ( entry.cliloc < 3000000 || entry.cliloc > 3065535 ) )
×
2735
    {
2736
      if ( chr->client->ClientType & CLIENTTYPE_6017 )
×
2737
        newformat = true;
×
2738
      else
2739
        return new BError( "Cliloc out of range in menu" );
×
2740
    }
2741
    entries.push_back( entry );
×
2742
  }
2743

2744
  PktHelper::PacketOut<PktOut_BF_Sub14> msg;
×
2745
  msg->offset += 4;
×
2746
  msg->Write<u8>( 0u );                   // unknown
×
2747
  msg->Write<u8>( newformat ? 2u : 1u );  // 1=2D, 2=KR
×
2748
  msg->Write<u32>( above->serial_ext );   // Above serial
×
2749
  msg->Write<u8>( static_cast<u8>( entries.size() ) );
×
2750
  for ( u16 i = 0; i < entries.size(); ++i )
×
2751
  {
2752
    auto& e = entries[i];
×
2753
    if ( newformat )
×
2754
    {
2755
      msg->WriteFlipped<u32>( e.cliloc );
×
2756
      msg->WriteFlipped<u16>( i + 1u );
×
2757
      if ( e.flags & PKTBI_BF_14_ENTRIES::POPUP_MENU_COLOR )  // new format does not support color
×
2758
        e.flags &= ~PKTBI_BF_14_ENTRIES::POPUP_MENU_COLOR;
×
2759
      msg->WriteFlipped<u16>( e.flags );
×
2760
    }
2761
    else
2762
    {
2763
      msg->WriteFlipped<u16>( i + 1u );                                  // Menu element ID
×
2764
      msg->WriteFlipped<u16>( static_cast<u16>( e.cliloc - 3000000 ) );  // Cliloc ID, adjusted
×
2765
      msg->WriteFlipped<u16>( e.flags );                                 // Flags
×
2766
      if ( e.flags & PKTBI_BF_14_ENTRIES::POPUP_MENU_COLOR )
×
2767
        msg->WriteFlipped<u16>( e.color );
×
2768
    }
2769
  }
2770

2771
  // Add lengths and send
2772
  u16 len = msg->offset;
×
2773
  msg->offset = 1;
×
2774
  msg->WriteFlipped<u16>( len );
×
2775
  msg.Send( chr->client, len );
×
2776

2777
  // Cancel any previously waiting popup response
2778
  if ( auto& old = chr->client->gd->popup_menu_selection_uoemod; old != nullptr )
×
2779
  {
2780
    // reset uomod members otherwise the deconstructor will reset the next request
2781
    old->popup_menu_selection_chr = nullptr;
×
2782
    old->popup_menu_selection_above = nullptr;
×
2783
    old->uoexec().revive();
×
2784

2785
    old = nullptr;
×
2786
    chr->client->gd->on_popup_menu_selection = nullptr;
×
2787
  }
2788

2789
  // Suspend the script first
2790
  if ( !uoexec().suspend() )
×
2791
  {
2792
    DEBUGLOGLN(
×
2793
        "Script Error in '{}' PC={}: \n"
2794
        "\tCall to function UO::SendPopupMenu():\n"
2795
        "\tThe execution of this script can't be blocked!",
2796
        scriptname(), exec.PC );
×
2797
    return new Bscript::BError( "Script can't be blocked" );
×
2798
  }
2799

2800
  // Prepare to restart the script once the response arrives
2801
  chr->client->gd->on_popup_menu_selection = popup_menu_selection_made;
×
2802
  chr->client->gd->popup_menu_selection_uoemod = this;
×
2803
  popup_menu_selection_chr = chr;
×
2804
  popup_menu_selection_above = above;
×
2805

2806
  return new BLong( 0 );
×
2807
}
×
2808

2809
BObjectImp* UOExecutorModule::mf_SingleClick()
×
2810
{
2811
  Character* chr = nullptr;
×
2812
  UObject* what = nullptr;
×
2813

2814
  if ( !getCharacterParam( 0, chr ) || !getUObjectParam( 1, what ) )
×
2815
    return new BError( "Invalid parameter" );
×
2816

2817
  if ( !chr->has_active_client() )
×
2818
    return new BError( "Mobile has no active client" );
×
2819

2820
  // If it got here, clear any errors from getUObjectParam/getCharacterParam
2821
  exec.setFunctionResult( nullptr );
×
2822

2823
  singleclick( chr->client, what->serial );
×
2824
  return new BLong( 1 );
×
2825
}
2826

2827
BObjectImp* UOExecutorModule::mf_ListStaticsNearLocationOfType(
×
2828
    /* x, y, z, range, objtype, flags, realm */ )
2829
{
2830
  Core::Pos2d pos;
×
2831
  int z, flags;
2832
  short range;
2833
  unsigned int objtype;
2834
  Realms::Realm* realm;
2835

2836
  if ( getRealmParam( 6, &realm ) && getPos2dParam( 0, 1, &pos, realm ) && getParam( 2, z ) &&
×
2837
       getParam( 3, range ) && getObjtypeParam( 4, objtype ) && getParam( 5, flags ) )
×
2838
  {
2839
    std::unique_ptr<ObjArray> newarr( new ObjArray );
×
2840
    Core::Vec2d radius( range, range );
×
2841
    Core::Range2d area( pos - radius, pos + radius, realm );
×
2842
    for ( const auto& tile : area )
×
2843
    {
2844
      if ( !( flags & ITEMS_IGNORE_STATICS ) )
×
2845
      {
2846
        Plib::StaticEntryList slist;
×
2847
        realm->getstatics( slist, tile );
×
2848

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

2866
      if ( !( flags & ITEMS_IGNORE_MULTIS ) )
×
2867
      {
2868
        Plib::StaticList mlist;
×
2869
        realm->readmultis( mlist, tile );
×
2870
        for ( unsigned i = 0; i < mlist.size(); ++i )
×
2871
        {
2872
          if ( mlist[i].graphic != objtype )
×
2873
            continue;
×
2874
          if ( ( z == LIST_IGNORE_Z ) || ( abs( mlist[i].z - z ) < CONST_DEFAULT_ZRANGE ) )
×
2875
          {
2876
            std::unique_ptr<BStruct> arr( new BStruct );
×
2877
            arr->addMember( "x", new BLong( tile.x() ) );
×
2878
            arr->addMember( "y", new BLong( tile.y() ) );
×
2879
            arr->addMember( "z", new BLong( mlist[i].z ) );
×
2880
            arr->addMember( "objtype", new BLong( mlist[i].graphic ) );
×
2881
            newarr->addElement( arr.release() );
×
2882
          }
×
2883
        }
2884
      }
×
2885
    }
2886

2887
    return newarr.release();
×
2888
  }
×
2889
  else
2890
    return new BError( "Invalid parameter" );
×
2891
}
2892

2893

2894
BObjectImp* UOExecutorModule::mf_ListStaticsNearLocationWithFlag(
×
2895
    /* x, y, z, range, flags, realm */ )
2896
{
2897
  Core::Pos2d pos;
×
2898
  int z, flags;
2899
  short range;
2900
  Realms::Realm* realm;
2901

2902
  if ( getRealmParam( 5, &realm ) && getPos2dParam( 0, 1, &pos, realm ) && getParam( 2, z ) &&
×
2903
       getParam( 3, range ) && getParam( 4, flags ) )
×
2904
  {
2905
    std::unique_ptr<ObjArray> newarr( new ObjArray );
×
2906
    Core::Vec2d radius( range, range );
×
2907
    Core::Range2d area( pos - radius, pos + radius, realm );
×
2908
    for ( const auto& tile : area )
×
2909
    {
2910
      if ( !( flags & ITEMS_IGNORE_STATICS ) )
×
2911
      {
2912
        Plib::StaticEntryList slist;
×
2913
        realm->getstatics( slist, tile );
×
2914

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

2933
      if ( !( flags & ITEMS_IGNORE_MULTIS ) )
×
2934
      {
2935
        Plib::StaticList mlist;
×
2936
        realm->readmultis( mlist, tile );
×
2937
        for ( unsigned i = 0; i < mlist.size(); ++i )
×
2938
        {
2939
          if ( ( Plib::tile_uoflags( mlist[i].graphic ) & flags ) )
×
2940
          {
2941
            if ( ( z == LIST_IGNORE_Z ) || ( abs( mlist[i].z - z ) < CONST_DEFAULT_ZRANGE ) )
×
2942
            {
2943
              std::unique_ptr<BStruct> arr( new BStruct );
×
2944
              arr->addMember( "x", new BLong( tile.x() ) );
×
2945
              arr->addMember( "y", new BLong( tile.y() ) );
×
2946
              arr->addMember( "z", new BLong( mlist[i].z ) );
×
2947
              arr->addMember( "objtype", new BLong( mlist[i].graphic ) );
×
2948
              newarr->addElement( arr.release() );
×
2949
            }
×
2950
          }
2951
        }
2952
      }
×
2953
    }
2954

2955
    return newarr.release();
×
2956
  }
×
2957
  else
2958
    return new BError( "Invalid parameter" );
×
2959
}
2960
}  // namespace Module
2961
}  // 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