ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/DC.pm
(Generate patch)

Comparing deliantra/Deliantra-Client/DC.pm (file contents):
Revision 1.116 by root, Fri Aug 18 01:01:00 2006 UTC vs.
Revision 1.186 by root, Tue Aug 19 17:38:29 2008 UTC

1=head1 NAME 1=head1 NAME
2 2
3CFPlus - undocumented utility garbage for our crossfire client 3DC - undocumented utility garbage for our deliantra client
4 4
5=head1 SYNOPSIS 5=head1 SYNOPSIS
6 6
7 use CFPlus; 7 use DC;
8 8
9=head1 DESCRIPTION 9=head1 DESCRIPTION
10 10
11=over 4 11=over 4
12 12
13=cut 13=cut
14 14
15package Deliantra::Client; # work around CPAN breakage
16package App::Deliantra; # try to reserve namespace
15package CFPlus; 17package DC;
18
19use Carp ();
16 20
17BEGIN { 21BEGIN {
18 $VERSION = '0.2'; 22 $VERSION = '0.9975';
19 23
20 use XSLoader; 24 use XSLoader;
21 XSLoader::load "CFPlus", $VERSION; 25 XSLoader::load "Deliantra::Client", $VERSION;
22} 26}
23 27
24use utf8; 28use utf8;
25 29
26use Carp ();
27use AnyEvent (); 30use AnyEvent ();
28use BerkeleyDB;
29use Pod::POM (); 31use Pod::POM ();
30use Scalar::Util (); 32use File::Path ();
31use Storable (); # finally 33use Storable (); # finally
34use Fcntl ();
35use JSON::XS qw(encode_json decode_json);
32 36
33=item guard { BLOCK } 37=item guard { BLOCK }
34 38
35Returns an object that executes the given block as soon as it is destroyed. 39Returns an object that executes the given block as soon as it is destroyed.
36 40
37=cut 41=cut
38 42
39sub guard(&) { 43sub guard(&) {
40 bless \(my $cb = $_[0]), "CFPlus::Guard" 44 bless \(my $cb = $_[0]), "DC::Guard"
41} 45}
42 46
43sub CFPlus::Guard::DESTROY { 47sub DC::Guard::DESTROY {
44 ${$_[0]}->() 48 ${$_[0]}->()
49}
50
51=item shorten $string[, $maxlength]
52
53=cut
54
55sub shorten($;$) {
56 my ($str, $len) = @_;
57 substr $str, $len, (length $str), "..." if $len + 3 <= length $str;
58 $str
45} 59}
46 60
47sub asxml($) { 61sub asxml($) {
48 local $_ = $_[0]; 62 local $_ = $_[0];
49 63
52 s/</&lt;/g; 66 s/</&lt;/g;
53 67
54 $_ 68 $_
55} 69}
56 70
57package CFPlus::Database; 71sub socketpipe() {
72 socketpair my $fh1, my $fh2, Socket::AF_UNIX, Socket::SOCK_STREAM, Socket::PF_UNSPEC
73 or die "cannot establish bidirectional pipe: $!\n";
58 74
59our @ISA = BerkeleyDB::Btree::; 75 ($fh1, $fh2)
60
61sub get($$) {
62 my $data;
63
64 $_[0]->db_get ($_[1], $data) == 0
65 ? $data
66 : ()
67} 76}
68 77
69my %DB_SYNC; 78sub background(&;&) {
79 my ($bg, $cb) = @_;
70 80
71sub put($$$) { 81 my ($fh_r, $fh_w) = DC::socketpipe;
72 my ($db, $key, $data) = @_;
73 82
74 my $key = $db + 0; 83 my $pid = fork;
75 Scalar::Util::weaken $db; 84
76 $DB_SYNC{$key} ||= AnyEvent->timer (after => 5, cb => sub { 85 if (defined $pid && !$pid) {
77 delete $DB_SYNC{$key}; 86 local $SIG{__DIE__};
78 $db->db_sync if $db; 87
88 open STDOUT, ">&", $fh_w;
89 open STDERR, ">&", $fh_w;
90 close $fh_r;
91 close $fh_w;
92
93 $| = 1;
94
95 eval { $bg->() };
96
97 if ($@) {
98 my $msg = $@;
99 $msg =~ s/\n+/\n/;
100 warn "FATAL: $msg";
101 DC::_exit 1;
102 }
103
104 # win32 is fucked up, of course. exit will clean stuff up,
105 # which destroys our database etc. _exit will exit ALL
106 # forked processes, because of the dreaded fork emulation.
107 DC::_exit 0;
108 }
109
110 close $fh_w;
111
112 my $buffer;
113
114 my $w; $w = AnyEvent->io (fh => $fh_r, poll => 'r', cb => sub {
115 unless (sysread $fh_r, $buffer, 4096, length $buffer) {
116 undef $w;
117 $cb->();
118 return;
119 }
120
121 while ($buffer =~ s/^(.*)\n//) {
122 my $line = $1;
123 $line =~ s/\s+$//;
124 utf8::decode $line;
125 if ($line =~ /^\x{e877}json_msg (.*)$/s) {
126 $cb->(JSON::XS->new->allow_nonref->decode ($1));
127 } else {
128 ::message ({
129 markup => "background($pid): " . DC::asxml $line,
130 });
131 }
132 }
79 }); 133 });
80
81 $db->db_put ($key => $data)
82} 134}
83 135
136sub background_msg {
137 my ($msg) = @_;
138
139 $msg = "\x{e877}json_msg " . JSON::XS->new->allow_nonref->encode ($msg);
140 $msg =~ s/\n//g;
141 utf8::encode $msg;
142 print $msg, "\n";
143}
144
84package CFPlus; 145package DC;
85 146
86sub find_rcfile($) { 147sub find_rcfile($) {
87 my $path; 148 my $path;
88 149
89 for (grep !ref, @INC) { 150 for (grep !ref, @INC) {
90 $path = "$_/CFPlus/resources/$_[0]"; 151 $path = "$_/Deliantra/Client/private/resources/$_[0]";
91 return $path if -r $path; 152 return $path if -r $path;
92 } 153 }
93 154
94 die "FATAL: can't find required file $_[0]\n"; 155 die "FATAL: can't find required file $_[0]\n";
95}
96
97BEGIN {
98 use Crossfire::Protocol::Base ();
99 *to_json = \&Crossfire::Protocol::Base::to_json;
100 *from_json = \&Crossfire::Protocol::Base::from_json;
101} 156}
102 157
103sub read_cfg { 158sub read_cfg {
104 my ($file) = @_; 159 my ($file) = @_;
105 160
107 or return; 162 or return;
108 163
109 local $/; 164 local $/;
110 my $CFG = <$fh>; 165 my $CFG = <$fh>;
111 166
112 if ($CFG =~ /^---/) { ## TODO compatibility cruft, remove
113 require YAML;
114 utf8::decode $CFG;
115 $::CFG = YAML::Load ($CFG);
116 } elsif ($CFG =~ /^\{/) {
117 $::CFG = from_json $CFG; 167 $::CFG = decode_json $CFG;
118 } else {
119 $::CFG = eval $CFG; ## todo comaptibility cruft
120 }
121} 168}
122 169
123sub write_cfg { 170sub write_cfg {
124 my ($file) = @_; 171 my $file = "$Deliantra::VARDIR/client.cf";
125 172
126 $::CFG->{VERSION} = $::VERSION; 173 $::CFG->{VERSION} = $::VERSION;
127 174
128 open my $fh, ">:utf8", $file 175 open my $fh, ">:utf8", $file
129 or return; 176 or return;
130 print $fh to_json $::CFG; 177 print $fh JSON::XS->new->utf8->pretty->encode ($::CFG);
131} 178}
132 179
133our $DB_ENV; 180sub http_proxy {
181 my @proxy = win32_proxy_info;
134 182
135{ 183 if (@proxy) {
136 use strict; 184 "http://" . (@proxy < 2 ? "" : @proxy < 3 ? "$proxy[1]\@" : "$proxy[1]:$proxy[2]\@") . $proxy[0]
137 185 } elsif (exists $ENV{http_proxy}) {
138 mkdir "$Crossfire::VARDIR/cfplus", 0777; 186 $ENV{http_proxy}
139 my $recover = $BerkeleyDB::db_version >= 4.4 187 } else {
140 ? eval "DB_REGISTER | DB_RECOVER" 188 ()
141 : 0; 189 }
142
143 $DB_ENV = new BerkeleyDB::Env
144 -Home => "$Crossfire::VARDIR/cfplus",
145 -Cachesize => 1_000_000,
146 -ErrFile => "$Crossfire::VARDIR/cfplus/errorlog.txt",
147# -ErrPrefix => "DATABASE",
148 -Verbose => 1,
149 -Flags => DB_CREATE | DB_RECOVER | DB_INIT_MPOOL | DB_INIT_LOCK | DB_INIT_TXN | $recover,
150 -SetFlags => DB_AUTO_COMMIT | DB_LOG_AUTOREMOVE,
151 or die "unable to create/open database home $Crossfire::VARDIR/cfplus: $BerkeleyDB::Error";
152} 190}
153 191
154sub db_table($) { 192sub set_proxy {
193 my $proxy = http_proxy
194 or return;
195
196 $ENV{http_proxy} = $proxy;
197}
198
199sub lwp_useragent {
200 require LWP::UserAgent;
201
202 DC::set_proxy;
203
204 my $ua = LWP::UserAgent->new (
205 agent => "deliantra $VERSION",
206 keep_alive => 1,
207 env_proxy => 1,
208 timeout => 30,
209 );
210}
211
212sub lwp_check($) {
155 my ($table) = @_; 213 my ($res) = @_;
156 214
157 $table =~ s/([^a-zA-Z0-9_\-])/sprintf "=%x=", ord $1/ge; 215 $res->is_error
216 and die $res->status_line;
158 217
159 new CFPlus::Database 218 $res
160 -Env => $DB_ENV,
161 -Filename => $table,
162# -Filename => "database",
163# -Subname => $table,
164 -Property => DB_CHKSUM,
165 -Flags => DB_CREATE | DB_UPGRADE,
166 or die "unable to create/open database table $_[0]: $BerkeleyDB::Error"
167} 219}
168 220
221sub fh_nonblocking($$) {
222 my ($fh, $nb) = @_;
223
224 if ($^O eq "MSWin32") {
225 $nb = (! ! $nb) + 0;
226 ioctl $fh, 0x8004667e, \$nb; # FIONBIO
227 } else {
228 fcntl $fh, &Fcntl::F_SETFL, $nb ? &Fcntl::O_NONBLOCK : 0;
229 }
230}
231
169package CFPlus::Layout; 232package DC::Layout;
170 233
171$CFPlus::OpenGL::SHUTDOWN_HOOK{"CFPlus::Layout"} = sub { 234$DC::OpenGL::INIT_HOOK{"DC::Layout"} = sub {
172 reset_glyph_cache; 235 glyph_cache_restore;
173}; 236};
174 237
175package CFPlus::Item; 238$DC::OpenGL::SHUTDOWN_HOOK{"DC::Layout"} = sub {
176 239 glyph_cache_backup;
177use strict; 240};
178use Crossfire::Protocol::Constants;
179
180my $last_enter_count = 1;
181
182sub desc_string {
183 my ($self) = @_;
184
185 my $desc =
186 $self->{nrof} < 2
187 ? $self->{name}
188 : "$self->{nrof} × $self->{name_pl}";
189
190 $self->{flags} & F_OPEN
191 and $desc .= " (open)";
192 $self->{flags} & F_APPLIED
193 and $desc .= " (applied)";
194 $self->{flags} & F_UNPAID
195 and $desc .= " (unpaid)";
196 $self->{flags} & F_MAGIC
197 and $desc .= " (magic)";
198 $self->{flags} & F_CURSED
199 and $desc .= " (cursed)";
200 $self->{flags} & F_DAMNED
201 and $desc .= " (damned)";
202 $self->{flags} & F_LOCKED
203 and $desc .= " *";
204
205 $desc
206}
207
208sub weight_string {
209 my ($self) = @_;
210
211 my $weight = ($self->{nrof} || 1) * $self->{weight};
212
213 $weight < 0 ? "?" : $weight * 0.001
214}
215
216sub do_n_dialog {
217 my ($cb) = @_;
218
219 my $w = new CFPlus::UI::Toplevel
220 on_delete => sub { $_[0]->destroy; 1 },
221 has_close_button => 1,
222 ;
223
224 $w->add (my $vb = new CFPlus::UI::VBox x => "center", y => "center");
225 $vb->add (new CFPlus::UI::Label text => "Enter item count:");
226 $vb->add (my $entry = new CFPlus::UI::Entry
227 text => $last_enter_count,
228 on_activate => sub {
229 my ($entry) = @_;
230 $last_enter_count = $entry->get_text;
231 $cb->($last_enter_count);
232 $w->hide;
233 $w->destroy;
234
235 0
236 },
237 on_escape => sub { $w->destroy; 1 },
238 );
239 $entry->grab_focus;
240 $w->show;
241}
242
243sub update_widgets {
244 my ($self) = @_;
245
246 # necessary to avoid cyclic references
247 Scalar::Util::weaken $self;
248
249 my $button_cb = sub {
250 my (undef, $ev, $x, $y) = @_;
251
252 my $targ = $::CONN->{player}{tag};
253
254 if ($self->{container} == $::CONN->{player}{tag}) {
255 $targ = $::CONN->{open_container};
256 }
257
258 if (($ev->{mod} & CFPlus::KMOD_SHIFT) && $ev->{button} == 1) {
259 $::CONN->send ("move $targ $self->{tag} 0")
260 if $targ || !($self->{flags} & F_LOCKED);
261 } elsif (($ev->{mod} & CFPlus::KMOD_SHIFT) && $ev->{button} == 2) {
262 $self->{flags} & F_LOCKED
263 ? $::CONN->send ("lock " . pack "CN", 0, $self->{tag})
264 : $::CONN->send ("lock " . pack "CN", 1, $self->{tag})
265 } elsif ($ev->{button} == 1) {
266 $::CONN->send ("examine $self->{tag}");
267 } elsif ($ev->{button} == 2) {
268 $::CONN->send ("apply $self->{tag}");
269 } elsif ($ev->{button} == 3) {
270 my $move_prefix = $::CONN->{open_container} ? 'put' : 'drop';
271 if ($self->{container} == $::CONN->{open_container}) {
272 $move_prefix = "take";
273 }
274
275 my @menu_items = (
276 ["examine", sub { $::CONN->send ("examine $self->{tag}") }],
277 ["mark", sub { $::CONN->send ("mark ". pack "N", $self->{tag}) }],
278 ["ignite/thaw", # first try of an easier use of flint&steel
279 sub {
280 $::CONN->send ("mark ". pack "N", $self->{tag});
281 $::CONN->send ("command apply flint and steel");
282 }
283 ],
284 ["inscribe", # first try of an easier use of flint&steel
285 sub {
286 &::open_string_query ("Text to inscribe", sub {
287 my ($entry, $txt) = @_;
288 $::CONN->send ("mark ". pack "N", $self->{tag});
289 $::CONN->send ("command use_skill inscription $txt");
290 });
291 }
292 ],
293 ["rename", # first try of an easier use of flint&steel
294 sub {
295 &::open_string_query ("Rename item to:", sub {
296 my ($entry, $txt) = @_;
297 $::CONN->send ("mark ". pack "N", $self->{tag});
298 $::CONN->send ("command rename to <$txt>");
299 }, $self->{name},
300 "If you input no name or erase the current custom name, the custom name will be unset");
301 }
302 ],
303 ["apply", sub { $::CONN->send ("apply $self->{tag}") }],
304 (
305 $self->{flags} & F_LOCKED
306 ? (
307 ["unlock", sub { $::CONN->send ("lock " . pack "CN", 0, $self->{tag}) }],
308 )
309 : (
310 ["lock", sub { $::CONN->send ("lock " . pack "CN", 1, $self->{tag}) }],
311 ["$move_prefix all", sub { $::CONN->send ("move $targ $self->{tag} 0") }],
312 ["$move_prefix &lt;n&gt;",
313 sub {
314 do_n_dialog (sub { $::CONN->send ("move $targ $self->{tag} $_[0]") })
315 }
316 ]
317 )
318 ),
319 );
320
321 CFPlus::UI::Menu->new (items => \@menu_items)->popup ($ev);
322 }
323
324 1
325 };
326
327 my $tooltip_std = "<small>"
328 . "Left click - examine item\n"
329 . "Shift-Left click - " . ($self->{container} ? "move or drop" : "take") . " item\n"
330 . "Middle click - apply\n"
331 . "Shift-Middle click - lock/unlock\n"
332 . "Right click - further options"
333 . "</small>\n";
334
335 my $bg = $self->{flags} & F_CURSED ? [1 , 0 , 0, 0.5]
336 : $self->{flags} & F_MAGIC ? [0.2, 0.2, 1, 0.5]
337 : undef;
338
339 $self->{face_widget} ||= new CFPlus::UI::Face
340 can_events => 1,
341 can_hover => 1,
342 anim => $self->{anim},
343 animspeed => $self->{animspeed}, # TODO# must be set at creation time
344 on_button_down => $button_cb,
345 ;
346 $self->{face_widget}{bg} = $bg;
347 $self->{face_widget}{face} = $self->{face};
348 $self->{face_widget}{anim} = $self->{anim};
349 $self->{face_widget}{animspeed} = $self->{animspeed};
350 $self->{face_widget}->set_tooltip (
351 "<b>Face/Animation.</b>\n"
352 . "Item uses face #$self->{face}. "
353 . ($self->{animspeed} ? "Item uses animation #$self->{anim} at " . (1 / $self->{animspeed}) . "fps. " : "Item is not animated. ")
354 . "\n\n$tooltip_std"
355 );
356
357 $self->{desc_widget} ||= new CFPlus::UI::Label
358 can_events => 1,
359 can_hover => 1,
360 ellipsise => 2,
361 align => -1,
362 on_button_down => $button_cb,
363 ;
364 my $desc = CFPlus::Item::desc_string $self;
365 $self->{desc_widget}{bg} = $bg;
366 $self->{desc_widget}->set_text ($desc);
367 $self->{desc_widget}->set_tooltip ("<b>$desc</b>.\n$tooltip_std");
368
369 $self->{weight_widget} ||= new CFPlus::UI::Label
370 can_events => 1,
371 can_hover => 1,
372 ellipsise => 0,
373 align => 0,
374 on_button_down => $button_cb,
375 ;
376 $self->{weight_widget}{bg} = $bg;
377 $self->{weight_widget}->set_text (CFPlus::Item::weight_string $self);
378 $self->{weight_widget}->set_tooltip (
379 "<b>Weight</b>.\n"
380 . ($self->{weight} >= 0 ? "One item weighs $self->{weight}g. " : "You have no idea how much this weighs. ")
381 . ($self->{nrof} ? "You have $self->{nrof} of it. " : "Item cannot stack with others of it's kind. ")
382 . "\n\n$tooltip_std"
383 );
384}
385 241
3861; 2421;
387 243
388=back 244=back
389 245

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines