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

polserver / polserver / 13163655424

05 Feb 2025 06:02PM UTC coverage: 58.476% (+0.4%) from 58.057%
13163655424

push

github

web-flow
Expansion helper classes for central handling of features (#756)

* use uoexpansion enums for A9, B9 flags
added TOL to uoexpansion
currently unsure where this will end

simplified account initialisation, added errorcheck

* Expansion classes for server and account
fixed flags

* use of AccountExpansion,ServerExpansion
extended tests
splitted unittest file
added missing test for Min/MaxAttackRange

* more micro performance for world saving
use all 4 cores in CI builds
fix compiler flag warning on windows

* save test for weight_multiplier

* enable gothic,rustictiles when using HSA expansion

* removed ancient unused files

* addressed comments
renamed ServerExpansion to ServerFeatures
added method for expansion name

* got rid of client UOExpansionFlag which is the same as the accout
expansion

* updated core-changes and docs

* updated generated ssopt

* test for tooltips
changed method names
removed/clarified comments

* npc hitchance was never saved
added missing npc save tests

* hitchance bug exists also for items...
added missing test for movemode

465 of 525 new or added lines in 30 files covered. (88.57%)

16 existing lines in 7 files now uncovered.

41768 of 71427 relevant lines covered (58.48%)

388521.09 hits per line

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

11.14
/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 );
×
486
            else
487
              for_sale->add( tobuy );
×
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 );
×
521
            else
522
              for_sale->add( tobuy );
×
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 );
×
545
          else
546
            for_sale->add( tobuy );
×
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
      auto contpos = vendor_bought->get_random_location();
×
843
      backpack->remove( item );
×
844
      if ( remainder_not_sold != nullptr )
×
845
      {
846
        // FIXME : Add Grid Index Default Location Checks here.
847
        // Remember, if index fails, move to the ground.
848
        backpack->add( remainder_not_sold );
×
849
        update_item_to_inrange( remainder_not_sold );
×
850
        remainder_not_sold = nullptr;
×
851
      }
852
      item->setposition( Core::Pos4d( contpos, 0, vendor_bought->realm() ) );
×
853
      // FIXME : Add Grid Index Default Location Checks here.
854
      // Remember, if index fails, move to the ground.
855
      vendor_bought->add( item );
×
856
      update_item_to_inrange( item );
×
857
      cost += buyprice * amount;
×
858
    }
859

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

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

890
  client->pause();
×
891

892
  send_full_statmsg( client, client->chr );
×
893

894
  send_clear_vendorwindow( client, vendor );
×
895

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

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

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

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

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

936
    Item* item = backpack->find_toplevel( serial );
×
937

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

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

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

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

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

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

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

1019

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

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

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

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

1065
  pos = msg->offset;
×
1066

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

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

1082
    ++numlines;
×
1083
    auto utf16 = Bscript::String::toUTF16( s );
×
1084

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

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

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

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

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

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

1121
  return new BLong( 0 );
×
1122
}
×
1123

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

1138
  u32 layoutdlen = 0;
1✔
1139

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

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

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

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

1183
  bfr->offset = 0;
1✔
1184

1185
  u32 numlines = 0;
1✔
1186
  u32 datadlen = 0;
1✔
1187

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

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

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

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

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

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

1250
  return new BLong( 0 );
1✔
1251
}
1✔
1252

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

1265
private:
1266
  typedef std::map<int, BObjectRef> Contents;
1267
  Contents contents_;
1268

1269
  // not implemented:
1270
  BIntHash& operator=( const BIntHash& );
1271
};
1272

1273
BIntHash::BIntHash() : BObjectImp( OTUnknown ), contents_() {}
×
1274

1275
BIntHash::BIntHash( const BIntHash& ih ) : BObjectImp( OTUnknown ), contents_( ih.contents_ ) {}
×
1276

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

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

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

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

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

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

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

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

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

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

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

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

1365
  Client* client = chr->client;
×
1366

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

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

1379
  msg.Send( client );
×
1380

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

1384
  return new BLong( 1 );
×
1385
}
×
1386

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

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

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

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

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

1419
  msg.Send( chr->client );
×
1420

1421
  return new BLong( 1 );
×
1422
}
×
1423

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

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

1432
  u32 gumpid = cfBEu32( hdr->dialogid );
1✔
1433
  u32 buttonid = cfBEu32( hdr->buttonid );
1✔
1434

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

1460

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

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

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

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

1550
  clear_gumphandler( client, uoemod );
1✔
1551
}
1552

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

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

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

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

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

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

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

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

1615
  return new BLong( 0 );
×
1616
}
×
1617

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

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

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

1669
private:
1670
  // not implemented:
1671
  PolCore& operator=( const PolCore& );
1672
};
1673

1674
PolCore::PolCore() : PolObjectImp( OTPolCoreRef ) {}
7✔
1675

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

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

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

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

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

1712
BObjectImp* GetRunningScriptList()
×
1713
{
1714
  ObjArray* arr = new ObjArray;
×
1715

1716
  const ExecList& runlist = scriptScheduler.getRunlist();
×
1717
  const ExecList& ranlist = scriptScheduler.getRanlist();
×
1718

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

1730
BObjectImp* GetAllScriptList()
×
1731
{
1732
  ObjArray* arr = new ObjArray;
×
1733

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

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

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

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

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

1773

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

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

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

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

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

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

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

1815
  return arr.release();
×
1816
}
×
1817

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

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

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

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

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

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

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

1883
  LONG_COREVAR( last_character_serial, GetCurrentCharSerialNumber() );
3✔
1884
  LONG_COREVAR( last_item_serial, GetCurrentItemSerialNumber() );
3✔
1885

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

1890
  LONG_COREVAR( scripts_late_per_min, GET_PROFILEVAR_PER_MIN( scripts_late ) );
3✔
1891
  LONG_COREVAR( scripts_ontime_per_min, GET_PROFILEVAR_PER_MIN( scripts_ontime ) );
3✔
1892

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

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

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

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

1977
        bobject_alloc.log_stuff( "bobject" );
1978
        uninit_alloc.log_stuff( "uninit" );
1979
        blong_alloc.log_stuff( "blong" );
1980
        double_alloc.log_stuff( "double" );
1981
        ConfigFiles_log_stuff();
1982
        Clib::PrintHeapData();  // Will print endl in llog
1983
      }
1984
#endif
1985
#ifdef ESCRIPT_PROFILE
1986
      DEBUGLOGLN( "FuncName,Count,Min,Max,Sum,Avarage" );
1987
      for ( escript_profile_map::iterator itr = EscriptProfileMap.begin();
1988
            itr != EscriptProfileMap.end(); ++itr )
1989
      {
1990
        DEBUGLOGLN( "{},{},{},{},{},{}", itr->first, itr->second.count, itr->second.min,
1991
                    itr->second.max, itr->second.sum,
1992
                    ( itr->second.sum / ( 1.0 * itr->second.count ) ) );
1993
      }
1994
#endif
1995
      if ( type == 2 )
4✔
1996
      {
1997
        Core::MemoryUsage::log();
1✔
1998
      }
1999
      else if ( type == 3 )
3✔
2000
      {
2001
        POLLOG_ERRORLN( "Forcing crash" );
×
2002
        int* i = nullptr;
×
2003
        *i = 1;
×
2004
      }
2005
      else if ( type == 4 )
3✔
2006
      {
2007
        POLLOG_ERRORLN( "Forcing assert crash" );
×
2008
        passert_always( false );
×
2009
      }
2010
      else if ( type == 5 )
3✔
2011
      {
2012
        Core::scriptScheduler.estimateSize( true );
1✔
2013
      }
2014
      else if ( type == 6 )
2✔
2015
      {
2016
        const String* script;
2017
        if ( !ex.getStringParam( 1, script ) )
1✔
2018
          return new BLong( 0 );
×
2019
        Core::scriptScheduler.logScriptVariables( script->data() );
1✔
2020
        return new BLong( 1 );
1✔
2021
      }
2022
      else if ( type == 7 )
1✔
2023
      {
2024
        Core::objStorageManager.objecthash.Clear( false );
1✔
2025
      }
2026
      return new BLong( 1 );
3✔
2027
    }
2028
    else
2029
      return new BError( "polcore.internal(value) requires 1 parameter." );
×
2030
  }
2031
  return nullptr;
×
2032
}
2033

2034
BObjectImp* UOExecutorModule::mf_POLCore()
7✔
2035
{
2036
  return new PolCore;
7✔
2037
}
2038

2039
BObjectImp* MakeMapCacheImp()
×
2040
{
2041
  ObjArray* arr = new ObjArray;
×
2042
  return arr;
×
2043
}
2044

2045

2046
BObjectImp* UOExecutorModule::mf_CreateAccount()
12✔
2047
{
2048
  const String* acctname;
2049
  const String* password;
2050
  int enabled;
2051
  if ( getStringParam( 0, acctname ) && getStringParam( 1, password ) && getParam( 2, enabled ) )
12✔
2052
  {
2053
    if ( acctname->SafeCharAmt() < acctname->length() )
12✔
2054
    {
2055
      return new BError(
2056
          "Attempted to use username in account creation with non-allowed characters." );
×
2057
    }
2058
    if ( password->SafeCharAmt() < password->length() )
12✔
2059
    {
2060
      return new BError(
2061
          "Attempted to use password in account creation with non-allowed characters." );
×
2062
    }
2063

2064
    if ( Accounts::find_account( acctname->data() ) )
12✔
2065
    {
2066
      return new BError( "Account already exists" );
×
2067
    }
2068

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

2073
    return new Accounts::AccountObjImp( Accounts::AccountPtrHolder( AccountRef( acct ) ) );
12✔
2074
  }
2075
  else
2076
  {
2077
    return new BError( "Invalid parameter type" );
×
2078
  }
2079
}
2080

2081
BObjectImp* UOExecutorModule::mf_FindAccount()
41✔
2082
{
2083
  const String* acctname;
2084
  if ( getStringParam( 0, acctname ) )
41✔
2085
  {
2086
    Accounts::Account* acct = Accounts::find_account( acctname->data() );
41✔
2087
    if ( acct != nullptr )
41✔
2088
    {
2089
      return new Accounts::AccountObjImp( Accounts::AccountPtrHolder( AccountRef( acct ) ) );
30✔
2090
    }
2091
    else
2092
    {
2093
      return new BError( "Account not found." );
11✔
2094
    }
2095
  }
2096
  else
2097
  {
2098
    return new BError( "Invalid parameter type" );
×
2099
  }
2100
}
2101

2102
BObjectImp* UOExecutorModule::mf_ListAccounts()
×
2103
{
2104
  std::unique_ptr<ObjArray> arr( new ObjArray );
×
2105
  for ( unsigned idx = 0; idx < gamestate.accounts.size(); idx++ )
×
2106
  {
2107
    arr->addElement( new String( gamestate.accounts[idx]->name() ) );
×
2108
  }
2109
  return arr.release();
×
2110
}
×
2111

2112
void handle_resurrect_menu( Client* client, PKTBI_2C* msg )
×
2113
{
2114
  if ( msg->choice )
×
2115
  {
2116
    INFO_PRINTLN( "Resurrect Menu Choice: {}", int( msg->choice ) );
×
2117
    // transmit( client, msg, sizeof *msg );
2118
  }
2119

2120
  if ( client->chr != nullptr && client->gd != nullptr && client->gd->resurrect_uoemod != nullptr )
×
2121
  {
2122
    auto& uoex = client->gd->resurrect_uoemod->uoexec();
×
2123
    uoex.ValueStack.back().set( new BObject( new BLong( msg->choice ) ) );
×
2124
    uoex.revive();
×
2125
    client->gd->resurrect_uoemod->resurrect_chr = nullptr;
×
2126
    client->gd->resurrect_uoemod = nullptr;
×
2127
  }
2128
}
×
2129

2130
BObjectImp* UOExecutorModule::mf_SendInstaResDialog()
×
2131
{
2132
  Character* chr;
2133
  if ( !getCharacterParam( 0, chr ) )
×
2134
    return new BError( "Invalid parameter type" );
×
2135
  if ( !chr->has_active_client() )
×
2136
    return new BError( "No client attached" );
×
2137
  if ( chr->client->gd->resurrect_uoemod != nullptr )
×
2138
    return new BError( "Client busy with another instares dialog" );
×
2139

2140
  if ( !uoexec().suspend() )
×
2141
  {
2142
    DEBUGLOGLN(
×
2143
        "Script Error in '{}' PC={}: \n"
2144
        "\tCall to function UO::SendInstaResDialog():\n"
2145
        "\tThe execution of this script can't be blocked!",
2146
        scriptname(), exec.PC );
×
2147
    return new Bscript::BError( "Script can't be blocked" );
×
2148
  }
2149

2150
  PktHelper::PacketOut<PktOut_2C> msg;
×
2151
  msg->Write<u8>( RESURRECT_CHOICE_SELECT );
×
2152
  msg.Send( chr->client );
×
2153
  chr->client->gd->resurrect_uoemod = this;
×
2154
  resurrect_chr = chr;
×
2155

2156
  return new BLong( 0 );
×
2157
}
×
2158

2159
void handle_selcolor( Client* client, PKTBI_95* msg )
×
2160
{
2161
  if ( client->chr != nullptr && client->gd != nullptr && client->gd->selcolor_uoemod != nullptr )
×
2162
  {
2163
    auto& uoex = client->gd->selcolor_uoemod->uoexec();
×
2164
    unsigned short color = cfBEu16( msg->graphic_or_color ) & Plib::VALID_ITEM_COLOR_MASK;
×
2165
    BObject* valstack;
2166
    if ( color >= 2 && color <= 1001 )
×
2167
    {
2168
      valstack = new BObject( new BLong( color ) );
×
2169
    }
2170
    else
2171
    {
2172
      valstack = new BObject( new BError( "Client selected an out-of-range color" ) );
×
2173

2174
      // unsigned short newcolor = ((color - 2) % 1000) + 2;
2175
      POLLOG_ERRORLN( "Client #{:d} (account {}) selected an out-of-range color {:#x}",
×
2176
                      static_cast<unsigned long>( client->instance_ ),
×
2177
                      ( ( client->acct != nullptr ) ? client->acct->name() : "unknown" ), color );
×
2178
    }
2179

2180
    // client->gd->selcolor_uoemod->uoexec.ValueStack.back().set( new BObject( new BLong( color )
2181
    // )
2182
    // );
2183
    uoex.ValueStack.back().set( valstack );
×
2184
    uoex.revive();
×
2185
    client->gd->selcolor_uoemod->selcolor_chr = nullptr;
×
2186
    client->gd->selcolor_uoemod = nullptr;
×
2187
  }
2188
}
×
2189

2190

2191
BObjectImp* UOExecutorModule::mf_SelectColor()
×
2192
{
2193
  Character* chr;
2194
  Item* item;
2195
  if ( !getCharacterParam( 0, chr ) || !getItemParam( 1, item ) )
×
2196
  {
2197
    return new BError( "Invalid parameter type" );
×
2198
  }
2199
  if ( !chr->has_active_client() )
×
2200
    return new BError( "No client attached" );
×
2201
  if ( chr->client->gd->resurrect_uoemod != nullptr )
×
2202
    return new BError( "Client is already selecting a color" );
×
2203

2204
  PktHelper::PacketOut<PktOut_95> msg;
×
2205
  msg->Write<u32>( item->serial_ext );
×
2206
  msg->offset += 2;  // u16 unk
×
2207
  msg->WriteFlipped<u16>( item->graphic );
×
2208

2209
  if ( !uoexec().suspend() )
×
2210
  {
2211
    DEBUGLOGLN(
×
2212
        "Script Error in '{}' PC={}: \n"
2213
        "\tCall to function UO::SelectColor():\n"
2214
        "\tThe execution of this script can't be blocked!",
2215
        scriptname(), exec.PC );
×
2216
    return new Bscript::BError( "Script can't be blocked" );
×
2217
  }
2218

2219
  msg.Send( chr->client );
×
2220

2221
  chr->client->gd->selcolor_uoemod = this;
×
2222
  selcolor_chr = chr;
×
2223
  return new BLong( 0 );
×
2224
}
×
2225

2226
BObjectImp* UOExecutorModule::mf_SendOpenBook()
×
2227
{
2228
  Character* chr;
2229
  Item* book;
2230

2231
  if ( !( getCharacterParam( 0, chr ) && getItemParam( 1, book ) ) )
×
2232
  {
2233
    return new BError( "Invalid parameter type" );
×
2234
  }
2235
  if ( !chr->has_active_client() )
×
2236
  {
2237
    return new BError( "No active client" );
×
2238
  }
2239

2240
  bool writable = book->call_custom_method( "iswritable" )->isTrue();
×
2241
  BObject nlines_ob( book->call_custom_method( "getnumlines" ) );
×
2242
  int nlines;
2243
  if ( nlines_ob.isa( BObjectImp::OTLong ) )
×
2244
  {
2245
    BLong* blong = nlines_ob.impptr<BLong>();
×
2246
    nlines = blong->value();
×
2247
  }
2248
  else
2249
  {
2250
    return new BError( "book.GetNumLines() did not return an Integer" );
×
2251
  }
2252
  std::string title = book->call_custom_method( "gettitle" )->getStringRep();
×
2253
  std::string author = book->call_custom_method( "getauthor" )->getStringRep();
×
2254

2255
  int npages = ( nlines + 7 ) / 8;
×
2256

2257
  BObject contents_ob( UninitObject::create() );
×
2258
  if ( writable )
×
2259
  {
2260
    contents_ob.setimp( book->call_custom_method( "getcontents" ).impptr() );
×
2261
    if ( !contents_ob.isa( BObjectImp::OTArray ) )
×
2262
    {
2263
      if ( contents_ob.isa( BObjectImp::OTError ) )
×
2264
        return contents_ob->copy();
×
2265
      else
2266
        return new BError( "book.GetContents() must return an array" );
×
2267
    }
2268
  }
2269

2270
  PktHelper::PacketOut<PktOut_93> msg93;
×
2271
  msg93->Write<u32>( book->serial_ext );
×
2272
  msg93->Write<u8>( writable ? 1u : 0u );
×
2273
  msg93->Write<u8>( 1u );
×
2274
  msg93->WriteFlipped<u16>( static_cast<u16>( npages ) );
×
2275
  msg93->Write( title.c_str(), 60, false );
×
2276
  msg93->Write( author.c_str(), 30, false );
×
2277
  msg93.Send( chr->client );
×
2278

2279
  if ( writable )
×
2280
  {
2281
    PktHelper::PacketOut<PktOut_66> msg;
×
2282
    msg->offset += 2;
×
2283
    msg->Write<u32>( book->serial_ext );
×
2284
    msg->WriteFlipped<u16>( static_cast<u16>( npages ) );
×
2285

2286
    ObjArray* arr = contents_ob.impptr<ObjArray>();
×
2287

2288
    int linenum = 1;
×
2289
    for ( int page = 1; page <= npages; ++page )
×
2290
    {
2291
      if ( msg->offset + 4 > static_cast<int>( sizeof msg->buffer ) )
×
2292
      {
2293
        return new BError( "Buffer overflow" );
×
2294
      }
2295
      msg->WriteFlipped<u16>( static_cast<u16>( page ) );
×
2296
      u16 offset = msg->offset;
×
2297
      msg->offset += 2;
×
2298

2299
      int pagelines;
2300
      for ( pagelines = 0; pagelines < 8 && linenum <= nlines; ++pagelines, ++linenum )
×
2301
      {
2302
        const BObjectImp* line_imp = arr->imp_at( linenum );
×
2303
        std::string linetext;
×
2304
        if ( line_imp )
×
2305
          linetext = line_imp->getStringRep();  // This is UTF-8
×
2306
        if ( msg->offset + linetext.size() + 1 > sizeof msg->buffer )
×
2307
        {
2308
          return new BError( "Buffer overflow" );
×
2309
        }
2310
        msg->Write( linetext.c_str(), static_cast<u16>( linetext.size() + 1 ) );
×
2311
      }
×
2312
      u16 len = msg->offset;
×
2313
      msg->offset = offset;
×
2314
      msg->WriteFlipped<u16>( static_cast<u16>( pagelines ) );
×
2315
      msg->offset = len;
×
2316
    }
2317

2318
    /*
2319
            int linenum = 1;
2320
            for( int page = 1; page <= npages; ++page )
2321
            {
2322
            PKTBI_66_CONTENTS* ppage = reinterpret_cast<PKTBI_66_CONTENTS*>(&buffer[msglen]);
2323
            msglen += sizeof(*ppage);
2324
            if (msglen > sizeof buffer)
2325
            return new BError( "Buffer overflow" );
2326
            ppage->page = ctBEu16( page );
2327

2328
            int pagelines;
2329
            for( pagelines = 0; pagelines < 8 && linenum <= nlines; ++pagelines, ++linenum )
2330
            {
2331
            string linetext;
2332

2333
            BObjectVec params;
2334
            params.push_back( BObject(new BLong(linenum)) );
2335
            BObject line_ob = book->call_custom_method( "getline", params );
2336
            linetext = line_ob->getStringRep();
2337

2338
            char* linebuf = reinterpret_cast<char*>(&buffer[msglen]);
2339
            msglen += linetext.size()+1;
2340
            if (msglen > sizeof buffer)
2341
            return new BError( "Buffer overflow" );
2342
            memcpy( linebuf, linetext.c_str(), linetext.size()+1 );
2343
            }
2344
            ppage->lines = ctBEu16( pagelines );
2345
            }
2346
            */
2347
    u16 len = msg->offset;
×
2348
    msg->offset = 1;
×
2349
    msg->WriteFlipped<u16>( len );
×
2350
    msg.Send( chr->client, len );
×
2351
  }
×
2352

2353
  return new BLong( 1 );
×
2354
}
×
2355

2356
void read_book_page_handler( Client* client, PKTBI_66* msg )
×
2357
{
2358
  unsigned int book_serial = cfBEu32( msg->book_serial );
×
2359
  u16 page = cfBEu16( msg->page );
×
2360
  Item* book = find_legal_item( client->chr, book_serial );
×
2361
  if ( book == nullptr )
×
2362
  {
2363
    POLLOGLN( "Unable to find book {:#x} for character {:#x}", book_serial, client->chr->serial );
×
2364
    return;
×
2365
  }
2366

2367
  if ( msg->lines == 0xFFFF )
×
2368
  {
2369
    BObject nlines_ob( book->call_custom_method( "getnumlines" ) );
×
2370
    int nlines;
2371
    if ( nlines_ob.isa( BObjectImp::OTLong ) )
×
2372
    {
2373
      BLong* blong = nlines_ob.impptr<BLong>();
×
2374
      nlines = blong->value();
×
2375
    }
2376
    else
2377
    {
2378
      return;
×
2379
    }
2380

2381
    PktHelper::PacketOut<PktOut_66> msgOut;
×
2382
    msgOut->offset += 2;
×
2383
    msgOut->Write<u32>( book->serial_ext );
×
2384
    msgOut->WriteFlipped<u16>( 1u );
×
2385

2386
    int linenum = ( page - 1 ) * 8 + 1;
×
2387

2388
    msgOut->WriteFlipped<u16>( page );
×
2389
    u16 offset = msgOut->offset;
×
2390
    msgOut->offset += 2;
×
2391

2392
    int pagelines;
2393
    for ( pagelines = 0; pagelines < 8 && linenum <= nlines; ++pagelines, ++linenum )
×
2394
    {
2395
      std::string linetext;
×
2396

2397
      BObjectImpRefVec params;
×
2398
      params.push_back( ref_ptr<BObjectImp>( new BLong( linenum ) ) );
×
2399
      BObject line_ob = book->call_custom_method( "getline", params );
×
2400
      linetext = line_ob->getStringRep();  // this is UTF-8
×
2401

2402
      if ( msgOut->offset + linetext.size() + 1 > sizeof msgOut->buffer )
×
2403
      {
2404
        return;
×
2405
      }
2406
      msgOut->Write( linetext.c_str(), static_cast<u16>( linetext.size() + 1 ) );
×
2407
    }
×
2408

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

2439
      BObjectImpRefVec params;
×
2440
      params.push_back( ref_ptr<BObjectImp>( new BLong( ( page - 1 ) * 8 + i ) ) );
×
2441
      params.push_back( ref_ptr<BObjectImp>( new String( line ) ) );
×
2442

2443
      BObject page_on = book->call_custom_method( "setline", params );
×
2444
    }
×
2445
  }
2446
}
2447

2448
char strip_ctrl_chars( char c )
×
2449
{
2450
  if ( c < 0x20 )
×
2451
    return 0x20;
×
2452
  else
2453
    return c;
×
2454
}
2455

2456
void open_book_handler( Client* client, PKTBI_93* msg )
×
2457
{
2458
  // fdump( stdout, msg, sizeof *msg );
2459

2460
  // string title( msg->title, sizeof msg->title );
2461
  // string author( msg->author, sizeof msg->author );
2462

2463
  // Dave changed this 12/19 from sizeof msg->title. The protocol defines garbage after the
2464
  // terminator for
2465
  // the title and author strings, so we were writing this garbage into save files. This caused
2466
  // some
2467
  //"No SERIAL property" bugs, because the parser barfed on the bad characters.
2468
  std::string title( msg->title, strlen( msg->title ) );
×
2469
  std::string author( msg->author, strlen( msg->author ) );
×
2470

2471
  // dave 1/20/3 cheaters insert cntrl chars into books causing severe problems
2472
  std::transform( title.begin(), title.end(), title.begin(), strip_ctrl_chars );
×
2473
  std::transform( author.begin(), author.end(), author.begin(), strip_ctrl_chars );
×
2474

2475

2476
  unsigned int book_serial = cfBEu32( msg->serial );
×
2477
  Item* book = find_legal_item( client->chr, book_serial );
×
2478
  if ( book == nullptr )
×
2479
  {
2480
    POLLOGLN( "Unable to find book {:#x} for character {:#x}", book_serial, client->chr->serial );
×
2481
    return;
×
2482
  }
2483
  BObjectImpRefVec params;
×
2484
  params.push_back( ref_ptr<BObjectImp>( new String( title ) ) );
×
2485
  book->call_custom_method( "settitle", params );
×
2486

2487
  params[0].set( new String( author ) );
×
2488
  book->call_custom_method( "setauthor", params );
×
2489
}
×
2490

2491
BObjectImp* UOExecutorModule::mf_SendHousingTool()
×
2492
{
2493
  Character* chr;
2494
  Multi::UMulti* multi;
2495

2496
  if ( !( getCharacterParam( 0, chr ) && getMultiParam( 1, multi ) ) )
×
2497
  {
2498
    return new BError( "Invalid parameter type" );
×
2499
  }
NEW
2500
  if ( !chr->client->acctSupports( Plib::ExpansionVersion::AOS ) )
×
2501
    return new BError( "Charater does not have AOS enabled." );
×
2502

2503
  if ( multi == nullptr )
×
2504
    return new BError( "House not found." );
×
2505

2506
  Multi::UHouse* house = multi->as_house();
×
2507
  if ( house == nullptr )
×
2508
    return new BError( "Not a House multi." );
×
2509

2510
  if ( !house->IsCustom() )
×
2511
    return new BError( "Not a Custom House." );
×
2512

2513
  if ( house->editing )
×
2514
    return new BError( "House currently being customized." );
×
2515

2516
  if ( house->IsWaitingForAccept() )
×
2517
    return new BError( "House currently being waiting for a commit" );
×
2518

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

2522
  chr->client->gd->custom_house_serial = house->serial;
×
2523
  chr->client->gd->custom_house_chrserial = chr->serial;
×
2524

2525
  {
2526
    PktHelper::PacketOut<PktOut_BF_Sub20> msg;
×
2527
    msg->WriteFlipped<u16>( 17u );
×
2528
    msg->offset += 2;  // sub
×
2529
    msg->Write<u32>( house->serial_ext );
×
2530
    msg->Write<u8>( 0x4u );          // begin
×
2531
    msg->offset += 2;                // u16 unk2 FIXME what's the meaning
×
2532
    msg->Write<u32>( 0xFFFFFFFFu );  // fixme
×
2533
    msg->Write<u8>( 0xFFu );         // fixme
×
2534
    msg.Send( chr->client );
×
2535
  }
×
2536
  Core::Pos4d newpos = house->pos() + Core::Vec3d( 0, 0, 7 );
×
2537
  move_character_to( chr, newpos, MOVEITEM_FORCELOCATION );
×
2538

2539
  house->WorkingDesign.AddComponents( house );
×
2540
  house->CurrentDesign.AddComponents( house );
×
2541
  house->WorkingDesign.ClearComponents( house );
×
2542
  Multi::ItemList itemlist;
×
2543
  Multi::MobileList moblist;
×
2544
  Multi::UHouse::list_contents( house, itemlist, moblist );
×
2545
  const Multi::MultiDef& def = house->multidef();
×
2546
  while ( !itemlist.empty() )
×
2547
  {
2548
    Item* item = itemlist.front();
×
2549
    send_remove_object_if_inrange( chr->client, item );
×
2550
    itemlist.pop_front();
×
2551
  }
2552

2553
  while ( !moblist.empty() )
×
2554
  {
2555
    Character* multichr = moblist.back();
×
2556
    if ( multichr != chr )
×
2557
    {
2558
      Core::Pos4d pos = house->pos() + Core::Vec2d( def.minrxyz.x(), def.maxrxyz.y() + 1 );
×
2559
      move_character_to( multichr, pos, MOVEITEM_FORCELOCATION );
×
2560
    }
2561
    moblist.pop_back();
×
2562
  }
2563

2564
  house->editing = true;
×
2565
  house->editing_floor_num = 1;
×
2566
  CustomHousesSendFull( house, chr->client, Multi::HOUSE_DESIGN_WORKING );
×
2567

2568
  return new BLong( 1 );
×
2569
}
×
2570

