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

polserver / polserver / 21108840797

18 Jan 2026 08:35AM UTC coverage: 60.508% (+0.02%) from 60.492%
21108840797

push

github

web-flow
ClangTidy readability-else-after-return (#857)

* trigger tidy

* Automated clang-tidy change: readability-else-after-return

* compile test

* rerun

* Automated clang-tidy change: readability-else-after-return

* trigger..

* Automated clang-tidy change: readability-else-after-return

* manually removed a few

* Automated clang-tidy change: readability-else-after-return

* removed duplicate code

* fix remaining warnings

* fixed scope

---------

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

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

46 existing lines in 25 files now uncovered.

44448 of 73458 relevant lines covered (60.51%)

525066.38 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

NEW
119
  if ( ( ( 0x203B <= HairStyle ) && ( HairStyle <= 0x203D ) ) ||
×
NEW
120
       ( ( 0x2044 <= HairStyle ) && ( HairStyle <= 0x204A ) ) ||
×
NEW
121
       ( ( 0x2FBF <= HairStyle ) && ( HairStyle <= 0x2FC2 ) ) ||
×
NEW
122
       ( ( 0x2FCC <= HairStyle ) && ( HairStyle <= 0x2FD2 ) ) ||
×
NEW
123
       ( ( 0x4258 <= HairStyle ) && ( HairStyle <= 0x425F ) ) ||
×
NEW
124
       ( ( 0x4261 <= HairStyle ) && ( HairStyle <= 0x4262 ) ) ||
×
NEW
125
       ( ( 0x4273 <= HairStyle ) && ( HairStyle <= 0x4275 ) ) ||
×
NEW
126
       ( ( 0x42aa <= HairStyle ) && ( HairStyle <= 0x42ab ) ) || ( HairStyle == 0x42B1 ) )
×
NEW
127
    return true;
×
NEW
128
  return false;
×
129
}
130

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

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

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

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

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

273
  Mobile::Character* chr = new Mobile::Character( graphic );
×
274

275
  chr->acct.set( client->acct );
×
276
  chr->client = client;
×
277
  chr->set_privs( client->acct->default_privlist() );
×
278
  chr->cmdlevel( client->acct->default_cmdlevel(), false );
×
279

280
  client->UOExpansionFlagClient = ctBEu32( msg->clientflag );
×
281

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

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

302
  chr->serial = GetNextSerialNumber();
×
303
  chr->serial_ext = ctBEu32( chr->serial );
×
304
  chr->wornitems->serial = chr->serial;
×
305
  chr->wornitems->serial_ext = chr->serial_ext;
×
306

307
  chr->graphic = graphic;
×
308
  chr->race = race;
×
309
  chr->gender = gender;
×
310

311
  chr->trueobjtype = chr->objtype_;
×
312
  chr->color = cfBEu16( msg->SkinColor );
×
313
  chr->truecolor = chr->color;
×
314

315
  chr->setposition( gamestate.startlocations[msg->StartIndex]->select_coordinate() );
×
316
  chr->facing = Core::FACING_W;
×
317
  chr->position_changed();
×
318

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

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

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

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

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

394
  chr->calc_vital_stuff();
×
395
  chr->set_vitals_to_maximum();
×
396

397

398
  chr->created_at = read_gameclock();
×
399

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

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

429
  UContainer* backpack = (UContainer*)Items::Item::create( UOBJ_BACKPACK );
×
430
  backpack->layer = LAYER_BACKPACK;
×
431
  chr->equip( backpack );
×
432

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

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

458
    tmpitem = Items::Item::create( 0xF51 );
×
459
    tmpitem->newbie( settingsManager.ssopt.newbie_starting_equipment );
×
460
    tmpitem->layer = LAYER_HAND1;
×
461
    chr->equip( tmpitem );
×
462

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

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

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

496
  client->chr = chr;
×
497
  client->acct->set_character( msg->CharNumber, client->chr );
×
498

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

504
  // FIXME : Shouldn't this be triggered at the end of creation?
505
  run_logon_script( chr );
×
506

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

513
    std::unique_ptr<Bscript::ObjArray> arr( new Bscript::ObjArray );
×
514
    arr->addElement( new Bscript::BLong( msg->SkillNumber1 ) );
×
515
    arr->addElement( new Bscript::BLong( msg->SkillNumber2 ) );
×
516
    arr->addElement( new Bscript::BLong( msg->SkillNumber3 ) );
×
517

518
    ex->pushArg( new Bscript::BLong( msg->profession ) );
×
519
    ex->pushArg( arr.release() );
×
520
    ex->pushArg( make_mobileref( chr ) );
×
521

522
    ex->addModule( new Module::UOExecutorModule( *ex ) );
×
523
    ex->critical( true );
×
524

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

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

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

557
  objStorageManager.objecthash.Insert( chr );
12✔
558
  chr->logged_in( false );  // constructor sets it
12✔
559
}
12✔
560

561

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

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

605

606
  Mobile::Character* chr = new Mobile::Character( graphic );
×
607

608
  chr->acct.set( client->acct );
×
609
  chr->client = client;
×
610
  chr->set_privs( client->acct->default_privlist() );
×
611
  chr->cmdlevel( client->acct->default_cmdlevel(), false );
×
612

613
  client->UOExpansionFlagClient = msg->flags;
×
614

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

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

635
  chr->serial = GetNextSerialNumber();
×
636
  chr->serial_ext = ctBEu32( chr->serial );
×
637
  chr->wornitems->serial = chr->serial;
×
638
  chr->wornitems->serial_ext = chr->serial_ext;
×
639

640
  chr->graphic = graphic;
×
641
  chr->race = race;
×
642
  chr->gender = gender;
×
643

644
  chr->trueobjtype = chr->objtype_;
×
645
  chr->color = cfBEu16( msg->skin_color );
×
646
  chr->truecolor = chr->color;
×
647

648
  chr->setposition( gamestate.startlocations[0]->select_coordinate() );
×
649
  chr->position_changed();
×
650
  chr->facing = Core::FACING_W;
×
651

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

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

691

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

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

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

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

737
  chr->calc_vital_stuff();
×
738
  chr->set_vitals_to_maximum();
×
739

740

741
  chr->created_at = read_gameclock();
×
742

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

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

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

786
  UContainer* backpack = (UContainer*)Items::Item::create( UOBJ_BACKPACK );
×
787
  backpack->layer = LAYER_BACKPACK;
×
788
  chr->equip( backpack );
×
789

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

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

815
    tmpitem = Items::Item::create( 0xF51 );
×
816
    tmpitem->newbie( settingsManager.ssopt.newbie_starting_equipment );
×
817
    tmpitem->layer = LAYER_HAND1;
×
818
    chr->equip( tmpitem );
×
819

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

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

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

853
  client->chr = chr;
×
854
  client->acct->set_character( charslot, client->chr );
×
855

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

861
  // FIXME : Shouldn't this be triggered at the end of creation?
862
  run_logon_script( chr );
×
863

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

870
    std::unique_ptr<Bscript::ObjArray> arr( new Bscript::ObjArray );
×
871
    arr->addElement( new Bscript::BLong( msg->skillnumber1 ) );
×
872
    arr->addElement( new Bscript::BLong( msg->skillnumber2 ) );
×
873
    arr->addElement( new Bscript::BLong( msg->skillnumber3 ) );
×
874
    arr->addElement( new Bscript::BLong( msg->skillnumber4 ) );
×
875

876
    ex->pushArg( new Bscript::BLong( msg->profession ) );
×
877
    ex->pushArg( arr.release() );
×
878
    ex->pushArg( make_mobileref( chr ) );
×
879

880
    ex->addModule( new Module::UOExecutorModule( *ex ) );
×
881
    ex->critical( true );
×
882

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

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

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

973
  Mobile::Character* chr = new Mobile::Character( graphic );
×
974

975
  chr->acct.set( client->acct );
×
976
  chr->client = client;
×
977
  chr->set_privs( client->acct->default_privlist() );
×
978
  chr->cmdlevel( client->acct->default_cmdlevel(), false );
×
979

980
  client->UOExpansionFlagClient = ctBEu32( msg->clientflag );
×
981

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

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

1002
  chr->serial = GetNextSerialNumber();
×
1003
  chr->serial_ext = ctBEu32( chr->serial );
×
1004
  chr->wornitems->serial = chr->serial;
×
1005
  chr->wornitems->serial_ext = chr->serial_ext;
×
1006

1007
  chr->graphic = graphic;
×
1008
  chr->race = race;
×
1009
  chr->gender = gender;
×
1010

1011
  chr->trueobjtype = chr->objtype_;
×
1012
  chr->color = cfBEu16( msg->SkinColor );
×
1013
  chr->truecolor = chr->color;
×
1014

1015
  chr->setposition( gamestate.startlocations[msg->StartIndex]->select_coordinate() );
×
1016
  chr->position_changed();
×
1017
  chr->facing = Core::FACING_W;
×
1018

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

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

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

1063
  if ( broken_prof )
×
1064
  {
1065
    unsigned char temp_skillid = 0;
×
1066

1067
    if ( msg->profession == 2 )  // Mage profession
×
1068
      temp_skillid = SKILLID_EVALUATINGINTEL;
×
1069
    if ( msg->profession == 4 )  // Necromancy profession
×
1070
      temp_skillid = SKILLID_SPIRITSPEAK;
×
1071

1072

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

1095

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

1106
  bool noskills =
×
1107
      ( msg->SkillValue1 + msg->SkillValue2 + msg->SkillValue3 + msg->SkillValue4 == 0 ) &&
×
1108
      msg->profession;
×
1109

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

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

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

1142
  chr->calc_vital_stuff();
×
1143
  chr->set_vitals_to_maximum();
×
1144

1145

1146
  chr->created_at = read_gameclock();
×
1147

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

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

1177
  UContainer* backpack = (UContainer*)Items::Item::create( UOBJ_BACKPACK );
×
1178
  backpack->layer = LAYER_BACKPACK;
×
1179
  chr->equip( backpack );
×
1180

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

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

1206
    tmpitem = Items::Item::create( 0xF51 );
×
1207
    tmpitem->newbie( settingsManager.ssopt.newbie_starting_equipment );
×
1208
    tmpitem->layer = LAYER_HAND1;
×
1209
    chr->equip( tmpitem );
×
1210

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

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

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

1244
  client->chr = chr;
×
1245
  client->acct->set_character( msg->CharNumber, client->chr );
×
1246

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

1252
  // FIXME : Shouldn't this be triggered at the end of creation?
1253
  run_logon_script( chr );
×
1254

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

1261
    std::unique_ptr<Bscript::ObjArray> arr( new Bscript::ObjArray );
×
1262
    arr->addElement( new Bscript::BLong( msg->SkillNumber1 ) );
×
1263
    arr->addElement( new Bscript::BLong( msg->SkillNumber2 ) );
×
1264
    arr->addElement( new Bscript::BLong( msg->SkillNumber3 ) );
×
1265
    arr->addElement( new Bscript::BLong( msg->SkillNumber4 ) );
×
1266

1267
    ex->pushArg( new Bscript::BLong( msg->profession ) );
×
1268
    ex->pushArg( arr.release() );
×
1269
    ex->pushArg( make_mobileref( chr ) );
×
1270

1271
    ex->addModule( new Module::UOExecutorModule( *ex ) );
×
1272
    ex->critical( true );
×
1273

1274
    if ( ex->setProgram( prog.get() ) )
×
1275
    {
1276
      schedule_executor( ex.release() );
×
1277
    }
1278
    else
1279
    {
1280
      ERROR_PRINTLN( "script misc/oncreate: setProgram failed" );
×
1281
    }
1282
  }
×
1283
}
×
1284
}  // 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