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

polserver / polserver / 21100551564

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

Pull #857

github

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

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

48 existing lines in 26 files now uncovered.

44445 of 73458 relevant lines covered (60.5%)

515341.61 hits per line

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

15.57
/pol-core/pol/module/npcmod.cpp
1
/** @file
2
 *
3
 * @par History
4
 * - 2009/09/14 MuadDib:   CreateItem() now has slot support.
5
 */
6

7
#include "npcmod.h"
8
#include <iostream>
9
#include <stddef.h>
10
#include <string>
11

12
#include "../../bscript/berror.h"
13
#include "../../bscript/bobject.h"
14
#include "../../bscript/impstr.h"
15
#include "../../clib/clib.h"
16
#include "../../clib/logfacility.h"
17
#include "../../clib/random.h"
18
#include "../../clib/rawtypes.h"
19
#include "../../clib/stlutil.h"
20
#include "../../clib/strutil.h"
21
#include "../../plib/objtype.h"
22
#include "../../plib/poltype.h"
23
#include "../containr.h"
24
#include "../item/item.h"
25
#include "../mobile/boundbox.h"
26
#include "../mobile/charactr.h"
27
#include "../mobile/npc.h"
28
#include "../mobile/ufacing.h"
29
#include "../network/packethelper.h"
30
#include "../network/packets.h"
31
#include "../network/pktdef.h"
32
#include "../polobject.h"
33
#include "../uoscrobj.h"
34
#include "../uworld.h"
35
#include "osmod.h"
36
#include "unimod.h"
37

38
#include <module_defs/npc.h>
39

40

41
namespace Pol::Module
42
{
43
using namespace Bscript;
44
NPCExecutorModule::NPCExecutorModule( Executor& ex, Mobile::NPC& npc )
10✔
45
    : TmplExecutorModule<NPCExecutorModule, Core::PolModule>( ex ), npcref( &npc ), npc( npc )
10✔
46
{
47
  os_module = static_cast<OSExecutorModule*>( exec.findModule( "OS" ) );
10✔
48
  if ( os_module == nullptr )
10✔
49
    throw std::runtime_error( "NPCExecutorModule needs OS module!" );
×
50
}
10✔
51

52
NPCExecutorModule::~NPCExecutorModule()
20✔
53
{
54
  if ( npc.ex == &exec )
10✔
55
    npc.ex = nullptr;
10✔
56
}
20✔
57

58
BApplicObjType bounding_box_type;
59

60
class BoundingBoxObjImp final : public Core::PolApplicObj<Mobile::BoundingBox>
61
{
62
public:
63
  BoundingBoxObjImp() : PolApplicObj<Mobile::BoundingBox>( &bounding_box_type ) {}
×
64
  explicit BoundingBoxObjImp( const Mobile::BoundingBox& b )
×
65
      : PolApplicObj<Mobile::BoundingBox>( &bounding_box_type, b )
×
66
  {
67
  }
×
68
  const char* typeOf() const override { return "BoundingBox"; }
×
69
  u8 typeOfInt() const override { return OTBoundingBox; }
×
70
  BObjectImp* copy() const override { return new BoundingBoxObjImp( value() ); }
×
71
};
72

73
/* IsLegalMove: parameters (move, bounding box)*/
74
BObjectImp* NPCExecutorModule::mf_IsLegalMove()
×
75
{
76
  String* facing_str = static_cast<String*>( exec.getParamImp( 0, BObjectImp::OTString ) );
×
77
  BApplicObjBase* appobj =
78
      static_cast<BApplicObjBase*>( exec.getParamImp( 1, BObjectImp::OTApplicObj ) );
×
79
  if ( facing_str == nullptr || appobj == nullptr || appobj->object_type() != &bounding_box_type )
×
80
  {
81
    return new BLong( 0 );
×
82
  }
83

84
  BApplicObj<Mobile::BoundingBox>* ao_bbox =
×
85
      static_cast<BApplicObj<Mobile::BoundingBox>*>( appobj );
86
  const Mobile::BoundingBox& bbox = ao_bbox->value();
×
87

88
  Core::UFACING facing;
89
  if ( Mobile::DecodeFacing( facing_str->value().c_str(), facing ) == false )
×
90
    return new BLong( 0 );
×
91

92
  auto pos = npc.pos().move( facing );
×
93

94
  return new BLong( bbox.contains( pos.xy() ) );
×
95
}
96

97
/* CanMove: parameters (facing)*/
98
BObjectImp* NPCExecutorModule::mf_CanMove()
×
99
{
100
  if ( exec.fparams.size() == 1 )
×
101
  {
102
    BObjectImp* param0 = exec.getParamImp( 0 );
×
103

104
    if ( auto* s = impptrIf<String>( param0 ) )
×
105
    {
106
      const char* dir = s->data();
×
107
      Core::UFACING facing;
108

109
      if ( Mobile::DecodeFacing( dir, facing ) == false )
×
110
      {
111
        DEBUGLOGLN(
×
112
            "Script Error in '{}' PC={}: \n"
113
            "\tCall to function npc::canmove():\n"
114
            "\tParameter 0: Expected direction: N S E W NW NE SW SE, got {}",
115
            scriptname(), exec.PC, dir );
×
116
        return new BError( "Invalid facing value" );
×
117
      }
118

119
      return new BLong( npc.could_move( facing ) ? 1 : 0 );
×
120
    }
NEW
121
    if ( auto* l = impptrIf<BLong>( param0 ) )
×
122
    {
123
      Core::UFACING facing = static_cast<Core::UFACING>( l->value() & PKTIN_02_FACING_MASK );
×
124
      return new BLong( npc.could_move( facing ) ? 1 : 0 );
×
125
    }
126

NEW
127
    DEBUGLOGLN(
×
128
        "Script Error in '{}' PC={}: \n"
129
        "\tCall to function npc::canmove():\n"
130
        "\tParameter 0: Expected direction, got datatype {}",
NEW
131
        scriptname(), exec.PC, BObjectImp::typestr( param0->type() ) );
×
NEW
132
    return new BError( "Invalid parameter type" );
×
133
  }
NEW
134
  return new BError( "Invalid parameter count" );
×
135
}
136

137
BObjectImp* NPCExecutorModule::mf_Self()
8✔
138
{
139
  return new ECharacterRefObjImp( &npc );
8✔
140
}
141

142
BObjectImp* NPCExecutorModule::mf_SetAnchor()
×
143
{
144
  Core::Pos2d pos;
×
145
  int dstart, psub;
146
  if ( getPos2dParam( 0, 1, &pos, npc.realm() ) && getParam( 2, dstart ) && getParam( 3, psub ) )
×
147
  {
148
    if ( dstart )
×
149
    {
150
      npc.anchor.enabled = true;
×
151
      npc.anchor.pos = pos;
×
152
      npc.anchor.dstart = static_cast<unsigned short>( dstart );
×
153
      npc.anchor.psub = static_cast<unsigned short>( psub );
×
154
      return new BLong( 1 );
×
155
    }
156

NEW
157
    npc.anchor.enabled = false;
×
NEW
158
    return new BLong( 1 );
×
159
  }
160

NEW
161
  return new BError( "Invalid parameter type" );
×
162
}
163

164

165
bool NPCExecutorModule::_internal_move( Core::UFACING facing, int run )
×
166
{
167
  bool success = false;
×
168
  int dir = facing;
×
169
  if ( run )
×
170
    dir |= 0x80;  // FIXME HARDCODE
×
171

172
  if ( npc.could_move( facing ) )
×
173
  {
174
    if ( npc.move(
×
175
             static_cast<unsigned char>( dir ) ) )  // this could still fail, if paralyzed or frozen
176
    {
177
      npc.tellmove();
×
178
      // move was successful
179
      success = true;
×
180
    }
181
    // else, npc could move, but move failed.
182
  }
183
  // else npc could not move
184

185
  return success;
×
186
}
187

188
BObjectImp* NPCExecutorModule::move_self( Core::UFACING facing, bool run, bool adjust_ok )
1✔
189
{
190
  bool success = false;
1✔
191
  int dir = facing;
1✔
192

193
  if ( run )
1✔
194
    dir |= 0x80;  // FIXME HARDCODE
×
195

196
  if ( adjust_ok )
1✔
197
  {
198
    if ( npc.use_adjustments() )
×
199
    {
200
      for ( int adjust : Core::adjustments )
×
201
      {
202
        facing = static_cast<Core::UFACING>( ( dir + adjust ) & 7 );
×
203

204
        success = _internal_move( facing, run );
×
205
        if ( success == true )
×
206
          break;
×
207
      }
208
    }
209
    else
210
    {
211
      success = _internal_move( facing, run );
×
212
    }
213
  }
214
  else
215
  {
216
    if ( npc.anchor_allows_move( facing ) && npc.move( static_cast<unsigned char>( dir ) ) )
1✔
217
    {
218
      npc.tellmove();
1✔
219
      success = true;
1✔
220
    }
221
  }
222

223
  // int delay = 1000 - npc.dexterity() * 3;
224
  int delay = std::max<int>( 1000 - npc.run_speed * 3,
2✔
225
                             Core::settingsManager.ssopt.npc_minimum_movement_delay );
1✔
226
  u32 sleep = static_cast<u32>( delay );
1✔
227
  os_module->SleepForMs( run ? ( sleep / 2 ) : sleep );
1✔
228

229
  // return new String( FacingStr(facing) );
230
  return new BLong( success ? 1 : 0 );
1✔
231
}
232

233
BObjectImp* NPCExecutorModule::mf_Wander()
×
234
{
235
  u8 newfacing = 0;
×
236
  bool adjust_ok = true;
×
237
  switch ( Clib::random_int( 7 ) )
×
238
  {
239
  case 0:
×
240
  case 1:
241
  case 2:
242
  case 3:
243
  case 4:
244
  case 5:
245
    newfacing = npc.facing;
×
246
    break;
×
247
  case 6:
×
248
    newfacing = ( static_cast<int>( npc.facing ) - 1 ) & PKTIN_02_FACING_MASK;
×
249
    adjust_ok = false;
×
250
    break;
×
251
  case 7:
×
252
    newfacing = ( static_cast<int>( npc.facing ) + 1 ) & PKTIN_02_FACING_MASK;
×
253
    adjust_ok = false;
×
254
    break;
×
255
  }
256
  return move_self( static_cast<Core::UFACING>( newfacing ), false, adjust_ok );
×
257
}
258

259
BObjectImp* NPCExecutorModule::mf_Face()
×
260
{
261
  BObjectImp* param0 = exec.getParamImp( 0 );
×
262
  int flags;
263

264
  if ( param0 == nullptr || !exec.getParam( 1, flags ) )
×
265
    return new BError( "Invalid parameter type." );
×
266

267
  Core::UFACING i_facing;
268

269
  if ( auto* s = impptrIf<String>( param0 ) )
×
270
  {
271
    const char* dir = s->data();
×
272

273
    if ( Mobile::DecodeFacing( dir, i_facing ) == false )
×
274
    {
275
      DEBUGLOGLN(
×
276
          "Script Error in '{}' PC={}: \n"
277
          "\tCall to function npc::face():\n"
278
          "\tParameter 0: Expected direction: N S E W NW NE SW SE, got {}",
279
          scriptname(), exec.PC, dir );
×
280
      return nullptr;
×
281
    }
282
  }
283
  else if ( auto* l = impptrIf<BLong>( param0 ) )
×
284
  {
285
    i_facing = static_cast<Core::UFACING>( l->value() & PKTIN_02_FACING_MASK );
×
286
  }
287
  else
288
  {
289
    DEBUGLOGLN(
×
290
        "Script Error in '{}' PC={}: \n"
291
        "\tCall to function npc::face():\n"
292
        "\tParameter 0: Expected direction, , got datatype {}",
293
        scriptname(), exec.PC, BObjectImp::typestr( param0->type() ) );
×
294

295
    return nullptr;
×
296
  }
297

298
  if ( !npc.face( i_facing, flags ) )
×
299
    return new BLong( 0 );
×
300

301
  npc.on_facing_changed();
×
302
  return new BLong( i_facing );
×
303
}
304

305
BObjectImp* NPCExecutorModule::mf_Move()
1✔
306
{
307
  BObjectImp* param0 = exec.getParamImp( 0 );
1✔
308

309
  if ( auto* s = impptrIf<String>( param0 ) )
1✔
310
  {
311
    const char* dir = s->data();
1✔
312
    Core::UFACING facing;
313

314
    if ( Mobile::DecodeFacing( dir, facing ) == false )
1✔
315
    {
316
      DEBUGLOGLN(
×
317
          "Script Error in '{}' PC={}: \n"
318
          "\tCall to function npc::move():\n"
319
          "\tParameter 0: Expected direction: N S E W NW NE SW SE, got {}",
320
          scriptname(), exec.PC, dir );
×
321
      return nullptr;
×
322
    }
323

324
    return move_self( facing, false );
1✔
325
  }
NEW
326
  if ( auto* l = impptrIf<BLong>( param0 ) )
×
327
  {
328
    Core::UFACING facing = static_cast<Core::UFACING>( l->value() & PKTIN_02_FACING_MASK );
×
329
    return move_self( facing, false );
×
330
  }
NEW
331
  if ( param0->isa( BObjectImp::OTApplicObj ) )
×
332
  {
333
    BApplicObjBase* appobj = static_cast<BApplicObjBase*>( param0 );
×
334
    if ( appobj->object_type() == &bounding_box_type )
×
335
    {
336
      BApplicObj<Mobile::BoundingBox>* ao_bbox =
×
337
          static_cast<BApplicObj<Mobile::BoundingBox>*>( appobj );
338
      const Mobile::BoundingBox& bbox = ao_bbox->value();
×
339
      Core::UFACING facing = Mobile::GetRandomFacing();
×
340

341
      auto pos = npc.pos().move( facing );
×
342
      if ( bbox.contains( pos.xy() ) || !bbox.contains( npc.pos2d() ) )
×
343
      {
344
        npc.move( static_cast<unsigned char>( facing ) );
×
345
        npc.tellmove();
×
346
        os_module->SleepFor( 1 );
×
347
        return new String( Mobile::FacingStr( facing ) );
×
348
      }
349

NEW
350
      return new String( "" );
×
351
    }
352
  }
NEW
353
  DEBUGLOGLN(
×
354
      "Script Error in '{}' PC={}: \n"
355
      "\tCall to function npc::move():\n"
356
      "\tParameter 0: Expected direction or bounding box, , got datatype {}",
NEW
357
      scriptname(), exec.PC, BObjectImp::typestr( param0->type() ) );
×
NEW
358
  return nullptr;
×
359
}
360

361
BObjectImp* NPCExecutorModule::mf_WalkToward()
×
362
{
363
  Core::UObject* obj;
364
  if ( getUObjectParam( 0, obj ) )
×
365
  {
366
    if ( obj->ismobile() )
×
367
    {
368
      Mobile::Character* chr = static_cast<Mobile::Character*>( obj );
×
369
      if ( !npc.is_visible_to_me( chr, /*check_range*/ false ) )
×
370
        return new BError( "Mobile specified cannot be seen" );
×
371
    }
372
    Core::UFACING fac = npc.direction_toward( obj );
×
373
    return move_self( fac, false, true );
×
374
  }
375

NEW
376
  return new BError( "Invalid parameter type" );
×
377
}
378

379

380
BObjectImp* NPCExecutorModule::mf_RunToward()
×
381
{
382
  Core::UObject* obj;
383
  if ( getUObjectParam( 0, obj ) )
×
384
  {
385
    if ( obj->ismobile() )
×
386
    {
387
      Mobile::Character* chr = static_cast<Mobile::Character*>( obj );
×
388
      if ( !npc.is_visible_to_me( chr, /*check_range*/ false ) )
×
389
        return new BError( "Mobile specified cannot be seen" );
×
390
    }
391
    return move_self( npc.direction_toward( obj ), true, true );
×
392
  }
393

NEW
394
  return new BError( "Invalid parameter type" );
×
395
}
396

397
BObjectImp* NPCExecutorModule::mf_WalkAwayFrom()
×
398
{
399
  Core::UObject* obj;
400
  if ( getUObjectParam( 0, obj ) )
×
401
  {
402
    if ( obj->ismobile() )
×
403
    {
404
      Mobile::Character* chr = static_cast<Mobile::Character*>( obj );
×
405
      if ( !npc.is_visible_to_me( chr, /*check_range*/ false ) )
×
406
        return new BError( "Mobile specified cannot be seen" );
×
407
    }
408
    return move_self( npc.direction_away( obj ),
×
409

410
                      false, true );
×
411
  }
412

NEW
413
  return new BError( "Invalid parameter type" );
×
414
}
415

416
BObjectImp* NPCExecutorModule::mf_RunAwayFrom()
×
417
{
418
  Core::UObject* obj;
419
  if ( getUObjectParam( 0, obj ) )
×
420
  {
421
    if ( obj->ismobile() )
×
422
    {
423
      Mobile::Character* chr = static_cast<Mobile::Character*>( obj );
×
424
      if ( !npc.is_visible_to_me( chr, /*check_range*/ false ) )
×
425
        return new BError( "Mobile specified cannot be seen" );
×
426
    }
427
    return move_self( npc.direction_away( obj ),
×
428

429
                      true, true );
×
430
  }
431

NEW
432
  return new BError( "Invalid parameter type" );
×
433
}
434

435
BObjectImp* NPCExecutorModule::mf_TurnToward()
×
436
{
437
  Core::UObject* obj;
438
  int flags;
439

440
  if ( !getUObjectParam( 0, obj ) || !exec.getParam( 1, flags ) )
×
441
  {
442
    return new BError( "Invalid parameter type" );
×
443
  }
444

445
  if ( obj->ismobile() )
×
446
  {
447
    Mobile::Character* chr = static_cast<Mobile::Character*>( obj );
×
448
    if ( !npc.is_visible_to_me( chr, /*check_range*/ false ) )
×
449
      return new BError( "Mobile specified cannot be seen" );
×
450
  }
451

452
  Core::UFACING facing = npc.direction_toward( obj );
×
453
  if ( facing == npc.facing )
×
454
    return new BLong( 0 );  // nothing to do here, I'm already facing that direction
×
455

456
  if ( !npc.face( facing, flags ) )
×
457
    return new BLong( 0 );  // Uh-oh, seems that I can't move to face that
×
458

459
  npc.on_facing_changed();
×
460
  return new BLong( 1 );
×
461
}
462

463
BObjectImp* NPCExecutorModule::mf_TurnAwayFrom()
×
464
{
465
  Core::UObject* obj;
466
  int flags;
467

468
  if ( !getUObjectParam( 0, obj ) || !exec.getParam( 1, flags ) )
×
469
  {
470
    return new BError( "Invalid parameter type" );
×
471
  }
472

473

474
  if ( obj->ismobile() )
×
475
  {
476
    Mobile::Character* chr = static_cast<Mobile::Character*>( obj );
×
477
    if ( !npc.is_visible_to_me( chr, /*check_range*/ false ) )
×
478
      return new BError( "Mobile specified cannot be seen" );
×
479
  }
480

481
  Core::UFACING facing = npc.direction_away( obj );
×
482
  if ( facing == npc.facing )
×
483
    return new BLong( 0 );  // nothing to do here
×
484

485
  if ( !npc.face( facing, flags ) )
×
486
    return new BLong( 0 );  // couldn't move for some reason
×
487

488
  npc.on_facing_changed();
×
489
  return new BLong( 1 );
×
490
}
491

492
BObjectImp* NPCExecutorModule::mf_WalkTowardLocation()
×
493
{
494
  Core::Pos2d pos;
×
495
  if ( getPos2dParam( 0, 1, &pos, npc.realm() ) )
×
496
  {
497
    Core::UFACING fac = npc.direction_toward( pos );
×
498
    return move_self( fac, false, true );
×
499
  }
500
  return new BError( "Invalid parameter type" );
×
501
}
502

503
BObjectImp* NPCExecutorModule::mf_RunTowardLocation()
×
504
{
505
  Core::Pos2d pos;
×
506
  if ( getPos2dParam( 0, 1, &pos, npc.realm() ) )
×
507
  {
508
    Core::UFACING fac = npc.direction_toward( pos );
×
509
    return move_self( fac, true, true );
×
510
  }
511
  return new BError( "Invalid parameter type" );
×
512
}
513

514
BObjectImp* NPCExecutorModule::mf_WalkAwayFromLocation()
×
515
{
516
  Core::Pos2d pos;
×
517
  if ( getPos2dParam( 0, 1, &pos, npc.realm() ) )
×
518
  {
519
    Core::UFACING fac = npc.direction_away( pos );
×
520
    return move_self( fac, false, true );
×
521
  }
522
  return new BError( "Invalid parameter type" );
×
523
}
524

525
BObjectImp* NPCExecutorModule::mf_RunAwayFromLocation()
×
526
{
527
  Core::Pos2d pos;
×
528
  if ( getPos2dParam( 0, 1, &pos, npc.realm() ) )
×
529
  {
530
    Core::UFACING fac = npc.direction_away( pos );
×
531
    return move_self( fac, true, true );
×
532
  }
533
  return new BError( "Invalid parameter type" );
×
534
}
535

536
BObjectImp* NPCExecutorModule::mf_TurnTowardLocation()
×
537
{
538
  Core::Pos2d pos;
×
539
  int flags;
540

541
  if ( !getPos2dParam( 0, 1, &pos, npc.realm() ) || !exec.getParam( 2, flags ) )
×
542
  {
543
    return new BError( "Invalid parameter type" );
×
544
  }
545

546
  Core::UFACING fac = npc.direction_toward( pos );
×
547
  if ( npc.facing == fac )
×
548
    return new BLong( 0 );  // nothing to do here
×
549

550
  if ( !npc.face( fac, flags ) )
×
551
    return new BLong( 0 );  // I couldn't move!
×
552

553
  npc.on_facing_changed();
×
554
  return new BLong( 1 );
×
555
}
556

557
BObjectImp* NPCExecutorModule::mf_TurnAwayFromLocation()
×
558
{
559
  Core::Pos2d pos;
×
560
  int flags;
561

562
  if ( !getPos2dParam( 0, 1, &pos, npc.realm() ) || !exec.getParam( 2, flags ) )
×
563
  {
564
    return new BError( "Invalid parameter type" );
×
565
  }
566

567
  Core::UFACING fac = npc.direction_away( pos );
×
568
  if ( npc.facing == fac )
×
569
    return new BLong( 0 );  // nothing to do here
×
570

571
  if ( !npc.face( fac, flags ) )
×
572
    return new BLong( 0 );  // I couldn't move!
×
573

574
  npc.on_facing_changed();
×
575
  return new BLong( 1 );
×
576
}
577

578

579
BObjectImp* NPCExecutorModule::mf_Say()
1✔
580
{
581
  if ( npc.squelched() )
1✔
582
    return new BError( "NPC is squelched" );
×
583
  if ( npc.hidden() )
1✔
584
    npc.unhide();
×
585

586
  const char* text = exec.paramAsString( 0 );
1✔
587
  std::string texttype_str = Clib::strlowerASCII( exec.paramAsString( 1 ) );
1✔
588
  int doevent;
589
  exec.getParam( 2, doevent );
1✔
590
  u8 texttype;
591
  if ( texttype_str == "default" )
1✔
592
    texttype = Plib::TEXTTYPE_NORMAL;
1✔
593
  else if ( texttype_str == "whisper" )
×
594
    texttype = Plib::TEXTTYPE_WHISPER;
×
595
  else if ( texttype_str == "yell" )
×
596
    texttype = Plib::TEXTTYPE_YELL;
×
597
  else
598
    return new BError( "texttype string param must be either 'default', 'whisper', or 'yell'" );
×
599

600

601
  Network::PktHelper::PacketOut<Network::PktOut_1C> msg;
1✔
602
  Network::PktHelper::PacketOut<Network::PktOut_AE> ucmsg;
1✔
603
  u16 len = 0;
1✔
604
  u16 uclen = 0;
1✔
605
  // switch to other pkt if utf8 found
606
  if ( !Bscript::String::hasUTF8Characters( text ) )
1✔
607
  {
608
    msg->offset += 2;
1✔
609
    msg->Write<u32>( npc.serial_ext );
1✔
610
    msg->WriteFlipped<u16>( npc.graphic );
1✔
611
    msg->Write<u8>( texttype );
1✔
612
    msg->WriteFlipped<u16>( npc.speech_color() );
1✔
613
    msg->WriteFlipped<u16>( npc.speech_font() );
1✔
614
    msg->Write( Clib::strUtf8ToCp1252( npc.name() ).c_str(), 30 );
1✔
615
    msg->Write( text, ( strlen( text ) > SPEECH_MAX_LEN + 1 )
2✔
616
                          ? SPEECH_MAX_LEN + 1
617
                          : static_cast<u16>( strlen( text ) + 1 ) );
1✔
618
    len = msg->offset;
1✔
619
    msg->offset = 1;
1✔
620
    msg->WriteFlipped<u16>( len );
1✔
621
  }
622
  else
623
  {
624
    std::vector<u16> utf16 = Bscript::String::toUTF16( text );
×
625
    if ( utf16.size() > SPEECH_MAX_LEN )
×
626
      utf16.resize( SPEECH_MAX_LEN );
×
627
    ucmsg->offset += 2;
×
628
    ucmsg->Write<u32>( npc.serial_ext );
×
629
    ucmsg->WriteFlipped<u16>( npc.graphic );
×
630
    ucmsg->Write<u8>( texttype );
×
631
    ucmsg->WriteFlipped<u16>( npc.speech_color() );
×
632
    ucmsg->WriteFlipped<u16>( npc.speech_font() );
×
633
    ucmsg->Write( "ENU", 4 );
×
634
    ucmsg->Write( Clib::strUtf8ToCp1252( npc.description() ).c_str(), 30 );
×
635
    ucmsg->WriteFlipped( utf16, true );
×
636
    uclen = ucmsg->offset;
×
637
    ucmsg->offset = 1;
×
638
    ucmsg->WriteFlipped<u16>( uclen );
×
639
  }
×
640

641
  // send to those nearby
642
  u16 range;
643
  if ( texttype == Plib::TEXTTYPE_WHISPER )
1✔
644
    range = Core::settingsManager.ssopt.whisper_range;
×
645
  else if ( texttype == Plib::TEXTTYPE_YELL )
1✔
646
    range = Core::settingsManager.ssopt.yell_range;
×
647
  else
648
    range = Core::settingsManager.ssopt.speech_range;
1✔
649
  Core::WorldIterator<Core::OnlinePlayerFilter>::InRange(
1✔
650
      &npc, range,
1✔
651
      [&]( Mobile::Character* chr )
1✔
652
      {
653
        if ( !chr->is_visible_to_me( &npc, /*check_range*/ false ) )
2✔
654
          return;
×
655
        if ( !uclen )
2✔
656
          msg.Send( chr->client, len );
2✔
657
        else
658
          ucmsg.Send( chr->client, uclen );
×
659
      } );
660

661
  if ( doevent >= 1 )
1✔
662
  {
663
    Core::WorldIterator<Core::NPCFilter>::InRange(
×
664
        &npc, range,
×
665
        [&]( Mobile::Character* chr )
×
666
        {
667
          Mobile::NPC* othernpc = static_cast<Mobile::NPC*>( chr );
×
668
          if ( chr != &npc )
×
669
            othernpc->on_pc_spoke( &npc, text, texttype );
×
670
        } );
×
671
  }
672

673
  return nullptr;
1✔
674
}
1✔
675

676
BObjectImp* NPCExecutorModule::mf_SayUC()
×
677
{
678
  if ( npc.squelched() )
×
679
    return new BError( "NPC is squelched" );
×
NEW
680
  if ( npc.hidden() )
×
681
    npc.unhide();
×
682

683
  const String* text;
684
  const String* lang;
685
  int doevent;
686

687
  if ( getUnicodeStringParam( 0, text ) && getStringParam( 2, lang ) && getParam( 3, doevent ) )
×
688
  {
689
    std::string texttype_str = Clib::strlowerASCII( exec.paramAsString( 1 ) );
×
690
    if ( texttype_str != "default" && texttype_str != "whisper" && texttype_str != "yell" )
×
691
    {
692
      return new BError( "texttype string param must be either 'default', 'whisper', or 'yell'" );
×
693
    }
694

695
    if ( text->length() > SPEECH_MAX_LEN )
×
696
      return new BError( "Text exceeds maximum size." );
×
697
    if ( lang->length() != 3 )
×
698
      return new BError( "langcode must be a 3-character code." );
×
699

700
    std::vector<u16> utf16 = text->toUTF16();
×
701
    if ( utf16.size() > SPEECH_MAX_LEN )
×
702
      utf16.resize( SPEECH_MAX_LEN );
×
703
    std::string languc = Clib::strupperASCII( lang->value() );
×
704

705
    u8 texttype;
706
    if ( texttype_str == "whisper" )
×
707
      texttype = Plib::TEXTTYPE_WHISPER;
×
708
    else if ( texttype_str == "yell" )
×
709
      texttype = Plib::TEXTTYPE_YELL;
×
710
    else
711
      texttype = Plib::TEXTTYPE_NORMAL;
×
712

713
    Network::PktHelper::PacketOut<Network::PktOut_AE> talkmsg;
×
714
    talkmsg->offset += 2;
×
715
    talkmsg->Write<u32>( npc.serial_ext );
×
716
    talkmsg->WriteFlipped<u16>( npc.graphic );
×
717
    talkmsg->Write<u8>( texttype );
×
718
    talkmsg->WriteFlipped<u16>( npc.speech_color() );
×
719
    talkmsg->WriteFlipped<u16>( npc.speech_font() );
×
720
    talkmsg->Write( languc.c_str(), 4 );
×
721
    talkmsg->Write( Clib::strUtf8ToCp1252( npc.description() ).c_str(), 30 );
×
722
    talkmsg->WriteFlipped( utf16, true );
×
723
    u16 len = talkmsg->offset;
×
724
    talkmsg->offset = 1;
×
725
    talkmsg->WriteFlipped<u16>( len );
×
726

727
    u16 range;
728
    if ( texttype == Plib::TEXTTYPE_WHISPER )
×
729
      range = Core::settingsManager.ssopt.whisper_range;
×
730
    else if ( texttype == Plib::TEXTTYPE_YELL )
×
731
      range = Core::settingsManager.ssopt.yell_range;
×
732
    else
733
      range = Core::settingsManager.ssopt.speech_range;
×
734
    Core::WorldIterator<Core::OnlinePlayerFilter>::InRange(
×
735
        &npc, range,
×
736
        [&]( Mobile::Character* chr )
×
737
        {
738
          if ( !chr->is_visible_to_me( &npc, /*check_range*/ false ) )
×
739
            return;
×
740
          talkmsg.Send( chr->client, len );
×
741
        } );
742

743
    if ( doevent >= 1 )
×
744
    {
745
      Core::WorldIterator<Core::NPCFilter>::InRange(
×
746
          &npc, range,
×
747
          [&]( Mobile::Character* chr )
×
748
          {
749
            Mobile::NPC* othernpc = static_cast<Mobile::NPC*>( chr );
×
750
            if ( othernpc != &npc )
×
751
              othernpc->on_pc_spoke( &npc, text->value(), texttype, languc );
×
752
          } );
×
753
    }
754
  }
×
755
  else
756
  {
757
    return new BError( "A parameter was invalid" );
×
758
  }
759
  return nullptr;
×
760
}
761

762
BObjectImp* NPCExecutorModule::mf_position()
×
763
{
764
  std::unique_ptr<BStruct> oa( new BStruct );
×
765

766
  oa->addMember( "x", new BLong( npc.x() ) );
×
767
  oa->addMember( "y", new BLong( npc.y() ) );
×
768
  oa->addMember( "z", new BLong( npc.z() ) );
×
769

770
  return oa.release();
×
771
}
×
772

773
BObjectImp* NPCExecutorModule::mf_Facing()
×
774
{
775
  return new String( Mobile::FacingStr( static_cast<Core::UFACING>( npc.facing ) ) );
×
776
}
777

778
BObjectImp* NPCExecutorModule::mf_GetProperty()
×
779
{
780
  const String* propname_str;
781
  if ( exec.getStringParam( 0, propname_str ) )
×
782
  {
783
    std::string val;
×
784
    if ( npc.getprop( propname_str->value(), val ) )
×
785
    {
786
      return BObjectImp::unpack( val.c_str() );
×
787
    }
788

NEW
789
    return new BError( "Property not found" );
×
UNCOV
790
  }
×
791

NEW
792
  return new BError( "Invalid parameter type" );
×
793
}
794

795
BObjectImp* NPCExecutorModule::mf_SetProperty()
×
796
{
797
  const String* propname_str;
798
  if ( exec.getStringParam( 0, propname_str ) )
×
799
  {
800
    BObjectImp* propval = getParamImp( 1 );
×
801
    npc.setprop( propname_str->value(), propval->pack() );
×
802
    return new BLong( 1 );
×
803
  }
804

NEW
805
  return new BError( "Invalid parameter type" );
×
806
}
807

808
BObjectImp* NPCExecutorModule::mf_CreateBackpack()
×
809
{
810
  // UNTESTED
811
  if ( !npc.layer_is_equipped( Core::LAYER_BACKPACK ) )
×
812
  {
813
    Items::Item* i = Items::Item::create( UOBJ_BACKPACK );
×
814
    std::unique_ptr<Items::Item> item( i );
×
815
    item->layer = Core::LAYER_BACKPACK;
×
816
    if ( npc.equippable( item.get() ) )
×
817
    {
818
      npc.equip( item.release() );
×
819
    }
820
    else
821
      item->destroy();
×
822
  }
×
823
  return new BLong( 1 );
×
824
}
825

826
BObjectImp* NPCExecutorModule::mf_CreateItem()
×
827
{
828
  // UNTESTED
829
  const BLong* objtype = exec.getLongParam( 0 );
×
830
  if ( objtype == nullptr )
×
831
    return new BLong( 0 );
×
832

833
  Core::UContainer* backpack = npc.backpack();
×
834
  if ( backpack == nullptr )
×
835
    return new BLong( 0 );
×
836

837
  std::unique_ptr<Items::Item> item(
838
      Items::Item::create( static_cast<unsigned int>( objtype->value() ) ) );
×
839
  if ( item.get() == nullptr )
×
840
    return new BLong( 0 );
×
841

842
  if ( !backpack->can_add( *item ) )
×
843
    return new BLong( 0 );
×
844

845
  u8 slotIndex = item->slot_index();
×
846
  if ( !backpack->can_add_to_slot( slotIndex ) )
×
847
    return new BLong( 0 );
×
848

849
  if ( !item->slot_index( slotIndex ) )
×
850
    return new BLong( 0 );
×
851

852
  u32 serial = item->serial;
×
853

854
  backpack->add_at_random_location( item.release() );
×
855

856
  return new BLong( serial );
×
857
}
×
858

859
BObjectImp* NPCExecutorModule::mf_MakeBoundingBox( /* areastring */ )
×
860
{
861
  auto arealist = static_cast<String*>( getParamImp( 0, BObjectImp::OTString ) );
×
862
  if ( arealist == nullptr )
×
863
    return new String( "" );
×
864

865
  BoundingBoxObjImp* bbox = new BoundingBoxObjImp;
×
866
  std::unique_ptr<BoundingBoxObjImp> bbox_owner( bbox );
×
867

868
  ISTRINGSTREAM is( arealist->value() );
×
869

870
  u16 tlx, tly, brx, bry;
871
  // FIXME this is a terrible data format.
872
  while ( is >> tlx >> tly >> brx >> bry )
×
873
  {
874
    ( *bbox )->addarea(
×
875
        Core::Range2d( Core::Pos2d{ tlx, tly }, Core::Pos2d{ brx, bry }, npc.realm() ) );
×
876
  }
877

878
  return bbox_owner.release();
×
879
}
×
880

881
BObjectImp* NPCExecutorModule::mf_SetOpponent()
11✔
882
{
883
  Mobile::Character* chr;
884
  if ( getCharacterParam( 0, chr ) && chr != &npc )
11✔
885
  {
886
    npc.set_opponent( chr );
4✔
887
    return new BLong( 1 );
4✔
888
  }
889

890
  npc.set_opponent( nullptr );
7✔
891
  return new BLong( 0 );
7✔
892
}
893

894
BObjectImp* NPCExecutorModule::mf_SetWarMode()
×
895
{
896
  int warmode;
897
  if ( exec.getParam( 0, warmode ) )
×
898
  {
899
    npc.set_warmode( warmode != 0 );
×
900
    return new BLong( 1 );
×
901
  }
902

NEW
903
  return new BLong( 0 );
×
904
}
905

906
size_t NPCExecutorModule::sizeEstimate() const
×
907
{
908
  return sizeof( *this );
×
909
}
910
}  // namespace Pol::Module
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