ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/pclient
Revision: 1.179
Committed: Tue Apr 25 09:52:05 2006 UTC (18 years, 1 month ago) by root
Branch: MAIN
Changes since 1.178: +44 -1 lines
Log Message:
*** empty log message ***

File Contents

# User Rev Content
1 root 1.1 #!/opt/bin/perl
2 root 1.25
3 root 1.2 use strict;
4 root 1.25 use utf8;
5 root 1.2
6 root 1.176 BEGIN {
7     if (%PAR::LibCache) {
8     @INC = grep ref, @INC; # weed out all paths except pars loader refs
9    
10     while (my ($filename, $zip) = each %PAR::LibCache) {
11     for ($zip->memberNames) {
12     next unless /^\/root\/(.*)/;
13     $zip->extractMember ($_, "$ENV{PAR_TEMP}/$1")
14     unless -e "$ENV{PAR_TEMP}/$1";
15     }
16     }
17    
18     unshift @INC, $ENV{PAR_TEMP};
19    
20     if ($^O eq "MSWin32") {
21     $ENV{GTK_RC_FILES} = "$ENV{PAR_TEMP}/share/themes/MS-Windows/gtk-2.0/gtkrc";
22     }
23     }
24     }
25    
26     # need to do it again because that pile of garbage called PAR nukes it before main
27     unshift @INC, $ENV{PAR_TEMP};
28    
29 root 1.87 use Time::HiRes 'time';
30     use Event;
31 root 1.13
32 elmex 1.11 use Crossfire;
33 root 1.2 use Crossfire::Protocol;
34    
35 root 1.116 use Compress::LZF;
36    
37 root 1.67 use CFClient;
38 root 1.72 use CFClient::UI;
39 root 1.141 use CFClient::MapWidget;
40 elmex 1.10
41 root 1.177 $Event::DIED = sub {
42     CFClient::error $_[1];
43     };
44 root 1.176
45 root 1.178 #$SIG{__WARN__} = sub { Carp::cluck $_[0] };#d#
46    
47 root 1.63 our $VERSION = '0.1';
48    
49 root 1.96 my $MAX_FPS = 60;
50 root 1.90 my $MIN_FPS = 5; # unused as of yet
51 root 1.63
52 root 1.112 our $META_SERVER = "crossfire.real-time.com:13326";
53    
54 root 1.116 our $FACEMAP;
55     our $TILECACHE;
56     our $MAPCACHE;
57 root 1.19
58 root 1.87 our $LAST_REFRESH;
59     our $NOW;
60    
61 elmex 1.10 our $CFG;
62 root 1.13 our $CONN;
63 root 1.85 our $FAST; # fast, low-quality mode, possibly useful for software-rendering
64 root 1.2
65 root 1.75 our @SDL_MODES;
66 root 1.30 our $WIDTH;
67     our $HEIGHT;
68     our $FULLSCREEN;
69 root 1.99 our $FONTSIZE;
70 root 1.30
71 root 1.168 our $FONT_PROP;
72     our $FONT_FIXED;
73    
74 root 1.95 our $MAP;
75 root 1.69 our $MAPWIDGET;
76 root 1.112 our $BUTTONBAR;
77     our $LOGVIEW;
78     our $CONSOLE;
79     our $METASERVER;
80 root 1.57
81 root 1.173 our $FLOORBOX;
82 elmex 1.125 our $GAUGES;
83 elmex 1.154 our $STATWIDS;
84 elmex 1.125
85 root 1.86 our $SDL_ACTIVE;
86 root 1.13 our %SDL_CB;
87 root 1.18
88 root 1.134 our $SDL_MIXER;
89     our @SOUNDS; # event => file mapping
90     our %AUDIO_CHUNKS; # audio files
91    
92 root 1.30 our $ALT_ENTER_MESSAGE;
93 root 1.51 our $STATUS_LINE;
94 root 1.64 our $DEBUG_STATUS;
95 root 1.30
96 root 1.82 sub status {
97     $STATUS_LINE->set_text ($_[0]);
98 root 1.128 $STATUS_LINE->move (0, $HEIGHT - $ALT_ENTER_MESSAGE->{h} - $STATUS_LINE->{h});
99 root 1.82 }
100    
101     sub debug {
102     $DEBUG_STATUS->set_text ($_[0]);
103 root 1.128 $DEBUG_STATUS->move ($WIDTH - $DEBUG_STATUS->{w}, 0, $DEBUG_STATUS->{w}, $DEBUG_STATUS->{h});
104 root 1.82 }
105    
106 root 1.84 sub start_game {
107 root 1.85 status "logging in...";
108    
109 root 1.106 my $mapsize = List::Util::min 32, List::Util::max 11, int $WIDTH * $CFG->{mapsize} * 0.01 / 32;
110 root 1.84
111 root 1.116 $MAPCACHE = CFClient::db_table "mapcache_$CFG->{host}";
112    
113 root 1.95 $MAP = new CFClient::Map $mapsize, $mapsize;
114 root 1.112
115     my ($host, $port) = split /:/, $CFG->{host};
116 root 1.95
117 root 1.84 $CONN = new conn
118 root 1.112 host => $host,
119     port => $port || 13327,
120 root 1.84 user => $CFG->{user},
121     pass => $CFG->{password},
122     mapw => $mapsize,
123     maph => $mapsize,
124     ;
125    
126 root 1.85 status "login successful";
127    
128 root 1.84 CFClient::lowdelay fileno $CONN->{fh};
129     }
130    
131     sub stop_game {
132     undef $CONN;
133     }
134    
135 root 1.111 sub client_setup {
136 root 1.99 my $dialog = new CFClient::UI::FancyFrame
137 root 1.150 title => "Client Setup",
138 root 1.81 child => (my $vbox = new CFClient::UI::VBox);
139     $vbox->add (my $table = new CFClient::UI::Table expand => 1, col_expand => [0, 1]);
140    
141 root 1.140 $table->add (0, 0, new CFClient::UI::Label valign => 0, align => 1, text => "Video Mode");
142 root 1.81 $table->add (1, 0, my $hbox = new CFClient::UI::HBox);
143    
144     $hbox->add (my $mode_slider = new CFClient::UI::Slider expand => 1, req_w => 100, range => [$CFG->{sdl_mode}, 0, scalar @SDL_MODES, 1]);
145 root 1.150 $hbox->add (my $mode_label = new CFClient::UI::Label align => 0, valign => 0, height => 0.8, template => "9999x9999");
146 root 1.81
147     $mode_slider->connect (changed => sub {
148     my ($self, $value) = @_;
149    
150     $CFG->{sdl_mode} = $self->{range}[0] = $value = int $value;
151     $mode_label->set_text (sprintf "%dx%d", @{$SDL_MODES[$value]});
152     });
153     $mode_slider->emit (changed => $mode_slider->{range}[0]);
154 root 1.82
155 elmex 1.158 my $row = 1;
156    
157     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Fullscreen");
158 elmex 1.166 $table->add (1, $row++, new CFClient::UI::CheckBox
159     state => $CFG->{fullscreen},
160     tooltip => "Bring the client into fullscreen mode",
161     connect_changed => sub {
162     my ($self, $value) = @_;
163     $CFG->{fullscreen} = $value;
164     }
165     );
166 root 1.85
167 elmex 1.158 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Fast & Ugly");
168 root 1.163 $table->add (1, $row++, new CFClient::UI::CheckBox
169     state => $CFG->{fast},
170     tooltip => "Lower the visual quality considerably to speed up rendering.",
171     connect_changed => sub {
172     my ($self, $value) = @_;
173     $CFG->{fast} = $value;
174     }
175     );
176 root 1.89
177 root 1.169 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Map Scale");
178     $table->add (1, $row++, new CFClient::UI::Slider
179     range => [$CFG->{map_scale}, 0.25, 2, 0.05],
180     tooltip => "Enlarge or shrink the displayed map",
181     connect_changed => sub {
182     my ($self, $value) = @_;
183     $CFG->{map_scale} = 0.05 * int $value / 0.05;
184     }
185     );
186    
187 elmex 1.158 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Fog of War");
188 root 1.163 $table->add (1, $row++, new CFClient::UI::CheckBox
189     state => $CFG->{fow_enable},
190     tooltip => "Fog-of-War marks areas that cannot be seen by the player",
191     connect_changed => sub {
192     my ($self, $value) = @_;
193     $CFG->{fow_enable} = $value;
194     }
195     );
196 root 1.97
197 elmex 1.158 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "FoW Intensity");
198 root 1.163 $table->add (1, $row++, new CFClient::UI::Slider
199     range => [$CFG->{fow_intensity}, 0, 1 + 0.001, 0.001],
200     tooltip => "The higher the intensity, the lighter the Fog-of-War color",
201     connect_changed => sub {
202     my ($self, $value) = @_;
203     $CFG->{fow_intensity} = $value;
204     }
205     );
206 root 1.90
207 elmex 1.158 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "FoW Smooth");
208 root 1.163 $table->add (1, $row++, new CFClient::UI::CheckBox
209     state => $CFG->{fow_smooth},
210     tooltip => "Smooth the Fog-of-War a bit to make it more realistic",
211     connect_changed => sub {
212     my ($self, $value) = @_;
213     $CFG->{fow_smooth} = $value;
214     status "Fog of War smoothing requires OpenGL 1.2 or higher" if $CFClient::GL_VERSION < 1.2;
215     }
216     );
217 root 1.91
218 elmex 1.158 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "GUI Fontsize");
219 root 1.163 $table->add (1, $row++, new CFClient::UI::Slider
220     range => [$CFG->{gui_fontsize}, 0.5, 2, 0.1],
221     tooltip => "The font size used by most GUI elements",
222     connect_changed => sub {
223     $CFG->{gui_fontsize} = 0.1 * int $_[1] * 10;
224 root 1.140 # $FONTSIZE = int $HEIGHT / 40 * $CFG->{gui_fontsize};
225 root 1.163 }
226     );
227 root 1.140
228 elmex 1.158 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Server Log Fontsize");
229 root 1.163 $table->add (1, $row++, new CFClient::UI::Slider
230     range => [$CFG->{log_fontsize}, 0.5, 2, 0.1],
231     tooltip => "The font size used by the server log window only",
232     connect_changed => sub {
233     $LOGVIEW->set_fontsize ($CFG->{log_fontsize} = 0.1 * int $_[1] * 10);
234     }
235     );
236 root 1.105
237 elmex 1.158 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Stats Fontsize");
238 root 1.163
239     $table->add (1, $row++, new CFClient::UI::Slider
240     range => [$CFG->{stat_fontsize}, 0.5, 2, 0.1],
241     tooltip => "The font size used by the statistics window only",
242     connect_changed => sub {
243     $CFG->{stat_fontsize} = 0.1 * int $_[1] * 10;
244     &set_stats_window_fontsize;
245     }
246     );
247 elmex 1.157
248 root 1.163 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Gauge size");
249     $table->add (1, $row++, new CFClient::UI::Slider
250     range => [$CFG->{gauge_size}, 0.2, 0.8, 0.02],
251     tooltip => "Adjust the size of the stats gauges at the bottom right",
252     connect_changed => sub {
253     $CFG->{gauge_size} = $_[1];
254 root 1.164 my $h = int $HEIGHT * $CFG->{gauge_size};
255 root 1.163 $GAUGES->{win}->set_size ($WIDTH, $h);
256 root 1.164 $GAUGES->{win}->move (0, $HEIGHT - $h);
257 root 1.163 }
258     );
259 elmex 1.158
260     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Gauge fontsize");
261 root 1.163 $table->add (1, $row++, new CFClient::UI::Slider
262     range => [$CFG->{gauge_fontsize}, 0.5, 2.0, 0.1],
263 elmex 1.166 tooltip => "Adjusts the fontsize of the gauges at the bottom right",
264 root 1.163 connect_changed => sub {
265     $CFG->{gauge_fontsize} = 0.1 * int $_[1] * 10;
266     &set_gauge_window_fontsize;
267     }
268     );
269 elmex 1.158
270 root 1.163 $table->add (1, $row++, new CFClient::UI::Button
271     expand => 1, align => 0, text => "Apply",
272 root 1.168 tooltip => "Apply the video settings",
273 root 1.163 connect_activate => sub {
274     video_shutdown ();
275     video_init ();
276     }
277     );
278 root 1.111
279 elmex 1.158 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Audio Enable");
280 root 1.163 $table->add (1, $row++, new CFClient::UI::CheckBox
281     state => $CFG->{audio_enable},
282     tooltip => "If enabled, sound effects and music will be played. If disabled, no audio will be used and the soundcard will not be opened.",
283     connect_changed => sub {
284     $CFG->{audio_enable} = $_[1];
285     }
286     );
287 root 1.140 # $table->add (0, 9, new CFClient::UI::Label valign => 0, align => 1, text => "Effects Volume");
288     # $table->add (1, 8, new CFClient::UI::Slider range => [$CFG->{effects_volume}, 0, 128, 1], connect_changed => sub {
289     # $CFG->{effects_volume} = $_[1];
290     # });
291 elmex 1.158 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Background Music");
292     $table->add (1, $row++, my $hbox = new CFClient::UI::HBox);
293 root 1.163 $hbox->add (new CFClient::UI::CheckBox
294     expand => 1, state => $CFG->{bgm_enable},
295     tooltip => "Enable background music playing",
296     connect_changed => sub {
297     $CFG->{bgm_enable} = $_[1];
298     }
299     );
300     $hbox->add (new CFClient::UI::Slider
301     expand => 1, range => [$CFG->{bgm_volume}, 0, 1, 0.1],
302     tooltip => "The volume of the background music",
303     connect_changed => sub {
304     $CFG->{bgm_volume} = $_[1];
305     CFClient::MixMusic::volume $_[1] * 128;
306     }
307     );
308 root 1.140
309 root 1.163 $table->add (1, $row++, new CFClient::UI::Button
310     expand => 1, align => 0, text => "Apply",
311 root 1.168 tooltip => "Apply the audio settings",
312 root 1.163 connect_activate => sub {
313     audio_shutdown ();
314     audio_init ();
315     }
316     );
317 elmex 1.137
318 root 1.111 $dialog
319     }
320    
321 elmex 1.157 sub set_stats_window_fontsize {
322 elmex 1.158 for (values %{$STATWIDS}) {
323 elmex 1.157 $_->set_fontsize ($::CFG->{stat_fontsize});
324     }
325     }
326    
327 elmex 1.158 sub set_gauge_window_fontsize {
328     for (map { $GAUGES->{$_} } grep { $_ ne 'win' } keys %{$GAUGES}) {
329     $_->set_fontsize ($::CFG->{gauge_fontsize});
330     }
331 root 1.169
332     # local $GAUGES->{win}{parent};#d#
333     # use PApp::Util; open D, ">:utf8", "d"; print D PApp::Util::dumpval $GAUGES->{win}; close D;
334 elmex 1.158 }
335    
336     sub make_gauge_window {
337     my $gh = int ($HEIGHT * $CFG->{gauge_size});
338 elmex 1.161 # my $gw = int ($WIDTH * $CFG->{gauge_w_size});
339 elmex 1.158
340     my $win = new CFClient::UI::Frame (
341 root 1.169 y => $HEIGHT - $gh, x => 0, user_w => $WIDTH, user_h => $gh
342 elmex 1.158 );
343 root 1.173 $win->add (my $hbox = new CFClient::UI::HBox
344     children => [
345     (new CFClient::UI::HBox expand => 1),
346     ($FLOORBOX = new CFClient::UI::VBox),
347     (my $vbox = new CFClient::UI::VBox),
348     ],
349     );
350 elmex 1.158
351 root 1.173 $vbox->add (new CFClient::UI::HBox
352     expand => 1,
353     children => [
354     (new CFClient::UI::Empty expand => 1),
355     (my $hb = new CFClient::UI::HBox),
356     ],
357     );
358 elmex 1.161
359 root 1.172 $hb->add (my $hg = new CFClient::UI::Gauge type => 'hp',
360     tooltip => "Health points - depletes when you get wounded, refills when you heal or idle");
361     $hb->add (my $mg = new CFClient::UI::Gauge type => 'mana',
362     tooltip => "Spell points - deplete when you cast wizard spells, refills when you idle");
363     $hb->add (my $gg = new CFClient::UI::Gauge type => 'grace',
364     tooltip => "Grace points - deplete when you cast priest spells, refills when you pray");
365     $hb->add (my $fg = new CFClient::UI::Gauge type => 'food',
366     tooltip => "Food - depletes with time, faster when you heal or build mana, refills when you eat healthy food");
367    
368 root 1.173 $vbox->add (my $exp = new CFClient::UI::Label valign => 0, align => 1, can_hover => 1, can_events => 1,
369 root 1.172 tooltip => "Experience points and level - increases when you kill monsters or successfully use skills");
370 root 1.173 $vbox->add (my $rng = new CFClient::UI::Label valign => 0, align => 1, can_hover => 1, can_events => 1,
371 root 1.172 tooltip => "Ranged attack - how you attack when you press shift-cursor (spell, skill, weapon etc.)");
372 elmex 1.158
373     $GAUGES = {
374 elmex 1.166 exp => $exp, win => $win, range => $rng,
375 elmex 1.158 food => $fg, mana => $mg, hp => $hg, grace => $gg
376     };
377 root 1.169
378     &set_gauge_window_fontsize;
379    
380 elmex 1.158 $win
381     }
382    
383 elmex 1.154 sub make_stats_window {
384 elmex 1.156 my $tgw = new CFClient::UI::FancyFrame (x => $WIDTH * 2/5, y => 0, title => "Stats");
385 root 1.155
386     $tgw->add (my $vb = new CFClient::UI::VBox);
387 root 1.168 $vb->add ($STATWIDS->{title} = new CFClient::UI::Label valign => 0, align => -1, text => "Title:", expand => 1);
388     $vb->add ($STATWIDS->{map} = new CFClient::UI::Label valign => 0, align => -1, text => "Map:", expand => 1);
389 elmex 1.156
390     $vb->add (my $hb = new CFClient::UI::HBox expand => 1);
391    
392     $hb->add (my $tbl = new CFClient::UI::Table expand => 1);
393    
394 root 1.174 my $black = [0, 0, 0];
395    
396 root 1.168 $tbl->add (0, 0, $STATWIDS->{st_str} = new CFClient::UI::Label valign => 0, align => +1, template => "30");
397     $tbl->add (0, 1, $STATWIDS->{st_dex} = new CFClient::UI::Label valign => 0, align => +1, template => "30");
398     $tbl->add (0, 2, $STATWIDS->{st_con} = new CFClient::UI::Label valign => 0, align => +1, template => "30");
399     $tbl->add (0, 3, $STATWIDS->{st_int} = new CFClient::UI::Label valign => 0, align => +1, template => "30");
400     $tbl->add (0, 4, $STATWIDS->{st_wis} = new CFClient::UI::Label valign => 0, align => +1, template => "30");
401     $tbl->add (0, 5, $STATWIDS->{st_pow} = new CFClient::UI::Label valign => 0, align => +1, template => "30");
402     $tbl->add (0, 6, $STATWIDS->{st_cha} = new CFClient::UI::Label valign => 0, align => +1, template => "30");
403    
404 root 1.174 $tbl->add (1, 0, $STATWIDS->{st_str_lbl} = new CFClient::UI::Label fg => $black, valign => 0, align => -1, text => "Str");
405     $tbl->add (1, 1, $STATWIDS->{st_dex_lbl} = new CFClient::UI::Label fg => $black, valign => 0, align => -1, text => "Dex");
406     $tbl->add (1, 2, $STATWIDS->{st_con_lbl} = new CFClient::UI::Label fg => $black, valign => 0, align => -1, text => "Con");
407     $tbl->add (1, 3, $STATWIDS->{st_int_lbl} = new CFClient::UI::Label fg => $black, valign => 0, align => -1, text => "Int");
408     $tbl->add (1, 4, $STATWIDS->{st_wis_lbl} = new CFClient::UI::Label fg => $black, valign => 0, align => -1, text => "Wis");
409     $tbl->add (1, 5, $STATWIDS->{st_pow_lbl} = new CFClient::UI::Label fg => $black, valign => 0, align => -1, text => "Pow");
410     $tbl->add (1, 6, $STATWIDS->{st_cha_lbl} = new CFClient::UI::Label fg => $black, valign => 0, align => -1, text => "Cha");
411 root 1.168
412     $tbl->add (2, 0, $STATWIDS->{st_wc} = new CFClient::UI::Label valign => 0, align => +1, template => "-120");
413     $tbl->add (2, 1, $STATWIDS->{st_ac} = new CFClient::UI::Label valign => 0, align => +1, template => "-120");
414     $tbl->add (2, 2, $STATWIDS->{st_dam} = new CFClient::UI::Label valign => 0, align => +1, template => "120");
415     $tbl->add (2, 3, $STATWIDS->{st_arm} = new CFClient::UI::Label valign => 0, align => +1, template => "120");
416     $tbl->add (2, 4, $STATWIDS->{st_spd} = new CFClient::UI::Label valign => 0, align => +1, template => "10.54");
417     $tbl->add (2, 5, $STATWIDS->{st_wspd} = new CFClient::UI::Label valign => 0, align => +1, template => "9");
418    
419 root 1.174 $tbl->add (3, 0, $STATWIDS->{st_wc_lbl} = new CFClient::UI::Label fg => $black, valign => 0, align => -1, text => "Wc");
420     $tbl->add (3, 1, $STATWIDS->{st_ac_lbl} = new CFClient::UI::Label fg => $black, valign => 0, align => -1, text => "Ac");
421     $tbl->add (3, 2, $STATWIDS->{st_dam_lbl} = new CFClient::UI::Label fg => $black, valign => 0, align => -1, text => "Dam");
422     $tbl->add (3, 3, $STATWIDS->{st_arm_lbl} = new CFClient::UI::Label fg => $black, valign => 0, align => -1, text => "Arm");
423     $tbl->add (3, 4, $STATWIDS->{st_spd_lbl} = new CFClient::UI::Label fg => $black, valign => 0, align => -1, text => "Sp");
424     $tbl->add (3, 5, $STATWIDS->{st_wspd_lbl} = new CFClient::UI::Label fg => $black, valign => 0, align => -1, text => "WSp");
425 root 1.155
426 elmex 1.158 $hb->add (my $tbl2 = new CFClient::UI::Table expand => 1);
427 root 1.155
428 elmex 1.156 my $row = 0;
429     my $col = 0;
430 root 1.155
431 elmex 1.166 my %resist_names = (
432     slow => "Slow",
433     holyw => "Holy Word",
434     conf => "Confusion",
435     fire => "Fire",
436     depl => "Depletion",
437     magic => "Magic",
438     drain => "Draining",
439     acid => "Acid",
440     pois => "Poison",
441     para => "Paralysation",
442     deat => "Death",
443     phys => "Physical",
444     blind => "Blind",
445     fear => "Fear",
446     tund => "Turn undead",
447     elec => "Electricity",
448     cold => "Cold",
449     ghit => "Ghost hit",
450     );
451 elmex 1.156 for (qw/slow holyw conf fire depl magic
452     drain acid pois para deat phys
453     blind fear tund elec cold ghit/)
454     {
455 root 1.164 $tbl2->add ($col, $row,
456 elmex 1.156 $STATWIDS->{"res_$_"} =
457 root 1.168 new CFClient::UI::Label
458     template => "-100%",
459     align => +1,
460     valign => 0,
461     tooltip => $resist_names{$_}
462     );
463     $tbl2->add ($col + 1, $row, new CFClient::UI::Image
464     can_hover => 1,
465     can_events => 1,
466     image => "ui/resist/resist_$_.png",
467     tooltip => $resist_names{$_}
468 elmex 1.156 );
469    
470     $row++;
471     if ($row % 6 == 0) {
472     $col += 2;
473     $row = 0;
474     }
475     }
476    
477 elmex 1.157 &set_stats_window_fontsize;
478 elmex 1.156 update_stats_window ({});
479 root 1.155
480 elmex 1.154 $tgw
481     }
482    
483 root 1.169 sub formsep {
484     reverse join ",", grep length, split /(...)/, reverse $_[0] * 1
485     }
486    
487 elmex 1.154 sub update_stats_window {
488     my ($stats) = @_;
489    
490 elmex 1.156 # i love text protocols!!!
491 root 1.169 my $hp = $stats->{Crossfire::Protocol::CS_STAT_HP} * 1;
492     my $hp_m = $stats->{Crossfire::Protocol::CS_STAT_MAXHP} * 1;
493     my $sp = $stats->{Crossfire::Protocol::CS_STAT_SP} * 1;
494     my $sp_m = $stats->{Crossfire::Protocol::CS_STAT_MAXSP} * 1;
495     my $fo = $stats->{Crossfire::Protocol::CS_STAT_FOOD} * 1;
496 elmex 1.156 my $fo_m = 999;
497 root 1.169 my $gr = $stats->{Crossfire::Protocol::CS_STAT_GRACE} * 1;
498     my $gr_m = $stats->{Crossfire::Protocol::CS_STAT_MAXGRACE} * 1;
499 elmex 1.156
500     $GAUGES->{hp} ->set_value ($hp, $hp_m);
501     $GAUGES->{mana} ->set_value ($sp, $sp_m);
502     $GAUGES->{food} ->set_value ($fo, $fo_m);
503     $GAUGES->{grace} ->set_value ($gr, $gr_m);
504 root 1.169 $GAUGES->{exp} ->set_text ("Exp: " . (formsep $stats->{Crossfire::Protocol::CS_STAT_EXP64})
505     . " (lvl " . ($stats->{Crossfire::Protocol::CS_STAT_LEVEL} * 1) . ")");
506     my $rng = $stats->{Crossfire::Protocol::CS_STAT_RANGE};
507 elmex 1.159 $rng =~ s/^Range: //; # thank you so much dear server
508     $GAUGES->{range} ->set_text ("Rng: " . $rng);
509 root 1.169 my $title = $stats->{Crossfire::Protocol::CS_STAT_TITLE};
510 elmex 1.165 $title =~ s/^Player: //;
511     $STATWIDS->{title} ->set_text ("Title: " . $title);
512 elmex 1.156
513 root 1.169 $STATWIDS->{st_str} ->set_text (sprintf "%d", $stats->{5});
514     $STATWIDS->{st_dex} ->set_text (sprintf "%d", $stats->{8});
515     $STATWIDS->{st_con} ->set_text (sprintf "%d", $stats->{9});
516     $STATWIDS->{st_int} ->set_text (sprintf "%d", $stats->{6});
517     $STATWIDS->{st_wis} ->set_text (sprintf "%d", $stats->{7});
518     $STATWIDS->{st_pow} ->set_text (sprintf "%d", $stats->{22});
519     $STATWIDS->{st_cha} ->set_text (sprintf "%d", $stats->{10});
520     $STATWIDS->{st_wc} ->set_text (sprintf "%d", $stats->{13});
521     $STATWIDS->{st_ac} ->set_text (sprintf "%d", $stats->{14});
522     $STATWIDS->{st_dam} ->set_text (sprintf "%d", $stats->{15});
523     $STATWIDS->{st_arm} ->set_text (sprintf "%d", $stats->{16});
524     $STATWIDS->{st_spd} ->set_text (sprintf "%.1f", $stats->{Crossfire::Protocol::CS_STAT_SPEED});
525     $STATWIDS->{st_wspd}->set_text (sprintf "%.1f", $stats->{Crossfire::Protocol::CS_STAT_WEAP_SP});
526 elmex 1.156
527     my %tbl = (
528     phys => 100,
529     magic => 101,
530     fire => 102,
531     elec => 103,
532     cold => 104,
533     conf => 105,
534     acid => 106,
535     drain => 107,
536     ghit => 108,
537     pois => 109,
538     slow => 110,
539     para => 111,
540     tund => 112,
541     fear => 113,
542 elmex 1.165 depl => 113,
543 elmex 1.156 deat => 115,
544     holyw => 116,
545     blind => 117
546 elmex 1.154 );
547 elmex 1.156
548     for (keys %tbl) {
549     $STATWIDS->{"res_$_"}->set_text (sprintf "%d%", $stats->{$tbl{$_}});
550     }
551    
552 elmex 1.154 }
553    
554 root 1.112 sub metaserver_dialog {
555     my $dialog = new CFClient::UI::FancyFrame
556 root 1.150 title => "Metaserver",
557 root 1.112 child => (my $vbox = new CFClient::UI::VBox);
558    
559     $vbox->add ($dialog->{table} = new CFClient::UI::Table);
560    
561     $dialog
562     }
563    
564 root 1.179 my $METASERVER_ATIME;
565    
566 root 1.112 sub update_metaserver {
567 root 1.114 my ($HOST) = @_;
568    
569 root 1.179 return if $METASERVER_ATIME > time;
570     $METASERVER_ATIME = time + 60;
571    
572 root 1.178 my $table = $METASERVER->{table};
573     $table->clear;
574 root 1.179 $table->add (0, 0, my $label = new CFClient::UI::Label max_w => $WIDTH * 0.8, text => "fetching server list...");
575 root 1.112
576     my $buf;
577    
578     my $fh = new IO::Socket::INET PeerHost => $META_SERVER, Blocking => 0;
579    
580 root 1.178 unless ($fh) {
581     $label->set_text ("unable to contact metaserver: $!");
582     return;
583     }
584    
585 root 1.112 Event->io (fd => $fh, poll => 'r', cb => sub {
586     my $res = sysread $fh, $buf, 8192, length $buf;
587    
588     if (!defined $res) {
589     $_[0]->w->cancel;
590 root 1.178 $label->set_text ("error while retrieving server list: $!");
591 root 1.112 } elsif ($res == 0) {
592     $_[0]->w->cancel;
593     status "server list retrieved";
594 root 1.113
595 root 1.178 utf8::decode $buf if utf8::valid $buf;
596 root 1.113
597     $table->clear;
598    
599 root 1.114 my @col = qw(Use #Users Host Uptime Version Description);
600 root 1.113 $table->add ($_, 0, new CFClient::UI::Label align => 0, fg => [1, 1, 0], text => $col[$_])
601     for 0 .. $#col;
602    
603     my @align = qw(1 0 1 1 -1);
604    
605     my $y = 0;
606 root 1.114 for my $m (sort { $b->[3] <=> $a->[3] } map [split /\|/], split /\015?\012/, $buf) {
607 root 1.113 my ($ip, $last, $host, $users, $version, $desc, $ibytes, $obytes, $uptime) = @$m;
608    
609     for ($desc) {
610     s/<br>/\n/gi;
611     s/<li>/\n· /gi;
612     s/<.*?>//sgi;
613     s/&/&amp;/g;
614     s/</&lt;/g;
615     s/>/&gt;/g;
616     }
617    
618     $uptime = sprintf "%dd %02d:%02d:%02d",
619     (int $m->[8] / 86400),
620     (int $m->[8] / 3600) % 24,
621     (int $m->[8] / 60) % 60,
622     $m->[8] % 60;
623    
624     $m = [$users, $host, $uptime, $version, $desc];
625    
626     $y++;
627 root 1.114
628     $table->add (0, $y, new CFClient::UI::VBox children => [
629 root 1.178 (new CFClient::UI::Button text => "Use", connect_activate => sub {
630 root 1.114 $HOST->set_text ($CFG->{host} = $host);
631     }),
632     (new CFClient::UI::Empty expand => 1),
633     ]);
634    
635 root 1.140 $table->add ($_ + 1, $y, new CFClient::UI::Label align => $align[$_], text => $m->[$_], fontsize => 0.8)
636 root 1.113 for 0 .. $#$m;
637     }
638 root 1.112 }
639     });
640     }
641    
642 root 1.111 sub server_setup {
643     my $dialog = new CFClient::UI::FancyFrame
644 root 1.150 title => "Server Setup",
645 root 1.111 child => (my $vbox = new CFClient::UI::VBox);
646 root 1.81
647 root 1.82 $vbox->add (my $table = new CFClient::UI::Table expand => 1, col_expand => [0, 1]);
648 root 1.141 $table->add (0, 2, new CFClient::UI::Label valign => 0, align => 1, text => "Host:Port");
649 root 1.112
650     {
651     $table->add (1, 2, my $vbox = new CFClient::UI::VBox);
652    
653 elmex 1.166 $vbox->add (
654     my $HOST = new CFClient::UI::Entry
655     expand => 1,
656     text => $CFG->{host},
657     tooltip => "The hostname or ip address of the Crossfire(+) server to connect to",
658     connect_changed => sub {
659     my ($self, $value) = @_;
660     $CFG->{host} = $value;
661     }
662     );
663 root 1.112
664     $METASERVER = metaserver_dialog;
665 elmex 1.101
666 elmex 1.166 $vbox->add (new CFClient::UI::Flopper
667     expand => 1,
668     text => "Metaserver",
669     other => $METASERVER,
670 root 1.167 tooltip => "Show a list of avaible crossfire servers",
671 elmex 1.166 connect_open => sub {
672     update_metaserver $HOST;
673     }
674     );
675 root 1.112 }
676 root 1.81
677 root 1.141 $table->add (0, 4, new CFClient::UI::Label valign => 0, align => 1, text => "Username");
678 elmex 1.166 $table->add (1, 4, new CFClient::UI::Entry
679     text => $CFG->{user},
680     tooltip => "The name of your character on the server",
681     connect_changed => sub {
682     my ($self, $value) = @_;
683     $CFG->{user} = $value;
684     }
685     );
686 root 1.81
687 root 1.141 $table->add (0, 5, new CFClient::UI::Label valign => 0, align => 1, text => "Password");
688 elmex 1.166 $table->add (1, 5, new CFClient::UI::Entry
689     text => $CFG->{password},
690     hidden => 1,
691     tooltip => "The password for your character",
692     connect_changed => sub {
693     my ($self, $value) = @_;
694     $CFG->{password} = $value;
695     }
696     );
697 elmex 1.101
698 root 1.141 $table->add (0, 6, new CFClient::UI::Label valign => 0, align => 1, text => "Def. say cmd");
699 elmex 1.166 $table->add (1, 6, my $saycmd = new CFClient::UI::Entry
700     text => $CFG->{say_command},
701     tooltip => "This is the command that will be used if you write a line in the message window entry. "
702     ."Usually you want to enter something like 'say' or 'shout' or 'gsay' here. "
703 root 1.173 ."But you could also set it to 'tell &lt;playername&gt;' to only chat with that user.",
704 elmex 1.166 connect_changed => sub {
705     my ($self, $value) = @_;
706     $CFG->{say_command} = $value;
707     }
708     );
709 root 1.81
710 root 1.141 $table->add (0, 7, new CFClient::UI::Label valign => 0, align => 1, text => "Map Size");
711 elmex 1.101 $table->add (1, 7, new CFClient::UI::Slider
712 root 1.81 req_w => 100,
713     range => [$CFG->{mapsize}, 10, 100 + 1, 1],
714 elmex 1.166 tooltip => "This is the size of the portion of the map update the server sends you. "
715     ."If you set this to a high value you will be able to see further for example.",
716 root 1.81 connect_changed => sub {
717     my ($self, $value) = @_;
718    
719     $CFG->{mapsize} = $self->{range}[0] = $value = int $value;
720     },
721     );
722    
723 elmex 1.101 $table->add (1, 8, new CFClient::UI::Button expand => 1, align => 0, text => "Login", connect_activate => sub {
724 root 1.84 start_game;
725 root 1.82 });
726    
727 root 1.98 $dialog
728 root 1.81 }
729 root 1.58
730 root 1.111 sub message_window {
731 root 1.99 my $window = new CFClient::UI::FancyFrame
732 root 1.150 title => "Messages",
733 root 1.99 border_bg => [1, 1, 1, 0.5],
734     bg => [0.3, 0.3, 0.3, 0.8],
735 root 1.124 user_w => int $::WIDTH / 3,
736     user_h => int $::HEIGHT / 5,
737 root 1.99 child => (my $vbox = new CFClient::UI::VBox);
738    
739 root 1.105 $vbox->add ($LOGVIEW = new CFClient::UI::TextView
740     expand => 1,
741 root 1.168 font => $FONT_FIXED,
742 root 1.105 fontsize => $::CFG->{log_fontsize},
743     );
744    
745 root 1.122 $vbox->add (my $input = new CFClient::UI::Entry
746 elmex 1.118 connect_focus_in => sub {
747     my ($input, $prev_focus) = @_;
748    
749     delete $input->{refocus_map};
750    
751     if ($prev_focus == $MAPWIDGET && $input->{auto_activated}) {
752     $input->{refocus_map} = 1;
753     }
754     delete $input->{auto_activated};
755     },
756 root 1.116 connect_activate => sub {
757 elmex 1.100 my ($input, $text) = @_;
758     $input->set_text ('');
759    
760     if ($text =~ /^\/(.*)/) {
761 root 1.123 $::CONN->user_send ($1);
762 elmex 1.100 } else {
763 elmex 1.101 my $say_cmd = $::CFG->{say_command} || 'say';
764 root 1.123 $::CONN->user_send ("$say_cmd $text");
765 elmex 1.100 }
766 elmex 1.118 if ($input->{refocus_map}) {
767     delete $input->{refocus_map};
768     $MAPWIDGET->focus_in
769     }
770 root 1.116 },
771     connect_escape => sub {
772 elmex 1.102 $MAPWIDGET->focus_in
773 root 1.116 },
774     );
775 elmex 1.102
776     $CONSOLE = {
777     window => $window,
778     input => $input
779     };
780 root 1.99
781     $window
782     }
783    
784 root 1.89 sub sdl_init {
785 root 1.145 CFClient::SDL_Init
786 root 1.89 and die "SDL::Init failed!\n";
787     }
788    
789 root 1.134 sub video_init {
790 root 1.89 sdl_init;
791    
792 root 1.84 ($WIDTH, $HEIGHT) = @{ $SDL_MODES[$CFG->{sdl_mode}] };
793     $FULLSCREEN = $CFG->{fullscreen};
794 root 1.89 $FAST = $CFG->{fast};
795 root 1.84
796 root 1.145 CFClient::SDL_SetVideoMode $WIDTH, $HEIGHT, $FULLSCREEN
797     or die "SDL_SetVideoMode failed!\n";
798 root 1.2
799 root 1.86 $SDL_ACTIVE = 1;
800    
801 root 1.87 $LAST_REFRESH = time - 0.01;
802 root 1.45
803 root 1.67 CFClient::gl_init;
804 root 1.30
805 root 1.140 $FONTSIZE = int $HEIGHT / 40 * $CFG->{gui_fontsize};
806 root 1.39
807 root 1.52 #############################################################################
808    
809 root 1.99 $DEBUG_STATUS = new CFClient::UI::Label padding => 0, z => 100;
810 root 1.140 $DEBUG_STATUS->show;
811 root 1.52
812 root 1.72 $STATUS_LINE = new CFClient::UI::Label
813 root 1.77 padding => 0,
814 root 1.140 y => $HEIGHT - $FONTSIZE * 1.8;
815     $STATUS_LINE->show;
816 root 1.51
817 root 1.72 $ALT_ENTER_MESSAGE = new CFClient::UI::Label
818 root 1.123 padding => 0,
819 root 1.140 fontsize => 0.8,
820 root 1.123 markup => "Use <b>Alt-Enter</b> to toggle fullscreen mode";
821 root 1.140 $ALT_ENTER_MESSAGE->show;
822     $ALT_ENTER_MESSAGE->move (0, $HEIGHT - $ALT_ENTER_MESSAGE->{h});
823 root 1.30
824 root 1.141 $CFClient::UI::ROOT->add ($MAPWIDGET = new CFClient::MapWidget);
825 root 1.69 $MAPWIDGET->focus_in;
826 elmex 1.102 $MAPWIDGET->connect (activate_console => sub {
827 elmex 1.103 my ($mapwidget, $preset) = @_;
828    
829 elmex 1.102 if ($CONSOLE) {
830 elmex 1.118 $CONSOLE->{input}->{auto_activated} = 1;
831 elmex 1.102 $CONSOLE->{input}->focus_in;
832 elmex 1.103
833     if ($preset && $CONSOLE->{input}->get_text eq '') {
834     $CONSOLE->{input}->set_text ($preset);
835     }
836 elmex 1.102 }
837     });
838 root 1.81
839 root 1.111 $CFClient::UI::ROOT->add ($BUTTONBAR = new CFClient::UI::HBox);
840    
841     $BUTTONBAR->add (new CFClient::UI::Flopper text => "Client Setup", other => client_setup);
842     $BUTTONBAR->add (new CFClient::UI::Flopper text => "Server Setup", other => server_setup);
843     $BUTTONBAR->add (new CFClient::UI::Flopper text => "Message Window", other => message_window);
844    
845 root 1.167 $CFClient::UI::ROOT->add (make_gauge_window); # XXX: this has to be set before make_stats_window as make_stats_window calls update_stats_window which updated the gauges also X-D
846     $BUTTONBAR->add (new CFClient::UI::Flopper text => "Stats Window", other => make_stats_window);
847    
848 root 1.111 $BUTTONBAR->add (new CFClient::UI::Button text => "Save Config", connect_activate => sub {
849     CFClient::write_cfg "$Crossfire::VARDIR/pclientrc";
850     status "Configuration Saved";
851     });
852 root 1.98
853 root 1.119 $BUTTONBAR->{children}[1]->emit ("activate"); # pop up server setup
854 root 1.2 }
855    
856 root 1.134 sub video_shutdown {
857 root 1.111 $CFClient::UI::ROOT->{children} = [];
858 root 1.177 undef $CFClient::UI::GRAB;
859     undef $CFClient::UI::HOVER;
860 root 1.86 undef $SDL_ACTIVE;
861 root 1.134 }
862    
863 root 1.153 my @bgmusic = qw(game1.ogg game2.ogg game3.ogg game5.ogg game6.ogg ross1.ogg ross2.ogg ross3.ogg ross4.ogg ross5.ogg); #d#
864 root 1.135 my $bgmusic;#TODO#hack#d#
865    
866 root 1.153 sub audio_music_finished {
867     return unless $CFG->{bgm_enable};
868    
869     # TODO: hack, do play loop and mood music
870     $bgmusic = new_from_file CFClient::MixMusic CFClient::find_rcfile "music/$bgmusic[0]";
871     $bgmusic->play (0);
872    
873     push @bgmusic, shift @bgmusic;
874     }
875    
876 root 1.134 sub audio_init {
877 root 1.139 if ($CFG->{audio_enable}) {
878 root 1.134 if (open my $fh, "<:utf8", CFClient::find_rcfile "sounds/config") {
879 root 1.146 $SDL_MIXER = !CFClient::Mix_OpenAudio;
880     CFClient::Mix_AllocateChannels 8;
881 root 1.149 CFClient::MixMusic::volume $CFG->{bgm_volume} * 128;
882 root 1.134
883 root 1.153 audio_music_finished;
884 root 1.135
885 root 1.134 while (<$fh>) {
886     next if /^\s*#/;
887     next if /^\s*$/;
888    
889     my ($file, $volume, $event) = split /\s+/, $_, 3;
890    
891     push @SOUNDS, "$volume,$file";
892    
893     $AUDIO_CHUNKS{"$volume,$file"} ||= do {
894 root 1.146 my $chunk = new_from_file CFClient::MixChunk CFClient::find_rcfile "sounds/$file";
895 root 1.134 $chunk->volume ($volume * 128 / 100);
896     $chunk
897     };
898     }
899     } else {
900     status "unable to open sound config: $!";
901     }
902     }
903     }
904    
905     sub audio_shutdown {
906 root 1.146 CFClient::Mix_CloseAudio if $SDL_MIXER;
907 root 1.134 undef $SDL_MIXER;
908     @SOUNDS = ();
909     %AUDIO_CHUNKS = ();
910 root 1.62 }
911    
912 root 1.87 my %animate_object;
913     my $animate_timer;
914    
915     my $want_refresh;
916     my $can_refresh;
917    
918     my $fps = 9;
919    
920 root 1.30 sub force_refresh {
921 root 1.87 $fps = $fps * 0.95 + 1 / ($NOW - $LAST_REFRESH) * 0.05;
922     debug sprintf "%3.2f", $fps;
923    
924 root 1.96 $want_refresh = 0;
925 root 1.87 $can_refresh = 0;
926    
927 root 1.111 $CFClient::UI::ROOT->draw;
928 root 1.1
929 root 1.148 CFClient::SDL_GL_SwapBuffers;
930 root 1.87
931     $LAST_REFRESH = $NOW;
932 root 1.1 }
933    
934 root 1.87 my $refresh_watcher = Event->timer (after => 0, hard => 1, interval => 1 / $MAX_FPS, cb => sub {
935     $NOW = time;
936    
937 root 1.147 ($SDL_CB{$_->{type}} || sub { warn "unhandled event $_->{type}" })->($_)
938     for CFClient::SDL_PollEvent;
939 root 1.87
940     if (%animate_object) {
941     $_->animate ($LAST_REFRESH - $NOW) for values %animate_object;
942     $want_refresh++;
943     }
944    
945     if ($want_refresh) {
946     force_refresh;
947     } else {
948     $can_refresh = 1;
949     }
950     });
951 root 1.64
952 root 1.30 sub refresh {
953 root 1.87 $want_refresh++;
954 root 1.30 }
955    
956 root 1.45 sub animation_start {
957     my ($widget) = @_;
958 root 1.87 $animate_object{$widget} = $widget;
959 root 1.45 }
960    
961     sub animation_stop {
962     my ($widget) = @_;
963 root 1.87 delete $animate_object{$widget};
964 root 1.45 }
965    
966 root 1.2 @conn::ISA = Crossfire::Protocol::;
967 root 1.1
968 elmex 1.125 sub conn::stats_update {
969     my ($self, $stats) = @_;
970    
971 elmex 1.154 update_stats_window ($stats);
972 elmex 1.125 }
973    
974 root 1.89 sub conn::user_send {
975 root 1.88 my ($self, $command) = @_;
976    
977 root 1.123 $self->send_command ($command);
978 root 1.88 status $command;
979     }
980    
981 root 1.119 sub conn::map_scroll {
982     my ($self, $dx, $dy) = @_;
983    
984     $MAP->scroll ($dx, $dy);
985     }
986    
987 root 1.94 sub conn::feed_map1a {
988     my ($self, $data) = @_;
989    
990 root 1.95 # $self->Crossfire::Protocol::feed_map1a ($data);
991 root 1.1
992 root 1.95 $MAP->map1a_update ($data);
993 root 1.69 $MAPWIDGET->update;
994 root 1.1 }
995    
996 root 1.116 sub conn::flush_map {
997     my ($self) = @_;
998    
999     my $map_info = delete $self->{map_info}
1000     or return;
1001    
1002     my ($hash, $x, $y, $w, $h) = @$map_info;
1003    
1004     my $data = $MAP->get_rect ($x, $y, $w, $h);
1005     $MAPCACHE->put ($hash => Compress::LZF::compress $data);
1006 root 1.152 #warn sprintf "SAVEmap[%s] length %d\n", $hash, length $data;#d#
1007 root 1.116 }
1008 root 1.1
1009 root 1.2 sub conn::map_clear {
1010 root 1.1 my ($self) = @_;
1011    
1012 root 1.116 $self->flush_map;
1013 root 1.150 delete $self->{neigh_map};
1014 root 1.116
1015 root 1.95 $MAP->clear;
1016 root 1.1 }
1017    
1018 root 1.116
1019 root 1.119 sub conn::load_map($$$) {
1020     my ($self, $hash, $x, $y) = @_;
1021 root 1.115
1022 root 1.116 if (defined (my $data = $MAPCACHE->get ($hash))) {
1023     $data = Compress::LZF::decompress $data;
1024 root 1.152 #warn sprintf "LOADmap[%s,%d,%d] length %d\n", $hash, $x, $y, length $data;#d#
1025 root 1.116 for my $id ($MAP->set_rect ($x, $y, $data)) {
1026     my $data = $TILECACHE->get ($id)
1027     or next;
1028    
1029     $self->set_texture ($id => $data);
1030     }
1031     }
1032 root 1.115 }
1033    
1034 root 1.152 # this method does a "flood fill" into every tile direction
1035     # it assumes that tiles are arranged in a rectangular grid,
1036     # i.e. a map is the same as the left of the right map etc.
1037     # failure to comply are harmless and result in display errors
1038     # at worst.
1039 root 1.119 sub conn::flood_fill {
1040 root 1.150 my ($self, $gx, $gy, $path, $hash, $flags) = @_;
1041 root 1.119
1042 root 1.121 # the server does not allow map paths > 6
1043 root 1.120 return if 6 <= length $path;
1044    
1045 root 1.150 my ($x0, $y0, $x1, $y1) = @{$self->{neigh_rect}};
1046    
1047     for (
1048     [1, 0, -1],
1049     [2, 1, 0],
1050     [3, 0, 1],
1051     [4, -1, 0],
1052     ) {
1053     my ($tile, $dx, $dy) = @$_;
1054    
1055     my $gx = $gx + $dx;
1056     my $gy = $gy + $dy;
1057    
1058 root 1.119 next unless $flags & (1 << ($tile - 1));
1059 root 1.150 next if $self->{neigh_grid}{$gx, $gy}++;
1060 root 1.119
1061 root 1.150 my $neigh = $self->{neigh_map}{$hash} ||= [];
1062     if (my $info = $neigh->[$tile]) {
1063     my ($flags, $x, $y, $w, $h, $hash) = @$info;
1064 root 1.119
1065 root 1.150 $self->flood_fill ($gx, $gy, "$path$tile", $hash, $flags)
1066     if $x >= $x0 && $x + $w < $x1 && $y >= $y0 && $y + $h < $y1;
1067    
1068     } else {
1069     $self->send_mapinfo ("spatial $path$tile", sub {
1070     my ($mode, $flags, $x, $y, $w, $h, $hash) = @_;
1071 root 1.119
1072 root 1.150 return if $mode ne "spatial";
1073 root 1.119
1074 root 1.150 $x += $MAP->ox;
1075     $y += $MAP->oy;
1076    
1077     $self->load_map ($hash, $x, $y)
1078     unless $self->{neigh_map}{$hash}[5]++;#d#
1079 root 1.119
1080 root 1.150 $neigh->[$tile] = [$flags, $x, $y, $w, $h, $hash];
1081 root 1.119
1082 root 1.150 $self->flood_fill ($gx, $gy, "$path$tile", $hash, $flags)
1083     if $x >= $x0 && $x + $w < $x1 && $y >= $y0 && $y + $h < $y1;
1084     });
1085     }
1086 root 1.119 }
1087     }
1088    
1089     sub conn::map_change {
1090     my ($self, $mode, $flags, $x, $y, $w, $h, $hash) = @_;
1091    
1092     $self->flush_map;
1093    
1094     my ($ox, $oy) = ($::MAP->ox, $::MAP->oy);
1095    
1096     my $mapmapw = 250;
1097     my $mapmaph = 250;
1098 root 1.150
1099     $self->{neigh_rect} = [
1100 root 1.152 $ox - $mapmapw * 0.5, $oy - $mapmapw * 0.5,
1101     $ox + $mapmapw * 0.5 + $w, $oy + $mapmapw * 0.5 + $h,
1102 root 1.150 ];
1103 root 1.119
1104 root 1.150 delete $self->{neigh_grid};
1105     $self->flood_fill (0, 0, "", $hash, $flags);
1106 root 1.119
1107     $x += $ox;
1108     $y += $oy;
1109    
1110     $self->{map_info} = [$hash, $x, $y, $w, $h];
1111    
1112 elmex 1.158 my $map = $self->{map_info}[0];
1113     $map =~ s/^.*?\/([^\/]+)$/\1/;
1114     $STATWIDS->{map}->set_text ("Map: " . $map);
1115 elmex 1.157
1116 root 1.119 $self->load_map ($hash, $x, $y);
1117     }
1118    
1119 root 1.19 sub conn::face_find {
1120 root 1.116 my ($self, $facenum, $face) = @_;
1121    
1122     my $hash = "$face->{chksum},$face->{name}";
1123    
1124     my $id = $FACEMAP->get ($hash);
1125    
1126     unless ($id) {
1127     # create new id for face
1128     # i love transactions
1129     for (1..100) {
1130     my $txn = $CFClient::DB_ENV->txn_begin;
1131     my $status = $FACEMAP->db_get (id => $id, BerkeleyDB::DB_RMW);
1132     if ($status == 0 || $status == BerkeleyDB::DB_NOTFOUND) {
1133     $id++;
1134     if ($FACEMAP->put (id => $id) == 0
1135     && $FACEMAP->put ($hash => $id) == 0) {
1136     $txn->txn_commit;
1137    
1138     goto gotid;
1139     }
1140     }
1141     $txn->abort;
1142     }
1143 root 1.19
1144 root 1.116 CFClient::fatal "maximum number of transaction retries reached - database problems?";
1145     }
1146 root 1.114
1147 root 1.116 gotid:
1148     $face->{id} = $id;
1149     $MAP->set_face ($facenum => $id);
1150 root 1.173 $self->{faceid}[$facenum] = $id;#d#
1151 root 1.116 $TILECACHE->get ($id)
1152 root 1.19 }
1153    
1154 root 1.2 sub conn::face_update {
1155 root 1.95 my ($self, $facenum, $face) = @_;
1156 root 1.19
1157 root 1.116 $TILECACHE->put ($face->{id} => $face->{image}); #TODO: try to avoid duplicate writes
1158    
1159     $self->set_texture ($face->{id} => delete $face->{image});
1160     }
1161 root 1.1
1162 root 1.116 sub conn::set_texture {
1163     my ($self, $id, $data) = @_;
1164 root 1.95
1165 root 1.116 $self->{texture}[$id] ||= do {
1166     my $tex =
1167     new_from_image CFClient::Texture
1168 root 1.173 $data, minify => 1, mipmap => 1;
1169 root 1.116
1170     $MAP->set_texture ($id, @$tex{qw(name w h s t)}, @{$tex->{minified}});
1171     $MAPWIDGET->update;
1172    
1173     $tex
1174     };
1175 root 1.1 }
1176    
1177 root 1.134 sub conn::sound_play {
1178     my ($self, $x, $y, $soundnum, $type) = @_;
1179    
1180 root 1.139 $SDL_MIXER
1181     or return;
1182    
1183 root 1.134 my $chunk = $AUDIO_CHUNKS{$SOUNDS[$soundnum]}
1184     or return;
1185    
1186 root 1.146 $chunk->play;
1187 root 1.143 # warn "sound $x,$y,$soundnum,$type\n";#d#
1188 root 1.134 }
1189    
1190 root 1.170 my $LAST_QUERY; # server is stupid, stupid, stupid
1191    
1192 root 1.33 sub conn::query {
1193     my ($self, $flags, $prompt) = @_;
1194    
1195 root 1.170 $prompt = $LAST_QUERY unless length $prompt;
1196     $LAST_QUERY = $prompt;
1197    
1198     my $dialog = new CFClient::UI::FancyFrame
1199     title => "Query",
1200     child => my $vbox = new CFClient::UI::VBox;
1201    
1202     $vbox->add (new CFClient::UI::Label
1203     max_w => $::WIDTH * 0.4,
1204     text => $prompt);
1205    
1206     if ($flags & Crossfire::Protocol::CS_QUERY_YESNO) {
1207     $vbox->add (my $hbox = new CFClient::HBox);
1208     $hbox->add (new CFClient::Button
1209     text => "No",
1210     connect_activate => sub {
1211     $self->send ("reply n");
1212     $dialog->destroy;
1213     $MAPWIDGET->focus_in;
1214     }
1215     );
1216     $hbox->add (new CFClient::Button
1217     text => "Yes",
1218     connect_activate => sub {
1219     $self->send ("reply y");
1220     $dialog->destroy;
1221     $MAPWIDGET->focus_in;
1222     },
1223     );
1224    
1225     $dialog->focus_in;
1226    
1227     } elsif ($flags & Crossfire::Protocol::CS_QUERY_SINGLECHAR) {
1228     $dialog->{tooltip} = "Press a key (click on the entry to make sure it has keyboard focus)";
1229     $vbox->add (my $entry = new CFClient::UI::Entry
1230     connect_changed => sub {
1231     $self->send ("reply $_[1]");
1232     $dialog->destroy;
1233     $MAPWIDGET->focus_in;
1234     },
1235     );
1236    
1237     $entry->focus_in;
1238    
1239     } else {
1240     $dialog->{tooltip} = "Enter the reply and press return (click on the entry to make sure it has keyboard focus)";
1241    
1242     $vbox->add (my $entry = new CFClient::UI::Entry
1243     $flags & Crossfire::Protocol::CS_QUERY_HIDEINPUT ? (hiddenchar => "*") : (),
1244     connect_activate => sub {
1245     $self->send ("reply $_[1]");
1246     $dialog->destroy;
1247     $MAPWIDGET->focus_in;
1248     },
1249     );
1250    
1251     $entry->focus_in;
1252     }
1253    
1254     $dialog->show;
1255 root 1.33 }
1256    
1257 root 1.99 sub conn::drawinfo {
1258     my ($self, $color, $text) = @_;
1259    
1260     my @color = (
1261     [1.00, 1.00, 1.00], #[0.00, 0.00, 0.00],
1262     [1.00, 1.00, 1.00],
1263 root 1.117 [0.50, 0.50, 1.00], #[0.00, 0.00, 0.55]
1264 root 1.99 [1.00, 0.00, 0.00],
1265     [1.00, 0.54, 0.00],
1266     [0.11, 0.56, 1.00],
1267     [0.93, 0.46, 0.00],
1268     [0.18, 0.54, 0.34],
1269     [0.56, 0.73, 0.56],
1270     [0.80, 0.80, 0.80],
1271     [0.55, 0.41, 0.13],
1272     [0.99, 0.77, 0.26],
1273     [0.74, 0.65, 0.41],
1274     );
1275    
1276     $LOGVIEW->add_paragraph ($color[$color], $text);
1277     }
1278    
1279 root 1.144 sub conn::spell_add {
1280 root 1.143 my ($self, $spell) = @_;
1281    
1282 root 1.171 # TODO
1283     # create a widget dynamically, using spell face (CF::Protocol downloads them)
1284     $MAPWIDGET->add_command ("invoke $spell->{name}", $spell->{message});
1285     $MAPWIDGET->add_command ("cast $spell->{name}", $spell->{message});
1286 root 1.144 }
1287    
1288     sub conn::spell_delete {
1289     my ($self, $spell) = @_;
1290     }
1291    
1292     sub conn::addme_success {
1293     my ($self) = @_;
1294    
1295     for my $skill (values %{$self->{skill_info}}) {
1296 root 1.171 $MAPWIDGET->add_command ("ready_skill $skill", "Ready the skill '$skill'");
1297     $MAPWIDGET->add_command ("use_skill $skill", "Immediately use the skill '$skill'");
1298 root 1.144 }
1299 root 1.143 }
1300    
1301 root 1.173 sub update_floorbox {
1302     $CFClient::UI::ROOT->on_refresh ($FLOORBOX => sub {
1303     $FLOORBOX->clear;
1304     $FLOORBOX->add (new CFClient::UI::Empty expand => 1);
1305    
1306     my @items = values %{ $CONN->{container}{0} };
1307    
1308     # we basically have to use the same sorting as everybody else
1309     @items = sort { $a->{type} <=> $b->{type} } @items;
1310    
1311     for my $item (reverse @items) {
1312     my $desc = $item->{nrof} < 2
1313     ? $item->{name}
1314     : "$item->{nrof} $item->{name_pl}";
1315     # todo: animation widget, face widget, weight(?) etc.
1316     $FLOORBOX->add (my $hbox = new CFClient::UI::HBox
1317     tooltip => (CFClient::UI::Label->escape ($desc)
1318     . "\n<small>leftclick - pick up\nmiddle click - apply\nrightclick - menu</small>"),
1319     can_hover => 1,
1320     can_events => 1,
1321 root 1.174 connect_button_down => sub {
1322     my ($self, $ev, $x, $y) = @_;
1323    
1324     # todo: maybe put examine on 1? but should just be a tooltip :(
1325     if ($ev->{button} == 1) {
1326     $CONN->send ("move $CONN->{player}{tag} $item->{tag} 0");
1327     } elsif ($ev->{button} == 2) {
1328     $CONN->send ("apply $item->{tag}");
1329     } elsif ($ev->{button} == 3) {
1330     # examine, lock, mark, maybe other things
1331     warn "MENU not implemented yet\n";
1332     }
1333    
1334     1
1335     },
1336 root 1.173 );
1337    
1338     $hbox->add (new CFClient::UI::Face
1339 root 1.175 can_events => 0,
1340     face => $item->{face},
1341     anim => $item->{anim},
1342     animspeed => $item->{animspeed},
1343 root 1.173 );
1344    
1345     $hbox->add (new CFClient::UI::Label
1346 root 1.175 can_events => 0,
1347     text => $desc,
1348 root 1.173 );
1349     }
1350     });
1351     refresh;
1352     }
1353    
1354 root 1.169 sub conn::container_add {
1355     my ($self, $id, $items) = @_;
1356    
1357 root 1.173 update_floorbox if $id == 0;
1358 root 1.169 # $self-<{player}{tag} => player inv
1359     #use PApp::Util; warn PApp::Util::dumpval $self->{container}{$self->{player}{tag}};
1360     }
1361    
1362     sub conn::container_clear {
1363     my ($self, $id) = @_;
1364 root 1.173
1365     update_floorbox if $id == 0;
1366 root 1.169 # use PApp::Util; warn PApp::Util::dumpval $self->{container}{0};
1367     }
1368    
1369 root 1.173 sub conn::item_delete {
1370     my ($self, @items) = @_;
1371    
1372     for (@items) {
1373     update_floorbox if $_->{container} == 0;
1374     }
1375     }
1376    
1377     sub conn::item_update {
1378     my ($self, $item) = @_;
1379    
1380     update_floorbox if $item->{container} == 0;
1381     }
1382    
1383 root 1.87 %SDL_CB = (
1384 root 1.145 CFClient::SDL_QUIT => sub {
1385 root 1.87 Event::unloop -1;
1386     },
1387 root 1.145 CFClient::SDL_VIDEORESIZE => sub {
1388 root 1.87 },
1389 root 1.153 CFClient::SDL_VIDEOEXPOSE => \&refresh,
1390     CFClient::SDL_ACTIVEEVENT => sub {
1391     # printf "active %x %x\n", $SDL_EV->active_gain, $SDL_EV->active_state;#d#
1392 root 1.87 },
1393 root 1.145 CFClient::SDL_KEYDOWN => sub {
1394 root 1.147 if ($_[0]{mod} & CFClient::KMOD_ALT && $_[0]{sym} == 13) {
1395 root 1.87 # alt-enter
1396 root 1.134 video_shutdown;
1397 root 1.99 $CFG->{fullscreen} = !$CFG->{fullscreen};
1398 root 1.134 video_init;
1399 root 1.87 } else {
1400 root 1.147 CFClient::UI::feed_sdl_key_down_event ($_[0]);
1401 elmex 1.23 }
1402 root 1.87 },
1403 root 1.153 CFClient::SDL_KEYUP => \&CFClient::UI::feed_sdl_key_up_event,
1404     CFClient::SDL_MOUSEMOTION => \&CFClient::UI::feed_sdl_motion_event,
1405     CFClient::SDL_MOUSEBUTTONDOWN => \&CFClient::UI::feed_sdl_button_down_event,
1406     CFClient::SDL_MOUSEBUTTONUP => \&CFClient::UI::feed_sdl_button_up_event,
1407     CFClient::SDL_USEREVENT => \&audio_music_finished,
1408 root 1.87 );
1409 elmex 1.23
1410 root 1.1 #############################################################################
1411    
1412 root 1.131 $SIG{INT} = $SIG{TERM} = sub { exit };
1413    
1414 root 1.116 $TILECACHE = CFClient::db_table "tilecache";
1415     $FACEMAP = CFClient::db_table "facemap";
1416 root 1.114
1417 root 1.67 CFClient::read_cfg "$Crossfire::VARDIR/pclientrc";
1418 elmex 1.10
1419 root 1.90 my %DEF_CFG = (
1420 root 1.105 sdl_mode => 0,
1421 root 1.90 width => 640,
1422     height => 480,
1423 root 1.105 fullscreen => 0,
1424 root 1.90 fast => 0,
1425 root 1.169 map_scale => 0.5,
1426 root 1.97 fow_enable => 1,
1427 root 1.90 fow_intensity => 0.45,
1428 root 1.92 fow_smooth => 0,
1429 root 1.140 gui_fontsize => 1,
1430 elmex 1.157 log_fontsize => 1,
1431 elmex 1.158 gauge_fontsize => 1,
1432     gauge_size => 0.35,
1433 elmex 1.157 stat_fontsize => 1,
1434 root 1.90 mapsize => 100,
1435     host => "crossfire.schmorp.de",
1436 elmex 1.101 say_command => 'say',
1437 root 1.139 audio_enable => 1,
1438     bgm_enable => 1,
1439 root 1.149 bgm_volume => 0.25,
1440 root 1.90 );
1441    
1442     while (my ($k, $v) = each %DEF_CFG) {
1443     $CFG->{$k} = $v unless exists $CFG->{$k};
1444     }
1445 elmex 1.12
1446 root 1.89 sdl_init;
1447 root 1.87
1448 root 1.93 @SDL_MODES = reverse
1449     grep $_->[0] >= 640 && $_->[1] >= 480,
1450 root 1.145 CFClient::SDL_ListModes;
1451 root 1.87
1452 root 1.89 @SDL_MODES or CFClient::fatal "Unable to find a usable video mode\n(hardware accelerated opengl fullscreen)";
1453    
1454     $CFG->{sdl_mode} = 0 if $CFG->{sdl_mode} > @SDL_MODES;
1455    
1456 root 1.65 {
1457 root 1.168 my @fonts = map CFClient::find_rcfile "fonts/$_", qw(
1458     DejaVuSans.ttf
1459     DejaVuSansMono.ttf
1460     DejaVuSans-Bold.ttf
1461     DejaVuSansMono-Bold.ttf
1462     DejaVuSans-Oblique.ttf
1463     DejaVuSansMono-Oblique.ttf
1464     DejaVuSans-BoldOblique.ttf
1465     DejaVuSansMono-BoldOblique.ttf
1466     );
1467 root 1.65
1468 root 1.67 CFClient::add_font $_ for @fonts;
1469 root 1.168
1470     $FONT_PROP = new_from_file CFClient::Font $fonts[0];
1471     $FONT_FIXED = new_from_file CFClient::Font $fonts[1];
1472    
1473     $FONT_PROP->make_default;
1474 root 1.65 }
1475 root 1.40
1476 root 1.134 video_init;
1477     audio_init;
1478 root 1.122
1479 root 1.87 Event::loop;
1480 root 1.19
1481 root 1.148 END { CFClient::SDL_Quit }
1482 root 1.131
1483 root 1.178 =head1 pclient - Crossfire+ and Crossfire game client
1484    
1485     Pclient is a Crossfire+ and Crossfire game client.
1486    
1487     =head2 Features
1488    
1489     =over 4
1490    
1491     =item Fullscreen Map
1492    
1493     PClient can uses a fullscreen map, which greatly enhances how much of the
1494     game world you can see.
1495    
1496     =item Persistent Map Cache (Crossfire+ only)
1497    
1498     PClient can persistently cache all map data it received from the
1499     server. This not only allows it to display an overview map, but also
1500     ensures that once-explored areas will be available the next time you want
1501     to explore more.
1502    
1503     =item Hardware acceleration
1504    
1505     Unlike most Crossfire clients, PClient take advantage of OpenGL hardware
1506     acceleration. Most modern graphics cards have difficulties with 2D
1507     acceleration, while 3D graphics is accelerated well.
1508    
1509     =item No arbitrary limits
1510    
1511     Unlike other Crossfire clients, pclient does not suffer from arbitrary
1512     limits (like a fixed amount of face numbers). There are still limits, but
1513     they are not arbitrarily low :)
1514    
1515     =back
1516    
1517 root 1.179 =head1 USAGE
1518    
1519     =head2 The Map
1520    
1521     The map is always displayed in the background, behind all other windows and UI elements.
1522    
1523     #TODO# middle-click scrolls
1524     #
1525     # keys:
1526     #
1527     # a apply
1528     # keypad moves, kp_5 applies ranged attack to self
1529    
1530     Starting to type enters the I<completion mode>. In that mode, you can type
1531     abbreviations or commands and have them executed as soon as they match a
1532     valid command. This is best explained by a few examples:
1533    
1534     Typing B<climb> will display a list of commands with I<climb> in their
1535     name, such as I<ready_skill climbing> and I<use_skill climbing>.
1536    
1537     You can abbreviate commands by typing only the first character of every
1538     word. For example, typing I<iwor> will likely select I<invoke word of
1539     recall>, while I<ccfo> will select I<cast create food>. Likewise, I<rscli>
1540     will likely select I<ready_skill climbing> and I<usl> will give you
1541     I<use_skill levitation>.
1542    
1543     =head2 The map overview
1544    
1545     #TODO#
1546    
1547     =head2 The Status area in the lower right corner
1548    
1549     #TODO#
1550    
1551     =head2 The I<Statistics>/I>Stats> window
1552    
1553     #TODO#
1554    
1555 root 1.178 =head1 FAQ
1556    
1557     =over 4
1558    
1559     =item The client is very sluggish and slow, what can I do about this?
1560    
1561     Most likely, you don't have accelerated OpenGL support. Try to find a
1562     newer driver, or a driver from your hardware vendor, that features OpenGL
1563     support.
1564    
1565     If this is not an option, the following Setup options reduce the load and
1566     will likely make the client playable with sofwtare rendering (it will
1567     still be slow, though):
1568    
1569     =over 4
1570    
1571     =item B<Video Mode> should be set as low as possible (e.g. 640x480)
1572    
1573     =item Enable B<Fast & Ugly> mode
1574    
1575     =item Disable B<Fog of War>
1576    
1577     =item Increase B<Map Scale>
1578    
1579     =back
1580    
1581     =back
1582    
1583     =head1 AUTHOR
1584    
1585     Marc Lehmann <crossfire@schmorp.de>, Robin Redeker <elmex@ta-sa.org>
1586    
1587    
1588 root 1.82