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

polserver / polserver / 13163655424

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

push

github

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

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

simplified account initialisation, added errorcheck

* Expansion classes for server and account
fixed flags

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

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

* save test for weight_multiplier

* enable gothic,rustictiles when using HSA expansion

* removed ancient unused files

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

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

* updated core-changes and docs

* updated generated ssopt

* test for tooltips
changed method names
removed/clarified comments

* npc hitchance was never saved
added missing npc save tests

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

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

16 existing lines in 7 files now uncovered.

41768 of 71427 relevant lines covered (58.48%)

388521.09 hits per line

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

2.97
/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
53
{
54
namespace Core
55
{
56
void start_client_char( Network::Client* client );
57
void run_logon_script( Mobile::Character* chr );
58

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

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

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

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

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

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

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

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

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

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

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

279
  Mobile::Character* chr = new Mobile::Character( graphic );
×
280

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

286
  client->UOExpansionFlagClient = ctBEu32( msg->clientflag );
×
287

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

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

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

313
  chr->graphic = graphic;
×
314
  chr->race = race;
×
315
  chr->gender = gender;
×
316

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

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

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

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

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

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

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

400
  chr->calc_vital_stuff();
×
401
  chr->set_vitals_to_maximum();
×
402

403

404
  chr->created_at = read_gameclock();
×
405

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

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

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

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

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

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

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

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

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

503
  client->chr = chr;
×
504
  client->acct->set_character( msg->CharNumber, client->chr );
×
505

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

511
  // FIXME : Shouldn't this be triggered at the end of creation?
512
  run_logon_script( chr );
×
513

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

520
    std::unique_ptr<Bscript::ObjArray> arr( new Bscript::ObjArray );
×
521
    arr->addElement( new Bscript::BLong( msg->SkillNumber1 ) );
×
522
    arr->addElement( new Bscript::BLong( msg->SkillNumber2 ) );
×
523
    arr->addElement( new Bscript::BLong( msg->SkillNumber3 ) );
×
524

525
    ex->pushArg( new Bscript::BLong( msg->profession ) );
×
526
    ex->pushArg( arr.release() );
×
527
    ex->pushArg( make_mobileref( chr ) );
×
528

529
    ex->addModule( new Module::UOExecutorModule( *ex ) );
×
530
    ex->critical( true );
×
531

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

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

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

564
  objStorageManager.objecthash.Insert( chr );
12✔
565
  chr->logged_in( false );  // constructor sets it
12✔
566
}
12✔
567

568

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

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

612

613
  Mobile::Character* chr = new Mobile::Character( graphic );
×
614

615
  chr->acct.set( client->acct );
×
616
  chr->client = client;
×
617
  chr->set_privs( client->acct->default_privlist() );
×
618
  chr->cmdlevel( client->acct->default_cmdlevel(), false );
×
619

620
  client->UOExpansionFlagClient = msg->flags;
×
621

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

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

642
  chr->serial = GetNextSerialNumber();
×
643
  chr->serial_ext = ctBEu32( chr->serial );
×
644
  chr->wornitems->serial = chr->serial;
×
645
  chr->wornitems->serial_ext = chr->serial_ext;
×
646

647
  chr->graphic = graphic;
×
648
  chr->race = race;
×
649
  chr->gender = gender;
×
650

651
  chr->trueobjtype = chr->objtype_;
×
652
  chr->color = cfBEu16( msg->skin_color );
×
653
  chr->truecolor = chr->color;
×
654

655
  chr->setposition( gamestate.startlocations[0]->select_coordinate() );
×
656
  chr->position_changed();
×
657
  chr->facing = Core::FACING_W;
×
658

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

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

698

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

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

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

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

744
  chr->calc_vital_stuff();
×
745
  chr->set_vitals_to_maximum();
×
746

747

748
  chr->created_at = read_gameclock();
×
749

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

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

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

793
  UContainer* backpack = (UContainer*)Items::Item::create( UOBJ_BACKPACK );
×
794
  backpack->layer = LAYER_BACKPACK;
×
795
  chr->equip( backpack );
×
796

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

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

823
    tmpitem = Items::Item::create( 0xF51 );
×
824
    tmpitem->newbie( settingsManager.ssopt.newbie_starting_equipment );
×
825
    tmpitem->layer = LAYER_HAND1;
×
826
    chr->equip( tmpitem );
×
827

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

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

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

861
  client->chr = chr;
×
862
  client->acct->set_character( charslot, client->chr );
×
863

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

869
  // FIXME : Shouldn't this be triggered at the end of creation?
870
  run_logon_script( chr );
×
871

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

878
    std::unique_ptr<Bscript::ObjArray> arr( new Bscript::ObjArray );
×
879
    arr->addElement( new Bscript::BLong( msg->skillnumber1 ) );
×
880
    arr->addElement( new Bscript::BLong( msg->skillnumber2 ) );
×
881
    arr->addElement( new Bscript::BLong( msg->skillnumber3 ) );
×
882
    arr->addElement( new Bscript::BLong( msg->skillnumber4 ) );
×
883

884
    ex->pushArg( new Bscript::BLong( msg->profession ) );
×
885
    ex->pushArg( arr.release() );
×
886
    ex->pushArg( make_mobileref( chr ) );
×
887

888
    ex->addModule( new Module::UOExecutorModule( *ex ) );
×
889
    ex->critical( true );
×
890

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

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

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

981
  Mobile::Character* chr = new Mobile::Character( graphic );
×
982

983
  chr->acct.set( client->acct );
×
984
  chr->client = client;
×
985
  chr->set_privs( client->acct->default_privlist() );
×
986
  chr->cmdlevel( client->acct->default_cmdlevel(), false );
×
987

988
  client->UOExpansionFlagClient = ctBEu32( msg->clientflag );
×
989

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

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

1010
  chr->serial = GetNextSerialNumber();
×
1011
  chr->serial_ext = ctBEu32( chr->serial );
×
1012
  chr->wornitems->serial = chr->serial;
×
1013
  chr->wornitems->serial_ext = chr->serial_ext;
×
1014

1015
  chr->graphic = graphic;
×
1016
  chr->race = race;
×
1017
  chr->gender = gender;
×
1018

1019
  chr->trueobjtype = chr->objtype_;
×
1020
  chr->color = cfBEu16( msg->SkinColor );
×
1021
  chr->truecolor = chr->color;
×
1022

1023
  chr->setposition( gamestate.startlocations[msg->StartIndex]->select_coordinate() );
×
1024
  chr->position_changed();
×
1025
  chr->facing = Core::FACING_W;
×
1026

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

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

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

1071
  if ( broken_prof )
×
1072
  {
1073
    unsigned char temp_skillid = 0;
×
1074

1075
    if ( msg->profession == 2 )  // Mage profession
×
1076
      temp_skillid = SKILLID_EVALUATINGINTEL;
×
1077
    if ( msg->profession == 4 )  // Necromancy profession
×
1078
      temp_skillid = SKILLID_SPIRITSPEAK;
×
1079

1080

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

1103

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

1114
  bool noskills =
×
1115
      ( msg->SkillValue1 + msg->SkillValue2 + msg->SkillValue3 + msg->SkillValue4 == 0 ) &&
×
1116
      msg->profession;
×
1117

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

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

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

1150
  chr->calc_vital_stuff();
×
1151
  chr->set_vitals_to_maximum();
×
1152

1153

1154
  chr->created_at = read_gameclock();
×
1155

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

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

1185
  UContainer* backpack = (UContainer*)Items::Item::create( UOBJ_BACKPACK );
×
1186
  backpack->layer = LAYER_BACKPACK;
×
1187
  chr->equip( backpack );
×
1188

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

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

1215
    tmpitem = Items::Item::create( 0xF51 );
×
1216
    tmpitem->newbie( settingsManager.ssopt.newbie_starting_equipment );
×
1217
    tmpitem->layer = LAYER_HAND1;
×
1218
    chr->equip( tmpitem );
×
1219

1220
    unsigned short pantstype, shirttype;
1221
    if ( chr->gender == Plib::GENDER_FEMALE )
×
1222
    {
1223
      pantstype = 0x1516;
×
1224
      shirttype = 0x1517;
×
1225
    }
1226
    else
1227
    {
1228
      pantstype = 0x152e;
×
1229
      shirttype = 0x1517;
×
1230
    }
1231

1232
    tmpitem = Items::Item::create( pantstype );
×
1233
    tmpitem->newbie( settingsManager.ssopt.newbie_starting_equipment );
×
1234
    tmpitem->layer = Plib::tilelayer( pantstype );
×
1235
    tmpitem->color = cfBEu16( msg->pantscolor );  // 0x0284;
×
1236
    chr->equip( tmpitem );
×
1237

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

1253
  client->chr = chr;
×
1254
  client->acct->set_character( msg->CharNumber, client->chr );
×
1255

1256
  POLLOGLN( "Account {} created character {:#x}", client->acct->name(), chr->serial );
×
1257
  SetCharacterWorldPosition( chr, Realms::WorldChangeReason::PlayerEnter );
×
1258
  client->msgtype_filter = networkManager.game_filter.get();
×
1259
  start_client_char( client );
×
1260

1261
  // FIXME : Shouldn't this be triggered at the end of creation?
1262
  run_logon_script( chr );
×
1263

1264
  ref_ptr<Bscript::EScriptProgram> prog =
1265
      find_script( "misc/oncreate", true, Plib::systemstate.config.cache_interactive_scripts );
×
1266
  if ( prog.get() != nullptr )
×
1267
  {
1268
    std::unique_ptr<UOExecutor> ex( create_script_executor() );
×
1269

1270
    std::unique_ptr<Bscript::ObjArray> arr( new Bscript::ObjArray );
×
1271
    arr->addElement( new Bscript::BLong( msg->SkillNumber1 ) );
×
1272
    arr->addElement( new Bscript::BLong( msg->SkillNumber2 ) );
×
1273
    arr->addElement( new Bscript::BLong( msg->SkillNumber3 ) );
×
1274
    arr->addElement( new Bscript::BLong( msg->SkillNumber4 ) );
×
1275

1276
    ex->pushArg( new Bscript::BLong( msg->profession ) );
×
1277
    ex->pushArg( arr.release() );
×
1278
    ex->pushArg( make_mobileref( chr ) );
×
1279

1280
    ex->addModule( new Module::UOExecutorModule( *ex ) );
×
1281
    ex->critical( true );
×
1282

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

© 2026 Coveralls, Inc