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

polserver / polserver / 28964407401

08 Jul 2026 05:58PM UTC coverage: 61.276% (+0.3%) from 60.927%
28964407401

push

github

web-flow
Split bobject header  (#892)

* splitted bobject header

* fixed runecl

* crashfix

* missing header

* more missing (debug build)

* runecl debug include

* renamed contiter, moved formating

* removed more unused headers

1145 of 1517 new or added lines in 23 files covered. (75.48%)

217 existing lines in 4 files now uncovered.

44972 of 73392 relevant lines covered (61.28%)

557151.3 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 "bscript/barray.h"
17
#include "bscript/blong.h"
18
#include "clib/clib_endian.h"
19
#include "clib/logfacility.h"
20
#include "clib/rawtypes.h"
21
#include "clib/refptr.h"
22
#include "plib/clidata.h"
23
#include "plib/objtype.h"
24
#include "plib/systemstate.h"
25
#include "plib/uconst.h"
26

27
#include "accounts/account.h"
28
#include "containr.h"
29
#include "gameclck.h"
30
#include "globals/network.h"
31
#include "globals/object_storage.h"
32
#include "globals/uvars.h"
33
#include "item/item.h"
34
#include "layers.h"
35
#include "mkscrobj.h"
36
#include "mobile/attribute.h"
37
#include "mobile/charactr.h"
38
#include "mobile/wornitems.h"
39
#include "module/uomod.h"
40
#include "network/client.h"
41
#include "network/pktdef.h"
42
#include "network/pktin.h"
43
#include "realms/WorldChangeReasons.h"
44
#include "scrsched.h"
45
#include "scrstore.h"
46
#include "skillid.h"
47
#include "startloc.h"
48
#include "ufunc.h"
49
#include "uoclient.h"
50
#include "uoexec.h"
51
#include "uoskills.h"
52
#include "uworld.h"
53

54

55
namespace Pol::Core
56
{
57
void start_client_char( Network::Client* client );
58
void run_logon_script( Mobile::Character* chr );
59

60
short validhaircolor( u16 /*color*/ )
×
61
{
62
  return 1;
×
63
}
64

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

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

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

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

106
    0x4261  Female Horn Style 1  // Gargoyle Female (SA)
107
    0x4262  Female Horn Style 2
108
    0x4273  Female Horn Style 3
109
    0x4274  Female Horn Style 4
110
    0x4275  Female Horn Style 5
111
    0x42aa  Female Horn Style 6
112
    0x42ab  Female Horn Style 7
113
    0x42b1  Femaly Horn Style 8
114
    */
115
bool validhair( u16 HairStyle )
×
116
{
117
  if ( Plib::systemstate.config.max_tile_id < HairStyle )
×
118
  {
119
    return false;
×
120
  }
121

122
  if ( ( ( 0x203B <= HairStyle ) && ( HairStyle <= 0x203D ) ) ||
×
123
       ( ( 0x2044 <= HairStyle ) && ( HairStyle <= 0x204A ) ) ||
×
124
       ( ( 0x2FBF <= HairStyle ) && ( HairStyle <= 0x2FC2 ) ) ||
×
125
       ( ( 0x2FCC <= HairStyle ) && ( HairStyle <= 0x2FD2 ) ) ||
×
126
       ( ( 0x4258 <= HairStyle ) && ( HairStyle <= 0x425F ) ) ||
×
127
       ( ( 0x4261 <= HairStyle ) && ( HairStyle <= 0x4262 ) ) ||
×
128
       ( ( 0x4273 <= HairStyle ) && ( HairStyle <= 0x4275 ) ) ||
×
129
       ( ( 0x42aa <= HairStyle ) && ( HairStyle <= 0x42ab ) ) || ( HairStyle == 0x42B1 ) )
×
130
    return true;
×
131
  return false;
×
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
  return false;
×
156
}
157

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

400

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

564

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

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

608

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

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

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

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

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

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

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

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

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

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

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

694

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

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

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

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

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

743

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1075

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

1098

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

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

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

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

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

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

1148

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1277
    if ( ex->setProgram( prog.get() ) )
×
1278
    {
1279
      schedule_executor( ex.release() );
×
1280
    }
1281
    else
1282
    {
1283
      ERROR_PRINTLN( "script misc/oncreate: setProgram failed" );
×
1284
    }
1285
  }
×
1286
}
×
1287
}  // namespace Pol::Core
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc