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

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines