ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.14
Committed: Sat May 27 08:45:24 2006 UTC (18 years ago) by elmex
Branch: MAIN
Changes since 1.13: +11 -2 lines
Log Message:
Added Close button and fixed statusbox messages and experience messages

File Contents

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