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

polserver / polserver / 21092164466

17 Jan 2026 09:30AM UTC coverage: 60.506% (-0.001%) from 60.507%
21092164466

push

github

web-flow
Clang Tidy concat namespaces (#855)

* tidy

* Automated clang-tidy change: modernize-concat-nested-namespaces

* compile test

* fix namespace

---------

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

9 of 40 new or added lines in 14 files covered. (22.5%)

44454 of 73471 relevant lines covered (60.51%)

507366.17 hits per line

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

2.98
/pol-core/pol/create.cpp
1
/** @file
2
 *
3
 * @par History
4
 * - 2006/05/16 Shinigami: ClientCreateChar() updated to support Elfs
5
 * - 2006/05/23 Shinigami: added Elf Hair Style to validhair() & comments added
6
 *                         validbeard() rewritten & comments added
7
 * - 2009/12/02 Turley:    added gargoyle support, 0x8D char create, face support
8
 * - 2010/01/14 Turley:    more error checks, Tomi's startequip patch
9
 * - 2011/10/26 Tomi:      added 0xF8 char create for clients >= 7.0.16.0
10
 */
11

12

13
#include <stdlib.h>
14
#include <string>
15

16
#include "../clib/clib_endian.h"
17
#include "../clib/logfacility.h"
18
#include "../clib/rawtypes.h"
19
#include "../clib/refptr.h"
20
#include "../plib/clidata.h"
21
#include "../plib/systemstate.h"
22
#include "../plib/uconst.h"
23
#include "accounts/account.h"
24
#include "containr.h"
25
#include "gameclck.h"
26
#include "globals/network.h"
27
#include "globals/object_storage.h"
28
#include "globals/uvars.h"
29
#include "item/item.h"
30
#include "layers.h"
31
#include "mkscrobj.h"
32
#include "mobile/attribute.h"
33
#include "mobile/charactr.h"
34
#include "mobile/wornitems.h"
35
#include "module/uomod.h"
36
#include "network/client.h"
37
#include "network/pktdef.h"
38
#include "network/pktin.h"
39
#include "plib/objtype.h"
40
#include "realms/WorldChangeReasons.h"
41
#include "scrsched.h"
42
#include "scrstore.h"
43
#include "skillid.h"
44
#include "startloc.h"
45
#include "ufunc.h"
46
#include "uoclient.h"
47
#include "uoexec.h"
48
#include "uoskills.h"
49
#include "uworld.h"
50

51

52
namespace Pol::Core
53
{
54
void start_client_char( Network::Client* client );
55
void run_logon_script( Mobile::Character* chr );
56

57
short validhaircolor( u16 /*color*/ )
×
58
{
59
  return 1;
×
60
}
61

62
/* Ah, I just realized what may be the true way to do this:
63
   Read the tile file (given an object type, it gives data),
64
   and make sure the resultant tile is on the right layer.
65
   Only object types in the 0x2000's should be considered.
66
   Also, make sure weight is 0 - some wall sconces are on
67
   the beard layer. (!) (they aren't in the 0x2000's..)
68
   */
69

70
/* hair can be:
71
    0x203B  Short Hair      // Human
72
    0x203C  Long Hair
73
    0x203D  PonyTail
74
    0x2044  Mohawk
75
    0x2045  Pageboy Hair
76
    0x2046  Buns Hair
77
    0x2047  Afro
78
    0x2048  Receeding Hair
79
    0x2049  Two Pig Tails
80
    0x204A  Krisna Hair
81

82
    0x2FBF  Mid Long Hair     // Elf (Mondain's Legacy)
83
    0x2FC0  Long Feather Hair
84
    0x2FC1  Short Elf Hair
85
    0x2FC2  Mullet
86
    0x2FCC  Flower Hair
87
    0x2FCD  Long Elf Hair
88
    0x2FCE  Long Big Knob Hair
89
    0x2FCF  Long Big Braid Hair
90
    0x2FD0  Long Big Bun Hair
91
    0x2FD1  Spiked Hair
92
    0x2FD2  Long Elf Two Hair
93

94
    0x4258  Horn Style 1       // Gargoyle Male (SA)
95
    0x4259  Horn Style 2
96
    0x425a  Horn Style 3
97
    0x425b  Horn Style 4
98
    0x425c  Horn Style 5
99
    0x425d  Horn Style 6
100
    0x425e  Horn Style 7
101
    0x425f  Horn Style 8
102

103
    0x4261  Female Horn Style 1  // Gargoyle Female (SA)
104
    0x4262  Female Horn Style 2
105
    0x4273  Female Horn Style 3
106
    0x4274  Female Horn Style 4
107
    0x4275  Female Horn Style 5
108
    0x42aa  Female Horn Style 6
109
    0x42ab  Female Horn Style 7
110
    0x42b1  Femaly Horn Style 8
111
    */
112
bool validhair( u16 HairStyle )
×
113
{
114
  if ( Plib::systemstate.config.max_tile_id < HairStyle )
×
115
  {
116
    return false;
×
117
  }
118
  else
119
  {
120
    if ( ( ( 0x203B <= HairStyle ) && ( HairStyle <= 0x203D ) ) ||
×
121
         ( ( 0x2044 <= HairStyle ) && ( HairStyle <= 0x204A ) ) ||
×
122
         ( ( 0x2FBF <= HairStyle ) && ( HairStyle <= 0x2FC2 ) ) ||
×
123
         ( ( 0x2FCC <= HairStyle ) && ( HairStyle <= 0x2FD2 ) ) ||
×
124
         ( ( 0x4258 <= HairStyle ) && ( HairStyle <= 0x425F ) ) ||
×
125
         ( ( 0x4261 <= HairStyle ) && ( HairStyle <= 0x4262 ) ) ||
×
126
         ( ( 0x4273 <= HairStyle ) && ( HairStyle <= 0x4275 ) ) ||
×
127
         ( ( 0x42aa <= HairStyle ) && ( HairStyle <= 0x42ab ) ) || ( HairStyle == 0x42B1 ) )
×
128
      return true;
×
129
    else
130
      return false;
×
131
  }
132
}
133

134
/* beard can be:
135
    0x203E  Long Beard     // Human
136
    0x203F  Short Beard
137
    0x2040  Goatee
138
    0x2041  Mustache
139
    0x204B  Medium Short Beard
140
    0x204C  Medium Long Beard
141
    0x204D  Vandyke
142

143
    0x42ad  facial horn style 1 // Gargoyle (SA)
144
    0x42ae  facial horn style 2
145
    0x42af  facial horn style 3
146
    0x42b0  facial horn style 4
147
    */
148
bool validbeard( u16 BeardStyle )
×
149
{
150
  if ( ( ( 0x203E <= BeardStyle ) && ( BeardStyle <= 0x2041 ) ) ||
×
151
       ( ( 0x204B <= BeardStyle ) && ( BeardStyle <= 0x204D ) ) ||
×
152
       ( ( 0x42AD <= BeardStyle ) && ( BeardStyle <= 0x42B0 ) &&
×
153
         ( Plib::systemstate.config.max_tile_id > BeardStyle ) ) )
×
154
    return true;
×
155
  else
156
    return false;
×
157
}
158

159
/* face can be:
160
    0x3B44  face 1
161
    0x3B45  face 2
162
    0x3B46  face 3
163
    0x3B47  face 4
164
    0x3B48  face 5
165
    0x3B49  face 6
166
    0x3B4A  face 7
167
    0x3B4B  face 8
168
    0x3B4C  face 9
169
    0x3B4D  face 10
170
    0x3B4E  anime     //roleplay faces
171
    0x3B4F  hellian
172
    0x3B50  juka
173
    0x3B51  undead
174
    0x3B52  meer
175
    0x3B53  elder
176
    0x3B54  orc
177
    0x3B55  pirate
178
    0x3B56  native papuan
179
    0x3B57  vampire
180
    */
181
bool validface( u16 FaceStyle )
×
182
{
183
  switch ( settingsManager.ssopt.features.faceSupport() )
×
184
  {
185
  case Plib::FaceSupport::None:
×
186
    return false;
×
187
  case Plib::FaceSupport::RolePlay:
×
188
    if ( ( 0x3B4E <= FaceStyle ) && ( FaceStyle <= 0x3B57 ) )
×
189
      return true;
×
190
    [[fallthrough]];
191
  case Plib::FaceSupport::Basic:
192
    if ( ( 0x3B44 <= FaceStyle ) && ( FaceStyle <= 0x3B4D ) )
×
193
      return true;
×
194
  }
195
  return false;
×
196
}
197

198
void ClientCreateChar( Network::Client* client, PKTIN_00* msg )
×
199
{
200
  if ( client->acct == nullptr )
×
201
  {
202
    ERROR_PRINTLN( "Client from {} tried to create a character without an account!",
×
203
                   client->ipaddrAsString() );
×
204
    client->forceDisconnect();
×
205
    return;
×
206
  }
207
  else if ( Plib::systemstate.config.min_cmdlevel_to_login > client->acct->default_cmdlevel() )
×
208
  {
209
    // FIXME: Add send_login_error!
210
    client->Disconnect();
×
211
    return;
×
212
  }
213
  else if ( msg->CharNumber >= Plib::systemstate.config.character_slots ||
×
214
            client->acct->get_character( msg->CharNumber ) != nullptr ||
×
215
            msg->StartIndex >= gamestate.startlocations.size() )
×
216
  {
217
    ERROR_PRINTLN( "Create Character: Invalid parameters." );
×
218
    send_login_error( client, LOGIN_ERROR_MISC );
×
219
    client->Disconnect();
×
220
    return;
×
221
  }
222
  else if ( !Plib::systemstate.config.allow_multi_clients_per_account &&
×
223
            client->acct->has_active_characters() )
×
224
  {
225
    send_login_error( client, LOGIN_ERROR_OTHER_CHAR_INUSE );
×
226
    client->Disconnect();
×
227
    return;
×
228
  }
229

230
  unsigned short graphic;
231
  Plib::URACE race;
232
  Plib::UGENDER gender = ( ( msg->Sex & Network::FLAG_GENDER ) == Network::FLAG_GENDER )
×
233
                             ? Plib::GENDER_FEMALE
234
                             : Plib::GENDER_MALE;
235
  if ( client->ClientType & Network::CLIENTTYPE_7000 )
×
236
  {
237
    /*
238
    0x00 / 0x01 = human male/female
239
    0x02 / 0x03 = human male/female
240
    0x04 / 0x05 = elf male/female
241
    0x06 / 0x07 = gargoyle male/female
242
    */
243
    if ( ( msg->Sex & 0x6 ) == 0x6 )
×
244
    {
245
      race = Plib::RACE_GARGOYLE;
×
246
      graphic = ( gender == Plib::GENDER_FEMALE ) ? UOBJ_GARGOYLE_FEMALE : UOBJ_GARGOYLE_MALE;
×
247
    }
248
    else if ( ( msg->Sex & 0x4 ) == 0x4 )
×
249
    {
250
      race = Plib::RACE_ELF;
×
251
      graphic = ( gender == Plib::GENDER_FEMALE ) ? UOBJ_ELF_FEMALE : UOBJ_ELF_MALE;
×
252
    }
253
    else
254
    {
255
      race = Plib::RACE_HUMAN;
×
256
      graphic = ( gender == Plib::GENDER_FEMALE ) ? UOBJ_HUMAN_FEMALE : UOBJ_HUMAN_MALE;
×
257
    }
258
  }
259
  else
260
  {
261
    /*
262
    0x00 / 0x01 = human male/female
263
    0x02 / 0x03 = elf male/female
264
    */
265
    if ( ( msg->Sex & Network::FLAG_RACE ) == Network::FLAG_RACE )
×
266
    {
267
      race = Plib::RACE_ELF;
×
268
      graphic = ( gender == Plib::GENDER_FEMALE ) ? UOBJ_ELF_FEMALE : UOBJ_ELF_MALE;
×
269
    }
270
    else
271
    {
272
      race = Plib::RACE_HUMAN;
×
273
      graphic = ( gender == Plib::GENDER_FEMALE ) ? UOBJ_HUMAN_FEMALE : UOBJ_HUMAN_MALE;
×
274
    }
275
  }
276

277
  Mobile::Character* chr = new Mobile::Character( graphic );
×
278

279
  chr->acct.set( client->acct );
×
280
  chr->client = client;
×
281
  chr->set_privs( client->acct->default_privlist() );
×
282
  chr->cmdlevel( client->acct->default_cmdlevel(), false );
×
283

284
  client->UOExpansionFlagClient = ctBEu32( msg->clientflag );
×
285

286
  std::string tmpstr( msg->Name, sizeof msg->Name );
×
287
  const char* tstr = tmpstr.c_str();
×
288
  for ( unsigned int i = 0; i < strlen( tstr ); i++ )
×
289
  {
290
    char tmpchr = tstr[i];
×
291
    if ( tmpchr >= ' ' && tmpchr <= '~' )
×
292
    {
293
      if ( tmpchr != '{' && tmpchr != '}' )
×
294
        continue;
×
295
    }
296

297
    ERROR_PRINTLN(
×
298
        "Create Character: Attempted to use invalid character '{}' pos '{}' in name '{}'. Client "
299
        "IP: {} Client Name: {}",
300
        tmpchr, i, tstr, client->ipaddrAsString(), client->acct->name() );
×
301
    client->forceDisconnect();
×
302
    return;
×
303
  }
304
  chr->name_ = tstr;
×
305

306
  chr->serial = GetNextSerialNumber();
×
307
  chr->serial_ext = ctBEu32( chr->serial );
×
308
  chr->wornitems->serial = chr->serial;
×
309
  chr->wornitems->serial_ext = chr->serial_ext;
×
310

311
  chr->graphic = graphic;
×
312
  chr->race = race;
×
313
  chr->gender = gender;
×
314

315
  chr->trueobjtype = chr->objtype_;
×
316
  chr->color = cfBEu16( msg->SkinColor );
×
317
  chr->truecolor = chr->color;
×
318

319
  chr->setposition( gamestate.startlocations[msg->StartIndex]->select_coordinate() );
×
320
  chr->facing = Core::FACING_W;
×
321
  chr->position_changed();
×
322

323
  bool valid_stats = false;
×
324
  unsigned int stat_total = msg->Strength + msg->Intelligence + msg->Dexterity;
×
325
  unsigned int stat_min, stat_max;
326
  char* maxpos;
327
  std::vector<std::string>::size_type sidx;
328
  for ( sidx = 0; !valid_stats && sidx < settingsManager.ssopt.total_stats_at_creation.size();
×
329
        ++sidx )
330
  {
331
    const char* statstr = settingsManager.ssopt.total_stats_at_creation[sidx].c_str();
×
332
    stat_max = ( stat_min = strtoul( statstr, &maxpos, 0 ) );
×
333
    if ( *( maxpos++ ) == '-' )
×
334
      stat_max = strtoul( maxpos, nullptr, 0 );
×
335
    if ( stat_total >= stat_min && stat_total <= stat_max )
×
336
      valid_stats = true;
×
337
  }
338
  if ( !valid_stats )
×
339
  {
340
    ERROR_PRINTLN( "Create Character: Stats sum to {}.\nValid values/ranges are: {}", stat_total,
×
341
                   settingsManager.ssopt.total_stats_at_creation );
342
    client->forceDisconnect();
×
343
    return;
×
344
  }
345
  if ( msg->Strength < 10 || msg->Intelligence < 10 || msg->Dexterity < 10 )
×
346
  {
347
    ERROR_PRINTLN( "Create Character: A stat was too small. Str={} Int={} Dex={}", msg->Strength,
×
348
                   msg->Intelligence, msg->Dexterity );
×
349

350
    client->forceDisconnect();
×
351
    return;
×
352
  }
353
  if ( gamestate.pAttrStrength )
×
354
    chr->attribute( gamestate.pAttrStrength->attrid ).base( msg->Strength * 10 );
×
355
  if ( gamestate.pAttrIntelligence )
×
356
    chr->attribute( gamestate.pAttrIntelligence->attrid ).base( msg->Intelligence * 10 );
×
357
  if ( gamestate.pAttrDexterity )
×
358
    chr->attribute( gamestate.pAttrDexterity->attrid ).base( msg->Dexterity * 10 );
×
359

360
  if ( msg->SkillNumber1 > networkManager.uoclient_general.maxskills ||
×
361
       msg->SkillNumber2 > networkManager.uoclient_general.maxskills ||
×
362
       msg->SkillNumber3 > networkManager.uoclient_general.maxskills )
×
363
  {
364
    ERROR_PRINTLN( "Create Character: A skill number was out of range" );
×
365
    client->forceDisconnect();
×
366
    return;
×
367
  }
368
  bool noskills =
×
369
      ( msg->SkillValue1 + msg->SkillValue2 + msg->SkillValue3 == 0 ) && msg->profession;
×
370
  if ( ( !noskills ) &&
×
371
       ( ( msg->SkillValue1 + msg->SkillValue2 + msg->SkillValue3 != 100 ) ||
×
372
         msg->SkillValue1 > 50 || msg->SkillValue2 > 50 || msg->SkillValue3 > 50 ) )
×
373
  {
374
    ERROR_PRINTLN( "Create Character: Starting skill values incorrect" );
×
375
    client->forceDisconnect();
×
376
    return;
×
377
  }
378

379
  ////HASH
380
  // moved down here, after all error checking passes, else we get a half-created PC in the save.
381
  objStorageManager.objecthash.Insert( chr );
×
382
  ////
383

384
  if ( !noskills )
×
385
  {
386
    const Mobile::Attribute* pAttr;
387
    pAttr = GetUOSkill( msg->SkillNumber1 ).pAttr;
×
388
    if ( pAttr )
×
389
      chr->attribute( pAttr->attrid ).base( msg->SkillValue1 * 10 );
×
390
    pAttr = GetUOSkill( msg->SkillNumber2 ).pAttr;
×
391
    if ( pAttr )
×
392
      chr->attribute( pAttr->attrid ).base( msg->SkillValue2 * 10 );
×
393
    pAttr = GetUOSkill( msg->SkillNumber3 ).pAttr;
×
394
    if ( pAttr )
×
395
      chr->attribute( pAttr->attrid ).base( msg->SkillValue3 * 10 );
×
396
  }
397

398
  chr->calc_vital_stuff();
×
399
  chr->set_vitals_to_maximum();
×
400

401

402
  chr->created_at = read_gameclock();
×
403

404
  Items::Item* tmpitem;
405
  if ( validhair( cfBEu16( msg->HairStyle ) ) )
×
406
  {
407
    tmpitem = Items::Item::create( cfBEu16( msg->HairStyle ) );
×
408
    tmpitem->layer = LAYER_HAIR;
×
409
    tmpitem->color = cfBEu16( msg->HairColor );
×
410
    if ( chr->equippable( tmpitem ) )  // check it or passert will trigger
×
411
      chr->equip( tmpitem );
×
412
    else
413
    {
414
      ERROR_PRINTLN( "Create Character: Failed to equip hair {:#x}", tmpitem->graphic );
×
415
      tmpitem->destroy();
×
416
    }
417
  }
418

419
  if ( validbeard( cfBEu16( msg->BeardStyle ) ) )
×
420
  {
421
    tmpitem = Items::Item::create( cfBEu16( msg->BeardStyle ) );
×
422
    tmpitem->layer = LAYER_BEARD;
×
423
    tmpitem->color = cfBEu16( msg->BeardColor );
×
424
    if ( chr->equippable( tmpitem ) )  // check it or passert will trigger
×
425
      chr->equip( tmpitem );
×
426
    else
427
    {
428
      ERROR_PRINTLN( "Create Character: Failed to equip beard {:#x}", tmpitem->graphic );
×
429
      tmpitem->destroy();
×
430
    }
431
  }
432

433
  UContainer* backpack = (UContainer*)Items::Item::create( UOBJ_BACKPACK );
×
434
  backpack->layer = LAYER_BACKPACK;
×
435
  chr->equip( backpack );
×
436

437
  if ( settingsManager.ssopt.starting_gold != 0 )
×
438
  {
439
    tmpitem = Items::Item::create( 0x0EED );
×
440
    tmpitem->setamount( settingsManager.ssopt.starting_gold );
×
441
    u8 newSlot = 1;
×
442
    if ( !backpack->can_add_to_slot( newSlot ) || !tmpitem->slot_index( newSlot ) )
×
443
    {
444
      tmpitem->setposition( chr->pos() );
×
445
      add_item_to_world( tmpitem );
×
446
      register_with_supporting_multi( tmpitem );
×
447
      move_item( tmpitem, tmpitem->pos() );
×
448
    }
449
    else
450
      backpack->add( tmpitem, Pos2d( 46, 91 ) );
×
451
  }
452

453
  if ( chr->race == Plib::RACE_HUMAN ||
×
454
       chr->race == Plib::RACE_ELF )  // Gargoyles dont have shirts, pants, shoes and daggers.
×
455
  {
456
    tmpitem = Items::Item::create( 0x170F );
×
457
    tmpitem->newbie( settingsManager.ssopt.newbie_starting_equipment );
×
458
    tmpitem->layer = LAYER_SHOES;
×
459
    tmpitem->color = 0x021F;
×
460
    chr->equip( tmpitem );
×
461

462
    tmpitem = Items::Item::create( 0xF51 );
×
463
    tmpitem->newbie( settingsManager.ssopt.newbie_starting_equipment );
×
464
    tmpitem->layer = LAYER_HAND1;
×
465
    chr->equip( tmpitem );
×
466

467
    unsigned short pantstype, shirttype;
468
    if ( chr->gender == Plib::GENDER_FEMALE )
×
469
    {
470
      pantstype = 0x1516;
×
471
      shirttype = 0x1517;
×
472
    }
473
    else
474
    {
475
      pantstype = 0x152e;
×
476
      shirttype = 0x1517;
×
477
    }
478

479
    tmpitem = Items::Item::create( pantstype );
×
480
    tmpitem->newbie( settingsManager.ssopt.newbie_starting_equipment );
×
481
    tmpitem->layer = Plib::tilelayer( pantstype );
×
482
    tmpitem->color = cfBEu16( msg->pantscolor );  // 0x0284;
×
483
    chr->equip( tmpitem );
×
484

485
    tmpitem = Items::Item::create( shirttype );
×
486
    tmpitem->newbie( settingsManager.ssopt.newbie_starting_equipment );
×
487
    tmpitem->layer = Plib::tilelayer( shirttype );
×
488
    tmpitem->color = cfBEu16( msg->shirtcolor );
×
489
    chr->equip( tmpitem );
×
490
  }
×
491
  else if ( chr->race == Plib::RACE_GARGOYLE )  // Gargoyles have Robes.
×
492
  {
493
    tmpitem = Items::Item::create( 0x1F03 );
×
494
    tmpitem->newbie( settingsManager.ssopt.newbie_starting_equipment );
×
495
    tmpitem->layer = LAYER_ROBE_DRESS;
×
496
    tmpitem->color = cfBEu16( msg->shirtcolor );
×
497
    chr->equip( tmpitem );
×
498
  }
499

500
  client->chr = chr;
×
501
  client->acct->set_character( msg->CharNumber, client->chr );
×
502

503
  POLLOGLN( "Account {} created character {:#x}", client->acct->name(), chr->serial );
×
504
  SetCharacterWorldPosition( chr, Realms::WorldChangeReason::PlayerEnter );
×
505
  client->msgtype_filter = networkManager.game_filter.get();
×
506
  start_client_char( client );
×
507

508
  // FIXME : Shouldn't this be triggered at the end of creation?
509
  run_logon_script( chr );
×
510

511
  ref_ptr<Bscript::EScriptProgram> prog =
512
      find_script( "misc/oncreate", true, Plib::systemstate.config.cache_interactive_scripts );
×
513
  if ( prog.get() != nullptr )
×
514
  {
515
    std::unique_ptr<UOExecutor> ex( create_script_executor() );
×
516

517
    std::unique_ptr<Bscript::ObjArray> arr( new Bscript::ObjArray );
×
518
    arr->addElement( new Bscript::BLong( msg->SkillNumber1 ) );
×
519
    arr->addElement( new Bscript::BLong( msg->SkillNumber2 ) );
×
520
    arr->addElement( new Bscript::BLong( msg->SkillNumber3 ) );
×
521

522
    ex->pushArg( new Bscript::BLong( msg->profession ) );
×
523
    ex->pushArg( arr.release() );
×
524
    ex->pushArg( make_mobileref( chr ) );
×
525

526
    ex->addModule( new Module::UOExecutorModule( *ex ) );
×
527
    ex->critical( true );
×
528

529
    if ( ex->setProgram( prog.get() ) )
×
530
    {
531
      schedule_executor( ex.release() );
×
532
    }
533
    else
534
    {
535
      ERROR_PRINTLN( "script misc/oncreate: setProgram failed" );
×
536
    }
537
  }
×
538
}
×
539

540
void createchar2( Accounts::Account* acct, unsigned index )
12✔
541
{
542
  Mobile::Character* chr = new Mobile::Character( UOBJ_HUMAN_MALE );
12✔
543
  chr->acct.set( acct );
12✔
544
  acct->set_character( index, chr );
12✔
545
  chr->setname( "new character" );
12✔
546

547
  chr->serial = GetNextSerialNumber();
12✔
548
  chr->serial_ext = ctBEu32( chr->serial );
12✔
549
  chr->setposition( Pos4d( 1, 1, 1, find_realm( std::string( "britannia" ) ) ) );
12✔
550
  chr->facing = 1;
12✔
551
  chr->wornitems->serial = chr->serial;
12✔
552
  chr->wornitems->serial_ext = chr->serial_ext;
12✔
553
  chr->position_changed();
12✔
554
  chr->graphic = UOBJ_HUMAN_MALE;
12✔
555
  chr->gender = Plib::GENDER_MALE;
12✔
556
  chr->trueobjtype = chr->objtype_;
12✔
557
  chr->color = ctBEu16( 0 );
12✔
558
  chr->truecolor = chr->color;
12✔
559
  chr->created_at = read_gameclock();
12✔
560

561
  objStorageManager.objecthash.Insert( chr );
12✔
562
  chr->logged_in( false );  // constructor sets it
12✔
563
}
12✔
564

565

566
void ClientCreateCharKR( Network::Client* client, PKTIN_8D* msg )
×
567
{
568
  int charslot = ctBEu32( msg->char_slot );
×
569
  if ( client->acct == nullptr )
×
570
  {
571
    ERROR_PRINTLN( "Client from {} tried to create a character without an account!",
×
572
                   client->ipaddrAsString() );
×
573
    client->Disconnect();
×
574
    return;
×
575
  }
576
  else if ( Plib::systemstate.config.min_cmdlevel_to_login > client->acct->default_cmdlevel() )
×
577
  {
578
    // FIXME: Add send_login_error ...
579
    client->Disconnect();
×
580
    return;
×
581
  }
582
  else if ( charslot >= Plib::systemstate.config.character_slots ||
×
583
            client->acct->get_character( charslot ) != nullptr )
×
584
  {
585
    ERROR_PRINTLN( "Create Character: Invalid parameters." );
×
586
    send_login_error( client, LOGIN_ERROR_MISC );
×
587
    client->Disconnect();
×
588
    return;
×
589
  }
590
  else if ( !Plib::systemstate.config.allow_multi_clients_per_account &&
×
591
            client->acct->has_active_characters() )
×
592
  {
593
    send_login_error( client, LOGIN_ERROR_OTHER_CHAR_INUSE );
×
594
    client->Disconnect();
×
595
    return;
×
596
  }
597

598
  unsigned short graphic;
NEW
599
  Plib::URACE race = ( Plib::URACE )( msg->race - 1 );
×
600
  Plib::UGENDER gender =
×
601
      ( msg->gender & Plib::GENDER_FEMALE ) ? Plib::GENDER_FEMALE : Plib::GENDER_MALE;
×
602
  if ( race == Plib::RACE_HUMAN )
×
603
    graphic = ( gender == Plib::GENDER_FEMALE ) ? UOBJ_HUMAN_FEMALE : UOBJ_HUMAN_MALE;
×
604
  else if ( race == Plib::RACE_ELF )
×
605
    graphic = ( gender == Plib::GENDER_FEMALE ) ? UOBJ_ELF_FEMALE : UOBJ_ELF_MALE;
×
606
  else
607
    graphic = ( gender == Plib::GENDER_FEMALE ) ? UOBJ_GARGOYLE_FEMALE : UOBJ_GARGOYLE_MALE;
×
608

609

610
  Mobile::Character* chr = new Mobile::Character( graphic );
×
611

612
  chr->acct.set( client->acct );
×
613
  chr->client = client;
×
614
  chr->set_privs( client->acct->default_privlist() );
×
615
  chr->cmdlevel( client->acct->default_cmdlevel(), false );
×
616

617
  client->UOExpansionFlagClient = msg->flags;
×
618

619
  std::string tmpstr( msg->name, sizeof msg->name );
×
620
  const char* tstr = tmpstr.c_str();
×
621
  for ( unsigned int i = 0; i < strlen( tstr ); i++ )
×
622
  {
623
    char tmpchr = tstr[i];
×
624
    if ( tmpchr >= ' ' && tmpchr <= '~' )
×
625
    {
626
      if ( tmpchr != '{' && tmpchr != '}' )
×
627
        continue;
×
628
    }
629

630
    ERROR_PRINTLN(
×
631
        "Create Character: Attempted to use invalid character '{}' pos '{}' in name '{}'. Client "
632
        "IP: {} Client Name: {}",
633
        tmpchr, i, tstr, client->ipaddrAsString(), client->acct->name() );
×
634
    client->forceDisconnect();
×
635
    return;
×
636
  }
637
  chr->name_ = tstr;
×
638

639
  chr->serial = GetNextSerialNumber();
×
640
  chr->serial_ext = ctBEu32( chr->serial );
×
641
  chr->wornitems->serial = chr->serial;
×
642
  chr->wornitems->serial_ext = chr->serial_ext;
×
643

644
  chr->graphic = graphic;
×
645
  chr->race = race;
×
646
  chr->gender = gender;
×
647

648
  chr->trueobjtype = chr->objtype_;
×
649
  chr->color = cfBEu16( msg->skin_color );
×
650
  chr->truecolor = chr->color;
×
651

652
  chr->setposition( gamestate.startlocations[0]->select_coordinate() );
×
653
  chr->position_changed();
×
654
  chr->facing = Core::FACING_W;
×
655

656
  bool valid_stats = false;
×
657
  unsigned int stat_total = msg->strength + msg->intelligence + msg->dexterity;
×
658
  unsigned int stat_min, stat_max;
659
  char* maxpos;
660
  std::vector<std::string>::size_type sidx;
661
  for ( sidx = 0; !valid_stats && sidx < settingsManager.ssopt.total_stats_at_creation.size();
×
662
        ++sidx )
663
  {
664
    const char* statstr = settingsManager.ssopt.total_stats_at_creation[sidx].c_str();
×
665
    stat_max = ( stat_min = strtoul( statstr, &maxpos, 0 ) );
×
666
    if ( *( maxpos++ ) == '-' )
×
667
      stat_max = strtoul( maxpos, nullptr, 0 );
×
668
    if ( stat_total >= stat_min && stat_total <= stat_max )
×
669
      valid_stats = true;
×
670
  }
671
  if ( !valid_stats )
×
672
  {
673
    ERROR_PRINTLN(
×
674
        "Create Character: Stats sum to {}. "
675
        "Valid values/ranges are: {}",
676
        stat_total, settingsManager.ssopt.total_stats_at_creation );
677
    client->forceDisconnect();
×
678
    return;
×
679
  }
680
  if ( msg->strength < 10 || msg->intelligence < 10 || msg->dexterity < 10 )
×
681
  {
682
    ERROR_PRINTLN( "Create Character: A stat was too small. Str={} Int={} Dex={}", msg->strength,
×
683
                   msg->intelligence, msg->dexterity );
×
684

685
    client->forceDisconnect();
×
686
    return;
×
687
  }
688
  if ( gamestate.pAttrStrength )
×
689
    chr->attribute( gamestate.pAttrStrength->attrid ).base( msg->strength * 10 );
×
690
  if ( gamestate.pAttrIntelligence )
×
691
    chr->attribute( gamestate.pAttrIntelligence->attrid ).base( msg->intelligence * 10 );
×
692
  if ( gamestate.pAttrDexterity )
×
693
    chr->attribute( gamestate.pAttrDexterity->attrid ).base( msg->dexterity * 10 );
×
694

695

696
  if ( msg->skillnumber1 > networkManager.uoclient_general.maxskills ||
×
697
       msg->skillnumber2 > networkManager.uoclient_general.maxskills ||
×
698
       msg->skillnumber3 > networkManager.uoclient_general.maxskills ||
×
699
       msg->skillnumber4 > networkManager.uoclient_general.maxskills )
×
700
  {
701
    ERROR_PRINTLN( "Create Character: A skill number was out of range" );
×
702
    client->forceDisconnect();
×
703
    return;
×
704
  }
705

706
  bool noskills =
×
707
      ( msg->skillvalue1 + msg->skillvalue2 + msg->skillvalue3 + msg->skillvalue4 == 0 ) &&
×
708
      msg->profession;
×
709
  if ( ( !noskills ) &&
×
710
       ( ( msg->skillvalue1 + msg->skillvalue2 + msg->skillvalue3 + msg->skillvalue4 != 120 ) ||
×
711
         msg->skillvalue1 > 50 || msg->skillvalue2 > 50 || msg->skillvalue3 > 50 ||
×
712
         msg->skillvalue4 > 50 ) )
×
713
  {
714
    ERROR_PRINTLN( "Create Character: Starting skill values incorrect" );
×
715
    client->forceDisconnect();
×
716
    return;
×
717
  }
718

719
  ////HASH
720
  // moved down here, after all error checking passes, else we get a half-created PC in the save.
721
  objStorageManager.objecthash.Insert( chr );
×
722
  ////
723

724
  if ( !noskills )
×
725
  {
726
    const Mobile::Attribute* pAttr;
727
    pAttr = GetUOSkill( msg->skillnumber1 ).pAttr;
×
728
    if ( pAttr )
×
729
      chr->attribute( pAttr->attrid ).base( msg->skillvalue1 * 10 );
×
730
    pAttr = GetUOSkill( msg->skillnumber2 ).pAttr;
×
731
    if ( pAttr )
×
732
      chr->attribute( pAttr->attrid ).base( msg->skillvalue2 * 10 );
×
733
    pAttr = GetUOSkill( msg->skillnumber3 ).pAttr;
×
734
    if ( pAttr )
×
735
      chr->attribute( pAttr->attrid ).base( msg->skillvalue3 * 10 );
×
736
    pAttr = GetUOSkill( msg->skillnumber4 ).pAttr;
×
737
    if ( pAttr )
×
738
      chr->attribute( pAttr->attrid ).base( msg->skillvalue4 * 10 );
×
739
  }
740

741
  chr->calc_vital_stuff();
×
742
  chr->set_vitals_to_maximum();
×
743

744

745
  chr->created_at = read_gameclock();
×
746

747
  Items::Item* tmpitem;
748
  if ( validhair( cfBEu16( msg->hairstyle ) ) )
×
749
  {
750
    tmpitem = Items::Item::create( cfBEu16( msg->hairstyle ) );
×
751
    tmpitem->layer = LAYER_HAIR;
×
752
    tmpitem->color = cfBEu16( msg->haircolor );
×
753
    if ( chr->equippable( tmpitem ) )  // check it or passert will trigger
×
754
      chr->equip( tmpitem );
×
755
    else
756
    {
757
      ERROR_PRINTLN( "Create Character: Failed to equip hair {:#x}", tmpitem->graphic );
×
758
      tmpitem->destroy();
×
759
    }
760
  }
761

762
  if ( validbeard( cfBEu16( msg->beardstyle ) ) )
×
763
  {
764
    tmpitem = Items::Item::create( cfBEu16( msg->beardstyle ) );
×
765
    tmpitem->layer = LAYER_BEARD;
×
766
    tmpitem->color = cfBEu16( msg->beardcolor );
×
767
    if ( chr->equippable( tmpitem ) )  // check it or passert will trigger
×
768
      chr->equip( tmpitem );
×
769
    else
770
    {
771
      ERROR_PRINTLN( "Create Character: Failed to equip beard {:#x}", tmpitem->graphic );
×
772
      tmpitem->destroy();
×
773
    }
774
  }
775

776
  if ( validface( cfBEu16( msg->face_id ) ) )
×
777
  {
778
    tmpitem = Items::Item::create( cfBEu16( msg->face_id ) );
×
779
    tmpitem->layer = LAYER_FACE;
×
780
    tmpitem->color = cfBEu16( msg->face_color );
×
781
    if ( chr->equippable( tmpitem ) )  // check it or passert will trigger
×
782
      chr->equip( tmpitem );
×
783
    else
784
    {
785
      ERROR_PRINTLN( "Create Character: Failed to equip face {:#x}", tmpitem->graphic );
×
786
      tmpitem->destroy();
×
787
    }
788
  }
789

790
  UContainer* backpack = (UContainer*)Items::Item::create( UOBJ_BACKPACK );
×
791
  backpack->layer = LAYER_BACKPACK;
×
792
  chr->equip( backpack );
×
793

794
  if ( settingsManager.ssopt.starting_gold != 0 )
×
795
  {
796
    tmpitem = Items::Item::create( 0x0EED );
×
797
    tmpitem->setamount( settingsManager.ssopt.starting_gold );
×
798
    u8 newSlot = 1;
×
799
    if ( !backpack->can_add_to_slot( newSlot ) || !tmpitem->slot_index( newSlot ) )
×
800
    {
801
      tmpitem->setposition( chr->pos() );
×
802
      add_item_to_world( tmpitem );
×
803
      register_with_supporting_multi( tmpitem );
×
804
      move_item( tmpitem, tmpitem->pos() );
×
805
    }
806
    else
807
      backpack->add( tmpitem, Pos2d( 46, 91 ) );
×
808
  }
809

810
  if ( chr->race == Plib::RACE_HUMAN ||
×
811
       chr->race == Plib::RACE_ELF )  // Gargoyles dont have shirts, pants, shoes and daggers.
×
812
  {
813
    tmpitem = Items::Item::create( 0x170F );
×
814
    tmpitem->newbie( settingsManager.ssopt.newbie_starting_equipment );
×
815
    tmpitem->layer = LAYER_SHOES;
×
816
    tmpitem->color = 0x021F;
×
817
    chr->equip( tmpitem );
×
818

819
    tmpitem = Items::Item::create( 0xF51 );
×
820
    tmpitem->newbie( settingsManager.ssopt.newbie_starting_equipment );
×
821
    tmpitem->layer = LAYER_HAND1;
×
822
    chr->equip( tmpitem );
×
823

824
    unsigned short pantstype, shirttype;
825
    if ( chr->gender == Plib::GENDER_FEMALE )
×
826
    {
827
      pantstype = 0x1516;
×
828
      shirttype = 0x1517;
×
829
    }
830
    else
831
    {
832
      pantstype = 0x152e;
×
833
      shirttype = 0x1517;
×
834
    }
835

836
    tmpitem = Items::Item::create( pantstype );
×
837
    tmpitem->newbie( settingsManager.ssopt.newbie_starting_equipment );
×
838
    tmpitem->layer = Plib::tilelayer( pantstype );
×
839
    tmpitem->color = cfBEu16( msg->pantscolor );  // 0x0284;
×
840
    chr->equip( tmpitem );
×
841

842
    tmpitem = Items::Item::create( shirttype );
×
843
    tmpitem->newbie( settingsManager.ssopt.newbie_starting_equipment );
×
844
    tmpitem->layer = Plib::tilelayer( shirttype );
×
845
    tmpitem->color = cfBEu16( msg->shirtcolor );
×
846
    chr->equip( tmpitem );
×
847
  }
×
848
  else if ( chr->race == Plib::RACE_GARGOYLE )  // Gargoyles have Robes.
×
849
  {
850
    tmpitem = Items::Item::create( 0x1F03 );
×
851
    tmpitem->newbie( settingsManager.ssopt.newbie_starting_equipment );
×
852
    tmpitem->layer = LAYER_ROBE_DRESS;
×
853
    tmpitem->color = cfBEu16( msg->shirtcolor );
×
854
    chr->equip( tmpitem );
×
855
  }
856

857
  client->chr = chr;
×
858
  client->acct->set_character( charslot, client->chr );
×
859

860
  POLLOGLN( "Account {} created character {:#x}", client->acct->name(), chr->serial );
×
861
  SetCharacterWorldPosition( chr, Realms::WorldChangeReason::PlayerEnter );
×
862
  client->msgtype_filter = networkManager.game_filter.get();
×
863
  start_client_char( client );
×
864

865
  // FIXME : Shouldn't this be triggered at the end of creation?
866
  run_logon_script( chr );
×
867

868
  ref_ptr<Bscript::EScriptProgram> prog =
869
      find_script( "misc/oncreate", true, Plib::systemstate.config.cache_interactive_scripts );
×
870
  if ( prog.get() != nullptr )
×
871
  {
872
    std::unique_ptr<UOExecutor> ex( create_script_executor() );
×
873

874
    std::unique_ptr<Bscript::ObjArray> arr( new Bscript::ObjArray );
×
875
    arr->addElement( new Bscript::BLong( msg->skillnumber1 ) );
×
876
    arr->addElement( new Bscript::BLong( msg->skillnumber2 ) );
×
877
    arr->addElement( new Bscript::BLong( msg->skillnumber3 ) );
×
878
    arr->addElement( new Bscript::BLong( msg->skillnumber4 ) );
×
879

880
    ex->pushArg( new Bscript::BLong( msg->profession ) );
×
881
    ex->pushArg( arr.release() );
×
882
    ex->pushArg( make_mobileref( chr ) );
×
883

884
    ex->addModule( new Module::UOExecutorModule( *ex ) );
×
885
    ex->critical( true );
×
886

887
    if ( ex->setProgram( prog.get() ) )
×
888
    {
889
      schedule_executor( ex.release() );
×
890
    }
891
    else
892
    {
893
      ERROR_PRINTLN( "script misc/oncreate: setProgram failed" );
×
894
    }
895
  }
×
896
}
×
897

898
void ClientCreateChar70160( Network::Client* client, PKTIN_F8* msg )
×
899
{
900
  if ( client->acct == nullptr )
×
901
  {
902
    ERROR_PRINTLN( "Client from {} tried to create a character without an account!",
×
903
                   client->ipaddrAsString() );
×
904
    client->forceDisconnect();
×
905
    return;
×
906
  }
907
  else if ( Plib::systemstate.config.min_cmdlevel_to_login > client->acct->default_cmdlevel() )
×
908
  {
909
    send_login_error( client, LOGIN_ERROR_MISC );
×
910
    client->Disconnect();
×
911
    return;
×
912
  }
913
  else if ( msg->CharNumber >= Plib::systemstate.config.character_slots ||
×
914
            client->acct->get_character( msg->CharNumber ) != nullptr ||
×
915
            msg->StartIndex >= gamestate.startlocations.size() )
×
916
  {
917
    ERROR_PRINTLN( "Create Character: Invalid parameters." );
×
918
    send_login_error( client, LOGIN_ERROR_MISC );
×
919
    client->Disconnect();
×
920
    return;
×
921
  }
922
  else if ( !Plib::systemstate.config.allow_multi_clients_per_account &&
×
923
            client->acct->has_active_characters() )
×
924
  {
925
    send_login_error( client, LOGIN_ERROR_OTHER_CHAR_INUSE );
×
926
    client->Disconnect();
×
927
    return;
×
928
  }
929

930
  unsigned short graphic;
931
  Plib::URACE race;
932
  Plib::UGENDER gender = ( ( msg->Sex & Network::FLAG_GENDER ) == Network::FLAG_GENDER )
×
933
                             ? Plib::GENDER_FEMALE
934
                             : Plib::GENDER_MALE;
935
  if ( client->ClientType & Network::CLIENTTYPE_7000 )
×
936
  {
937
    /*
938
    0x00 / 0x01 = human male/female
939
    0x02 / 0x03 = human male/female
940
    0x04 / 0x05 = elf male/female
941
    0x06 / 0x07 = gargoyle male/female
942
    */
943
    if ( ( msg->Sex & 0x6 ) == 0x6 )
×
944
    {
945
      race = Plib::RACE_GARGOYLE;
×
946
      graphic = ( gender == Plib::GENDER_FEMALE ) ? UOBJ_GARGOYLE_FEMALE : UOBJ_GARGOYLE_MALE;
×
947
    }
948
    else if ( ( msg->Sex & 0x4 ) == 0x4 )
×
949
    {
950
      race = Plib::RACE_ELF;
×
951
      graphic = ( gender == Plib::GENDER_FEMALE ) ? UOBJ_ELF_FEMALE : UOBJ_ELF_MALE;
×
952
    }
953
    else
954
    {
955
      race = Plib::RACE_HUMAN;
×
956
      graphic = ( gender == Plib::GENDER_FEMALE ) ? UOBJ_HUMAN_FEMALE : UOBJ_HUMAN_MALE;
×
957
    }
958
  }
959
  else
960
  {
961
    /*
962
    0x00 / 0x01 = human male/female
963
    0x02 / 0x03 = elf male/female
964
    */
965
    if ( ( msg->Sex & Network::FLAG_RACE ) == Network::FLAG_RACE )
×
966
    {
967
      race = Plib::RACE_ELF;
×
968
      graphic = ( gender == Plib::GENDER_FEMALE ) ? UOBJ_ELF_FEMALE : UOBJ_ELF_MALE;
×
969
    }
970
    else
971
    {
972
      race = Plib::RACE_HUMAN;
×
973
      graphic = ( gender == Plib::GENDER_FEMALE ) ? UOBJ_HUMAN_FEMALE : UOBJ_HUMAN_MALE;
×
974
    }
975
  }
976

977
  Mobile::Character* chr = new Mobile::Character( graphic );
×
978

979
  chr->acct.set( client->acct );
×
980
  chr->client = client;
×
981
  chr->set_privs( client->acct->default_privlist() );
×
982
  chr->cmdlevel( client->acct->default_cmdlevel(), false );
×
983

984
  client->UOExpansionFlagClient = ctBEu32( msg->clientflag );
×
985

986
  std::string tmpstr( msg->Name, sizeof msg->Name );
×
987
  const char* tstr = tmpstr.c_str();
×
988
  for ( unsigned int i = 0; i < strlen( tstr ); i++ )
×
989
  {
990
    char tmpchr = tstr[i];
×
991
    if ( tmpchr >= ' ' && tmpchr <= '~' )
×
992
    {
993
      if ( tmpchr != '{' && tmpchr != '}' )
×
994
        continue;
×
995
    }
996

997
    ERROR_PRINTLN(
×
998
        "Create Character: Attempted to use invalid character '{}' pos '{}' in name '{}'. Client "
999
        "IP: {} Client Name: {}",
1000
        tmpchr, i, tstr, client->ipaddrAsString(), client->acct->name() );
×
1001
    client->Disconnect();
×
1002
    return;
×
1003
  }
1004
  chr->name_ = tstr;
×
1005

1006
  chr->serial = GetNextSerialNumber();
×
1007
  chr->serial_ext = ctBEu32( chr->serial );
×
1008
  chr->wornitems->serial = chr->serial;
×
1009
  chr->wornitems->serial_ext = chr->serial_ext;
×
1010

1011
  chr->graphic = graphic;
×
1012
  chr->race = race;
×
1013
  chr->gender = gender;
×
1014

1015
  chr->trueobjtype = chr->objtype_;
×
1016
  chr->color = cfBEu16( msg->SkinColor );
×
1017
  chr->truecolor = chr->color;
×
1018

1019
  chr->setposition( gamestate.startlocations[msg->StartIndex]->select_coordinate() );
×
1020
  chr->position_changed();
×
1021
  chr->facing = Core::FACING_W;
×
1022

1023
  bool valid_stats = false;
×
1024
  unsigned int stat_total = msg->Strength + msg->Intelligence + msg->Dexterity;
×
1025
  unsigned int stat_min, stat_max;
1026
  char* maxpos;
1027
  std::vector<std::string>::size_type sidx;
1028
  for ( sidx = 0; !valid_stats && sidx < settingsManager.ssopt.total_stats_at_creation.size();
×
1029
        ++sidx )
1030
  {
1031
    const char* statstr = settingsManager.ssopt.total_stats_at_creation[sidx].c_str();
×
1032
    stat_max = ( stat_min = strtoul( statstr, &maxpos, 0 ) );
×
1033
    if ( *( maxpos++ ) == '-' )
×
1034
      stat_max = strtoul( maxpos, nullptr, 0 );
×
1035
    if ( stat_total >= stat_min && stat_total <= stat_max )
×
1036
      valid_stats = true;
×
1037
  }
1038
  if ( !valid_stats )
×
1039
  {
1040
    ERROR_PRINTLN( "Create Character: Stats sum to {}.\nValid values/ranges are: {}", stat_total,
×
1041
                   settingsManager.ssopt.total_stats_at_creation );
1042
    client->forceDisconnect();
×
1043
    return;
×
1044
  }
1045
  if ( msg->Strength < 10 || msg->Intelligence < 10 || msg->Dexterity < 10 )
×
1046
  {
1047
    ERROR_PRINTLN( "Create Character: A stat was too small. Str={} Int={} Dex={}", msg->Strength,
×
1048
                   msg->Intelligence, msg->Dexterity );
×
1049

1050
    client->forceDisconnect();
×
1051
    return;
×
1052
  }
1053
  if ( gamestate.pAttrStrength )
×
1054
    chr->attribute( gamestate.pAttrStrength->attrid ).base( msg->Strength * 10 );
×
1055
  if ( gamestate.pAttrIntelligence )
×
1056
    chr->attribute( gamestate.pAttrIntelligence->attrid ).base( msg->Intelligence * 10 );
×
1057
  if ( gamestate.pAttrDexterity )
×
1058
    chr->attribute( gamestate.pAttrDexterity->attrid ).base( msg->Dexterity * 10 );
×
1059

1060
  // With latest clients EA broke the prof.txt, added Evaluating Intelligence and Spirit Speak which
1061
  // returns SkillNumber 0xFF
1062
  // Check for it here to not crash the client during char creation
1063
  bool broken_prof = ( msg->SkillNumber1 == 0xFF || msg->SkillNumber2 == 0xFF ||
×
1064
                       msg->SkillNumber3 == 0xFF || msg->SkillNumber4 == 0xFF ) &&
×
1065
                     msg->profession;
×
1066

1067
  if ( broken_prof )
×
1068
  {
1069
    unsigned char temp_skillid = 0;
×
1070

1071
    if ( msg->profession == 2 )  // Mage profession
×
1072
      temp_skillid = SKILLID_EVALUATINGINTEL;
×
1073
    if ( msg->profession == 4 )  // Necromancy profession
×
1074
      temp_skillid = SKILLID_SPIRITSPEAK;
×
1075

1076

1077
    if ( msg->SkillNumber1 == 0xFF )
×
1078
    {
1079
      msg->SkillNumber1 = temp_skillid;
×
1080
      msg->SkillValue1 = 30;
×
1081
    }
1082
    else if ( msg->SkillNumber2 == 0xFF )
×
1083
    {
1084
      msg->SkillNumber2 = temp_skillid;
×
1085
      msg->SkillValue2 = 30;
×
1086
    }
1087
    else if ( msg->SkillNumber3 == 0xFF )
×
1088
    {
1089
      msg->SkillNumber3 = temp_skillid;
×
1090
      msg->SkillValue3 = 30;
×
1091
    }
1092
    else if ( msg->SkillNumber4 == 0xFF )
×
1093
    {
1094
      msg->SkillNumber4 = temp_skillid;
×
1095
      msg->SkillValue4 = 30;
×
1096
    }
1097
  }
1098

1099

1100
  if ( msg->SkillNumber1 > networkManager.uoclient_general.maxskills ||
×
1101
       msg->SkillNumber2 > networkManager.uoclient_general.maxskills ||
×
1102
       msg->SkillNumber3 > networkManager.uoclient_general.maxskills ||
×
1103
       msg->SkillNumber4 > networkManager.uoclient_general.maxskills )
×
1104
  {
1105
    ERROR_PRINTLN( "Create Character: A skill number was out of range" );
×
1106
    client->forceDisconnect();
×
1107
    return;
×
1108
  }
1109

1110
  bool noskills =
×
1111
      ( msg->SkillValue1 + msg->SkillValue2 + msg->SkillValue3 + msg->SkillValue4 == 0 ) &&
×
1112
      msg->profession;
×
1113

1114
  if ( ( !noskills ) &&
×
1115
       ( ( msg->SkillValue1 + msg->SkillValue2 + msg->SkillValue3 + msg->SkillValue4 != 120 ) ||
×
1116
         msg->SkillValue1 > 50 || msg->SkillValue2 > 50 || msg->SkillValue3 > 50 ||
×
1117
         msg->SkillValue4 > 50 ) )
×
1118
  {
1119
    ERROR_PRINTLN( "Create Character: Starting skill values incorrect" );
×
1120
    client->forceDisconnect();
×
1121
    return;
×
1122
  }
1123

1124
  ////HASH
1125
  // moved down here, after all error checking passes, else we get a half-created PC in the save.
1126
  objStorageManager.objecthash.Insert( chr );
×
1127
  ////
1128

1129
  if ( !noskills )
×
1130
  {
1131
    const Mobile::Attribute* pAttr;
1132
    pAttr = GetUOSkill( msg->SkillNumber1 ).pAttr;
×
1133
    if ( pAttr )
×
1134
      chr->attribute( pAttr->attrid ).base( msg->SkillValue1 * 10 );
×
1135
    pAttr = GetUOSkill( msg->SkillNumber2 ).pAttr;
×
1136
    if ( pAttr )
×
1137
      chr->attribute( pAttr->attrid ).base( msg->SkillValue2 * 10 );
×
1138
    pAttr = GetUOSkill( msg->SkillNumber3 ).pAttr;
×
1139
    if ( pAttr )
×
1140
      chr->attribute( pAttr->attrid ).base( msg->SkillValue3 * 10 );
×
1141
    pAttr = GetUOSkill( msg->SkillNumber4 ).pAttr;
×
1142
    if ( pAttr )
×
1143
      chr->attribute( pAttr->attrid ).base( msg->SkillValue4 * 10 );
×
1144
  }
1145

1146
  chr->calc_vital_stuff();
×
1147
  chr->set_vitals_to_maximum();
×
1148

1149

1150
  chr->created_at = read_gameclock();
×
1151

1152
  Items::Item* tmpitem;
1153
  if ( validhair( cfBEu16( msg->HairStyle ) ) )
×
1154
  {
1155
    tmpitem = Items::Item::create( cfBEu16( msg->HairStyle ) );
×
1156
    tmpitem->layer = LAYER_HAIR;
×
1157
    tmpitem->color = cfBEu16( msg->HairColor );
×
1158
    if ( chr->equippable( tmpitem ) )  // check it or passert will trigger
×
1159
      chr->equip( tmpitem );
×
1160
    else
1161
    {
1162
      ERROR_PRINTLN( "Create Character: Failed to equip hair {:#x}", tmpitem->graphic );
×
1163
      tmpitem->destroy();
×
1164
    }
1165
  }
1166

1167
  if ( validbeard( cfBEu16( msg->BeardStyle ) ) )
×
1168
  {
1169
    tmpitem = Items::Item::create( cfBEu16( msg->BeardStyle ) );
×
1170
    tmpitem->layer = LAYER_BEARD;
×
1171
    tmpitem->color = cfBEu16( msg->BeardColor );
×
1172
    if ( chr->equippable( tmpitem ) )  // check it or passert will trigger
×
1173
      chr->equip( tmpitem );
×
1174
    else
1175
    {
1176
      ERROR_PRINTLN( "Create Character: Failed to equip beard {:#x}", tmpitem->graphic );
×
1177
      tmpitem->destroy();
×
1178
    }
1179
  }
1180

1181
  UContainer* backpack = (UContainer*)Items::Item::create( UOBJ_BACKPACK );
×
1182
  backpack->layer = LAYER_BACKPACK;
×
1183
  chr->equip( backpack );
×
1184

1185
  if ( settingsManager.ssopt.starting_gold != 0 )
×
1186
  {
1187
    tmpitem = Items::Item::create( 0x0EED );
×
1188
    tmpitem->setamount( settingsManager.ssopt.starting_gold );
×
1189
    u8 newSlot = 1;
×
1190
    if ( !backpack->can_add_to_slot( newSlot ) || !tmpitem->slot_index( newSlot ) )
×
1191
    {
1192
      tmpitem->setposition( chr->pos() );
×
1193
      add_item_to_world( tmpitem );
×
1194
      register_with_supporting_multi( tmpitem );
×
1195
      move_item( tmpitem, tmpitem->pos() );
×
1196
    }
1197
    else
1198
      backpack->add( tmpitem, Pos2d( 46, 91 ) );
×
1199
  }
1200

1201
  if ( chr->race == Plib::RACE_HUMAN ||
×
1202
       chr->race == Plib::RACE_ELF )  // Gargoyles dont have shirts, pants, shoes and daggers.
×
1203
  {
1204
    tmpitem = Items::Item::create( 0x170F );
×
1205
    tmpitem->newbie( settingsManager.ssopt.newbie_starting_equipment );
×
1206
    tmpitem->layer = LAYER_SHOES;
×
1207
    tmpitem->color = 0x021F;
×
1208
    chr->equip( tmpitem );
×
1209

1210
    tmpitem = Items::Item::create( 0xF51 );
×
1211
    tmpitem->newbie( settingsManager.ssopt.newbie_starting_equipment );
×
1212
    tmpitem->layer = LAYER_HAND1;
×
1213
    chr->equip( tmpitem );
×
1214

1215
    unsigned short pantstype, shirttype;
1216
    if ( chr->gender == Plib::GENDER_FEMALE )
×
1217
    {
1218
      pantstype = 0x1516;
×
1219
      shirttype = 0x1517;
×
1220
    }
1221
    else
1222
    {
1223
      pantstype = 0x152e;
×
1224
      shirttype = 0x1517;
×
1225
    }
1226

1227
    tmpitem = Items::Item::create( pantstype );
×
1228
    tmpitem->newbie( settingsManager.ssopt.newbie_starting_equipment );
×
1229
    tmpitem->layer = Plib::tilelayer( pantstype );
×
1230
    tmpitem->color = cfBEu16( msg->pantscolor );  // 0x0284;
×
1231
    chr->equip( tmpitem );
×
1232

1233
    tmpitem = Items::Item::create( shirttype );
×
1234
    tmpitem->newbie( settingsManager.ssopt.newbie_starting_equipment );
×
1235
    tmpitem->layer = Plib::tilelayer( shirttype );
×
1236
    tmpitem->color = cfBEu16( msg->shirtcolor );
×
1237
    chr->equip( tmpitem );
×
1238
  }
×
1239
  else if ( chr->race == Plib::RACE_GARGOYLE )  // Gargoyles have Robes.
×
1240
  {
1241
    tmpitem = Items::Item::create( 0x1F03 );
×
1242
    tmpitem->newbie( settingsManager.ssopt.newbie_starting_equipment );
×
1243
    tmpitem->layer = LAYER_ROBE_DRESS;
×
1244
    tmpitem->color = cfBEu16( msg->shirtcolor );
×
1245
    chr->equip( tmpitem );
×
1246
  }
1247

1248
  client->chr = chr;
×
1249
  client->acct->set_character( msg->CharNumber, client->chr );
×
1250

1251
  POLLOGLN( "Account {} created character {:#x}", client->acct->name(), chr->serial );
×
1252
  SetCharacterWorldPosition( chr, Realms::WorldChangeReason::PlayerEnter );
×
1253
  client->msgtype_filter = networkManager.game_filter.get();
×
1254
  start_client_char( client );
×
1255

1256
  // FIXME : Shouldn't this be triggered at the end of creation?
1257
  run_logon_script( chr );
×
1258

1259
  ref_ptr<Bscript::EScriptProgram> prog =
1260
      find_script( "misc/oncreate", true, Plib::systemstate.config.cache_interactive_scripts );
×
1261
  if ( prog.get() != nullptr )
×
1262
  {
1263
    std::unique_ptr<UOExecutor> ex( create_script_executor() );
×
1264

1265
    std::unique_ptr<Bscript::ObjArray> arr( new Bscript::ObjArray );
×
1266
    arr->addElement( new Bscript::BLong( msg->SkillNumber1 ) );
×
1267
    arr->addElement( new Bscript::BLong( msg->SkillNumber2 ) );
×
1268
    arr->addElement( new Bscript::BLong( msg->SkillNumber3 ) );
×
1269
    arr->addElement( new Bscript::BLong( msg->SkillNumber4 ) );
×
1270

1271
    ex->pushArg( new Bscript::BLong( msg->profession ) );
×
1272
    ex->pushArg( arr.release() );
×
1273
    ex->pushArg( make_mobileref( chr ) );
×
1274

1275
    ex->addModule( new Module::UOExecutorModule( *ex ) );
×
1276
    ex->critical( true );
×
1277

1278
    if ( ex->setProgram( prog.get() ) )
×
1279
    {
1280
      schedule_executor( ex.release() );
×
1281
    }
1282
    else
1283
    {
1284
      ERROR_PRINTLN( "script misc/oncreate: setProgram failed" );
×
1285
    }
1286
  }
×
1287
}
×
1288
}  // namespace Pol::Core
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