ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.19
Committed: Sat May 27 21:15:57 2006 UTC (18 years ago) by elmex
Branch: MAIN
Changes since 1.18: +24 -6 lines
Log Message:
toggle_visibility and default toplevel window positions

File Contents

# User Rev Content
1 root 1.1 #!/opt/bin/perl
2    
3     use strict;
4     use utf8;
5    
6     # do things only needed for single-binary version (par)
7     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     # TODO: pango-rc file, anybody?
20    
21     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     unshift @INC, $ENV{PAR_TEMP}
27     if %PAR::LibCache;
28    
29     use Time::HiRes 'time';
30     use Pod::POM;
31     use Event;
32    
33     use Crossfire;
34 root 1.12 use Crossfire::Protocol::Constants;
35 root 1.1
36     use Compress::LZF;
37    
38     use CFClient;
39 root 1.10 use CFClient::OpenGL ();
40 root 1.11 use CFClient::Protocol;
41 root 1.1 use CFClient::UI;
42     use CFClient::MapWidget;
43    
44     $Event::DIED = sub {
45     # TODO: display dialog box or so
46     CFClient::error $_[1];
47     };
48    
49     #$SIG{__WARN__} = sub { Carp::cluck $_[0] };#d#
50    
51     our $VERSION = '0.1';
52    
53     my $MAX_FPS = 60;
54     my $MIN_FPS = 5; # unused as of yet
55    
56     our $META_SERVER = "crossfire.real-time.com:13326";
57    
58     our $LAST_REFRESH;
59     our $NOW;
60    
61     our $CFG;
62     our $CONN;
63     our $FAST; # fast, low-quality mode, possibly useful for software-rendering
64    
65     our $WANT_REFRESH;
66     our $CAN_REFRESH;
67    
68     our @SDL_MODES;
69     our $WIDTH;
70     our $HEIGHT;
71     our $FULLSCREEN;
72     our $FONTSIZE;
73    
74     our $FONT_PROP;
75     our $FONT_FIXED;
76    
77     our $MAP;
78     our $MAPMAP;
79     our $MAPWIDGET;
80     our $BUTTONBAR;
81     our $LOGVIEW;
82     our $CONSOLE;
83     our $METASERVER;
84     our $LOGIN_BUTTON;
85     our $QUIT_DIALOG;
86    
87     our $FLOORBOX;
88     our $GAUGES;
89     our $STATWIDS;
90    
91     our $SDL_ACTIVE;
92     our %SDL_CB;
93    
94     our $SDL_MIXER;
95     our @SOUNDS; # event => file mapping
96     our %AUDIO_CHUNKS; # audio files
97    
98     our $ALT_ENTER_MESSAGE;
99     our $STATUSBOX;
100     our $DEBUG_STATUS;
101    
102     our $INV;
103     our $INVR;
104     our $INVR_LBL;
105    
106     sub status {
107     $STATUSBOX->add (CFClient::UI::Label::escape $_[0], pri => -10, group => "status", timeout => 10, fg => [1, 1, 0, 1]);
108     }
109    
110     sub debug {
111     $DEBUG_STATUS->set_text ($_[0]);
112     my ($w, $h) = $DEBUG_STATUS->size_request;
113     $DEBUG_STATUS->move ($WIDTH - $w, 0);
114     }
115    
116     sub start_game {
117     status "logging in...";
118    
119     my $mapsize = List::Util::min 32, List::Util::max 11, int $WIDTH * $CFG->{mapsize} * 0.01 / 32;
120    
121 root 1.11 my ($host, $port) = split /:/, $CFG->{host};
122    
123 root 1.1 $MAP = new CFClient::Map $mapsize, $mapsize;
124    
125     $CONN = eval {
126 root 1.11 new CFClient::Protocol
127 root 1.1 host => $host,
128     port => $port || 13327,
129     user => $CFG->{user},
130     pass => $CFG->{password},
131     mapw => $mapsize,
132     maph => $mapsize,
133 root 1.11
134     map_widget => $MAPWIDGET,
135     logview => $LOGVIEW,
136     statusbox => $STATUSBOX,
137     map => $MAP,
138     mapmap => $MAPMAP,
139    
140     sound_play => sub {
141     my ($x, $y, $soundnum, $type) = @_;
142    
143     $SDL_MIXER
144     or return;
145    
146     my $chunk = $AUDIO_CHUNKS{$SOUNDS[$soundnum]}
147     or return;
148    
149     $chunk->play;
150     },
151 root 1.1 };
152    
153     if ($CONN) {
154     CFClient::lowdelay fileno $CONN->{fh};
155    
156     $LOGIN_BUTTON->set_text ("Logout");
157     status "login successful";
158    
159     $BUTTONBAR->{children}[1]->emit ("activate")
160     if $BUTTONBAR->{children}[1]->{state};
161    
162     } else {
163     status "unable to connect";
164     stop_game();
165     }
166     }
167    
168     sub stop_game {
169     return unless $CONN;
170    
171     status "connection closed";
172     $LOGIN_BUTTON->set_text ("Login");
173     $CONN->destroy;
174     $CONN = 0; # false, does not autovivify
175    
176     $BUTTONBAR->{children}[1]->emit ("activate")
177     unless $BUTTONBAR->{children}[1]->{state};
178     }
179    
180     sub client_setup {
181     my $dialog = new CFClient::UI::FancyFrame
182 elmex 1.19 req_x => 1,
183     req_y => $HEIGHT * (1/8),
184 elmex 1.16 name => "client_setup",
185 root 1.1 title => "Client Setup",
186     child => (my $vbox = new CFClient::UI::VBox);
187     $vbox->add (my $table = new CFClient::UI::Table expand => 1, col_expand => [0, 1]);
188    
189     $table->add (0, 0, new CFClient::UI::Label valign => 0, align => 1, text => "Video Mode");
190     $table->add (1, 0, my $hbox = new CFClient::UI::HBox);
191    
192     $hbox->add (my $mode_slider = new CFClient::UI::Slider expand => 1, req_w => 100, range => [$CFG->{sdl_mode}, 0, $#SDL_MODES, 0, 1]);
193     $hbox->add (my $mode_label = new CFClient::UI::Label align => 0, valign => 0, height => 0.8, template => "9999x9999");
194    
195     $mode_slider->connect (changed => sub {
196     my ($self, $value) = @_;
197    
198     $CFG->{sdl_mode} = $self->{range}[0] = $value = int $value;
199     $mode_label->set_text (sprintf "%dx%d", @{$SDL_MODES[$value]});
200     });
201     $mode_slider->emit (changed => $mode_slider->{range}[0]);
202    
203     my $row = 1;
204    
205     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Fullscreen");
206     $table->add (1, $row++, new CFClient::UI::CheckBox
207     state => $CFG->{fullscreen},
208     tooltip => "Bring the client into fullscreen mode.",
209 root 1.18 on_changed => sub {
210 root 1.1 my ($self, $value) = @_;
211     $CFG->{fullscreen} = $value;
212     }
213     );
214    
215     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Fast & Ugly");
216     $table->add (1, $row++, new CFClient::UI::CheckBox
217     state => $CFG->{fast},
218     tooltip => "Lower the visual quality considerably to speed up rendering.",
219 root 1.18 on_changed => sub {
220 root 1.1 my ($self, $value) = @_;
221     $CFG->{fast} = $value;
222     }
223     );
224    
225     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Map Scale");
226     $table->add (1, $row++, new CFClient::UI::Slider
227     range => [(log $CFG->{map_scale}) / (log 2), -3, 1, 0, 1],
228     tooltip => "Enlarge or shrink the displayed map. Changes are instant.",
229 root 1.18 on_changed => sub {
230 root 1.1 my ($self, $value) = @_;
231     $CFG->{map_scale} = 2 ** $value;
232     }
233     );
234    
235     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Fog of War");
236     $table->add (1, $row++, new CFClient::UI::CheckBox
237     state => $CFG->{fow_enable},
238     tooltip => "<b>Fog-of-War</b> marks areas that cannot be seen by the player. Changes are instant.",
239 root 1.18 on_changed => sub {
240 root 1.1 my ($self, $value) = @_;
241     $CFG->{fow_enable} = $value;
242     }
243     );
244    
245     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "FoW Intensity");
246     $table->add (1, $row++, new CFClient::UI::Slider
247     range => [$CFG->{fow_intensity}, 0, 1, 0, 1 / 256],
248     tooltip => "<b>Fog of War Lightness.</b> The higher the intensity, the lighter the Fog-of-War color. Changes are instant.",
249 root 1.18 on_changed => sub {
250 root 1.1 my ($self, $value) = @_;
251     $CFG->{fow_intensity} = $value;
252     }
253     );
254    
255     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "FoW Smooth");
256     $table->add (1, $row++, new CFClient::UI::CheckBox
257     state => $CFG->{fow_smooth},
258     tooltip => "Smooth the Fog-of-War a bit to make it more realistic. Changes are instant.",
259 root 1.18 on_changed => sub {
260 root 1.1 my ($self, $value) = @_;
261     $CFG->{fow_smooth} = $value;
262 root 1.15 status "Fog of War smoothing requires OpenGL 1.2 or higher" if $CFClient::OpenGL::GL_VERSION < 1.2;
263 root 1.1 }
264     );
265    
266     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "GUI Fontsize");
267     $table->add (1, $row++, new CFClient::UI::Slider
268     range => [$CFG->{gui_fontsize}, 0.5, 2, 0, 0.1],
269     tooltip => "The base font size used by most GUI elements that do not have their own setting.",
270 root 1.18 on_changed => sub { $CFG->{gui_fontsize} = $_[1] },
271 root 1.1 );
272    
273     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Message Fontsize");
274     $table->add (1, $row++, new CFClient::UI::Slider
275     range => [$CFG->{log_fontsize}, 0.5, 2, 0, 0.1],
276     tooltip => "The font size used by the <b>message/server log</b> window only. Changes are instant.",
277 root 1.18 on_changed => sub { $LOGVIEW->set_fontsize ($CFG->{log_fontsize} = $_[1]) },
278 root 1.1 );
279    
280     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Stats Fontsize");
281    
282     $table->add (1, $row++, new CFClient::UI::Slider
283     range => [$CFG->{stat_fontsize}, 0.5, 2, 0, 0.1],
284     tooltip => "The font size used by the <b>statistics window</b> only. Changes are instant.",
285 root 1.18 on_changed => sub {
286 root 1.1 $CFG->{stat_fontsize} = $_[1];
287     &set_stats_window_fontsize;
288     }
289     );
290    
291     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Gauge fontsize");
292     $table->add (1, $row++, new CFClient::UI::Slider
293     range => [$CFG->{gauge_fontsize}, 0.5, 2, 0, 0.1],
294     tooltip => "Adjusts the fontsize of the gauges at the bottom right. Changes are instant.",
295 root 1.18 on_changed => sub {
296 root 1.1 $CFG->{gauge_fontsize} = $_[1];
297     &set_gauge_window_fontsize;
298     }
299     );
300    
301     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Gauge size");
302     $table->add (1, $row++, new CFClient::UI::Slider
303 root 1.18 range => [$CFG->{gauge_size}, 0.2, 0.8],
304     tooltip => "Adjust the size of the stats gauges at the bottom right. Changes are instant.",
305     on_changed => sub {
306 root 1.1 $CFG->{gauge_size} = $_[1];
307     $GAUGES->{win}->set_size ($WIDTH, int $HEIGHT * $CFG->{gauge_size});
308     }
309     );
310    
311     $table->add (1, $row++, new CFClient::UI::Button
312     expand => 1, align => 0, text => "Apply",
313     tooltip => "Apply the video settings",
314 root 1.18 on_activate => sub {
315 root 1.1 video_shutdown ();
316     video_init ();
317     }
318     );
319    
320     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Audio Enable");
321     $table->add (1, $row++, new CFClient::UI::CheckBox
322     state => $CFG->{audio_enable},
323     tooltip => "<b>Master Audio Enable.</b> If enabled, sound effects and music will be played. If disabled, no audio will be used and the soundcard will not be opened.",
324 root 1.18 on_changed => sub {
325 root 1.1 $CFG->{audio_enable} = $_[1];
326     }
327     );
328     # $table->add (0, 9, new CFClient::UI::Label valign => 0, align => 1, text => "Effects Volume");
329 root 1.18 # $table->add (1, 8, new CFClient::UI::Slider range => [$CFG->{effects_volume}, 0, 128, 1], on_changed => sub {
330 root 1.1 # $CFG->{effects_volume} = $_[1];
331     # });
332     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Background Music");
333     $table->add (1, $row++, my $hbox = new CFClient::UI::HBox);
334     $hbox->add (new CFClient::UI::CheckBox
335     expand => 1, state => $CFG->{bgm_enable},
336     tooltip => "If enabled, playing of background music is enabled. If disabled, no background music will be played.",
337 root 1.18 on_changed => sub {
338 root 1.1 $CFG->{bgm_enable} = $_[1];
339     }
340     );
341     $hbox->add (new CFClient::UI::Slider
342     expand => 1, range => [$CFG->{bgm_volume}, 0, 1, 0, 1/128],
343     tooltip => "The volume of the background music. Changes are instant.",
344 root 1.18 on_changed => sub {
345 root 1.1 $CFG->{bgm_volume} = $_[1];
346     CFClient::MixMusic::volume $_[1] * 128;
347     }
348     );
349    
350     $table->add (1, $row++, new CFClient::UI::Button
351     expand => 1, align => 0, text => "Apply",
352     tooltip => "Apply the audio settings",
353 root 1.18 on_activate => sub {
354 root 1.1 audio_shutdown ();
355     audio_init ();
356     }
357     );
358    
359     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Chat Command");
360     $table->add (1, $row++, my $saycmd = new CFClient::UI::Entry
361     text => $CFG->{say_command},
362     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. "
363     . "Usually you want to enter something like 'say' or 'shout' or 'gsay' here. "
364     . "But you could also set it to <b>tell <i>playername</i></b> to only chat with that user.",
365 root 1.18 on_changed => sub {
366 root 1.1 my ($self, $value) = @_;
367     $CFG->{say_command} = $value;
368     }
369     );
370    
371     $dialog
372     }
373    
374     sub set_stats_window_fontsize {
375     for (values %{$STATWIDS}) {
376     $_->set_fontsize ($::CFG->{stat_fontsize});
377     }
378     }
379    
380     sub set_gauge_window_fontsize {
381     for (map { $GAUGES->{$_} } grep { $_ ne 'win' } keys %{$GAUGES}) {
382     $_->set_fontsize ($::CFG->{gauge_fontsize});
383     }
384     }
385    
386     sub make_gauge_window {
387     my $gh = int $HEIGHT * $CFG->{gauge_size};
388    
389     my $win = new CFClient::UI::Frame (
390     req_y => -1,
391     user_w => $WIDTH,
392     user_h => $gh,
393     );
394    
395     $win->add (my $hbox = new CFClient::UI::HBox
396     children => [
397     (new CFClient::UI::HBox expand => 1),
398     (new CFClient::UI::VBox children => [
399     (new CFClient::UI::Empty expand => 1),
400 root 1.2 (new CFClient::UI::Frame bg => [0, 0, 0, 0.4], child => ($FLOORBOX = new CFClient::UI::Table)),
401 root 1.1 ]),
402     (my $vbox = new CFClient::UI::VBox),
403     ],
404     );
405    
406     $vbox->add (new CFClient::UI::HBox
407     expand => 1,
408     children => [
409     (new CFClient::UI::Empty expand => 1),
410     (my $hb = new CFClient::UI::HBox),
411     ],
412     );
413    
414     $hb->add (my $hg = new CFClient::UI::Gauge type => 'hp',
415     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.");
416     $hb->add (my $mg = new CFClient::UI::Gauge type => 'mana',
417     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.");
418     $hb->add (my $gg = new CFClient::UI::Gauge type => 'grace',
419     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.");
420     $hb->add (my $fg = new CFClient::UI::Gauge type => 'food',
421     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.");
422    
423     $vbox->add (my $exp = new CFClient::UI::Label valign => 0, align => 1, can_hover => 1, can_events => 1,
424     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.");
425     $vbox->add (my $rng = new CFClient::UI::Label valign => 0, align => 1, can_hover => 1, can_events => 1,
426     tooltip => "<b>Ranged attack</b> - how you attack when you press shift-cursor (spell, skill, weapon etc.)");
427    
428     $GAUGES = {
429     exp => $exp, win => $win, range => $rng,
430     food => $fg, mana => $mg, hp => $hg, grace => $gg
431     };
432    
433     &set_gauge_window_fontsize;
434    
435     $win
436     }
437    
438     sub make_stats_window {
439 elmex 1.19 my $tgw = new CFClient::UI::FancyFrame
440     req_y => $HEIGHT * (2/8),
441     req_x => -1,
442     title => "Stats",
443     name => "stats_window";
444 root 1.1
445     $tgw->add (new CFClient::UI::Window child => my $vb = new CFClient::UI::VBox);
446     $vb->add ($STATWIDS->{title} = new CFClient::UI::Label valign => 0, align => -1, text => "Title:", expand => 1,
447     can_hover => 1, can_events => 1,
448     tooltip => "Your name and title. You can change your title by using the <b>title</b> command, if supported by the server.");
449     $vb->add ($STATWIDS->{map} = new CFClient::UI::Label valign => 0, align => -1, text => "Map:", expand => 1,
450     can_hover => 1, can_events => 1,
451     tooltip => "The map you are currently on (if supported by the server).");
452    
453 elmex 1.5 $vb->add (my $hb0 = new CFClient::UI::HBox);
454     $hb0->add ($STATWIDS->{weight} = new CFClient::UI::Label valign => 0, align => -1, text => "Weight:", expand => 1,
455     can_hover => 1, can_events => 1,
456 root 1.15 tooltip => "The weight of the player including all inventory items.");
457 elmex 1.5 $hb0->add ($STATWIDS->{m_weight} = new CFClient::UI::Label valign => 0, align => -1, text => "Max weight:", expand => 1,
458     can_hover => 1, can_events => 1,
459 root 1.15 tooltip => "The weight limit: you cannot carry more than this.");
460 elmex 1.5
461    
462 root 1.1 $vb->add (my $hb = new CFClient::UI::HBox expand => 1);
463     $hb->add (my $tbl = new CFClient::UI::Table expand => 1);
464    
465     my $color2 = [1, 1, 0];
466    
467     for (
468     [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"],
469     [0, 1, st_dex => "Dex", 30, "<b>Dexterity</b>, your physical agility. Determines chance of being hit and affects armor class and speed"],
470     [0, 2, st_con => "Con", 30, "<b>Constitution</b>, physical health and toughness. Determines how many healthpoints you can have"],
471     [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"],
472     [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"],
473     [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"],
474     [0, 6, st_cha => "Cha", 30, "<b>Charisma</b>, how well you are received by NPCs. Affects buying and selling prices in shops."],
475    
476     [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."],
477     [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."],
478     [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."],
479     [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."],
480     [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."],
481     [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."],
482     ) {
483     my ($col, $row, $id, $label, $template, $tooltip) = @$_;
484    
485     $tbl->add ($col , $row, $STATWIDS->{$id} = new CFClient::UI::Label
486     font => $FONT_FIXED, can_hover => 1, can_events => 1, valign => 0, align => +1, template => $template, tooltip => $tooltip);
487     $tbl->add ($col + 1, $row, $STATWIDS->{"$id\_lbl"} = new CFClient::UI::Label
488     font => $FONT_FIXED, can_hover => 1, can_events => 1, fg => $color2, valign => 0, align => -1, text => $label, tooltip => $tooltip);
489     }
490    
491     $hb->add (my $tbl2 = new CFClient::UI::Table expand => 1);
492    
493     my $row = 0;
494     my $col = 0;
495    
496     my %resist_names = (
497     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.)",
498     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.)",
499     conf => "<b>Confusion</b> (If you are hit by confusion you will move into random directions, and likely into monsters.)",
500     fire => "<b>Fire</b> (just your resistance to fire spells like burning hands, dragonbreath, meteor swarm fire, ...)",
501     depl => "<b>Depletion</b> (some monsters and other effects can cause stats depletion)",
502     magic => "<b>Magic</b> (resistance to magic spells like magic missile or similar)",
503     drain => "<b>Draining</b> (some monsters (e.g. vampires) and other effects can steal experience)",
504     acid => "<b>Acid</b> (resistance to acid, acid hurts pretty much and also corrodes your weapons)",
505     pois => "<b>Poison</b> (resistance to getting poisoned)",
506     para => "<b>Paralysation</b> (this resistance affects the chance you get paralysed)",
507     deat => "<b>Death</b> (resistance against death spells)",
508     phys => "<b>Physical</b> (this is the resistance against physical attacks, like when a monster hit you in melee combat)",
509     blind => "<b>Blind</b> (blind resistance affects the chance of a successful blinding attack)",
510     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)",
511     tund => "<b>Turn undead</b> (affects your resistancy to various forms of 'turn undead' spells. Only relevant when you are, in fact, undead...",
512     elec => "<b>Electricity</b> (resistance against electricity, spells like large lightning, small lightning, ...)",
513     cold => "<b>Cold</b> (this is your resistance against cold spells like icestorm, snowstorm, ...)",
514     ghit => "<b>Ghost hit</b> (special attack used by ghosts and ghost-like beings)",
515     );
516     for (qw/slow holyw conf fire depl magic
517     drain acid pois para deat phys
518     blind fear tund elec cold ghit/)
519     {
520     $tbl2->add ($col, $row,
521     $STATWIDS->{"res_$_"} =
522     new CFClient::UI::Label
523     font => $FONT_FIXED,
524     template => "-100%",
525     align => +1,
526     valign => 0,
527     can_events => 1,
528     can_hover => 1,
529     tooltip => $resist_names{$_},
530     );
531     $tbl2->add ($col + 1, $row, new CFClient::UI::Image
532     font => $FONT_FIXED,
533     can_hover => 1,
534     can_events => 1,
535     image => "ui/resist/resist_$_.png",
536     tooltip => $resist_names{$_},
537     );
538    
539     $row++;
540     if ($row % 6 == 0) {
541     $col += 2;
542     $row = 0;
543     }
544     }
545    
546     &set_stats_window_fontsize;
547     update_stats_window ({});
548    
549     $tgw
550     }
551    
552     sub formsep {
553     reverse join ",", grep length, split /(...)/, reverse $_[0] * 1
554     }
555    
556     sub update_stats_window {
557     my ($stats) = @_;
558    
559 root 1.12 # I love text protocols...
560    
561     my $hp = $stats->{+CS_STAT_HP} * 1;
562     my $hp_m = $stats->{+CS_STAT_MAXHP} * 1;
563     my $sp = $stats->{+CS_STAT_SP} * 1;
564     my $sp_m = $stats->{+CS_STAT_MAXSP} * 1;
565     my $fo = $stats->{+CS_STAT_FOOD} * 1;
566 root 1.1 my $fo_m = 999;
567 root 1.12 my $gr = $stats->{+CS_STAT_GRACE} * 1;
568     my $gr_m = $stats->{+CS_STAT_MAXGRACE} * 1;
569 root 1.1
570     $GAUGES->{hp} ->set_value ($hp, $hp_m);
571     $GAUGES->{mana} ->set_value ($sp, $sp_m);
572     $GAUGES->{food} ->set_value ($fo, $fo_m);
573     $GAUGES->{grace} ->set_value ($gr, $gr_m);
574 root 1.12 $GAUGES->{exp} ->set_text ("Exp: " . (formsep $stats->{+CS_STAT_EXP64})
575     . " (lvl " . ($stats->{+CS_STAT_LEVEL} * 1) . ")");
576     my $rng = $stats->{+CS_STAT_RANGE};
577 root 1.1 $rng =~ s/^Range: //; # thank you so much dear server
578     $GAUGES->{range} ->set_text ("Rng: " . $rng);
579 root 1.12 my $title = $stats->{+CS_STAT_TITLE};
580 root 1.1 $title =~ s/^Player: //;
581     $STATWIDS->{title} ->set_text ("Title: " . $title);
582    
583 root 1.12 $STATWIDS->{st_str} ->set_text (sprintf "%d" , $stats->{+CS_STAT_STR});
584     $STATWIDS->{st_dex} ->set_text (sprintf "%d" , $stats->{+CS_STAT_DEX});
585     $STATWIDS->{st_con} ->set_text (sprintf "%d" , $stats->{+CS_STAT_CON});
586     $STATWIDS->{st_int} ->set_text (sprintf "%d" , $stats->{+CS_STAT_INT});
587     $STATWIDS->{st_wis} ->set_text (sprintf "%d" , $stats->{+CS_STAT_WIS});
588     $STATWIDS->{st_pow} ->set_text (sprintf "%d" , $stats->{+CS_STAT_POW});
589     $STATWIDS->{st_cha} ->set_text (sprintf "%d" , $stats->{+CS_STAT_CHA});
590     $STATWIDS->{st_wc} ->set_text (sprintf "%d" , $stats->{+CS_STAT_WC});
591     $STATWIDS->{st_ac} ->set_text (sprintf "%d" , $stats->{+CS_STAT_AC});
592     $STATWIDS->{st_dam} ->set_text (sprintf "%d" , $stats->{+CS_STAT_DAM});
593     $STATWIDS->{st_arm} ->set_text (sprintf "%d" , $stats->{+CS_STAT_ARMOUR});
594     $STATWIDS->{st_spd} ->set_text (sprintf "%.1f", $stats->{+CS_STAT_SPEED});
595     $STATWIDS->{st_wspd}->set_text (sprintf "%.1f", $stats->{+CS_STAT_WEAP_SP});
596 root 1.1
597 root 1.12 $STATWIDS->{m_weight}->set_text (sprintf "Max weight: %.1fkg", $stats->{+CS_STAT_WEIGHT_LIM} / 1000);
598 elmex 1.5
599 root 1.12 # TODO: replace by CS_STAT_RES_xxx constants
600 root 1.1 my %tbl = (
601     phys => 100,
602     magic => 101,
603     fire => 102,
604     elec => 103,
605     cold => 104,
606     conf => 105,
607     acid => 106,
608     drain => 107,
609     ghit => 108,
610     pois => 109,
611     slow => 110,
612     para => 111,
613     tund => 112,
614     fear => 113,
615     depl => 113,
616     deat => 115,
617     holyw => 116,
618 root 1.12 blind => 117,
619 root 1.1 );
620    
621 root 1.12 $STATWIDS->{"res_$_"}->set_text (sprintf "%d%", $stats->{$tbl{$_}})
622     for keys %tbl;
623 root 1.1 }
624    
625     sub metaserver_dialog {
626     my $dialog = new CFClient::UI::FancyFrame
627     title => "Server List",
628     child => (my $vbox = new CFClient::UI::VBox);
629    
630     $vbox->add ($dialog->{table} = new CFClient::UI::Table);
631    
632     $dialog
633     }
634    
635     my $METASERVER_ATIME;
636    
637     sub update_metaserver {
638     my ($HOST) = @_;
639    
640     return if $METASERVER_ATIME > time;
641     $METASERVER_ATIME = time + 60;
642    
643     my $table = $METASERVER->{table};
644     $table->clear;
645     $table->add (0, 0, my $label = new CFClient::UI::Label max_w => $WIDTH * 0.8, text => "fetching server list...");
646    
647     my $buf;
648    
649     my $fh = new IO::Socket::INET PeerHost => $META_SERVER, Blocking => 0;
650    
651     unless ($fh) {
652     $label->set_text ("unable to contact metaserver: $!");
653     return;
654     }
655    
656     Event->io (fd => $fh, poll => 'r', cb => sub {
657     my $res = sysread $fh, $buf, 8192, length $buf;
658    
659     if (!defined $res) {
660     $_[0]->w->cancel;
661     $label->set_text ("error while retrieving server list: $!");
662     } elsif ($res == 0) {
663     $_[0]->w->cancel;
664     status "server list retrieved";
665    
666     utf8::decode $buf if utf8::valid $buf;
667    
668     $table->clear;
669    
670     my @col = qw(Use #Users Host Uptime Version Description);
671     $table->add ($_, 0, new CFClient::UI::Label align => 0, fg => [1, 1, 0], text => $col[$_])
672     for 0 .. $#col;
673    
674     my @align = qw(1 0 1 1 -1);
675    
676     my $y = 0;
677     for my $m (sort { $b->[3] <=> $a->[3] } map [split /\|/], split /\015?\012/, $buf) {
678     my ($ip, $last, $host, $users, $version, $desc, $ibytes, $obytes, $uptime) = @$m;
679    
680     for ($desc) {
681     s/<br>/\n/gi;
682     s/<li>/\n· /gi;
683     s/<.*?>//sgi;
684     s/&/&amp;/g;
685     s/</&lt;/g;
686     s/>/&gt;/g;
687     }
688    
689     $uptime = sprintf "%dd %02d:%02d:%02d",
690     (int $m->[8] / 86400),
691     (int $m->[8] / 3600) % 24,
692     (int $m->[8] / 60) % 60,
693     $m->[8] % 60;
694    
695     $m = [$users, $host, $uptime, $version, $desc];
696    
697     $y++;
698    
699     $table->add (0, $y, new CFClient::UI::VBox children => [
700 root 1.18 (new CFClient::UI::Button text => "Use", on_activate => sub {
701 root 1.1 $HOST->set_text ($CFG->{host} = $host);
702     }),
703     (new CFClient::UI::Empty expand => 1),
704     ]);
705    
706     $table->add ($_ + 1, $y, new CFClient::UI::Label
707     ellipsise => 0, align => $align[$_], text => $m->[$_], fontsize => 0.8)
708     for 0 .. $#$m;
709     }
710     }
711     });
712     }
713    
714     sub server_setup {
715     my $dialog = new CFClient::UI::FancyFrame
716 elmex 1.19 x => $WIDTH * (1/3),
717     y => $HEIGHT * (1/8),
718     name => "server_setup",
719     title => "Server Setup",
720     child => (my $vbox = new CFClient::UI::VBox),
721     on_visibility_change => sub {
722     $_[0]->show_centered if $_[1]
723     };
724    
725 root 1.1 $vbox->add (my $table = new CFClient::UI::Table expand => 1, col_expand => [0, 1]);
726     $table->add (0, 2, new CFClient::UI::Label valign => 0, align => 1, text => "Host:Port");
727    
728     {
729     $table->add (1, 2, my $vbox = new CFClient::UI::VBox);
730    
731     $vbox->add (
732     my $HOST = new CFClient::UI::Entry
733     expand => 1,
734     text => $CFG->{host},
735     tooltip => "The hostname or ip address of the Crossfire(+) server to connect to",
736 root 1.18 on_changed => sub {
737 root 1.1 my ($self, $value) = @_;
738     $CFG->{host} = $value;
739     }
740     );
741    
742     $METASERVER = metaserver_dialog;
743    
744     $vbox->add (new CFClient::UI::Flopper
745     expand => 1,
746     text => "Server List",
747     other => $METASERVER,
748     tooltip => "Show a list of available crossfire servers",
749 root 1.18 on_open => sub {
750 root 1.1 update_metaserver $HOST;
751     }
752     );
753     }
754    
755     $table->add (0, 4, new CFClient::UI::Label valign => 0, align => 1, text => "Username");
756     $table->add (1, 4, new CFClient::UI::Entry
757     text => $CFG->{user},
758     tooltip => "The name of your character on the server",
759 root 1.18 on_changed => sub {
760 root 1.1 my ($self, $value) = @_;
761     $CFG->{user} = $value;
762     }
763     );
764    
765     $table->add (0, 5, new CFClient::UI::Label valign => 0, align => 1, text => "Password");
766     $table->add (1, 5, new CFClient::UI::Entry
767     text => $CFG->{password},
768     hidden => 1,
769     tooltip => "The password for your character",
770 root 1.18 on_changed => sub {
771 root 1.1 my ($self, $value) = @_;
772     $CFG->{password} = $value;
773     }
774     );
775    
776     $table->add (0, 7, new CFClient::UI::Label valign => 0, align => 1, text => "Map Size");
777     $table->add (1, 7, new CFClient::UI::Slider
778     req_w => 100,
779     range => [$CFG->{mapsize}, 10, 100, 0, 1],
780     tooltip => "This is the size of the portion of the map update the server sends you. "
781     . "If you set this to a high value you will be able to see further, "
782     . "but you also increase bandwidth requirements and latency. "
783     . "This option is only used once at log-in.",
784 root 1.18 on_changed => sub {
785 root 1.1 my ($self, $value) = @_;
786    
787     $CFG->{mapsize} = $self->{range}[0] = $value = int $value;
788     },
789     );
790    
791     $table->add (0, 8, new CFClient::UI::Label valign => 0, align => 1, text => "Face Prefetch");
792     $table->add (1, 8, new CFClient::UI::CheckBox
793     state => $CFG->{face_prefetch},
794     tooltip => "<b>Background Image Prefetch</b>\n\n"
795     . "If enabled, the client automatically pre-fetches images from the server. "
796     . "This might increase or create lag, but increases the chances "
797     . "of faces being ready for display when you encounter them. "
798     . "It also uses up server bandwidth on every connect, "
799     . "so only set it if you really need to prefetch images. "
800     . "This option can be set and unset any time.",
801 root 1.18 on_changed => sub { $CFG->{face_prefetch} = $_[1] },
802 root 1.1 );
803    
804     $table->add (0, 9, new CFClient::UI::Label valign => 0, align => 1, text => "Output-Count");
805     $table->add (1, 9, new CFClient::UI::Entry
806     text => $CFG->{output_count},
807     tooltip => "Should be set to 1 unless you know what you are doing. This option is only used once at log-in.",
808 root 1.18 on_changed => sub { $CFG->{output_count} = $_[1] },
809 root 1.1 );
810    
811     $table->add (0, 10, new CFClient::UI::Label valign => 0, align => 1, text => "Output-Sync");
812     $table->add (1, 10, new CFClient::UI::Entry
813     text => $CFG->{output_sync},
814     tooltip => "Should be set to 1 unless you know what you are doing. This option is only used once at log-in.",
815 root 1.18 on_changed => sub { $CFG->{output_sync} = $_[1] },
816 root 1.1 );
817    
818     $table->add (1, 11, $LOGIN_BUTTON = new CFClient::UI::Button
819     expand => 1,
820     align => 0,
821     text => "Login",
822 root 1.18 on_activate => sub {
823 root 1.1 $CONN ? stop_game
824     : start_game;
825     },
826     );
827    
828     $dialog
829     }
830    
831     sub message_window {
832     my $window = new CFClient::UI::FancyFrame
833 elmex 1.16 name => "message_window",
834 root 1.1 title => "Messages",
835     border_bg => [1, 1, 1, 1],
836     bg => [0, 0, 0, 0.75],
837 elmex 1.19 req_x => -1,
838 root 1.1 user_w => int $::WIDTH / 3,
839     user_h => int $::HEIGHT / 5,
840     child => (my $vbox = new CFClient::UI::VBox);
841    
842     $vbox->add ($LOGVIEW);
843    
844     $vbox->add (my $input = new CFClient::UI::Entry
845     tooltip => "<b>Chat Box</b>. If you enter a text and press return/enter here, the current <i>communication command</i> "
846     . "from the client setup will be prepended (e.g. <b>shout</b>, <b>chat</b>...). "
847     . "If you prepend a slash (/), you will submit a command instead (similar to IRC). "
848     . "A better way to submit commands (and the occasional chat command) is often the map command completer.",
849 root 1.18 on_focus_in => sub {
850 root 1.1 my ($input, $prev_focus) = @_;
851    
852     delete $input->{refocus_map};
853    
854     if ($prev_focus == $MAPWIDGET && $input->{auto_activated}) {
855     $input->{refocus_map} = 1;
856     }
857     delete $input->{auto_activated};
858     },
859 root 1.18 on_activate => sub {
860 root 1.1 my ($input, $text) = @_;
861     $input->set_text ('');
862    
863     if ($text =~ /^\/(.*)/) {
864     $::CONN->user_send ($1);
865     } else {
866     my $say_cmd = $::CFG->{say_command} || 'say';
867     $::CONN->user_send ("$say_cmd $text");
868     }
869     if ($input->{refocus_map}) {
870     delete $input->{refocus_map};
871     $MAPWIDGET->focus_in
872     }
873     },
874 root 1.18 on_escape => sub {
875 root 1.1 $MAPWIDGET->focus_in
876     },
877     );
878    
879     $CONSOLE = {
880     window => $window,
881     input => $input
882     };
883    
884     $window
885     }
886    
887     sub open_quit_dialog {
888     unless ($QUIT_DIALOG) {
889    
890     $QUIT_DIALOG = new CFClient::UI::FancyFrame title => "Really Quit?";
891    
892     $QUIT_DIALOG->add (my $vb = new CFClient::UI::VBox expand => 1);
893    
894     $vb->add (new CFClient::UI::Label
895     text => "You should find a savebed and apply it first!",
896     max_w => $WIDTH * 0.25,
897     ellipsize => 0,
898     );
899     $vb->add (my $hb = new CFClient::UI::HBox expand => 1);
900     $hb->add (new CFClient::UI::Button
901     text => "Ok",
902     expand => 1,
903 root 1.18 on_activate => sub { $QUIT_DIALOG->hide },
904 root 1.1 );
905     $hb->add (new CFClient::UI::Button
906     text => "Quit anyway",
907     expand => 1,
908 root 1.18 on_activate => sub { exit },
909 root 1.1 );
910    
911     $QUIT_DIALOG->show_centered;
912     } else {
913     $QUIT_DIALOG->show_centered;
914     }
915     }
916    
917     sub make_inventory_window {
918     my $invwin = new CFClient::UI::FancyFrame
919 elmex 1.19 user_w => $WIDTH * (7/8),
920     user_h => $HEIGHT * (7/8),
921     title => "Inventory",
922     name => "inventory_window",
923     on_visibility_change => sub {
924     $_[0]->show_centered if $_[1]
925     };
926 root 1.1
927     $invwin->add (my $hb = new CFClient::UI::HBox expand => 1);
928    
929     $hb->add (my $vb1 = new CFClient::UI::VBox expand => 1);
930 root 1.13 $vb1->add (my $lbl = new CFClient::UI::Label align => 0);
931 root 1.1 $lbl->set_text ("Player");
932     $vb1->add ($INV = new CFClient::UI::Inventory expand => 1);
933    
934     $hb->add (my $vb2 = new CFClient::UI::VBox expand => 1);
935 elmex 1.17
936 elmex 1.14 $vb2->add (my $hb2 = new CFClient::UI::HBox);
937 elmex 1.17 $hb2->add ($INVR_LBL = new CFClient::UI::Label align => 0, expand => 1);
938 elmex 1.14 $hb2->add (new CFClient::UI::Button
939     text => "Close",
940     tooltip => "Close the currently open container (if one is open)",
941 root 1.18 on_activate => sub {
942 elmex 1.14 $CONN->send ("apply $CONN->{open_container}")
943     if $CONN->{open_container} != 0;
944     },
945     );
946    
947 root 1.1 $INVR_LBL->set_text ("Floor");
948     $vb2->add ($INVR = new CFClient::UI::Inventory expand => 1);
949    
950     $invwin
951     }
952    
953     sub make_help_window {
954     my $win = new CFClient::UI::FancyFrame
955     user_w => $WIDTH * (7/8), user_h => $HEIGHT * (7/8), title => "Documentation";
956    
957     $win->add (my $vbox = new CFClient::UI::VBox);
958    
959     $vbox->add (my $buttons = new CFClient::UI::HBox);
960     $vbox->add (my $viewer = new CFClient::UI::TextView expand => 1, fontsize => 0.8);
961    
962     for (
963     [intro => "Introduction"],
964     [manual => "Manual"],
965     [command_help => "Commands"],
966     [skill_help => "Skills"],
967     ) {
968     my ($pod, $label) = @$_;
969    
970     $buttons->add (new CFClient::UI::Button
971     text => $label,
972 root 1.18 on_activate => sub {
973 root 1.1 my $parser = new Pod::POM;
974     my $pom = $parser->parse_file (CFClient::find_rcfile "pod/$pod.pod");
975    
976     $viewer->clear;
977    
978     $viewer->add_paragraph ([1, 1, 1, 1], $_->[1], $_->[0])
979     for @{ CFClient::pod_to_pango_list $pom };
980    
981     $viewer->set_offset (0);
982     },
983     );
984     }
985    
986     $viewer->add_paragraph ([1, 1, 0, 1], "<big>Use one of the buttons above to display a document.</big>");
987    
988     $win
989     }
990    
991     sub sdl_init {
992     CFClient::SDL_Init
993     and die "SDL::Init failed!\n";
994     }
995    
996     sub video_init {
997     sdl_init;
998    
999     $CFG->{sdl_mode} = 0 if $CFG->{sdl_mode} >= @SDL_MODES;
1000    
1001     my ($old_w, $old_h) = ($WIDTH, $HEIGHT);
1002    
1003     ($WIDTH, $HEIGHT) = @{ $SDL_MODES[$CFG->{sdl_mode}] };
1004     $FULLSCREEN = $CFG->{fullscreen};
1005     $FAST = $CFG->{fast};
1006    
1007     CFClient::SDL_SetVideoMode $WIDTH, $HEIGHT, $FULLSCREEN
1008     or die "SDL_SetVideoMode failed: " . (CFClient::SDL_GetError) . "\n";
1009    
1010     $SDL_ACTIVE = 1;
1011     $LAST_REFRESH = time - 0.01;
1012    
1013 root 1.10 CFClient::OpenGL::init;
1014 root 1.1
1015     $FONTSIZE = int $HEIGHT / 40 * $CFG->{gui_fontsize};
1016    
1017     $CFClient::UI::ROOT->configure (0, 0, $WIDTH, $HEIGHT);#d#
1018    
1019     #############################################################################
1020    
1021     if ($DEBUG_STATUS) {
1022     CFClient::UI::rescale_widgets $WIDTH / $old_w, $HEIGHT / $old_h;
1023     } else {
1024     # create the widgets
1025    
1026     $DEBUG_STATUS = new CFClient::UI::Label padding => 0, z => 100, req_x => -1;
1027     $DEBUG_STATUS->show;
1028    
1029     $STATUSBOX = new CFClient::UI::Statusbox;
1030     $STATUSBOX->add ("Use <b>Alt-Enter</b> to toggle fullscreen mode", pri => -100, color => [1, 1, 1, 0.8]);
1031    
1032     (new CFClient::UI::Frame
1033     bg => [0, 0, 0, 0.4],
1034     req_y => -1,
1035     child => $STATUSBOX,
1036     )->show;
1037    
1038     CFClient::UI::FancyFrame->new (
1039     border_bg => [1, 1, 1, 192/255],
1040     bg => [1, 1, 1, 0],
1041     child => ($MAPMAP = new CFClient::MapWidget::MapMap
1042     tooltip => "<b>Map</b>. On servers that support this feature, this will display an overview of the surrounding areas.",
1043     ),
1044     )->show;
1045    
1046     $MAPWIDGET = new CFClient::MapWidget;
1047     $MAPWIDGET->connect (activate_console => sub {
1048     my ($mapwidget, $preset) = @_;
1049    
1050     if ($CONSOLE) {
1051     $CONSOLE->{input}->{auto_activated} = 1;
1052     $CONSOLE->{input}->focus_in;
1053    
1054     if ($preset && $CONSOLE->{input}->get_text eq '') {
1055     $CONSOLE->{input}->set_text ($preset);
1056     }
1057     }
1058     });
1059     $MAPWIDGET->show;
1060     $MAPWIDGET->focus_in;
1061    
1062     $LOGVIEW = new CFClient::UI::TextView
1063     expand => 1,
1064     font => $FONT_FIXED,
1065     fontsize => $::CFG->{log_fontsize},
1066     can_hover => 1,
1067     can_events => 1,
1068     tooltip => "<b>Server Log</b>. This text viewer contains all the messages sent by the server.",
1069     ;
1070    
1071     $BUTTONBAR = new CFClient::UI::HBox;
1072    
1073     $BUTTONBAR->add (new CFClient::UI::Flopper text => "Client Setup", other => client_setup,
1074     tooltip => "Toggles a dialog where you can configure various aspects of the client, such as graphics mode, performance, and audio options.");
1075     $BUTTONBAR->add (new CFClient::UI::Flopper text => "Server Setup", other => server_setup,
1076     tooltip => "Toggles a dialog where you can configure the server to play on, your username, password and other server-related options.");
1077     $BUTTONBAR->add (new CFClient::UI::Flopper text => "Message Window", other => message_window,
1078     tooltip => "Toggles the server message log, where the client collects <i>all</i> messages from the server.");
1079    
1080     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
1081    
1082     $BUTTONBAR->add (new CFClient::UI::Flopper text => "Stats Window", other => make_stats_window,
1083     tooltip => "Toggles the statistics window, where all your Stats and Resistances are being displayed at all times.");
1084     $BUTTONBAR->add (new CFClient::UI::Flopper text => "Inventory", other => make_inventory_window,
1085     tooltip => "Toggles the inventory window, where you can manage your loot (or treaures :).");
1086    
1087     $BUTTONBAR->add (new CFClient::UI::Button
1088     text => "Save Config",
1089     tooltip => "Saves the options chosen in the client setting, server settings and the window layout to be restored on later runs.",
1090 root 1.18 on_activate => sub {
1091 elmex 1.16 $::CFG->{layout} = CFClient::UI::get_layout;
1092 root 1.1 CFClient::write_cfg "$Crossfire::VARDIR/pclientrc";
1093     status "Configuration Saved";
1094     },
1095     );
1096    
1097     $BUTTONBAR->add (new CFClient::UI::Flopper text => "Help!", other => make_help_window,
1098     tooltip => "View Documentation");
1099    
1100     $BUTTONBAR->add (new CFClient::UI::Button
1101 root 1.18 text => "Quit",
1102     tooltip => "Terminates the program",
1103     on_activate => sub {
1104 root 1.1 if ($CONN) {
1105     open_quit_dialog;
1106     } else {
1107     exit;
1108     }
1109     },
1110     );
1111    
1112     $BUTTONBAR->show;
1113    
1114     $STATUSBOX->add ("Set video mode $WIDTH×$HEIGHT", timeout => 10, fg => [1, 1, 1, 0.5]);
1115    
1116     # delay till geometry is constant
1117     $CFClient::UI::ROOT->on_post_alloc (startup => sub {
1118     $BUTTONBAR->{children}[1]->emit ("activate"); # pop up server setup
1119     my $widget = $GAUGES->{win};
1120     $widget->move (0, $HEIGHT - $widget->{h});#d# to in toplevel
1121     });
1122     force_refresh ();
1123     }
1124     }
1125    
1126     sub video_shutdown {
1127     undef $SDL_ACTIVE;
1128     }
1129    
1130     my @bgmusic = qw(game1.ogg game2.ogg game3.ogg game5.ogg game6.ogg ross1.ogg ross2.ogg ross3.ogg ross4.ogg ross5.ogg); #d#
1131     my $bgmusic;#TODO#hack#d#
1132    
1133     sub audio_channel_finished {
1134     my ($channel) = @_;
1135    
1136     #warn "channel $channel finished\n";#d#
1137     }
1138    
1139     sub audio_music_finished {
1140     return unless $CFG->{bgm_enable};
1141    
1142     # TODO: hack, do play loop and mood music
1143     $bgmusic = new_from_file CFClient::MixMusic CFClient::find_rcfile "music/$bgmusic[0]";
1144     $bgmusic->play (0);
1145    
1146     push @bgmusic, shift @bgmusic;
1147     }
1148    
1149     sub audio_init {
1150     if ($CFG->{audio_enable}) {
1151     if (open my $fh, "<", CFClient::find_rcfile "sounds/config") {
1152     $SDL_MIXER = !CFClient::Mix_OpenAudio;
1153    
1154     unless ($SDL_MIXER) {
1155     status "Unable to open sound device: there will be no sound";
1156     return;
1157     }
1158    
1159     CFClient::Mix_AllocateChannels 8;
1160     CFClient::MixMusic::volume $CFG->{bgm_volume} * 128;
1161    
1162     audio_music_finished;
1163    
1164     while (<$fh>) {
1165     next if /^\s*#/;
1166     next if /^\s*$/;
1167    
1168     my ($file, $volume, $event) = split /\s+/, $_, 3;
1169    
1170     push @SOUNDS, "$volume,$file";
1171    
1172     $AUDIO_CHUNKS{"$volume,$file"} ||= do {
1173     my $chunk = new_from_file CFClient::MixChunk CFClient::find_rcfile "sounds/$file";
1174     $chunk->volume ($volume * 128 / 100);
1175     $chunk
1176     };
1177     }
1178     } else {
1179     status "unable to open sound config: $!";
1180     }
1181     }
1182     }
1183    
1184     sub audio_shutdown {
1185     CFClient::Mix_CloseAudio if $SDL_MIXER;
1186     undef $SDL_MIXER;
1187     @SOUNDS = ();
1188     %AUDIO_CHUNKS = ();
1189     }
1190    
1191     my %animate_object;
1192     my $animate_timer;
1193    
1194     my $fps = 9;
1195    
1196     my %demo;#d#
1197    
1198     sub force_refresh {
1199     $fps = $fps * 0.95 + 1 / (($NOW - $LAST_REFRESH) || 0.1) * 0.05;
1200     debug sprintf "%3.2f", $fps;
1201    
1202     $CFClient::UI::ROOT->draw;
1203    
1204     $WANT_REFRESH = 0;
1205     $CAN_REFRESH = 0;
1206     $LAST_REFRESH = $NOW;
1207    
1208     0 && do {
1209     # some weird model-drawing code, just a joke right now
1210     use CFClient::OpenGL;
1211    
1212     $demo{t}{eye_auv} ||= new_from_file CFClient::Texture "eye2.png" or die;
1213     $demo{t}{body_auv} ||= new_from_file CFClient::Texture "body_auv3.png" or die;
1214     $demo{r} ||= do {
1215     my $mod = Compress::LZF::sthaw do { local $/; open my $fh, "<:raw:perlio", "dread.lz3"; <$fh> };
1216     $mod->{v} = pack "f*", @{$mod->{v}};
1217     $_ = [scalar @$_, pack "S!*", @$_]
1218     for values %{$mod->{g}};
1219     $mod
1220     };
1221    
1222     my $r = $demo{r} or die;
1223    
1224     glDepthMask 1;
1225     glClear GL_DEPTH_BUFFER_BIT;
1226     glEnable GL_TEXTURE_2D;
1227     glEnable GL_DEPTH_TEST;
1228     glEnable GL_CULL_FACE;
1229     glShadeModel $::FAST ? GL_FLAT : GL_SMOOTH;
1230    
1231     glMatrixMode GL_PROJECTION;
1232     glLoadIdentity;
1233     glFrustum -1 * ($::WIDTH / $::HEIGHT), 1 * ($::WIDTH / $::HEIGHT), 1, -1, 1, 10000;
1234     #glOrtho 0, $::WIDTH, 0, $::HEIGHT, -10000, 10000;
1235     glMatrixMode GL_MODELVIEW;
1236     glLoadIdentity;
1237    
1238     glPushMatrix;
1239     glTranslate 0, 0, -800;
1240     glScale 1, -1, 1;
1241     glRotate $NOW * 1000 % 36000 / 5, 0, 1, 0;
1242     glRotate $NOW * 1000 % 36000 / 6, 1, 0, 0;
1243     glRotate $NOW * 1000 % 36000 / 7, 0, 0, 1;
1244     glScale 50, 50, 50;
1245    
1246     glInterleavedArrays GL_T2F_N3F_V3F, 0, $r->{v};
1247     while (my ($k, $v) = each %{$r->{g}}) {
1248     glBindTexture GL_TEXTURE_2D, ($demo{t}{$k}{name} or die);
1249     glDrawElements GL_TRIANGLES, $v->[0], GL_UNSIGNED_SHORT, $v->[1];
1250     }
1251    
1252     glPopMatrix;
1253    
1254     glShadeModel GL_FLAT;
1255     glDisable GL_DEPTH_TEST;
1256     glDisable GL_TEXTURE_2D;
1257     glDepthMask 0;
1258    
1259     $WANT_REFRESH++;
1260     };
1261    
1262     CFClient::SDL_GL_SwapBuffers;
1263     }
1264    
1265     my $refresh_watcher = Event->timer (after => 0, hard => 1, interval => 1 / $MAX_FPS, cb => sub {
1266     $NOW = time;
1267    
1268     ($SDL_CB{$_->{type}} || sub { warn "unhandled event $_->{type}" })->($_)
1269     for CFClient::SDL_PollEvent;
1270    
1271     if (%animate_object) {
1272     $_->animate ($LAST_REFRESH - $NOW) for values %animate_object;
1273     $WANT_REFRESH++;
1274     }
1275    
1276     if ($WANT_REFRESH) {
1277     force_refresh;
1278     } else {
1279     $CAN_REFRESH = 1;
1280     }
1281     });
1282    
1283     sub animation_start {
1284     my ($widget) = @_;
1285     $animate_object{$widget} = $widget;
1286     }
1287    
1288     sub animation_stop {
1289     my ($widget) = @_;
1290     delete $animate_object{$widget};
1291     }
1292    
1293     # check once/second for faces that need to be prefetched
1294     # this should, of course, only run on demand, but
1295     # SDL forces worse things on us....
1296    
1297     Event->timer (after => 1, interval => 0.25, cb => sub {
1298     $CONN->face_prefetch
1299     if $CONN;
1300     });
1301    
1302     %SDL_CB = (
1303     CFClient::SDL_QUIT => sub {
1304     Event::unloop -1;
1305     },
1306     CFClient::SDL_VIDEORESIZE => sub {
1307     },
1308     CFClient::SDL_VIDEOEXPOSE => sub {
1309     CFClient::UI::full_refresh;
1310     },
1311     CFClient::SDL_ACTIVEEVENT => sub {
1312     # printf "active %x %x\n", $SDL_EV->active_gain, $SDL_EV->active_state;#d#
1313     },
1314     CFClient::SDL_KEYDOWN => sub {
1315     if ($_[0]{mod} & CFClient::KMOD_ALT && $_[0]{sym} == 13) {
1316     # alt-enter
1317     video_shutdown;
1318     $CFG->{fullscreen} = !$CFG->{fullscreen};
1319     video_init;
1320     } else {
1321     CFClient::UI::feed_sdl_key_down_event ($_[0]);
1322     }
1323     },
1324     CFClient::SDL_KEYUP => \&CFClient::UI::feed_sdl_key_up_event,
1325     CFClient::SDL_MOUSEMOTION => \&CFClient::UI::feed_sdl_motion_event,
1326     CFClient::SDL_MOUSEBUTTONDOWN => \&CFClient::UI::feed_sdl_button_down_event,
1327     CFClient::SDL_MOUSEBUTTONUP => \&CFClient::UI::feed_sdl_button_up_event,
1328     CFClient::SDL_USEREVENT => sub {
1329     if ($_[0]{code} == 1) {
1330     audio_channel_finished $_[0]{data1};
1331     } elsif ($_[0]{code} == 0) {
1332     audio_music_finished;
1333     }
1334     },
1335     );
1336    
1337     #############################################################################
1338    
1339     $SIG{INT} = $SIG{TERM} = sub { exit };
1340    
1341     {
1342     local $SIG{__DIE__} = sub { CFClient::fatal $_[0] };
1343    
1344     CFClient::read_cfg "$Crossfire::VARDIR/pclientrc";
1345 elmex 1.16 CFClient::UI::set_layout ($::CFG->{layout});
1346 root 1.1
1347     my %DEF_CFG = (
1348     sdl_mode => 0,
1349     width => 640,
1350     height => 480,
1351     fullscreen => 0,
1352     fast => 0,
1353     map_scale => 1,
1354     fow_enable => 1,
1355     fow_intensity => 0.45,
1356     fow_smooth => 0,
1357     gui_fontsize => 1,
1358     log_fontsize => 1,
1359     gauge_fontsize=> 1,
1360     gauge_size => 0.35,
1361     stat_fontsize => 1,
1362     mapsize => 100,
1363     host => "crossfire.schmorp.de",
1364     say_command => 'say',
1365     audio_enable => 1,
1366     bgm_enable => 1,
1367     bgm_volume => 0.25,
1368     face_prefetch => 0,
1369     output_sync => 1,
1370     output_count => 1,
1371     );
1372    
1373     while (my ($k, $v) = each %DEF_CFG) {
1374     $CFG->{$k} = $v unless exists $CFG->{$k};
1375     }
1376    
1377     sdl_init;
1378    
1379     @SDL_MODES = reverse
1380     grep $_->[0] >= 640 && $_->[1] >= 480,
1381     CFClient::SDL_ListModes;
1382    
1383     @SDL_MODES or CFClient::fatal "Unable to find a usable video mode\n(hardware accelerated opengl fullscreen)";
1384    
1385     $CFG->{sdl_mode} = 0 if $CFG->{sdl_mode} > @SDL_MODES;
1386    
1387     {
1388     my @fonts = map CFClient::find_rcfile "fonts/$_", qw(
1389     DejaVuSans.ttf
1390     DejaVuSansMono.ttf
1391     DejaVuSans-Bold.ttf
1392     DejaVuSansMono-Bold.ttf
1393     DejaVuSans-Oblique.ttf
1394     DejaVuSansMono-Oblique.ttf
1395     DejaVuSans-BoldOblique.ttf
1396     DejaVuSansMono-BoldOblique.ttf
1397     );
1398    
1399     CFClient::add_font $_ for @fonts;
1400    
1401     CFClient::pango_init;
1402    
1403     $FONT_PROP = new_from_file CFClient::Font $fonts[0];
1404     $FONT_FIXED = new_from_file CFClient::Font $fonts[1];
1405    
1406     $FONT_PROP->make_default;
1407     }
1408    
1409     # compare mono (ft) vs. rgba (cairo)
1410     # ft - 1.8s, cairo 3s, even in alpha-only mode
1411     # for my $rgba (0..1) {
1412     # my $t1 = Time::HiRes::time;
1413     # for (1..1000) {
1414     # my $layout = CFClient::Layout->new ($rgba);
1415     # $layout->set_text ("hallo" x 100);
1416     # $layout->render;
1417     # }
1418     # my $t2 = Time::HiRes::time;
1419     # warn $t2-$t1;
1420     # }
1421    
1422     video_init;
1423     audio_init;
1424     }
1425    
1426     Event::loop;
1427    
1428     END { CFClient::SDL_Quit }
1429    
1430     =head1 NAME
1431    
1432     pclient - A Crossfire+ and Crossfire game client
1433    
1434     =head1 SYNOPSIS
1435    
1436     Just run it - no commandline arguments are supported.
1437    
1438     =head1 USAGE
1439    
1440     Pclient utilises OpenGL for all UI elements and the game. It is supposed to be used
1441     fullscreen and interactively.
1442    
1443     =head1 AUTHOR
1444    
1445     Marc Lehmann <crossfire@schmorp.de>, Robin Redeker <elmex@ta-sa.org>
1446    
1447    
1448