2571
BObjectImp* UOExecutorModule::mf_SendCharacterRaceChanger( /* Character */ )
×
2572
{
2573
  Character* chr;
2574
  if ( getCharacterParam( 0, chr ) )
×
2575
  {
2576
    PktHelper::PacketOut<PktOut_BF_Sub2A> msg;
×
2577
    msg->WriteFlipped<u16>( 7u );
×
2578
    msg->offset += 2;  // sub
×
2579
    msg->Write<u8>( chr->gender );
×
2580
    msg->Write<u8>( chr->race + 1u );
×
2581
    msg.Send( chr->client );
×
2582
    return new BLong( 1 );
×
2583
  }
×
2584
  else
2585
    return new BError( "Invalid parameter" );
×
2586
}
2587

2588

2589
void character_race_changer_handler( Client* client, PKTBI_BF* msg )
×
2590
{
2591
  Item* tmpitem;
2592

2593
  if ( msg->msglen == ctBEu16( 5 ) )
×
2594
    return;
×
2595

2596
  if ( ( msg->characterracechanger.result.BodyHue == 0 ) &&
×
2597
       ( msg->characterracechanger.result.HairId == 0 ) &&
×
2598
       ( msg->characterracechanger.result.HairHue == 0 ) &&
×
2599
       ( msg->characterracechanger.result.BeardId == 0 ) &&
×
2600
       ( msg->characterracechanger.result.BeardHue == 0 ) )
×
2601
    return;
×
2602

2603
  client->chr->setcolor( cfBEu16( msg->characterracechanger.result.BodyHue ) | 0x8000 );
×
2604
  client->chr->truecolor = client->chr->color;
×
2605

2606
  client->chr->on_color_changed();
×
2607

2608
  // Create Hair
2609
  if ( client->chr->layer_is_equipped( LAYER_HAIR ) )
×
2610
    destroy_item( client->chr->wornitem( LAYER_HAIR ) );
×
2611

2612
  if ( validhair( cfBEu16( msg->characterracechanger.result.HairId ) ) )
×
2613
  {
2614
    tmpitem = Item::create( cfBEu16( msg->characterracechanger.result.HairId ) );
×
2615
    tmpitem->layer = LAYER_HAIR;
×
2616
    tmpitem->color = cfBEu16( msg->characterracechanger.result.HairHue );
×
2617
    client->chr->equip( tmpitem );
×
2618
    send_wornitem_to_inrange( client->chr, tmpitem );
×
2619
  }
2620

2621
  // Create Beard
2622
  if ( client->chr->layer_is_equipped( LAYER_BEARD ) )
×
2623
    destroy_item( client->chr->wornitem( LAYER_BEARD ) );
×
2624

2625
  if ( validbeard( cfBEu16( msg->characterracechanger.result.BeardId ) ) )
×
2626
  {
2627
    tmpitem = Item::create( cfBEu16( msg->characterracechanger.result.BeardId ) );
×
2628
    tmpitem->layer = LAYER_BEARD;
×
2629
    tmpitem->color = cfBEu16( msg->characterracechanger.result.BeardHue );
×
2630
    client->chr->equip( tmpitem );
×
2631
    send_wornitem_to_inrange( client->chr, tmpitem );
×
2632
  }
2633
}
2634

2635
// Called when selection made or when selection canceled with nullptr parameters
2636
void popup_menu_selection_made( Network::Client* client, u32 serial, u16 id )
×
2637
{
2638
  if ( client == nullptr )
×
2639
    return;
×
2640

2641
  Character* chr = client->chr;
×
2642
  if ( chr == nullptr || chr->client->gd->popup_menu_selection_uoemod == nullptr )
×
2643
    return;
×
2644

2645
  // The function sending the PopUp menu is responsible to set this
2646
  passert_always_r(
×
2647
      chr->client->gd->popup_menu_selection_uoemod->popup_menu_selection_above != nullptr,
2648
      "Bug in handling PopUp menu selection. Please report this on the forums." );
2649

2650
  auto& uoex = chr->client->gd->popup_menu_selection_uoemod->uoexec();
×
2651

2652
  if ( id && serial )
×
2653
  {
2654
    if ( chr->client->gd->popup_menu_selection_uoemod->popup_menu_selection_above->serial ==
×
2655
         serial )
2656
      uoex.ValueStack.back().set( new BObject( new BLong( id ) ) );
×
2657
    else
2658
      POLLOG_INFO( "{}/{} send an unexpected popup reply for {:#x}.\n", client->acct->name(),
×
2659
                   client->chr->name(), serial );
×
2660
  }
2661

2662
  uoex.revive();
×
2663
  chr->client->gd->popup_menu_selection_uoemod->popup_menu_selection_chr = nullptr;
×
2664
  chr->client->gd->popup_menu_selection_uoemod->popup_menu_selection_above = nullptr;
×
2665
  chr->client->gd->popup_menu_selection_uoemod = nullptr;
×
2666
}
2667

