ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/ext/login.ext
Revision: 1.119
Committed: Thu Nov 15 05:54:02 2012 UTC (11 years, 6 months ago) by root
Branch: MAIN
Changes since 1.118: +111 -112 lines
Log Message:
tentative refactoring

File Contents

# User Rev Content
1 root 1.102 #! perl # mandatory depends=highscore
2 root 1.1
3     # login handling
4    
5     use Fcntl;
6     use Coro::AIO;
7 root 1.53
8 root 1.117 CONF MAX_DISCONNECT_TIME = 3600;
9 root 1.64
10 root 1.1 sub query {
11     my ($ns, $flags, $text) = @_;
12    
13 root 1.94 $ns->query ($flags, $text, Coro::rouse_cb);
14     Coro::rouse_wait
15 root 1.1 }
16    
17     sub can_cleanup {
18 root 1.19 my ($pl, $mtime) = @_;
19 root 1.1
20     my $age = time - $mtime;
21 root 1.19 my $level = $pl->ob->level;
22 root 1.1
23     ($level <= 3 && $age > 7 * 86400) # 7 days for level 0..3
24     || ($level <= 9 && $age > 90 * 86400) # 3 months for level 4..9
25     || ($level <= 20 && $age > 180 * 86400) # 6 months for level 10..20
26     || $age > 700 * 86400 # 2 years for everybody else
27     }
28    
29     sub check_playing {
30     my ($ns, $user) = @_;
31    
32 root 1.11 return unless cf::player::find_active $user;
33 root 1.1
34     $ns->send_drawinfo (
35     "That player is already logged in on this server. "
36     . "If you want to create a new player, choose another name. "
37 root 1.59 . "If you have already a registered, make sure nobody "
38     . "else is using your account at this time. If you lost your connection "
39 root 1.1 . "then the server will likely timeout within a minute. If you still "
40     . "cannot log-in after a minute, you are still logged in. Make sure "
41     . "you do not have another client running. If you use windows, reboot, "
42     . "this will fix anything.",
43     cf::NDI_RED
44     );
45    
46     1
47     }
48    
49 root 1.79 sub safe_spot($) {
50     my ($pl) = @_;
51    
52     my $ob = $pl->ob;
53 root 1.76
54 root 1.79 my $m = $ob->map
55     or return;
56     my $x = $ob->x;
57     my $y = $ob->y;
58    
59 root 1.113 # never happens normally, but helps when shell users make mistakes
60 root 1.116 $m->linkable
61 root 1.113 or return 1;
62    
63 root 1.79 # return 0;#d#
64     # warn join ":", $m->at ($x, $y);#d#
65     # warn "FOO$m { ".scalar ($m->at ($x, $y))." }\n";
66     # return 0;
67 root 1.78
68 root 1.76 scalar grep $_->type == cf::SAVEBED, $m->at ($x, $y)
69     }
70    
71 root 1.48 sub enter_map {
72 root 1.11 my ($pl) = @_;
73    
74 root 1.62 my $ob = $pl->ob;
75    
76 root 1.48 my ($map, $x, $y)
77 root 1.62 = $ob->{_link_pos}
78     ? @{delete $ob->{_link_pos}}
79     : ($pl->maplevel, $ob->x, $ob->y);
80 root 1.48
81 root 1.62 $ob->enter_link;
82 root 1.48
83 root 1.77 my $m = cf::map::find $map;
84     my $time = delete $pl->{unclean_save};
85    
86 root 1.79 if ($time && $m) {
87 root 1.77 if ($time < $m->{instantiate_time}) {
88     # the map was reset in the meantime
89     my $age = $cf::RUNTIME - $time;
90    
91 root 1.107 cf::info $ob->name, " map reset after logout, logout age $age (>= $MAX_DISCONNECT_TIME)\n";#d#
92 root 1.77
93     if ($age >= $MAX_DISCONNECT_TIME) {
94     $ob->message (
95     "You didn't use a bed to reality to leave this realm, leaving your body in great danger. "
96     . "Unfortunately, nobody was near to help you when the monsters arrived to eat you. "
97     . "Maybe you can find comfort in the thought that your body was quite satisfying in taste... "
98     . "H<You disconnected too long without having used a savebed.>",
99     cf::NDI_RED
100     );
101     # kill them.
102     # reminds me of the famous badness 10000 syndrome...
103     $ob->stats->hp (-10000); #] if they survive this they deserved to live
104 root 1.86 my $killer = cf::arch::get "killer_login"; $pl->killer ($killer); $killer->destroy;
105 root 1.48 } else {
106 root 1.81 ($map, $x, $y) = $pl->savebed;
107    
108 root 1.64 $ob->message (
109 root 1.77 "You didn't use a bed to reality to leave this realm, leaving your body in great danger. "
110     . "Fortunately, some friendly dwellers found you, checked your passport, and brought you to safety. "
111     . "Better use a savebed next time, much worse things could have happened... "
112     . "H<You disconnected without having used a savebed. When you do that for too long, you might die.>",
113 root 1.48 cf::NDI_RED
114     );
115     }
116 root 1.77 } else {
117     $ob->message (
118     "You didn't use a bed to reality to leave this realm. This is very dangerous, "
119     . "as lots of things could happen when you leave by other means, such as cave-ins, "
120     . "or monsters suddenly snapping your body. Better use a savebed next time. "
121     . "H<Always apply a bed of reality to disconnect from the server.>",
122     cf::NDI_RED
123     );
124 root 1.48 }
125 root 1.11 }
126 root 1.48
127 root 1.100 $ob->goto ($map, $x, $y);
128 root 1.11 }
129    
130 root 1.110 sub encode_password($) {
131     "!" . unpack "H*", $_[0]
132     }
133    
134     sub compare_password($$) {
135     my ($pass, $token) = @_;
136    
137 root 1.118 if ($token =~ /!!(.*)/) {
138     return +(substr $pass, 0, 8) eq pack "H*", $1;
139     } elsif ($token =~ /!(.*)/) {
140 root 1.110 return $pass eq pack "H*", $1;
141     } else {
142     return $token eq crypt $pass, $token;
143     }
144 elmex 1.106 }
145    
146 root 1.111 # delete a player directory
147 root 1.1 sub nuke_playerdir {
148     my ($user) = @_;
149    
150 root 1.111 my $lock = cf::lock_acquire "ext::login::nuke_playerdir";
151    
152 root 1.71 my $temp = "$PLAYERDIR/~$Coro::current~deleting~";
153 root 1.111 aio_rename "$PLAYERDIR/$user", $temp;
154     IO::AIO::aio_rmtree $temp;
155 root 1.1 }
156    
157 root 1.119 sub login_done {
158     my ($pl) = @_;
159    
160     if (0 < Coro::AIO::aio_load "$cf::CONFDIR/motd", my $motd) {
161     $pl->ns->send_msg ("c/motd" => $motd, cf::NDI_CLEAR);
162     }
163     }
164    
165     sub chargen {
166     my ($ns, $user, $pass) = @_;
167    
168     # the rest of this function is character creation
169     $Coro::current->{desc} = "addme($user) chargen";
170    
171     # just to make sure nothing is left over
172     # normally, nothing is there.
173     nuke_playerdir $user;
174    
175     my $pass2 = query $ns, cf::CS_QUERY_HIDEINPUT, "Please type your password again.";
176    
177     if ($pass2 ne $pass) {
178     $ns->send_drawinfo (
179     "The passwords do not match, please try again.",
180     cf::NDI_RED
181     );
182     Coro::Timer::sleep 0.5;
183     next;
184     }
185    
186     my $pl = cf::player::new $user;
187     $pl->password (encode_password $pass);
188     $pl->connect ($ns);
189     my $ob = $pl->ob;
190    
191     $ob->goto ($pl->maplevel, $ob->x, $ob->y);
192    
193     while () {
194     $ob->update_stats;
195     $pl->save_stats;
196    
197     my $res = query $ns, cf::CS_QUERY_SINGLECHAR,
198     "[y] to roll new stats [n] to use stats\n[1-7] [1-7] to swap stats.\nRoll again (y/n/1-7)?";
199    
200     if ($res =~ /^[Nn]/) {
201     last;
202     } elsif ($res > 0 && $res <= 7) {
203     my $swap = query $ns, cf::CS_QUERY_SINGLECHAR, "Swap stat with (will not roll new stats) [1-7]?";
204    
205     if ($swap > 0 && $swap <= 7) {
206     $ob->swap_stats ($res - 1, $swap - 1);
207     }
208     } else {
209     $ob->roll_stats;
210     }
211    
212     Coro::Timer::sleep 0.05;
213     }
214    
215     $ob->set_animation (2);
216     $ob->add_statbonus;
217    
218     while () {
219     $ns->send_msg ("chargen-race-title", ucfirst $pl->title, -1);
220     my $msg = $ob->msg;
221     $msg =~ s/(?<=\S)\n(?=\S)/ /g;
222     $ns->send_msg ("chargen-race-description", $msg, cf::NDI_BLUE);
223    
224     my $res = query $ns, cf::CS_QUERY_SINGLECHAR,
225     "Now choose a character.\nPress any key to change outlook.\nPress `d' when you're pleased.\n";
226    
227     last if $res =~ /[dD]/;
228    
229     $pl->chargen_race_next;
230     Coro::Timer::sleep 0.05;
231     }
232    
233     # create the playerdir, if necessary, as chargen_race_done did it before
234     # presumably because of unique maps
235     aio_mkdir playerdir $pl, 0770;
236     $pl->chargen_race_done;
237    
238     while () {
239     my $res = query $ns, cf::CS_QUERY_SINGLECHAR,
240     "Now choose a gender.\nPress 'f' to become female, and 'm' to become male.\n";
241    
242     if ($res =~ /^[fF]/) {
243     $pl->gender (1);
244     last;
245     } elsif ($res =~ /^[mM]/) {
246     $pl->gender (0);
247     last;
248     }
249     Coro::Timer::sleep 0.05;
250     }
251    
252     $ob->reply (undef, "Welcome to Deliantra!");
253    
254     # XXX: Workaround for delayed client ext protocol handshake
255     $pl->esrv_new_player;
256    
257     delete $pl->{deny_save};
258     }
259    
260 root 1.60 cf::client->attach (on_addme => sub {
261 root 1.1 my ($ns) = @_;
262    
263 root 1.32 $ns->pl and return $ns->destroy;
264 root 1.1
265 root 1.10 $ns->async (sub {
266 root 1.72 $Coro::current->{desc} = "addme init";
267    
268 root 1.1 my ($user, $pass);
269    
270     $ns->send_packet ("addme_success");
271    
272     for (;;) {
273     $ns->send_drawinfo (
274     "Please enter your username now. If you are a new user, "
275     . "make one up that describes your character best. "
276     . "Only letters and digits are allowed, though.",
277     cf::NDI_BLUE
278     );
279    
280     # read username
281     while () {
282 root 1.89 $user = query $ns, 0, "What is your name? (login names are case-sensitive)\n:";
283 root 1.3
284     if ($cf::LOGIN_LOCK{$user}) {
285     $ns->send_drawinfo (
286     "That username is currently used in another login session. "
287     . "Chose another, or wait till the other session has ended.",
288     cf::NDI_RED
289     );
290 root 1.92 } elsif ($user =~ /^[a-zA-Z0-9][a-zA-Z0-9\-_]{2,19}\z/) {
291 root 1.3 last;
292     } else {
293     $ns->send_drawinfo (
294     "Your username contains illegal characters "
295     . "(only a-z, A-Z and 0-9 are allowed), "
296 root 1.92 . "or is not between 3 and 20 characters in length.",
297 root 1.3 cf::NDI_RED
298     );
299     }
300 root 1.61 Coro::Timer::sleep 0.4;
301 root 1.1 }
302    
303     check_playing $ns, $user and next;
304    
305 root 1.72 $Coro::current->{desc} = "addme($user) pass";
306    
307 root 1.1 $ns->send_drawinfo (
308     "Welcome $user, please enter your password now. "
309     . "New users should now choose a password. "
310     . "Anything your client lets you enter is fine.",
311     cf::NDI_BLUE
312     );
313    
314     # read password
315     while () {
316     $pass = query $ns, cf::CS_QUERY_HIDEINPUT, "What is your password?\n:";
317     last if $pass =~ /.../;
318     $ns->send_drawinfo (
319     "Try to use at least three characters as your password please, "
320     . "that cannot be too much to ask for :)",
321     cf::NDI_RED
322     );
323 root 1.61 Coro::Timer::sleep 0.4;
324 root 1.1 }
325    
326 root 1.3 # lock this username for the remainder of this login session
327     if ($cf::LOGIN_LOCK{$user}) {
328     $ns->send_drawinfo (
329     "That username is currently used in another login session. "
330     . "Chose another, or wait till the other session has ended.",
331     cf::NDI_RED
332     );
333     next;
334     }
335     local $cf::LOGIN_LOCK{$user} = 1;
336    
337     check_playing $ns, $user and next;
338    
339 root 1.72 $Coro::current->{desc} = "addme($user) check";
340    
341 root 1.1 # try to read the user file and check the password
342 root 1.19 if (my $pl = cf::player::find $user) {
343     aio_stat $pl->path and next;
344     my $mtime = (stat _)[9];
345 root 1.110 my $token = $pl->password;
346 root 1.1
347 root 1.110 if ($cf::CFG{ext_login_nocheck} or compare_password $pass, $token) {
348 root 1.119 # player exists and passwords match - we can proceed
349    
350 root 1.110 $pl->password (encode_password $pass); # make sure we store the new encoding #d#
351 root 1.1 # password matches, wonderful
352 root 1.11 my $pl = cf::player::find $user or next;
353 root 1.1 $pl->connect ($ns);
354 root 1.48 enter_map $pl;
355 root 1.119 login_done $pl;
356     return;
357 root 1.19 } elsif (can_cleanup $pl, $mtime) {
358 root 1.1 Coro::Timer::sleep 1;
359    
360     $ns->send_drawinfo (
361 root 1.3 "Player exists, but password does not match. If this is your account, "
362     . "please try again. If not, you can now decide to take over this account "
363 root 1.1 . "because it has not been in-use for some time.",
364     cf::NDI_RED
365     );
366    
367     (query $ns, cf::CS_QUERY_SINGLECHAR, "Delete existing account and create a new one (Y/N)?") =~ /^[yY]/
368     or next;
369    
370     # check if the file hasn't changed
371 root 1.11 aio_stat cf::player::path $user and next;
372 root 1.1 $mtime == (stat _)[9] or next;
373    
374 root 1.19 $pl->quit_character;
375 root 1.1
376     # fall through to creation
377     } else {
378     Coro::Timer::sleep 1;
379    
380     $ns->send_drawinfo (
381     "Wrong username or password. Please try again "
382     . "(check for Numlock and other semi-obvious error sources).",
383     cf::NDI_RED
384     );
385     next;
386     }
387 root 1.37 } else {
388     # unable to load the playerfile:
389 root 1.114 # check whether the player dir exists, which means the file is corrupted or
390 root 1.37 # something very similar.
391     if (!aio_stat cf::player::playerdir $user) {
392     $ns->send_drawinfo (
393     "Unable to retrieve this player. It might be a locked or broken account. "
394     . "If this is your account, ask a dungeon master for assistance. "
395     . "Otherwise choose a different login name.",
396     cf::NDI_RED
397     );
398     next;
399     }
400 root 1.1 }
401    
402     last;
403     }
404 root 1.101
405 root 1.119 # lock again, too layz to make this nicer
406     local $cf::LOGIN_LOCK{$user} = 1;
407     chargen $ns, $user, $pass;
408     login_done $ns->pl;
409 root 1.1 });
410 root 1.60 });
411 root 1.1
412 elmex 1.106 cf::register_command password => sub {
413     my ($pl, $arg) = @_;
414    
415 elmex 1.112 unless ($pl->flag (cf::FLAG_WIZ)) {
416     $pl->message (
417     "The password can currently only changed by a DM.",
418     cf::NDI_UNIQUE | cf::NDI_REPLY);
419     return;
420     }
421    
422 elmex 1.106 my (@args) = split /\s+/, $arg;
423 elmex 1.112 my ($player, $new_pw) = @args;
424 elmex 1.106
425     if ($pl->flag (cf::FLAG_WIZ) && $player eq '') {
426     $pl->message (
427     "Usage: password <player> [<new password>]",
428     cf::NDI_UNIQUE | cf::NDI_REPLY);
429     return;
430     }
431    
432 elmex 1.112 if ($new_pw eq '') {
433     $new_pw =
434     join '',
435     map { ('.', '/', 0..9, 'A'..'Z', 'a'..'z')[(cf::rndm 64)] }
436     1..9;
437     }
438 elmex 1.106
439 elmex 1.112 cf::async {
440     my $plc = cf::player::find $player;
441     if ($plc) {
442     $plc->password (encode_password $new_pw);
443 elmex 1.106 $pl->message (
444 elmex 1.112 "Ok, changed password of '$player' to '$new_pw'!",
445 elmex 1.106 cf::NDI_UNIQUE | cf::NDI_RED | cf::NDI_REPLY);
446     } else {
447     $pl->message (
448 elmex 1.112 "Fail! Couldn't set password for '$player', "
449     . "he doesn't seem to exist!",
450 elmex 1.106 cf::NDI_UNIQUE | cf::NDI_RED | cf::NDI_REPLY);
451     }
452 elmex 1.112 };
453 elmex 1.106 };
454    
455 root 1.12 cf::register_command quit => sub {
456     my ($ob, $arg) = @_;
457    
458 root 1.95 $ob->send_msg (undef,
459     "Quitting will delete your character PERMANENTLY: It will be gone forever and any progress will be lost. "
460     . "If you are sure you want to do this, then use the quit_character command instead of quit.",
461     cf::NDI_UNIQUE | cf::NDI_RED | cf::NDI_REPLY);
462 root 1.12 };
463    
464     cf::register_command quit_character => sub {
465     my ($ob, $arg) = @_;
466    
467     my $pl = $ob->contr;
468    
469     $pl->ns->query (cf::CS_QUERY_SINGLECHAR, "Do you want to PERMANENTLY delete your character and all associated data (y/n)?", sub {
470     if ($_[0] !~ /^[yY]/) {
471 root 1.95 $ob->send_msg (undef, "Ok, not not quitting then.", cf::NDI_UNIQUE | cf::NDI_RED | cf::NDI_REPLY);
472 root 1.12 } else {
473 root 1.95 $ob->send_msg (undef, "Ok, quitting, hope to see you again.", cf::NDI_UNIQUE | cf::NDI_RED | cf::NDI_REPLY);
474 root 1.105 cf::async {
475     $pl->quit_character;
476     };
477 root 1.12 }
478     });
479     };
480 root 1.11
481 root 1.1 cf::object->attach (
482     type => cf::SAVEBED,
483     on_apply => sub {
484     my ($bed, $ob) = @_;
485    
486     return cf::override 0 unless $ob->type == cf::PLAYER;
487    
488 root 1.15 my $pl = $ob->contr;
489 root 1.11
490 root 1.1 # update respawn position
491 root 1.11 $pl->savebed ($bed->map->path, $bed->x, $bed->y);
492 root 1.1
493 root 1.111 cf::async {
494     my $killer = cf::arch::get "killer_logout"; $pl->killer ($killer); $killer->destroy;
495     ext::highscore::check $ob;
496    
497     $pl->save;
498 root 1.1
499 root 1.111 $ob->send_msg ($cf::SAY_CHANNEL => "In the future, you will wake up here when you die.", cf::NDI_DEF | cf::NDI_REPLY);
500 root 1.1
501 root 1.115 my $ns = $pl->ns
502     or return;
503    
504     $ns->query (cf::CS_QUERY_SINGLECHAR, "Do you want to continue playing (y/n)?", sub {
505 root 1.111 if ($_[0] !~ /^[yY]/) {
506     $pl->invoke (cf::EVENT_PLAYER_LOGOUT, 1);
507     $pl->deactivate;
508     $pl->ns->destroy;
509     }
510     });
511     };
512 root 1.1 },
513     );
514    
515 root 1.8 cf::player->attach (
516     on_login => sub {
517     my ($pl) = @_;
518     my $name = $pl->ob->name;
519    
520     $_->ob->message ("$name has entered the game.", cf::NDI_DK_ORANGE | cf::NDI_UNIQUE) for cf::player::list;
521     },
522     on_logout => sub {
523     my ($pl, $cleanly) = @_;
524     my $name = $pl->ob->name;
525    
526     if ($cleanly) {
527     $_->ob->message ("$name left the game.", cf::NDI_DK_ORANGE | cf::NDI_UNIQUE) for cf::player::list;
528     } else {
529     $_->ob->message ("$name uncerimoniously disconnected.", cf::NDI_DK_ORANGE | cf::NDI_UNIQUE) for cf::player::list;
530 root 1.79 $pl->{unclean_save} = $cf::RUNTIME
531     unless safe_spot $pl;
532 root 1.8 }
533     },
534     );
535