ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/pclient
Revision: 1.252
Committed: Thu May 25 03:43:48 2006 UTC (18 years ago) by root
Branch: MAIN
Changes since 1.251: +2 -0 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.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.250 $STATUSBOX->add (CFClient::UI::Label::escape $_[0], pri => -10, group => "status", timeout => 10, 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.243 $hbox->add (my $mode_slider = new CFClient::UI::Slider expand => 1, req_w => 100, range => [$CFG->{sdl_mode}, 0, $#SDL_MODES, 0, 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 root 1.243 range => [$CFG->{fow_intensity}, 0, 1, 0, 1 / 256],
236 root 1.163 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.243 range => [$CFG->{gui_fontsize}, 0.5, 2, 0, 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.243 range => [$CFG->{log_fontsize}, 0.5, 2, 0, 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.243 range => [$CFG->{stat_fontsize}, 0.5, 2, 0, 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.243 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Gauge fontsize");
280 root 1.163 $table->add (1, $row++, new CFClient::UI::Slider
281 root 1.243 range => [$CFG->{gauge_fontsize}, 0.5, 2, 0, 0.1],
282     tooltip => "Adjusts the fontsize of the gauges at the bottom right",
283 root 1.163 connect_changed => sub {
284 root 1.243 $CFG->{gauge_fontsize} = $_[1];
285     &set_gauge_window_fontsize;
286 root 1.163 }
287     );
288 elmex 1.158
289 root 1.243 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Gauge size");
290 root 1.163 $table->add (1, $row++, new CFClient::UI::Slider
291 root 1.243 range => [$CFG->{gauge_size}, 0.2, 0.8],
292     tooltip => "Adjust the size of the stats gauges at the bottom right",
293 root 1.163 connect_changed => sub {
294 root 1.243 $CFG->{gauge_size} = $_[1];
295     $GAUGES->{win}->set_size ($WIDTH, int $HEIGHT * $CFG->{gauge_size});
296 root 1.163 }
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 root 1.243 expand => 1, range => [$CFG->{bgm_volume}, 0, 1, 0, 1/128],
331 root 1.163 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 root 1.247 $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Chat Command");
348 elmex 1.188 $table->add (1, $row++, my $saycmd = new CFClient::UI::Entry
349     text => $CFG->{say_command},
350 root 1.247 tooltip => "This is the command that will be used if you write a line in the message window entry or press <b>\"</b> in the map window. "
351     . "Usually you want to enter something like 'say' or 'shout' or 'gsay' here. "
352     . "But you could also set it to <b>tell <i>playername</i></b> to only chat with that user.",
353 elmex 1.188 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     }
373    
374     sub make_gauge_window {
375 root 1.215 my $gh = int $HEIGHT * $CFG->{gauge_size};
376 elmex 1.158
377     my $win = new CFClient::UI::Frame (
378 root 1.215 req_y => -1,
379     user_w => $WIDTH,
380     user_h => $gh,
381 elmex 1.158 );
382 root 1.215
383 root 1.173 $win->add (my $hbox = new CFClient::UI::HBox
384     children => [
385     (new CFClient::UI::HBox expand => 1),
386 root 1.212 (new CFClient::UI::VBox children => [
387     (new CFClient::UI::Empty expand => 1),
388     (new CFClient::UI::Frame bg => [0, 0, 0, 0.4], child => ($FLOORBOX = new CFClient::UI::VBox)),
389     ]),
390 root 1.173 (my $vbox = new CFClient::UI::VBox),
391     ],
392     );
393 elmex 1.158
394 root 1.173 $vbox->add (new CFClient::UI::HBox
395     expand => 1,
396     children => [
397     (new CFClient::UI::Empty expand => 1),
398     (my $hb = new CFClient::UI::HBox),
399     ],
400     );
401 elmex 1.161
402 root 1.172 $hb->add (my $hg = new CFClient::UI::Gauge type => 'hp',
403 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.");
404 root 1.172 $hb->add (my $mg = new CFClient::UI::Gauge type => 'mana',
405 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.");
406 root 1.172 $hb->add (my $gg = new CFClient::UI::Gauge type => 'grace',
407 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.");
408 root 1.172 $hb->add (my $fg = new CFClient::UI::Gauge type => 'food',
409 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.");
410 root 1.172
411 root 1.173 $vbox->add (my $exp = new CFClient::UI::Label valign => 0, align => 1, can_hover => 1, can_events => 1,
412 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.");
413 root 1.173 $vbox->add (my $rng = new CFClient::UI::Label valign => 0, align => 1, can_hover => 1, can_events => 1,
414 root 1.236 tooltip => "<b>Ranged attack</b> - how you attack when you press shift-cursor (spell, skill, weapon etc.)");
415 elmex 1.158
416     $GAUGES = {
417 elmex 1.166 exp => $exp, win => $win, range => $rng,
418 elmex 1.158 food => $fg, mana => $mg, hp => $hg, grace => $gg
419     };
420 root 1.169
421     &set_gauge_window_fontsize;
422    
423 elmex 1.158 $win
424     }
425    
426 elmex 1.154 sub make_stats_window {
427 root 1.241 my $tgw = new CFClient::UI::FancyFrame title => "Stats";
428 root 1.155
429 root 1.185 $tgw->add (new CFClient::UI::Window child => my $vb = new CFClient::UI::VBox);
430 root 1.236 $vb->add ($STATWIDS->{title} = new CFClient::UI::Label valign => 0, align => -1, text => "Title:", expand => 1,
431     can_hover => 1, can_events => 1,
432     tooltip => "Your name and title. You can change your title by using the <b>title</b> command, if supported by the server.");
433     $vb->add ($STATWIDS->{map} = new CFClient::UI::Label valign => 0, align => -1, text => "Map:", expand => 1,
434     can_hover => 1, can_events => 1,
435     tooltip => "The map you are currently on (if supported by the server).");
436 elmex 1.156
437     $vb->add (my $hb = new CFClient::UI::HBox expand => 1);
438     $hb->add (my $tbl = new CFClient::UI::Table expand => 1);
439    
440 root 1.241 my $color2 = [1, 1, 0];
441 root 1.174
442 root 1.180 for (
443 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"],
444     [0, 1, st_dex => "Dex", 30, "<b>Dexterity</b>, your physical agility. Determines chance of being hit and affects armor class and speed"],
445     [0, 2, st_con => "Con", 30, "<b>Constitution</b>, physical health and toughness. Determines how many healthpoints you can have"],
446     [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"],
447     [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"],
448     [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"],
449     [0, 6, st_cha => "Cha", 30, "<b>Charisma</b>, how well you are received by NPCs. Affects buying and selling prices in shops."],
450    
451     [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."],
452     [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."],
453     [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."],
454     [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."],
455     [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."],
456     [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."],
457 root 1.180 ) {
458     my ($col, $row, $id, $label, $template, $tooltip) = @$_;
459    
460     $tbl->add ($col , $row, $STATWIDS->{$id} = new CFClient::UI::Label
461 root 1.184 font => $FONT_FIXED, can_hover => 1, can_events => 1, valign => 0, align => +1, template => $template, tooltip => $tooltip);
462 root 1.180 $tbl->add ($col + 1, $row, $STATWIDS->{"$id\_lbl"} = new CFClient::UI::Label
463 root 1.241 font => $FONT_FIXED, can_hover => 1, can_events => 1, fg => $color2, valign => 0, align => -1, text => $label, tooltip => $tooltip);
464 root 1.180 }
465 root 1.155
466 elmex 1.158 $hb->add (my $tbl2 = new CFClient::UI::Table expand => 1);
467 root 1.155
468 elmex 1.156 my $row = 0;
469     my $col = 0;
470 root 1.155
471 elmex 1.166 my %resist_names = (
472 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.)",
473     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.)",
474     conf => "<b>Confusion</b> (If you are hit by confusion you will move into random directions, and likely into monsters.)",
475     fire => "<b>Fire</b> (just your resistance to fire spells like burning hands, dragonbreath, meteor swarm fire, ...)",
476     depl => "<b>Depletion</b> (some monsters and other effects can cause stats depletion)",
477     magic => "<b>Magic</b> (resistance to magic spells like magic missile or similar)",
478     drain => "<b>Draining</b> (some monsters (e.g. vampires) and other effects can steal experience)",
479     acid => "<b>Acid</b> (resistance to acid, acid hurts pretty much and also corrodes your weapons)",
480     pois => "<b>Poison</b> (resistance to getting poisoned)",
481     para => "<b>Paralysation</b> (this resistance affects the chance you get paralysed)",
482     deat => "<b>Death</b> (resistance against death spells)",
483     phys => "<b>Physical</b> (this is the resistance against physical attacks, like when a monster hit you in melee combat)",
484     blind => "<b>Blind</b> (blind resistance affects the chance of a successful blinding attack)",
485     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)",
486     tund => "<b>Turn undead</b> (affects your resistancy to various forms of 'turn undead' spells. Only relevant when you are, in fact, undead...",
487     elec => "<b>Electricity</b> (resistance against electricity, spells like large lightning, small lightning, ...)",
488     cold => "<b>Cold</b> (this is your resistance against cold spells like icestorm, snowstorm, ...)",
489     ghit => "<b>Ghost hit</b> (special attack used by ghosts and ghost-like beings)",
490 elmex 1.166 );
491 elmex 1.156 for (qw/slow holyw conf fire depl magic
492     drain acid pois para deat phys
493     blind fear tund elec cold ghit/)
494     {
495 root 1.164 $tbl2->add ($col, $row,
496 elmex 1.156 $STATWIDS->{"res_$_"} =
497 root 1.168 new CFClient::UI::Label
498 root 1.184 font => $FONT_FIXED,
499 root 1.180 template => "-100%",
500     align => +1,
501     valign => 0,
502     can_events => 1,
503     can_hover => 1,
504     tooltip => $resist_names{$_},
505 root 1.168 );
506     $tbl2->add ($col + 1, $row, new CFClient::UI::Image
507 root 1.185 font => $FONT_FIXED,
508 root 1.180 can_hover => 1,
509 root 1.168 can_events => 1,
510 root 1.180 image => "ui/resist/resist_$_.png",
511     tooltip => $resist_names{$_},
512 elmex 1.156 );
513    
514     $row++;
515     if ($row % 6 == 0) {
516     $col += 2;
517     $row = 0;
518     }
519     }
520    
521 elmex 1.157 &set_stats_window_fontsize;
522 elmex 1.156 update_stats_window ({});
523 root 1.155
524 elmex 1.154 $tgw
525     }
526    
527 root 1.169 sub formsep {
528     reverse join ",", grep length, split /(...)/, reverse $_[0] * 1
529     }
530    
531 elmex 1.154 sub update_stats_window {
532     my ($stats) = @_;
533    
534 elmex 1.156 # i love text protocols!!!
535 root 1.169 my $hp = $stats->{Crossfire::Protocol::CS_STAT_HP} * 1;
536     my $hp_m = $stats->{Crossfire::Protocol::CS_STAT_MAXHP} * 1;
537     my $sp = $stats->{Crossfire::Protocol::CS_STAT_SP} * 1;
538     my $sp_m = $stats->{Crossfire::Protocol::CS_STAT_MAXSP} * 1;
539     my $fo = $stats->{Crossfire::Protocol::CS_STAT_FOOD} * 1;
540 elmex 1.156 my $fo_m = 999;
541 root 1.169 my $gr = $stats->{Crossfire::Protocol::CS_STAT_GRACE} * 1;
542     my $gr_m = $stats->{Crossfire::Protocol::CS_STAT_MAXGRACE} * 1;
543 elmex 1.156
544     $GAUGES->{hp} ->set_value ($hp, $hp_m);
545     $GAUGES->{mana} ->set_value ($sp, $sp_m);
546     $GAUGES->{food} ->set_value ($fo, $fo_m);
547     $GAUGES->{grace} ->set_value ($gr, $gr_m);
548 root 1.169 $GAUGES->{exp} ->set_text ("Exp: " . (formsep $stats->{Crossfire::Protocol::CS_STAT_EXP64})
549     . " (lvl " . ($stats->{Crossfire::Protocol::CS_STAT_LEVEL} * 1) . ")");
550     my $rng = $stats->{Crossfire::Protocol::CS_STAT_RANGE};
551 elmex 1.159 $rng =~ s/^Range: //; # thank you so much dear server
552     $GAUGES->{range} ->set_text ("Rng: " . $rng);
553 root 1.169 my $title = $stats->{Crossfire::Protocol::CS_STAT_TITLE};
554 elmex 1.165 $title =~ s/^Player: //;
555     $STATWIDS->{title} ->set_text ("Title: " . $title);
556 elmex 1.156
557 root 1.169 $STATWIDS->{st_str} ->set_text (sprintf "%d", $stats->{5});
558     $STATWIDS->{st_dex} ->set_text (sprintf "%d", $stats->{8});
559     $STATWIDS->{st_con} ->set_text (sprintf "%d", $stats->{9});
560     $STATWIDS->{st_int} ->set_text (sprintf "%d", $stats->{6});
561     $STATWIDS->{st_wis} ->set_text (sprintf "%d", $stats->{7});
562     $STATWIDS->{st_pow} ->set_text (sprintf "%d", $stats->{22});
563     $STATWIDS->{st_cha} ->set_text (sprintf "%d", $stats->{10});
564     $STATWIDS->{st_wc} ->set_text (sprintf "%d", $stats->{13});
565     $STATWIDS->{st_ac} ->set_text (sprintf "%d", $stats->{14});
566     $STATWIDS->{st_dam} ->set_text (sprintf "%d", $stats->{15});
567     $STATWIDS->{st_arm} ->set_text (sprintf "%d", $stats->{16});
568     $STATWIDS->{st_spd} ->set_text (sprintf "%.1f", $stats->{Crossfire::Protocol::CS_STAT_SPEED});
569     $STATWIDS->{st_wspd}->set_text (sprintf "%.1f", $stats->{Crossfire::Protocol::CS_STAT_WEAP_SP});
570 elmex 1.156
571     my %tbl = (
572     phys => 100,
573     magic => 101,
574     fire => 102,
575     elec => 103,
576     cold => 104,
577     conf => 105,
578     acid => 106,
579     drain => 107,
580     ghit => 108,
581     pois => 109,
582     slow => 110,
583     para => 111,
584     tund => 112,
585     fear => 113,
586 elmex 1.165 depl => 113,
587 elmex 1.156 deat => 115,
588     holyw => 116,
589     blind => 117
590 elmex 1.154 );
591 elmex 1.156
592     for (keys %tbl) {
593     $STATWIDS->{"res_$_"}->set_text (sprintf "%d%", $stats->{$tbl{$_}});
594     }
595    
596 elmex 1.154 }
597    
598 root 1.112 sub metaserver_dialog {
599     my $dialog = new CFClient::UI::FancyFrame
600 root 1.199 title => "Server List",
601 root 1.112 child => (my $vbox = new CFClient::UI::VBox);
602    
603     $vbox->add ($dialog->{table} = new CFClient::UI::Table);
604    
605     $dialog
606     }
607    
608 root 1.179 my $METASERVER_ATIME;
609    
610 root 1.112 sub update_metaserver {
611 root 1.114 my ($HOST) = @_;
612    
613 root 1.179 return if $METASERVER_ATIME > time;
614     $METASERVER_ATIME = time + 60;
615    
616 root 1.178 my $table = $METASERVER->{table};
617     $table->clear;
618 root 1.179 $table->add (0, 0, my $label = new CFClient::UI::Label max_w => $WIDTH * 0.8, text => "fetching server list...");
619 root 1.112
620     my $buf;
621    
622     my $fh = new IO::Socket::INET PeerHost => $META_SERVER, Blocking => 0;
623    
624 root 1.178 unless ($fh) {
625     $label->set_text ("unable to contact metaserver: $!");
626     return;
627     }
628    
629 root 1.112 Event->io (fd => $fh, poll => 'r', cb => sub {
630     my $res = sysread $fh, $buf, 8192, length $buf;
631    
632     if (!defined $res) {
633     $_[0]->w->cancel;
634 root 1.178 $label->set_text ("error while retrieving server list: $!");
635 root 1.112 } elsif ($res == 0) {
636     $_[0]->w->cancel;
637     status "server list retrieved";
638 root 1.113
639 root 1.178 utf8::decode $buf if utf8::valid $buf;
640 root 1.113
641     $table->clear;
642    
643 root 1.114 my @col = qw(Use #Users Host Uptime Version Description);
644 root 1.113 $table->add ($_, 0, new CFClient::UI::Label align => 0, fg => [1, 1, 0], text => $col[$_])
645     for 0 .. $#col;
646    
647     my @align = qw(1 0 1 1 -1);
648    
649     my $y = 0;
650 root 1.114 for my $m (sort { $b->[3] <=> $a->[3] } map [split /\|/], split /\015?\012/, $buf) {
651 root 1.113 my ($ip, $last, $host, $users, $version, $desc, $ibytes, $obytes, $uptime) = @$m;
652    
653     for ($desc) {
654     s/<br>/\n/gi;
655     s/<li>/\n· /gi;
656     s/<.*?>//sgi;
657     s/&/&amp;/g;
658     s/</&lt;/g;
659     s/>/&gt;/g;
660     }
661    
662     $uptime = sprintf "%dd %02d:%02d:%02d",
663     (int $m->[8] / 86400),
664     (int $m->[8] / 3600) % 24,
665     (int $m->[8] / 60) % 60,
666     $m->[8] % 60;
667    
668     $m = [$users, $host, $uptime, $version, $desc];
669    
670     $y++;
671 root 1.114
672     $table->add (0, $y, new CFClient::UI::VBox children => [
673 root 1.178 (new CFClient::UI::Button text => "Use", connect_activate => sub {
674 root 1.114 $HOST->set_text ($CFG->{host} = $host);
675     }),
676     (new CFClient::UI::Empty expand => 1),
677     ]);
678    
679 root 1.224 $table->add ($_ + 1, $y, new CFClient::UI::Label
680     ellipsise => 0, align => $align[$_], text => $m->[$_], fontsize => 0.8)
681 root 1.113 for 0 .. $#$m;
682     }
683 root 1.112 }
684     });
685     }
686    
687 root 1.111 sub server_setup {
688     my $dialog = new CFClient::UI::FancyFrame
689 root 1.150 title => "Server Setup",
690 root 1.111 child => (my $vbox = new CFClient::UI::VBox);
691 root 1.81
692 root 1.82 $vbox->add (my $table = new CFClient::UI::Table expand => 1, col_expand => [0, 1]);
693 root 1.141 $table->add (0, 2, new CFClient::UI::Label valign => 0, align => 1, text => "Host:Port");
694 root 1.112
695     {
696     $table->add (1, 2, my $vbox = new CFClient::UI::VBox);
697    
698 elmex 1.166 $vbox->add (
699     my $HOST = new CFClient::UI::Entry
700     expand => 1,
701     text => $CFG->{host},
702     tooltip => "The hostname or ip address of the Crossfire(+) server to connect to",
703     connect_changed => sub {
704     my ($self, $value) = @_;
705     $CFG->{host} = $value;
706     }
707     );
708 root 1.112
709     $METASERVER = metaserver_dialog;
710 elmex 1.101
711 elmex 1.166 $vbox->add (new CFClient::UI::Flopper
712     expand => 1,
713 root 1.200 text => "Server List",
714 elmex 1.166 other => $METASERVER,
715 root 1.200 tooltip => "Show a list of available crossfire servers",
716 elmex 1.166 connect_open => sub {
717     update_metaserver $HOST;
718     }
719     );
720 root 1.112 }
721 root 1.81
722 root 1.141 $table->add (0, 4, new CFClient::UI::Label valign => 0, align => 1, text => "Username");
723 elmex 1.166 $table->add (1, 4, new CFClient::UI::Entry
724     text => $CFG->{user},
725     tooltip => "The name of your character on the server",
726     connect_changed => sub {
727     my ($self, $value) = @_;
728     $CFG->{user} = $value;
729     }
730     );
731 root 1.81
732 root 1.141 $table->add (0, 5, new CFClient::UI::Label valign => 0, align => 1, text => "Password");
733 elmex 1.166 $table->add (1, 5, new CFClient::UI::Entry
734     text => $CFG->{password},
735     hidden => 1,
736     tooltip => "The password for your character",
737     connect_changed => sub {
738     my ($self, $value) = @_;
739     $CFG->{password} = $value;
740     }
741     );
742 elmex 1.101
743 root 1.141 $table->add (0, 7, new CFClient::UI::Label valign => 0, align => 1, text => "Map Size");
744 elmex 1.101 $table->add (1, 7, new CFClient::UI::Slider
745 root 1.81 req_w => 100,
746 root 1.243 range => [$CFG->{mapsize}, 10, 100, 0, 1],
747 elmex 1.166 tooltip => "This is the size of the portion of the map update the server sends you. "
748 root 1.250 . "If you set this to a high value you will be able to see further, "
749     . "but you also increase bandwidht requirements and latency. "
750     . "This option is only used once at log-in.",
751 root 1.81 connect_changed => sub {
752     my ($self, $value) = @_;
753    
754     $CFG->{mapsize} = $self->{range}[0] = $value = int $value;
755     },
756     );
757    
758 root 1.250 $table->add (0, 8, new CFClient::UI::Label valign => 0, align => 1, text => "Face Prefetch");
759     $table->add (1, 8, new CFClient::UI::CheckBox
760     state => $CFG->{face_prefetch},
761     tooltip => "<b>Background Image Prefetch</b>\n\n"
762 root 1.251 . "If enabled, the client automatically pre-fetches images from the server. "
763 root 1.250 . "This might increase or create lag, but increases the chances "
764     . "of faces being ready for display when you encounter them. "
765     . "It also uses up server bandwidth on every connect, "
766     . "so only set it if you really need to prefetch images. "
767     . "This option can be set and unset any time.",
768     connect_changed => sub { $CFG->{face_prefetch} = $_[1] },
769     );
770    
771     $table->add (0, 9, new CFClient::UI::Label valign => 0, align => 1, text => "Output-Count");
772     $table->add (1, 9, new CFClient::UI::Entry
773 root 1.226 text => $CFG->{output_count},
774 root 1.250 tooltip => "Should be set to 1 unless you know what you are doing. This option is only used once at log-in.",
775 root 1.226 connect_changed => sub { $CFG->{output_count} = $_[1] },
776     );
777    
778 root 1.250 $table->add (0, 10, new CFClient::UI::Label valign => 0, align => 1, text => "Output-Sync");
779     $table->add (1, 10, new CFClient::UI::Entry
780 root 1.226 text => $CFG->{output_sync},
781 root 1.250 tooltip => "Should be set to 1 unless you know what you are doing. This option is only used once at log-in.",
782 root 1.226 connect_changed => sub { $CFG->{output_sync} = $_[1] },
783     );
784    
785 root 1.250 $table->add (1, 11, $LOGIN_BUTTON = new CFClient::UI::Button
786 root 1.199 expand => 1,
787     align => 0,
788     text => "Login",
789     connect_activate => sub {
790     $CONN ? stop_game
791     : start_game;
792     },
793     );
794 root 1.82
795 root 1.98 $dialog
796 root 1.81 }
797 root 1.58
798 root 1.111 sub message_window {
799 root 1.99 my $window = new CFClient::UI::FancyFrame
800 root 1.150 title => "Messages",
801 root 1.186 border_bg => [1, 1, 1, 1],
802 root 1.236 bg => [0, 0, 0, 0.75],
803 root 1.124 user_w => int $::WIDTH / 3,
804     user_h => int $::HEIGHT / 5,
805 root 1.99 child => (my $vbox = new CFClient::UI::VBox);
806    
807 root 1.229 $vbox->add ($LOGVIEW);
808 root 1.105
809 root 1.122 $vbox->add (my $input = new CFClient::UI::Entry
810 root 1.236 tooltip => "<b>Chat Box</b>. If you enter a text and press return/enter here, the current <i>communication command</i> "
811     . "from the client setup will be prepended (e.g. <b>shout</b>, <b>chat</b>...). "
812     . "If you prepend a slash (/), you will submit a command instead (similar to IRC). "
813     . "A better way to submit commands (and the occasional chat command) is often the map command completer.",
814 elmex 1.118 connect_focus_in => sub {
815     my ($input, $prev_focus) = @_;
816    
817     delete $input->{refocus_map};
818    
819     if ($prev_focus == $MAPWIDGET && $input->{auto_activated}) {
820     $input->{refocus_map} = 1;
821     }
822     delete $input->{auto_activated};
823     },
824 root 1.116 connect_activate => sub {
825 elmex 1.100 my ($input, $text) = @_;
826     $input->set_text ('');
827    
828     if ($text =~ /^\/(.*)/) {
829 root 1.123 $::CONN->user_send ($1);
830 elmex 1.100 } else {
831 elmex 1.101 my $say_cmd = $::CFG->{say_command} || 'say';
832 root 1.123 $::CONN->user_send ("$say_cmd $text");
833 elmex 1.100 }
834 elmex 1.118 if ($input->{refocus_map}) {
835     delete $input->{refocus_map};
836     $MAPWIDGET->focus_in
837     }
838 root 1.116 },
839     connect_escape => sub {
840 elmex 1.102 $MAPWIDGET->focus_in
841 root 1.116 },
842     );
843 elmex 1.102
844     $CONSOLE = {
845     window => $window,
846     input => $input
847     };
848 root 1.99
849     $window
850     }
851    
852 elmex 1.237 sub open_quit_dialog {
853     unless ($QUIT_DIALOG) {
854    
855 elmex 1.238 $QUIT_DIALOG = new CFClient::UI::FancyFrame title => "Really Quit?";
856 elmex 1.237
857     $QUIT_DIALOG->add (my $vb = new CFClient::UI::VBox expand => 1);
858    
859     $vb->add (new CFClient::UI::Label
860 root 1.246 text => "You should find a savebed and apply it first!",
861     max_w => $WIDTH * 0.25,
862     ellipsize => 0,
863 elmex 1.237 );
864     $vb->add (my $hb = new CFClient::UI::HBox expand => 1);
865     $hb->add (new CFClient::UI::Button
866     text => "Ok",
867 root 1.246 expand => 1,
868 elmex 1.237 connect_activate => sub { $QUIT_DIALOG->hide },
869     );
870     $hb->add (new CFClient::UI::Button
871     text => "Quit anyway",
872 root 1.246 expand => 1,
873     connect_activate => sub { exit },
874 elmex 1.237 );
875    
876 elmex 1.238 $QUIT_DIALOG->show_centered;
877 elmex 1.237 } else {
878 elmex 1.238 $QUIT_DIALOG->show_centered;
879 elmex 1.237 }
880     }
881    
882 elmex 1.191 sub make_inventory_window {
883 elmex 1.217 my $invwin = new CFClient::UI::FancyFrame
884 elmex 1.239 user_w => $WIDTH * (7/8), user_h => $HEIGHT * (7/8), title => "Inventory";
885 elmex 1.217
886 root 1.230 $invwin->add (my $hb = new CFClient::UI::HBox expand => 1);
887 elmex 1.223
888     $hb->add (my $vb1 = new CFClient::UI::VBox expand => 1);
889     $vb1->add (my $lbl = new CFClient::UI::Label);
890     $lbl->set_text ("Player");
891     $vb1->add ($INV = new CFClient::UI::Inventory expand => 1);
892    
893     $hb->add (my $vb2 = new CFClient::UI::VBox expand => 1);
894     $vb2->add ($INVR_LBL = new CFClient::UI::Label);
895     $INVR_LBL->set_text ("Floor");
896     $vb2->add ($INVR = new CFClient::UI::Inventory expand => 1);
897 elmex 1.217
898 elmex 1.191 $invwin
899     }
900    
901 root 1.241 sub make_help_window {
902     my $win = new CFClient::UI::FancyFrame
903     user_w => $WIDTH * (7/8), user_h => $HEIGHT * (7/8), title => "Documentation";
904    
905     $win->add (my $vbox = new CFClient::UI::VBox);
906    
907     $vbox->add (my $buttons = new CFClient::UI::HBox);
908     $vbox->add (my $viewer = new CFClient::UI::TextView expand => 1, fontsize => 0.8);
909    
910     for (
911     [intro => "Introduction"],
912 root 1.248 [manual => "Manual"],
913 root 1.241 [command_help => "Commands"],
914     [skill_help => "Skills"],
915     ) {
916     my ($pod, $label) = @$_;
917    
918     $buttons->add (new CFClient::UI::Button
919     text => $label,
920     connect_activate => sub {
921     my $parser = new Pod::POM;
922     my $pom = $parser->parse_file (CFClient::find_rcfile "pod/$pod.pod");
923    
924     $viewer->clear;
925    
926     $viewer->add_paragraph ([1, 1, 1, 1], $_->[1], $_->[0])
927     for @{ CFClient::pod_to_pango_list $pom };
928 root 1.243
929     $viewer->set_offset (0);
930 root 1.241 },
931     );
932     }
933    
934     $viewer->add_paragraph ([1, 1, 0, 1], "<big>Use one of the buttons above to display a document.</big>");
935    
936     $win
937     }
938    
939 root 1.89 sub sdl_init {
940 root 1.145 CFClient::SDL_Init
941 root 1.89 and die "SDL::Init failed!\n";
942     }
943    
944 root 1.134 sub video_init {
945 root 1.89 sdl_init;
946    
947 root 1.197 $CFG->{sdl_mode} = 0 if $CFG->{sdl_mode} >= @SDL_MODES;
948    
949 root 1.245 my ($old_w, $old_h) = ($WIDTH, $HEIGHT);
950    
951 root 1.84 ($WIDTH, $HEIGHT) = @{ $SDL_MODES[$CFG->{sdl_mode}] };
952     $FULLSCREEN = $CFG->{fullscreen};
953 root 1.89 $FAST = $CFG->{fast};
954 root 1.84
955 root 1.145 CFClient::SDL_SetVideoMode $WIDTH, $HEIGHT, $FULLSCREEN
956 root 1.230 or die "SDL_SetVideoMode failed: " . (CFClient::SDL_GetError) . "\n";
957 root 1.2
958 root 1.86 $SDL_ACTIVE = 1;
959 root 1.87 $LAST_REFRESH = time - 0.01;
960 root 1.45
961 root 1.67 CFClient::gl_init;
962 root 1.30
963 root 1.140 $FONTSIZE = int $HEIGHT / 40 * $CFG->{gui_fontsize};
964 root 1.39
965 root 1.202 $CFClient::UI::ROOT->configure (0, 0, $WIDTH, $HEIGHT);#d#
966    
967 root 1.52 #############################################################################
968    
969 root 1.245 if ($DEBUG_STATUS) {
970     CFClient::UI::rescale_widgets $WIDTH / $old_w, $HEIGHT / $old_h;
971     } else {
972 root 1.202 # create the widgets
973    
974 root 1.215 $DEBUG_STATUS = new CFClient::UI::Label padding => 0, z => 100, req_x => -1;
975 root 1.202 $DEBUG_STATUS->show;
976    
977 root 1.212 $STATUSBOX = new CFClient::UI::Statusbox;
978 root 1.215 $STATUSBOX->add ("Use <b>Alt-Enter</b> to toggle fullscreen mode", pri => -100, color => [1, 1, 1, 0.8]);
979 root 1.212
980     (new CFClient::UI::Frame
981 root 1.215 bg => [0, 0, 0, 0.4],
982     req_y => -1,
983     child => $STATUSBOX,
984 root 1.212 )->show;
985 root 1.202
986     CFClient::UI::FancyFrame->new (
987     border_bg => [1, 1, 1, 192/255],
988     bg => [1, 1, 1, 0],
989 root 1.236 child => ($MAPMAP = new CFClient::MapWidget::MapMap
990     tooltip => "<b>Map</b>. On servers that support this feature, this will display an overview of the surrounding areas.",
991     ),
992 root 1.202 )->show;
993    
994     $MAPWIDGET = new CFClient::MapWidget;
995     $MAPWIDGET->connect (activate_console => sub {
996     my ($mapwidget, $preset) = @_;
997    
998     if ($CONSOLE) {
999     $CONSOLE->{input}->{auto_activated} = 1;
1000     $CONSOLE->{input}->focus_in;
1001 elmex 1.103
1002 root 1.202 if ($preset && $CONSOLE->{input}->get_text eq '') {
1003     $CONSOLE->{input}->set_text ($preset);
1004     }
1005 elmex 1.103 }
1006 root 1.202 });
1007     $MAPWIDGET->show;
1008     $MAPWIDGET->focus_in;
1009 root 1.81
1010 root 1.229 $LOGVIEW = new CFClient::UI::TextView
1011 root 1.236 expand => 1,
1012     font => $FONT_FIXED,
1013     fontsize => $::CFG->{log_fontsize},
1014     can_hover => 1,
1015     can_events => 1,
1016     tooltip => "<b>Server Log</b>. This text viewer contains all the messages sent by the server.",
1017 root 1.229 ;
1018    
1019 root 1.202 $BUTTONBAR = new CFClient::UI::HBox;
1020 root 1.111
1021 root 1.236 $BUTTONBAR->add (new CFClient::UI::Flopper text => "Client Setup", other => client_setup,
1022     tooltip => "Toggles a dialog where you can configure various aspects of the client, such as graphics mode, performance, and audio options.");
1023     $BUTTONBAR->add (new CFClient::UI::Flopper text => "Server Setup", other => server_setup,
1024     tooltip => "Toggles a dialog where you can configure the server to play on, your username, password and other server-related options.");
1025     $BUTTONBAR->add (new CFClient::UI::Flopper text => "Message Window", other => message_window,
1026     tooltip => "Toggles the server message log, where the client collects <i>all</i> messages from the server.");
1027 root 1.111
1028 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
1029 root 1.192
1030 root 1.236 $BUTTONBAR->add (new CFClient::UI::Flopper text => "Stats Window", other => make_stats_window,
1031 root 1.249 tooltip => "Toggles the statistics window, where all your Stats and Resistances are being displayed at all times.");
1032 root 1.236 $BUTTONBAR->add (new CFClient::UI::Flopper text => "Inventory", other => make_inventory_window,
1033     tooltip => "Toggles the inventory window, where you can manage your loot (or treaures :).");
1034    
1035     $BUTTONBAR->add (new CFClient::UI::Button
1036     text => "Save Config",
1037     tooltip => "Saves the options chosen in the client setting, server settings and the window layout to be restored on later runs.",
1038     connect_activate => sub {
1039     CFClient::write_cfg "$Crossfire::VARDIR/pclientrc";
1040     status "Configuration Saved";
1041     },
1042     );
1043 root 1.98
1044 root 1.241 $BUTTONBAR->add (new CFClient::UI::Flopper text => "Help!", other => make_help_window,
1045     tooltip => "View Documentation");
1046    
1047 elmex 1.237 $BUTTONBAR->add (new CFClient::UI::Button
1048     text => "Quit",
1049     tooltip => "Terminates the program",
1050     connect_activate => sub {
1051     if ($CONN) {
1052 root 1.241 open_quit_dialog;
1053 elmex 1.237 } else {
1054 root 1.241 exit;
1055 elmex 1.237 }
1056     },
1057     );
1058    
1059 root 1.202 $BUTTONBAR->show;
1060 root 1.187
1061 root 1.215 $STATUSBOX->add ("Set video mode $WIDTH×$HEIGHT", timeout => 10, fg => [1, 1, 1, 0.5]);
1062    
1063 root 1.206 # delay till geometry is constant
1064 root 1.211 $CFClient::UI::ROOT->on_post_alloc (startup => sub {
1065 root 1.206 $BUTTONBAR->{children}[1]->emit ("activate"); # pop up server setup
1066 root 1.215 my $widget = $GAUGES->{win};
1067     $widget->move (0, $HEIGHT - $widget->{h});#d# to in toplevel
1068 root 1.206 });
1069     force_refresh ();
1070 root 1.202 }
1071 root 1.2 }
1072    
1073 root 1.134 sub video_shutdown {
1074 root 1.86 undef $SDL_ACTIVE;
1075 root 1.134 }
1076    
1077 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#
1078 root 1.135 my $bgmusic;#TODO#hack#d#
1079    
1080 root 1.198 sub audio_channel_finished {
1081     my ($channel) = @_;
1082    
1083 root 1.226 #warn "channel $channel finished\n";#d#
1084 root 1.198 }
1085    
1086 root 1.153 sub audio_music_finished {
1087     return unless $CFG->{bgm_enable};
1088    
1089     # TODO: hack, do play loop and mood music
1090     $bgmusic = new_from_file CFClient::MixMusic CFClient::find_rcfile "music/$bgmusic[0]";
1091     $bgmusic->play (0);
1092    
1093     push @bgmusic, shift @bgmusic;
1094     }
1095    
1096 root 1.134 sub audio_init {
1097 root 1.139 if ($CFG->{audio_enable}) {
1098 root 1.195 if (open my $fh, "<", CFClient::find_rcfile "sounds/config") {
1099 root 1.146 $SDL_MIXER = !CFClient::Mix_OpenAudio;
1100 root 1.240
1101     unless ($SDL_MIXER) {
1102     status "Unable to open sound device: there will be no sound";
1103     return;
1104     }
1105    
1106 root 1.146 CFClient::Mix_AllocateChannels 8;
1107 root 1.149 CFClient::MixMusic::volume $CFG->{bgm_volume} * 128;
1108 root 1.134
1109 root 1.153 audio_music_finished;
1110 root 1.135
1111 root 1.134 while (<$fh>) {
1112     next if /^\s*#/;
1113     next if /^\s*$/;
1114    
1115     my ($file, $volume, $event) = split /\s+/, $_, 3;
1116    
1117     push @SOUNDS, "$volume,$file";
1118    
1119     $AUDIO_CHUNKS{"$volume,$file"} ||= do {
1120 root 1.146 my $chunk = new_from_file CFClient::MixChunk CFClient::find_rcfile "sounds/$file";
1121 root 1.134 $chunk->volume ($volume * 128 / 100);
1122     $chunk
1123     };
1124     }
1125     } else {
1126     status "unable to open sound config: $!";
1127     }
1128     }
1129     }
1130    
1131     sub audio_shutdown {
1132 root 1.146 CFClient::Mix_CloseAudio if $SDL_MIXER;
1133 root 1.134 undef $SDL_MIXER;
1134     @SOUNDS = ();
1135     %AUDIO_CHUNKS = ();
1136 root 1.62 }
1137    
1138 root 1.87 my %animate_object;
1139     my $animate_timer;
1140    
1141     my $fps = 9;
1142    
1143 root 1.231 my %demo;#d#
1144    
1145 root 1.30 sub force_refresh {
1146 root 1.215 $fps = $fps * 0.95 + 1 / (($NOW - $LAST_REFRESH) || 0.1) * 0.05;
1147 root 1.87 debug sprintf "%3.2f", $fps;
1148    
1149 root 1.111 $CFClient::UI::ROOT->draw;
1150 root 1.231
1151 root 1.232 $WANT_REFRESH = 0;
1152     $CAN_REFRESH = 0;
1153     $LAST_REFRESH = $NOW;
1154    
1155 root 1.231 0 && do {
1156     # some weird model-drawing code, just a joke right now
1157     use CFClient::OpenGL;
1158    
1159     $demo{t}{eye_auv} ||= new_from_file CFClient::Texture "eye2.png" or die;
1160     $demo{t}{body_auv} ||= new_from_file CFClient::Texture "body_auv3.png" or die;
1161     $demo{r} ||= do {
1162     my $mod = Compress::LZF::sthaw do { local $/; open my $fh, "<:raw:perlio", "dread.lz3"; <$fh> };
1163     $mod->{v} = pack "f*", @{$mod->{v}};
1164     $_ = [scalar @$_, pack "S!*", @$_]
1165     for values %{$mod->{g}};
1166     $mod
1167     };
1168    
1169     my $r = $demo{r} or die;
1170    
1171     glDepthMask 1;
1172     glClear GL_DEPTH_BUFFER_BIT;
1173     glEnable GL_TEXTURE_2D;
1174     glEnable GL_DEPTH_TEST;
1175 root 1.233 glEnable GL_CULL_FACE;
1176 root 1.232 glShadeModel $::FAST ? GL_FLAT : GL_SMOOTH;
1177 root 1.231
1178     glMatrixMode GL_PROJECTION;
1179     glLoadIdentity;
1180 root 1.233 glFrustum -1 * ($::WIDTH / $::HEIGHT), 1 * ($::WIDTH / $::HEIGHT), 1, -1, 1, 10000;
1181     #glOrtho 0, $::WIDTH, 0, $::HEIGHT, -10000, 10000;
1182 root 1.231 glMatrixMode GL_MODELVIEW;
1183     glLoadIdentity;
1184    
1185     glPushMatrix;
1186 root 1.233 glTranslate 0, 0, -800;
1187     glScale 1, -1, 1;
1188     glRotate $NOW * 1000 % 36000 / 5, 0, 1, 0;
1189     glRotate $NOW * 1000 % 36000 / 6, 1, 0, 0;
1190     glRotate $NOW * 1000 % 36000 / 7, 0, 0, 1;
1191 root 1.231 glScale 50, 50, 50;
1192    
1193     glInterleavedArrays GL_T2F_N3F_V3F, 0, $r->{v};
1194     while (my ($k, $v) = each %{$r->{g}}) {
1195     glBindTexture GL_TEXTURE_2D, ($demo{t}{$k}{name} or die);
1196     glDrawElements GL_TRIANGLES, $v->[0], GL_UNSIGNED_SHORT, $v->[1];
1197     }
1198 root 1.232
1199 root 1.231 glPopMatrix;
1200    
1201     glShadeModel GL_FLAT;
1202     glDisable GL_DEPTH_TEST;
1203     glDisable GL_TEXTURE_2D;
1204     glDepthMask 0;
1205    
1206     $WANT_REFRESH++;
1207     };
1208    
1209 root 1.148 CFClient::SDL_GL_SwapBuffers;
1210 root 1.1 }
1211    
1212 root 1.87 my $refresh_watcher = Event->timer (after => 0, hard => 1, interval => 1 / $MAX_FPS, cb => sub {
1213     $NOW = time;
1214    
1215 root 1.147 ($SDL_CB{$_->{type}} || sub { warn "unhandled event $_->{type}" })->($_)
1216     for CFClient::SDL_PollEvent;
1217 root 1.87
1218     if (%animate_object) {
1219     $_->animate ($LAST_REFRESH - $NOW) for values %animate_object;
1220 root 1.206 $WANT_REFRESH++;
1221 root 1.87 }
1222    
1223 root 1.206 if ($WANT_REFRESH) {
1224 root 1.87 force_refresh;
1225     } else {
1226 root 1.206 $CAN_REFRESH = 1;
1227 root 1.87 }
1228     });
1229 root 1.64
1230 root 1.45 sub animation_start {
1231     my ($widget) = @_;
1232 root 1.87 $animate_object{$widget} = $widget;
1233 root 1.45 }
1234    
1235     sub animation_stop {
1236     my ($widget) = @_;
1237 root 1.87 delete $animate_object{$widget};
1238 root 1.45 }
1239    
1240 root 1.2 @conn::ISA = Crossfire::Protocol::;
1241 root 1.1
1242 root 1.226 sub conn::new {
1243     my $class = shift;
1244    
1245     my $self = $class->Crossfire::Protocol::new (@_);
1246    
1247     $MAPWIDGET->clr_commands;
1248    
1249     my $parser = new Pod::POM;
1250     my $pod = $parser->parse_file (CFClient::find_rcfile "pod/command_help.pod");
1251    
1252     for my $head2 ($pod->head2) {
1253     $head2->title =~ /^(\S+) (?:\s+ \( ([^\)]*) \) )?/x
1254     or next;
1255    
1256     my $cmd = $1;
1257     my @args = split /\|/, $2;
1258     @args = (".*") unless @args;
1259    
1260     my $text = CFClient::pod_to_pango $head2->content;
1261    
1262     for my $arg (@args) {
1263     $arg = $arg eq ".*" ? "" : " $arg";
1264    
1265     $MAPWIDGET->add_command ("$cmd$arg", $text);
1266     }
1267     }
1268    
1269 root 1.240 $self->{noface} = new_from_file CFClient::Texture
1270     CFClient::find_rcfile "noface.png", minify => 1, mipmap => 1;
1271    
1272 root 1.226 $self
1273     }
1274    
1275 elmex 1.125 sub conn::stats_update {
1276     my ($self, $stats) = @_;
1277    
1278 root 1.224 if (my $exp = $stats->{Crossfire::Protocol::CS_STAT_EXP64}) {
1279     my $diff = $exp - $self->{prev_exp};
1280     $STATUSBOX->add ("$diff experience gained", group => "experience $diff", fg => [0.5, 1, 0.5, 0.8], timeout => 5)
1281     if exists $self->{prev_exp} && $diff;
1282     $self->{prev_exp} = $exp;
1283     }
1284    
1285 elmex 1.154 update_stats_window ($stats);
1286 elmex 1.125 }
1287    
1288 root 1.89 sub conn::user_send {
1289 root 1.88 my ($self, $command) = @_;
1290    
1291 root 1.123 $self->send_command ($command);
1292 root 1.88 status $command;
1293     }
1294    
1295 root 1.119 sub conn::map_scroll {
1296     my ($self, $dx, $dy) = @_;
1297    
1298     $MAP->scroll ($dx, $dy);
1299     }
1300    
1301 root 1.94 sub conn::feed_map1a {
1302     my ($self, $data) = @_;
1303    
1304 root 1.95 # $self->Crossfire::Protocol::feed_map1a ($data);
1305 root 1.1
1306 root 1.95 $MAP->map1a_update ($data);
1307 root 1.69 $MAPWIDGET->update;
1308 root 1.1 }
1309    
1310 root 1.116 sub conn::flush_map {
1311     my ($self) = @_;
1312    
1313     my $map_info = delete $self->{map_info}
1314     or return;
1315    
1316     my ($hash, $x, $y, $w, $h) = @$map_info;
1317    
1318     my $data = $MAP->get_rect ($x, $y, $w, $h);
1319     $MAPCACHE->put ($hash => Compress::LZF::compress $data);
1320 root 1.152 #warn sprintf "SAVEmap[%s] length %d\n", $hash, length $data;#d#
1321 root 1.116 }
1322 root 1.1
1323 root 1.2 sub conn::map_clear {
1324 root 1.1 my ($self) = @_;
1325    
1326 root 1.116 $self->flush_map;
1327 root 1.150 delete $self->{neigh_map};
1328 root 1.116
1329 root 1.95 $MAP->clear;
1330 root 1.1 }
1331    
1332 root 1.116
1333 root 1.119 sub conn::load_map($$$) {
1334     my ($self, $hash, $x, $y) = @_;
1335 root 1.115
1336 root 1.116 if (defined (my $data = $MAPCACHE->get ($hash))) {
1337     $data = Compress::LZF::decompress $data;
1338 root 1.152 #warn sprintf "LOADmap[%s,%d,%d] length %d\n", $hash, $x, $y, length $data;#d#
1339 root 1.116 for my $id ($MAP->set_rect ($x, $y, $data)) {
1340     my $data = $TILECACHE->get ($id)
1341     or next;
1342    
1343     $self->set_texture ($id => $data);
1344     }
1345     }
1346 root 1.115 }
1347    
1348 root 1.233 # hardcode /world/world_xxx_xxx map names, the savings are enourmous,
1349     # (server resource,s latency, bandwidth), so this hack is warranted.
1350     # the right fix is to make real tiled maps with an overview file
1351     sub conn::send_mapinfo {
1352     my ($self, $data, $cb) = @_;
1353    
1354     if ($self->{map_info}[0] =~ m%^/world/world_(\d\d\d)_(\d\d\d)$%) {
1355     my ($wx, $wy) = ($1, $2);
1356    
1357     if ($data =~ /^spatial ([1-4]+)$/) {
1358     my @dx = (0, 0, 1, 0, -1);
1359     my @dy = (0, -1, 0, 1, 0);
1360     my ($dx, $dy);
1361    
1362     for (split //, $1) {
1363     $dx += $dx[$_];
1364     $dy += $dy[$_];
1365     }
1366    
1367     $cb->(spatial => 15,
1368     $self->{map_info}[1] - $MAP->ox + $dx * 50,
1369     $self->{map_info}[2] - $MAP->oy + $dy * 50,
1370     50, 50,
1371     sprintf "/world/world_%03d_%03d", $wx + $dx, $wy + $dy
1372     );
1373    
1374     return;
1375     }
1376     }
1377    
1378 root 1.244 $self->Crossfire::Protocol::send_mapinfo ($data, $cb);
1379 root 1.233 }
1380    
1381 root 1.152 # this method does a "flood fill" into every tile direction
1382     # it assumes that tiles are arranged in a rectangular grid,
1383     # i.e. a map is the same as the left of the right map etc.
1384     # failure to comply are harmless and result in display errors
1385     # at worst.
1386 root 1.119 sub conn::flood_fill {
1387 root 1.233 my ($self, $block, $gx, $gy, $path, $hash, $flags) = @_;
1388 root 1.119
1389 root 1.121 # the server does not allow map paths > 6
1390 root 1.187 return if 7 <= length $path;
1391 root 1.120
1392 root 1.150 my ($x0, $y0, $x1, $y1) = @{$self->{neigh_rect}};
1393    
1394     for (
1395 root 1.233 [1, 3, 0, -1],
1396     [2, 4, 1, 0],
1397     [3, 1, 0, 1],
1398     [4, 2, -1, 0],
1399 root 1.150 ) {
1400 root 1.233 my ($tile, $tile2, $dx, $dy) = @$_;
1401    
1402     next if $block & (1 << $tile);
1403     my $block = $block | (1 << $tile2);
1404 root 1.150
1405     my $gx = $gx + $dx;
1406     my $gy = $gy + $dy;
1407    
1408 root 1.119 next unless $flags & (1 << ($tile - 1));
1409 root 1.150 next if $self->{neigh_grid}{$gx, $gy}++;
1410 root 1.119
1411 root 1.150 my $neigh = $self->{neigh_map}{$hash} ||= [];
1412     if (my $info = $neigh->[$tile]) {
1413     my ($flags, $x, $y, $w, $h, $hash) = @$info;
1414 root 1.119
1415 root 1.233 $self->flood_fill ($block, $gx, $gy, "$path$tile", $hash, $flags)
1416 root 1.150 if $x >= $x0 && $x + $w < $x1 && $y >= $y0 && $y + $h < $y1;
1417    
1418     } else {
1419     $self->send_mapinfo ("spatial $path$tile", sub {
1420     my ($mode, $flags, $x, $y, $w, $h, $hash) = @_;
1421 root 1.119
1422 root 1.150 return if $mode ne "spatial";
1423 root 1.119
1424 root 1.150 $x += $MAP->ox;
1425     $y += $MAP->oy;
1426 root 1.233
1427 root 1.150 $self->load_map ($hash, $x, $y)
1428     unless $self->{neigh_map}{$hash}[5]++;#d#
1429 root 1.119
1430 root 1.150 $neigh->[$tile] = [$flags, $x, $y, $w, $h, $hash];
1431 root 1.119
1432 root 1.233 $self->flood_fill ($block, $gx, $gy, "$path$tile", $hash, $flags)
1433 root 1.150 if $x >= $x0 && $x + $w < $x1 && $y >= $y0 && $y + $h < $y1;
1434     });
1435     }
1436 root 1.119 }
1437     }
1438    
1439     sub conn::map_change {
1440     my ($self, $mode, $flags, $x, $y, $w, $h, $hash) = @_;
1441    
1442     $self->flush_map;
1443    
1444     my ($ox, $oy) = ($::MAP->ox, $::MAP->oy);
1445    
1446 root 1.187 my $mapmapw = $MAPMAP->{w};
1447     my $mapmaph = $MAPMAP->{h};
1448 root 1.150
1449     $self->{neigh_rect} = [
1450 root 1.152 $ox - $mapmapw * 0.5, $oy - $mapmapw * 0.5,
1451     $ox + $mapmapw * 0.5 + $w, $oy + $mapmapw * 0.5 + $h,
1452 root 1.150 ];
1453 root 1.119
1454 root 1.150 delete $self->{neigh_grid};
1455 root 1.119
1456     $x += $ox;
1457     $y += $oy;
1458    
1459     $self->{map_info} = [$hash, $x, $y, $w, $h];
1460    
1461 root 1.233 (my $map = $hash) =~ s/^.*?\/([^\/]+)$/\1/;
1462 elmex 1.158 $STATWIDS->{map}->set_text ("Map: " . $map);
1463 elmex 1.157
1464 root 1.119 $self->load_map ($hash, $x, $y);
1465 root 1.233 $self->flood_fill (0, 0, 0, "", $hash, $flags);
1466 root 1.119 }
1467    
1468 root 1.19 sub conn::face_find {
1469 root 1.116 my ($self, $facenum, $face) = @_;
1470    
1471     my $hash = "$face->{chksum},$face->{name}";
1472    
1473     my $id = $FACEMAP->get ($hash);
1474    
1475     unless ($id) {
1476     # create new id for face
1477 root 1.240 # I love transactions
1478 root 1.116 for (1..100) {
1479     my $txn = $CFClient::DB_ENV->txn_begin;
1480     my $status = $FACEMAP->db_get (id => $id, BerkeleyDB::DB_RMW);
1481     if ($status == 0 || $status == BerkeleyDB::DB_NOTFOUND) {
1482 root 1.240 $id = ($id || 16) + 1;
1483 root 1.116 if ($FACEMAP->put (id => $id) == 0
1484     && $FACEMAP->put ($hash => $id) == 0) {
1485     $txn->txn_commit;
1486    
1487     goto gotid;
1488     }
1489     }
1490     $txn->abort;
1491     }
1492 root 1.19
1493 root 1.116 CFClient::fatal "maximum number of transaction retries reached - database problems?";
1494     }
1495 root 1.114
1496 root 1.116 gotid:
1497     $face->{id} = $id;
1498     $MAP->set_face ($facenum => $id);
1499 root 1.201 $self->{faceid}[$facenum] = $id;#d#
1500 root 1.240
1501 root 1.250 my $face = $TILECACHE->get ($id);
1502    
1503     if ($face) {
1504     $self->face_prefetch;
1505     $face
1506     } else {
1507 root 1.240 my $tex = $self->{noface};
1508     $MAP->set_texture ($id, @$tex{qw(name w h s t)}, @{$tex->{minified}});
1509     undef
1510     };
1511 root 1.19 }
1512    
1513 root 1.2 sub conn::face_update {
1514 root 1.95 my ($self, $facenum, $face) = @_;
1515 root 1.19
1516 root 1.116 $TILECACHE->put ($face->{id} => $face->{image}); #TODO: try to avoid duplicate writes
1517    
1518     $self->set_texture ($face->{id} => delete $face->{image});
1519     }
1520 root 1.1
1521 root 1.116 sub conn::set_texture {
1522     my ($self, $id, $data) = @_;
1523 root 1.95
1524 root 1.116 $self->{texture}[$id] ||= do {
1525     my $tex =
1526     new_from_image CFClient::Texture
1527 root 1.173 $data, minify => 1, mipmap => 1;
1528 root 1.116
1529     $MAP->set_texture ($id, @$tex{qw(name w h s t)}, @{$tex->{minified}});
1530     $MAPWIDGET->update;
1531    
1532     $tex
1533     };
1534 root 1.1 }
1535    
1536 root 1.134 sub conn::sound_play {
1537     my ($self, $x, $y, $soundnum, $type) = @_;
1538    
1539 root 1.139 $SDL_MIXER
1540     or return;
1541    
1542 root 1.134 my $chunk = $AUDIO_CHUNKS{$SOUNDS[$soundnum]}
1543     or return;
1544    
1545 root 1.146 $chunk->play;
1546 root 1.143 # warn "sound $x,$y,$soundnum,$type\n";#d#
1547 root 1.134 }
1548    
1549 root 1.170 my $LAST_QUERY; # server is stupid, stupid, stupid
1550    
1551 root 1.33 sub conn::query {
1552     my ($self, $flags, $prompt) = @_;
1553    
1554 root 1.170 $prompt = $LAST_QUERY unless length $prompt;
1555     $LAST_QUERY = $prompt;
1556    
1557     my $dialog = new CFClient::UI::FancyFrame
1558     title => "Query",
1559     child => my $vbox = new CFClient::UI::VBox;
1560    
1561     $vbox->add (new CFClient::UI::Label
1562     max_w => $::WIDTH * 0.4,
1563 root 1.249 ellipsise => 0,
1564 root 1.170 text => $prompt);
1565    
1566     if ($flags & Crossfire::Protocol::CS_QUERY_YESNO) {
1567     $vbox->add (my $hbox = new CFClient::HBox);
1568     $hbox->add (new CFClient::Button
1569     text => "No",
1570     connect_activate => sub {
1571     $self->send ("reply n");
1572     $dialog->destroy;
1573     $MAPWIDGET->focus_in;
1574     }
1575     );
1576     $hbox->add (new CFClient::Button
1577     text => "Yes",
1578     connect_activate => sub {
1579     $self->send ("reply y");
1580     $dialog->destroy;
1581     },
1582     );
1583    
1584     $dialog->focus_in;
1585    
1586     } elsif ($flags & Crossfire::Protocol::CS_QUERY_SINGLECHAR) {
1587     $dialog->{tooltip} = "Press a key (click on the entry to make sure it has keyboard focus)";
1588     $vbox->add (my $entry = new CFClient::UI::Entry
1589     connect_changed => sub {
1590     $self->send ("reply $_[1]");
1591     $dialog->destroy;
1592     },
1593     );
1594    
1595     $entry->focus_in;
1596    
1597     } else {
1598     $dialog->{tooltip} = "Enter the reply and press return (click on the entry to make sure it has keyboard focus)";
1599    
1600     $vbox->add (my $entry = new CFClient::UI::Entry
1601     $flags & Crossfire::Protocol::CS_QUERY_HIDEINPUT ? (hiddenchar => "*") : (),
1602     connect_activate => sub {
1603     $self->send ("reply $_[1]");
1604     $dialog->destroy;
1605     },
1606     );
1607    
1608     $entry->focus_in;
1609     }
1610    
1611 elmex 1.238 $dialog->show_centered;
1612 root 1.33 }
1613    
1614 root 1.99 sub conn::drawinfo {
1615     my ($self, $color, $text) = @_;
1616    
1617     my @color = (
1618     [1.00, 1.00, 1.00], #[0.00, 0.00, 0.00],
1619     [1.00, 1.00, 1.00],
1620 root 1.117 [0.50, 0.50, 1.00], #[0.00, 0.00, 0.55]
1621 root 1.99 [1.00, 0.00, 0.00],
1622     [1.00, 0.54, 0.00],
1623     [0.11, 0.56, 1.00],
1624     [0.93, 0.46, 0.00],
1625     [0.18, 0.54, 0.34],
1626     [0.56, 0.73, 0.56],
1627     [0.80, 0.80, 0.80],
1628     [0.55, 0.41, 0.13],
1629     [0.99, 0.77, 0.26],
1630     [0.74, 0.65, 0.41],
1631     );
1632    
1633 root 1.208 my $time = sprintf "%02d:%02d:%02d", (localtime time)[2,1,0];
1634    
1635 root 1.219 $text = CFClient::UI::Label::escape $text;
1636 root 1.208 $text =~ s/\[b\](.*?)\[\/b\]/<b>\1<\/b>/g;
1637     $text =~ s/\[color=(.*?)\](.*?)\[\/color\]/<span foreground='\1'>\2<\/span>/g;
1638 root 1.209
1639     $LOGVIEW->add_paragraph ($color[$color],
1640     join "\n", map "$time $_", split /\n/, $text);
1641 root 1.211
1642 root 1.212 $STATUSBOX->add ($text,
1643 root 1.211 group => $text,
1644 root 1.215 fg => $color[$color],
1645 root 1.250 timeout => 10,
1646 root 1.211 tooltip_font => $::FONT_FIXED,
1647     );
1648 root 1.208 }
1649    
1650     sub conn::drawextinfo {
1651     my ($self, $color, $type, $subtype, $message) = @_;
1652    
1653     $self->drawinfo ($color, $message);
1654 root 1.99 }
1655    
1656 root 1.144 sub conn::spell_add {
1657 root 1.143 my ($self, $spell) = @_;
1658    
1659 root 1.171 # TODO
1660     # create a widget dynamically, using spell face (CF::Protocol downloads them)
1661 root 1.224 $MAPWIDGET->add_command ("invoke $spell->{name}", CFClient::UI::Label::escape $spell->{message});
1662     $MAPWIDGET->add_command ("cast $spell->{name}", CFClient::UI::Label::escape $spell->{message});
1663 root 1.144 }
1664    
1665     sub conn::spell_delete {
1666     my ($self, $spell) = @_;
1667     }
1668    
1669     sub conn::addme_success {
1670     my ($self) = @_;
1671    
1672 root 1.226 $self->send ("command output-sync $CFG->{output_sync}");
1673     $self->send ("command output-count $CFG->{output_count}");
1674 root 1.219
1675 root 1.234 my $parser = new Pod::POM;
1676     my $pod = $parser->parse_file (CFClient::find_rcfile "pod/skill_help.pod");
1677    
1678     my %skill_tooltip;
1679    
1680     for my $head2 ($pod->head2) {
1681     $skill_tooltip{$head2->title} = CFClient::pod_to_pango $head2->content;
1682     }
1683    
1684 root 1.144 for my $skill (values %{$self->{skill_info}}) {
1685 root 1.234 $MAPWIDGET->add_command ("ready_skill $skill",
1686     (CFClient::UI::Label::escape "Ready the skill '$skill'\n\n")
1687     . $skill_tooltip{$skill});
1688     $MAPWIDGET->add_command ("use_skill $skill",
1689     (CFClient::UI::Label::escape "Immediately use the skill '$skill'\n\n")
1690     . $skill_tooltip{$skill});
1691 root 1.219 }
1692 root 1.200 }
1693    
1694     sub conn::eof {
1695 root 1.219 $MAPWIDGET->clr_commands;
1696    
1697 root 1.200 stop_game;
1698 root 1.143 }
1699    
1700 root 1.250 sub conn::image_info {
1701     my ($self, $numfaces) = @_;
1702    
1703     $self->{num_faces} = $numfaces;
1704     $self->{face_prefetch} = [1 .. $numfaces];
1705     $self->face_prefetch;
1706     }
1707    
1708     sub conn::face_prefetch {
1709     my ($self) = @_;
1710    
1711     return unless $CFG->{face_prefetch};
1712    
1713     if ($self->{num_faces}) {
1714     return if @{ $self->{send_queue} || [] };
1715     my $todo = @{ $self->{face_prefetch} }
1716     or return;
1717    
1718     my ($face) = splice @{ $self->{face_prefetch} }, 1 + rand @{ $self->{face_prefetch} }, 1, ();
1719    
1720     $self->send ("requestinfo image_sums $face $face");
1721    
1722     $STATUSBOX->add (CFClient::UI::Label::escape "prefetching $todo",
1723     group => "prefetch", timeout => 2, fg => [1, 1, 0, 0.5]);
1724     } elsif (!exists $self->{num_faces}) {
1725     $self->send ("requestinfo image_info");
1726    
1727     $self->{num_faces} = 0;
1728    
1729     $STATUSBOX->add (CFClient::UI::Label::escape "starting to prefetch",
1730     group => "prefetch", timeout => 2, fg => [1, 1, 0, 0.5]);
1731     }
1732     }
1733    
1734     # check once/second for faces that need to be prefetched
1735 root 1.252 # this should, of course, only run on demand, but
1736     # SDL forces worse things on us....
1737 root 1.250
1738     Event->timer (after => 1, interval => 1, cb => sub {
1739     $CONN->face_prefetch
1740     if $CONN;
1741     });
1742    
1743 root 1.173 sub update_floorbox {
1744     $CFClient::UI::ROOT->on_refresh ($FLOORBOX => sub {
1745 root 1.200 return unless $CONN;
1746    
1747 root 1.173 $FLOORBOX->clear;
1748     $FLOORBOX->add (new CFClient::UI::Empty expand => 1);
1749    
1750 root 1.242 my $count = 7;
1751 root 1.207 for (@{ $CONN->{container}{0} }) {
1752     if (--$count) {
1753     $FLOORBOX->add (new CFClient::UI::InventoryItem item => $_);
1754     } else {
1755     $FLOORBOX->add (new CFClient::UI::Label text => "More...");
1756     last;
1757     }
1758     }
1759 root 1.173 });
1760 root 1.206
1761     $WANT_REFRESH++;
1762 root 1.173 }
1763    
1764 root 1.169 sub conn::container_add {
1765 root 1.203 my ($self, $tag, $items) = @_;
1766    
1767 elmex 1.222 #d# print "container_add: container $tag ($self->{player}{tag})\n";
1768    
1769 elmex 1.217 if ($tag == 0) {
1770     update_floorbox;
1771 elmex 1.222 $OPENCONT = 0;
1772 elmex 1.223 $INVR_LBL->set_text ("Floor");
1773 elmex 1.217 $INVR->set_items ($self->{container}{0});
1774     } elsif ($tag == $self->{player}{tag}) {
1775 elmex 1.223 $INVR_LBL->set_text ("Player");
1776 elmex 1.217 $INV->set_items ($self->{container}{$self->{player}{tag}})
1777     } else {
1778     $OPENCONT = $tag;
1779 elmex 1.223 $INVR_LBL->set_text (CFClient::UI::InventoryItem::_item_to_desc ($self->{item}->{$OPENCONT}));
1780 elmex 1.217 $INVR->set_items ($self->{container}{$tag});
1781     }
1782 root 1.169
1783     # $self-<{player}{tag} => player inv
1784     #use PApp::Util; warn PApp::Util::dumpval $self->{container}{$self->{player}{tag}};
1785     }
1786    
1787     sub conn::container_clear {
1788 root 1.203 my ($self, $tag) = @_;
1789 root 1.173
1790 elmex 1.222 #d# print "container_clear: container $tag ($self->{player}{tag})\n";
1791    
1792 elmex 1.217 if ($tag == 0) {
1793     update_floorbox;
1794 elmex 1.222 $OPENCONT = 0;
1795 elmex 1.223 $INVR_LBL->set_text ("Floor");
1796 elmex 1.217 $INVR->set_items ($self->{container}{0});
1797     } elsif ($tag == $self->{player}{tag}) {
1798 elmex 1.223 $INVR_LBL->set_text ("Player");
1799 elmex 1.217 $INV->set_items ($self->{container}{$tag})
1800     } else {
1801 elmex 1.222 $OPENCONT = $tag;
1802 elmex 1.223 $INVR_LBL->set_text (CFClient::UI::InventoryItem::_item_to_desc ($self->{item}->{$OPENCONT}));
1803 elmex 1.217 $INVR->set_items ($self->{container}{$tag});
1804     }
1805 elmex 1.191
1806 root 1.169 # use PApp::Util; warn PApp::Util::dumpval $self->{container}{0};
1807     }
1808    
1809 root 1.173 sub conn::item_delete {
1810     my ($self, @items) = @_;
1811    
1812     for (@items) {
1813 elmex 1.222 #d# print "item_delete: $_->{tag} from $_->{container} ($self->{player}{tag})\n";
1814    
1815 elmex 1.217 if ($_->{container} == 0) {
1816     update_floorbox;
1817 elmex 1.222 $OPENCONT = 0;
1818 elmex 1.223 $INVR_LBL->set_text ("Floor");
1819 elmex 1.217 $INVR->set_items ($self->{container}{0});
1820     } elsif ($_->{container} == $self->{player}{tag}) {
1821 elmex 1.223 $INVR_LBL->set_text ("Player");
1822 elmex 1.217 $INV->set_items ($self->{container}{$self->{player}{tag}})
1823     } else {
1824 elmex 1.222 $OPENCONT = $_->{container};
1825 elmex 1.223 $INVR_LBL->set_text (CFClient::UI::InventoryItem::_item_to_desc ($self->{item}->{$OPENCONT}));
1826 elmex 1.222 $INVR->set_items ($self->{container}{$_->{container}});
1827 elmex 1.217 }
1828 root 1.173 }
1829     }
1830    
1831     sub conn::item_update {
1832     my ($self, $item) = @_;
1833    
1834 elmex 1.222 #d# print "item_update: $item->{tag} in $item->{container} ($self->{player}{tag}) ($OPENCONT)\n";
1835    
1836     if ($item->{tag} == $OPENCONT && not ($item->{flags} & Crossfire::Protocol::F_OPEN)) {
1837     $OPENCONT = 0;
1838 elmex 1.223 $INVR_LBL->set_text ("Floor");
1839 elmex 1.217 $INVR->set_items ($self->{container}{0});
1840 elmex 1.223
1841     $item->{widget}->update_item
1842     if $item->{widget};
1843 elmex 1.222 } else {
1844     if ($item->{container} == 0) {
1845     update_floorbox;
1846     $OPENCONT = 0;
1847 elmex 1.223 $INVR_LBL->set_text ("Floor");
1848 elmex 1.222 $INVR->set_items ($self->{container}{0});
1849     } elsif ($item->{container} == $self->{player}{tag}) {
1850     $INV->set_items ($self->{container}{$item->{container}})
1851     }
1852 elmex 1.217 }
1853 root 1.173 }
1854    
1855 root 1.87 %SDL_CB = (
1856 root 1.145 CFClient::SDL_QUIT => sub {
1857 root 1.87 Event::unloop -1;
1858     },
1859 root 1.145 CFClient::SDL_VIDEORESIZE => sub {
1860 root 1.87 },
1861 root 1.206 CFClient::SDL_VIDEOEXPOSE => sub {
1862 root 1.236 CFClient::UI::full_refresh;
1863 root 1.206 },
1864 root 1.153 CFClient::SDL_ACTIVEEVENT => sub {
1865     # printf "active %x %x\n", $SDL_EV->active_gain, $SDL_EV->active_state;#d#
1866 root 1.87 },
1867 root 1.145 CFClient::SDL_KEYDOWN => sub {
1868 root 1.147 if ($_[0]{mod} & CFClient::KMOD_ALT && $_[0]{sym} == 13) {
1869 root 1.87 # alt-enter
1870 root 1.134 video_shutdown;
1871 root 1.99 $CFG->{fullscreen} = !$CFG->{fullscreen};
1872 root 1.134 video_init;
1873 root 1.87 } else {
1874 root 1.147 CFClient::UI::feed_sdl_key_down_event ($_[0]);
1875 elmex 1.23 }
1876 root 1.87 },
1877 root 1.198 CFClient::SDL_KEYUP => \&CFClient::UI::feed_sdl_key_up_event,
1878     CFClient::SDL_MOUSEMOTION => \&CFClient::UI::feed_sdl_motion_event,
1879 root 1.153 CFClient::SDL_MOUSEBUTTONDOWN => \&CFClient::UI::feed_sdl_button_down_event,
1880 root 1.198 CFClient::SDL_MOUSEBUTTONUP => \&CFClient::UI::feed_sdl_button_up_event,
1881     CFClient::SDL_USEREVENT => sub {
1882     if ($_[0]{code} == 1) {
1883     audio_channel_finished $_[0]{data1};
1884     } elsif ($_[0]{code} == 0) {
1885     audio_music_finished;
1886     }
1887     },
1888 root 1.87 );
1889 elmex 1.23
1890 root 1.1 #############################################################################
1891    
1892 root 1.131 $SIG{INT} = $SIG{TERM} = sub { exit };
1893    
1894 root 1.205 {
1895     local $SIG{__DIE__} = sub { CFClient::fatal $_[0] };
1896 root 1.194
1897 root 1.205 CFClient::read_cfg "$Crossfire::VARDIR/pclientrc";
1898 root 1.114
1899 root 1.205 $TILECACHE = CFClient::db_table "tilecache";
1900     $FACEMAP = CFClient::db_table "facemap";
1901 root 1.90
1902 root 1.205 my %DEF_CFG = (
1903     sdl_mode => 0,
1904     width => 640,
1905     height => 480,
1906     fullscreen => 0,
1907     fast => 0,
1908 root 1.230 map_scale => 1,
1909 root 1.205 fow_enable => 1,
1910     fow_intensity => 0.45,
1911     fow_smooth => 0,
1912     gui_fontsize => 1,
1913     log_fontsize => 1,
1914 root 1.206 gauge_fontsize=> 1,
1915     gauge_size => 0.35,
1916 root 1.205 stat_fontsize => 1,
1917     mapsize => 100,
1918     host => "crossfire.schmorp.de",
1919     say_command => 'say',
1920     audio_enable => 1,
1921     bgm_enable => 1,
1922     bgm_volume => 0.25,
1923 root 1.250 face_prefetch => 0,
1924 root 1.226 output_sync => 1,
1925     output_count => 1,
1926 root 1.205 );
1927 root 1.87
1928 root 1.205 while (my ($k, $v) = each %DEF_CFG) {
1929     $CFG->{$k} = $v unless exists $CFG->{$k};
1930     }
1931    
1932     sdl_init;
1933    
1934     @SDL_MODES = reverse
1935     grep $_->[0] >= 640 && $_->[1] >= 480,
1936     CFClient::SDL_ListModes;
1937    
1938     @SDL_MODES or CFClient::fatal "Unable to find a usable video mode\n(hardware accelerated opengl fullscreen)";
1939    
1940     $CFG->{sdl_mode} = 0 if $CFG->{sdl_mode} > @SDL_MODES;
1941    
1942     {
1943     my @fonts = map CFClient::find_rcfile "fonts/$_", qw(
1944     DejaVuSans.ttf
1945     DejaVuSansMono.ttf
1946     DejaVuSans-Bold.ttf
1947     DejaVuSansMono-Bold.ttf
1948     DejaVuSans-Oblique.ttf
1949     DejaVuSansMono-Oblique.ttf
1950     DejaVuSans-BoldOblique.ttf
1951     DejaVuSansMono-BoldOblique.ttf
1952     );
1953    
1954     CFClient::add_font $_ for @fonts;
1955    
1956 root 1.214 CFClient::pango_init;
1957    
1958 root 1.205 $FONT_PROP = new_from_file CFClient::Font $fonts[0];
1959     $FONT_FIXED = new_from_file CFClient::Font $fonts[1];
1960 root 1.89
1961 root 1.205 $FONT_PROP->make_default;
1962     }
1963 root 1.89
1964 root 1.219 # compare mono (ft) vs. rgba (cairo)
1965     # ft - 1.8s, cairo 3s, even in alpha-only mode
1966     # for my $rgba (0..1) {
1967     # my $t1 = Time::HiRes::time;
1968     # for (1..1000) {
1969     # my $layout = CFClient::Layout->new ($rgba);
1970     # $layout->set_text ("hallo" x 100);
1971     # $layout->render;
1972     # }
1973     # my $t2 = Time::HiRes::time;
1974     # warn $t2-$t1;
1975     # }
1976    
1977 root 1.205 video_init;
1978     audio_init;
1979 root 1.65 }
1980 root 1.40
1981 root 1.87 Event::loop;
1982 root 1.19
1983 root 1.148 END { CFClient::SDL_Quit }
1984 root 1.131
1985 root 1.241 =head1 NAME
1986 root 1.178
1987 root 1.241 pclient - A Crossfire+ and Crossfire game client
1988 root 1.178
1989 root 1.241 =head1 SYNOPSIS
1990 root 1.178
1991 root 1.241 Just run it - no commandline arguments are supported.
1992 root 1.178
1993 root 1.179 =head1 USAGE
1994    
1995 root 1.241 Pclient utilises OpenGL for all UI elements and the game. It is supposed to be used
1996     fullscreen and interactively.
1997 root 1.178
1998     =head1 AUTHOR
1999    
2000     Marc Lehmann <crossfire@schmorp.de>, Robin Redeker <elmex@ta-sa.org>
2001    
2002    
2003 root 1.82