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; |