ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.30
Committed: Tue May 30 01:42:16 2006 UTC (17 years, 11 months ago) by root
Branch: MAIN
Changes since 1.29: +48 -47 lines
Log Message:
first try at layout saving and new layotu algorithm

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