ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/pclient
Revision: 1.249
Committed: Thu May 25 02:23:14 2006 UTC (18 years ago) by root
Branch: MAIN
Changes since 1.248: +2 -4 lines
Log Message:
fix entry widget causing random activates

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.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.243 . "If you set this to a high value you will be able to see further for example.",
749 root 1.81 connect_changed => sub {
750     my ($self, $value) = @_;
751    
752     $CFG->{mapsize} = $self->{range}[0] = $value = int $value;
753     },
754     );
755    
756 root 1.226 $table->add (0, 8, new CFClient::UI::Label valign => 0, align => 1, text => "Output-Count");
757     $table->add (1, 8, new CFClient::UI::Entry
758     text => $CFG->{output_count},
759     tooltip => "Should be set to 1 unless you know what you are doing",
760     connect_changed => sub { $CFG->{output_count} = $_[1] },
761     );
762    
763     $table->add (0, 9, new CFClient::UI::Label valign => 0, align => 1, text => "Output-Sync");
764     $table->add (1, 9, new CFClient::UI::Entry
765     text => $CFG->{output_sync},
766     tooltip => "Should be set to 1 unless you know what you are doing",
767     connect_changed => sub { $CFG->{output_sync} = $_[1] },
768     );
769    
770     $table->add (1, 10, $LOGIN_BUTTON = new CFClient::UI::Button
771 root 1.199 expand => 1,
772     align => 0,
773     text => "Login",
774     connect_activate => sub {
775     $CONN ? stop_game
776     : start_game;
777     },
778     );
779 root 1.82
780 root 1.98 $dialog
781 root 1.81 }
782 root 1.58
783 root 1.111 sub message_window {
784 root 1.99 my $window = new CFClient::UI::FancyFrame
785 root 1.150 title => "Messages",
786 root 1.186 border_bg => [1, 1, 1, 1],
787 root 1.236 bg => [0, 0, 0, 0.75],
788 root 1.124 user_w => int $::WIDTH / 3,
789     user_h => int $::HEIGHT / 5,
790 root 1.99 child => (my $vbox = new CFClient::UI::VBox);
791    
792 root 1.229 $vbox->add ($LOGVIEW);
793 root 1.105
794 root 1.122 $vbox->add (my $input = new CFClient::UI::Entry
795 root 1.236 tooltip => "<b>Chat Box</b>. If you enter a text and press return/enter here, the current <i>communication command</i> "
796     . "from the client setup will be prepended (e.g. <b>shout</b>, <b>chat</b>...). "
797     . "If you prepend a slash (/), you will submit a command instead (similar to IRC). "
798     . "A better way to submit commands (and the occasional chat command) is often the map command completer.",
799 elmex 1.118 connect_focus_in => sub {
800     my ($input, $prev_focus) = @_;
801    
802     delete $input->{refocus_map};
803    
804     if ($prev_focus == $MAPWIDGET && $input->{auto_activated}) {
805     $input->{refocus_map} = 1;
806     }
807     delete $input->{auto_activated};
808     },
809 root 1.116 connect_activate => sub {
810 elmex 1.100 my ($input, $text) = @_;
811     $input->set_text ('');
812    
813     if ($text =~ /^\/(.*)/) {
814 root 1.123 $::CONN->user_send ($1);
815 elmex 1.100 } else {
816 elmex 1.101 my $say_cmd = $::CFG->{say_command} || 'say';
817 root 1.123 $::CONN->user_send ("$say_cmd $text");
818 elmex 1.100 }
819 elmex 1.118 if ($input->{refocus_map}) {
820     delete $input->{refocus_map};
821     $MAPWIDGET->focus_in
822     }
823 root 1.116 },
824     connect_escape => sub {
825 elmex 1.102 $MAPWIDGET->focus_in
826 root 1.116 },
827     );
828 elmex 1.102
829     $CONSOLE = {
830     window => $window,
831     input => $input
832     };
833 root 1.99
834     $window
835     }
836    
837 elmex 1.237 sub open_quit_dialog {
838     unless ($QUIT_DIALOG) {
839    
840 elmex 1.238 $QUIT_DIALOG = new CFClient::UI::FancyFrame title => "Really Quit?";
841 elmex 1.237
842     $QUIT_DIALOG->add (my $vb = new CFClient::UI::VBox expand => 1);
843    
844     $vb->add (new CFClient::UI::Label
845 root 1.246 text => "You should find a savebed and apply it first!",
846     max_w => $WIDTH * 0.25,
847     ellipsize => 0,
848 elmex 1.237 );
849     $vb->add (my $hb = new CFClient::UI::HBox expand => 1);
850     $hb->add (new CFClient::UI::Button
851     text => "Ok",
852 root 1.246 expand => 1,
853 elmex 1.237 connect_activate => sub { $QUIT_DIALOG->hide },
854     );
855     $hb->add (new CFClient::UI::Button
856     text => "Quit anyway",
857 root 1.246 expand => 1,
858     connect_activate => sub { exit },
859 elmex 1.237 );
860    
861 elmex 1.238 $QUIT_DIALOG->show_centered;
862 elmex 1.237 } else {
863 elmex 1.238 $QUIT_DIALOG->show_centered;
864 elmex 1.237 }
865     }
866    
867 elmex 1.191 sub make_inventory_window {
868 elmex 1.217 my $invwin = new CFClient::UI::FancyFrame
869 elmex 1.239 user_w => $WIDTH * (7/8), user_h => $HEIGHT * (7/8), title => "Inventory";
870 elmex 1.217
871 root 1.230 $invwin->add (my $hb = new CFClient::UI::HBox expand => 1);
872 elmex 1.223
873     $hb->add (my $vb1 = new CFClient::UI::VBox expand => 1);
874     $vb1->add (my $lbl = new CFClient::UI::Label);
875     $lbl->set_text ("Player");
876     $vb1->add ($INV = new CFClient::UI::Inventory expand => 1);
877    
878     $hb->add (my $vb2 = new CFClient::UI::VBox expand => 1);
879     $vb2->add ($INVR_LBL = new CFClient::UI::Label);
880     $INVR_LBL->set_text ("Floor");
881     $vb2->add ($INVR = new CFClient::UI::Inventory expand => 1);
882 elmex 1.217
883 elmex 1.191 $invwin
884     }
885    
886 root 1.241 sub make_help_window {
887     my $win = new CFClient::UI::FancyFrame
888     user_w => $WIDTH * (7/8), user_h => $HEIGHT * (7/8), title => "Documentation";
889    
890     $win->add (my $vbox = new CFClient::UI::VBox);
891    
892     $vbox->add (my $buttons = new CFClient::UI::HBox);
893     $vbox->add (my $viewer = new CFClient::UI::TextView expand => 1, fontsize => 0.8);
894    
895     for (
896     [intro => "Introduction"],
897 root 1.248 [manual => "Manual"],
898 root 1.241 [command_help => "Commands"],
899     [skill_help => "Skills"],
900     ) {
901     my ($pod, $label) = @$_;
902    
903     $buttons->add (new CFClient::UI::Button
904     text => $label,
905     connect_activate => sub {
906     my $parser = new Pod::POM;
907     my $pom = $parser->parse_file (CFClient::find_rcfile "pod/$pod.pod");
908    
909     $viewer->clear;
910    
911     $viewer->add_paragraph ([1, 1, 1, 1], $_->[1], $_->[0])
912     for @{ CFClient::pod_to_pango_list $pom };
913 root 1.243
914     $viewer->set_offset (0);
915 root 1.241 },
916     );
917     }
918    
919     $viewer->add_paragraph ([1, 1, 0, 1], "<big>Use one of the buttons above to display a document.</big>");
920    
921     $win
922     }
923    
924 root 1.89 sub sdl_init {
925 root 1.145 CFClient::SDL_Init
926 root 1.89 and die "SDL::Init failed!\n";
927     }
928    
929 root 1.134 sub video_init {
930 root 1.89 sdl_init;
931    
932 root 1.197 $CFG->{sdl_mode} = 0 if $CFG->{sdl_mode} >= @SDL_MODES;
933    
934 root 1.245 my ($old_w, $old_h) = ($WIDTH, $HEIGHT);
935    
936 root 1.84 ($WIDTH, $HEIGHT) = @{ $SDL_MODES[$CFG->{sdl_mode}] };
937     $FULLSCREEN = $CFG->{fullscreen};
938 root 1.89 $FAST = $CFG->{fast};
939 root 1.84
940 root 1.145 CFClient::SDL_SetVideoMode $WIDTH, $HEIGHT, $FULLSCREEN
941 root 1.230 or die "SDL_SetVideoMode failed: " . (CFClient::SDL_GetError) . "\n";
942 root 1.2
943 root 1.86 $SDL_ACTIVE = 1;
944 root 1.87 $LAST_REFRESH = time - 0.01;
945 root 1.45
946 root 1.67 CFClient::gl_init;
947 root 1.30
948 root 1.140 $FONTSIZE = int $HEIGHT / 40 * $CFG->{gui_fontsize};
949 root 1.39
950 root 1.202 $CFClient::UI::ROOT->configure (0, 0, $WIDTH, $HEIGHT);#d#
951    
952 root 1.52 #############################################################################
953    
954 root 1.245 if ($DEBUG_STATUS) {
955     CFClient::UI::rescale_widgets $WIDTH / $old_w, $HEIGHT / $old_h;
956     } else {
957 root 1.202 # create the widgets
958    
959 root 1.215 $DEBUG_STATUS = new CFClient::UI::Label padding => 0, z => 100, req_x => -1;
960 root 1.202 $DEBUG_STATUS->show;
961    
962 root 1.212 $STATUSBOX = new CFClient::UI::Statusbox;
963 root 1.215 $STATUSBOX->add ("Use <b>Alt-Enter</b> to toggle fullscreen mode", pri => -100, color => [1, 1, 1, 0.8]);
964 root 1.212
965     (new CFClient::UI::Frame
966 root 1.215 bg => [0, 0, 0, 0.4],
967     req_y => -1,
968     child => $STATUSBOX,
969 root 1.212 )->show;
970 root 1.202
971     CFClient::UI::FancyFrame->new (
972     border_bg => [1, 1, 1, 192/255],
973     bg => [1, 1, 1, 0],
974 root 1.236 child => ($MAPMAP = new CFClient::MapWidget::MapMap
975     tooltip => "<b>Map</b>. On servers that support this feature, this will display an overview of the surrounding areas.",
976     ),
977 root 1.202 )->show;
978    
979     $MAPWIDGET = new CFClient::MapWidget;
980     $MAPWIDGET->connect (activate_console => sub {
981     my ($mapwidget, $preset) = @_;
982    
983     if ($CONSOLE) {
984     $CONSOLE->{input}->{auto_activated} = 1;
985     $CONSOLE->{input}->focus_in;
986 elmex 1.103
987 root 1.202 if ($preset && $CONSOLE->{input}->get_text eq '') {
988     $CONSOLE->{input}->set_text ($preset);
989     }
990 elmex 1.103 }
991 root 1.202 });
992     $MAPWIDGET->show;
993     $MAPWIDGET->focus_in;
994 root 1.81
995 root 1.229 $LOGVIEW = new CFClient::UI::TextView
996 root 1.236 expand => 1,
997     font => $FONT_FIXED,
998     fontsize => $::CFG->{log_fontsize},
999     can_hover => 1,
1000     can_events => 1,
1001     tooltip => "<b>Server Log</b>. This text viewer contains all the messages sent by the server.",
1002 root 1.229 ;
1003    
1004 root 1.202 $BUTTONBAR = new CFClient::UI::HBox;
1005 root 1.111
1006 root 1.236 $BUTTONBAR->add (new CFClient::UI::Flopper text => "Client Setup", other => client_setup,
1007     tooltip => "Toggles a dialog where you can configure various aspects of the client, such as graphics mode, performance, and audio options.");
1008     $BUTTONBAR->add (new CFClient::UI::Flopper text => "Server Setup", other => server_setup,
1009     tooltip => "Toggles a dialog where you can configure the server to play on, your username, password and other server-related options.");
1010     $BUTTONBAR->add (new CFClient::UI::Flopper text => "Message Window", other => message_window,
1011     tooltip => "Toggles the server message log, where the client collects <i>all</i> messages from the server.");
1012 root 1.111
1013 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
1014 root 1.192
1015 root 1.236 $BUTTONBAR->add (new CFClient::UI::Flopper text => "Stats Window", other => make_stats_window,
1016 root 1.249 tooltip => "Toggles the statistics window, where all your Stats and Resistances are being displayed at all times.");
1017 root 1.236 $BUTTONBAR->add (new CFClient::UI::Flopper text => "Inventory", other => make_inventory_window,
1018     tooltip => "Toggles the inventory window, where you can manage your loot (or treaures :).");
1019    
1020     $BUTTONBAR->add (new CFClient::UI::Button
1021     text => "Save Config",
1022     tooltip => "Saves the options chosen in the client setting, server settings and the window layout to be restored on later runs.",
1023     connect_activate => sub {
1024     CFClient::write_cfg "$Crossfire::VARDIR/pclientrc";
1025     status "Configuration Saved";
1026     },
1027     );
1028 root 1.98
1029 root 1.241 $BUTTONBAR->add (new CFClient::UI::Flopper text => "Help!", other => make_help_window,
1030     tooltip => "View Documentation");
1031    
1032 elmex 1.237 $BUTTONBAR->add (new CFClient::UI::Button
1033     text => "Quit",
1034     tooltip => "Terminates the program",
1035     connect_activate => sub {
1036     if ($CONN) {
1037 root 1.241 open_quit_dialog;
1038 elmex 1.237 } else {
1039 root 1.241 exit;
1040 elmex 1.237 }
1041     },
1042     );
1043    
1044 root 1.202 $BUTTONBAR->show;
1045 root 1.187
1046 root 1.215 $STATUSBOX->add ("Set video mode $WIDTH×$HEIGHT", timeout => 10, fg => [1, 1, 1, 0.5]);
1047    
1048 root 1.206 # delay till geometry is constant
1049 root 1.211 $CFClient::UI::ROOT->on_post_alloc (startup => sub {
1050 root 1.206 $BUTTONBAR->{children}[1]->emit ("activate"); # pop up server setup
1051 root 1.215 my $widget = $GAUGES->{win};
1052     $widget->move (0, $HEIGHT - $widget->{h});#d# to in toplevel
1053 root 1.206 });
1054     force_refresh ();
1055 root 1.202 }
1056 root 1.2 }
1057    
1058 root 1.134 sub video_shutdown {
1059 root 1.86 undef $SDL_ACTIVE;
1060 root 1.134 }
1061    
1062 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#
1063 root 1.135 my $bgmusic;#TODO#hack#d#
1064    
1065 root 1.198 sub audio_channel_finished {
1066     my ($channel) = @_;
1067    
1068 root 1.226 #warn "channel $channel finished\n";#d#
1069 root 1.198 }
1070    
1071 root 1.153 sub audio_music_finished {
1072     return unless $CFG->{bgm_enable};
1073    
1074     # TODO: hack, do play loop and mood music
1075     $bgmusic = new_from_file CFClient::MixMusic CFClient::find_rcfile "music/$bgmusic[0]";
1076     $bgmusic->play (0);
1077    
1078     push @bgmusic, shift @bgmusic;
1079     }
1080    
1081 root 1.134 sub audio_init {
1082 root 1.139 if ($CFG->{audio_enable}) {
1083 root 1.195 if (open my $fh, "<", CFClient::find_rcfile "sounds/config") {
1084 root 1.146 $SDL_MIXER = !CFClient::Mix_OpenAudio;
1085 root 1.240
1086     unless ($SDL_MIXER) {
1087     status "Unable to open sound device: there will be no sound";
1088     return;
1089     }
1090    
1091 root 1.146 CFClient::Mix_AllocateChannels 8;
1092 root 1.149 CFClient::MixMusic::volume $CFG->{bgm_volume} * 128;
1093 root 1.134
1094 root 1.153 audio_music_finished;
1095 root 1.135
1096 root 1.134 while (<$fh>) {
1097     next if /^\s*#/;
1098     next if /^\s*$/;
1099    
1100     my ($file, $volume, $event) = split /\s+/, $_, 3;
1101    
1102     push @SOUNDS, "$volume,$file";
1103    
1104     $AUDIO_CHUNKS{"$volume,$file"} ||= do {
1105 root 1.146 my $chunk = new_from_file CFClient::MixChunk CFClient::find_rcfile "sounds/$file";
1106 root 1.134 $chunk->volume ($volume * 128 / 100);
1107     $chunk
1108     };
1109     }
1110     } else {
1111     status "unable to open sound config: $!";
1112     }
1113     }
1114     }
1115    
1116     sub audio_shutdown {
1117 root 1.146 CFClient::Mix_CloseAudio if $SDL_MIXER;
1118 root 1.134 undef $SDL_MIXER;
1119     @SOUNDS = ();
1120     %AUDIO_CHUNKS = ();
1121 root 1.62 }
1122    
1123 root 1.87 my %animate_object;
1124     my $animate_timer;
1125    
1126     my $fps = 9;
1127    
1128 root 1.231 my %demo;#d#
1129    
1130 root 1.30 sub force_refresh {
1131 root 1.215 $fps = $fps * 0.95 + 1 / (($NOW - $LAST_REFRESH) || 0.1) * 0.05;
1132 root 1.87 debug sprintf "%3.2f", $fps;
1133    
1134 root 1.111 $CFClient::UI::ROOT->draw;
1135 root 1.231
1136 root 1.232 $WANT_REFRESH = 0;
1137     $CAN_REFRESH = 0;
1138     $LAST_REFRESH = $NOW;
1139    
1140 root 1.231 0 && do {
1141     # some weird model-drawing code, just a joke right now
1142     use CFClient::OpenGL;
1143    
1144     $demo{t}{eye_auv} ||= new_from_file CFClient::Texture "eye2.png" or die;
1145     $demo{t}{body_auv} ||= new_from_file CFClient::Texture "body_auv3.png" or die;
1146     $demo{r} ||= do {
1147     my $mod = Compress::LZF::sthaw do { local $/; open my $fh, "<:raw:perlio", "dread.lz3"; <$fh> };
1148     $mod->{v} = pack "f*", @{$mod->{v}};
1149     $_ = [scalar @$_, pack "S!*", @$_]
1150     for values %{$mod->{g}};
1151     $mod
1152     };
1153    
1154     my $r = $demo{r} or die;
1155    
1156     glDepthMask 1;
1157     glClear GL_DEPTH_BUFFER_BIT;
1158     glEnable GL_TEXTURE_2D;
1159     glEnable GL_DEPTH_TEST;
1160 root 1.233 glEnable GL_CULL_FACE;
1161 root 1.232 glShadeModel $::FAST ? GL_FLAT : GL_SMOOTH;
1162 root 1.231
1163     glMatrixMode GL_PROJECTION;
1164     glLoadIdentity;
1165 root 1.233 glFrustum -1 * ($::WIDTH / $::HEIGHT), 1 * ($::WIDTH / $::HEIGHT), 1, -1, 1, 10000;
1166     #glOrtho 0, $::WIDTH, 0, $::HEIGHT, -10000, 10000;
1167 root 1.231 glMatrixMode GL_MODELVIEW;
1168     glLoadIdentity;
1169    
1170     glPushMatrix;
1171 root 1.233 glTranslate 0, 0, -800;
1172     glScale 1, -1, 1;
1173     glRotate $NOW * 1000 % 36000 / 5, 0, 1, 0;
1174     glRotate $NOW * 1000 % 36000 / 6, 1, 0, 0;
1175     glRotate $NOW * 1000 % 36000 / 7, 0, 0, 1;
1176 root 1.231 glScale 50, 50, 50;
1177    
1178     glInterleavedArrays GL_T2F_N3F_V3F, 0, $r->{v};
1179     while (my ($k, $v) = each %{$r->{g}}) {
1180     glBindTexture GL_TEXTURE_2D, ($demo{t}{$k}{name} or die);
1181     glDrawElements GL_TRIANGLES, $v->[0], GL_UNSIGNED_SHORT, $v->[1];
1182     }
1183 root 1.232
1184 root 1.231 glPopMatrix;
1185    
1186     glShadeModel GL_FLAT;
1187     glDisable GL_DEPTH_TEST;
1188     glDisable GL_TEXTURE_2D;
1189     glDepthMask 0;
1190    
1191     $WANT_REFRESH++;
1192     };
1193    
1194 root 1.148 CFClient::SDL_GL_SwapBuffers;
1195 root 1.1 }
1196    
1197 root 1.87 my $refresh_watcher = Event->timer (after => 0, hard => 1, interval => 1 / $MAX_FPS, cb => sub {
1198     $NOW = time;
1199    
1200 root 1.147 ($SDL_CB{$_->{type}} || sub { warn "unhandled event $_->{type}" })->($_)
1201     for CFClient::SDL_PollEvent;
1202 root 1.87
1203     if (%animate_object) {
1204     $_->animate ($LAST_REFRESH - $NOW) for values %animate_object;
1205 root 1.206 $WANT_REFRESH++;
1206 root 1.87 }
1207    
1208 root 1.206 if ($WANT_REFRESH) {
1209 root 1.87 force_refresh;
1210     } else {
1211 root 1.206 $CAN_REFRESH = 1;
1212 root 1.87 }
1213     });
1214 root 1.64
1215 root 1.45 sub animation_start {
1216     my ($widget) = @_;
1217 root 1.87 $animate_object{$widget} = $widget;
1218 root 1.45 }
1219    
1220     sub animation_stop {
1221     my ($widget) = @_;
1222 root 1.87 delete $animate_object{$widget};
1223 root 1.45 }
1224    
1225 root 1.2 @conn::ISA = Crossfire::Protocol::;
1226 root 1.1
1227 root 1.226 sub conn::new {
1228     my $class = shift;
1229    
1230     my $self = $class->Crossfire::Protocol::new (@_);
1231    
1232     $MAPWIDGET->clr_commands;
1233    
1234     my $parser = new Pod::POM;
1235     my $pod = $parser->parse_file (CFClient::find_rcfile "pod/command_help.pod");
1236    
1237     for my $head2 ($pod->head2) {
1238     $head2->title =~ /^(\S+) (?:\s+ \( ([^\)]*) \) )?/x
1239     or next;
1240    
1241     my $cmd = $1;
1242     my @args = split /\|/, $2;
1243     @args = (".*") unless @args;
1244    
1245     my $text = CFClient::pod_to_pango $head2->content;
1246    
1247     for my $arg (@args) {
1248     $arg = $arg eq ".*" ? "" : " $arg";
1249    
1250     $MAPWIDGET->add_command ("$cmd$arg", $text);
1251     }
1252     }
1253    
1254 root 1.240 $self->{noface} = new_from_file CFClient::Texture
1255     CFClient::find_rcfile "noface.png", minify => 1, mipmap => 1;
1256    
1257 root 1.226 $self
1258     }
1259    
1260 elmex 1.125 sub conn::stats_update {
1261     my ($self, $stats) = @_;
1262    
1263 root 1.224 if (my $exp = $stats->{Crossfire::Protocol::CS_STAT_EXP64}) {
1264     my $diff = $exp - $self->{prev_exp};
1265     $STATUSBOX->add ("$diff experience gained", group => "experience $diff", fg => [0.5, 1, 0.5, 0.8], timeout => 5)
1266     if exists $self->{prev_exp} && $diff;
1267     $self->{prev_exp} = $exp;
1268     }
1269    
1270 elmex 1.154 update_stats_window ($stats);
1271 elmex 1.125 }
1272    
1273 root 1.89 sub conn::user_send {
1274 root 1.88 my ($self, $command) = @_;
1275    
1276 root 1.123 $self->send_command ($command);
1277 root 1.88 status $command;
1278     }
1279    
1280 root 1.119 sub conn::map_scroll {
1281     my ($self, $dx, $dy) = @_;
1282    
1283     $MAP->scroll ($dx, $dy);
1284     }
1285    
1286 root 1.94 sub conn::feed_map1a {
1287     my ($self, $data) = @_;
1288    
1289 root 1.95 # $self->Crossfire::Protocol::feed_map1a ($data);
1290 root 1.1
1291 root 1.95 $MAP->map1a_update ($data);
1292 root 1.69 $MAPWIDGET->update;
1293 root 1.1 }
1294    
1295 root 1.116 sub conn::flush_map {
1296     my ($self) = @_;
1297    
1298     my $map_info = delete $self->{map_info}
1299     or return;
1300    
1301     my ($hash, $x, $y, $w, $h) = @$map_info;
1302    
1303     my $data = $MAP->get_rect ($x, $y, $w, $h);
1304     $MAPCACHE->put ($hash => Compress::LZF::compress $data);
1305 root 1.152 #warn sprintf "SAVEmap[%s] length %d\n", $hash, length $data;#d#
1306 root 1.116 }
1307 root 1.1
1308 root 1.2 sub conn::map_clear {
1309 root 1.1 my ($self) = @_;
1310    
1311 root 1.116 $self->flush_map;
1312 root 1.150 delete $self->{neigh_map};
1313 root 1.116
1314 root 1.95 $MAP->clear;
1315 root 1.1 }
1316    
1317 root 1.116
1318 root 1.119 sub conn::load_map($$$) {
1319     my ($self, $hash, $x, $y) = @_;
1320 root 1.115
1321 root 1.116 if (defined (my $data = $MAPCACHE->get ($hash))) {
1322     $data = Compress::LZF::decompress $data;
1323 root 1.152 #warn sprintf "LOADmap[%s,%d,%d] length %d\n", $hash, $x, $y, length $data;#d#
1324 root 1.116 for my $id ($MAP->set_rect ($x, $y, $data)) {
1325     my $data = $TILECACHE->get ($id)
1326     or next;
1327    
1328     $self->set_texture ($id => $data);
1329     }
1330     }
1331 root 1.115 }
1332    
1333 root 1.233 # hardcode /world/world_xxx_xxx map names, the savings are enourmous,
1334     # (server resource,s latency, bandwidth), so this hack is warranted.
1335     # the right fix is to make real tiled maps with an overview file
1336     sub conn::send_mapinfo {
1337     my ($self, $data, $cb) = @_;
1338    
1339     if ($self->{map_info}[0] =~ m%^/world/world_(\d\d\d)_(\d\d\d)$%) {
1340     my ($wx, $wy) = ($1, $2);
1341    
1342     if ($data =~ /^spatial ([1-4]+)$/) {
1343     my @dx = (0, 0, 1, 0, -1);
1344     my @dy = (0, -1, 0, 1, 0);
1345     my ($dx, $dy);
1346    
1347     for (split //, $1) {
1348     $dx += $dx[$_];
1349     $dy += $dy[$_];
1350     }
1351    
1352     $cb->(spatial => 15,
1353     $self->{map_info}[1] - $MAP->ox + $dx * 50,
1354     $self->{map_info}[2] - $MAP->oy + $dy * 50,
1355     50, 50,
1356     sprintf "/world/world_%03d_%03d", $wx + $dx, $wy + $dy
1357     );
1358    
1359     return;
1360     }
1361     }
1362    
1363 root 1.244 $self->Crossfire::Protocol::send_mapinfo ($data, $cb);
1364 root 1.233 }
1365    
1366 root 1.152 # this method does a "flood fill" into every tile direction
1367     # it assumes that tiles are arranged in a rectangular grid,
1368     # i.e. a map is the same as the left of the right map etc.
1369     # failure to comply are harmless and result in display errors
1370     # at worst.
1371 root 1.119 sub conn::flood_fill {
1372 root 1.233 my ($self, $block, $gx, $gy, $path, $hash, $flags) = @_;
1373 root 1.119
1374 root 1.121 # the server does not allow map paths > 6
1375 root 1.187 return if 7 <= length $path;
1376 root 1.120
1377 root 1.150 my ($x0, $y0, $x1, $y1) = @{$self->{neigh_rect}};
1378    
1379     for (
1380 root 1.233 [1, 3, 0, -1],
1381     [2, 4, 1, 0],
1382     [3, 1, 0, 1],
1383     [4, 2, -1, 0],
1384 root 1.150 ) {
1385 root 1.233 my ($tile, $tile2, $dx, $dy) = @$_;
1386    
1387     next if $block & (1 << $tile);
1388     my $block = $block | (1 << $tile2);
1389 root 1.150
1390     my $gx = $gx + $dx;
1391     my $gy = $gy + $dy;
1392    
1393 root 1.119 next unless $flags & (1 << ($tile - 1));
1394 root 1.150 next if $self->{neigh_grid}{$gx, $gy}++;
1395 root 1.119
1396 root 1.150 my $neigh = $self->{neigh_map}{$hash} ||= [];
1397     if (my $info = $neigh->[$tile]) {
1398     my ($flags, $x, $y, $w, $h, $hash) = @$info;
1399 root 1.119
1400 root 1.233 $self->flood_fill ($block, $gx, $gy, "$path$tile", $hash, $flags)
1401 root 1.150 if $x >= $x0 && $x + $w < $x1 && $y >= $y0 && $y + $h < $y1;
1402    
1403     } else {
1404     $self->send_mapinfo ("spatial $path$tile", sub {
1405     my ($mode, $flags, $x, $y, $w, $h, $hash) = @_;
1406 root 1.119
1407 root 1.150 return if $mode ne "spatial";
1408 root 1.119
1409 root 1.150 $x += $MAP->ox;
1410     $y += $MAP->oy;
1411 root 1.233
1412 root 1.150 $self->load_map ($hash, $x, $y)
1413     unless $self->{neigh_map}{$hash}[5]++;#d#
1414 root 1.119
1415 root 1.150 $neigh->[$tile] = [$flags, $x, $y, $w, $h, $hash];
1416 root 1.119
1417 root 1.233 $self->flood_fill ($block, $gx, $gy, "$path$tile", $hash, $flags)
1418 root 1.150 if $x >= $x0 && $x + $w < $x1 && $y >= $y0 && $y + $h < $y1;
1419     });
1420     }
1421 root 1.119 }
1422     }
1423    
1424     sub conn::map_change {
1425     my ($self, $mode, $flags, $x, $y, $w, $h, $hash) = @_;
1426    
1427     $self->flush_map;
1428    
1429     my ($ox, $oy) = ($::MAP->ox, $::MAP->oy);
1430    
1431 root 1.187 my $mapmapw = $MAPMAP->{w};
1432     my $mapmaph = $MAPMAP->{h};
1433 root 1.150
1434     $self->{neigh_rect} = [
1435 root 1.152 $ox - $mapmapw * 0.5, $oy - $mapmapw * 0.5,
1436     $ox + $mapmapw * 0.5 + $w, $oy + $mapmapw * 0.5 + $h,
1437 root 1.150 ];
1438 root 1.119
1439 root 1.150 delete $self->{neigh_grid};
1440 root 1.119
1441     $x += $ox;
1442     $y += $oy;
1443    
1444     $self->{map_info} = [$hash, $x, $y, $w, $h];
1445    
1446 root 1.233 (my $map = $hash) =~ s/^.*?\/([^\/]+)$/\1/;
1447 elmex 1.158 $STATWIDS->{map}->set_text ("Map: " . $map);
1448 elmex 1.157
1449 root 1.119 $self->load_map ($hash, $x, $y);
1450 root 1.233 $self->flood_fill (0, 0, 0, "", $hash, $flags);
1451 root 1.119 }
1452    
1453 root 1.19 sub conn::face_find {
1454 root 1.116 my ($self, $facenum, $face) = @_;
1455    
1456     my $hash = "$face->{chksum},$face->{name}";
1457    
1458     my $id = $FACEMAP->get ($hash);
1459    
1460     unless ($id) {
1461     # create new id for face
1462 root 1.240 # I love transactions
1463 root 1.116 for (1..100) {
1464     my $txn = $CFClient::DB_ENV->txn_begin;
1465     my $status = $FACEMAP->db_get (id => $id, BerkeleyDB::DB_RMW);
1466     if ($status == 0 || $status == BerkeleyDB::DB_NOTFOUND) {
1467 root 1.240 $id = ($id || 16) + 1;
1468 root 1.116 if ($FACEMAP->put (id => $id) == 0
1469     && $FACEMAP->put ($hash => $id) == 0) {
1470     $txn->txn_commit;
1471    
1472     goto gotid;
1473     }
1474     }
1475     $txn->abort;
1476     }
1477 root 1.19
1478 root 1.116 CFClient::fatal "maximum number of transaction retries reached - database problems?";
1479     }
1480 root 1.114
1481 root 1.116 gotid:
1482     $face->{id} = $id;
1483     $MAP->set_face ($facenum => $id);
1484 root 1.201 $self->{faceid}[$facenum] = $id;#d#
1485 root 1.240
1486     $TILECACHE->get ($id) || do {
1487     my $tex = $self->{noface};
1488     $MAP->set_texture ($id, @$tex{qw(name w h s t)}, @{$tex->{minified}});
1489     undef
1490     };
1491 root 1.19 }
1492    
1493 root 1.2 sub conn::face_update {
1494 root 1.95 my ($self, $facenum, $face) = @_;
1495 root 1.19
1496 root 1.116 $TILECACHE->put ($face->{id} => $face->{image}); #TODO: try to avoid duplicate writes
1497    
1498     $self->set_texture ($face->{id} => delete $face->{image});
1499     }
1500 root 1.1
1501 root 1.116 sub conn::set_texture {
1502     my ($self, $id, $data) = @_;
1503 root 1.95
1504 root 1.116 $self->{texture}[$id] ||= do {
1505     my $tex =
1506     new_from_image CFClient::Texture
1507 root 1.173 $data, minify => 1, mipmap => 1;
1508 root 1.116
1509     $MAP->set_texture ($id, @$tex{qw(name w h s t)}, @{$tex->{minified}});
1510     $MAPWIDGET->update;
1511    
1512     $tex
1513     };
1514 root 1.1 }
1515    
1516 root 1.134 sub conn::sound_play {
1517     my ($self, $x, $y, $soundnum, $type) = @_;
1518    
1519 root 1.139 $SDL_MIXER
1520     or return;
1521    
1522 root 1.134 my $chunk = $AUDIO_CHUNKS{$SOUNDS[$soundnum]}
1523     or return;
1524    
1525 root 1.146 $chunk->play;
1526 root 1.143 # warn "sound $x,$y,$soundnum,$type\n";#d#
1527 root 1.134 }
1528    
1529 root 1.170 my $LAST_QUERY; # server is stupid, stupid, stupid
1530    
1531 root 1.33 sub conn::query {
1532     my ($self, $flags, $prompt) = @_;
1533    
1534 root 1.170 $prompt = $LAST_QUERY unless length $prompt;
1535     $LAST_QUERY = $prompt;
1536    
1537     my $dialog = new CFClient::UI::FancyFrame
1538     title => "Query",
1539     child => my $vbox = new CFClient::UI::VBox;
1540    
1541     $vbox->add (new CFClient::UI::Label
1542     max_w => $::WIDTH * 0.4,
1543 root 1.249 ellipsise => 0,
1544 root 1.170 text => $prompt);
1545    
1546     if ($flags & Crossfire::Protocol::CS_QUERY_YESNO) {
1547     $vbox->add (my $hbox = new CFClient::HBox);
1548     $hbox->add (new CFClient::Button
1549     text => "No",
1550     connect_activate => sub {
1551     $self->send ("reply n");
1552     $dialog->destroy;
1553     $MAPWIDGET->focus_in;
1554     }
1555     );
1556     $hbox->add (new CFClient::Button
1557     text => "Yes",
1558     connect_activate => sub {
1559     $self->send ("reply y");
1560     $dialog->destroy;
1561     },
1562     );
1563    
1564     $dialog->focus_in;
1565    
1566     } elsif ($flags & Crossfire::Protocol::CS_QUERY_SINGLECHAR) {
1567     $dialog->{tooltip} = "Press a key (click on the entry to make sure it has keyboard focus)";
1568     $vbox->add (my $entry = new CFClient::UI::Entry
1569     connect_changed => sub {
1570     $self->send ("reply $_[1]");
1571     $dialog->destroy;
1572     },
1573     );
1574    
1575     $entry->focus_in;
1576    
1577     } else {
1578     $dialog->{tooltip} = "Enter the reply and press return (click on the entry to make sure it has keyboard focus)";
1579    
1580     $vbox->add (my $entry = new CFClient::UI::Entry
1581     $flags & Crossfire::Protocol::CS_QUERY_HIDEINPUT ? (hiddenchar => "*") : (),
1582     connect_activate => sub {
1583     $self->send ("reply $_[1]");
1584     $dialog->destroy;
1585     },
1586     );
1587    
1588     $entry->focus_in;
1589     }
1590    
1591 elmex 1.238 $dialog->show_centered;
1592 root 1.33 }
1593    
1594 root 1.99 sub conn::drawinfo {
1595     my ($self, $color, $text) = @_;
1596    
1597     my @color = (
1598     [1.00, 1.00, 1.00], #[0.00, 0.00, 0.00],
1599     [1.00, 1.00, 1.00],
1600 root 1.117 [0.50, 0.50, 1.00], #[0.00, 0.00, 0.55]
1601 root 1.99 [1.00, 0.00, 0.00],
1602     [1.00, 0.54, 0.00],
1603     [0.11, 0.56, 1.00],
1604     [0.93, 0.46, 0.00],
1605     [0.18, 0.54, 0.34],
1606     [0.56, 0.73, 0.56],
1607     [0.80, 0.80, 0.80],
1608     [0.55, 0.41, 0.13],
1609     [0.99, 0.77, 0.26],
1610     [0.74, 0.65, 0.41],
1611     );
1612    
1613 root 1.208 my $time = sprintf "%02d:%02d:%02d", (localtime time)[2,1,0];
1614    
1615 root 1.219 $text = CFClient::UI::Label::escape $text;
1616 root 1.208 $text =~ s/\[b\](.*?)\[\/b\]/<b>\1<\/b>/g;
1617     $text =~ s/\[color=(.*?)\](.*?)\[\/color\]/<span foreground='\1'>\2<\/span>/g;
1618 root 1.209
1619     $LOGVIEW->add_paragraph ($color[$color],
1620     join "\n", map "$time $_", split /\n/, $text);
1621 root 1.211
1622 root 1.212 $STATUSBOX->add ($text,
1623 root 1.211 group => $text,
1624 root 1.215 fg => $color[$color],
1625 root 1.211 timeout => 60,
1626     tooltip_font => $::FONT_FIXED,
1627     );
1628 root 1.208 }
1629    
1630     sub conn::drawextinfo {
1631     my ($self, $color, $type, $subtype, $message) = @_;
1632    
1633     $self->drawinfo ($color, $message);
1634 root 1.99 }
1635    
1636 root 1.144 sub conn::spell_add {
1637 root 1.143 my ($self, $spell) = @_;
1638    
1639 root 1.171 # TODO
1640     # create a widget dynamically, using spell face (CF::Protocol downloads them)
1641 root 1.224 $MAPWIDGET->add_command ("invoke $spell->{name}", CFClient::UI::Label::escape $spell->{message});
1642     $MAPWIDGET->add_command ("cast $spell->{name}", CFClient::UI::Label::escape $spell->{message});
1643 root 1.144 }
1644    
1645     sub conn::spell_delete {
1646     my ($self, $spell) = @_;
1647     }
1648    
1649     sub conn::addme_success {
1650     my ($self) = @_;
1651    
1652 root 1.226 $self->send ("command output-sync $CFG->{output_sync}");
1653     $self->send ("command output-count $CFG->{output_count}");
1654 root 1.219
1655 root 1.234 my $parser = new Pod::POM;
1656     my $pod = $parser->parse_file (CFClient::find_rcfile "pod/skill_help.pod");
1657    
1658     my %skill_tooltip;
1659    
1660     for my $head2 ($pod->head2) {
1661     $skill_tooltip{$head2->title} = CFClient::pod_to_pango $head2->content;
1662     }
1663    
1664 root 1.144 for my $skill (values %{$self->{skill_info}}) {
1665 root 1.234 $MAPWIDGET->add_command ("ready_skill $skill",
1666     (CFClient::UI::Label::escape "Ready the skill '$skill'\n\n")
1667     . $skill_tooltip{$skill});
1668     $MAPWIDGET->add_command ("use_skill $skill",
1669     (CFClient::UI::Label::escape "Immediately use the skill '$skill'\n\n")
1670     . $skill_tooltip{$skill});
1671 root 1.219 }
1672 root 1.200 }
1673    
1674     sub conn::eof {
1675 root 1.219 $MAPWIDGET->clr_commands;
1676    
1677 root 1.200 stop_game;
1678 root 1.143 }
1679    
1680 root 1.173 sub update_floorbox {
1681     $CFClient::UI::ROOT->on_refresh ($FLOORBOX => sub {
1682 root 1.200 return unless $CONN;
1683    
1684 root 1.173 $FLOORBOX->clear;
1685     $FLOORBOX->add (new CFClient::UI::Empty expand => 1);
1686    
1687 root 1.242 my $count = 7;
1688 root 1.207 for (@{ $CONN->{container}{0} }) {
1689     if (--$count) {
1690     $FLOORBOX->add (new CFClient::UI::InventoryItem item => $_);
1691     } else {
1692     $FLOORBOX->add (new CFClient::UI::Label text => "More...");
1693     last;
1694     }
1695     }
1696 root 1.173 });
1697 root 1.206
1698     $WANT_REFRESH++;
1699 root 1.173 }
1700    
1701 root 1.169 sub conn::container_add {
1702 root 1.203 my ($self, $tag, $items) = @_;
1703    
1704 elmex 1.222 #d# print "container_add: container $tag ($self->{player}{tag})\n";
1705    
1706 elmex 1.217 if ($tag == 0) {
1707     update_floorbox;
1708 elmex 1.222 $OPENCONT = 0;
1709 elmex 1.223 $INVR_LBL->set_text ("Floor");
1710 elmex 1.217 $INVR->set_items ($self->{container}{0});
1711     } elsif ($tag == $self->{player}{tag}) {
1712 elmex 1.223 $INVR_LBL->set_text ("Player");
1713 elmex 1.217 $INV->set_items ($self->{container}{$self->{player}{tag}})
1714     } else {
1715     $OPENCONT = $tag;
1716 elmex 1.223 $INVR_LBL->set_text (CFClient::UI::InventoryItem::_item_to_desc ($self->{item}->{$OPENCONT}));
1717 elmex 1.217 $INVR->set_items ($self->{container}{$tag});
1718     }
1719 root 1.169
1720     # $self-<{player}{tag} => player inv
1721     #use PApp::Util; warn PApp::Util::dumpval $self->{container}{$self->{player}{tag}};
1722     }
1723    
1724     sub conn::container_clear {
1725 root 1.203 my ($self, $tag) = @_;
1726 root 1.173
1727 elmex 1.222 #d# print "container_clear: container $tag ($self->{player}{tag})\n";
1728    
1729 elmex 1.217 if ($tag == 0) {
1730     update_floorbox;
1731 elmex 1.222 $OPENCONT = 0;
1732 elmex 1.223 $INVR_LBL->set_text ("Floor");
1733 elmex 1.217 $INVR->set_items ($self->{container}{0});
1734     } elsif ($tag == $self->{player}{tag}) {
1735 elmex 1.223 $INVR_LBL->set_text ("Player");
1736 elmex 1.217 $INV->set_items ($self->{container}{$tag})
1737     } else {
1738 elmex 1.222 $OPENCONT = $tag;
1739 elmex 1.223 $INVR_LBL->set_text (CFClient::UI::InventoryItem::_item_to_desc ($self->{item}->{$OPENCONT}));
1740 elmex 1.217 $INVR->set_items ($self->{container}{$tag});
1741     }
1742 elmex 1.191
1743 root 1.169 # use PApp::Util; warn PApp::Util::dumpval $self->{container}{0};
1744     }
1745    
1746 root 1.173 sub conn::item_delete {
1747     my ($self, @items) = @_;
1748    
1749     for (@items) {
1750 elmex 1.222 #d# print "item_delete: $_->{tag} from $_->{container} ($self->{player}{tag})\n";
1751    
1752 elmex 1.217 if ($_->{container} == 0) {
1753     update_floorbox;
1754 elmex 1.222 $OPENCONT = 0;
1755 elmex 1.223 $INVR_LBL->set_text ("Floor");
1756 elmex 1.217 $INVR->set_items ($self->{container}{0});
1757     } elsif ($_->{container} == $self->{player}{tag}) {
1758 elmex 1.223 $INVR_LBL->set_text ("Player");
1759 elmex 1.217 $INV->set_items ($self->{container}{$self->{player}{tag}})
1760     } else {
1761 elmex 1.222 $OPENCONT = $_->{container};
1762 elmex 1.223 $INVR_LBL->set_text (CFClient::UI::InventoryItem::_item_to_desc ($self->{item}->{$OPENCONT}));
1763 elmex 1.222 $INVR->set_items ($self->{container}{$_->{container}});
1764 elmex 1.217 }
1765 root 1.173 }
1766     }
1767    
1768     sub conn::item_update {
1769     my ($self, $item) = @_;
1770    
1771 elmex 1.222 #d# print "item_update: $item->{tag} in $item->{container} ($self->{player}{tag}) ($OPENCONT)\n";
1772    
1773     if ($item->{tag} == $OPENCONT && not ($item->{flags} & Crossfire::Protocol::F_OPEN)) {
1774     $OPENCONT = 0;
1775 elmex 1.223 $INVR_LBL->set_text ("Floor");
1776 elmex 1.217 $INVR->set_items ($self->{container}{0});
1777 elmex 1.223
1778     $item->{widget}->update_item
1779     if $item->{widget};
1780 elmex 1.222 } else {
1781     if ($item->{container} == 0) {
1782     update_floorbox;
1783     $OPENCONT = 0;
1784 elmex 1.223 $INVR_LBL->set_text ("Floor");
1785 elmex 1.222 $INVR->set_items ($self->{container}{0});
1786     } elsif ($item->{container} == $self->{player}{tag}) {
1787     $INV->set_items ($self->{container}{$item->{container}})
1788     }
1789 elmex 1.217 }
1790 root 1.173 }
1791    
1792 root 1.87 %SDL_CB = (
1793 root 1.145 CFClient::SDL_QUIT => sub {
1794 root 1.87 Event::unloop -1;
1795     },
1796 root 1.145 CFClient::SDL_VIDEORESIZE => sub {
1797 root 1.87 },
1798 root 1.206 CFClient::SDL_VIDEOEXPOSE => sub {
1799 root 1.236 CFClient::UI::full_refresh;
1800 root 1.206 },
1801 root 1.153 CFClient::SDL_ACTIVEEVENT => sub {
1802     # printf "active %x %x\n", $SDL_EV->active_gain, $SDL_EV->active_state;#d#
1803 root 1.87 },
1804 root 1.145 CFClient::SDL_KEYDOWN => sub {
1805 root 1.147 if ($_[0]{mod} & CFClient::KMOD_ALT && $_[0]{sym} == 13) {
1806 root 1.87 # alt-enter
1807 root 1.134 video_shutdown;
1808 root 1.99 $CFG->{fullscreen} = !$CFG->{fullscreen};
1809 root 1.134 video_init;
1810 root 1.87 } else {
1811 root 1.147 CFClient::UI::feed_sdl_key_down_event ($_[0]);
1812 elmex 1.23 }
1813 root 1.87 },
1814 root 1.198 CFClient::SDL_KEYUP => \&CFClient::UI::feed_sdl_key_up_event,
1815     CFClient::SDL_MOUSEMOTION => \&CFClient::UI::feed_sdl_motion_event,
1816 root 1.153 CFClient::SDL_MOUSEBUTTONDOWN => \&CFClient::UI::feed_sdl_button_down_event,
1817 root 1.198 CFClient::SDL_MOUSEBUTTONUP => \&CFClient::UI::feed_sdl_button_up_event,
1818     CFClient::SDL_USEREVENT => sub {
1819     if ($_[0]{code} == 1) {
1820     audio_channel_finished $_[0]{data1};
1821     } elsif ($_[0]{code} == 0) {
1822     audio_music_finished;
1823     }
1824     },
1825 root 1.87 );
1826 elmex 1.23
1827 root 1.1 #############################################################################
1828    
1829 root 1.131 $SIG{INT} = $SIG{TERM} = sub { exit };
1830    
1831 root 1.205 {
1832     local $SIG{__DIE__} = sub { CFClient::fatal $_[0] };
1833 root 1.194
1834 root 1.205 CFClient::read_cfg "$Crossfire::VARDIR/pclientrc";
1835 root 1.114
1836 root 1.205 $TILECACHE = CFClient::db_table "tilecache";
1837     $FACEMAP = CFClient::db_table "facemap";
1838 root 1.90
1839 root 1.205 my %DEF_CFG = (
1840     sdl_mode => 0,
1841     width => 640,
1842     height => 480,
1843     fullscreen => 0,
1844     fast => 0,
1845 root 1.230 map_scale => 1,
1846 root 1.205 fow_enable => 1,
1847     fow_intensity => 0.45,
1848     fow_smooth => 0,
1849     gui_fontsize => 1,
1850     log_fontsize => 1,
1851 root 1.206 gauge_fontsize=> 1,
1852     gauge_size => 0.35,
1853 root 1.205 stat_fontsize => 1,
1854     mapsize => 100,
1855     host => "crossfire.schmorp.de",
1856     say_command => 'say',
1857     audio_enable => 1,
1858     bgm_enable => 1,
1859     bgm_volume => 0.25,
1860 root 1.226 output_sync => 1,
1861     output_count => 1,
1862 root 1.205 );
1863 root 1.87
1864 root 1.205 while (my ($k, $v) = each %DEF_CFG) {
1865     $CFG->{$k} = $v unless exists $CFG->{$k};
1866     }
1867    
1868     sdl_init;
1869    
1870     @SDL_MODES = reverse
1871     grep $_->[0] >= 640 && $_->[1] >= 480,
1872     CFClient::SDL_ListModes;
1873    
1874     @SDL_MODES or CFClient::fatal "Unable to find a usable video mode\n(hardware accelerated opengl fullscreen)";
1875    
1876     $CFG->{sdl_mode} = 0 if $CFG->{sdl_mode} > @SDL_MODES;
1877    
1878     {
1879     my @fonts = map CFClient::find_rcfile "fonts/$_", qw(
1880     DejaVuSans.ttf
1881     DejaVuSansMono.ttf
1882     DejaVuSans-Bold.ttf
1883     DejaVuSansMono-Bold.ttf
1884     DejaVuSans-Oblique.ttf
1885     DejaVuSansMono-Oblique.ttf
1886     DejaVuSans-BoldOblique.ttf
1887     DejaVuSansMono-BoldOblique.ttf
1888     );
1889    
1890     CFClient::add_font $_ for @fonts;
1891    
1892 root 1.214 CFClient::pango_init;
1893    
1894 root 1.205 $FONT_PROP = new_from_file CFClient::Font $fonts[0];
1895     $FONT_FIXED = new_from_file CFClient::Font $fonts[1];
1896 root 1.89
1897 root 1.205 $FONT_PROP->make_default;
1898     }
1899 root 1.89
1900 root 1.219 # compare mono (ft) vs. rgba (cairo)
1901     # ft - 1.8s, cairo 3s, even in alpha-only mode
1902     # for my $rgba (0..1) {
1903     # my $t1 = Time::HiRes::time;
1904     # for (1..1000) {
1905     # my $layout = CFClient::Layout->new ($rgba);
1906     # $layout->set_text ("hallo" x 100);
1907     # $layout->render;
1908     # }
1909     # my $t2 = Time::HiRes::time;
1910     # warn $t2-$t1;
1911     # }
1912    
1913 root 1.205 video_init;
1914     audio_init;
1915 root 1.65 }
1916 root 1.40
1917 root 1.87 Event::loop;
1918 root 1.19
1919 root 1.148 END { CFClient::SDL_Quit }
1920 root 1.131
1921 root 1.241 =head1 NAME
1922 root 1.178
1923 root 1.241 pclient - A Crossfire+ and Crossfire game client
1924 root 1.178
1925 root 1.241 =head1 SYNOPSIS
1926 root 1.178
1927 root 1.241 Just run it - no commandline arguments are supported.
1928 root 1.178
1929 root 1.179 =head1 USAGE
1930    
1931 root 1.241 Pclient utilises OpenGL for all UI elements and the game. It is supposed to be used
1932     fullscreen and interactively.
1933 root 1.178
1934     =head1 AUTHOR
1935    
1936     Marc Lehmann <crossfire@schmorp.de>, Robin Redeker <elmex@ta-sa.org>
1937    
1938    
1939 root 1.82