ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/DC.pm
Revision: 1.126
Committed: Tue Nov 7 22:41:27 2006 UTC (17 years, 6 months ago) by root
Branch: MAIN
Changes since 1.125: +3 -3 lines
Log Message:
*** empty log message ***

File Contents

# User Rev Content
1 root 1.1 =head1 NAME
2    
3 root 1.108 CFPlus - undocumented utility garbage for our crossfire client
4 root 1.1
5     =head1 SYNOPSIS
6    
7 root 1.108 use CFPlus;
8 root 1.1
9     =head1 DESCRIPTION
10    
11     =over 4
12    
13     =cut
14    
15 root 1.108 package CFPlus;
16 root 1.1
17 root 1.121 use Carp ();
18    
19 root 1.1 BEGIN {
20 root 1.126 $VERSION = '0.95';
21 root 1.1
22 root 1.2 use XSLoader;
23 root 1.108 XSLoader::load "CFPlus", $VERSION;
24 root 1.1 }
25    
26 root 1.121 BEGIN {
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    
35 root 1.62 use utf8;
36    
37 root 1.52 use AnyEvent ();
38 root 1.34 use BerkeleyDB;
39 root 1.89 use Pod::POM ();
40 root 1.92 use Scalar::Util ();
41 root 1.89 use Storable (); # finally
42    
43 root 1.103 =item guard { BLOCK }
44    
45     Returns an object that executes the given block as soon as it is destroyed.
46    
47     =cut
48    
49     sub guard(&) {
50 root 1.108 bless \(my $cb = $_[0]), "CFPlus::Guard"
51 root 1.103 }
52    
53 root 1.108 sub CFPlus::Guard::DESTROY {
54 root 1.103 ${$_[0]}->()
55     }
56    
57 root 1.105 sub asxml($) {
58     local $_ = $_[0];
59 root 1.89
60 root 1.105 s/&/&/g;
61     s/>/>/g;
62     s/</&lt;/g;
63 root 1.89
64 root 1.105 $_
65 root 1.89 }
66    
67 root 1.123 sub socketpipe() {
68     socketpair my $fh1, my $fh2, Socket::AF_UNIX, Socket::SOCK_STREAM, Socket::PF_UNSPEC
69     or die "cannot establish bidiretcional pipe: $!\n";
70    
71     ($fh1, $fh2)
72     }
73    
74     sub background(&) {
75     my ($cb) = @_;
76    
77     my ($fh_r, $fh_w) = CFPlus::socketpipe;
78    
79     my $pid = fork;
80    
81     if (defined $pid && !$pid) {
82 root 1.124 local $SIG{__DIE__};
83 root 1.123
84     open STDOUT, ">&", $fh_w;
85     open STDERR, ">&", $fh_w;
86     close $fh_r;
87     close $fh_w;
88    
89     $| = 1;
90    
91 root 1.124 eval { $cb->() };
92    
93     if ($@) {
94     my $msg = $@;
95     $msg =~ s/\n+/\n/;
96     warn "FATAL: $msg";
97     CFPlus::_exit 1;
98     }
99 root 1.123
100     # win32 is fucked up, of course. exit will clean stuff up,
101     # which destroys our database etc. _exit will exit ALL
102     # forked processes, because of the dreaded fork emulation.
103 root 1.124 CFPlus::_exit 0;
104 root 1.123 }
105    
106     close $fh_w;
107    
108     my $buffer;
109    
110 root 1.126 my $w; $w = AnyEvent->io (fh => $fh_r, poll => 'r', cb => sub {
111 root 1.123 unless (sysread $fh_r, $buffer, 4096, length $buffer) {
112 root 1.126 undef $w;
113 root 1.123 $buffer .= "done\n";
114     }
115    
116     while ($buffer =~ s/^(.*)\n//) {
117     my $line = $1;
118 root 1.124 $line =~ s/\s+$//;
119 root 1.123 utf8::decode $line;
120     ::message ({
121     markup => "editor($pid): " . CFPlus::asxml $line,
122     });
123     }
124     });
125     }
126    
127 root 1.108 package CFPlus::Database;
128 root 1.89
129     our @ISA = BerkeleyDB::Btree::;
130    
131     sub get($$) {
132     my $data;
133    
134     $_[0]->db_get ($_[1], $data) == 0
135     ? $data
136     : ()
137     }
138    
139     my %DB_SYNC;
140    
141     sub put($$$) {
142     my ($db, $key, $data) = @_;
143    
144 root 1.117 my $hkey = $db + 0;
145 root 1.116 Scalar::Util::weaken $db;
146 root 1.117 $DB_SYNC{$hkey} ||= AnyEvent->timer (after => 5, cb => sub {
147     delete $DB_SYNC{$hkey};
148 root 1.116 $db->db_sync if $db;
149     });
150 root 1.89
151     $db->db_put ($key => $data)
152     }
153    
154 root 1.108 package CFPlus;
155 root 1.52
156 root 1.5 sub find_rcfile($) {
157     my $path;
158    
159 root 1.46 for (grep !ref, @INC) {
160 root 1.108 $path = "$_/CFPlus/resources/$_[0]";
161 root 1.5 return $path if -r $path;
162     }
163    
164     die "FATAL: can't find required file $_[0]\n";
165     }
166    
167 root 1.110 BEGIN {
168     use Crossfire::Protocol::Base ();
169     *to_json = \&Crossfire::Protocol::Base::to_json;
170     *from_json = \&Crossfire::Protocol::Base::from_json;
171 root 1.107 }
172    
173 root 1.5 sub read_cfg {
174     my ($file) = @_;
175    
176 root 1.107 open my $fh, $file
177 root 1.5 or return;
178    
179     local $/;
180 root 1.107 my $CFG = <$fh>;
181 root 1.5
182 root 1.108 if ($CFG =~ /^---/) { ## TODO compatibility cruft, remove
183     require YAML;
184     utf8::decode $CFG;
185     $::CFG = YAML::Load ($CFG);
186     } elsif ($CFG =~ /^\{/) {
187     $::CFG = from_json $CFG;
188 root 1.107 } else {
189 root 1.108 $::CFG = eval $CFG; ## todo comaptibility cruft
190 root 1.107 }
191 root 1.5 }
192    
193     sub write_cfg {
194     my ($file) = @_;
195    
196 root 1.107 $::CFG->{VERSION} = $::VERSION;
197    
198     open my $fh, ">:utf8", $file
199 root 1.5 or return;
200 root 1.108 print $fh to_json $::CFG;
201 root 1.5 }
202    
203 root 1.122 sub http_proxy {
204     my @proxy = win32_proxy_info;
205    
206     if (@proxy) {
207     "http://" . (@proxy < 2 ? "" : @proxy < 3 ? "$proxy[1]\@" : "$proxy[1]:$proxy[2]\@") . $proxy[0]
208     } elsif (exists $ENV{http_proxy}) {
209     $ENV{http_proxy}
210     } else {
211     ()
212     }
213     }
214    
215     sub set_proxy {
216     my $proxy = http_proxy
217     or return;
218    
219     $ENV{http_proxy} = $proxy;
220     }
221    
222 root 1.77 our $DB_ENV;
223 root 1.121 our $DB_STATE;
224    
225     sub db_table($) {
226     my ($table) = @_;
227    
228     $table =~ s/([^a-zA-Z0-9_\-])/sprintf "=%x=", ord $1/ge;
229    
230     new CFPlus::Database
231     -Env => $DB_ENV,
232     -Filename => $table,
233     # -Filename => "database",
234     # -Subname => $table,
235     -Property => DB_CHKSUM,
236     -Flags => DB_CREATE | DB_UPGRADE,
237     or die "unable to create/open database table $_[0]: $BerkeleyDB::Error"
238     }
239 root 1.77
240 root 1.76 {
241     use strict;
242    
243 root 1.87 mkdir "$Crossfire::VARDIR/cfplus", 0777;
244 root 1.77 my $recover = $BerkeleyDB::db_version >= 4.4
245     ? eval "DB_REGISTER | DB_RECOVER"
246     : 0;
247    
248     $DB_ENV = new BerkeleyDB::Env
249 root 1.76 -Home => "$Crossfire::VARDIR/cfplus",
250     -Cachesize => 1_000_000,
251     -ErrFile => "$Crossfire::VARDIR/cfplus/errorlog.txt",
252 root 1.39 # -ErrPrefix => "DATABASE",
253 root 1.76 -Verbose => 1,
254 root 1.77 -Flags => DB_CREATE | DB_RECOVER | DB_INIT_MPOOL | DB_INIT_LOCK | DB_INIT_TXN | $recover,
255 root 1.78 -SetFlags => DB_AUTO_COMMIT | DB_LOG_AUTOREMOVE,
256 root 1.76 or die "unable to create/open database home $Crossfire::VARDIR/cfplus: $BerkeleyDB::Error";
257    
258 root 1.121 $DB_STATE = db_table "state";
259 root 1.34 }
260    
261 root 1.108 package CFPlus::Layout;
262 root 1.97
263 root 1.108 $CFPlus::OpenGL::SHUTDOWN_HOOK{"CFPlus::Layout"} = sub {
264 root 1.98 reset_glyph_cache;
265 root 1.97 };
266    
267 root 1.108 package CFPlus::Item;
268 root 1.62
269 root 1.71 use strict;
270     use Crossfire::Protocol::Constants;
271    
272 elmex 1.84 my $last_enter_count = 1;
273    
274 root 1.62 sub desc_string {
275     my ($self) = @_;
276    
277     my $desc =
278     $self->{nrof} < 2
279     ? $self->{name}
280     : "$self->{nrof} × $self->{name_pl}";
281    
282 root 1.71 $self->{flags} & F_OPEN
283 root 1.62 and $desc .= " (open)";
284 root 1.71 $self->{flags} & F_APPLIED
285 root 1.62 and $desc .= " (applied)";
286 root 1.71 $self->{flags} & F_UNPAID
287 root 1.62 and $desc .= " (unpaid)";
288 root 1.71 $self->{flags} & F_MAGIC
289 root 1.62 and $desc .= " (magic)";
290 root 1.71 $self->{flags} & F_CURSED
291 root 1.62 and $desc .= " (cursed)";
292 root 1.71 $self->{flags} & F_DAMNED
293 root 1.62 and $desc .= " (damned)";
294 root 1.71 $self->{flags} & F_LOCKED
295 root 1.62 and $desc .= " *";
296    
297     $desc
298     }
299    
300     sub weight_string {
301     my ($self) = @_;
302    
303     my $weight = ($self->{nrof} || 1) * $self->{weight};
304    
305     $weight < 0 ? "?" : $weight * 0.001
306     }
307    
308 elmex 1.84 sub do_n_dialog {
309     my ($cb) = @_;
310    
311 root 1.113 my $w = new CFPlus::UI::Toplevel
312 root 1.100 on_delete => sub { $_[0]->destroy; 1 },
313     has_close_button => 1,
314     ;
315    
316 root 1.108 $w->add (my $vb = new CFPlus::UI::VBox x => "center", y => "center");
317     $vb->add (new CFPlus::UI::Label text => "Enter item count:");
318     $vb->add (my $entry = new CFPlus::UI::Entry
319 elmex 1.84 text => $last_enter_count,
320     on_activate => sub {
321     my ($entry) = @_;
322     $last_enter_count = $entry->get_text;
323     $cb->($last_enter_count);
324     $w->hide;
325 root 1.100 $w->destroy;
326    
327     0
328     },
329     on_escape => sub { $w->destroy; 1 },
330 elmex 1.84 );
331 root 1.93 $entry->grab_focus;
332 elmex 1.84 $w->show;
333     }
334    
335 root 1.62 sub update_widgets {
336     my ($self) = @_;
337    
338 root 1.92 # necessary to avoid cyclic references
339     Scalar::Util::weaken $self;
340    
341 root 1.63 my $button_cb = sub {
342     my (undef, $ev, $x, $y) = @_;
343    
344 elmex 1.84 my $targ = $::CONN->{player}{tag};
345 root 1.63
346 elmex 1.84 if ($self->{container} == $::CONN->{player}{tag}) {
347     $targ = $::CONN->{open_container};
348     }
349 root 1.63
350 root 1.108 if (($ev->{mod} & CFPlus::KMOD_SHIFT) && $ev->{button} == 1) {
351 root 1.79 $::CONN->send ("move $targ $self->{tag} 0")
352     if $targ || !($self->{flags} & F_LOCKED);
353 root 1.108 } elsif (($ev->{mod} & CFPlus::KMOD_SHIFT) && $ev->{button} == 2) {
354 elmex 1.86 $self->{flags} & F_LOCKED
355     ? $::CONN->send ("lock " . pack "CN", 0, $self->{tag})
356     : $::CONN->send ("lock " . pack "CN", 1, $self->{tag})
357 root 1.63 } elsif ($ev->{button} == 1) {
358     $::CONN->send ("examine $self->{tag}");
359     } elsif ($ev->{button} == 2) {
360     $::CONN->send ("apply $self->{tag}");
361     } elsif ($ev->{button} == 3) {
362 elmex 1.101 my $move_prefix = $::CONN->{open_container} ? 'put' : 'drop';
363     if ($self->{container} == $::CONN->{open_container}) {
364     $move_prefix = "take";
365     }
366    
367 root 1.63 my @menu_items = (
368     ["examine", sub { $::CONN->send ("examine $self->{tag}") }],
369     ["mark", sub { $::CONN->send ("mark ". pack "N", $self->{tag}) }],
370 elmex 1.99 ["ignite/thaw", # first try of an easier use of flint&steel
371     sub {
372     $::CONN->send ("mark ". pack "N", $self->{tag});
373     $::CONN->send ("command apply flint and steel");
374     }
375     ],
376 elmex 1.109 ["inscribe", # first try of an easier use of flint&steel
377     sub {
378     &::open_string_query ("Text to inscribe", sub {
379     my ($entry, $txt) = @_;
380     $::CONN->send ("mark ". pack "N", $self->{tag});
381     $::CONN->send ("command use_skill inscription $txt");
382     });
383     }
384     ],
385 elmex 1.114 ["rename", # first try of an easier use of flint&steel
386     sub {
387     &::open_string_query ("Rename item to:", sub {
388     my ($entry, $txt) = @_;
389     $::CONN->send ("mark ". pack "N", $self->{tag});
390     $::CONN->send ("command rename to <$txt>");
391 elmex 1.115 }, $self->{name},
392     "If you input no name or erase the current custom name, the custom name will be unset");
393 elmex 1.114 }
394     ],
395 root 1.63 ["apply", sub { $::CONN->send ("apply $self->{tag}") }],
396     (
397 root 1.71 $self->{flags} & F_LOCKED
398 root 1.63 ? (
399     ["unlock", sub { $::CONN->send ("lock " . pack "CN", 0, $self->{tag}) }],
400     )
401     : (
402     ["lock", sub { $::CONN->send ("lock " . pack "CN", 1, $self->{tag}) }],
403 elmex 1.101 ["$move_prefix all", sub { $::CONN->send ("move $targ $self->{tag} 0") }],
404 root 1.104 ["$move_prefix &lt;n&gt;",
405 elmex 1.84 sub {
406     do_n_dialog (sub { $::CONN->send ("move $targ $self->{tag} $_[0]") })
407     }
408     ]
409 root 1.63 )
410     ),
411     );
412    
413 root 1.108 CFPlus::UI::Menu->new (items => \@menu_items)->popup ($ev);
414 root 1.63 }
415    
416     1
417     };
418    
419 root 1.62 my $tooltip_std = "<small>"
420     . "Left click - examine item\n"
421     . "Shift-Left click - " . ($self->{container} ? "move or drop" : "take") . " item\n"
422     . "Middle click - apply\n"
423 elmex 1.86 . "Shift-Middle click - lock/unlock\n"
424 root 1.62 . "Right click - further options"
425     . "</small>\n";
426    
427 root 1.106 my $bg = $self->{flags} & F_CURSED ? [1 , 0 , 0, 0.5]
428     : $self->{flags} & F_MAGIC ? [0.2, 0.2, 1, 0.5]
429     : undef;
430    
431 root 1.108 $self->{face_widget} ||= new CFPlus::UI::Face
432 root 1.63 can_events => 1,
433     can_hover => 1,
434 root 1.67 anim => $self->{anim},
435 root 1.66 animspeed => $self->{animspeed}, # TODO# must be set at creation time
436 root 1.72 on_button_down => $button_cb,
437 root 1.63 ;
438 root 1.106 $self->{face_widget}{bg} = $bg;
439 root 1.62 $self->{face_widget}{face} = $self->{face};
440     $self->{face_widget}{anim} = $self->{anim};
441 root 1.65 $self->{face_widget}{animspeed} = $self->{animspeed};
442 root 1.62 $self->{face_widget}->set_tooltip (
443     "<b>Face/Animation.</b>\n"
444     . "Item uses face #$self->{face}. "
445     . ($self->{animspeed} ? "Item uses animation #$self->{anim} at " . (1 / $self->{animspeed}) . "fps. " : "Item is not animated. ")
446     . "\n\n$tooltip_std"
447     );
448    
449 root 1.108 $self->{desc_widget} ||= new CFPlus::UI::Label
450 root 1.63 can_events => 1,
451     can_hover => 1,
452     ellipsise => 2,
453 root 1.68 align => -1,
454 root 1.72 on_button_down => $button_cb,
455 root 1.63 ;
456 root 1.108 my $desc = CFPlus::Item::desc_string $self;
457 root 1.106 $self->{desc_widget}{bg} = $bg;
458 root 1.63 $self->{desc_widget}->set_text ($desc);
459     $self->{desc_widget}->set_tooltip ("<b>$desc</b>.\n$tooltip_std");
460    
461 root 1.108 $self->{weight_widget} ||= new CFPlus::UI::Label
462 root 1.63 can_events => 1,
463     can_hover => 1,
464     ellipsise => 0,
465 root 1.68 align => 0,
466 root 1.72 on_button_down => $button_cb,
467 root 1.63 ;
468 root 1.106 $self->{weight_widget}{bg} = $bg;
469 root 1.108 $self->{weight_widget}->set_text (CFPlus::Item::weight_string $self);
470 root 1.62 $self->{weight_widget}->set_tooltip (
471     "<b>Weight</b>.\n"
472     . ($self->{weight} >= 0 ? "One item weighs $self->{weight}g. " : "You have no idea how much this weighs. ")
473     . ($self->{nrof} ? "You have $self->{nrof} of it. " : "Item cannot stack with others of it's kind. ")
474     . "\n\n$tooltip_std"
475     );
476     }
477    
478 root 1.1 1;
479    
480     =back
481    
482     =head1 AUTHOR
483    
484     Marc Lehmann <schmorp@schmorp.de>
485     http://home.schmorp.de/
486    
487     =cut
488