Atrinik Server 2.5
server/login.c
Go to the documentation of this file.
00001 /************************************************************************
00002 *            Atrinik, a Multiplayer Online Role Playing Game            *
00003 *                                                                       *
00004 *    Copyright (C) 2009-2011 Alex Tokar and Atrinik Development Team    *
00005 *                                                                       *
00006 * Fork from Daimonin (Massive Multiplayer Online Role Playing Game)     *
00007 * and Crossfire (Multiplayer game for X-windows).                       *
00008 *                                                                       *
00009 * This program is free software; you can redistribute it and/or modify  *
00010 * it under the terms of the GNU General Public License as published by  *
00011 * the Free Software Foundation; either version 2 of the License, or     *
00012 * (at your option) any later version.                                   *
00013 *                                                                       *
00014 * This program is distributed in the hope that it will be useful,       *
00015 * but WITHOUT ANY WARRANTY; without even the implied warranty of        *
00016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
00017 * GNU General Public License for more details.                          *
00018 *                                                                       *
00019 * You should have received a copy of the GNU General Public License     *
00020 * along with this program; if not, write to the Free Software           *
00021 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.             *
00022 *                                                                       *
00023 * The author can be reached at admin@atrinik.org                        *
00024 ************************************************************************/
00025 
00030 #include <global.h>
00031 #include <loader.h>
00032 
00038 void emergency_save(int flag)
00039 {
00040 #ifndef NO_EMERGENCY_SAVE
00041     LOG(llevSystem, "Emergency save:  ");
00042 
00043     for (pl = first_player; pl; pl = pl->next)
00044     {
00045         if (!pl->ob)
00046         {
00047             LOG(llevSystem, "No name, ignoring this.\n");
00048             continue;
00049         }
00050 
00051         LOG(llevSystem, "%s ", pl->ob->name);
00052         new_draw_info(0, COLOR_WHITE, pl->ob, "Emergency save...");
00053 
00054         /* If we are not exiting the game (ie, this is sort of a backup
00055          * save), then don't change the location back to the village.
00056          * Note that there are other options to have backup saves be done
00057          * at the starting village */
00058         if (!flag)
00059         {
00060             strcpy(pl->maplevel, first_map_path);
00061 
00062             if (pl->ob->map != NULL)
00063             {
00064                 pl->ob->map = NULL;
00065             }
00066 
00067             pl->ob->x = -1;
00068             pl->ob->y = -1;
00069         }
00070 
00071         container_unlink(pl, NULL);
00072 
00073         if (!save_player(pl->ob, flag))
00074         {
00075             LOG(llevSystem, "(failed) ");
00076             new_draw_info(0, COLOR_WHITE, pl->ob, "Emergency save failed, checking score...");
00077         }
00078 
00079         hiscore_check(pl->ob, 1);
00080     }
00081 
00082     LOG(llevSystem, "\n");
00083 #else
00084     (void) flag;
00085     LOG(llevSystem, "Emergency saves disabled, no save attempted\n");
00086 #endif
00087 }
00088 
00096 int check_name(player *pl, char *name)
00097 {
00098     size_t name_len;
00099 
00100     if (name[0] == '\0')
00101     {
00102         send_socket_message(COLOR_RED, &pl->socket, "You must provide a name to log in.");
00103         return 0;
00104     }
00105 
00106     name_len = strlen(name);
00107 
00108     if (name_len < PLAYER_NAME_MIN || name_len > PLAYER_NAME_MAX)
00109     {
00110         send_socket_message(COLOR_RED, &pl->socket, "That name has an invalid length.");
00111         return 0;
00112     }
00113 
00114     if (!playername_ok(name))
00115     {
00116         send_socket_message(COLOR_RED, &pl->socket, "That name contains illegal characters.");
00117         return 0;
00118     }
00119 
00120     return 1;
00121 }
00122 
00129 int save_player(object *op, int flag)
00130 {
00131     FILE *fp;
00132     char filename[MAX_BUF], backupfile[MAX_BUF];
00133     player *pl = CONTR(op);
00134     int i, wiz = QUERY_FLAG(op, FLAG_WIZ);
00135 
00136     flag &= 1;
00137 
00138     /* Sanity check - some stuff changes this when player is exiting */
00139     if (op->type != PLAYER)
00140     {
00141         return 0;
00142     }
00143 
00144     /* Prevent accidental saves if connection is reset after player has
00145      * mostly exited. */
00146     if (pl->state != ST_PLAYING)
00147     {
00148         return 0;
00149     }
00150 
00151     /* Is this a map players can't save on? */
00152     if (op->map && MAP_PLAYER_NO_SAVE(op->map))
00153     {
00154         return 0;
00155     }
00156 
00157     snprintf(filename, sizeof(filename), "%s/%s/%s/%s.pl", settings.localdir, settings.playerdir, op->name, op->name);
00158     make_path_to_file(filename);
00159     fp = fopen(filename, "w");
00160     snprintf(backupfile, sizeof(backupfile), "%s.tmp", filename);
00161     rename(filename, backupfile);
00162 
00163     if (!fp)
00164     {
00165         new_draw_info(0, COLOR_WHITE, op, "Can't open file for saving.");
00166         LOG(llevDebug, "Can't open file for saving (%s).\n", filename);
00167         rename(backupfile, filename);
00168         return 0;
00169     }
00170 
00171     fprintf(fp, "password %s\n", pl->password);
00172     fprintf(fp, "dm_stealth %d\n", pl->dm_stealth);
00173     fprintf(fp, "ms_privacy %d\n", pl->ms_privacy);
00174     fprintf(fp, "no_shout %d\n", pl->no_shout);
00175     fprintf(fp, "gen_hp %d\n", pl->gen_hp);
00176     fprintf(fp, "gen_sp %d\n", pl->gen_sp);
00177     fprintf(fp, "gen_grace %d\n", pl->gen_grace);
00178     fprintf(fp, "spell %d\n", pl->chosen_spell);
00179     fprintf(fp, "shoottype %d\n", pl->shoottype);
00180     fprintf(fp, "digestion %d\n", pl->digestion);
00181 
00182     if (op->map)
00183     {
00184         fprintf(fp, "map %s\n", op->map->path);
00185     }
00186     else
00187     {
00188         fprintf(fp, "map %s\n", EMERGENCY_MAPPATH);
00189     }
00190 
00191     fprintf(fp, "savebed_map %s\n", pl->savebed_map);
00192     fprintf(fp, "bed_x %d\nbed_y %d\n", pl->bed_x, pl->bed_y);
00193     fprintf(fp, "Str %d\n", pl->orig_stats.Str);
00194     fprintf(fp, "Dex %d\n", pl->orig_stats.Dex);
00195     fprintf(fp, "Con %d\n", pl->orig_stats.Con);
00196     fprintf(fp, "Int %d\n", pl->orig_stats.Int);
00197     fprintf(fp, "Pow %d\n", pl->orig_stats.Pow);
00198     fprintf(fp, "Wis %d\n", pl->orig_stats.Wis);
00199     fprintf(fp, "Cha %d\n", pl->orig_stats.Cha);
00200 
00201     /* Save hp table */
00202     fprintf(fp, "lev_hp %d\n", op->level);
00203 
00204     for (i = 1; i <= op->level; i++)
00205     {
00206         fprintf(fp, "%d\n", pl->levhp[i]);
00207     }
00208 
00209     /* Save sp table */
00210     fprintf(fp, "lev_sp %d\n", pl->exp_ptr[EXP_MAGICAL]->level);
00211 
00212     for (i = 1; i <= pl->exp_ptr[EXP_MAGICAL]->level; i++)
00213     {
00214         fprintf(fp, "%d\n", pl->levsp[i]);
00215     }
00216 
00217     /* Save grace table */
00218     fprintf(fp, "lev_grace %d\n", pl->exp_ptr[EXP_WISDOM]->level);
00219 
00220     for (i = 1; i <= pl->exp_ptr[EXP_WISDOM]->level; i++)
00221     {
00222         fprintf(fp, "%d\n", pl->levgrace[i]);
00223     }
00224 
00225     for (i = 0; i < pl->nrofknownspells; i++)
00226     {
00227         fprintf(fp, "known_spell %s\n", spells[pl->known_spells[i]].name);
00228     }
00229 
00230     for (i = 0; i < pl->num_cmd_permissions; i++)
00231     {
00232         if (pl->cmd_permissions[i])
00233         {
00234             fprintf(fp, "cmd_permission %s\n", pl->cmd_permissions[i]);
00235         }
00236     }
00237 
00238     for (i = 0; i < MAX_QUICKSLOT; i++)
00239     {
00240         if (pl->spell_quickslots[i] != SP_NO_SPELL)
00241         {
00242             fprintf(fp, "spell_quickslot %d %d\n", i, pl->spell_quickslots[i]);
00243         }
00244     }
00245 
00246     for (i = 0; i < pl->num_faction_ids; i++)
00247     {
00248         if (pl->faction_ids[i])
00249         {
00250             fprintf(fp, "faction %s %"FMT64"\n", pl->faction_ids[i], pl->faction_reputation[i]);
00251         }
00252     }
00253 
00254     fprintf(fp, "fame %"FMT64"\n", pl->fame);
00255     fprintf(fp, "endplst\n");
00256 
00257     SET_FLAG(op, FLAG_NO_FIX_PLAYER);
00258     CLEAR_FLAG(op, FLAG_WIZ);
00259 
00260     /* Don't check and don't remove */
00261     save_object(fp, op, 3);
00262 
00263     /* Make sure the write succeeded */
00264     if (fclose(fp) == EOF)
00265     {
00266         new_draw_info(0, COLOR_WHITE, op, "Can't save character.");
00267         CLEAR_FLAG(op, FLAG_NO_FIX_PLAYER);
00268         rename(backupfile, filename);
00269         return 0;
00270     }
00271 
00272     if (wiz)
00273     {
00274         SET_FLAG(op, FLAG_WIZ);
00275     }
00276 
00277     rename(backupfile, filename);
00278     chmod(filename, SAVE_MODE);
00279     CLEAR_FLAG(op, FLAG_NO_FIX_PLAYER);
00280     return 1;
00281 }
00282 
00288 static int spell_sort(const void *a1, const void *a2)
00289 {
00290     return strcmp(spells[(int) *(sint16 *) a1].name, spells[(int) *(sint16 *) a2].name);
00291 }
00292 
00300 static void reorder_inventory(object *op)
00301 {
00302     object *tmp, *tmp2;
00303 
00304     tmp2 = op->inv->below;
00305     op->inv->above = NULL;
00306     op->inv->below = NULL;
00307 
00308     for (; tmp2; )
00309     {
00310         tmp = tmp2;
00311         /* Save the following element */
00312         tmp2 = tmp->below;
00313         tmp->above = NULL;
00314         /* Resort it like in insert_ob_in_ob() */
00315         tmp->below = op->inv;
00316         tmp->below->above = tmp;
00317         op->inv = tmp;
00318 
00319         if (tmp->inv)
00320         {
00321             reorder_inventory(tmp);
00322         }
00323     }
00324 }
00325 
00331 static void wrong_password(player *pl)
00332 {
00333     pl->socket.password_fails++;
00334 
00335     LOG(llevSystem, "%s@%s: Failed to provide correct password.\n", query_name(pl->ob, NULL), pl->socket.host);
00336 
00337     if (pl->socket.password_fails >= MAX_PASSWORD_FAILURES)
00338     {
00339         LOG(llevSystem, "%s@%s: Failed to provide a correct password too many times!\n", query_name(pl->ob, NULL), pl->socket.host);
00340         send_socket_message(COLOR_RED, &pl->socket, "You have failed to provide a correct password too many times.");
00341         pl->socket.status = Ns_Zombie;
00342     }
00343     else
00344     {
00345         FREE_AND_COPY_HASH(pl->ob->name, "noname");
00346         get_name(pl->ob);
00347     }
00348 }
00349 
00353 void check_login(object *op)
00354 {
00355     FILE *fp;
00356     void *mybuffer;
00357     char filename[MAX_BUF], buf[MAX_BUF], bufall[MAX_BUF];
00358     int i, value, comp, correct = 0, type;
00359     player *pl = CONTR(op), *pltmp;
00360     time_t elapsed_save_time = 0;
00361     struct stat statbuf;
00362     object *tmp;
00363 
00364     strcpy(pl->maplevel, first_map_path);
00365 
00366     /* Check if this matches a connected player, and if so disconnect old
00367      * and connect new. */
00368     for (pltmp = first_player; pltmp != NULL; pltmp = pltmp->next)
00369     {
00370         if (pltmp != pl && pltmp->ob->name != NULL && !strcmp(pltmp->ob->name, op->name))
00371         {
00372             if (check_password(pl->write_buf + 1, pltmp->password))
00373             {
00374                 pltmp->socket.status = Ns_Dead;
00375                 /* Need to call this, otherwise the player won't get saved correctly. */
00376                 remove_ns_dead_player(pltmp);
00377                 break;
00378             }
00379             else
00380             {
00381                 wrong_password(pl);
00382                 return;
00383             }
00384         }
00385     }
00386 
00387     if (pl->state == ST_PLAYING)
00388     {
00389         LOG(llevSystem, ">%s< from IP %s - double login!\n", op->name, pl->socket.host);
00390         send_socket_message(COLOR_RED, &pl->socket, "Connection refused.\nYou manipulated the login procedure.");
00391         pl->socket.status = Ns_Zombie;
00392         return;
00393     }
00394 
00395     if (checkbanned(op->name, pl->socket.host))
00396     {
00397         LOG(llevSystem, "Ban: Banned player tried to login. [%s@%s]\n", op->name, pl->socket.host);
00398         send_socket_message(COLOR_RED, &pl->socket, "Connection refused.\nYou are banned!");
00399         pl->socket.status = Ns_Zombie;
00400         return;
00401     }
00402 
00403     LOG(llevInfo, "Login %s from IP %s\n", op->name, pl->socket.host);
00404 
00405     snprintf(filename, sizeof(filename), "%s/%s/%s/%s.pl", settings.localdir, settings.playerdir, op->name, op->name);
00406 
00407     /* If no file, must be a new player, so lets get confirmation of
00408      * the password.  Return control to the higher level dispatch,
00409      * since the rest of this just deals with loading of the file. */
00410     if ((fp = open_and_uncompress(filename, 1, &comp)) == NULL)
00411     {
00412         confirm_password(op);
00413         return;
00414     }
00415 
00416     if (fstat(fileno(fp), &statbuf))
00417     {
00418         LOG(llevBug, "Unable to stat %s?\n", filename);
00419         elapsed_save_time = 0;
00420     }
00421     else
00422     {
00423         elapsed_save_time = time(NULL) - statbuf.st_mtime;
00424 
00425         if (elapsed_save_time < 0)
00426         {
00427             LOG(llevBug, "Player file %s was saved in the future? (%"FMT64U" time)\n", filename, (uint64) elapsed_save_time);
00428             elapsed_save_time = 0;
00429         }
00430     }
00431 
00432     if (fgets(bufall, sizeof(bufall), fp))
00433     {
00434         if (sscanf(bufall, "password %s\n", buf))
00435         {
00436             correct = check_password(pl->write_buf + 1, buf);
00437         }
00438     }
00439 
00440     if (!correct)
00441     {
00442         wrong_password(pl);
00443         return;
00444     }
00445 
00446 #ifdef SAVE_INTERVAL
00447     pl->last_save_time = time(NULL);
00448 #endif
00449 
00450     pl->party = NULL;
00451 
00452     pl->orig_stats.Str = 0;
00453     pl->orig_stats.Dex = 0;
00454     pl->orig_stats.Con = 0;
00455     pl->orig_stats.Int = 0;
00456     pl->orig_stats.Pow = 0;
00457     pl->orig_stats.Wis = 0;
00458     pl->orig_stats.Cha = 0;
00459     strcpy(pl->savebed_map, first_map_path);
00460     pl->bed_x = 0;
00461     pl->bed_y = 0;
00462 
00463     /* Loop through the file, loading the rest of the values. */
00464     while (fgets(bufall, sizeof(bufall), fp))
00465     {
00466         sscanf(bufall, "%s %d\n", buf, &value);
00467 
00468         if (!strcmp(buf, "endplst"))
00469         {
00470             break;
00471         }
00472         else if (!strcmp(buf, "dm_stealth"))
00473         {
00474             pl->dm_stealth = value;
00475         }
00476         else if (!strcmp(buf, "ms_privacy"))
00477         {
00478             pl->ms_privacy = value;
00479         }
00480         else if (!strcmp(buf, "no_shout"))
00481         {
00482             pl->no_shout = value;
00483         }
00484         else if (!strcmp(buf, "gen_hp"))
00485         {
00486             pl->gen_hp = value;
00487         }
00488         else if (!strcmp(buf, "shoottype"))
00489         {
00490             pl->shoottype = (rangetype) value;
00491         }
00492         else if (!strcmp(buf, "gen_sp"))
00493         {
00494             pl->gen_sp = value;
00495         }
00496         else if (!strcmp(buf, "gen_grace"))
00497         {
00498             pl->gen_grace = value;
00499         }
00500         else if (!strcmp(buf, "spell"))
00501         {
00502             pl->chosen_spell = value;
00503         }
00504         else if (!strcmp(buf, "digestion"))
00505         {
00506             pl->digestion = value;
00507         }
00508         else if (!strcmp(buf, "map"))
00509         {
00510             sscanf(bufall, "map %s", pl->maplevel);
00511         }
00512         else if (!strcmp(buf, "savebed_map"))
00513         {
00514             sscanf(bufall, "savebed_map %s", pl->savebed_map);
00515         }
00516         else if (!strcmp(buf, "bed_x"))
00517         {
00518             pl->bed_x = value;
00519         }
00520         else if (!strcmp(buf, "bed_y"))
00521         {
00522             pl->bed_y = value;
00523         }
00524         else if (!strcmp(buf, "Str"))
00525         {
00526             pl->orig_stats.Str = value;
00527         }
00528         else if (!strcmp(buf, "Dex"))
00529         {
00530             pl->orig_stats.Dex = value;
00531         }
00532         else if (!strcmp(buf, "Con"))
00533         {
00534             pl->orig_stats.Con = value;
00535         }
00536         else if (!strcmp(buf, "Int"))
00537         {
00538             pl->orig_stats.Int = value;
00539         }
00540         else if (!strcmp(buf, "Pow"))
00541         {
00542             pl->orig_stats.Pow = value;
00543         }
00544         else if (!strcmp(buf, "Wis"))
00545         {
00546             pl->orig_stats.Wis = value;
00547         }
00548         else if (!strcmp(buf, "Cha"))
00549         {
00550             pl->orig_stats.Cha = value;
00551         }
00552         else if (!strcmp(buf, "lev_hp"))
00553         {
00554             int j;
00555 
00556             for (i = 1; i <= value; i++)
00557             {
00558                 if (fscanf(fp, "%d\n", &j))
00559                 {
00560                     pl->levhp[i] = j;
00561                 }
00562             }
00563         }
00564         else if (!strcmp(buf, "lev_sp"))
00565         {
00566             int j;
00567 
00568             for (i = 1; i <= value; i++)
00569             {
00570                 if (fscanf(fp, "%d\n", &j))
00571                 {
00572                     pl->levsp[i] = j;
00573                 }
00574             }
00575         }
00576         else if (!strcmp(buf, "lev_grace"))
00577         {
00578             int j;
00579 
00580             for (i = 1; i <= value; i++)
00581             {
00582                 if (fscanf(fp, "%d\n", &j))
00583                 {
00584                     pl->levgrace[i] = j;
00585                 }
00586             }
00587         }
00588         else if (!strcmp(buf, "known_spell"))
00589         {
00590             char *cp = strchr(bufall, '\n');
00591 
00592             *cp = '\0';
00593             cp = strchr(bufall, ' ');
00594             cp++;
00595 
00596             for (i = 0; i < NROFREALSPELLS; i++)
00597             {
00598                 if (!strcmp(spells[i].name, cp))
00599                 {
00600                     pl->known_spells[pl->nrofknownspells++] = i;
00601                     break;
00602                 }
00603             }
00604 
00605             if (i == NROFREALSPELLS)
00606             {
00607                 LOG(llevDebug, "check_login(): Bogus spell (%s) in %s\n", cp, filename);
00608             }
00609         }
00610         else if (!strcmp(buf, "cmd_permission"))
00611         {
00612             char *cp = strchr(bufall, '\n');
00613 
00614             *cp = '\0';
00615             cp = strchr(bufall, ' ');
00616             cp++;
00617 
00618             pl->num_cmd_permissions++;
00619             pl->cmd_permissions = realloc(pl->cmd_permissions, sizeof(char *) * pl->num_cmd_permissions);
00620             pl->cmd_permissions[pl->num_cmd_permissions - 1] = strdup_local(cp);
00621         }
00622         else if (!strcmp(buf, "spell_quickslot"))
00623         {
00624             char *cp = strrchr(bufall, ' ');
00625             sint16 spell_id = atoi(cp + 1);
00626 
00627             if (spell_id < 0 || spell_id >= NROFREALSPELLS)
00628             {
00629                 LOG(llevDebug, "check_login(): Bogus spell ID (#%d) in %s\n", spell_id, filename);
00630             }
00631 
00632             pl->spell_quickslots[value] = spell_id;
00633         }
00634         else if (!strcmp(buf, "faction"))
00635         {
00636             char faction_id[MAX_BUF];
00637             sint64 rep;
00638 
00639             if (sscanf(bufall, "faction %s %"FMT64, faction_id, &rep) == 2)
00640             {
00641                 pl->faction_ids = realloc(pl->faction_ids, sizeof(*pl->faction_ids) * (pl->num_faction_ids + 1));
00642                 pl->faction_reputation = realloc(pl->faction_reputation, sizeof(*pl->faction_reputation) * (pl->num_faction_ids + 1));
00643                 pl->faction_ids[pl->num_faction_ids] = add_string(faction_id);
00644                 pl->faction_reputation[pl->num_faction_ids] = rep;
00645                 pl->num_faction_ids++;
00646             }
00647         }
00648         else if (!strcmp(buf, "fame"))
00649         {
00650             pl->fame = value;
00651         }
00652     }
00653 
00654     /* Take the player object out from the void */
00655     if (!QUERY_FLAG(op, FLAG_REMOVED))
00656     {
00657         remove_ob(op);
00658     }
00659 
00660     /* We transfer it to a new object */
00661     op->custom_attrset = NULL;
00662 
00663     LOG(llevDebug, "load obj for player: %s\n", op->name);
00664 
00665     /* Create a new object for the real player data */
00666     op = get_object();
00667 
00668     /* This loads the standard objects values. */
00669     mybuffer = create_loader_buffer(fp);
00670     load_object(fp, op, mybuffer, LO_REPEAT, 0);
00671     delete_loader_buffer(mybuffer);
00672     close_and_delete(fp, comp);
00673 
00674     /* The inventory of players is reverse loaded, so let's exchange the
00675      * order here. */
00676     if (op->inv)
00677     {
00678         reorder_inventory(op);
00679     }
00680 
00681     op->custom_attrset = pl;
00682     pl->ob = op;
00683     CLEAR_FLAG(op, FLAG_NO_FIX_PLAYER);
00684 
00685     op->type = PLAYER;
00686 
00687     /* This is a funny thing: what happens when the autosave function saves a player
00688      * with negative hp? Well, the sever tries to create a gravestone and heals the
00689      * player... and then server tries to insert gravestone and anim on a map - but
00690      * player is still in login! So, we are nice and set hp to 1 if here negative. */
00691     if (op->stats.hp < 0)
00692     {
00693         op->stats.hp = 1;
00694     }
00695 
00696     pl->state = ST_PLAYING;
00697 #ifdef AUTOSAVE
00698     pl->last_save_tick = pticks;
00699 #endif
00700     op->carrying = sum_weight(op);
00701 
00702     init_player_exp(op);
00703     link_player_skills(op);
00704 
00705     if (!legal_range(op, pl->shoottype))
00706     {
00707         pl->shoottype = range_none;
00708     }
00709 
00710     fix_player(op);
00711 
00712     /* Display Message of the Day */
00713     display_motd(op);
00714 
00715     if (!pl->dm_stealth)
00716     {
00717         new_draw_info_format(NDI_ALL, COLOR_DK_ORANGE, NULL, "%s has entered the game.", query_name(pl->ob, NULL));
00718     }
00719 
00720     /* Trigger the global LOGIN event */
00721     trigger_global_event(GEVENT_LOGIN, pl, pl->socket.host);
00722 
00723     esrv_new_player(pl, op->weight + op->carrying);
00724     esrv_send_inventory(op, op);
00725 
00726     /* This seems to compile without warnings now.  Don't know if it works
00727      * on SGI's or not, however. */
00728     qsort((void *) pl->known_spells, pl->nrofknownspells, sizeof(pl->known_spells[0]), (void *) (int (*)()) spell_sort);
00729 
00730     if (!QUERY_FLAG(op, FLAG_FRIENDLY))
00731     {
00732         LOG(llevBug, "Player %s was loaded without friendly flag!", query_name(op, NULL));
00733         SET_FLAG(op, FLAG_FRIENDLY);
00734     }
00735 
00736     enter_exit(op, NULL);
00737 
00738     pl->socket.update_tile = 0;
00739     pl->socket.look_position = 0;
00740     pl->socket.ext_title_flag = 1;
00741 
00742     /* No direction; default to southeast. */
00743     if (!op->direction)
00744     {
00745         op->direction = SOUTHEAST;
00746     }
00747 
00748     op->anim_last_facing = op->anim_last_facing_last = op->facing = op->direction;
00749     /* We assume that players always have a valid animation. */
00750     SET_ANIMATION(op, (NUM_ANIMATIONS(op) / NUM_FACINGS(op)) * op->direction);
00751     esrv_new_player(pl, op->weight + op->carrying);
00752     send_spelllist_cmd(op, NULL, SPLIST_MODE_ADD);
00753     send_skilllist_cmd(op, NULL, SPLIST_MODE_ADD);
00754     send_quickslots(pl);
00755 
00756     /* Go through the player's inventory and inform the client about
00757      * readied objects. */
00758     for (tmp = op->inv; tmp; tmp = tmp->below)
00759     {
00760         if (QUERY_FLAG(tmp, FLAG_IS_READY))
00761         {
00762             type = cmd_ready_determine(tmp);
00763 
00764             if (type != -1)
00765             {
00766                 pl->ready_object[type] = tmp;
00767                 pl->ready_object_tag[type] = tmp->count;
00768                 cmd_ready_send(pl, tmp->count, type);
00769             }
00770         }
00771     }
00772 
00773     if (op->map && op->map->events)
00774     {
00775         trigger_map_event(MEVENT_LOGIN, op->map, op, NULL, NULL, NULL, 0);
00776     }
00777 }