ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.40
Committed: Fri Jun 2 02:12:04 2006 UTC (17 years, 11 months ago) by root
Branch: MAIN
Changes since 1.39: +25 -21 lines
Log Message:
fix server dialog

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