ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.38
Committed: Wed May 31 13:44:26 2006 UTC (17 years, 11 months ago) by elmex
Branch: MAIN
Changes since 1.37: +15 -2 lines
Log Message:
added first version of a spell widget

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