2668
/// Sends a PopUp/Context menu
2669
BObjectImp* UOExecutorModule::mf_SendPopUpMenu()
×
2670
{
2671
  Character* chr;
2672
  UObject* above;
2673
  ObjArray* menu_arr;
2674
  if ( !( getCharacterParam( 0, chr ) && getUObjectParam( 1, above ) &&
×
2675
          exec.getObjArrayParam( 2, menu_arr ) ) )
×
2676
  {
2677
    return new BError( "Invalid parameter" );
×
2678
  }
2679
  if ( !chr->has_active_client() )
×
2680
    return new BError( "No client attached" );
×
2681
  if ( !menu_arr->ref_arr.size() )
×
2682
    return new BError( "Can't send empty menu" );
×
2683
  if ( menu_arr->ref_arr.size() > 0xfffe )
×
2684
    return new BError( "Too many entries in menu" );
×
2685

2686
  // Prepare packet
2687
  struct Entry
2688
  {
2689
    u32 cliloc = 0;
2690
    u16 flags = 0;
2691
    u16 color = 0;
2692
  };
2693
  std::vector<Entry> entries;
×
2694
  bool newformat = false;
×
2695
  if ( chr->client->ClientType & CLIENTTYPE_UOKR )
×
2696
    newformat = true;
×
2697

2698
  for ( u16 i = 0; i < menu_arr->ref_arr.size(); ++i )
×
2699
  {
2700
    BObject* bo = menu_arr->ref_arr[i].get();
×
2701
    if ( bo == nullptr )
×
2702
      continue;
×
2703
    BObjectImp* imp = bo->impptr();
×
2704

2705
    if ( entries.size() >= 255 )  // overflow
×
2706
      return new BError( "Too many entries in menu" );
×
2707
    Entry entry;
×
2708
    if ( auto* lng = impptrIf<BLong>( imp ) )
×
2709
    {
2710
      // Short form: menu is just an int
2711
      entry.cliloc = lng->value();
×
2712
    }
2713
    else if ( auto* elem = impptrIf<BStruct>( imp ) )
×
2714
    {
2715
      // Full form: menu is a struct
2716

2717
      BObjectImp* cl = const_cast<BObjectImp*>( elem->FindMember( "cliloc" ) );
×
2718
      if ( cl == nullptr )
×
2719
        return new BError( "Missing cliloc for menu element" );
×
2720
      if ( auto* lngcliloc = impptrIf<BLong>( cl ) )
×
2721
        entry.cliloc = lngcliloc->value();
×
2722
      else
2723
        return new BError( "Invalid cliloc for menu element" );
×
2724

2725
      const BObjectImp* ds = elem->FindMember( "disabled" );
×
2726
      if ( ds != nullptr && ds->isTrue() )
×
2727
        entry.flags |= PKTBI_BF_14_ENTRIES::POPUP_MENU_LOCKED;
×
2728

2729
      const BObjectImp* ar = elem->FindMember( "arrow" );
×
2730
      if ( ar != nullptr && ar->isTrue() )
×
2731
        entry.flags |= PKTBI_BF_14_ENTRIES::POPUP_MENU_ARROW;
×
2732

2733
      BObjectImp* co = const_cast<BObjectImp*>( elem->FindMember( "color" ) );
×
2734
      if ( auto* colng = impptrIf<BLong>( co ) )
×
2735
      {
2736
        entry.color = static_cast<u16>( colng->value() );
×
2737
        entry.flags |= PKTBI_BF_14_ENTRIES::POPUP_MENU_COLOR;
×
2738
      }
2739
    }
2740
    else
2741
      return new BError( "Menu elements must be int or struct" );
×
2742

2743
    if ( !newformat && ( entry.cliloc < 3000000 || entry.cliloc > 3065535 ) )
×
2744
    {
2745
      if ( chr->client->ClientType & CLIENTTYPE_6017 )
×
2746
        newformat = true;
×
2747
      else
2748
        return new BError( "Cliloc out of range in menu" );
×
2749
    }
2750
    entries.push_back( entry );
×
2751
  }
2752

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

2780
  // Add lengths and send
2781
  u16 len = msg->offset;
×
2782
  msg->offset = 1;
×
2783
  msg->WriteFlipped<u16>( len );
×
2784
  msg.Send( chr->client, len );
×
2785

2786
  // Cancel any previously waiting popup response
2787
  if ( auto& old = chr->client->gd->popup_menu_selection_uoemod; old != nullptr )
×
2788
  {
2789
    // reset uomod members otherwise the deconstructor will reset the next request
2790
    old->popup_menu_selection_chr = nullptr;
×
2791
    old->popup_menu_selection_above = nullptr;
×
2792
    old->uoexec().revive();
×
2793

2794
    old = nullptr;
×
2795
    chr->client->gd->on_popup_menu_selection = nullptr;
×
2796
  }
2797

2798
  // Suspend the script first
2799
  if ( !uoexec().suspend() )
×
2800
  {
2801
    DEBUGLOGLN(
×
2802
        "Script Error in '{}' PC={}: \n"
2803
        "\tCall to function UO::SendPopupMenu():\n"
2804
        "\tThe execution of this script can't be blocked!",
2805
        scriptname(), exec.PC );
×
2806
    return new Bscript::BError( "Script can't be blocked" );
×
2807
  }
2808

2809
  // Prepare to restart the script once the response arrives
2810
  chr->client->gd->on_popup_menu_selection = popup_menu_selection_made;
×
2811
  chr->client->gd->popup_menu_selection_uoemod = this;
×
2812
  popup_menu_selection_chr = chr;
×
2813
  popup_menu_selection_above = above;
×
2814

2815
  return new BLong( 0 );
×
2816
}
×
2817

2818
BObjectImp* UOExecutorModule::mf_SingleClick()
×
2819
{
2820
  Character* chr = nullptr;
×
2821
  UObject* what = nullptr;
×
2822

2823
  if ( !getCharacterParam( 0, chr ) || !getUObjectParam( 1, what ) )
×
2824
    return new BError( "Invalid parameter" );
×
2825

2826
  if ( !chr->has_active_client() )
×
2827
    return new BError( "Mobile has no active client" );
×
2828

2829
  // If it got here, clear any errors from getUObjectParam/getCharacterParam
2830
  exec.setFunctionResult( nullptr );
×
2831

2832
  singleclick( chr->client, what->serial );
×
2833
  return new BLong( 1 );
×
2834
}
2835

2836
BObjectImp* UOExecutorModule::mf_ListStaticsNearLocationOfType(
×
2837
    /* x, y, z, range, objtype, flags, realm */ )
2838
{
2839
  Core::Pos2d pos;
×
2840
  int z, flags;
2841
  short range;
2842
  unsigned int objtype;
2843
  Realms::Realm* realm;
2844

2845
  if ( getRealmParam( 6, &realm ) && getPos2dParam( 0, 1, &pos, realm ) && getParam( 2, z ) &&
×
2846
       getParam( 3, range ) && getObjtypeParam( 4, objtype ) && getParam( 5, flags ) )
×
2847
  {
2848
    std::unique_ptr<ObjArray> newarr( new ObjArray );
×
2849
    Core::Vec2d radius( range, range );
×
2850
    Core::Range2d area( pos - radius, pos + radius, realm );
×
2851
    for ( const auto& tile : area )
×
2852
    {
2853
      if ( !( flags & ITEMS_IGNORE_STATICS ) )
×
2854
      {
2855
        Plib::StaticEntryList slist;
×
2856
        realm->getstatics( slist, tile );
×
2857

2858
        for ( unsigned i = 0; i < slist.size(); ++i )
×
2859
        {
2860
          if ( slist[i].objtype != objtype )
×
2861
            continue;
×
2862
          if ( ( z == LIST_IGNORE_Z ) || ( abs( slist[i].z - z ) < CONST_DEFAULT_ZRANGE ) )
×
2863
          {
2864
            std::unique_ptr<BStruct> arr( new BStruct );
×
2865
            arr->addMember( "x", new BLong( tile.x() ) );
×
2866
            arr->addMember( "y", new BLong( tile.y() ) );
×
2867
            arr->addMember( "z", new BLong( slist[i].z ) );
×
2868
            arr->addMember( "objtype", new BLong( slist[i].objtype ) );
×
2869
            arr->addMember( "hue", new BLong( slist[i].hue ) );
×
2870
            newarr->addElement( arr.release() );
×
2871
          }
×
2872
        }
2873
      }
×
2874

2875
      if ( !( flags & ITEMS_IGNORE_MULTIS ) )
×
2876
      {
2877
        Plib::StaticList mlist;
×
2878
        realm->readmultis( mlist, tile );
×
2879
        for ( unsigned i = 0; i < mlist.size(); ++i )
×
2880
        {
2881
          if ( mlist[i].graphic != objtype )
×
2882
            continue;
×
2883
          if ( ( z == LIST_IGNORE_Z ) || ( abs( mlist[i].z - z ) < CONST_DEFAULT_ZRANGE ) )
×
2884
          {
2885
            std::unique_ptr<BStruct> arr( new BStruct );
×
2886
            arr->addMember( "x", new BLong( tile.x() ) );
×
2887
            arr->addMember( "y", new BLong( tile.y() ) );
×
2888
            arr->addMember( "z", new BLong( mlist[i].z ) );
×
2889
            arr->addMember( "objtype", new BLong( mlist[i].graphic ) );
×
2890
            newarr->addElement( arr.release() );
×
2891
          }
×
2892
        }
2893
      }
×
2894
    }
2895

2896
    return newarr.release();
×
2897
  }
×
2898
  else
2899
    return new BError( "Invalid parameter" );
×
2900
}
2901

2902

2903
BObjectImp* UOExecutorModule::mf_ListStaticsNearLocationWithFlag(
×
2904
    /* x, y, z, range, flags, realm */ )
2905
{
2906
  Core::Pos2d pos;
×
2907
  int z, flags;
2908
  short range;
2909
  Realms::Realm* realm;
2910

2911
  if ( getRealmParam( 5, &realm ) && getPos2dParam( 0, 1, &pos, realm ) && getParam( 2, z ) &&
×
2912
       getParam( 3, range ) && getParam( 4, flags ) )
×
2913
  {
2914
    std::unique_ptr<ObjArray> newarr( new ObjArray );
×
2915
    Core::Vec2d radius( range, range );
×
2916
    Core::Range2d area( pos - radius, pos + radius, realm );
×
2917
    for ( const auto& tile : area )
×
2918
    {
2919
      if ( !( flags & ITEMS_IGNORE_STATICS ) )
×
2920
      {
2921
        Plib::StaticEntryList slist;
×
2922
        realm->getstatics( slist, tile );
×
2923

2924
        for ( unsigned i = 0; i < slist.size(); ++i )
×
2925
        {
2926
          if ( ( Plib::tile_uoflags( slist[i].objtype ) & flags ) )
×
2927
          {
2928
            if ( ( z == LIST_IGNORE_Z ) || ( abs( slist[i].z - z ) < CONST_DEFAULT_ZRANGE ) )
×
2929
            {
2930
              std::unique_ptr<BStruct> arr( new BStruct );
×
2931
              arr->addMember( "x", new BLong( tile.x() ) );
×
2932
              arr->addMember( "y", new BLong( tile.y() ) );
×
2933
              arr->addMember( "z", new BLong( slist[i].z ) );
×
2934
              arr->addMember( "objtype", new BLong( slist[i].objtype ) );
×
2935
              arr->addMember( "hue", new BLong( slist[i].hue ) );
×
2936
              newarr->addElement( arr.release() );
×
2937
            }
×
2938
          }
2939
        }
2940
      }
×
2941

2942
      if ( !( flags & ITEMS_IGNORE_MULTIS ) )
×
2943
      {
2944
        Plib::StaticList mlist;
×
2945
        realm->readmultis( mlist, tile );
×
2946
        for ( unsigned i = 0; i < mlist.size(); ++i )
×
2947
        {
2948
          if ( ( Plib::tile_uoflags( mlist[i].graphic ) & flags ) )
×
2949
          {
2950
            if ( ( z == LIST_IGNORE_Z ) || ( abs( mlist[i].z - z ) < CONST_DEFAULT_ZRANGE ) )
×
2951
            {
2952
              std::unique_ptr<BStruct> arr( new BStruct );
×
2953
              arr->addMember( "x", new BLong( tile.x() ) );
×
2954
              arr->addMember( "y", new BLong( tile.y() ) );
×
2955
              arr->addMember( "z", new BLong( mlist[i].z ) );
×
2956
              arr->addMember( "objtype", new BLong( mlist[i].graphic ) );
×
2957
              newarr->addElement( arr.release() );
×
2958
            }
×
2959
          }
2960
        }
2961
      }
×
2962
    }
2963

2964
    return newarr.release();
×
2965
  }
×
2966
  else
2967
    return new BError( "Invalid parameter" );
×
2968
}
2969
}  // namespace Module
2970
}  // 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