ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.23
Committed: Sun May 28 02:31:04 2006 UTC (18 years ago) by root
Branch: MAIN
Changes since 1.22: +14 -12 lines
Log Message:
*** empty log message ***

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 root 1.23 our $SERVER_SETUP;
87 root 1.1
88     our $FLOORBOX;
89     our $GAUGES;
90     our $STATWIDS;
91    
92     our $SDL_ACTIVE;
93     our %SDL_CB;
94    
95     our $SDL_MIXER;
96     our @SOUNDS; # event => file mapping
97     our %AUDIO_CHUNKS; # audio files
98    
99     our $ALT_ENTER_MESSAGE;
100     our $STATUSBOX;
101     our $DEBUG_STATUS;
102    
103 root 1.23 our $INV_WINDOW;
104 root 1.1 our $INV;
105     our $INVR;
106     our $INVR_LBL;
107    
108     sub status {
109     $STATUSBOX->add (CFClient::UI::Label::escape $_[0], pri => -10, group => "status", timeout => 10, fg => [1, 1, 0, 1]);
110     }
111    
112     sub debug {
113     $DEBUG_STATUS->set_text ($_[0]);
114     my ($w, $h) = $DEBUG_STATUS->size_request;
115     $DEBUG_STATUS->move ($WIDTH - $w, 0);
116     }
117    
118     sub start_game {
119     status "logging in...";
120    
121 root 1.23 $LOGIN_BUTTON->set_text ("Logout");
122     $SERVER_SETUP->hide;
123    
124 root 1.1 my $mapsize = List::Util::min 32, List::Util::max 11, int $WIDTH * $CFG->{mapsize} * 0.01 / 32;
125    
126 root 1.11 my ($host, $port) = split /:/, $CFG->{host};
127    
128 root 1.1 $MAP = new CFClient::Map $mapsize, $mapsize;
129    
130     $CONN = eval {
131 root 1.11 new CFClient::Protocol
132 root 1.1 host => $host,
133     port => $port || 13327,
134     user => $CFG->{user},
135     pass => $CFG->{password},
136     mapw => $mapsize,
137     maph => $mapsize,
138 root 1.11
139     map_widget => $MAPWIDGET,
140     logview => $LOGVIEW,
141     statusbox => $STATUSBOX,
142     map => $MAP,
143     mapmap => $MAPMAP,
144    
145     sound_play => sub {
146     my ($x, $y, $soundnum, $type) = @_;
147    
148     $SDL_MIXER
149     or return;
150    
151     my $chunk = $AUDIO_CHUNKS{$SOUNDS[$soundnum]}
152     or return;
153    
154     $chunk->play;
155     },
156 root 1.1 };
157    
158     if ($CONN) {
159     CFClient::lowdelay fileno $CONN->{fh};
160    
161     status "login successful";
162     } else {
163     status "unable to connect";
164     stop_game();
165     }
166     }
167    
168     sub stop_game {
169 root 1.23 $LOGIN_BUTTON->set_text ("Login");
170     $SERVER_SETUP->show;
171     $INV_WINDOW->hide;
172     $LOGVIEW->hide;
173    
174 root 1.1 return unless $CONN;
175    
176     status "connection closed";
177 root 1.23
178 root 1.1 $CONN->destroy;
179     $CONN = 0; # false, does not autovivify
180     }
181    
182     sub client_setup {
183     my $dialog = new CFClient::UI::FancyFrame
184 elmex 1.19 req_x => 1,
185     req_y => $HEIGHT * (1/8),
186 elmex 1.16 name => "client_setup",
187 root 1.1 title => "Client Setup",
188     child => (my $vbox = new CFClient::UI::VBox);
189     $vbox->add (my $table = new CFClient::UI::Table expand => 1, col_expand => [0, 1]);
190    
191     $table->add (0, 0, new CFClient::UI::Label valign => 0, align => 1, text => "Video Mode");
192     $table->add (1, 0, my $hbox = new CFClient::UI::HBox);
193    
194     $hbox->add (my $mode_slider = new CFClient::UI::Slider expand => 1, req_w => 100, range => [$CFG->{sdl_mode}, 0, $#SDL_MODES, 0, 1]);
195     $hbox->add (my $mode_label = new CFClient::UI::Label align => 0, valign => 0, height => 0.8, template => "9999x9999");
196    
197     $mode_slider->connect (changed => sub {
198     my ($self, $value) = @_;
199    
200     $CFG->{sdl_mode} = $self->{range}[0] = $value = int $value;
201     $mode_label->set_text (sprintf "%dx%d", @{$SDL_MODES[$value]});
202     });
203     $mode_slider->emit (changed => $mode_slider->{range}[0]);
204    
205     my $row = 1;
206    
207     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Fullscreen");
208     $table->add (1, $row++, new CFClient::UI::CheckBox
209     state => $CFG->{fullscreen},
210     tooltip => "Bring the client into fullscreen mode.",
211 root 1.18 on_changed => sub {
212 root 1.1 my ($self, $value) = @_;
213     $CFG->{fullscreen} = $value;
214     }
215     );
216    
217     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Fast & Ugly");
218     $table->add (1, $row++, new CFClient::UI::CheckBox
219     state => $CFG->{fast},
220     tooltip => "Lower the visual quality considerably to speed up rendering.",
221 root 1.18 on_changed => sub {
222 root 1.1 my ($self, $value) = @_;
223     $CFG->{fast} = $value;
224     }
225     );
226    
227     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Map Scale");
228     $table->add (1, $row++, new CFClient::UI::Slider
229     range => [(log $CFG->{map_scale}) / (log 2), -3, 1, 0, 1],
230     tooltip => "Enlarge or shrink the displayed map. Changes are instant.",
231 root 1.18 on_changed => sub {
232 root 1.1 my ($self, $value) = @_;
233     $CFG->{map_scale} = 2 ** $value;
234     }
235     );
236    
237     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Fog of War");
238     $table->add (1, $row++, new CFClient::UI::CheckBox
239     state => $CFG->{fow_enable},
240     tooltip => "<b>Fog-of-War</b> marks areas that cannot be seen by the player. Changes are instant.",
241 root 1.18 on_changed => sub {
242 root 1.1 my ($self, $value) = @_;
243     $CFG->{fow_enable} = $value;
244     }
245     );
246    
247     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "FoW Intensity");
248     $table->add (1, $row++, new CFClient::UI::Slider
249     range => [$CFG->{fow_intensity}, 0, 1, 0, 1 / 256],
250     tooltip => "<b>Fog of War Lightness.</b> The higher the intensity, the lighter the Fog-of-War color. Changes are instant.",
251 root 1.18 on_changed => sub {
252 root 1.1 my ($self, $value) = @_;
253     $CFG->{fow_intensity} = $value;
254     }
255     );
256    
257     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "FoW Smooth");
258     $table->add (1, $row++, new CFClient::UI::CheckBox
259     state => $CFG->{fow_smooth},
260     tooltip => "Smooth the Fog-of-War a bit to make it more realistic. Changes are instant.",
261 root 1.18 on_changed => sub {
262 root 1.1 my ($self, $value) = @_;
263     $CFG->{fow_smooth} = $value;
264 root 1.15 status "Fog of War smoothing requires OpenGL 1.2 or higher" if $CFClient::OpenGL::GL_VERSION < 1.2;
265 root 1.1 }
266     );
267    
268     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "GUI Fontsize");
269     $table->add (1, $row++, new CFClient::UI::Slider
270     range => [$CFG->{gui_fontsize}, 0.5, 2, 0, 0.1],
271     tooltip => "The base font size used by most GUI elements that do not have their own setting.",
272 root 1.18 on_changed => sub { $CFG->{gui_fontsize} = $_[1] },
273 root 1.1 );
274    
275     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Message Fontsize");
276     $table->add (1, $row++, new CFClient::UI::Slider
277     range => [$CFG->{log_fontsize}, 0.5, 2, 0, 0.1],
278     tooltip => "The font size used by the <b>message/server log</b> window only. Changes are instant.",
279 root 1.18 on_changed => sub { $LOGVIEW->set_fontsize ($CFG->{log_fontsize} = $_[1]) },
280 root 1.1 );
281    
282     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Stats Fontsize");
283    
284     $table->add (1, $row++, new CFClient::UI::Slider
285     range => [$CFG->{stat_fontsize}, 0.5, 2, 0, 0.1],
286     tooltip => "The font size used by the <b>statistics window</b> only. Changes are instant.",
287 root 1.18 on_changed => sub {
288 root 1.1 $CFG->{stat_fontsize} = $_[1];
289     &set_stats_window_fontsize;
290     }
291     );
292    
293     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Gauge fontsize");
294     $table->add (1, $row++, new CFClient::UI::Slider
295     range => [$CFG->{gauge_fontsize}, 0.5, 2, 0, 0.1],
296     tooltip => "Adjusts the fontsize of the gauges at the bottom right. Changes are instant.",
297 root 1.18 on_changed => sub {
298 root 1.1 $CFG->{gauge_fontsize} = $_[1];
299     &set_gauge_window_fontsize;
300     }
301     );
302    
303     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Gauge size");
304     $table->add (1, $row++, new CFClient::UI::Slider
305 root 1.18 range => [$CFG->{gauge_size}, 0.2, 0.8],
306     tooltip => "Adjust the size of the stats gauges at the bottom right. Changes are instant.",
307     on_changed => sub {
308 root 1.1 $CFG->{gauge_size} = $_[1];
309     $GAUGES->{win}->set_size ($WIDTH, int $HEIGHT * $CFG->{gauge_size});
310     }
311     );
312    
313     $table->add (1, $row++, new CFClient::UI::Button
314     expand => 1, align => 0, text => "Apply",
315     tooltip => "Apply the video settings",
316 root 1.18 on_activate => sub {
317 root 1.1 video_shutdown ();
318     video_init ();
319     }
320     );
321    
322     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Audio Enable");
323     $table->add (1, $row++, new CFClient::UI::CheckBox
324     state => $CFG->{audio_enable},
325     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.",
326 root 1.18 on_changed => sub {
327 root 1.1 $CFG->{audio_enable} = $_[1];
328     }
329     );
330     # $table->add (0, 9, new CFClient::UI::Label valign => 0, align => 1, text => "Effects Volume");
331 root 1.18 # $table->add (1, 8, new CFClient::UI::Slider range => [$CFG->{effects_volume}, 0, 128, 1], on_changed => sub {
332 root 1.1 # $CFG->{effects_volume} = $_[1];
333     # });
334     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Background Music");
335     $table->add (1, $row++, my $hbox = new CFClient::UI::HBox);
336     $hbox->add (new CFClient::UI::CheckBox
337     expand => 1, state => $CFG->{bgm_enable},
338     tooltip => "If enabled, playing of background music is enabled. If disabled, no background music will be played.",
339 root 1.18 on_changed => sub {
340 root 1.1 $CFG->{bgm_enable} = $_[1];
341     }
342     );
343     $hbox->add (new CFClient::UI::Slider
344     expand => 1, range => [$CFG->{bgm_volume}, 0, 1, 0, 1/128],
345     tooltip => "The volume of the background music. Changes are instant.",
346 root 1.18 on_changed => sub {
347 root 1.1 $CFG->{bgm_volume} = $_[1];
348     CFClient::MixMusic::volume $_[1] * 128;
349     }
350     );
351    
352     $table->add (1, $row++, new CFClient::UI::Button
353     expand => 1, align => 0, text => "Apply",
354     tooltip => "Apply the audio settings",
355 root 1.18 on_activate => sub {
356 root 1.1 audio_shutdown ();
357     audio_init ();
358     }
359     );
360    
361     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Chat Command");
362     $table->add (1, $row++, my $saycmd = new CFClient::UI::Entry
363     text => $CFG->{say_command},
364     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. "
365     . "Usually you want to enter something like 'say' or 'shout' or 'gsay' here. "
366     . "But you could also set it to <b>tell <i>playername</i></b> to only chat with that user.",
367 root 1.18 on_changed => sub {
368 root 1.1 my ($self, $value) = @_;
369     $CFG->{say_command} = $value;
370     }
371     );
372    
373     $dialog
374     }
375    
376     sub set_stats_window_fontsize {
377     for (values %{$STATWIDS}) {
378     $_->set_fontsize ($::CFG->{stat_fontsize});
379     }
380     }
381    
382     sub set_gauge_window_fontsize {
383     for (map { $GAUGES->{$_} } grep { $_ ne 'win' } keys %{$GAUGES}) {
384     $_->set_fontsize ($::CFG->{gauge_fontsize});
385     }
386     }
387    
388     sub make_gauge_window {
389     my $gh = int $HEIGHT * $CFG->{gauge_size};
390    
391     my $win = new CFClient::UI::Frame (
392 root 1.20 req_x => 0,
393     req_y => -1,
394     def_w => $WIDTH,
395     def_h => $gh,
396 root 1.1 );
397    
398     $win->add (my $hbox = new CFClient::UI::HBox
399     children => [
400     (new CFClient::UI::HBox expand => 1),
401     (new CFClient::UI::VBox children => [
402     (new CFClient::UI::Empty expand => 1),
403 root 1.2 (new CFClient::UI::Frame bg => [0, 0, 0, 0.4], child => ($FLOORBOX = new CFClient::UI::Table)),
404 root 1.1 ]),
405     (my $vbox = new CFClient::UI::VBox),
406     ],
407     );
408    
409     $vbox->add (new CFClient::UI::HBox
410     expand => 1,
411     children => [
412     (new CFClient::UI::Empty expand => 1),
413     (my $hb = new CFClient::UI::HBox),
414     ],
415     );
416    
417     $hb->add (my $hg = new CFClient::UI::Gauge type => 'hp',
418     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.");
419     $hb->add (my $mg = new CFClient::UI::Gauge type => 'mana',
420     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.");
421     $hb->add (my $gg = new CFClient::UI::Gauge type => 'grace',
422     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.");
423     $hb->add (my $fg = new CFClient::UI::Gauge type => 'food',
424     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.");
425    
426     $vbox->add (my $exp = new CFClient::UI::Label valign => 0, align => 1, can_hover => 1, can_events => 1,
427     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.");
428     $vbox->add (my $rng = new CFClient::UI::Label valign => 0, align => 1, can_hover => 1, can_events => 1,
429     tooltip => "<b>Ranged attack</b> - how you attack when you press shift-cursor (spell, skill, weapon etc.)");
430    
431     $GAUGES = {
432     exp => $exp, win => $win, range => $rng,
433     food => $fg, mana => $mg, hp => $hg, grace => $gg
434     };
435    
436     &set_gauge_window_fontsize;
437    
438     $win
439     }
440    
441     sub make_stats_window {
442 elmex 1.19 my $tgw = new CFClient::UI::FancyFrame
443     req_y => $HEIGHT * (2/8),
444     req_x => -1,
445     title => "Stats",
446     name => "stats_window";
447 root 1.1
448     $tgw->add (new CFClient::UI::Window child => my $vb = new CFClient::UI::VBox);
449     $vb->add ($STATWIDS->{title} = new CFClient::UI::Label valign => 0, align => -1, text => "Title:", expand => 1,
450     can_hover => 1, can_events => 1,
451     tooltip => "Your name and title. You can change your title by using the <b>title</b> command, if supported by the server.");
452     $vb->add ($STATWIDS->{map} = new CFClient::UI::Label valign => 0, align => -1, text => "Map:", expand => 1,
453     can_hover => 1, can_events => 1,
454     tooltip => "The map you are currently on (if supported by the server).");
455    
456 elmex 1.5 $vb->add (my $hb0 = new CFClient::UI::HBox);
457     $hb0->add ($STATWIDS->{weight} = new CFClient::UI::Label valign => 0, align => -1, text => "Weight:", expand => 1,
458     can_hover => 1, can_events => 1,
459 root 1.15 tooltip => "The weight of the player including all inventory items.");
460 elmex 1.5 $hb0->add ($STATWIDS->{m_weight} = new CFClient::UI::Label valign => 0, align => -1, text => "Max weight:", expand => 1,
461     can_hover => 1, can_events => 1,
462 root 1.15 tooltip => "The weight limit: you cannot carry more than this.");
463 elmex 1.5
464    
465 root 1.1 $vb->add (my $hb = new CFClient::UI::HBox expand => 1);
466     $hb->add (my $tbl = new CFClient::UI::Table expand => 1);
467    
468     my $color2 = [1, 1, 0];
469    
470     for (
471     [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"],
472     [0, 1, st_dex => "Dex", 30, "<b>Dexterity</b>, your physical agility. Determines chance of being hit and affects armor class and speed"],
473     [0, 2, st_con => "Con", 30, "<b>Constitution</b>, physical health and toughness. Determines how many healthpoints you can have"],
474     [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"],
475     [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"],
476     [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"],
477     [0, 6, st_cha => "Cha", 30, "<b>Charisma</b>, how well you are received by NPCs. Affects buying and selling prices in shops."],
478    
479     [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."],
480     [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."],
481     [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."],
482     [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."],
483     [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."],
484     [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."],
485     ) {
486     my ($col, $row, $id, $label, $template, $tooltip) = @$_;
487    
488     $tbl->add ($col , $row, $STATWIDS->{$id} = new CFClient::UI::Label
489     font => $FONT_FIXED, can_hover => 1, can_events => 1, valign => 0, align => +1, template => $template, tooltip => $tooltip);
490     $tbl->add ($col + 1, $row, $STATWIDS->{"$id\_lbl"} = new CFClient::UI::Label
491     font => $FONT_FIXED, can_hover => 1, can_events => 1, fg => $color2, valign => 0, align => -1, text => $label, tooltip => $tooltip);
492     }
493    
494     $hb->add (my $tbl2 = new CFClient::UI::Table expand => 1);
495    
496     my $row = 0;
497     my $col = 0;
498    
499     my %resist_names = (
500     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.)",
501     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.)",
502     conf => "<b>Confusion</b> (If you are hit by confusion you will move into random directions, and likely into monsters.)",
503     fire => "<b>Fire</b> (just your resistance to fire spells like burning hands, dragonbreath, meteor swarm fire, ...)",
504     depl => "<b>Depletion</b> (some monsters and other effects can cause stats depletion)",
505     magic => "<b>Magic</b> (resistance to magic spells like magic missile or similar)",
506     drain => "<b>Draining</b> (some monsters (e.g. vampires) and other effects can steal experience)",
507     acid => "<b>Acid</b> (resistance to acid, acid hurts pretty much and also corrodes your weapons)",
508     pois => "<b>Poison</b> (resistance to getting poisoned)",
509     para => "<b>Paralysation</b> (this resistance affects the chance you get paralysed)",
510     deat => "<b>Death</b> (resistance against death spells)",
511     phys => "<b>Physical</b> (this is the resistance against physical attacks, like when a monster hit you in melee combat)",
512     blind => "<b>Blind</b> (blind resistance affects the chance of a successful blinding attack)",
513     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)",
514     tund => "<b>Turn undead</b> (affects your resistancy to various forms of 'turn undead' spells. Only relevant when you are, in fact, undead...",
515     elec => "<b>Electricity</b> (resistance against electricity, spells like large lightning, small lightning, ...)",
516     cold => "<b>Cold</b> (this is your resistance against cold spells like icestorm, snowstorm, ...)",
517     ghit => "<b>Ghost hit</b> (special attack used by ghosts and ghost-like beings)",
518     );
519     for (qw/slow holyw conf fire depl magic
520     drain acid pois para deat phys
521     blind fear tund elec cold ghit/)
522     {
523     $tbl2->add ($col, $row,
524     $STATWIDS->{"res_$_"} =
525     new CFClient::UI::Label
526     font => $FONT_FIXED,
527     template => "-100%",
528     align => +1,
529     valign => 0,
530     can_events => 1,
531     can_hover => 1,
532     tooltip => $resist_names{$_},
533     );
534     $tbl2->add ($col + 1, $row, new CFClient::UI::Image
535     font => $FONT_FIXED,
536     can_hover => 1,
537     can_events => 1,
538     image => "ui/resist/resist_$_.png",
539     tooltip => $resist_names{$_},
540     );
541    
542     $row++;
543     if ($row % 6 == 0) {
544     $col += 2;
545     $row = 0;
546     }
547     }
548    
549     &set_stats_window_fontsize;
550     update_stats_window ({});
551    
552     $tgw
553     }
554    
555     sub formsep {
556     reverse join ",", grep length, split /(...)/, reverse $_[0] * 1
557     }
558    
559     sub update_stats_window {
560     my ($stats) = @_;
561    
562 root 1.12 # I love text protocols...
563    
564     my $hp = $stats->{+CS_STAT_HP} * 1;
565     my $hp_m = $stats->{+CS_STAT_MAXHP} * 1;
566     my $sp = $stats->{+CS_STAT_SP} * 1;
567     my $sp_m = $stats->{+CS_STAT_MAXSP} * 1;
568     my $fo = $stats->{+CS_STAT_FOOD} * 1;
569 root 1.1 my $fo_m = 999;
570 root 1.12 my $gr = $stats->{+CS_STAT_GRACE} * 1;
571     my $gr_m = $stats->{+CS_STAT_MAXGRACE} * 1;
572 root 1.1
573     $GAUGES->{hp} ->set_value ($hp, $hp_m);
574     $GAUGES->{mana} ->set_value ($sp, $sp_m);
575     $GAUGES->{food} ->set_value ($fo, $fo_m);
576     $GAUGES->{grace} ->set_value ($gr, $gr_m);
577 root 1.12 $GAUGES->{exp} ->set_text ("Exp: " . (formsep $stats->{+CS_STAT_EXP64})
578     . " (lvl " . ($stats->{+CS_STAT_LEVEL} * 1) . ")");
579     my $rng = $stats->{+CS_STAT_RANGE};
580 root 1.1 $rng =~ s/^Range: //; # thank you so much dear server
581     $GAUGES->{range} ->set_text ("Rng: " . $rng);
582 root 1.12 my $title = $stats->{+CS_STAT_TITLE};
583 root 1.1 $title =~ s/^Player: //;
584     $STATWIDS->{title} ->set_text ("Title: " . $title);
585    
586 root 1.12 $STATWIDS->{st_str} ->set_text (sprintf "%d" , $stats->{+CS_STAT_STR});
587     $STATWIDS->{st_dex} ->set_text (sprintf "%d" , $stats->{+CS_STAT_DEX});
588     $STATWIDS->{st_con} ->set_text (sprintf "%d" , $stats->{+CS_STAT_CON});
589     $STATWIDS->{st_int} ->set_text (sprintf "%d" , $stats->{+CS_STAT_INT});
590     $STATWIDS->{st_wis} ->set_text (sprintf "%d" , $stats->{+CS_STAT_WIS});
591     $STATWIDS->{st_pow} ->set_text (sprintf "%d" , $stats->{+CS_STAT_POW});
592     $STATWIDS->{st_cha} ->set_text (sprintf "%d" , $stats->{+CS_STAT_CHA});
593     $STATWIDS->{st_wc} ->set_text (sprintf "%d" , $stats->{+CS_STAT_WC});
594     $STATWIDS->{st_ac} ->set_text (sprintf "%d" , $stats->{+CS_STAT_AC});
595     $STATWIDS->{st_dam} ->set_text (sprintf "%d" , $stats->{+CS_STAT_DAM});
596     $STATWIDS->{st_arm} ->set_text (sprintf "%d" , $stats->{+CS_STAT_ARMOUR});
597     $STATWIDS->{st_spd} ->set_text (sprintf "%.1f", $stats->{+CS_STAT_SPEED});
598     $STATWIDS->{st_wspd}->set_text (sprintf "%.1f", $stats->{+CS_STAT_WEAP_SP});
599 root 1.1
600 root 1.12 $STATWIDS->{m_weight}->set_text (sprintf "Max weight: %.1fkg", $stats->{+CS_STAT_WEIGHT_LIM} / 1000);
601 elmex 1.5
602 root 1.12 # TODO: replace by CS_STAT_RES_xxx constants
603 root 1.1 my %tbl = (
604     phys => 100,
605     magic => 101,
606     fire => 102,
607     elec => 103,
608     cold => 104,
609     conf => 105,
610     acid => 106,
611     drain => 107,
612     ghit => 108,
613     pois => 109,
614     slow => 110,
615     para => 111,
616     tund => 112,
617     fear => 113,
618     depl => 113,
619     deat => 115,
620     holyw => 116,
621 root 1.12 blind => 117,
622 root 1.1 );
623    
624 root 1.12 $STATWIDS->{"res_$_"}->set_text (sprintf "%d%", $stats->{$tbl{$_}})
625     for keys %tbl;
626 root 1.1 }
627    
628     sub metaserver_dialog {
629     my $dialog = new CFClient::UI::FancyFrame
630     title => "Server List",
631     child => (my $vbox = new CFClient::UI::VBox);
632    
633     $vbox->add ($dialog->{table} = new CFClient::UI::Table);
634    
635     $dialog
636     }
637    
638     my $METASERVER_ATIME;
639    
640     sub update_metaserver {
641     my ($HOST) = @_;
642    
643     return if $METASERVER_ATIME > time;
644     $METASERVER_ATIME = time + 60;
645    
646     my $table = $METASERVER->{table};
647     $table->clear;
648     $table->add (0, 0, my $label = new CFClient::UI::Label max_w => $WIDTH * 0.8, text => "fetching server list...");
649    
650     my $buf;
651    
652     my $fh = new IO::Socket::INET PeerHost => $META_SERVER, Blocking => 0;
653    
654     unless ($fh) {
655     $label->set_text ("unable to contact metaserver: $!");
656     return;
657     }
658    
659     Event->io (fd => $fh, poll => 'r', cb => sub {
660     my $res = sysread $fh, $buf, 8192, length $buf;
661    
662     if (!defined $res) {
663     $_[0]->w->cancel;
664     $label->set_text ("error while retrieving server list: $!");
665     } elsif ($res == 0) {
666     $_[0]->w->cancel;
667     status "server list retrieved";
668    
669     utf8::decode $buf if utf8::valid $buf;
670    
671     $table->clear;
672    
673     my @col = qw(Use #Users Host Uptime Version Description);
674     $table->add ($_, 0, new CFClient::UI::Label align => 0, fg => [1, 1, 0], text => $col[$_])
675     for 0 .. $#col;
676    
677     my @align = qw(1 0 1 1 -1);
678    
679     my $y = 0;
680     for my $m (sort { $b->[3] <=> $a->[3] } map [split /\|/], split /\015?\012/, $buf) {
681     my ($ip, $last, $host, $users, $version, $desc, $ibytes, $obytes, $uptime) = @$m;
682    
683     for ($desc) {
684     s/<br>/\n/gi;
685     s/<li>/\n· /gi;
686     s/<.*?>//sgi;
687     s/&/&amp;/g;
688     s/</&lt;/g;
689     s/>/&gt;/g;
690     }
691    
692     $uptime = sprintf "%dd %02d:%02d:%02d",
693     (int $m->[8] / 86400),
694     (int $m->[8] / 3600) % 24,
695     (int $m->[8] / 60) % 60,
696     $m->[8] % 60;
697    
698     $m = [$users, $host, $uptime, $version, $desc];
699    
700     $y++;
701    
702     $table->add (0, $y, new CFClient::UI::VBox children => [
703 root 1.18 (new CFClient::UI::Button text => "Use", on_activate => sub {
704 root 1.1 $HOST->set_text ($CFG->{host} = $host);
705     }),
706     (new CFClient::UI::Empty expand => 1),
707     ]);
708    
709     $table->add ($_ + 1, $y, new CFClient::UI::Label
710     ellipsise => 0, align => $align[$_], text => $m->[$_], fontsize => 0.8)
711     for 0 .. $#$m;
712     }
713     }
714     });
715     }
716    
717     sub server_setup {
718 root 1.23 my $dialog = $SERVER_SETUP = new CFClient::UI::FancyFrame
719 root 1.21 x => $WIDTH * (1/3),
720 elmex 1.19 y => $HEIGHT * (1/8),
721     name => "server_setup",
722     title => "Server Setup",
723     child => (my $vbox = new CFClient::UI::VBox),
724     on_visibility_change => sub {
725 root 1.21 my ($self, $visible) = @_;
726     $self->center if $visible;
727     },
728     ;
729 elmex 1.19
730 root 1.1 $vbox->add (my $table = new CFClient::UI::Table expand => 1, col_expand => [0, 1]);
731     $table->add (0, 2, new CFClient::UI::Label valign => 0, align => 1, text => "Host:Port");
732    
733     {
734     $table->add (1, 2, my $vbox = new CFClient::UI::VBox);
735    
736     $vbox->add (
737     my $HOST = new CFClient::UI::Entry
738     expand => 1,
739     text => $CFG->{host},
740     tooltip => "The hostname or ip address of the Crossfire(+) server to connect to",
741 root 1.18 on_changed => sub {
742 root 1.1 my ($self, $value) = @_;
743     $CFG->{host} = $value;
744     }
745     );
746    
747     $METASERVER = metaserver_dialog;
748    
749     $vbox->add (new CFClient::UI::Flopper
750     expand => 1,
751     text => "Server List",
752     other => $METASERVER,
753     tooltip => "Show a list of available crossfire servers",
754 root 1.18 on_open => sub {
755 root 1.1 update_metaserver $HOST;
756     }
757     );
758     }
759    
760     $table->add (0, 4, new CFClient::UI::Label valign => 0, align => 1, text => "Username");
761     $table->add (1, 4, new CFClient::UI::Entry
762     text => $CFG->{user},
763     tooltip => "The name of your character on the server",
764 root 1.18 on_changed => sub {
765 root 1.1 my ($self, $value) = @_;
766     $CFG->{user} = $value;
767     }
768     );
769    
770     $table->add (0, 5, new CFClient::UI::Label valign => 0, align => 1, text => "Password");
771     $table->add (1, 5, new CFClient::UI::Entry
772     text => $CFG->{password},
773     hidden => 1,
774     tooltip => "The password for your character",
775 root 1.18 on_changed => sub {
776 root 1.1 my ($self, $value) = @_;
777     $CFG->{password} = $value;
778     }
779     );
780    
781     $table->add (0, 7, new CFClient::UI::Label valign => 0, align => 1, text => "Map Size");
782     $table->add (1, 7, new CFClient::UI::Slider
783     req_w => 100,
784     range => [$CFG->{mapsize}, 10, 100, 0, 1],
785     tooltip => "This is the size of the portion of the map update the server sends you. "
786     . "If you set this to a high value you will be able to see further, "
787     . "but you also increase bandwidth requirements and latency. "
788     . "This option is only used once at log-in.",
789 root 1.18 on_changed => sub {
790 root 1.1 my ($self, $value) = @_;
791    
792     $CFG->{mapsize} = $self->{range}[0] = $value = int $value;
793     },
794     );
795    
796     $table->add (0, 8, new CFClient::UI::Label valign => 0, align => 1, text => "Face Prefetch");
797     $table->add (1, 8, new CFClient::UI::CheckBox
798     state => $CFG->{face_prefetch},
799     tooltip => "<b>Background Image Prefetch</b>\n\n"
800     . "If enabled, the client automatically pre-fetches images from the server. "
801     . "This might increase or create lag, but increases the chances "
802     . "of faces being ready for display when you encounter them. "
803     . "It also uses up server bandwidth on every connect, "
804     . "so only set it if you really need to prefetch images. "
805     . "This option can be set and unset any time.",
806 root 1.18 on_changed => sub { $CFG->{face_prefetch} = $_[1] },
807 root 1.1 );
808    
809     $table->add (0, 9, new CFClient::UI::Label valign => 0, align => 1, text => "Output-Count");
810     $table->add (1, 9, new CFClient::UI::Entry
811     text => $CFG->{output_count},
812     tooltip => "Should be set to 1 unless you know what you are doing. This option is only used once at log-in.",
813 root 1.18 on_changed => sub { $CFG->{output_count} = $_[1] },
814 root 1.1 );
815    
816     $table->add (0, 10, new CFClient::UI::Label valign => 0, align => 1, text => "Output-Sync");
817     $table->add (1, 10, new CFClient::UI::Entry
818     text => $CFG->{output_sync},
819     tooltip => "Should be set to 1 unless you know what you are doing. This option is only used once at log-in.",
820 root 1.18 on_changed => sub { $CFG->{output_sync} = $_[1] },
821 root 1.1 );
822    
823     $table->add (1, 11, $LOGIN_BUTTON = new CFClient::UI::Button
824     expand => 1,
825     align => 0,
826     text => "Login",
827 root 1.18 on_activate => sub {
828 root 1.1 $CONN ? stop_game
829     : start_game;
830     },
831     );
832    
833     $dialog
834     }
835    
836     sub message_window {
837     my $window = new CFClient::UI::FancyFrame
838 elmex 1.16 name => "message_window",
839 root 1.1 title => "Messages",
840     border_bg => [1, 1, 1, 1],
841     bg => [0, 0, 0, 0.75],
842 elmex 1.19 req_x => -1,
843 root 1.20 req_y => 0,
844     def_w => int $::WIDTH / 3,
845     def_h => int $::HEIGHT / 5,
846 root 1.1 child => (my $vbox = new CFClient::UI::VBox);
847    
848     $vbox->add ($LOGVIEW);
849    
850     $vbox->add (my $input = new CFClient::UI::Entry
851     tooltip => "<b>Chat Box</b>. If you enter a text and press return/enter here, the current <i>communication command</i> "
852     . "from the client setup will be prepended (e.g. <b>shout</b>, <b>chat</b>...). "
853     . "If you prepend a slash (/), you will submit a command instead (similar to IRC). "
854     . "A better way to submit commands (and the occasional chat command) is often the map command completer.",
855 root 1.18 on_focus_in => sub {
856 root 1.1 my ($input, $prev_focus) = @_;
857    
858     delete $input->{refocus_map};
859    
860     if ($prev_focus == $MAPWIDGET && $input->{auto_activated}) {
861     $input->{refocus_map} = 1;
862     }
863     delete $input->{auto_activated};
864     },
865 root 1.18 on_activate => sub {
866 root 1.1 my ($input, $text) = @_;
867     $input->set_text ('');
868    
869     if ($text =~ /^\/(.*)/) {
870     $::CONN->user_send ($1);
871     } else {
872     my $say_cmd = $::CFG->{say_command} || 'say';
873     $::CONN->user_send ("$say_cmd $text");
874     }
875     if ($input->{refocus_map}) {
876     delete $input->{refocus_map};
877     $MAPWIDGET->focus_in
878     }
879     },
880 root 1.18 on_escape => sub {
881 root 1.1 $MAPWIDGET->focus_in
882     },
883     );
884    
885     $CONSOLE = {
886     window => $window,
887     input => $input
888     };
889    
890     $window
891     }
892    
893     sub open_quit_dialog {
894     unless ($QUIT_DIALOG) {
895     $QUIT_DIALOG = new CFClient::UI::FancyFrame title => "Really Quit?";
896    
897     $QUIT_DIALOG->add (my $vb = new CFClient::UI::VBox expand => 1);
898    
899     $vb->add (new CFClient::UI::Label
900     text => "You should find a savebed and apply it first!",
901     max_w => $WIDTH * 0.25,
902     ellipsize => 0,
903     );
904     $vb->add (my $hb = new CFClient::UI::HBox expand => 1);
905     $hb->add (new CFClient::UI::Button
906     text => "Ok",
907     expand => 1,
908 root 1.18 on_activate => sub { $QUIT_DIALOG->hide },
909 root 1.1 );
910     $hb->add (new CFClient::UI::Button
911     text => "Quit anyway",
912     expand => 1,
913 root 1.18 on_activate => sub { exit },
914 root 1.1 );
915 root 1.21 }
916 root 1.1
917 root 1.21 $QUIT_DIALOG->show;
918     $QUIT_DIALOG->center;
919 root 1.1 }
920    
921     sub make_inventory_window {
922 root 1.23 my $invwin = $INV_WINDOW = new CFClient::UI::FancyFrame
923 root 1.21 def_w => $WIDTH * 7/8,
924     def_h => $HEIGHT * 7/8,
925 elmex 1.19 title => "Inventory",
926 root 1.20 name => "inventory_window",
927 elmex 1.19 on_visibility_change => sub {
928 root 1.21 my ($self, $visible) = @_;
929     $self->center if $visible;
930     },
931     ;
932 root 1.1
933 root 1.21 $invwin->add (my $hb = new CFClient::UI::HBox homogeneous => 1);
934 root 1.1
935 root 1.21 $hb->add (my $vb1 = new CFClient::UI::VBox);
936     $vb1->add (new CFClient::UI::Label align => 0, text => "Player");
937     $vb1->add ($INV = new CFClient::UI::Inventory expand => 1);
938 root 1.1
939 root 1.21 $hb->add (my $vb2 = new CFClient::UI::VBox);
940 elmex 1.17
941 elmex 1.14 $vb2->add (my $hb2 = new CFClient::UI::HBox);
942 root 1.21 $hb2->add ($INVR_LBL = new CFClient::UI::Label align => 0, expand => 1, text => "Floor");
943 elmex 1.14 $hb2->add (new CFClient::UI::Button
944     text => "Close",
945     tooltip => "Close the currently open container (if one is open)",
946 root 1.18 on_activate => sub {
947 elmex 1.14 $CONN->send ("apply $CONN->{open_container}")
948     if $CONN->{open_container} != 0;
949     },
950     );
951    
952 root 1.1 $vb2->add ($INVR = new CFClient::UI::Inventory expand => 1);
953    
954     $invwin
955     }
956    
957     sub make_help_window {
958     my $win = new CFClient::UI::FancyFrame
959 root 1.20 def_w => int $WIDTH * 7/8,
960     def_h => int $HEIGHT * 7/8,
961     title => "Documentation";
962 root 1.1
963     $win->add (my $vbox = new CFClient::UI::VBox);
964    
965     $vbox->add (my $buttons = new CFClient::UI::HBox);
966     $vbox->add (my $viewer = new CFClient::UI::TextView expand => 1, fontsize => 0.8);
967    
968     for (
969     [intro => "Introduction"],
970     [manual => "Manual"],
971     [command_help => "Commands"],
972     [skill_help => "Skills"],
973     ) {
974     my ($pod, $label) = @$_;
975    
976     $buttons->add (new CFClient::UI::Button
977     text => $label,
978 root 1.18 on_activate => sub {
979 root 1.1 my $parser = new Pod::POM;
980     my $pom = $parser->parse_file (CFClient::find_rcfile "pod/$pod.pod");
981    
982     $viewer->clear;
983    
984     $viewer->add_paragraph ([1, 1, 1, 1], $_->[1], $_->[0])
985     for @{ CFClient::pod_to_pango_list $pom };
986    
987     $viewer->set_offset (0);
988     },
989     );
990     }
991    
992     $viewer->add_paragraph ([1, 1, 0, 1], "<big>Use one of the buttons above to display a document.</big>");
993    
994     $win
995     }
996    
997     sub sdl_init {
998     CFClient::SDL_Init
999     and die "SDL::Init failed!\n";
1000     }
1001    
1002     sub video_init {
1003     sdl_init;
1004    
1005     $CFG->{sdl_mode} = 0 if $CFG->{sdl_mode} >= @SDL_MODES;
1006    
1007     my ($old_w, $old_h) = ($WIDTH, $HEIGHT);
1008    
1009     ($WIDTH, $HEIGHT) = @{ $SDL_MODES[$CFG->{sdl_mode}] };
1010     $FULLSCREEN = $CFG->{fullscreen};
1011     $FAST = $CFG->{fast};
1012    
1013     CFClient::SDL_SetVideoMode $WIDTH, $HEIGHT, $FULLSCREEN
1014     or die "SDL_SetVideoMode failed: " . (CFClient::SDL_GetError) . "\n";
1015    
1016     $SDL_ACTIVE = 1;
1017     $LAST_REFRESH = time - 0.01;
1018    
1019 root 1.10 CFClient::OpenGL::init;
1020 root 1.1
1021     $FONTSIZE = int $HEIGHT / 40 * $CFG->{gui_fontsize};
1022    
1023     $CFClient::UI::ROOT->configure (0, 0, $WIDTH, $HEIGHT);#d#
1024    
1025     #############################################################################
1026    
1027     if ($DEBUG_STATUS) {
1028     CFClient::UI::rescale_widgets $WIDTH / $old_w, $HEIGHT / $old_h;
1029     } else {
1030     # create the widgets
1031    
1032     $DEBUG_STATUS = new CFClient::UI::Label padding => 0, z => 100, req_x => -1;
1033     $DEBUG_STATUS->show;
1034    
1035     $STATUSBOX = new CFClient::UI::Statusbox;
1036     $STATUSBOX->add ("Use <b>Alt-Enter</b> to toggle fullscreen mode", pri => -100, color => [1, 1, 1, 0.8]);
1037    
1038     (new CFClient::UI::Frame
1039     bg => [0, 0, 0, 0.4],
1040     req_y => -1,
1041     child => $STATUSBOX,
1042     )->show;
1043    
1044     CFClient::UI::FancyFrame->new (
1045     border_bg => [1, 1, 1, 192/255],
1046     bg => [1, 1, 1, 0],
1047     child => ($MAPMAP = new CFClient::MapWidget::MapMap
1048     tooltip => "<b>Map</b>. On servers that support this feature, this will display an overview of the surrounding areas.",
1049     ),
1050     )->show;
1051    
1052     $MAPWIDGET = new CFClient::MapWidget;
1053     $MAPWIDGET->connect (activate_console => sub {
1054     my ($mapwidget, $preset) = @_;
1055    
1056     if ($CONSOLE) {
1057     $CONSOLE->{input}->{auto_activated} = 1;
1058     $CONSOLE->{input}->focus_in;
1059    
1060     if ($preset && $CONSOLE->{input}->get_text eq '') {
1061     $CONSOLE->{input}->set_text ($preset);
1062     }
1063     }
1064     });
1065     $MAPWIDGET->show;
1066     $MAPWIDGET->focus_in;
1067    
1068     $LOGVIEW = new CFClient::UI::TextView
1069     expand => 1,
1070     font => $FONT_FIXED,
1071     fontsize => $::CFG->{log_fontsize},
1072     can_hover => 1,
1073     can_events => 1,
1074     tooltip => "<b>Server Log</b>. This text viewer contains all the messages sent by the server.",
1075     ;
1076    
1077     $BUTTONBAR = new CFClient::UI::HBox;
1078    
1079     $BUTTONBAR->add (new CFClient::UI::Flopper text => "Client Setup", other => client_setup,
1080     tooltip => "Toggles a dialog where you can configure various aspects of the client, such as graphics mode, performance, and audio options.");
1081     $BUTTONBAR->add (new CFClient::UI::Flopper text => "Server Setup", other => server_setup,
1082     tooltip => "Toggles a dialog where you can configure the server to play on, your username, password and other server-related options.");
1083     $BUTTONBAR->add (new CFClient::UI::Flopper text => "Message Window", other => message_window,
1084     tooltip => "Toggles the server message log, where the client collects <i>all</i> messages from the server.");
1085    
1086     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
1087    
1088     $BUTTONBAR->add (new CFClient::UI::Flopper text => "Stats Window", other => make_stats_window,
1089     tooltip => "Toggles the statistics window, where all your Stats and Resistances are being displayed at all times.");
1090     $BUTTONBAR->add (new CFClient::UI::Flopper text => "Inventory", other => make_inventory_window,
1091     tooltip => "Toggles the inventory window, where you can manage your loot (or treaures :).");
1092    
1093     $BUTTONBAR->add (new CFClient::UI::Button
1094     text => "Save Config",
1095     tooltip => "Saves the options chosen in the client setting, server settings and the window layout to be restored on later runs.",
1096 root 1.18 on_activate => sub {
1097 elmex 1.16 $::CFG->{layout} = CFClient::UI::get_layout;
1098 root 1.1 CFClient::write_cfg "$Crossfire::VARDIR/pclientrc";
1099     status "Configuration Saved";
1100     },
1101     );
1102    
1103     $BUTTONBAR->add (new CFClient::UI::Flopper text => "Help!", other => make_help_window,
1104     tooltip => "View Documentation");
1105    
1106     $BUTTONBAR->add (new CFClient::UI::Button
1107 root 1.18 text => "Quit",
1108     tooltip => "Terminates the program",
1109     on_activate => sub {
1110 root 1.1 if ($CONN) {
1111     open_quit_dialog;
1112     } else {
1113     exit;
1114     }
1115     },
1116     );
1117    
1118     $BUTTONBAR->show;
1119    
1120     $STATUSBOX->add ("Set video mode $WIDTH×$HEIGHT", timeout => 10, fg => [1, 1, 1, 0.5]);
1121    
1122 root 1.23 $SERVER_SETUP->show;
1123 root 1.1 }
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