| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package Games::Roguelike::World::Daemon; |
|
2
|
|
|
|
|
|
|
|
|
3
|
1
|
|
|
1
|
|
64348
|
use strict; |
|
|
1
|
|
|
|
|
3
|
|
|
|
1
|
|
|
|
|
69
|
|
|
4
|
|
|
|
|
|
|
|
|
5
|
1
|
|
|
1
|
|
2139
|
use Games::Roguelike::Utils qw(:all); |
|
|
1
|
|
|
|
|
3
|
|
|
|
1
|
|
|
|
|
309
|
|
|
6
|
1
|
|
|
1
|
|
8
|
use Games::Roguelike::Console::ANSI; |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
47
|
|
|
7
|
1
|
|
|
1
|
|
7
|
use Games::Roguelike::Mob; |
|
|
1
|
|
|
|
|
1
|
|
|
|
1
|
|
|
|
|
21
|
|
|
8
|
1
|
|
|
1
|
|
5
|
use POSIX; |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
11
|
|
|
9
|
|
|
|
|
|
|
|
|
10
|
1
|
|
|
1
|
|
4511
|
use IO::Socket; |
|
|
1
|
|
|
|
|
37565
|
|
|
|
1
|
|
|
|
|
8
|
|
|
11
|
1
|
|
|
1
|
|
13027
|
use IO::Select; |
|
|
1
|
|
|
|
|
2466
|
|
|
|
1
|
|
|
|
|
69
|
|
|
12
|
1
|
|
|
1
|
|
9
|
use IO::File qw(); # this prevents warnings on win32 |
|
|
1
|
|
|
|
|
3
|
|
|
|
1
|
|
|
|
|
47
|
|
|
13
|
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
our $VERSION = '0.4.' . [qw$Revision: 253 $]->[1]; |
|
15
|
|
|
|
|
|
|
|
|
16
|
1
|
|
|
1
|
|
1305
|
use Time::HiRes qw(time); |
|
|
1
|
|
|
|
|
2459
|
|
|
|
1
|
|
|
|
|
8
|
|
|
17
|
|
|
|
|
|
|
|
|
18
|
1
|
|
|
1
|
|
237
|
use base 'Games::Roguelike::World'; |
|
|
1
|
|
|
|
|
3
|
|
|
|
1
|
|
|
|
|
1023
|
|
|
19
|
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
# purpose of module: |
|
21
|
|
|
|
|
|
|
# |
|
22
|
|
|
|
|
|
|
# multi-user telnet daemon |
|
23
|
|
|
|
|
|
|
# finite-state processor, allows for single-thread engine |
|
24
|
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
=head1 NAME |
|
26
|
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
Games::Roguelike::World::Daemon - roguelike game telnet daemon |
|
28
|
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
=head1 SYNOPSIS |
|
30
|
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
# for an extended example with move overrides, see the scripts/netgame included |
|
32
|
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
use strict; |
|
34
|
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
package myWorld; # always override |
|
36
|
|
|
|
|
|
|
use base 'Games::Roguelike::World::Daemon'; |
|
37
|
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
my $r = myWorld->new(w=>80,h=>50,dispw=>40,disph=>18); # create a networked world |
|
39
|
|
|
|
|
|
|
$r->area(new Games::Roguelike::Area(name=>'1')); # create a new area in this world called "1" |
|
40
|
|
|
|
|
|
|
$r->area->generate('cavelike'); # make a cavelike maze |
|
41
|
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
while (1) { |
|
43
|
|
|
|
|
|
|
$r->proc(); |
|
44
|
|
|
|
|
|
|
} |
|
45
|
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
sub readinput { # called when input is available |
|
47
|
|
|
|
|
|
|
my $self = shift; |
|
48
|
|
|
|
|
|
|
if (my $c = $self->getch()) { # returns undef on failure |
|
49
|
|
|
|
|
|
|
if ($self->{vp}->kbdmove($c, 1)) { # '1' in second param means "test only" |
|
50
|
|
|
|
|
|
|
$r->queuemove($self->{vp}, $c); # if the move is good, queue it |
|
51
|
|
|
|
|
|
|
} |
|
52
|
|
|
|
|
|
|
} |
|
53
|
|
|
|
|
|
|
} |
|
54
|
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
sub newconn { # called when someone connects |
|
56
|
|
|
|
|
|
|
my $self = shift; |
|
57
|
|
|
|
|
|
|
my $char = mychar->new($self->area(1), # create a new character |
|
58
|
|
|
|
|
|
|
sym=>'@', |
|
59
|
|
|
|
|
|
|
color=>'green', |
|
60
|
|
|
|
|
|
|
pov=>7 |
|
61
|
|
|
|
|
|
|
); |
|
62
|
|
|
|
|
|
|
$self->{vp} = $char; # viewpoint is a connection state obect |
|
63
|
|
|
|
|
|
|
$self->{state} = 'MOVE'; # set state (another state object) |
|
64
|
|
|
|
|
|
|
} |
|
65
|
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
package mychar; |
|
67
|
|
|
|
|
|
|
use base 'Games::Roguelike::Mob'; |
|
68
|
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
=head1 DESCRIPTION |
|
70
|
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
This module uses the Games::Roguelike::World object as the basis for a finite-state based |
|
72
|
|
|
|
|
|
|
network game engine. |
|
73
|
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
* uses Games::Roguelike::Console::ANSI library to draw the current area |
|
75
|
|
|
|
|
|
|
* currently assumes Games::Roguelike::Mob's as characters in the game |
|
76
|
|
|
|
|
|
|
* currently assumes Games::Roguelike::Item's as items in the game |
|
77
|
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
The module provides th eservice of accepting connections, maintainting he association between |
|
79
|
|
|
|
|
|
|
the connection and a "state" and "viewpoint" for each connection, managing "tick" times, |
|
80
|
|
|
|
|
|
|
and rendering maps for each connection. |
|
81
|
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
=head2 METHODS |
|
83
|
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
=over |
|
85
|
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
=cut |
|
87
|
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
my $WIN32 = ($^O=~/win32/i); |
|
89
|
|
|
|
|
|
|
my @SOCKS; |
|
90
|
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
=item new () |
|
92
|
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
Similar to ::World new, but with arguments: host, port, and addr |
|
94
|
|
|
|
|
|
|
|
|
95
|
|
|
|
|
|
|
This begins listening for connections, and sets up some signal handlers for |
|
96
|
|
|
|
|
|
|
graceful death. |
|
97
|
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
=cut |
|
99
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
sub new { |
|
101
|
1
|
|
|
1
|
1
|
1504
|
my $pkg = shift; |
|
102
|
1
|
|
|
|
|
12
|
my $r = $pkg->SUPER::new(@_, noconsole=>1); |
|
103
|
1
|
|
|
|
|
3
|
bless $r, $pkg; |
|
104
|
|
|
|
|
|
|
|
|
105
|
1
|
50
|
|
|
|
5
|
$r->{tick} = 0.5 if !$r->{tick}; |
|
106
|
|
|
|
|
|
|
|
|
107
|
1
|
|
|
|
|
35
|
local $! = 0; |
|
108
|
1
|
|
|
|
|
2
|
my %addrs; |
|
109
|
|
|
|
|
|
|
|
|
110
|
1
|
50
|
|
|
|
4
|
$addrs{LocalAddr} = $r->{addr} if $r->{addr}; |
|
111
|
1
|
50
|
|
|
|
3
|
$addrs{LocalHost} = $r->{host} if $r->{host}; |
|
112
|
1
|
50
|
|
|
|
4
|
$addrs{LocalPort} = $r->{port} if $r->{port}; |
|
113
|
|
|
|
|
|
|
|
|
114
|
1
|
|
|
|
|
11
|
$r->{main_sock} = new IO::Socket::INET( |
|
115
|
|
|
|
|
|
|
%addrs, |
|
116
|
|
|
|
|
|
|
Listen => 1, |
|
117
|
|
|
|
|
|
|
ReuseAddr => 1); |
|
118
|
|
|
|
|
|
|
|
|
119
|
1
|
50
|
|
|
|
330
|
die $! unless $r->{main_sock}; |
|
120
|
|
|
|
|
|
|
|
|
121
|
1
|
50
|
|
|
|
11
|
$r->{stdout} = *STDOUT unless $r->{stdout}; |
|
122
|
|
|
|
|
|
|
|
|
123
|
1
|
|
|
|
|
9
|
$r->{read_set} = new IO::Select(); |
|
124
|
1
|
|
|
|
|
15
|
$r->{read_set}->add($r->{main_sock}); |
|
125
|
1
|
|
|
|
|
40
|
$r->{write_set} = new IO::Select(); |
|
126
|
|
|
|
|
|
|
|
|
127
|
1
|
|
|
|
|
9
|
push @SOCKS, $r->{main_sock}; |
|
128
|
|
|
|
|
|
|
|
|
129
|
1
|
|
|
|
|
4
|
$SIG{__DIE__} = \&sig_die_handler; |
|
130
|
1
|
|
|
|
|
11
|
$SIG{INT} = \&sig_int_handler; |
|
131
|
|
|
|
|
|
|
|
|
132
|
1
|
|
|
|
|
5
|
return $r; |
|
133
|
|
|
|
|
|
|
} |
|
134
|
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
sub sig_int_handler { |
|
136
|
0
|
|
|
0
|
0
|
0
|
sig_die_handler(); |
|
137
|
0
|
|
|
|
|
0
|
exit(0); |
|
138
|
|
|
|
|
|
|
} |
|
139
|
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
sub sig_die_handler { |
|
141
|
0
|
|
|
0
|
0
|
0
|
for (@SOCKS) { |
|
142
|
0
|
|
|
|
|
0
|
close($_); |
|
143
|
|
|
|
|
|
|
} |
|
144
|
0
|
|
|
|
|
0
|
undef @SOCKS; |
|
145
|
0
|
|
|
|
|
0
|
1; |
|
146
|
|
|
|
|
|
|
} |
|
147
|
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
sub DESTROY { |
|
149
|
1
|
|
|
1
|
|
58
|
my $r = shift; |
|
150
|
1
|
50
|
|
|
|
5
|
if ($r->{main_sock}) { |
|
151
|
1
|
|
|
|
|
5
|
$r->{main_sock}->close(); |
|
152
|
|
|
|
|
|
|
} |
|
153
|
1
|
|
|
|
|
25
|
$r->SUPER::DESTROY(); |
|
154
|
|
|
|
|
|
|
} |
|
155
|
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
=item proc () |
|
157
|
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
Look for waiting input and calls: |
|
159
|
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
newconn() - for new conneciton |
|
161
|
|
|
|
|
|
|
readinput() - when input is available |
|
162
|
|
|
|
|
|
|
tick() - to process per-turn moves |
|
163
|
|
|
|
|
|
|
drawallmaps() - to render all the maps |
|
164
|
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
When those functions are called the class {vp} and {state} variables are |
|
166
|
|
|
|
|
|
|
set to the connection's "viewpoint" (character) and "state". |
|
167
|
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
Also, the special scalar state 'QUIT' gracefully removes a connection. |
|
169
|
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
(It might be interesting to use code refs as states) |
|
171
|
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
=cut |
|
173
|
|
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
sub proc { |
|
175
|
2
|
|
|
2
|
1
|
1295
|
my $self = shift; |
|
176
|
|
|
|
|
|
|
|
|
177
|
|
|
|
|
|
|
# $self->log("proc " . $self->{read_set}->count()); |
|
178
|
|
|
|
|
|
|
|
|
179
|
2
|
|
|
|
|
8
|
my $now = time(); |
|
180
|
2
|
100
|
|
|
|
10
|
$self->{ts} = $now unless $self->{ts}; |
|
181
|
2
|
|
|
|
|
14
|
my $rem = max(0.1, $self->{tick} - ($now - $self->{ts})); |
|
182
|
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
# $self->log("rem", $rem); |
|
184
|
|
|
|
|
|
|
|
|
185
|
2
|
|
|
|
|
18
|
my ($new_readable, $new_writable, $new_error) = IO::Select->select($self->{read_set}, $self->{write_set}, $self->{read_set}, $rem + .01); |
|
186
|
|
|
|
|
|
|
|
|
187
|
2
|
|
|
|
|
123
|
foreach my $sock (@$new_readable) { |
|
188
|
2
|
100
|
|
|
|
8
|
if ($sock == $self->{main_sock}) { |
|
189
|
1
|
|
|
|
|
9
|
my $new_sock = $sock->accept(); |
|
190
|
1
|
|
|
|
|
117
|
$self->log("incoming connection from: " , $new_sock->peerhost()); |
|
191
|
|
|
|
|
|
|
# new socket may not be readable yet. |
|
192
|
1
|
50
|
|
|
|
15
|
if ($new_sock) { |
|
193
|
1
|
|
|
|
|
3
|
push @SOCKS, $new_sock; |
|
194
|
1
|
|
|
|
|
3
|
++$self->{req_count}; |
|
195
|
1
|
50
|
|
|
|
4
|
if ($WIN32) { |
|
196
|
0
|
|
|
|
|
0
|
ioctl($new_sock, 0x8004667e, pack("I", 1)); |
|
197
|
|
|
|
|
|
|
} else { |
|
198
|
1
|
|
|
|
|
7
|
fcntl($new_sock, F_SETFL(), O_NONBLOCK()); |
|
199
|
|
|
|
|
|
|
} |
|
200
|
1
|
|
|
|
|
5
|
$new_sock->autoflush(1); |
|
201
|
1
|
|
|
|
|
32
|
my @opts; |
|
202
|
|
|
|
|
|
|
# pass through some options to console object on new connections |
|
203
|
1
|
|
|
|
|
4
|
for (qw(usereadkey noinit)) { |
|
204
|
2
|
100
|
|
|
|
11
|
push @opts, $_=>$self->{$_} if defined $self->{$_}; |
|
205
|
|
|
|
|
|
|
} |
|
206
|
1
|
|
|
|
|
5
|
$self->{read_set}->add($new_sock); |
|
207
|
1
|
|
|
|
|
49
|
*$new_sock{HASH}->{con} = new Games::Roguelike::Console::ANSI (in=>$new_sock, out=>$new_sock, @opts); |
|
208
|
1
|
|
|
|
|
5
|
*$new_sock{HASH}->{time} = time(); |
|
209
|
1
|
|
|
|
|
5
|
*$new_sock{HASH}->{errc} = 0; |
|
210
|
1
|
|
|
|
|
4
|
$self->{con} = *$new_sock{HASH}->{con}; |
|
211
|
1
|
|
|
|
|
10
|
$self->echo_off(); |
|
212
|
1
|
|
|
|
|
3
|
$self->{state} = ''; |
|
213
|
1
|
|
|
|
|
2
|
$self->{vp} = ''; |
|
214
|
1
|
|
|
|
|
5
|
$self->newconn($new_sock); |
|
215
|
1
|
|
|
|
|
6
|
*$new_sock{HASH}->{state} = $self->{state}; |
|
216
|
1
|
|
|
|
|
4
|
*$new_sock{HASH}->{char} = $self->{vp}; |
|
217
|
1
|
50
|
33
|
|
|
14
|
$self->{vp}->{con} = $self->{con} if $self->{vp} && !$self->{vp}->{con}; |
|
218
|
1
|
|
|
|
|
15
|
$self->log("state is: " , $self->{state}); |
|
219
|
|
|
|
|
|
|
} |
|
220
|
|
|
|
|
|
|
} else { |
|
221
|
1
|
50
|
33
|
|
|
10
|
if ($sock->eof() || !$sock->connected() || (*$sock{HASH}->{errc} > 5)) { |
|
|
|
|
33
|
|
|
|
|
|
222
|
0
|
|
|
|
|
0
|
$self->{state} = 'QUIT'; |
|
223
|
|
|
|
|
|
|
} else { |
|
224
|
1
|
|
|
|
|
61
|
$self->log("reading from: " , $sock->peerhost()); |
|
225
|
1
|
|
|
|
|
6
|
$self->log("state was: " , $self->{state}); |
|
226
|
1
|
|
|
|
|
4
|
$self->{con} = *$sock{HASH}->{con}; |
|
227
|
1
|
|
|
|
|
3
|
$self->{state} = *$sock{HASH}->{state}; |
|
228
|
1
|
|
|
|
|
3
|
$self->{vp} = *$sock{HASH}->{char}; |
|
229
|
1
|
|
|
|
|
4
|
$self->readinput($sock); |
|
230
|
1
|
|
|
|
|
6
|
*$sock{HASH}->{state} = $self->{state}; |
|
231
|
1
|
|
|
|
|
2
|
*$sock{HASH}->{char} = $self->{vp}; |
|
232
|
1
|
50
|
33
|
|
|
9
|
$self->{vp}->{con} = $self->{con} if $self->{vp} && !$self->{vp}->{con}; |
|
233
|
1
|
|
|
|
|
8
|
$self->log("state is: " , $self->{state}); |
|
234
|
|
|
|
|
|
|
} |
|
235
|
|
|
|
|
|
|
|
|
236
|
1
|
50
|
|
|
|
6
|
if ($self->{state} eq 'QUIT') { |
|
237
|
1
|
|
|
|
|
3
|
eval { |
|
238
|
1
|
50
|
|
|
|
10
|
*$sock{HASH}->{char}->{area}->delmob(*$sock{HASH}->{char}) if *$sock{HASH}->{char}; |
|
239
|
|
|
|
|
|
|
}; |
|
240
|
1
|
|
|
|
|
6
|
$self->{read_set}->remove($sock); |
|
241
|
1
|
|
|
|
|
40
|
$sock->close(); |
|
242
|
|
|
|
|
|
|
} |
|
243
|
|
|
|
|
|
|
} |
|
244
|
|
|
|
|
|
|
} |
|
245
|
2
|
|
|
|
|
63
|
foreach my $sock (@$new_error) { |
|
246
|
0
|
|
|
|
|
0
|
*$sock{HASH}->{char}->{area}->delmob(*$sock{HASH}->{char}); |
|
247
|
0
|
|
|
|
|
0
|
$self->{read_set}->remove($sock); |
|
248
|
0
|
|
|
|
|
0
|
close($sock); |
|
249
|
|
|
|
|
|
|
} |
|
250
|
|
|
|
|
|
|
{ |
|
251
|
2
|
|
|
|
|
4
|
my $now = time(); |
|
|
2
|
|
|
|
|
7
|
|
|
252
|
2
|
|
|
|
|
5
|
my $rem = $now - $self->{ts}; |
|
253
|
|
|
|
|
|
|
|
|
254
|
2
|
50
|
|
|
|
13
|
if ($rem >= $self->{tick}) { |
|
255
|
|
|
|
|
|
|
#$self->log("tick"); |
|
256
|
0
|
|
|
|
|
0
|
$self->tick(); |
|
257
|
0
|
|
|
|
|
0
|
$self->drawallmaps(); |
|
258
|
0
|
|
|
|
|
0
|
$self->{ts} = $now; |
|
259
|
|
|
|
|
|
|
} |
|
260
|
|
|
|
|
|
|
} |
|
261
|
|
|
|
|
|
|
} |
|
262
|
|
|
|
|
|
|
|
|
263
|
|
|
|
|
|
|
sub drawallmaps { |
|
264
|
0
|
|
|
0
|
0
|
0
|
my $self = shift; |
|
265
|
0
|
|
|
|
|
0
|
foreach my $sock ($self->{read_set}->handles()) { |
|
266
|
0
|
0
|
|
|
|
0
|
if (*$sock{HASH}->{char}) { |
|
267
|
0
|
|
|
|
|
0
|
$self->{vp} = *$sock{HASH}->{char}; |
|
268
|
0
|
|
|
|
|
0
|
$self->{con} = *$sock{HASH}->{con}; |
|
269
|
0
|
|
|
|
|
0
|
$self->{area} = $self->{vp}->{area}; |
|
270
|
0
|
|
|
|
|
0
|
my $color = $self->{vp}->{color}; |
|
271
|
0
|
|
|
|
|
0
|
my $sym = $self->{vp}->{sym}; |
|
272
|
0
|
|
|
|
|
0
|
$self->setfocuscolor(); |
|
273
|
0
|
|
|
|
|
0
|
$self->drawmap(); |
|
274
|
0
|
|
|
|
|
0
|
$sock->flush(); |
|
275
|
0
|
|
|
|
|
0
|
$self->{vp}->{color} = $color; |
|
276
|
0
|
|
|
|
|
0
|
$self->{vp}->{sym} = $sym; |
|
277
|
|
|
|
|
|
|
} |
|
278
|
|
|
|
|
|
|
} |
|
279
|
|
|
|
|
|
|
} |
|
280
|
|
|
|
|
|
|
|
|
281
|
|
|
|
|
|
|
sub echo_off { |
|
282
|
1
|
|
|
1
|
0
|
1
|
my $self = shift; |
|
283
|
1
|
|
|
|
|
3
|
my $sock = $self->{con}->{out}; |
|
284
|
|
|
|
|
|
|
# i will echo if needed, you don't echo, i will suppress go ahead, you do suppress goahead |
|
285
|
1
|
|
|
|
|
46
|
print $sock "\xff\xfb\x01\xff\xfb\x03\xff\xfd\x03"; |
|
286
|
|
|
|
|
|
|
} |
|
287
|
|
|
|
|
|
|
|
|
288
|
|
|
|
|
|
|
sub echo_on { |
|
289
|
0
|
|
|
0
|
0
|
0
|
my $self = shift; |
|
290
|
0
|
|
|
|
|
0
|
my $sock = $self->{con}->{out}; |
|
291
|
|
|
|
|
|
|
# i wont echo, you do echo |
|
292
|
0
|
|
|
|
|
0
|
print $sock "\xff\xfc\x01\xff\xfd\x01"; |
|
293
|
|
|
|
|
|
|
} |
|
294
|
|
|
|
|
|
|
|
|
295
|
|
|
|
|
|
|
=item getstr () |
|
296
|
|
|
|
|
|
|
|
|
297
|
|
|
|
|
|
|
Reads a string from the active connection. |
|
298
|
|
|
|
|
|
|
|
|
299
|
|
|
|
|
|
|
Returns undef if the string is not ready. |
|
300
|
|
|
|
|
|
|
|
|
301
|
|
|
|
|
|
|
=cut |
|
302
|
|
|
|
|
|
|
|
|
303
|
|
|
|
|
|
|
sub hexify { |
|
304
|
0
|
|
|
0
|
0
|
0
|
my ($s) = @_; |
|
305
|
0
|
|
|
|
|
0
|
my $ret = ''; |
|
306
|
0
|
|
|
|
|
0
|
for (split(//,$s)) { |
|
307
|
0
|
|
|
|
|
0
|
$ret .= sprintf("x%x", ord($_)); |
|
308
|
0
|
0
|
|
|
|
0
|
$ret .= "($_)" if $_ =~ /\w/; |
|
309
|
|
|
|
|
|
|
} |
|
310
|
0
|
|
|
|
|
0
|
return $ret; |
|
311
|
|
|
|
|
|
|
} |
|
312
|
|
|
|
|
|
|
|
|
313
|
|
|
|
|
|
|
sub getstr { |
|
314
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
|
315
|
0
|
|
|
|
|
0
|
my $sock = $self->{con}->{in}; |
|
316
|
0
|
|
|
|
|
0
|
my $first = 1; |
|
317
|
|
|
|
|
|
|
|
|
318
|
0
|
|
|
|
|
0
|
while (1) { |
|
319
|
0
|
|
|
|
|
0
|
my $b = $self->getch(); |
|
320
|
0
|
0
|
0
|
|
|
0
|
if (!defined($b)) { |
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
321
|
0
|
0
|
|
|
|
0
|
++(*$sock{HASH}->{errc}) if $first; |
|
322
|
0
|
|
|
|
|
0
|
return undef; |
|
323
|
|
|
|
|
|
|
} elsif($b eq 'BACKSPACE') { |
|
324
|
0
|
|
|
|
|
0
|
$self->log("getstr read $b"); |
|
325
|
0
|
0
|
|
|
|
0
|
if (length(*$sock{HASH}->{sbuf}) > 0) { |
|
326
|
0
|
|
|
|
|
0
|
syswrite($sock, chr(8), 1); |
|
327
|
0
|
|
|
|
|
0
|
syswrite($sock, ' ', 1); |
|
328
|
0
|
|
|
|
|
0
|
syswrite($sock, chr(8), 1); |
|
329
|
0
|
|
|
|
|
0
|
substr(*$sock{HASH}->{sbuf},-1,1) = ''; |
|
330
|
|
|
|
|
|
|
} |
|
331
|
|
|
|
|
|
|
} elsif(length($b) > 1 || $b eq '') { |
|
332
|
0
|
|
|
|
|
0
|
next; |
|
333
|
|
|
|
|
|
|
} else { |
|
334
|
0
|
|
|
|
|
0
|
$self->log("getstr read " . ord($b)); |
|
335
|
0
|
|
|
|
|
0
|
syswrite($sock,$b,1); # echo on getstr |
|
336
|
0
|
0
|
|
|
|
0
|
$first = 0 if $first; |
|
337
|
0
|
|
|
|
|
0
|
*$sock{HASH}->{errc} = 0; |
|
338
|
0
|
|
|
|
|
0
|
*$sock{HASH}->{sbuf} .= $b; |
|
339
|
|
|
|
|
|
|
} |
|
340
|
0
|
0
|
0
|
|
|
0
|
if ($b eq "\n" || $b eq "\r") { |
|
341
|
0
|
|
|
|
|
0
|
my $temp = *$sock{HASH}->{sbuf}; |
|
342
|
0
|
|
|
|
|
0
|
*$sock{HASH}->{sbuf} = ''; |
|
343
|
0
|
|
|
|
|
0
|
return $temp; |
|
344
|
|
|
|
|
|
|
} |
|
345
|
|
|
|
|
|
|
} |
|
346
|
|
|
|
|
|
|
} |
|
347
|
|
|
|
|
|
|
|
|
348
|
|
|
|
|
|
|
=item getch () |
|
349
|
|
|
|
|
|
|
|
|
350
|
|
|
|
|
|
|
Reads a character from the active connection. |
|
351
|
|
|
|
|
|
|
|
|
352
|
|
|
|
|
|
|
Returns undef if no input is ready. |
|
353
|
|
|
|
|
|
|
|
|
354
|
|
|
|
|
|
|
=cut |
|
355
|
|
|
|
|
|
|
|
|
356
|
|
|
|
|
|
|
sub getch { |
|
357
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
|
358
|
0
|
|
|
|
|
0
|
my $c = $self->{con}->nbgetch(); |
|
359
|
0
|
0
|
|
|
|
0
|
if (! defined $c) { |
|
360
|
0
|
|
|
|
|
0
|
my $sock = $self->{con}->{in}; |
|
361
|
0
|
|
|
|
|
0
|
++(*$sock{HASH}->{errc}) |
|
362
|
|
|
|
|
|
|
} |
|
363
|
0
|
|
|
|
|
0
|
return $c; |
|
364
|
|
|
|
|
|
|
} |
|
365
|
|
|
|
|
|
|
|
|
366
|
|
|
|
|
|
|
=item charmsg ($char) |
|
367
|
|
|
|
|
|
|
|
|
368
|
|
|
|
|
|
|
Calls showmsg on the console contained in $char; |
|
369
|
|
|
|
|
|
|
|
|
370
|
|
|
|
|
|
|
=cut |
|
371
|
|
|
|
|
|
|
|
|
372
|
|
|
|
|
|
|
sub charmsg { |
|
373
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
|
374
|
0
|
|
|
|
|
0
|
my ($char, $msg, $attr) = @_; |
|
375
|
0
|
|
|
|
|
0
|
my $con = $self->{con}; |
|
376
|
0
|
|
|
|
|
0
|
$self->{con} = $char->{con}; |
|
377
|
0
|
|
|
|
|
0
|
$self->showmsg($msg,$attr); |
|
378
|
0
|
|
|
|
|
0
|
$self->{con} = $con; |
|
379
|
|
|
|
|
|
|
} |
|
380
|
|
|
|
|
|
|
|
|
381
|
|
|
|
|
|
|
# log and debug print are essentially the same thing |
|
382
|
|
|
|
|
|
|
|
|
383
|
|
|
|
|
|
|
sub log { |
|
384
|
5
|
|
|
5
|
0
|
88
|
my $self = shift; |
|
385
|
5
|
|
|
|
|
11
|
my $out = $self->{stdout}; |
|
386
|
5
|
|
|
|
|
277
|
print $out scalar(localtime()) . "\t" . join("\t", @_) . "\n"; |
|
387
|
|
|
|
|
|
|
} |
|
388
|
|
|
|
|
|
|
|
|
389
|
|
|
|
|
|
|
sub dprint { |
|
390
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
|
391
|
0
|
|
|
|
|
|
my $out = $self->{stdout}; |
|
392
|
0
|
|
|
|
|
|
print $out scalar(localtime()) . "\t" . join("\t", @_) . "\n"; |
|
393
|
|
|
|
|
|
|
} |
|
394
|
|
|
|
|
|
|
|
|
395
|
|
|
|
|
|
|
# override this for your game |
|
396
|
|
|
|
|
|
|
|
|
397
|
|
|
|
|
|
|
# for now, the way we report back state changes is to modify |
|
398
|
|
|
|
|
|
|
# |
|
399
|
|
|
|
|
|
|
# $self->{state} |
|
400
|
|
|
|
|
|
|
# $self->{vp} # for creating/loading/switching to a character's viewpoint |
|
401
|
|
|
|
|
|
|
# |
|
402
|
|
|
|
|
|
|
# these are then linked to the socket |
|
403
|
|
|
|
|
|
|
# |
|
404
|
|
|
|
|
|
|
# actual action/movement by a charcter should be queued here, then processed according to a random sort and/or a sort based |
|
405
|
|
|
|
|
|
|
# on the speed of the character at tick() time |
|
406
|
|
|
|
|
|
|
# |
|
407
|
|
|
|
|
|
|
# ie: if an ogre and a sprite move during the same tick, the sprite always goes first, even if the |
|
408
|
|
|
|
|
|
|
# ogre's player has a faster internet connection |
|
409
|
|
|
|
|
|
|
# |
|
410
|
|
|
|
|
|
|
# use getch for a no-echo read of a character |
|
411
|
|
|
|
|
|
|
# use getstr for an echoed read of a carraige return delimited string |
|
412
|
|
|
|
|
|
|
# |
|
413
|
|
|
|
|
|
|
# both will return undef if there's no input yet |
|
414
|
|
|
|
|
|
|
# don't "wait" for anything in your functons, game is single threaded! |
|
415
|
|
|
|
|
|
|
# |
|
416
|
|
|
|
|
|
|
|
|
417
|
|
|
|
|
|
|
=item readinput () |
|
418
|
|
|
|
|
|
|
|
|
419
|
|
|
|
|
|
|
Must override and call getch() or getstr(). |
|
420
|
|
|
|
|
|
|
|
|
421
|
|
|
|
|
|
|
The {vp}, {state}, and {con} vars are set on this call, can be |
|
422
|
|
|
|
|
|
|
changed, and will be preserved. |
|
423
|
|
|
|
|
|
|
|
|
424
|
|
|
|
|
|
|
Actual action/movement by a charcter should be queued here, then processed according to |
|
425
|
|
|
|
|
|
|
a random sort and/or a sort based on the speed of the character. |
|
426
|
|
|
|
|
|
|
|
|
427
|
|
|
|
|
|
|
For example: If a tank and a motorcycle move during the same tick, the motorcycle would always go first, even if the tank's player has a faster internet connection. Queueing the moves allows you to do this. |
|
428
|
|
|
|
|
|
|
|
|
429
|
|
|
|
|
|
|
Remember never to do something that blocks or waits for input, game is single-threaded. |
|
430
|
|
|
|
|
|
|
|
|
431
|
|
|
|
|
|
|
=cut |
|
432
|
|
|
|
|
|
|
|
|
433
|
|
|
|
|
|
|
sub readinput { |
|
434
|
0
|
|
|
0
|
1
|
|
die "need to overide this, see netgame example"; |
|
435
|
|
|
|
|
|
|
} |
|
436
|
|
|
|
|
|
|
|
|
437
|
|
|
|
|
|
|
# override this for intro screen, please enter yor name, etc. |
|
438
|
|
|
|
|
|
|
# use $self->{con} for the the Games::Roguelike::Console object (remember, chars are not actually written until flushed, which you can do here if you want) |
|
439
|
|
|
|
|
|
|
|
|
440
|
|
|
|
|
|
|
=item newconn () |
|
441
|
|
|
|
|
|
|
|
|
442
|
|
|
|
|
|
|
Must override and either create a character or show an intro screen, or something. |
|
443
|
|
|
|
|
|
|
|
|
444
|
|
|
|
|
|
|
The {vp}, {state}, and {con} vars are set on this call, can be changed, and will be preserved. |
|
445
|
|
|
|
|
|
|
|
|
446
|
|
|
|
|
|
|
=cut |
|
447
|
|
|
|
|
|
|
|
|
448
|
|
|
|
|
|
|
sub newconn { |
|
449
|
0
|
|
|
0
|
1
|
|
die "need to overide this, see netgame example"; |
|
450
|
|
|
|
|
|
|
} |
|
451
|
|
|
|
|
|
|
|
|
452
|
|
|
|
|
|
|
=item setfocuscolor () |
|
453
|
|
|
|
|
|
|
|
|
454
|
|
|
|
|
|
|
Change the display color/symbol of the {vp} character here in order to distinguish it from other (enemy?) characters. |
|
455
|
|
|
|
|
|
|
|
|
456
|
|
|
|
|
|
|
=cut |
|
457
|
|
|
|
|
|
|
|
|
458
|
|
|
|
|
|
|
# change the symbol/color of the character when it's "in focus" |
|
459
|
|
|
|
|
|
|
sub setfocuscolor { |
|
460
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
|
461
|
0
|
|
|
|
|
|
$self->{vp}->{color} = 'bold yellow'; |
|
462
|
|
|
|
|
|
|
} |
|
463
|
|
|
|
|
|
|
|
|
464
|
|
|
|
|
|
|
=item queuemove ($char, $move[, $msg]) |
|
465
|
|
|
|
|
|
|
|
|
466
|
|
|
|
|
|
|
Pushes a "move" for char $char showing message $msg. By default will not queu if a move has been set. The "move" variabe is set in the "char" object to record whether a move has occured. |
|
467
|
|
|
|
|
|
|
|
|
468
|
|
|
|
|
|
|
=cut |
|
469
|
|
|
|
|
|
|
|
|
470
|
|
|
|
|
|
|
# queue a move until tick time |
|
471
|
|
|
|
|
|
|
sub queuemove { |
|
472
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
|
473
|
0
|
|
|
|
|
|
my ($char, $move, $msg) = @_; |
|
474
|
0
|
0
|
|
|
|
|
if ($char->{move}) { |
|
475
|
|
|
|
|
|
|
# already moving, so do nothing |
|
476
|
|
|
|
|
|
|
# might what to show a message here |
|
477
|
|
|
|
|
|
|
} else { |
|
478
|
0
|
0
|
|
|
|
|
$self->showmsg($msg) if $msg; |
|
479
|
0
|
|
|
|
|
|
$self->{con}->refresh(); |
|
480
|
0
|
|
|
|
|
|
$char->{move} = $move; |
|
481
|
0
|
|
|
|
|
|
push @{$self->{qmove}}, $char; |
|
|
0
|
|
|
|
|
|
|
|
482
|
|
|
|
|
|
|
} |
|
483
|
|
|
|
|
|
|
} |
|
484
|
|
|
|
|
|
|
|
|
485
|
|
|
|
|
|
|
# override this to sort the queue by character speed, display hit points, turn-counts or other status info, etc. |
|
486
|
|
|
|
|
|
|
# override to process character and mob actions/movement map is auto-redrawn for all connections after the tick (if changed) |
|
487
|
|
|
|
|
|
|
# don't try to draw here... since no character has the focus...it will fail |
|
488
|
|
|
|
|
|
|
|
|
489
|
|
|
|
|
|
|
=item tick () |
|
490
|
|
|
|
|
|
|
|
|
491
|
|
|
|
|
|
|
Override for per-turn move processing. This is called for each game turn, which defaults to a half-second. |
|
492
|
|
|
|
|
|
|
Default behavior is to sort all the queued moves and execute them. |
|
493
|
|
|
|
|
|
|
|
|
494
|
|
|
|
|
|
|
A good way to handle this might be to make the "moves" be code references, which get passed "char" as the argument. |
|
495
|
|
|
|
|
|
|
|
|
496
|
|
|
|
|
|
|
=cut |
|
497
|
|
|
|
|
|
|
|
|
498
|
|
|
|
|
|
|
sub tick { |
|
499
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
|
500
|
0
|
|
|
|
|
|
my @auto; |
|
501
|
0
|
|
|
|
|
|
foreach my $char (randsort(@{$self->{qmove}})) { |
|
|
0
|
|
|
|
|
|
|
|
502
|
0
|
|
|
|
|
|
$char->kbdmove($char->{move}); |
|
503
|
0
|
|
|
|
|
|
$char->{move} = ''; |
|
504
|
|
|
|
|
|
|
} |
|
505
|
|
|
|
|
|
|
} |
|
506
|
|
|
|
|
|
|
|
|
507
|
|
|
|
|
|
|
=back |
|
508
|
|
|
|
|
|
|
|
|
509
|
|
|
|
|
|
|
=head1 BUGS |
|
510
|
|
|
|
|
|
|
|
|
511
|
|
|
|
|
|
|
Currently this fails on Win32 |
|
512
|
|
|
|
|
|
|
|
|
513
|
|
|
|
|
|
|
=head1 SEE ALSO |
|
514
|
|
|
|
|
|
|
|
|
515
|
|
|
|
|
|
|
L |
|
516
|
|
|
|
|
|
|
|
|
517
|
|
|
|
|
|
|
=head1 AUTHOR |
|
518
|
|
|
|
|
|
|
|
|
519
|
|
|
|
|
|
|
Erik Aronesty C |
|
520
|
|
|
|
|
|
|
|
|
521
|
|
|
|
|
|
|
=head1 LICENSE |
|
522
|
|
|
|
|
|
|
|
|
523
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or |
|
524
|
|
|
|
|
|
|
modify it under the same terms as Perl itself. |
|
525
|
|
|
|
|
|
|
|
|
526
|
|
|
|
|
|
|
See L or the included LICENSE file. |
|
527
|
|
|
|
|
|
|
|
|
528
|
|
|
|
|
|
|
=cut |
|
529
|
|
|
|
|
|
|
|
|
530
|
|
|
|
|
|
|
1; |