ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/pclient
Revision: 1.240
Committed: Tue May 23 21:14:42 2006 UTC (18 years ago) by root
Branch: MAIN
Changes since 1.239: +20 -6 lines
Log Message:
fix crash on startup when sound device not available

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.224 # do things only needed for single-binary version (par)
7 root 1.176 BEGIN {
8     if (%PAR::LibCache) {
9     @INC = grep ref, @INC; # weed out all paths except pars loader refs
10    
11     while (my ($filename, $zip) = each %PAR::LibCache) {
12     for ($zip->memberNames) {
13     next unless /^\/root\/(.*)/;
14     $zip->extractMember ($_, "$ENV{PAR_TEMP}/$1")
15     unless -e "$ENV{PAR_TEMP}/$1";
16     }
17     }
18    
19 root 1.224 # TODO: pango-rc file, anybody?
20    
21 root 1.176 unshift @INC, $ENV{PAR_TEMP};
22     }
23     }
24    
25     # need to do it again because that pile of garbage called PAR nukes it before main
26 root 1.224 unshift @INC, $ENV{PAR_TEMP}
27     if %PAR::LibCache;
28 root 1.176
29 root 1.87 use Time::HiRes 'time';
30 root 1.224 use Pod::POM;
31 root 1.87 use Event;
32 root 1.13
33 elmex 1.11 use Crossfire;
34 root 1.2 use Crossfire::Protocol;
35    
36 root 1.116 use Compress::LZF;
37    
38 root 1.67 use CFClient;
39 root 1.72 use CFClient::UI;
40 root 1.141 use CFClient::MapWidget;
41 elmex 1.10
42 root 1.177 $Event::DIED = sub {
43 root 1.208 # TODO: display dialog box or so
44 root 1.177 CFClient::error $_[1];
45     };
46 root 1.176
47 root 1.178 #$SIG{__WARN__} = sub { Carp::cluck $_[0] };#d#
48    
49 root 1.63 our $VERSION = '0.1';
50    
51 root 1.96 my $MAX_FPS = 60;
52 root 1.90 my $MIN_FPS = 5; # unused as of yet
53 root 1.63
54 root 1.112 our $META_SERVER = "crossfire.real-time.com:13326";
55    
56 root 1.116 our $FACEMAP;
57     our $TILECACHE;
58     our $MAPCACHE;
59 root 1.19
60 root 1.87 our $LAST_REFRESH;
61     our $NOW;
62    
63 elmex 1.10 our $CFG;
64 root 1.13 our $CONN;
65 root 1.85 our $FAST; # fast, low-quality mode, possibly useful for software-rendering
66 root 1.2
67 root 1.206 our $WANT_REFRESH;
68     our $CAN_REFRESH;
69    
70 root 1.75 our @SDL_MODES;
71 root 1.30 our $WIDTH;
72     our $HEIGHT;
73     our $FULLSCREEN;
74 root 1.99 our $FONTSIZE;
75 root 1.30
76 root 1.168 our $FONT_PROP;
77     our $FONT_FIXED;
78    
79 root 1.95 our $MAP;
80 root 1.187 our $MAPMAP;
81 root 1.69 our $MAPWIDGET;
82 root 1.112 our $BUTTONBAR;
83     our $LOGVIEW;
84     our $CONSOLE;
85     our $METASERVER;
86 root 1.199 our $LOGIN_BUTTON;
87 elmex 1.237 our $QUIT_DIALOG;
88 root 1.57
89 root 1.173 our $FLOORBOX;
90 elmex 1.125 our $GAUGES;
91 elmex 1.154 our $STATWIDS;
92 elmex 1.125
93 root 1.86 our $SDL_ACTIVE;
94 root 1.13 our %SDL_CB;
95 root 1.18
96 root 1.134 our $SDL_MIXER;
97     our @SOUNDS; # event => file mapping
98     our %AUDIO_CHUNKS; # audio files
99    
100 root 1.30 our $ALT_ENTER_MESSAGE;
101 root 1.212 our $STATUSBOX;
102 root 1.64 our $DEBUG_STATUS;
103 root 1.30
104 elmex 1.191 our $INVWIN;
105     our $INV;
106 elmex 1.217 our $INVR;
107 elmex 1.223 our $INVR_LBL;
108 elmex 1.217 our $OPENCONT;
109 elmex 1.191
110 root 1.82 sub status {
111 root 1.228 $STATUSBOX->add (CFClient::UI::Label::escape $_[0], pri => -10, group => "status", timeout => 20, fg => [1, 1, 0, 1]);
112 root 1.82 }
113    
114     sub debug {
115     $DEBUG_STATUS->set_text ($_[0]);
116 root 1.206 my ($w, $h) = $DEBUG_STATUS->size_request;
117     $DEBUG_STATUS->move ($WIDTH - $w, 0);
118 root 1.82 }
119    
120 root 1.84 sub start_game {
121 root 1.85 status "logging in...";
122    
123 root 1.106 my $mapsize = List::Util::min 32, List::Util::max 11, int $WIDTH * $CFG->{mapsize} * 0.01 / 32;
124 root 1.84
125 root 1.116 $MAPCACHE = CFClient::db_table "mapcache_$CFG->{host}";
126 root 1.95 $MAP = new CFClient::Map $mapsize, $mapsize;
127 root 1.112
128     my ($host, $port) = split /:/, $CFG->{host};
129 root 1.95
130 root 1.194 $CONN = eval {
131     new conn
132     host => $host,
133     port => $port || 13327,
134     user => $CFG->{user},
135     pass => $CFG->{password},
136     mapw => $mapsize,
137     maph => $mapsize,
138     ;
139     };
140 root 1.84
141 root 1.194 if ($CONN) {
142 root 1.225 CFClient::lowdelay fileno $CONN->{fh};
143    
144 root 1.200 $LOGIN_BUTTON->set_text ("Logout");
145 root 1.225 status "login successful";
146 root 1.200
147 root 1.225 $BUTTONBAR->{children}[1]->emit ("activate")
148     if $BUTTONBAR->{children}[1]->{state};
149 root 1.85
150 root 1.194 } else {
151     status "unable to connect";
152 root 1.199 stop_game();
153 root 1.194 }
154 root 1.84 }
155    
156     sub stop_game {
157 root 1.200 return unless $CONN;
158    
159 root 1.199 status "connection closed";
160     $LOGIN_BUTTON->set_text ("Login");
161 root 1.200 $CONN->destroy;
162     $CONN = 0; # false, does not autovivify
163    
164 root 1.225 $BUTTONBAR->{children}[1]->emit ("activate")
165     unless $BUTTONBAR->{children}[1]->{state};
166    
167 root 1.200 undef $MAPCACHE;
168     undef $MAP;
169 root 1.84 }
170    
171 root 1.111 sub client_setup {
172 root 1.99 my $dialog = new CFClient::UI::FancyFrame
173 root 1.150 title => "Client Setup",
174 root 1.81 child => (my $vbox = new CFClient::UI::VBox);
175     $vbox->add (my $table = new CFClient::UI::Table expand => 1, col_expand => [0, 1]);
176    
177 root 1.140 $table->add (0, 0, new CFClient::UI::Label valign => 0, align => 1, text => "Video Mode");
178 root 1.81 $table->add (1, 0, my $hbox = new CFClient::UI::HBox);
179    
180 root 1.216 $hbox->add (my $mode_slider = new CFClient::UI::Slider expand => 1, req_w => 100, range => [$CFG->{sdl_mode}, 0, $#SDL_MODES, 1, 1]);
181 root 1.150 $hbox->add (my $mode_label = new CFClient::UI::Label align => 0, valign => 0, height => 0.8, template => "9999x9999");
182 root 1.81
183     $mode_slider->connect (changed => sub {
184     my ($self, $value) = @_;
185    
186     $CFG->{sdl_mode} = $self->{range}[0] = $value = int $value;
187     $mode_label->set_text (sprintf "%dx%d", @{$SDL_MODES[$value]});
188     });
189     $mode_slider->emit (changed => $mode_slider->{range}[0]);
190 root 1.82
191 elmex 1.158 my $row = 1;
192    
193     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Fullscreen");
194 elmex 1.166 $table->add (1, $row++, new CFClient::UI::CheckBox
195     state => $CFG->{fullscreen},
196     tooltip => "Bring the client into fullscreen mode",
197     connect_changed => sub {
198     my ($self, $value) = @_;
199     $CFG->{fullscreen} = $value;
200     }
201     );
202 root 1.85
203 elmex 1.158 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Fast & Ugly");
204 root 1.163 $table->add (1, $row++, new CFClient::UI::CheckBox
205     state => $CFG->{fast},
206     tooltip => "Lower the visual quality considerably to speed up rendering.",
207     connect_changed => sub {
208     my ($self, $value) = @_;
209     $CFG->{fast} = $value;
210     }
211     );
212 root 1.89
213 root 1.169 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Map Scale");
214     $table->add (1, $row++, new CFClient::UI::Slider
215 root 1.240 range => [(log $CFG->{map_scale}) / (log 2), -3, 1, 0, 1],
216     tooltip => "Enlarge or shrink the displayed map",
217 root 1.169 connect_changed => sub {
218     my ($self, $value) = @_;
219 root 1.240 $CFG->{map_scale} = 2 ** $value;
220 root 1.169 }
221     );
222    
223 elmex 1.158 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Fog of War");
224 root 1.163 $table->add (1, $row++, new CFClient::UI::CheckBox
225     state => $CFG->{fow_enable},
226     tooltip => "Fog-of-War marks areas that cannot be seen by the player",
227     connect_changed => sub {
228     my ($self, $value) = @_;
229     $CFG->{fow_enable} = $value;
230     }
231     );
232 root 1.97
233 elmex 1.158 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "FoW Intensity");
234 root 1.163 $table->add (1, $row++, new CFClient::UI::Slider
235     range => [$CFG->{fow_intensity}, 0, 1 + 0.001, 0.001],
236     tooltip => "The higher the intensity, the lighter the Fog-of-War color",
237     connect_changed => sub {
238     my ($self, $value) = @_;
239     $CFG->{fow_intensity} = $value;
240     }
241     );
242 root 1.90
243 elmex 1.158 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "FoW Smooth");
244 root 1.163 $table->add (1, $row++, new CFClient::UI::CheckBox
245     state => $CFG->{fow_smooth},
246     tooltip => "Smooth the Fog-of-War a bit to make it more realistic",
247     connect_changed => sub {
248     my ($self, $value) = @_;
249     $CFG->{fow_smooth} = $value;
250     status "Fog of War smoothing requires OpenGL 1.2 or higher" if $CFClient::GL_VERSION < 1.2;
251     }
252     );
253 root 1.91
254 elmex 1.158 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "GUI Fontsize");
255 root 1.163 $table->add (1, $row++, new CFClient::UI::Slider
256 root 1.216 range => [$CFG->{gui_fontsize}, 0.5, 2, 0.1, 0.1],
257 root 1.163 tooltip => "The font size used by most GUI elements",
258 root 1.216 connect_changed => sub { $CFG->{gui_fontsize} = $_[1] },
259 root 1.163 );
260 root 1.140
261 elmex 1.158 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Server Log Fontsize");
262 root 1.163 $table->add (1, $row++, new CFClient::UI::Slider
263 root 1.216 range => [$CFG->{log_fontsize}, 0.5, 2, 0.1, 0.1],
264 root 1.163 tooltip => "The font size used by the server log window only",
265 root 1.216 connect_changed => sub { $LOGVIEW->set_fontsize ($CFG->{log_fontsize} = $_[1]) },
266 root 1.163 );
267 root 1.105
268 elmex 1.158 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Stats Fontsize");
269 root 1.163
270     $table->add (1, $row++, new CFClient::UI::Slider
271 root 1.216 range => [$CFG->{stat_fontsize}, 0.5, 2, 0.1, 0.1],
272 root 1.163 tooltip => "The font size used by the statistics window only",
273     connect_changed => sub {
274 root 1.216 $CFG->{stat_fontsize} = $_[1];
275 root 1.163 &set_stats_window_fontsize;
276     }
277     );
278 elmex 1.157
279 root 1.163 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Gauge size");
280     $table->add (1, $row++, new CFClient::UI::Slider
281     range => [$CFG->{gauge_size}, 0.2, 0.8, 0.02],
282     tooltip => "Adjust the size of the stats gauges at the bottom right",
283     connect_changed => sub {
284     $CFG->{gauge_size} = $_[1];
285 root 1.216 $GAUGES->{win}->set_size ($WIDTH, int $HEIGHT * $CFG->{gauge_size});
286 root 1.163 }
287     );
288 elmex 1.158
289     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Gauge fontsize");
290 root 1.163 $table->add (1, $row++, new CFClient::UI::Slider
291 root 1.216 range => [$CFG->{gauge_fontsize}, 0.5, 2.0, 0.1, 0.1],
292 elmex 1.166 tooltip => "Adjusts the fontsize of the gauges at the bottom right",
293 root 1.163 connect_changed => sub {
294 root 1.216 $CFG->{gauge_fontsize} = $_[1];
295 root 1.163 &set_gauge_window_fontsize;
296     }
297     );
298 elmex 1.158
299 root 1.163 $table->add (1, $row++, new CFClient::UI::Button
300     expand => 1, align => 0, text => "Apply",
301 root 1.168 tooltip => "Apply the video settings",
302 root 1.163 connect_activate => sub {
303     video_shutdown ();
304     video_init ();
305     }
306     );
307 root 1.111
308 elmex 1.158 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Audio Enable");
309 root 1.163 $table->add (1, $row++, new CFClient::UI::CheckBox
310     state => $CFG->{audio_enable},
311     tooltip => "If enabled, sound effects and music will be played. If disabled, no audio will be used and the soundcard will not be opened.",
312     connect_changed => sub {
313     $CFG->{audio_enable} = $_[1];
314     }
315     );
316 root 1.140 # $table->add (0, 9, new CFClient::UI::Label valign => 0, align => 1, text => "Effects Volume");
317     # $table->add (1, 8, new CFClient::UI::Slider range => [$CFG->{effects_volume}, 0, 128, 1], connect_changed => sub {
318     # $CFG->{effects_volume} = $_[1];
319     # });
320 elmex 1.158 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Background Music");
321     $table->add (1, $row++, my $hbox = new CFClient::UI::HBox);
322 root 1.163 $hbox->add (new CFClient::UI::CheckBox
323     expand => 1, state => $CFG->{bgm_enable},
324     tooltip => "Enable background music playing",
325     connect_changed => sub {
326     $CFG->{bgm_enable} = $_[1];
327     }
328     );
329     $hbox->add (new CFClient::UI::Slider
330     expand => 1, range => [$CFG->{bgm_volume}, 0, 1, 0.1],
331     tooltip => "The volume of the background music",
332     connect_changed => sub {
333     $CFG->{bgm_volume} = $_[1];
334     CFClient::MixMusic::volume $_[1] * 128;
335     }
336     );
337 root 1.140
338 root 1.163 $table->add (1, $row++, new CFClient::UI::Button
339     expand => 1, align => 0, text => "Apply",
340 root 1.168 tooltip => "Apply the audio settings",
341 root 1.163 connect_activate => sub {
342     audio_shutdown ();
343     audio_init ();
344     }
345     );
346 elmex 1.137
347 elmex 1.188 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Communication cmd");
348     $table->add (1, $row++, my $saycmd = new CFClient::UI::Entry
349     text => $CFG->{say_command},
350     tooltip => "This is the command that will be used if you write a line in the message window entry. "
351     ."Usually you want to enter something like 'say' or 'shout' or 'gsay' here. "
352     ."But you could also set it to 'tell &lt;playername&gt;' to only chat with that user.",
353     connect_changed => sub {
354     my ($self, $value) = @_;
355     $CFG->{say_command} = $value;
356     }
357     );
358    
359 root 1.111 $dialog
360     }
361    
362 elmex 1.157 sub set_stats_window_fontsize {
363 elmex 1.158 for (values %{$STATWIDS}) {
364 elmex 1.157 $_->set_fontsize ($::CFG->{stat_fontsize});
365     }
366     }
367    
368 elmex 1.158 sub set_gauge_window_fontsize {
369     for (map { $GAUGES->{$_} } grep { $_ ne 'win' } keys %{$GAUGES}) {
370     $_->set_fontsize ($::CFG->{gauge_fontsize});
371     }
372 root 1.169
373     # local $GAUGES->{win}{parent};#d#
374     # use PApp::Util; open D, ">:utf8", "d"; print D PApp::Util::dumpval $GAUGES->{win}; close D;
375 elmex 1.158 }
376    
377     sub make_gauge_window {
378 root 1.215 my $gh = int $HEIGHT * $CFG->{gauge_size};
379 elmex 1.158
380     my $win = new CFClient::UI::Frame (
381 root 1.215 req_y => -1,
382     user_w => $WIDTH,
383     user_h => $gh,
384 elmex 1.158 );
385 root 1.215
386 root 1.173 $win->add (my $hbox = new CFClient::UI::HBox
387     children => [
388     (new CFClient::UI::HBox expand => 1),
389 root 1.212 (new CFClient::UI::VBox children => [
390     (new CFClient::UI::Empty expand => 1),
391     (new CFClient::UI::Frame bg => [0, 0, 0, 0.4], child => ($FLOORBOX = new CFClient::UI::VBox)),
392     ]),
393 root 1.173 (my $vbox = new CFClient::UI::VBox),
394     ],
395     );
396 elmex 1.158
397 root 1.173 $vbox->add (new CFClient::UI::HBox
398     expand => 1,
399     children => [
400     (new CFClient::UI::Empty expand => 1),
401     (my $hb = new CFClient::UI::HBox),
402     ],
403     );
404 elmex 1.161
405 root 1.172 $hb->add (my $hg = new CFClient::UI::Gauge type => 'hp',
406 root 1.236 tooltip => "<b>Health points</b>. Measures of how much damage you can take before dying. Hit points are determined from your level and are influenced by the value of your Con. Hp value may range between 1 to beyond 500 and higher values indicate a greater ability to withstand punishment.");
407 root 1.172 $hb->add (my $mg = new CFClient::UI::Gauge type => 'mana',
408 root 1.236 tooltip => "<b>Spell points</b>. Measures of how much \"fuel\" you have for casting spells and incantations. Mana is calculated from your level and your Pow. Mana values can range between 1 to beyond 500 (glowing crystals can increase the current spell points beyond your normal maximum). Higher values indicate greater amounts of mana.");
409 root 1.172 $hb->add (my $gg = new CFClient::UI::Gauge type => 'grace',
410 root 1.236 tooltip => "<b>Grace points</b> - how favored you are by your god. In game terms, how much divine magic you can cast. Your level, Wis and Pow effect what the value of grace is. Prayong on an altar of your god can increase this value beyond your normal maximum. Grace can take on large positive and negative values. Positive values indicate favor by the gods.");
411 root 1.172 $hb->add (my $fg = new CFClient::UI::Gauge type => 'food',
412 root 1.236 tooltip => "<b>Food</b>. Ranges between 0 (starving) and 999 (satiated). At a value of 0 the character begins to die. Some magic can speed up or slow down the character digestion. Healing wounds will speed up digestion too.");
413 root 1.172
414 root 1.173 $vbox->add (my $exp = new CFClient::UI::Label valign => 0, align => 1, can_hover => 1, can_events => 1,
415 root 1.236 tooltip => "<b>Experience points and overall level</b> - experience is increased as a reward for appropriate action (such as killing monsters) and may decrease as a result of a magical attack or dying. Level is directly derived from the experience value. As the level of the character increases, the character becomes able to succeed at more difficult tasks. A character's level starts at a value of 0 and may range up beyond 100.");
416 root 1.173 $vbox->add (my $rng = new CFClient::UI::Label valign => 0, align => 1, can_hover => 1, can_events => 1,
417 root 1.236 tooltip => "<b>Ranged attack</b> - how you attack when you press shift-cursor (spell, skill, weapon etc.)");
418 elmex 1.158
419     $GAUGES = {
420 elmex 1.166 exp => $exp, win => $win, range => $rng,
421 elmex 1.158 food => $fg, mana => $mg, hp => $hg, grace => $gg
422     };
423 root 1.169
424     &set_gauge_window_fontsize;
425    
426 elmex 1.158 $win
427     }
428    
429 elmex 1.154 sub make_stats_window {
430 root 1.183 my $tgw = new CFClient::UI::FancyFrame x => $WIDTH * 2/5, y => 0, title => "Stats";
431 root 1.155
432 root 1.185 $tgw->add (new CFClient::UI::Window child => my $vb = new CFClient::UI::VBox);
433 root 1.236 $vb->add ($STATWIDS->{title} = new CFClient::UI::Label valign => 0, align => -1, text => "Title:", expand => 1,
434     can_hover => 1, can_events => 1,
435     tooltip => "Your name and title. You can change your title by using the <b>title</b> command, if supported by the server.");
436     $vb->add ($STATWIDS->{map} = new CFClient::UI::Label valign => 0, align => -1, text => "Map:", expand => 1,
437     can_hover => 1, can_events => 1,
438     tooltip => "The map you are currently on (if supported by the server).");
439 elmex 1.156
440     $vb->add (my $hb = new CFClient::UI::HBox expand => 1);
441     $hb->add (my $tbl = new CFClient::UI::Table expand => 1);
442    
443 root 1.174 my $black = [0, 0, 0];
444    
445 root 1.180 for (
446 root 1.236 [0, 0, st_str => "Str", 30, "<b>Physical Strength</b>, determines damage dealt with weapons, how much you can carry, and how often you can attack"],
447     [0, 1, st_dex => "Dex", 30, "<b>Dexterity</b>, your physical agility. Determines chance of being hit and affects armor class and speed"],
448     [0, 2, st_con => "Con", 30, "<b>Constitution</b>, physical health and toughness. Determines how many healthpoints you can have"],
449     [0, 3, st_int => "Int", 30, "<b>Intelligence</b>, your ability to learn and use skills and incantations (both prayers and magic) and determines how much spell points you can have"],
450     [0, 4, st_wis => "Wis", 30, "<b>Wisdom</b>, the ability to learn and use divine magic (prayers). Determines how many grace points you can have"],
451     [0, 5, st_pow => "Pow", 30, "<b>Power</b>, your magical potential. Influences the strength of spell effects, and also how much your spell and grace points increase when leveling up"],
452     [0, 6, st_cha => "Cha", 30, "<b>Charisma</b>, how well you are received by NPCs. Affects buying and selling prices in shops."],
453    
454     [2, 0, st_wc => "Wc", -120, "<b>Weapon Class</b>, effectiveness of melee/missile attacks. Lower is more potent. Current weapon, level and Str are some things which effect the value of Wc. The value of Wc may range between 25 and -72."],
455     [2, 1, st_ac => "Ac", -120, "<b>Armour Class</b>, how protected you are from being hit by any attack. Lower values are better. Ac is based on your race and is modified by the Dex and current armour worn. For characters that cannot wear armour, Ac improves as their level increases."],
456     [2, 2, st_dam => "Dam", 120, "<b>Damage</b>, how much damage your melee/missile attack inflicts. Higher values indicate a greater amount of damage will be inflicted with each attack."],
457     [2, 3, st_arm => "Arm", 120, "<b>Armour</b>, how much damage (from physical attacks) will be subtracted from successful hits made upon you. This value ranges between 0 to 99%. Current armour worn primarily determines Arm value."],
458     [2, 4, st_spd => "Spd", 10.54, "<b>Speed</b>, how fast you can move. The value of speed may range between nearly 0 (\"very slow\") to higher than 5 (\"lightning fast\"). Base speed is determined from the Dex and modified downward proportionally by the amount of weight carried which exceeds the Max Carry limit. The armour worn also sets the upper limit on speed."],
459     [2, 5, st_wspd => "WSp", 10.54, "<b>Weapon Speed</b>, how many attacks you may make per unit of time (0.120s). Higher values indicate faster attack speed. Current weapon and Dex effect the value of weapon speed."],
460 root 1.180 ) {
461     my ($col, $row, $id, $label, $template, $tooltip) = @$_;
462    
463     $tbl->add ($col , $row, $STATWIDS->{$id} = new CFClient::UI::Label
464 root 1.184 font => $FONT_FIXED, can_hover => 1, can_events => 1, valign => 0, align => +1, template => $template, tooltip => $tooltip);
465 root 1.180 $tbl->add ($col + 1, $row, $STATWIDS->{"$id\_lbl"} = new CFClient::UI::Label
466 root 1.184 font => $FONT_FIXED, can_hover => 1, can_events => 1, fg => $black, valign => 0, align => -1, text => $label, tooltip => $tooltip);
467 root 1.180 }
468 root 1.155
469 elmex 1.158 $hb->add (my $tbl2 = new CFClient::UI::Table expand => 1);
470 root 1.155
471 elmex 1.156 my $row = 0;
472     my $col = 0;
473 root 1.155
474 elmex 1.166 my %resist_names = (
475 root 1.235 slow => "<b>Slow</b> (slows you down when you are hit by the spell. Monsters will have an opportunity to come near you faster and hit you more often.)",
476     holyw => "<b>Holy Word</b> (resistance you against getting the fear when someone whose god doesn't like you spells the holy word on you.)",
477     conf => "<b>Confusion</b> (If you are hit by confusion you will move into random directions, and likely into monsters.)",
478     fire => "<b>Fire</b> (just your resistance to fire spells like burning hands, dragonbreath, meteor swarm fire, ...)",
479     depl => "<b>Depletion</b> (some monsters and other effects can cause stats depletion)",
480     magic => "<b>Magic</b> (resistance to magic spells like magic missile or similar)",
481     drain => "<b>Draining</b> (some monsters (e.g. vampires) and other effects can steal experience)",
482     acid => "<b>Acid</b> (resistance to acid, acid hurts pretty much and also corrodes your weapons)",
483     pois => "<b>Poison</b> (resistance to getting poisoned)",
484     para => "<b>Paralysation</b> (this resistance affects the chance you get paralysed)",
485     deat => "<b>Death</b> (resistance against death spells)",
486     phys => "<b>Physical</b> (this is the resistance against physical attacks, like when a monster hit you in melee combat)",
487     blind => "<b>Blind</b> (blind resistance affects the chance of a successful blinding attack)",
488     fear => "<b>Fear</b> (this attack will drive you away from monsters who cast this and hit you successfully, being resistant to this helps a lot when fighting those monsters)",
489     tund => "<b>Turn undead</b> (affects your resistancy to various forms of 'turn undead' spells. Only relevant when you are, in fact, undead...",
490     elec => "<b>Electricity</b> (resistance against electricity, spells like large lightning, small lightning, ...)",
491     cold => "<b>Cold</b> (this is your resistance against cold spells like icestorm, snowstorm, ...)",
492     ghit => "<b>Ghost hit</b> (special attack used by ghosts and ghost-like beings)",
493 elmex 1.166 );
494 elmex 1.156 for (qw/slow holyw conf fire depl magic
495     drain acid pois para deat phys
496     blind fear tund elec cold ghit/)
497     {
498 root 1.164 $tbl2->add ($col, $row,
499 elmex 1.156 $STATWIDS->{"res_$_"} =
500 root 1.168 new CFClient::UI::Label
501 root 1.184 font => $FONT_FIXED,
502 root 1.180 template => "-100%",
503     align => +1,
504     valign => 0,
505     can_events => 1,
506     can_hover => 1,
507     tooltip => $resist_names{$_},
508 root 1.168 );
509     $tbl2->add ($col + 1, $row, new CFClient::UI::Image
510 root 1.185 font => $FONT_FIXED,
511 root 1.180 can_hover => 1,
512 root 1.168 can_events => 1,
513 root 1.180 image => "ui/resist/resist_$_.png",
514     tooltip => $resist_names{$_},
515 elmex 1.156 );
516    
517     $row++;
518     if ($row % 6 == 0) {
519     $col += 2;
520     $row = 0;
521     }
522     }
523    
524 elmex 1.157 &set_stats_window_fontsize;
525 elmex 1.156 update_stats_window ({});
526 root 1.155
527 elmex 1.154 $tgw
528     }
529    
530 root 1.169 sub formsep {
531     reverse join ",", grep length, split /(...)/, reverse $_[0] * 1
532     }
533    
534 elmex 1.154 sub update_stats_window {
535     my ($stats) = @_;
536    
537 elmex 1.156 # i love text protocols!!!
538 root 1.169 my $hp = $stats->{Crossfire::Protocol::CS_STAT_HP} * 1;
539     my $hp_m = $stats->{Crossfire::Protocol::CS_STAT_MAXHP} * 1;
540     my $sp = $stats->{Crossfire::Protocol::CS_STAT_SP} * 1;
541     my $sp_m = $stats->{Crossfire::Protocol::CS_STAT_MAXSP} * 1;
542     my $fo = $stats->{Crossfire::Protocol::CS_STAT_FOOD} * 1;
543 elmex 1.156 my $fo_m = 999;
544 root 1.169 my $gr = $stats->{Crossfire::Protocol::CS_STAT_GRACE} * 1;
545     my $gr_m = $stats->{Crossfire::Protocol::CS_STAT_MAXGRACE} * 1;
546 elmex 1.156
547     $GAUGES->{hp} ->set_value ($hp, $hp_m);
548     $GAUGES->{mana} ->set_value ($sp, $sp_m);
549     $GAUGES->{food} ->set_value ($fo, $fo_m);
550     $GAUGES->{grace} ->set_value ($gr, $gr_m);
551 root 1.169 $GAUGES->{exp} ->set_text ("Exp: " . (formsep $stats->{Crossfire::Protocol::CS_STAT_EXP64})
552     . " (lvl " . ($stats->{Crossfire::Protocol::CS_STAT_LEVEL} * 1) . ")");
553     my $rng = $stats->{Crossfire::Protocol::CS_STAT_RANGE};
554 elmex 1.159 $rng =~ s/^Range: //; # thank you so much dear server
555     $GAUGES->{range} ->set_text ("Rng: " . $rng);
556 root 1.169 my $title = $stats->{Crossfire::Protocol::CS_STAT_TITLE};
557 elmex 1.165 $title =~ s/^Player: //;
558     $STATWIDS->{title} ->set_text ("Title: " . $title);
559 elmex 1.156
560 root 1.169 $STATWIDS->{st_str} ->set_text (sprintf "%d", $stats->{5});
561     $STATWIDS->{st_dex} ->set_text (sprintf "%d", $stats->{8});
562     $STATWIDS->{st_con} ->set_text (sprintf "%d", $stats->{9});
563     $STATWIDS->{st_int} ->set_text (sprintf "%d", $stats->{6});
564     $STATWIDS->{st_wis} ->set_text (sprintf "%d", $stats->{7});
565     $STATWIDS->{st_pow} ->set_text (sprintf "%d", $stats->{22});
566     $STATWIDS->{st_cha} ->set_text (sprintf "%d", $stats->{10});
567     $STATWIDS->{st_wc} ->set_text (sprintf "%d", $stats->{13});
568     $STATWIDS->{st_ac} ->set_text (sprintf "%d", $stats->{14});
569     $STATWIDS->{st_dam} ->set_text (sprintf "%d", $stats->{15});
570     $STATWIDS->{st_arm} ->set_text (sprintf "%d", $stats->{16});
571     $STATWIDS->{st_spd} ->set_text (sprintf "%.1f", $stats->{Crossfire::Protocol::CS_STAT_SPEED});
572     $STATWIDS->{st_wspd}->set_text (sprintf "%.1f", $stats->{Crossfire::Protocol::CS_STAT_WEAP_SP});
573 elmex 1.156
574     my %tbl = (
575     phys => 100,
576     magic => 101,
577     fire => 102,
578     elec => 103,
579     cold => 104,
580     conf => 105,
581     acid => 106,
582     drain => 107,
583     ghit => 108,
584     pois => 109,
585     slow => 110,
586     para => 111,
587     tund => 112,
588     fear => 113,
589 elmex 1.165 depl => 113,
590 elmex 1.156 deat => 115,
591     holyw => 116,
592     blind => 117
593 elmex 1.154 );
594 elmex 1.156
595     for (keys %tbl) {
596     $STATWIDS->{"res_$_"}->set_text (sprintf "%d%", $stats->{$tbl{$_}});
597     }
598    
599 elmex 1.154 }
600    
601 root 1.112 sub metaserver_dialog {
602     my $dialog = new CFClient::UI::FancyFrame
603 root 1.199 title => "Server List",
604 root 1.112 child => (my $vbox = new CFClient::UI::VBox);
605    
606     $vbox->add ($dialog->{table} = new CFClient::UI::Table);
607    
608     $dialog
609     }
610    
611 root 1.179 my $METASERVER_ATIME;
612    
613 root 1.112 sub update_metaserver {
614 root 1.114 my ($HOST) = @_;
615    
616 root 1.179 return if $METASERVER_ATIME > time;
617     $METASERVER_ATIME = time + 60;
618    
619 root 1.178 my $table = $METASERVER->{table};
620     $table->clear;
621 root 1.179 $table->add (0, 0, my $label = new CFClient::UI::Label max_w => $WIDTH * 0.8, text => "fetching server list...");
622 root 1.112
623     my $buf;
624    
625     my $fh = new IO::Socket::INET PeerHost => $META_SERVER, Blocking => 0;
626    
627 root 1.178 unless ($fh) {
628     $label->set_text ("unable to contact metaserver: $!");
629     return;
630     }
631    
632 root 1.112 Event->io (fd => $fh, poll => 'r', cb => sub {
633     my $res = sysread $fh, $buf, 8192, length $buf;
634    
635     if (!defined $res) {
636     $_[0]->w->cancel;
637 root 1.178 $label->set_text ("error while retrieving server list: $!");
638 root 1.112 } elsif ($res == 0) {
639     $_[0]->w->cancel;
640     status "server list retrieved";
641 root 1.113
642 root 1.178 utf8::decode $buf if utf8::valid $buf;
643 root 1.113
644     $table->clear;
645    
646 root 1.114 my @col = qw(Use #Users Host Uptime Version Description);
647 root 1.113 $table->add ($_, 0, new CFClient::UI::Label align => 0, fg => [1, 1, 0], text => $col[$_])
648     for 0 .. $#col;
649    
650     my @align = qw(1 0 1 1 -1);
651    
652     my $y = 0;
653 root 1.114 for my $m (sort { $b->[3] <=> $a->[3] } map [split /\|/], split /\015?\012/, $buf) {
654 root 1.113 my ($ip, $last, $host, $users, $version, $desc, $ibytes, $obytes, $uptime) = @$m;
655    
656     for ($desc) {
657     s/<br>/\n/gi;
658     s/<li>/\n· /gi;
659     s/<.*?>//sgi;
660     s/&/&amp;/g;
661     s/</&lt;/g;
662     s/>/&gt;/g;
663     }
664    
665     $uptime = sprintf "%dd %02d:%02d:%02d",
666     (int $m->[8] / 86400),
667     (int $m->[8] / 3600) % 24,
668     (int $m->[8] / 60) % 60,
669     $m->[8] % 60;
670    
671     $m = [$users, $host, $uptime, $version, $desc];
672    
673     $y++;
674 root 1.114
675     $table->add (0, $y, new CFClient::UI::VBox children => [
676 root 1.178 (new CFClient::UI::Button text => "Use", connect_activate => sub {
677 root 1.114 $HOST->set_text ($CFG->{host} = $host);
678     }),
679     (new CFClient::UI::Empty expand => 1),
680     ]);
681    
682 root 1.224 $table->add ($_ + 1, $y, new CFClient::UI::Label
683     ellipsise => 0, align => $align[$_], text => $m->[$_], fontsize => 0.8)
684 root 1.113 for 0 .. $#$m;
685     }
686 root 1.112 }
687     });
688     }
689    
690 root 1.111 sub server_setup {
691     my $dialog = new CFClient::UI::FancyFrame
692 root 1.150 title => "Server Setup",
693 root 1.111 child => (my $vbox = new CFClient::UI::VBox);
694 root 1.81
695 root 1.82 $vbox->add (my $table = new CFClient::UI::Table expand => 1, col_expand => [0, 1]);
696 root 1.141 $table->add (0, 2, new CFClient::UI::Label valign => 0, align => 1, text => "Host:Port");
697 root 1.112
698     {
699     $table->add (1, 2, my $vbox = new CFClient::UI::VBox);
700    
701 elmex 1.166 $vbox->add (
702     my $HOST = new CFClient::UI::Entry
703     expand => 1,
704     text => $CFG->{host},
705     tooltip => "The hostname or ip address of the Crossfire(+) server to connect to",
706     connect_changed => sub {
707     my ($self, $value) = @_;
708     $CFG->{host} = $value;
709     }
710     );
711 root 1.112
712     $METASERVER = metaserver_dialog;
713 elmex 1.101
714 elmex 1.166 $vbox->add (new CFClient::UI::Flopper
715     expand => 1,
716 root 1.200 text => "Server List",
717 elmex 1.166 other => $METASERVER,
718 root 1.200 tooltip => "Show a list of available crossfire servers",
719 elmex 1.166 connect_open => sub {
720     update_metaserver $HOST;
721     }
722     );
723 root 1.112 }
724 root 1.81
725 root 1.141 $table->add (0, 4, new CFClient::UI::Label valign => 0, align => 1, text => "Username");
726 elmex 1.166 $table->add (1, 4, new CFClient::UI::Entry
727     text => $CFG->{user},
728     tooltip => "The name of your character on the server",
729     connect_changed => sub {
730     my ($self, $value) = @_;
731     $CFG->{user} = $value;
732     }
733     );
734 root 1.81
735 root 1.141 $table->add (0, 5, new CFClient::UI::Label valign => 0, align => 1, text => "Password");
736 elmex 1.166 $table->add (1, 5, new CFClient::UI::Entry
737     text => $CFG->{password},
738     hidden => 1,
739     tooltip => "The password for your character",
740     connect_changed => sub {
741     my ($self, $value) = @_;
742     $CFG->{password} = $value;
743     }
744     );
745 elmex 1.101
746 root 1.141 $table->add (0, 7, new CFClient::UI::Label valign => 0, align => 1, text => "Map Size");
747 elmex 1.101 $table->add (1, 7, new CFClient::UI::Slider
748 root 1.81 req_w => 100,
749 root 1.216 range => [$CFG->{mapsize}, 10, 100 + 1, 1, 1],
750 elmex 1.166 tooltip => "This is the size of the portion of the map update the server sends you. "
751     ."If you set this to a high value you will be able to see further for example.",
752 root 1.81 connect_changed => sub {
753     my ($self, $value) = @_;
754    
755     $CFG->{mapsize} = $self->{range}[0] = $value = int $value;
756     },
757     );
758    
759 root 1.226 $table->add (0, 8, new CFClient::UI::Label valign => 0, align => 1, text => "Output-Count");
760     $table->add (1, 8, new CFClient::UI::Entry
761     text => $CFG->{output_count},
762     tooltip => "Should be set to 1 unless you know what you are doing",
763     connect_changed => sub { $CFG->{output_count} = $_[1] },
764     );
765    
766     $table->add (0, 9, new CFClient::UI::Label valign => 0, align => 1, text => "Output-Sync");
767     $table->add (1, 9, new CFClient::UI::Entry
768     text => $CFG->{output_sync},
769     tooltip => "Should be set to 1 unless you know what you are doing",
770     connect_changed => sub { $CFG->{output_sync} = $_[1] },
771     );
772    
773     $table->add (1, 10, $LOGIN_BUTTON = new CFClient::UI::Button
774 root 1.199 expand => 1,
775     align => 0,
776     text => "Login",
777     connect_activate => sub {
778     $CONN ? stop_game
779     : start_game;
780     },
781     );
782 root 1.82
783 root 1.98 $dialog
784 root 1.81 }
785 root 1.58
786 root 1.111 sub message_window {
787 root 1.99 my $window = new CFClient::UI::FancyFrame
788 root 1.150 title => "Messages",
789 root 1.186 border_bg => [1, 1, 1, 1],
790 root 1.236 bg => [0, 0, 0, 0.75],
791 root 1.124 user_w => int $::WIDTH / 3,
792     user_h => int $::HEIGHT / 5,
793 root 1.99 child => (my $vbox = new CFClient::UI::VBox);
794    
795 root 1.229 $vbox->add ($LOGVIEW);
796 root 1.105
797 root 1.122 $vbox->add (my $input = new CFClient::UI::Entry
798 root 1.236 tooltip => "<b>Chat Box</b>. If you enter a text and press return/enter here, the current <i>communication command</i> "
799     . "from the client setup will be prepended (e.g. <b>shout</b>, <b>chat</b>...). "
800     . "If you prepend a slash (/), you will submit a command instead (similar to IRC). "
801     . "A better way to submit commands (and the occasional chat command) is often the map command completer.",
802 elmex 1.118 connect_focus_in => sub {
803     my ($input, $prev_focus) = @_;
804    
805     delete $input->{refocus_map};
806    
807     if ($prev_focus == $MAPWIDGET && $input->{auto_activated}) {
808     $input->{refocus_map} = 1;
809     }
810     delete $input->{auto_activated};
811     },
812 root 1.116 connect_activate => sub {
813 elmex 1.100 my ($input, $text) = @_;
814     $input->set_text ('');
815    
816     if ($text =~ /^\/(.*)/) {
817 root 1.123 $::CONN->user_send ($1);
818 elmex 1.100 } else {
819 elmex 1.101 my $say_cmd = $::CFG->{say_command} || 'say';
820 root 1.123 $::CONN->user_send ("$say_cmd $text");
821 elmex 1.100 }
822 elmex 1.118 if ($input->{refocus_map}) {
823     delete $input->{refocus_map};
824     $MAPWIDGET->focus_in
825     }
826 root 1.116 },
827     connect_escape => sub {
828 elmex 1.102 $MAPWIDGET->focus_in
829 root 1.116 },
830     );
831 elmex 1.102
832     $CONSOLE = {
833     window => $window,
834     input => $input
835     };
836 root 1.99
837     $window
838     }
839    
840 elmex 1.237 sub open_quit_dialog {
841     unless ($QUIT_DIALOG) {
842    
843 elmex 1.238 $QUIT_DIALOG = new CFClient::UI::FancyFrame title => "Really Quit?";
844 elmex 1.237
845     $QUIT_DIALOG->add (my $vb = new CFClient::UI::VBox expand => 1);
846    
847     $vb->add (new CFClient::UI::Label
848     text => "You should find a savebed and apply it first!",
849     max_w => $WIDTH * 0.25
850     );
851     $vb->add (my $hb = new CFClient::UI::HBox expand => 1);
852     $hb->add (new CFClient::UI::Button
853     text => "Ok",
854     connect_activate => sub { $QUIT_DIALOG->hide },
855     expand => 1
856     );
857     $hb->add (new CFClient::UI::Button
858     text => "Quit anyway",
859     connect_activate => sub { exit 1 },
860     expand => 1
861     );
862     $hb->add (new CFClient::UI::Label "You should find a savebed and apply it first!");
863    
864 elmex 1.238 $QUIT_DIALOG->show_centered;
865 elmex 1.237 } else {
866 elmex 1.238 $QUIT_DIALOG->show_centered;
867 elmex 1.237 }
868     }
869    
870 elmex 1.191 sub make_inventory_window {
871 elmex 1.217 my $invwin = new CFClient::UI::FancyFrame
872 elmex 1.239 user_w => $WIDTH * (7/8), user_h => $HEIGHT * (7/8), title => "Inventory";
873 elmex 1.217
874 root 1.230 $invwin->add (my $hb = new CFClient::UI::HBox expand => 1);
875 elmex 1.223
876     $hb->add (my $vb1 = new CFClient::UI::VBox expand => 1);
877     $vb1->add (my $lbl = new CFClient::UI::Label);
878     $lbl->set_text ("Player");
879     $vb1->add ($INV = new CFClient::UI::Inventory expand => 1);
880    
881     $hb->add (my $vb2 = new CFClient::UI::VBox expand => 1);
882     $vb2->add ($INVR_LBL = new CFClient::UI::Label);
883     $INVR_LBL->set_text ("Floor");
884     $vb2->add ($INVR = new CFClient::UI::Inventory expand => 1);
885 elmex 1.217
886 elmex 1.191 $invwin
887     }
888    
889 root 1.89 sub sdl_init {
890 root 1.145 CFClient::SDL_Init
891 root 1.89 and die "SDL::Init failed!\n";
892     }
893    
894 root 1.134 sub video_init {
895 root 1.89 sdl_init;
896    
897 root 1.197 $CFG->{sdl_mode} = 0 if $CFG->{sdl_mode} >= @SDL_MODES;
898    
899 root 1.84 ($WIDTH, $HEIGHT) = @{ $SDL_MODES[$CFG->{sdl_mode}] };
900     $FULLSCREEN = $CFG->{fullscreen};
901 root 1.89 $FAST = $CFG->{fast};
902 root 1.84
903 root 1.145 CFClient::SDL_SetVideoMode $WIDTH, $HEIGHT, $FULLSCREEN
904 root 1.230 or die "SDL_SetVideoMode failed: " . (CFClient::SDL_GetError) . "\n";
905 root 1.2
906 root 1.86 $SDL_ACTIVE = 1;
907 root 1.87 $LAST_REFRESH = time - 0.01;
908 root 1.45
909 root 1.67 CFClient::gl_init;
910 root 1.30
911 root 1.140 $FONTSIZE = int $HEIGHT / 40 * $CFG->{gui_fontsize};
912 root 1.39
913 root 1.202 $CFClient::UI::ROOT->configure (0, 0, $WIDTH, $HEIGHT);#d#
914    
915 root 1.52 #############################################################################
916    
917 root 1.213 unless ($DEBUG_STATUS) {
918 root 1.202 # create the widgets
919    
920 root 1.215 $DEBUG_STATUS = new CFClient::UI::Label padding => 0, z => 100, req_x => -1;
921 root 1.202 $DEBUG_STATUS->show;
922    
923 root 1.212 $STATUSBOX = new CFClient::UI::Statusbox;
924 root 1.215 $STATUSBOX->add ("Use <b>Alt-Enter</b> to toggle fullscreen mode", pri => -100, color => [1, 1, 1, 0.8]);
925 root 1.212
926     (new CFClient::UI::Frame
927 root 1.215 bg => [0, 0, 0, 0.4],
928     req_y => -1,
929     child => $STATUSBOX,
930 root 1.212 )->show;
931 root 1.202
932     CFClient::UI::FancyFrame->new (
933     border_bg => [1, 1, 1, 192/255],
934     bg => [1, 1, 1, 0],
935 root 1.236 child => ($MAPMAP = new CFClient::MapWidget::MapMap
936     tooltip => "<b>Map</b>. On servers that support this feature, this will display an overview of the surrounding areas.",
937     ),
938 root 1.202 )->show;
939    
940     $MAPWIDGET = new CFClient::MapWidget;
941     $MAPWIDGET->connect (activate_console => sub {
942     my ($mapwidget, $preset) = @_;
943    
944     if ($CONSOLE) {
945     $CONSOLE->{input}->{auto_activated} = 1;
946     $CONSOLE->{input}->focus_in;
947 elmex 1.103
948 root 1.202 if ($preset && $CONSOLE->{input}->get_text eq '') {
949     $CONSOLE->{input}->set_text ($preset);
950     }
951 elmex 1.103 }
952 root 1.202 });
953     $MAPWIDGET->show;
954     $MAPWIDGET->focus_in;
955 root 1.81
956 root 1.229 $LOGVIEW = new CFClient::UI::TextView
957 root 1.236 expand => 1,
958     font => $FONT_FIXED,
959     fontsize => $::CFG->{log_fontsize},
960     can_hover => 1,
961     can_events => 1,
962     tooltip => "<b>Server Log</b>. This text viewer contains all the messages sent by the server.",
963 root 1.229 ;
964    
965 root 1.202 $BUTTONBAR = new CFClient::UI::HBox;
966 root 1.111
967 root 1.236 $BUTTONBAR->add (new CFClient::UI::Flopper text => "Client Setup", other => client_setup,
968     tooltip => "Toggles a dialog where you can configure various aspects of the client, such as graphics mode, performance, and audio options.");
969     $BUTTONBAR->add (new CFClient::UI::Flopper text => "Server Setup", other => server_setup,
970     tooltip => "Toggles a dialog where you can configure the server to play on, your username, password and other server-related options.");
971     $BUTTONBAR->add (new CFClient::UI::Flopper text => "Message Window", other => message_window,
972     tooltip => "Toggles the server message log, where the client collects <i>all</i> messages from the server.");
973 root 1.111
974 root 1.202 make_gauge_window->show; # 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
975 root 1.192
976 root 1.236 $BUTTONBAR->add (new CFClient::UI::Flopper text => "Stats Window", other => make_stats_window,
977     tooltip => "Toggles the statistics window, where all your Stats and Resistances are beign displaye at all times.");
978     $BUTTONBAR->add (new CFClient::UI::Flopper text => "Inventory", other => make_inventory_window,
979     tooltip => "Toggles the inventory window, where you can manage your loot (or treaures :).");
980    
981     $BUTTONBAR->add (new CFClient::UI::Button
982     text => "Save Config",
983     tooltip => "Saves the options chosen in the client setting, server settings and the window layout to be restored on later runs.",
984     connect_activate => sub {
985     CFClient::write_cfg "$Crossfire::VARDIR/pclientrc";
986     status "Configuration Saved";
987     },
988     );
989 root 1.98
990 elmex 1.237 $BUTTONBAR->add (new CFClient::UI::Button
991     text => "Quit",
992     tooltip => "Terminates the program",
993     connect_activate => sub {
994     if ($CONN) {
995     open_quit_dialog
996     } else {
997     exit 1
998     }
999     },
1000     );
1001    
1002 root 1.202 $BUTTONBAR->show;
1003 root 1.187
1004 root 1.215 $STATUSBOX->add ("Set video mode $WIDTH×$HEIGHT", timeout => 10, fg => [1, 1, 1, 0.5]);
1005    
1006 root 1.206 # delay till geometry is constant
1007 root 1.211 $CFClient::UI::ROOT->on_post_alloc (startup => sub {
1008 root 1.206 $BUTTONBAR->{children}[1]->emit ("activate"); # pop up server setup
1009 root 1.215 my $widget = $GAUGES->{win};
1010     $widget->move (0, $HEIGHT - $widget->{h});#d# to in toplevel
1011 root 1.206 });
1012     force_refresh ();
1013 root 1.202 }
1014 root 1.2 }
1015    
1016 root 1.134 sub video_shutdown {
1017 root 1.86 undef $SDL_ACTIVE;
1018 root 1.134 }
1019    
1020 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#
1021 root 1.135 my $bgmusic;#TODO#hack#d#
1022    
1023 root 1.198 sub audio_channel_finished {
1024     my ($channel) = @_;
1025    
1026 root 1.226 #warn "channel $channel finished\n";#d#
1027 root 1.198 }
1028    
1029 root 1.153 sub audio_music_finished {
1030     return unless $CFG->{bgm_enable};
1031    
1032     # TODO: hack, do play loop and mood music
1033     $bgmusic = new_from_file CFClient::MixMusic CFClient::find_rcfile "music/$bgmusic[0]";
1034     $bgmusic->play (0);
1035    
1036     push @bgmusic, shift @bgmusic;
1037     }
1038    
1039 root 1.134 sub audio_init {
1040 root 1.139 if ($CFG->{audio_enable}) {
1041 root 1.195 if (open my $fh, "<", CFClient::find_rcfile "sounds/config") {
1042 root 1.146 $SDL_MIXER = !CFClient::Mix_OpenAudio;
1043 root 1.240
1044     unless ($SDL_MIXER) {
1045     status "Unable to open sound device: there will be no sound";
1046     return;
1047     }
1048    
1049 root 1.146 CFClient::Mix_AllocateChannels 8;
1050 root 1.149 CFClient::MixMusic::volume $CFG->{bgm_volume} * 128;
1051 root 1.134
1052 root 1.153 audio_music_finished;
1053 root 1.135
1054 root 1.134 while (<$fh>) {
1055     next if /^\s*#/;
1056     next if /^\s*$/;
1057    
1058     my ($file, $volume, $event) = split /\s+/, $_, 3;
1059    
1060     push @SOUNDS, "$volume,$file";
1061    
1062     $AUDIO_CHUNKS{"$volume,$file"} ||= do {
1063 root 1.146 my $chunk = new_from_file CFClient::MixChunk CFClient::find_rcfile "sounds/$file";
1064 root 1.134 $chunk->volume ($volume * 128 / 100);
1065     $chunk
1066     };
1067     }
1068     } else {
1069     status "unable to open sound config: $!";
1070     }
1071     }
1072     }
1073    
1074     sub audio_shutdown {
1075 root 1.146 CFClient::Mix_CloseAudio if $SDL_MIXER;
1076 root 1.134 undef $SDL_MIXER;
1077     @SOUNDS = ();
1078     %AUDIO_CHUNKS = ();
1079 root 1.62 }
1080    
1081 root 1.87 my %animate_object;
1082     my $animate_timer;
1083    
1084     my $fps = 9;
1085    
1086 root 1.231 my %demo;#d#
1087    
1088 root 1.30 sub force_refresh {
1089 root 1.215 $fps = $fps * 0.95 + 1 / (($NOW - $LAST_REFRESH) || 0.1) * 0.05;
1090 root 1.87 debug sprintf "%3.2f", $fps;
1091    
1092 root 1.111 $CFClient::UI::ROOT->draw;
1093 root 1.231
1094 root 1.232 $WANT_REFRESH = 0;
1095     $CAN_REFRESH = 0;
1096     $LAST_REFRESH = $NOW;
1097    
1098 root 1.231 0 && do {
1099     # some weird model-drawing code, just a joke right now
1100     use CFClient::OpenGL;
1101    
1102     $demo{t}{eye_auv} ||= new_from_file CFClient::Texture "eye2.png" or die;
1103     $demo{t}{body_auv} ||= new_from_file CFClient::Texture "body_auv3.png" or die;
1104     $demo{r} ||= do {
1105     my $mod = Compress::LZF::sthaw do { local $/; open my $fh, "<:raw:perlio", "dread.lz3"; <$fh> };
1106     $mod->{v} = pack "f*", @{$mod->{v}};
1107     $_ = [scalar @$_, pack "S!*", @$_]
1108     for values %{$mod->{g}};
1109     $mod
1110     };
1111    
1112     my $r = $demo{r} or die;
1113    
1114     glDepthMask 1;
1115     glClear GL_DEPTH_BUFFER_BIT;
1116     glEnable GL_TEXTURE_2D;
1117     glEnable GL_DEPTH_TEST;
1118 root 1.233 glEnable GL_CULL_FACE;
1119 root 1.232 glShadeModel $::FAST ? GL_FLAT : GL_SMOOTH;
1120 root 1.231
1121     glMatrixMode GL_PROJECTION;
1122     glLoadIdentity;
1123 root 1.233 glFrustum -1 * ($::WIDTH / $::HEIGHT), 1 * ($::WIDTH / $::HEIGHT), 1, -1, 1, 10000;
1124     #glOrtho 0, $::WIDTH, 0, $::HEIGHT, -10000, 10000;
1125 root 1.231 glMatrixMode GL_MODELVIEW;
1126     glLoadIdentity;
1127    
1128     glPushMatrix;
1129 root 1.233 glTranslate 0, 0, -800;
1130     glScale 1, -1, 1;
1131     glRotate $NOW * 1000 % 36000 / 5, 0, 1, 0;
1132     glRotate $NOW * 1000 % 36000 / 6, 1, 0, 0;
1133     glRotate $NOW * 1000 % 36000 / 7, 0, 0, 1;
1134 root 1.231 glScale 50, 50, 50;
1135    
1136     glInterleavedArrays GL_T2F_N3F_V3F, 0, $r->{v};
1137     while (my ($k, $v) = each %{$r->{g}}) {
1138     glBindTexture GL_TEXTURE_2D, ($demo{t}{$k}{name} or die);
1139     glDrawElements GL_TRIANGLES, $v->[0], GL_UNSIGNED_SHORT, $v->[1];
1140     }
1141 root 1.232
1142 root 1.231 glPopMatrix;
1143    
1144     glShadeModel GL_FLAT;
1145     glDisable GL_DEPTH_TEST;
1146     glDisable GL_TEXTURE_2D;
1147     glDepthMask 0;
1148    
1149     $WANT_REFRESH++;
1150     };
1151    
1152 root 1.148 CFClient::SDL_GL_SwapBuffers;
1153 root 1.1 }
1154    
1155 root 1.87 my $refresh_watcher = Event->timer (after => 0, hard => 1, interval => 1 / $MAX_FPS, cb => sub {
1156     $NOW = time;
1157    
1158 root 1.147 ($SDL_CB{$_->{type}} || sub { warn "unhandled event $_->{type}" })->($_)
1159     for CFClient::SDL_PollEvent;
1160 root 1.87
1161     if (%animate_object) {
1162     $_->animate ($LAST_REFRESH - $NOW) for values %animate_object;
1163 root 1.206 $WANT_REFRESH++;
1164 root 1.87 }
1165    
1166 root 1.206 if ($WANT_REFRESH) {
1167 root 1.87 force_refresh;
1168     } else {
1169 root 1.206 $CAN_REFRESH = 1;
1170 root 1.87 }
1171     });
1172 root 1.64
1173 root 1.45 sub animation_start {
1174     my ($widget) = @_;
1175 root 1.87 $animate_object{$widget} = $widget;
1176 root 1.45 }
1177    
1178     sub animation_stop {
1179     my ($widget) = @_;
1180 root 1.87 delete $animate_object{$widget};
1181 root 1.45 }
1182    
1183 root 1.2 @conn::ISA = Crossfire::Protocol::;
1184 root 1.1
1185 root 1.226 sub conn::new {
1186     my $class = shift;
1187    
1188     my $self = $class->Crossfire::Protocol::new (@_);
1189    
1190     $MAPWIDGET->clr_commands;
1191    
1192     my $parser = new Pod::POM;
1193     my $pod = $parser->parse_file (CFClient::find_rcfile "pod/command_help.pod");
1194    
1195     for my $head2 ($pod->head2) {
1196     $head2->title =~ /^(\S+) (?:\s+ \( ([^\)]*) \) )?/x
1197     or next;
1198    
1199     my $cmd = $1;
1200     my @args = split /\|/, $2;
1201     @args = (".*") unless @args;
1202    
1203     my $text = CFClient::pod_to_pango $head2->content;
1204    
1205     for my $arg (@args) {
1206     $arg = $arg eq ".*" ? "" : " $arg";
1207    
1208     $MAPWIDGET->add_command ("$cmd$arg", $text);
1209     }
1210     }
1211    
1212 root 1.240 $self->{noface} = new_from_file CFClient::Texture
1213     CFClient::find_rcfile "noface.png", minify => 1, mipmap => 1;
1214    
1215 root 1.226 $self
1216     }
1217    
1218 elmex 1.125 sub conn::stats_update {
1219     my ($self, $stats) = @_;
1220    
1221 root 1.224 if (my $exp = $stats->{Crossfire::Protocol::CS_STAT_EXP64}) {
1222     my $diff = $exp - $self->{prev_exp};
1223     $STATUSBOX->add ("$diff experience gained", group => "experience $diff", fg => [0.5, 1, 0.5, 0.8], timeout => 5)
1224     if exists $self->{prev_exp} && $diff;
1225     $self->{prev_exp} = $exp;
1226     }
1227    
1228 elmex 1.154 update_stats_window ($stats);
1229 elmex 1.125 }
1230    
1231 root 1.89 sub conn::user_send {
1232 root 1.88 my ($self, $command) = @_;
1233    
1234 root 1.123 $self->send_command ($command);
1235 root 1.88 status $command;
1236     }
1237    
1238 root 1.119 sub conn::map_scroll {
1239     my ($self, $dx, $dy) = @_;
1240    
1241     $MAP->scroll ($dx, $dy);
1242     }
1243    
1244 root 1.94 sub conn::feed_map1a {
1245     my ($self, $data) = @_;
1246    
1247 root 1.95 # $self->Crossfire::Protocol::feed_map1a ($data);
1248 root 1.1
1249 root 1.95 $MAP->map1a_update ($data);
1250 root 1.69 $MAPWIDGET->update;
1251 root 1.1 }
1252    
1253 root 1.116 sub conn::flush_map {
1254     my ($self) = @_;
1255    
1256     my $map_info = delete $self->{map_info}
1257     or return;
1258    
1259     my ($hash, $x, $y, $w, $h) = @$map_info;
1260    
1261     my $data = $MAP->get_rect ($x, $y, $w, $h);
1262     $MAPCACHE->put ($hash => Compress::LZF::compress $data);
1263 root 1.152 #warn sprintf "SAVEmap[%s] length %d\n", $hash, length $data;#d#
1264 root 1.116 }
1265 root 1.1
1266 root 1.2 sub conn::map_clear {
1267 root 1.1 my ($self) = @_;
1268    
1269 root 1.116 $self->flush_map;
1270 root 1.150 delete $self->{neigh_map};
1271 root 1.116
1272 root 1.95 $MAP->clear;
1273 root 1.1 }
1274    
1275 root 1.116
1276 root 1.119 sub conn::load_map($$$) {
1277     my ($self, $hash, $x, $y) = @_;
1278 root 1.115
1279 root 1.116 if (defined (my $data = $MAPCACHE->get ($hash))) {
1280     $data = Compress::LZF::decompress $data;
1281 root 1.152 #warn sprintf "LOADmap[%s,%d,%d] length %d\n", $hash, $x, $y, length $data;#d#
1282 root 1.116 for my $id ($MAP->set_rect ($x, $y, $data)) {
1283     my $data = $TILECACHE->get ($id)
1284     or next;
1285    
1286     $self->set_texture ($id => $data);
1287     }
1288     }
1289 root 1.115 }
1290    
1291 root 1.233 # hardcode /world/world_xxx_xxx map names, the savings are enourmous,
1292     # (server resource,s latency, bandwidth), so this hack is warranted.
1293     # the right fix is to make real tiled maps with an overview file
1294     sub conn::send_mapinfo {
1295     my ($self, $data, $cb) = @_;
1296    
1297     if ($self->{map_info}[0] =~ m%^/world/world_(\d\d\d)_(\d\d\d)$%) {
1298     my ($wx, $wy) = ($1, $2);
1299    
1300     if ($data =~ /^spatial ([1-4]+)$/) {
1301     my @dx = (0, 0, 1, 0, -1);
1302     my @dy = (0, -1, 0, 1, 0);
1303     my ($dx, $dy);
1304    
1305     for (split //, $1) {
1306     $dx += $dx[$_];
1307     $dy += $dy[$_];
1308     }
1309    
1310     $cb->(spatial => 15,
1311     $self->{map_info}[1] - $MAP->ox + $dx * 50,
1312     $self->{map_info}[2] - $MAP->oy + $dy * 50,
1313     50, 50,
1314     sprintf "/world/world_%03d_%03d", $wx + $dx, $wy + $dy
1315     );
1316    
1317     return;
1318     }
1319     }
1320    
1321     $self->SUPER::send_mapinfo ($data, $cb);
1322     }
1323    
1324 root 1.152 # this method does a "flood fill" into every tile direction
1325     # it assumes that tiles are arranged in a rectangular grid,
1326     # i.e. a map is the same as the left of the right map etc.
1327     # failure to comply are harmless and result in display errors
1328     # at worst.
1329 root 1.119 sub conn::flood_fill {
1330 root 1.233 my ($self, $block, $gx, $gy, $path, $hash, $flags) = @_;
1331 root 1.119
1332 root 1.121 # the server does not allow map paths > 6
1333 root 1.187 return if 7 <= length $path;
1334 root 1.120
1335 root 1.150 my ($x0, $y0, $x1, $y1) = @{$self->{neigh_rect}};
1336    
1337     for (
1338 root 1.233 [1, 3, 0, -1],
1339     [2, 4, 1, 0],
1340     [3, 1, 0, 1],
1341     [4, 2, -1, 0],
1342 root 1.150 ) {
1343 root 1.233 my ($tile, $tile2, $dx, $dy) = @$_;
1344    
1345     next if $block & (1 << $tile);
1346     my $block = $block | (1 << $tile2);
1347 root 1.150
1348     my $gx = $gx + $dx;
1349     my $gy = $gy + $dy;
1350    
1351 root 1.119 next unless $flags & (1 << ($tile - 1));
1352 root 1.150 next if $self->{neigh_grid}{$gx, $gy}++;
1353 root 1.119
1354 root 1.150 my $neigh = $self->{neigh_map}{$hash} ||= [];
1355     if (my $info = $neigh->[$tile]) {
1356     my ($flags, $x, $y, $w, $h, $hash) = @$info;
1357 root 1.119
1358 root 1.233 $self->flood_fill ($block, $gx, $gy, "$path$tile", $hash, $flags)
1359 root 1.150 if $x >= $x0 && $x + $w < $x1 && $y >= $y0 && $y + $h < $y1;
1360    
1361     } else {
1362     $self->send_mapinfo ("spatial $path$tile", sub {
1363     my ($mode, $flags, $x, $y, $w, $h, $hash) = @_;
1364 root 1.119
1365 root 1.150 return if $mode ne "spatial";
1366 root 1.119
1367 root 1.150 $x += $MAP->ox;
1368     $y += $MAP->oy;
1369 root 1.233
1370 root 1.150 $self->load_map ($hash, $x, $y)
1371     unless $self->{neigh_map}{$hash}[5]++;#d#
1372 root 1.119
1373 root 1.150 $neigh->[$tile] = [$flags, $x, $y, $w, $h, $hash];
1374 root 1.119
1375 root 1.233 $self->flood_fill ($block, $gx, $gy, "$path$tile", $hash, $flags)
1376 root 1.150 if $x >= $x0 && $x + $w < $x1 && $y >= $y0 && $y + $h < $y1;
1377     });
1378     }
1379 root 1.119 }
1380     }
1381    
1382     sub conn::map_change {
1383     my ($self, $mode, $flags, $x, $y, $w, $h, $hash) = @_;
1384    
1385     $self->flush_map;
1386    
1387     my ($ox, $oy) = ($::MAP->ox, $::MAP->oy);
1388    
1389 root 1.187 my $mapmapw = $MAPMAP->{w};
1390     my $mapmaph = $MAPMAP->{h};
1391 root 1.150
1392     $self->{neigh_rect} = [
1393 root 1.152 $ox - $mapmapw * 0.5, $oy - $mapmapw * 0.5,
1394     $ox + $mapmapw * 0.5 + $w, $oy + $mapmapw * 0.5 + $h,
1395 root 1.150 ];
1396 root 1.119
1397 root 1.150 delete $self->{neigh_grid};
1398 root 1.119
1399     $x += $ox;
1400     $y += $oy;
1401    
1402     $self->{map_info} = [$hash, $x, $y, $w, $h];
1403    
1404 root 1.233 (my $map = $hash) =~ s/^.*?\/([^\/]+)$/\1/;
1405 elmex 1.158 $STATWIDS->{map}->set_text ("Map: " . $map);
1406 elmex 1.157
1407 root 1.119 $self->load_map ($hash, $x, $y);
1408 root 1.233 $self->flood_fill (0, 0, 0, "", $hash, $flags);
1409 root 1.119 }
1410    
1411 root 1.19 sub conn::face_find {
1412 root 1.116 my ($self, $facenum, $face) = @_;
1413    
1414     my $hash = "$face->{chksum},$face->{name}";
1415    
1416     my $id = $FACEMAP->get ($hash);
1417    
1418     unless ($id) {
1419     # create new id for face
1420 root 1.240 # I love transactions
1421 root 1.116 for (1..100) {
1422     my $txn = $CFClient::DB_ENV->txn_begin;
1423     my $status = $FACEMAP->db_get (id => $id, BerkeleyDB::DB_RMW);
1424     if ($status == 0 || $status == BerkeleyDB::DB_NOTFOUND) {
1425 root 1.240 $id = ($id || 16) + 1;
1426 root 1.116 if ($FACEMAP->put (id => $id) == 0
1427     && $FACEMAP->put ($hash => $id) == 0) {
1428     $txn->txn_commit;
1429    
1430     goto gotid;
1431     }
1432     }
1433     $txn->abort;
1434     }
1435 root 1.19
1436 root 1.116 CFClient::fatal "maximum number of transaction retries reached - database problems?";
1437     }
1438 root 1.114
1439 root 1.116 gotid:
1440     $face->{id} = $id;
1441     $MAP->set_face ($facenum => $id);
1442 root 1.201 $self->{faceid}[$facenum] = $id;#d#
1443 root 1.240
1444     $TILECACHE->get ($id) || do {
1445     my $tex = $self->{noface};
1446     $MAP->set_texture ($id, @$tex{qw(name w h s t)}, @{$tex->{minified}});
1447     undef
1448     };
1449 root 1.19 }
1450    
1451 root 1.2 sub conn::face_update {
1452 root 1.95 my ($self, $facenum, $face) = @_;
1453 root 1.19
1454 root 1.116 $TILECACHE->put ($face->{id} => $face->{image}); #TODO: try to avoid duplicate writes
1455    
1456     $self->set_texture ($face->{id} => delete $face->{image});
1457     }
1458 root 1.1
1459 root 1.116 sub conn::set_texture {
1460     my ($self, $id, $data) = @_;
1461 root 1.95
1462 root 1.116 $self->{texture}[$id] ||= do {
1463     my $tex =
1464     new_from_image CFClient::Texture
1465 root 1.173 $data, minify => 1, mipmap => 1;
1466 root 1.116
1467     $MAP->set_texture ($id, @$tex{qw(name w h s t)}, @{$tex->{minified}});
1468     $MAPWIDGET->update;
1469    
1470     $tex
1471     };
1472 root 1.1 }
1473    
1474 root 1.134 sub conn::sound_play {
1475     my ($self, $x, $y, $soundnum, $type) = @_;
1476    
1477 root 1.139 $SDL_MIXER
1478     or return;
1479    
1480 root 1.134 my $chunk = $AUDIO_CHUNKS{$SOUNDS[$soundnum]}
1481     or return;
1482    
1483 root 1.146 $chunk->play;
1484 root 1.143 # warn "sound $x,$y,$soundnum,$type\n";#d#
1485 root 1.134 }
1486    
1487 root 1.170 my $LAST_QUERY; # server is stupid, stupid, stupid
1488    
1489 root 1.33 sub conn::query {
1490     my ($self, $flags, $prompt) = @_;
1491    
1492 root 1.170 $prompt = $LAST_QUERY unless length $prompt;
1493     $LAST_QUERY = $prompt;
1494    
1495     my $dialog = new CFClient::UI::FancyFrame
1496     title => "Query",
1497     child => my $vbox = new CFClient::UI::VBox;
1498    
1499     $vbox->add (new CFClient::UI::Label
1500     max_w => $::WIDTH * 0.4,
1501     text => $prompt);
1502    
1503     if ($flags & Crossfire::Protocol::CS_QUERY_YESNO) {
1504     $vbox->add (my $hbox = new CFClient::HBox);
1505     $hbox->add (new CFClient::Button
1506     text => "No",
1507     connect_activate => sub {
1508     $self->send ("reply n");
1509     $dialog->destroy;
1510     $MAPWIDGET->focus_in;
1511     }
1512     );
1513     $hbox->add (new CFClient::Button
1514     text => "Yes",
1515     connect_activate => sub {
1516     $self->send ("reply y");
1517     $dialog->destroy;
1518     $MAPWIDGET->focus_in;
1519     },
1520     );
1521    
1522     $dialog->focus_in;
1523    
1524     } elsif ($flags & Crossfire::Protocol::CS_QUERY_SINGLECHAR) {
1525     $dialog->{tooltip} = "Press a key (click on the entry to make sure it has keyboard focus)";
1526     $vbox->add (my $entry = new CFClient::UI::Entry
1527     connect_changed => sub {
1528     $self->send ("reply $_[1]");
1529     $dialog->destroy;
1530     $MAPWIDGET->focus_in;
1531     },
1532     );
1533    
1534     $entry->focus_in;
1535    
1536     } else {
1537     $dialog->{tooltip} = "Enter the reply and press return (click on the entry to make sure it has keyboard focus)";
1538    
1539     $vbox->add (my $entry = new CFClient::UI::Entry
1540     $flags & Crossfire::Protocol::CS_QUERY_HIDEINPUT ? (hiddenchar => "*") : (),
1541     connect_activate => sub {
1542     $self->send ("reply $_[1]");
1543     $dialog->destroy;
1544     $MAPWIDGET->focus_in;
1545     },
1546     );
1547    
1548     $entry->focus_in;
1549     }
1550    
1551 elmex 1.238 $dialog->show_centered;
1552 root 1.33 }
1553    
1554 root 1.99 sub conn::drawinfo {
1555     my ($self, $color, $text) = @_;
1556    
1557     my @color = (
1558     [1.00, 1.00, 1.00], #[0.00, 0.00, 0.00],
1559     [1.00, 1.00, 1.00],
1560 root 1.117 [0.50, 0.50, 1.00], #[0.00, 0.00, 0.55]
1561 root 1.99 [1.00, 0.00, 0.00],
1562     [1.00, 0.54, 0.00],
1563     [0.11, 0.56, 1.00],
1564     [0.93, 0.46, 0.00],
1565     [0.18, 0.54, 0.34],
1566     [0.56, 0.73, 0.56],
1567     [0.80, 0.80, 0.80],
1568     [0.55, 0.41, 0.13],
1569     [0.99, 0.77, 0.26],
1570     [0.74, 0.65, 0.41],
1571     );
1572    
1573 root 1.208 my $time = sprintf "%02d:%02d:%02d", (localtime time)[2,1,0];
1574    
1575 root 1.219 $text = CFClient::UI::Label::escape $text;
1576 root 1.208 $text =~ s/\[b\](.*?)\[\/b\]/<b>\1<\/b>/g;
1577     $text =~ s/\[color=(.*?)\](.*?)\[\/color\]/<span foreground='\1'>\2<\/span>/g;
1578 root 1.209
1579     $LOGVIEW->add_paragraph ($color[$color],
1580     join "\n", map "$time $_", split /\n/, $text);
1581 root 1.211
1582 root 1.212 $STATUSBOX->add ($text,
1583 root 1.211 group => $text,
1584 root 1.215 fg => $color[$color],
1585 root 1.211 timeout => 60,
1586     tooltip_font => $::FONT_FIXED,
1587     );
1588 root 1.208 }
1589    
1590     sub conn::drawextinfo {
1591     my ($self, $color, $type, $subtype, $message) = @_;
1592    
1593     $self->drawinfo ($color, $message);
1594 root 1.99 }
1595    
1596 root 1.144 sub conn::spell_add {
1597 root 1.143 my ($self, $spell) = @_;
1598    
1599 root 1.171 # TODO
1600     # create a widget dynamically, using spell face (CF::Protocol downloads them)
1601 root 1.224 $MAPWIDGET->add_command ("invoke $spell->{name}", CFClient::UI::Label::escape $spell->{message});
1602     $MAPWIDGET->add_command ("cast $spell->{name}", CFClient::UI::Label::escape $spell->{message});
1603 root 1.144 }
1604    
1605     sub conn::spell_delete {
1606     my ($self, $spell) = @_;
1607     }
1608    
1609     sub conn::addme_success {
1610     my ($self) = @_;
1611    
1612 root 1.226 $self->send ("command output-sync $CFG->{output_sync}");
1613     $self->send ("command output-count $CFG->{output_count}");
1614 root 1.219
1615 root 1.234 my $parser = new Pod::POM;
1616     my $pod = $parser->parse_file (CFClient::find_rcfile "pod/skill_help.pod");
1617    
1618     my %skill_tooltip;
1619    
1620     for my $head2 ($pod->head2) {
1621     $skill_tooltip{$head2->title} = CFClient::pod_to_pango $head2->content;
1622     }
1623    
1624 root 1.144 for my $skill (values %{$self->{skill_info}}) {
1625 root 1.234 $MAPWIDGET->add_command ("ready_skill $skill",
1626     (CFClient::UI::Label::escape "Ready the skill '$skill'\n\n")
1627     . $skill_tooltip{$skill});
1628     $MAPWIDGET->add_command ("use_skill $skill",
1629     (CFClient::UI::Label::escape "Immediately use the skill '$skill'\n\n")
1630     . $skill_tooltip{$skill});
1631 root 1.219 }
1632 root 1.200 }
1633    
1634     sub conn::eof {
1635 root 1.219 $MAPWIDGET->clr_commands;
1636    
1637 root 1.200 stop_game;
1638 root 1.143 }
1639    
1640 root 1.173 sub update_floorbox {
1641     $CFClient::UI::ROOT->on_refresh ($FLOORBOX => sub {
1642 root 1.200 return unless $CONN;
1643    
1644 root 1.173 $FLOORBOX->clear;
1645     $FLOORBOX->add (new CFClient::UI::Empty expand => 1);
1646    
1647 root 1.207 my $count = 4;
1648     for (@{ $CONN->{container}{0} }) {
1649     if (--$count) {
1650     $FLOORBOX->add (new CFClient::UI::InventoryItem item => $_);
1651     } else {
1652     $FLOORBOX->add (new CFClient::UI::Label text => "More...");
1653     last;
1654     }
1655     }
1656 root 1.173 });
1657 root 1.206
1658     $WANT_REFRESH++;
1659 root 1.173 }
1660    
1661 root 1.169 sub conn::container_add {
1662 root 1.203 my ($self, $tag, $items) = @_;
1663    
1664 elmex 1.222 #d# print "container_add: container $tag ($self->{player}{tag})\n";
1665    
1666 elmex 1.217 if ($tag == 0) {
1667     update_floorbox;
1668 elmex 1.222 $OPENCONT = 0;
1669 elmex 1.223 $INVR_LBL->set_text ("Floor");
1670 elmex 1.217 $INVR->set_items ($self->{container}{0});
1671     } elsif ($tag == $self->{player}{tag}) {
1672 elmex 1.223 $INVR_LBL->set_text ("Player");
1673 elmex 1.217 $INV->set_items ($self->{container}{$self->{player}{tag}})
1674     } else {
1675     $OPENCONT = $tag;
1676 elmex 1.223 $INVR_LBL->set_text (CFClient::UI::InventoryItem::_item_to_desc ($self->{item}->{$OPENCONT}));
1677 elmex 1.217 $INVR->set_items ($self->{container}{$tag});
1678     }
1679 root 1.169
1680     # $self-<{player}{tag} => player inv
1681     #use PApp::Util; warn PApp::Util::dumpval $self->{container}{$self->{player}{tag}};
1682     }
1683    
1684     sub conn::container_clear {
1685 root 1.203 my ($self, $tag) = @_;
1686 root 1.173
1687 elmex 1.222 #d# print "container_clear: container $tag ($self->{player}{tag})\n";
1688    
1689 elmex 1.217 if ($tag == 0) {
1690     update_floorbox;
1691 elmex 1.222 $OPENCONT = 0;
1692 elmex 1.223 $INVR_LBL->set_text ("Floor");
1693 elmex 1.217 $INVR->set_items ($self->{container}{0});
1694     } elsif ($tag == $self->{player}{tag}) {
1695 elmex 1.223 $INVR_LBL->set_text ("Player");
1696 elmex 1.217 $INV->set_items ($self->{container}{$tag})
1697     } else {
1698 elmex 1.222 $OPENCONT = $tag;
1699 elmex 1.223 $INVR_LBL->set_text (CFClient::UI::InventoryItem::_item_to_desc ($self->{item}->{$OPENCONT}));
1700 elmex 1.217 $INVR->set_items ($self->{container}{$tag});
1701     }
1702 elmex 1.191
1703 root 1.169 # use PApp::Util; warn PApp::Util::dumpval $self->{container}{0};
1704     }
1705    
1706 root 1.173 sub conn::item_delete {
1707     my ($self, @items) = @_;
1708    
1709     for (@items) {
1710 elmex 1.222 #d# print "item_delete: $_->{tag} from $_->{container} ($self->{player}{tag})\n";
1711    
1712 elmex 1.217 if ($_->{container} == 0) {
1713     update_floorbox;
1714 elmex 1.222 $OPENCONT = 0;
1715 elmex 1.223 $INVR_LBL->set_text ("Floor");
1716 elmex 1.217 $INVR->set_items ($self->{container}{0});
1717     } elsif ($_->{container} == $self->{player}{tag}) {
1718 elmex 1.223 $INVR_LBL->set_text ("Player");
1719 elmex 1.217 $INV->set_items ($self->{container}{$self->{player}{tag}})
1720     } else {
1721 elmex 1.222 $OPENCONT = $_->{container};
1722 elmex 1.223 $INVR_LBL->set_text (CFClient::UI::InventoryItem::_item_to_desc ($self->{item}->{$OPENCONT}));
1723 elmex 1.222 $INVR->set_items ($self->{container}{$_->{container}});
1724 elmex 1.217 }
1725 root 1.173 }
1726     }
1727    
1728     sub conn::item_update {
1729     my ($self, $item) = @_;
1730    
1731 elmex 1.222 #d# print "item_update: $item->{tag} in $item->{container} ($self->{player}{tag}) ($OPENCONT)\n";
1732    
1733     if ($item->{tag} == $OPENCONT && not ($item->{flags} & Crossfire::Protocol::F_OPEN)) {
1734     $OPENCONT = 0;
1735 elmex 1.223 $INVR_LBL->set_text ("Floor");
1736 elmex 1.217 $INVR->set_items ($self->{container}{0});
1737 elmex 1.223
1738     $item->{widget}->update_item
1739     if $item->{widget};
1740 elmex 1.222 } else {
1741     if ($item->{container} == 0) {
1742     update_floorbox;
1743     $OPENCONT = 0;
1744 elmex 1.223 $INVR_LBL->set_text ("Floor");
1745 elmex 1.222 $INVR->set_items ($self->{container}{0});
1746     } elsif ($item->{container} == $self->{player}{tag}) {
1747     $INV->set_items ($self->{container}{$item->{container}})
1748     }
1749 elmex 1.217 }
1750 root 1.173 }
1751    
1752 root 1.87 %SDL_CB = (
1753 root 1.145 CFClient::SDL_QUIT => sub {
1754 root 1.87 Event::unloop -1;
1755     },
1756 root 1.145 CFClient::SDL_VIDEORESIZE => sub {
1757 root 1.87 },
1758 root 1.206 CFClient::SDL_VIDEOEXPOSE => sub {
1759 root 1.236 CFClient::UI::full_refresh;
1760 root 1.206 },
1761 root 1.153 CFClient::SDL_ACTIVEEVENT => sub {
1762     # printf "active %x %x\n", $SDL_EV->active_gain, $SDL_EV->active_state;#d#
1763 root 1.87 },
1764 root 1.145 CFClient::SDL_KEYDOWN => sub {
1765 root 1.147 if ($_[0]{mod} & CFClient::KMOD_ALT && $_[0]{sym} == 13) {
1766 root 1.87 # alt-enter
1767 root 1.134 video_shutdown;
1768 root 1.99 $CFG->{fullscreen} = !$CFG->{fullscreen};
1769 root 1.134 video_init;
1770 root 1.87 } else {
1771 root 1.147 CFClient::UI::feed_sdl_key_down_event ($_[0]);
1772 elmex 1.23 }
1773 root 1.87 },
1774 root 1.198 CFClient::SDL_KEYUP => \&CFClient::UI::feed_sdl_key_up_event,
1775     CFClient::SDL_MOUSEMOTION => \&CFClient::UI::feed_sdl_motion_event,
1776 root 1.153 CFClient::SDL_MOUSEBUTTONDOWN => \&CFClient::UI::feed_sdl_button_down_event,
1777 root 1.198 CFClient::SDL_MOUSEBUTTONUP => \&CFClient::UI::feed_sdl_button_up_event,
1778     CFClient::SDL_USEREVENT => sub {
1779     if ($_[0]{code} == 1) {
1780     audio_channel_finished $_[0]{data1};
1781     } elsif ($_[0]{code} == 0) {
1782     audio_music_finished;
1783     }
1784     },
1785 root 1.87 );
1786 elmex 1.23
1787 root 1.1 #############################################################################
1788    
1789 root 1.131 $SIG{INT} = $SIG{TERM} = sub { exit };
1790    
1791 root 1.205 {
1792     local $SIG{__DIE__} = sub { CFClient::fatal $_[0] };
1793 root 1.194
1794 root 1.205 CFClient::read_cfg "$Crossfire::VARDIR/pclientrc";
1795 root 1.114
1796 root 1.205 $TILECACHE = CFClient::db_table "tilecache";
1797     $FACEMAP = CFClient::db_table "facemap";
1798 root 1.90
1799 root 1.205 my %DEF_CFG = (
1800     sdl_mode => 0,
1801     width => 640,
1802     height => 480,
1803     fullscreen => 0,
1804     fast => 0,
1805 root 1.230 map_scale => 1,
1806 root 1.205 fow_enable => 1,
1807     fow_intensity => 0.45,
1808     fow_smooth => 0,
1809     gui_fontsize => 1,
1810     log_fontsize => 1,
1811 root 1.206 gauge_fontsize=> 1,
1812     gauge_size => 0.35,
1813 root 1.205 stat_fontsize => 1,
1814     mapsize => 100,
1815     host => "crossfire.schmorp.de",
1816     say_command => 'say',
1817     audio_enable => 1,
1818     bgm_enable => 1,
1819     bgm_volume => 0.25,
1820 root 1.226 output_sync => 1,
1821     output_count => 1,
1822 root 1.205 );
1823 root 1.87
1824 root 1.205 while (my ($k, $v) = each %DEF_CFG) {
1825     $CFG->{$k} = $v unless exists $CFG->{$k};
1826     }
1827    
1828     sdl_init;
1829    
1830     @SDL_MODES = reverse
1831     grep $_->[0] >= 640 && $_->[1] >= 480,
1832     CFClient::SDL_ListModes;
1833    
1834     @SDL_MODES or CFClient::fatal "Unable to find a usable video mode\n(hardware accelerated opengl fullscreen)";
1835    
1836     $CFG->{sdl_mode} = 0 if $CFG->{sdl_mode} > @SDL_MODES;
1837    
1838     {
1839     my @fonts = map CFClient::find_rcfile "fonts/$_", qw(
1840     DejaVuSans.ttf
1841     DejaVuSansMono.ttf
1842     DejaVuSans-Bold.ttf
1843     DejaVuSansMono-Bold.ttf
1844     DejaVuSans-Oblique.ttf
1845     DejaVuSansMono-Oblique.ttf
1846     DejaVuSans-BoldOblique.ttf
1847     DejaVuSansMono-BoldOblique.ttf
1848     );
1849    
1850     CFClient::add_font $_ for @fonts;
1851    
1852 root 1.214 CFClient::pango_init;
1853    
1854 root 1.205 $FONT_PROP = new_from_file CFClient::Font $fonts[0];
1855     $FONT_FIXED = new_from_file CFClient::Font $fonts[1];
1856 root 1.89
1857 root 1.205 $FONT_PROP->make_default;
1858     }
1859 root 1.89
1860 root 1.219 # compare mono (ft) vs. rgba (cairo)
1861     # ft - 1.8s, cairo 3s, even in alpha-only mode
1862     # for my $rgba (0..1) {
1863     # my $t1 = Time::HiRes::time;
1864     # for (1..1000) {
1865     # my $layout = CFClient::Layout->new ($rgba);
1866     # $layout->set_text ("hallo" x 100);
1867     # $layout->render;
1868     # }
1869     # my $t2 = Time::HiRes::time;
1870     # warn $t2-$t1;
1871     # }
1872    
1873 root 1.205 video_init;
1874     audio_init;
1875 root 1.65 }
1876 root 1.40
1877 root 1.87 Event::loop;
1878 root 1.19
1879 root 1.148 END { CFClient::SDL_Quit }
1880 root 1.131
1881 root 1.178 =head1 pclient - Crossfire+ and Crossfire game client
1882    
1883     Pclient is a Crossfire+ and Crossfire game client.
1884    
1885     =head2 Features
1886    
1887     =over 4
1888    
1889     =item Fullscreen Map
1890    
1891     PClient can uses a fullscreen map, which greatly enhances how much of the
1892     game world you can see.
1893    
1894     =item Persistent Map Cache (Crossfire+ only)
1895    
1896     PClient can persistently cache all map data it received from the
1897     server. This not only allows it to display an overview map, but also
1898     ensures that once-explored areas will be available the next time you want
1899     to explore more.
1900    
1901     =item Hardware acceleration
1902    
1903     Unlike most Crossfire clients, PClient take advantage of OpenGL hardware
1904     acceleration. Most modern graphics cards have difficulties with 2D
1905     acceleration, while 3D graphics is accelerated well.
1906    
1907     =item No arbitrary limits
1908    
1909     Unlike other Crossfire clients, pclient does not suffer from arbitrary
1910     limits (like a fixed amount of face numbers). There are still limits, but
1911     they are not arbitrarily low :)
1912    
1913     =back
1914    
1915 root 1.179 =head1 USAGE
1916    
1917     =head2 The Map
1918    
1919     The map is always displayed in the background, behind all other windows and UI elements.
1920    
1921     #TODO# middle-click scrolls
1922     #
1923     # keys:
1924     #
1925     # a apply
1926     # keypad moves, kp_5 applies ranged attack to self
1927    
1928     Starting to type enters the I<completion mode>. In that mode, you can type
1929     abbreviations or commands and have them executed as soon as they match a
1930     valid command. This is best explained by a few examples:
1931    
1932     Typing B<climb> will display a list of commands with I<climb> in their
1933     name, such as I<ready_skill climbing> and I<use_skill climbing>.
1934    
1935     You can abbreviate commands by typing only the first character of every
1936 root 1.227 word (or even characters within the word - the client will try to make
1937     a good guess, as long as the characters are in order). For example,
1938     typing I<iwor> will likely select I<invoke word of recall>, while I<ccfo>
1939     will select I<cast create food>. Likewise, I<rscli> will likely select
1940     I<ready_skill climbing> and I<usl> will give you I<use_skill levitation>.
1941    
1942     You can enter space and other text as arguemnt to the command. For
1943     example, C<cfoo waybread> will expand to C<cast create food waybread>.
1944 root 1.179
1945     =head2 The map overview
1946    
1947     #TODO#
1948    
1949     =head2 The Status area in the lower right corner
1950    
1951     #TODO#
1952    
1953     =head2 The I<Statistics>/I>Stats> window
1954    
1955     #TODO#
1956    
1957 root 1.178 =head1 FAQ
1958    
1959     =over 4
1960    
1961     =item The client is very sluggish and slow, what can I do about this?
1962    
1963     Most likely, you don't have accelerated OpenGL support. Try to find a
1964     newer driver, or a driver from your hardware vendor, that features OpenGL
1965     support.
1966    
1967     If this is not an option, the following Setup options reduce the load and
1968     will likely make the client playable with sofwtare rendering (it will
1969     still be slow, though):
1970    
1971     =over 4
1972    
1973     =item B<Video Mode> should be set as low as possible (e.g. 640x480)
1974    
1975     =item Enable B<Fast & Ugly> mode
1976    
1977     =item Disable B<Fog of War>
1978    
1979     =item Increase B<Map Scale>
1980    
1981     =back
1982    
1983     =back
1984    
1985     =head1 AUTHOR
1986    
1987     Marc Lehmann <crossfire@schmorp.de>, Robin Redeker <elmex@ta-sa.org>
1988    
1989    
1990 root 1.82