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.121 by root, Fri Sep 29 00:56:05 2006 UTC vs.
Revision 1.161 by root, Sat Dec 8 18:38:10 2007 UTC

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

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines