ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.3
Committed: Thu May 25 16:54:29 2006 UTC (18 years ago) by root
Branch: MAIN
Changes since 1.2: +3 -0 lines
Log Message:
nothign beats a good hack: fix floorbox and inventory tabe layout

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     use Crossfire::Protocol;
35    
36     use Compress::LZF;
37    
38     use CFClient;
39     use CFClient::UI;
40     use CFClient::MapWidget;
41    
42     $Event::DIED = sub {
43     # TODO: display dialog box or so
44     CFClient::error $_[1];
45     };
46    
47     #$SIG{__WARN__} = sub { Carp::cluck $_[0] };#d#
48    
49     our $VERSION = '0.1';
50    
51     my $MAX_FPS = 60;
52     my $MIN_FPS = 5; # unused as of yet
53    
54     our $META_SERVER = "crossfire.real-time.com:13326";
55    
56     our $FACEMAP;
57     our $TILECACHE;
58     our $MAPCACHE;
59    
60     our $LAST_REFRESH;
61     our $NOW;
62    
63     our $CFG;
64     our $CONN;
65     our $FAST; # fast, low-quality mode, possibly useful for software-rendering
66    
67     our $WANT_REFRESH;
68     our $CAN_REFRESH;
69    
70     our @SDL_MODES;
71     our $WIDTH;
72     our $HEIGHT;
73     our $FULLSCREEN;
74     our $FONTSIZE;
75    
76     our $FONT_PROP;
77     our $FONT_FIXED;
78    
79     our $MAP;
80     our $MAPMAP;
81     our $MAPWIDGET;
82     our $BUTTONBAR;
83     our $LOGVIEW;
84     our $CONSOLE;
85     our $METASERVER;
86     our $LOGIN_BUTTON;
87     our $QUIT_DIALOG;
88    
89     our $FLOORBOX;
90     our $GAUGES;
91     our $STATWIDS;
92    
93     our $SDL_ACTIVE;
94     our %SDL_CB;
95    
96     our $SDL_MIXER;
97     our @SOUNDS; # event => file mapping
98     our %AUDIO_CHUNKS; # audio files
99    
100     our $ALT_ENTER_MESSAGE;
101     our $STATUSBOX;
102     our $DEBUG_STATUS;
103    
104     our $INVWIN;
105     our $INV;
106     our $INVR;
107     our $INVR_LBL;
108     our $OPENCONT;
109    
110     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     my ($w, $h) = $DEBUG_STATUS->size_request;
117     $DEBUG_STATUS->move ($WIDTH - $w, 0);
118     }
119    
120     sub start_game {
121     status "logging in...";
122    
123     my $mapsize = List::Util::min 32, List::Util::max 11, int $WIDTH * $CFG->{mapsize} * 0.01 / 32;
124    
125     $MAPCACHE = CFClient::db_table "mapcache_$CFG->{host}";
126     $MAP = new CFClient::Map $mapsize, $mapsize;
127    
128     my ($host, $port) = split /:/, $CFG->{host};
129    
130     $CONN = eval {
131     new conn
132     host => $host,
133     port => $port || 13327,
134     user => $CFG->{user},
135     pass => $CFG->{password},
136     mapw => $mapsize,
137     maph => $mapsize,
138     ;
139     };
140    
141     if ($CONN) {
142     CFClient::lowdelay fileno $CONN->{fh};
143    
144     $LOGIN_BUTTON->set_text ("Logout");
145     status "login successful";
146    
147     $BUTTONBAR->{children}[1]->emit ("activate")
148     if $BUTTONBAR->{children}[1]->{state};
149    
150     } else {
151     status "unable to connect";
152     stop_game();
153     }
154     }
155    
156     sub stop_game {
157     return unless $CONN;
158    
159     status "connection closed";
160     $LOGIN_BUTTON->set_text ("Login");
161     $CONN->destroy;
162     $CONN = 0; # false, does not autovivify
163    
164     $BUTTONBAR->{children}[1]->emit ("activate")
165     unless $BUTTONBAR->{children}[1]->{state};
166    
167     undef $MAPCACHE;
168     undef $MAP;
169     }
170    
171     sub client_setup {
172     my $dialog = new CFClient::UI::FancyFrame
173     title => "Client Setup",
174     child => (my $vbox = new CFClient::UI::VBox);
175     $vbox->add (my $table = new CFClient::UI::Table expand => 1, col_expand => [0, 1]);
176    
177     $table->add (0, 0, new CFClient::UI::Label valign => 0, align => 1, text => "Video Mode");
178     $table->add (1, 0, my $hbox = new CFClient::UI::HBox);
179    
180     $hbox->add (my $mode_slider = new CFClient::UI::Slider expand => 1, req_w => 100, range => [$CFG->{sdl_mode}, 0, $#SDL_MODES, 0, 1]);
181     $hbox->add (my $mode_label = new CFClient::UI::Label align => 0, valign => 0, height => 0.8, template => "9999x9999");
182    
183     $mode_slider->connect (changed => sub {
184     my ($self, $value) = @_;
185    
186     $CFG->{sdl_mode} = $self->{range}[0] = $value = int $value;
187     $mode_label->set_text (sprintf "%dx%d", @{$SDL_MODES[$value]});
188     });
189     $mode_slider->emit (changed => $mode_slider->{range}[0]);
190    
191     my $row = 1;
192    
193     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Fullscreen");
194     $table->add (1, $row++, new CFClient::UI::CheckBox
195     state => $CFG->{fullscreen},
196     tooltip => "Bring the client into fullscreen mode.",
197     connect_changed => sub {
198     my ($self, $value) = @_;
199     $CFG->{fullscreen} = $value;
200     }
201     );
202    
203     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Fast & Ugly");
204     $table->add (1, $row++, new CFClient::UI::CheckBox
205     state => $CFG->{fast},
206     tooltip => "Lower the visual quality considerably to speed up rendering.",
207     connect_changed => sub {
208     my ($self, $value) = @_;
209     $CFG->{fast} = $value;
210     }
211     );
212    
213     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Map Scale");
214     $table->add (1, $row++, new CFClient::UI::Slider
215     range => [(log $CFG->{map_scale}) / (log 2), -3, 1, 0, 1],
216     tooltip => "Enlarge or shrink the displayed map. Changes are instant.",
217     connect_changed => sub {
218     my ($self, $value) = @_;
219     $CFG->{map_scale} = 2 ** $value;
220     }
221     );
222    
223     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Fog of War");
224     $table->add (1, $row++, new CFClient::UI::CheckBox
225     state => $CFG->{fow_enable},
226     tooltip => "<b>Fog-of-War</b> marks areas that cannot be seen by the player. Changes are instant.",
227     connect_changed => sub {
228     my ($self, $value) = @_;
229     $CFG->{fow_enable} = $value;
230     }
231     );
232    
233     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "FoW Intensity");
234     $table->add (1, $row++, new CFClient::UI::Slider
235     range => [$CFG->{fow_intensity}, 0, 1, 0, 1 / 256],
236     tooltip => "<b>Fog of War Lightness.</b> The higher the intensity, the lighter the Fog-of-War color. Changes are instant.",
237     connect_changed => sub {
238     my ($self, $value) = @_;
239     $CFG->{fow_intensity} = $value;
240     }
241     );
242    
243     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "FoW Smooth");
244     $table->add (1, $row++, new CFClient::UI::CheckBox
245     state => $CFG->{fow_smooth},
246     tooltip => "Smooth the Fog-of-War a bit to make it more realistic. Changes are instant.",
247     connect_changed => sub {
248     my ($self, $value) = @_;
249     $CFG->{fow_smooth} = $value;
250     status "Fog of War smoothing requires OpenGL 1.2 or higher" if $CFClient::GL_VERSION < 1.2;
251     }
252     );
253    
254     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "GUI Fontsize");
255     $table->add (1, $row++, new CFClient::UI::Slider
256     range => [$CFG->{gui_fontsize}, 0.5, 2, 0, 0.1],
257     tooltip => "The base font size used by most GUI elements that do not have their own setting.",
258     connect_changed => sub { $CFG->{gui_fontsize} = $_[1] },
259     );
260    
261     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Message Fontsize");
262     $table->add (1, $row++, new CFClient::UI::Slider
263     range => [$CFG->{log_fontsize}, 0.5, 2, 0, 0.1],
264     tooltip => "The font size used by the <b>message/server log</b> window only. Changes are instant.",
265     connect_changed => sub { $LOGVIEW->set_fontsize ($CFG->{log_fontsize} = $_[1]) },
266     );
267    
268     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Stats Fontsize");
269    
270     $table->add (1, $row++, new CFClient::UI::Slider
271     range => [$CFG->{stat_fontsize}, 0.5, 2, 0, 0.1],
272     tooltip => "The font size used by the <b>statistics window</b> only. Changes are instant.",
273     connect_changed => sub {
274     $CFG->{stat_fontsize} = $_[1];
275     &set_stats_window_fontsize;
276     }
277     );
278    
279     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Gauge fontsize");
280     $table->add (1, $row++, new CFClient::UI::Slider
281     range => [$CFG->{gauge_fontsize}, 0.5, 2, 0, 0.1],
282     tooltip => "Adjusts the fontsize of the gauges at the bottom right. Changes are instant.",
283     connect_changed => sub {
284     $CFG->{gauge_fontsize} = $_[1];
285     &set_gauge_window_fontsize;
286     }
287     );
288    
289     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Gauge size");
290     $table->add (1, $row++, new CFClient::UI::Slider
291     range => [$CFG->{gauge_size}, 0.2, 0.8],
292     tooltip => "Adjust the size of the stats gauges at the bottom right. Changes are instant.",
293     connect_changed => sub {
294     $CFG->{gauge_size} = $_[1];
295     $GAUGES->{win}->set_size ($WIDTH, int $HEIGHT * $CFG->{gauge_size});
296     }
297     );
298    
299     $table->add (1, $row++, new CFClient::UI::Button
300     expand => 1, align => 0, text => "Apply",
301     tooltip => "Apply the video settings",
302     connect_activate => sub {
303     video_shutdown ();
304     video_init ();
305     }
306     );
307    
308     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Audio Enable");
309     $table->add (1, $row++, new CFClient::UI::CheckBox
310     state => $CFG->{audio_enable},
311     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.",
312     connect_changed => sub {
313     $CFG->{audio_enable} = $_[1];
314     }
315     );
316     # $table->add (0, 9, new CFClient::UI::Label valign => 0, align => 1, text => "Effects Volume");
317     # $table->add (1, 8, new CFClient::UI::Slider range => [$CFG->{effects_volume}, 0, 128, 1], connect_changed => sub {
318     # $CFG->{effects_volume} = $_[1];
319     # });
320     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Background Music");
321     $table->add (1, $row++, my $hbox = new CFClient::UI::HBox);
322     $hbox->add (new CFClient::UI::CheckBox
323     expand => 1, state => $CFG->{bgm_enable},
324     tooltip => "If enabled, playing of background music is enabled. If disabled, no background music will be played.",
325     connect_changed => sub {
326     $CFG->{bgm_enable} = $_[1];
327     }
328     );
329     $hbox->add (new CFClient::UI::Slider
330     expand => 1, range => [$CFG->{bgm_volume}, 0, 1, 0, 1/128],
331     tooltip => "The volume of the background music. Changes are instant.",
332     connect_changed => sub {
333     $CFG->{bgm_volume} = $_[1];
334     CFClient::MixMusic::volume $_[1] * 128;
335     }
336     );
337    
338     $table->add (1, $row++, new CFClient::UI::Button
339     expand => 1, align => 0, text => "Apply",
340     tooltip => "Apply the audio settings",
341     connect_activate => sub {
342     audio_shutdown ();
343     audio_init ();
344     }
345     );
346    
347     $table->add (0, $row, new CFClient::UI::Label valign => 0, align => 1, text => "Chat Command");
348     $table->add (1, $row++, my $saycmd = new CFClient::UI::Entry
349     text => $CFG->{say_command},
350     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. "
351     . "Usually you want to enter something like 'say' or 'shout' or 'gsay' here. "
352     . "But you could also set it to <b>tell <i>playername</i></b> to only chat with that user.",
353     connect_changed => sub {
354     my ($self, $value) = @_;
355     $CFG->{say_command} = $value;
356     }
357     );
358    
359     $dialog
360     }
361    
362     sub set_stats_window_fontsize {
363     for (values %{$STATWIDS}) {
364     $_->set_fontsize ($::CFG->{stat_fontsize});
365     }
366     }
367    
368     sub set_gauge_window_fontsize {
369     for (map { $GAUGES->{$_} } grep { $_ ne 'win' } keys %{$GAUGES}) {
370     $_->set_fontsize ($::CFG->{gauge_fontsize});
371     }
372     }
373    
374     sub make_gauge_window {
375     my $gh = int $HEIGHT * $CFG->{gauge_size};
376    
377     my $win = new CFClient::UI::Frame (
378     req_y => -1,
379     user_w => $WIDTH,
380     user_h => $gh,
381     );
382    
383     $win->add (my $hbox = new CFClient::UI::HBox
384     children => [
385     (new CFClient::UI::HBox expand => 1),
386     (new CFClient::UI::VBox children => [
387     (new CFClient::UI::Empty expand => 1),
388 root 1.2 (new CFClient::UI::Frame bg => [0, 0, 0, 0.4], child => ($FLOORBOX = new CFClient::UI::Table)),
389 root 1.1 ]),
390     (my $vbox = new CFClient::UI::VBox),
391     ],
392     );
393    
394     $vbox->add (new CFClient::UI::HBox
395     expand => 1,
396     children => [
397     (new CFClient::UI::Empty expand => 1),
398     (my $hb = new CFClient::UI::HBox),
399     ],
400     );
401    
402     $hb->add (my $hg = new CFClient::UI::Gauge type => 'hp',
403     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.");
404     $hb->add (my $mg = new CFClient::UI::Gauge type => 'mana',
405     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.");
406     $hb->add (my $gg = new CFClient::UI::Gauge type => 'grace',
407     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.");
408     $hb->add (my $fg = new CFClient::UI::Gauge type => 'food',
409     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.");
410    
411     $vbox->add (my $exp = new CFClient::UI::Label valign => 0, align => 1, can_hover => 1, can_events => 1,
412     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.");
413     $vbox->add (my $rng = new CFClient::UI::Label valign => 0, align => 1, can_hover => 1, can_events => 1,
414     tooltip => "<b>Ranged attack</b> - how you attack when you press shift-cursor (spell, skill, weapon etc.)");
415    
416     $GAUGES = {
417     exp => $exp, win => $win, range => $rng,
418     food => $fg, mana => $mg, hp => $hg, grace => $gg
419     };
420    
421     &set_gauge_window_fontsize;
422    
423     $win
424     }
425    
426     sub make_stats_window {
427     my $tgw = new CFClient::UI::FancyFrame title => "Stats";
428    
429     $tgw->add (new CFClient::UI::Window child => my $vb = new CFClient::UI::VBox);
430     $vb->add ($STATWIDS->{title} = new CFClient::UI::Label valign => 0, align => -1, text => "Title:", expand => 1,
431     can_hover => 1, can_events => 1,
432     tooltip => "Your name and title. You can change your title by using the <b>title</b> command, if supported by the server.");
433     $vb->add ($STATWIDS->{map} = new CFClient::UI::Label valign => 0, align => -1, text => "Map:", expand => 1,
434     can_hover => 1, can_events => 1,
435     tooltip => "The map you are currently on (if supported by the server).");
436    
437     $vb->add (my $hb = new CFClient::UI::HBox expand => 1);
438     $hb->add (my $tbl = new CFClient::UI::Table expand => 1);
439    
440     my $color2 = [1, 1, 0];
441    
442     for (
443     [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"],
444     [0, 1, st_dex => "Dex", 30, "<b>Dexterity</b>, your physical agility. Determines chance of being hit and affects armor class and speed"],
445     [0, 2, st_con => "Con", 30, "<b>Constitution</b>, physical health and toughness. Determines how many healthpoints you can have"],
446     [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"],
447     [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"],
448     [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"],
449     [0, 6, st_cha => "Cha", 30, "<b>Charisma</b>, how well you are received by NPCs. Affects buying and selling prices in shops."],
450    
451     [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."],
452     [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."],
453     [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."],
454     [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."],
455     [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."],
456     [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."],
457     ) {
458     my ($col, $row, $id, $label, $template, $tooltip) = @$_;
459    
460     $tbl->add ($col , $row, $STATWIDS->{$id} = new CFClient::UI::Label
461     font => $FONT_FIXED, can_hover => 1, can_events => 1, valign => 0, align => +1, template => $template, tooltip => $tooltip);
462     $tbl->add ($col + 1, $row, $STATWIDS->{"$id\_lbl"} = new CFClient::UI::Label
463     font => $FONT_FIXED, can_hover => 1, can_events => 1, fg => $color2, valign => 0, align => -1, text => $label, tooltip => $tooltip);
464     }
465    
466     $hb->add (my $tbl2 = new CFClient::UI::Table expand => 1);
467    
468     my $row = 0;
469     my $col = 0;
470    
471     my %resist_names = (
472     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.)",
473     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.)",
474     conf => "<b>Confusion</b> (If you are hit by confusion you will move into random directions, and likely into monsters.)",
475     fire => "<b>Fire</b> (just your resistance to fire spells like burning hands, dragonbreath, meteor swarm fire, ...)",
476     depl => "<b>Depletion</b> (some monsters and other effects can cause stats depletion)",
477     magic => "<b>Magic</b> (resistance to magic spells like magic missile or similar)",
478     drain => "<b>Draining</b> (some monsters (e.g. vampires) and other effects can steal experience)",
479     acid => "<b>Acid</b> (resistance to acid, acid hurts pretty much and also corrodes your weapons)",
480     pois => "<b>Poison</b> (resistance to getting poisoned)",
481     para => "<b>Paralysation</b> (this resistance affects the chance you get paralysed)",
482     deat => "<b>Death</b> (resistance against death spells)",
483     phys => "<b>Physical</b> (this is the resistance against physical attacks, like when a monster hit you in melee combat)",
484     blind => "<b>Blind</b> (blind resistance affects the chance of a successful blinding attack)",
485     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)",
486     tund => "<b>Turn undead</b> (affects your resistancy to various forms of 'turn undead' spells. Only relevant when you are, in fact, undead...",
487     elec => "<b>Electricity</b> (resistance against electricity, spells like large lightning, small lightning, ...)",
488     cold => "<b>Cold</b> (this is your resistance against cold spells like icestorm, snowstorm, ...)",
489     ghit => "<b>Ghost hit</b> (special attack used by ghosts and ghost-like beings)",
490     );
491     for (qw/slow holyw conf fire depl magic
492     drain acid pois para deat phys
493     blind fear tund elec cold ghit/)
494     {
495     $tbl2->add ($col, $row,
496     $STATWIDS->{"res_$_"} =
497     new CFClient::UI::Label
498     font => $FONT_FIXED,
499     template => "-100%",
500     align => +1,
501     valign => 0,
502     can_events => 1,
503     can_hover => 1,
504     tooltip => $resist_names{$_},
505     );
506     $tbl2->add ($col + 1, $row, new CFClient::UI::Image
507     font => $FONT_FIXED,
508     can_hover => 1,
509     can_events => 1,
510     image => "ui/resist/resist_$_.png",
511     tooltip => $resist_names{$_},
512     );
513    
514     $row++;
515     if ($row % 6 == 0) {
516     $col += 2;
517     $row = 0;
518     }
519     }
520    
521     &set_stats_window_fontsize;
522     update_stats_window ({});
523    
524     $tgw
525     }
526    
527     sub formsep {
528     reverse join ",", grep length, split /(...)/, reverse $_[0] * 1
529     }
530    
531     sub update_stats_window {
532     my ($stats) = @_;
533    
534     # i love text protocols!!!
535     my $hp = $stats->{Crossfire::Protocol::CS_STAT_HP} * 1;
536     my $hp_m = $stats->{Crossfire::Protocol::CS_STAT_MAXHP} * 1;
537     my $sp = $stats->{Crossfire::Protocol::CS_STAT_SP} * 1;
538     my $sp_m = $stats->{Crossfire::Protocol::CS_STAT_MAXSP} * 1;
539     my $fo = $stats->{Crossfire::Protocol::CS_STAT_FOOD} * 1;
540     my $fo_m = 999;
541     my $gr = $stats->{Crossfire::Protocol::CS_STAT_GRACE} * 1;
542     my $gr_m = $stats->{Crossfire::Protocol::CS_STAT_MAXGRACE} * 1;
543    
544     $GAUGES->{hp} ->set_value ($hp, $hp_m);
545     $GAUGES->{mana} ->set_value ($sp, $sp_m);
546     $GAUGES->{food} ->set_value ($fo, $fo_m);
547     $GAUGES->{grace} ->set_value ($gr, $gr_m);
548     $GAUGES->{exp} ->set_text ("Exp: " . (formsep $stats->{Crossfire::Protocol::CS_STAT_EXP64})
549     . " (lvl " . ($stats->{Crossfire::Protocol::CS_STAT_LEVEL} * 1) . ")");
550     my $rng = $stats->{Crossfire::Protocol::CS_STAT_RANGE};
551     $rng =~ s/^Range: //; # thank you so much dear server
552     $GAUGES->{range} ->set_text ("Rng: " . $rng);
553     my $title = $stats->{Crossfire::Protocol::CS_STAT_TITLE};
554     $title =~ s/^Player: //;
555     $STATWIDS->{title} ->set_text ("Title: " . $title);
556    
557     $STATWIDS->{st_str} ->set_text (sprintf "%d", $stats->{5});
558     $STATWIDS->{st_dex} ->set_text (sprintf "%d", $stats->{8});
559     $STATWIDS->{st_con} ->set_text (sprintf "%d", $stats->{9});
560     $STATWIDS->{st_int} ->set_text (sprintf "%d", $stats->{6});
561     $STATWIDS->{st_wis} ->set_text (sprintf "%d", $stats->{7});
562     $STATWIDS->{st_pow} ->set_text (sprintf "%d", $stats->{22});
563     $STATWIDS->{st_cha} ->set_text (sprintf "%d", $stats->{10});
564     $STATWIDS->{st_wc} ->set_text (sprintf "%d", $stats->{13});
565     $STATWIDS->{st_ac} ->set_text (sprintf "%d", $stats->{14});
566     $STATWIDS->{st_dam} ->set_text (sprintf "%d", $stats->{15});
567     $STATWIDS->{st_arm} ->set_text (sprintf "%d", $stats->{16});
568     $STATWIDS->{st_spd} ->set_text (sprintf "%.1f", $stats->{Crossfire::Protocol::CS_STAT_SPEED});
569     $STATWIDS->{st_wspd}->set_text (sprintf "%.1f", $stats->{Crossfire::Protocol::CS_STAT_WEAP_SP});
570    
571     my %tbl = (
572     phys => 100,
573     magic => 101,
574     fire => 102,
575     elec => 103,
576     cold => 104,
577     conf => 105,
578     acid => 106,
579     drain => 107,
580     ghit => 108,
581     pois => 109,
582     slow => 110,
583     para => 111,
584     tund => 112,
585     fear => 113,
586     depl => 113,
587     deat => 115,
588     holyw => 116,
589     blind => 117
590     );
591    
592     for (keys %tbl) {
593     $STATWIDS->{"res_$_"}->set_text (sprintf "%d%", $stats->{$tbl{$_}});
594     }
595    
596     }
597    
598     sub metaserver_dialog {
599     my $dialog = new CFClient::UI::FancyFrame
600     title => "Server List",
601     child => (my $vbox = new CFClient::UI::VBox);
602    
603     $vbox->add ($dialog->{table} = new CFClient::UI::Table);
604    
605     $dialog
606     }
607    
608     my $METASERVER_ATIME;
609    
610     sub update_metaserver {
611     my ($HOST) = @_;
612    
613     return if $METASERVER_ATIME > time;
614     $METASERVER_ATIME = time + 60;
615    
616     my $table = $METASERVER->{table};
617     $table->clear;
618     $table->add (0, 0, my $label = new CFClient::UI::Label max_w => $WIDTH * 0.8, text => "fetching server list...");
619    
620     my $buf;
621    
622     my $fh = new IO::Socket::INET PeerHost => $META_SERVER, Blocking => 0;
623    
624     unless ($fh) {
625     $label->set_text ("unable to contact metaserver: $!");
626     return;
627     }
628    
629     Event->io (fd => $fh, poll => 'r', cb => sub {
630     my $res = sysread $fh, $buf, 8192, length $buf;
631    
632     if (!defined $res) {
633     $_[0]->w->cancel;
634     $label->set_text ("error while retrieving server list: $!");
635     } elsif ($res == 0) {
636     $_[0]->w->cancel;
637     status "server list retrieved";
638    
639     utf8::decode $buf if utf8::valid $buf;
640    
641     $table->clear;
642    
643     my @col = qw(Use #Users Host Uptime Version Description);
644     $table->add ($_, 0, new CFClient::UI::Label align => 0, fg => [1, 1, 0], text => $col[$_])
645     for 0 .. $#col;
646    
647     my @align = qw(1 0 1 1 -1);
648    
649     my $y = 0;
650     for my $m (sort { $b->[3] <=> $a->[3] } map [split /\|/], split /\015?\012/, $buf) {
651     my ($ip, $last, $host, $users, $version, $desc, $ibytes, $obytes, $uptime) = @$m;
652    
653     for ($desc) {
654     s/<br>/\n/gi;
655     s/<li>/\n· /gi;
656     s/<.*?>//sgi;
657     s/&/&amp;/g;
658     s/</&lt;/g;
659     s/>/&gt;/g;
660     }
661    
662     $uptime = sprintf "%dd %02d:%02d:%02d",
663     (int $m->[8] / 86400),
664     (int $m->[8] / 3600) % 24,
665     (int $m->[8] / 60) % 60,
666     $m->[8] % 60;
667    
668     $m = [$users, $host, $uptime, $version, $desc];
669    
670     $y++;
671    
672     $table->add (0, $y, new CFClient::UI::VBox children => [
673     (new CFClient::UI::Button text => "Use", connect_activate => sub {
674     $HOST->set_text ($CFG->{host} = $host);
675     }),
676     (new CFClient::UI::Empty expand => 1),
677     ]);
678    
679     $table->add ($_ + 1, $y, new CFClient::UI::Label
680     ellipsise => 0, align => $align[$_], text => $m->[$_], fontsize => 0.8)
681     for 0 .. $#$m;
682     }
683     }
684     });
685     }
686    
687     sub server_setup {
688     my $dialog = new CFClient::UI::FancyFrame
689     title => "Server Setup",
690     child => (my $vbox = new CFClient::UI::VBox);
691    
692     $vbox->add (my $table = new CFClient::UI::Table expand => 1, col_expand => [0, 1]);
693     $table->add (0, 2, new CFClient::UI::Label valign => 0, align => 1, text => "Host:Port");
694    
695     {
696     $table->add (1, 2, my $vbox = new CFClient::UI::VBox);
697    
698     $vbox->add (
699     my $HOST = new CFClient::UI::Entry
700     expand => 1,
701     text => $CFG->{host},
702     tooltip => "The hostname or ip address of the Crossfire(+) server to connect to",
703     connect_changed => sub {
704     my ($self, $value) = @_;
705     $CFG->{host} = $value;
706     }
707     );
708    
709     $METASERVER = metaserver_dialog;
710    
711     $vbox->add (new CFClient::UI::Flopper
712     expand => 1,
713     text => "Server List",
714     other => $METASERVER,
715     tooltip => "Show a list of available crossfire servers",
716     connect_open => sub {
717     update_metaserver $HOST;
718     }
719     );
720     }
721    
722     $table->add (0, 4, new CFClient::UI::Label valign => 0, align => 1, text => "Username");
723     $table->add (1, 4, new CFClient::UI::Entry
724     text => $CFG->{user},
725     tooltip => "The name of your character on the server",
726     connect_changed => sub {
727     my ($self, $value) = @_;
728     $CFG->{user} = $value;
729     }
730     );
731    
732     $table->add (0, 5, new CFClient::UI::Label valign => 0, align => 1, text => "Password");
733     $table->add (1, 5, new CFClient::UI::Entry
734     text => $CFG->{password},
735     hidden => 1,
736     tooltip => "The password for your character",
737     connect_changed => sub {
738     my ($self, $value) = @_;
739     $CFG->{password} = $value;
740     }
741     );
742    
743     $table->add (0, 7, new CFClient::UI::Label valign => 0, align => 1, text => "Map Size");
744     $table->add (1, 7, new CFClient::UI::Slider
745     req_w => 100,
746     range => [$CFG->{mapsize}, 10, 100, 0, 1],
747     tooltip => "This is the size of the portion of the map update the server sends you. "
748     . "If you set this to a high value you will be able to see further, "
749     . "but you also increase bandwidth requirements and latency. "
750     . "This option is only used once at log-in.",
751     connect_changed => sub {
752     my ($self, $value) = @_;
753    
754     $CFG->{mapsize} = $self->{range}[0] = $value = int $value;
755     },
756     );
757    
758     $table->add (0, 8, new CFClient::UI::Label valign => 0, align => 1, text => "Face Prefetch");
759     $table->add (1, 8, new CFClient::UI::CheckBox
760     state => $CFG->{face_prefetch},
761     tooltip => "<b>Background Image Prefetch</b>\n\n"
762     . "If enabled, the client automatically pre-fetches images from the server. "
763     . "This might increase or create lag, but increases the chances "
764     . "of faces being ready for display when you encounter them. "
765     . "It also uses up server bandwidth on every connect, "
766     . "so only set it if you really need to prefetch images. "
767     . "This option can be set and unset any time.",
768     connect_changed => sub { $CFG->{face_prefetch} = $_[1] },
769     );
770    
771     $table->add (0, 9, new CFClient::UI::Label valign => 0, align => 1, text => "Output-Count");
772     $table->add (1, 9, new CFClient::UI::Entry
773     text => $CFG->{output_count},
774     tooltip => "Should be set to 1 unless you know what you are doing. This option is only used once at log-in.",
775     connect_changed => sub { $CFG->{output_count} = $_[1] },
776     );
777    
778     $table->add (0, 10, new CFClient::UI::Label valign => 0, align => 1, text => "Output-Sync");
779     $table->add (1, 10, new CFClient::UI::Entry
780     text => $CFG->{output_sync},
781     tooltip => "Should be set to 1 unless you know what you are doing. This option is only used once at log-in.",
782     connect_changed => sub { $CFG->{output_sync} = $_[1] },
783     );
784    
785     $table->add (1, 11, $LOGIN_BUTTON = new CFClient::UI::Button
786     expand => 1,
787     align => 0,
788     text => "Login",
789     connect_activate => sub {
790     $CONN ? stop_game
791     : start_game;
792     },
793     );
794    
795     $dialog
796     }
797    
798     sub message_window {
799     my $window = new CFClient::UI::FancyFrame
800     title => "Messages",
801     border_bg => [1, 1, 1, 1],
802     bg => [0, 0, 0, 0.75],
803     user_w => int $::WIDTH / 3,
804     user_h => int $::HEIGHT / 5,
805     child => (my $vbox = new CFClient::UI::VBox);
806    
807     $vbox->add ($LOGVIEW);
808    
809     $vbox->add (my $input = new CFClient::UI::Entry
810     tooltip => "<b>Chat Box</b>. If you enter a text and press return/enter here, the current <i>communication command</i> "
811     . "from the client setup will be prepended (e.g. <b>shout</b>, <b>chat</b>...). "
812     . "If you prepend a slash (/), you will submit a command instead (similar to IRC). "
813     . "A better way to submit commands (and the occasional chat command) is often the map command completer.",
814     connect_focus_in => sub {
815     my ($input, $prev_focus) = @_;
816    
817     delete $input->{refocus_map};
818    
819     if ($prev_focus == $MAPWIDGET && $input->{auto_activated}) {
820     $input->{refocus_map} = 1;
821     }
822     delete $input->{auto_activated};
823     },
824     connect_activate => sub {
825     my ($input, $text) = @_;
826     $input->set_text ('');
827    
828     if ($text =~ /^\/(.*)/) {
829     $::CONN->user_send ($1);
830     } else {
831     my $say_cmd = $::CFG->{say_command} || 'say';
832     $::CONN->user_send ("$say_cmd $text");
833     }
834     if ($input->{refocus_map}) {
835     delete $input->{refocus_map};
836     $MAPWIDGET->focus_in
837     }
838     },
839     connect_escape => sub {
840     $MAPWIDGET->focus_in
841     },
842     );
843    
844     $CONSOLE = {
845     window => $window,
846     input => $input
847     };
848    
849     $window
850     }
851    
852     sub open_quit_dialog {
853     unless ($QUIT_DIALOG) {
854    
855     $QUIT_DIALOG = new CFClient::UI::FancyFrame title => "Really Quit?";
856    
857     $QUIT_DIALOG->add (my $vb = new CFClient::UI::VBox expand => 1);
858    
859     $vb->add (new CFClient::UI::Label
860     text => "You should find a savebed and apply it first!",
861     max_w => $WIDTH * 0.25,
862     ellipsize => 0,
863     );
864     $vb->add (my $hb = new CFClient::UI::HBox expand => 1);
865     $hb->add (new CFClient::UI::Button
866     text => "Ok",
867     expand => 1,
868     connect_activate => sub { $QUIT_DIALOG->hide },
869     );
870     $hb->add (new CFClient::UI::Button
871     text => "Quit anyway",
872     expand => 1,
873     connect_activate => sub { exit },
874     );
875    
876     $QUIT_DIALOG->show_centered;
877     } else {
878     $QUIT_DIALOG->show_centered;
879     }
880     }
881    
882     sub make_inventory_window {
883     my $invwin = new CFClient::UI::FancyFrame
884     user_w => $WIDTH * (7/8), user_h => $HEIGHT * (7/8), title => "Inventory";
885    
886     $invwin->add (my $hb = new CFClient::UI::HBox expand => 1);
887    
888     $hb->add (my $vb1 = new CFClient::UI::VBox expand => 1);
889     $vb1->add (my $lbl = new CFClient::UI::Label);
890     $lbl->set_text ("Player");
891     $vb1->add ($INV = new CFClient::UI::Inventory expand => 1);
892    
893     $hb->add (my $vb2 = new CFClient::UI::VBox expand => 1);
894     $vb2->add ($INVR_LBL = new CFClient::UI::Label);
895     $INVR_LBL->set_text ("Floor");
896     $vb2->add ($INVR = new CFClient::UI::Inventory expand => 1);
897    
898     $invwin
899     }
900    
901     sub make_help_window {
902     my $win = new CFClient::UI::FancyFrame
903     user_w => $WIDTH * (7/8), user_h => $HEIGHT * (7/8), title => "Documentation";
904    
905     $win->add (my $vbox = new CFClient::UI::VBox);
906    
907     $vbox->add (my $buttons = new CFClient::UI::HBox);
908     $vbox->add (my $viewer = new CFClient::UI::TextView expand => 1, fontsize => 0.8);
909    
910     for (
911     [intro => "Introduction"],
912     [manual => "Manual"],
913     [command_help => "Commands"],
914     [skill_help => "Skills"],
915     ) {
916     my ($pod, $label) = @$_;
917    
918     $buttons->add (new CFClient::UI::Button
919     text => $label,
920     connect_activate => sub {
921     my $parser = new Pod::POM;
922     my $pom = $parser->parse_file (CFClient::find_rcfile "pod/$pod.pod");
923    
924     $viewer->clear;
925    
926     $viewer->add_paragraph ([1, 1, 1, 1], $_->[1], $_->[0])
927     for @{ CFClient::pod_to_pango_list $pom };
928    
929     $viewer->set_offset (0);
930     },
931     );
932     }
933    
934     $viewer->add_paragraph ([1, 1, 0, 1], "<big>Use one of the buttons above to display a document.</big>");
935    
936     $win
937     }
938    
939     sub sdl_init {
940     CFClient::SDL_Init
941     and die "SDL::Init failed!\n";
942     }
943    
944     sub video_init {
945     sdl_init;
946    
947     $CFG->{sdl_mode} = 0 if $CFG->{sdl_mode} >= @SDL_MODES;
948    
949     my ($old_w, $old_h) = ($WIDTH, $HEIGHT);
950    
951     ($WIDTH, $HEIGHT) = @{ $SDL_MODES[$CFG->{sdl_mode}] };
952     $FULLSCREEN = $CFG->{fullscreen};
953     $FAST = $CFG->{fast};
954    
955     CFClient::SDL_SetVideoMode $WIDTH, $HEIGHT, $FULLSCREEN
956     or die "SDL_SetVideoMode failed: " . (CFClient::SDL_GetError) . "\n";
957    
958     $SDL_ACTIVE = 1;
959     $LAST_REFRESH = time - 0.01;
960    
961     CFClient::gl_init;
962    
963     $FONTSIZE = int $HEIGHT / 40 * $CFG->{gui_fontsize};
964    
965     $CFClient::UI::ROOT->configure (0, 0, $WIDTH, $HEIGHT);#d#
966    
967     #############################################################################
968    
969     if ($DEBUG_STATUS) {
970     CFClient::UI::rescale_widgets $WIDTH / $old_w, $HEIGHT / $old_h;
971     } else {
972     # create the widgets
973    
974     $DEBUG_STATUS = new CFClient::UI::Label padding => 0, z => 100, req_x => -1;
975     $DEBUG_STATUS->show;
976    
977     $STATUSBOX = new CFClient::UI::Statusbox;
978     $STATUSBOX->add ("Use <b>Alt-Enter</b> to toggle fullscreen mode", pri => -100, color => [1, 1, 1, 0.8]);
979    
980     (new CFClient::UI::Frame
981     bg => [0, 0, 0, 0.4],
982     req_y => -1,
983     child => $STATUSBOX,
984     )->show;
985    
986     CFClient::UI::FancyFrame->new (
987     border_bg => [1, 1, 1, 192/255],
988     bg => [1, 1, 1, 0],
989     child => ($MAPMAP = new CFClient::MapWidget::MapMap
990     tooltip => "<b>Map</b>. On servers that support this feature, this will display an overview of the surrounding areas.",
991     ),
992     )->show;
993    
994     $MAPWIDGET = new CFClient::MapWidget;
995     $MAPWIDGET->connect (activate_console => sub {
996     my ($mapwidget, $preset) = @_;
997    
998     if ($CONSOLE) {
999     $CONSOLE->{input}->{auto_activated} = 1;
1000     $CONSOLE->{input}->focus_in;
1001    
1002     if ($preset && $CONSOLE->{input}->get_text eq '') {
1003     $CONSOLE->{input}->set_text ($preset);
1004     }
1005     }
1006     });
1007     $MAPWIDGET->show;
1008     $MAPWIDGET->focus_in;
1009    
1010     $LOGVIEW = new CFClient::UI::TextView
1011     expand => 1,
1012     font => $FONT_FIXED,
1013     fontsize => $::CFG->{log_fontsize},
1014     can_hover => 1,
1015     can_events => 1,
1016     tooltip => "<b>Server Log</b>. This text viewer contains all the messages sent by the server.",
1017     ;
1018    
1019     $BUTTONBAR = new CFClient::UI::HBox;
1020    
1021     $BUTTONBAR->add (new CFClient::UI::Flopper text => "Client Setup", other => client_setup,
1022     tooltip => "Toggles a dialog where you can configure various aspects of the client, such as graphics mode, performance, and audio options.");
1023     $BUTTONBAR->add (new CFClient::UI::Flopper text => "Server Setup", other => server_setup,
1024     tooltip => "Toggles a dialog where you can configure the server to play on, your username, password and other server-related options.");
1025     $BUTTONBAR->add (new CFClient::UI::Flopper text => "Message Window", other => message_window,
1026     tooltip => "Toggles the server message log, where the client collects <i>all</i> messages from the server.");
1027    
1028     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
1029    
1030     $BUTTONBAR->add (new CFClient::UI::Flopper text => "Stats Window", other => make_stats_window,
1031     tooltip => "Toggles the statistics window, where all your Stats and Resistances are being displayed at all times.");
1032     $BUTTONBAR->add (new CFClient::UI::Flopper text => "Inventory", other => make_inventory_window,
1033     tooltip => "Toggles the inventory window, where you can manage your loot (or treaures :).");
1034    
1035     $BUTTONBAR->add (new CFClient::UI::Button
1036     text => "Save Config",
1037     tooltip => "Saves the options chosen in the client setting, server settings and the window layout to be restored on later runs.",
1038     connect_activate => sub {
1039     CFClient::write_cfg "$Crossfire::VARDIR/pclientrc";
1040     status "Configuration Saved";
1041     },
1042     );
1043    
1044     $BUTTONBAR->add (new CFClient::UI::Flopper text => "Help!", other => make_help_window,
1045     tooltip => "View Documentation");
1046    
1047     $BUTTONBAR->add (new CFClient::UI::Button
1048     text => "Quit",
1049     tooltip => "Terminates the program",
1050     connect_activate => sub {
1051     if ($CONN) {
1052     open_quit_dialog;
1053     } else {
1054     exit;
1055     }
1056     },
1057     );
1058    
1059     $BUTTONBAR->show;
1060    
1061     $STATUSBOX->add ("Set video mode $WIDTH×$HEIGHT", timeout => 10, fg => [1, 1, 1, 0.5]);
1062    
1063     # delay till geometry is constant
1064     $CFClient::UI::ROOT->on_post_alloc (startup => sub {
1065     $BUTTONBAR->{children}[1]->emit ("activate"); # pop up server setup
1066     my $widget = $GAUGES->{win};
1067     $widget->move (0, $HEIGHT - $widget->{h});#d# to in toplevel
1068     });
1069     force_refresh ();
1070     }
1071     }
1072    
1073     sub video_shutdown {
1074     undef $SDL_ACTIVE;
1075     }
1076    
1077     my @bgmusic = qw(game1.ogg game2.ogg game3.ogg game5.ogg game6.ogg ross1.ogg ross2.ogg ross3.ogg ross4.ogg ross5.ogg); #d#
1078     my $bgmusic;#TODO#hack#d#
1079    
1080     sub audio_channel_finished {
1081     my ($channel) = @_;
1082    
1083     #warn "channel $channel finished\n";#d#
1084     }
1085    
1086     sub audio_music_finished {
1087     return unless $CFG->{bgm_enable};
1088    
1089     # TODO: hack, do play loop and mood music
1090     $bgmusic = new_from_file CFClient::MixMusic CFClient::find_rcfile "music/$bgmusic[0]";
1091     $bgmusic->play (0);
1092    
1093     push @bgmusic, shift @bgmusic;
1094     }
1095    
1096     sub audio_init {
1097     if ($CFG->{audio_enable}) {
1098     if (open my $fh, "<", CFClient::find_rcfile "sounds/config") {
1099     $SDL_MIXER = !CFClient::Mix_OpenAudio;
1100    
1101     unless ($SDL_MIXER) {
1102     status "Unable to open sound device: there will be no sound";
1103     return;
1104     }
1105    
1106     CFClient::Mix_AllocateChannels 8;
1107     CFClient::MixMusic::volume $CFG->{bgm_volume} * 128;
1108    
1109     audio_music_finished;
1110    
1111     while (<$fh>) {
1112     next if /^\s*#/;
1113     next if /^\s*$/;
1114    
1115     my ($file, $volume, $event) = split /\s+/, $_, 3;
1116    
1117     push @SOUNDS, "$volume,$file";
1118    
1119     $AUDIO_CHUNKS{"$volume,$file"} ||= do {
1120     my $chunk = new_from_file CFClient::MixChunk CFClient::find_rcfile "sounds/$file";
1121     $chunk->volume ($volume * 128 / 100);
1122     $chunk
1123     };
1124     }
1125     } else {
1126     status "unable to open sound config: $!";
1127     }
1128     }
1129     }
1130    
1131     sub audio_shutdown {
1132     CFClient::Mix_CloseAudio if $SDL_MIXER;
1133     undef $SDL_MIXER;
1134     @SOUNDS = ();
1135     %AUDIO_CHUNKS = ();
1136     }
1137    
1138     my %animate_object;
1139     my $animate_timer;
1140    
1141     my $fps = 9;
1142    
1143     my %demo;#d#
1144    
1145     sub force_refresh {
1146     $fps = $fps * 0.95 + 1 / (($NOW - $LAST_REFRESH) || 0.1) * 0.05;
1147     debug sprintf "%3.2f", $fps;
1148    
1149     $CFClient::UI::ROOT->draw;
1150    
1151     $WANT_REFRESH = 0;
1152     $CAN_REFRESH = 0;
1153     $LAST_REFRESH = $NOW;
1154    
1155     0 && do {
1156     # some weird model-drawing code, just a joke right now
1157     use CFClient::OpenGL;
1158    
1159     $demo{t}{eye_auv} ||= new_from_file CFClient::Texture "eye2.png" or die;
1160     $demo{t}{body_auv} ||= new_from_file CFClient::Texture "body_auv3.png" or die;
1161     $demo{r} ||= do {
1162     my $mod = Compress::LZF::sthaw do { local $/; open my $fh, "<:raw:perlio", "dread.lz3"; <$fh> };
1163     $mod->{v} = pack "f*", @{$mod->{v}};
1164     $_ = [scalar @$_, pack "S!*", @$_]
1165     for values %{$mod->{g}};
1166     $mod
1167     };
1168    
1169     my $r = $demo{r} or die;
1170    
1171     glDepthMask 1;
1172     glClear GL_DEPTH_BUFFER_BIT;
1173     glEnable GL_TEXTURE_2D;
1174     glEnable GL_DEPTH_TEST;
1175     glEnable GL_CULL_FACE;
1176     glShadeModel $::FAST ? GL_FLAT : GL_SMOOTH;
1177    
1178     glMatrixMode GL_PROJECTION;
1179     glLoadIdentity;
1180     glFrustum -1 * ($::WIDTH / $::HEIGHT), 1 * ($::WIDTH / $::HEIGHT), 1, -1, 1, 10000;
1181     #glOrtho 0, $::WIDTH, 0, $::HEIGHT, -10000, 10000;
1182     glMatrixMode GL_MODELVIEW;
1183     glLoadIdentity;
1184    
1185     glPushMatrix;
1186     glTranslate 0, 0, -800;
1187     glScale 1, -1, 1;
1188     glRotate $NOW * 1000 % 36000 / 5, 0, 1, 0;
1189     glRotate $NOW * 1000 % 36000 / 6, 1, 0, 0;
1190     glRotate $NOW * 1000 % 36000 / 7, 0, 0, 1;
1191     glScale 50, 50, 50;
1192    
1193     glInterleavedArrays GL_T2F_N3F_V3F, 0, $r->{v};
1194     while (my ($k, $v) = each %{$r->{g}}) {
1195     glBindTexture GL_TEXTURE_2D, ($demo{t}{$k}{name} or die);
1196     glDrawElements GL_TRIANGLES, $v->[0], GL_UNSIGNED_SHORT, $v->[1];
1197     }
1198    
1199     glPopMatrix;
1200    
1201     glShadeModel GL_FLAT;
1202     glDisable GL_DEPTH_TEST;
1203     glDisable GL_TEXTURE_2D;
1204     glDepthMask 0;
1205    
1206     $WANT_REFRESH++;
1207     };
1208    
1209     CFClient::SDL_GL_SwapBuffers;
1210     }
1211    
1212     my $refresh_watcher = Event->timer (after => 0, hard => 1, interval => 1 / $MAX_FPS, cb => sub {
1213     $NOW = time;
1214    
1215     ($SDL_CB{$_->{type}} || sub { warn "unhandled event $_->{type}" })->($_)
1216     for CFClient::SDL_PollEvent;
1217    
1218     if (%animate_object) {
1219     $_->animate ($LAST_REFRESH - $NOW) for values %animate_object;
1220     $WANT_REFRESH++;
1221     }
1222    
1223     if ($WANT_REFRESH) {
1224     force_refresh;
1225     } else {
1226     $CAN_REFRESH = 1;
1227     }
1228     });
1229    
1230     sub animation_start {
1231     my ($widget) = @_;
1232     $animate_object{$widget} = $widget;
1233     }
1234    
1235     sub animation_stop {
1236     my ($widget) = @_;
1237     delete $animate_object{$widget};
1238     }
1239    
1240     @conn::ISA = Crossfire::Protocol::;
1241    
1242     sub conn::new {
1243     my $class = shift;
1244    
1245     my $self = $class->Crossfire::Protocol::new (@_);
1246    
1247     $MAPWIDGET->clr_commands;
1248    
1249     my $parser = new Pod::POM;
1250     my $pod = $parser->parse_file (CFClient::find_rcfile "pod/command_help.pod");
1251    
1252     for my $head2 ($pod->head2) {
1253     $head2->title =~ /^(\S+) (?:\s+ \( ([^\)]*) \) )?/x
1254     or next;
1255    
1256     my $cmd = $1;
1257     my @args = split /\|/, $2;
1258     @args = (".*") unless @args;
1259    
1260     my $text = CFClient::pod_to_pango $head2->content;
1261    
1262     for my $arg (@args) {
1263     $arg = $arg eq ".*" ? "" : " $arg";
1264    
1265     $MAPWIDGET->add_command ("$cmd$arg", $text);
1266     }
1267     }
1268    
1269     $self->{noface} = new_from_file CFClient::Texture
1270     CFClient::find_rcfile "noface.png", minify => 1, mipmap => 1;
1271    
1272     $self
1273     }
1274    
1275     sub conn::stats_update {
1276     my ($self, $stats) = @_;
1277    
1278     if (my $exp = $stats->{Crossfire::Protocol::CS_STAT_EXP64}) {
1279     my $diff = $exp - $self->{prev_exp};
1280     $STATUSBOX->add ("$diff experience gained", group => "experience $diff", fg => [0.5, 1, 0.5, 0.8], timeout => 5)
1281     if exists $self->{prev_exp} && $diff;
1282     $self->{prev_exp} = $exp;
1283     }
1284    
1285     update_stats_window ($stats);
1286     }
1287    
1288     sub conn::user_send {
1289     my ($self, $command) = @_;
1290    
1291     $self->send_command ($command);
1292     status $command;
1293     }
1294    
1295     sub conn::map_scroll {
1296     my ($self, $dx, $dy) = @_;
1297    
1298     $MAP->scroll ($dx, $dy);
1299     }
1300    
1301     sub conn::feed_map1a {
1302     my ($self, $data) = @_;
1303    
1304     # $self->Crossfire::Protocol::feed_map1a ($data);
1305    
1306     $MAP->map1a_update ($data);
1307     $MAPWIDGET->update;
1308     }
1309    
1310     sub conn::flush_map {
1311     my ($self) = @_;
1312    
1313     my $map_info = delete $self->{map_info}
1314     or return;
1315    
1316     my ($hash, $x, $y, $w, $h) = @$map_info;
1317    
1318     my $data = $MAP->get_rect ($x, $y, $w, $h);
1319     $MAPCACHE->put ($hash => Compress::LZF::compress $data);
1320     #warn sprintf "SAVEmap[%s] length %d\n", $hash, length $data;#d#
1321     }
1322    
1323     sub conn::map_clear {
1324     my ($self) = @_;
1325    
1326     $self->flush_map;
1327     delete $self->{neigh_map};
1328    
1329     $MAP->clear;
1330     }
1331    
1332    
1333     sub conn::load_map($$$) {
1334     my ($self, $hash, $x, $y) = @_;
1335    
1336     if (defined (my $data = $MAPCACHE->get ($hash))) {
1337     $data = Compress::LZF::decompress $data;
1338     #warn sprintf "LOADmap[%s,%d,%d] length %d\n", $hash, $x, $y, length $data;#d#
1339     for my $id ($MAP->set_rect ($x, $y, $data)) {
1340     my $data = $TILECACHE->get ($id)
1341     or next;
1342    
1343     $self->set_texture ($id => $data);
1344     }
1345     }
1346     }
1347    
1348     # hardcode /world/world_xxx_xxx map names, the savings are enourmous,
1349     # (server resource,s latency, bandwidth), so this hack is warranted.
1350     # the right fix is to make real tiled maps with an overview file
1351     sub conn::send_mapinfo {
1352     my ($self, $data, $cb) = @_;
1353    
1354     if ($self->{map_info}[0] =~ m%^/world/world_(\d\d\d)_(\d\d\d)$%) {
1355     my ($wx, $wy) = ($1, $2);
1356    
1357     if ($data =~ /^spatial ([1-4]+)$/) {
1358     my @dx = (0, 0, 1, 0, -1);
1359     my @dy = (0, -1, 0, 1, 0);
1360     my ($dx, $dy);
1361    
1362     for (split //, $1) {
1363     $dx += $dx[$_];
1364     $dy += $dy[$_];
1365     }
1366    
1367     $cb->(spatial => 15,
1368     $self->{map_info}[1] - $MAP->ox + $dx * 50,
1369     $self->{map_info}[2] - $MAP->oy + $dy * 50,
1370     50, 50,
1371     sprintf "/world/world_%03d_%03d", $wx + $dx, $wy + $dy
1372     );
1373    
1374     return;
1375     }
1376     }
1377    
1378     $self->Crossfire::Protocol::send_mapinfo ($data, $cb);
1379     }
1380    
1381     # this method does a "flood fill" into every tile direction
1382     # it assumes that tiles are arranged in a rectangular grid,
1383     # i.e. a map is the same as the left of the right map etc.
1384     # failure to comply are harmless and result in display errors
1385     # at worst.
1386     sub conn::flood_fill {
1387     my ($self, $block, $gx, $gy, $path, $hash, $flags) = @_;
1388    
1389     # the server does not allow map paths > 6
1390     return if 7 <= length $path;
1391    
1392     my ($x0, $y0, $x1, $y1) = @{$self->{neigh_rect}};
1393    
1394     for (
1395     [1, 3, 0, -1],
1396     [2, 4, 1, 0],
1397     [3, 1, 0, 1],
1398     [4, 2, -1, 0],
1399     ) {
1400     my ($tile, $tile2, $dx, $dy) = @$_;
1401    
1402     next if $block & (1 << $tile);
1403     my $block = $block | (1 << $tile2);
1404    
1405     my $gx = $gx + $dx;
1406     my $gy = $gy + $dy;
1407    
1408     next unless $flags & (1 << ($tile - 1));
1409     next if $self->{neigh_grid}{$gx, $gy}++;
1410    
1411     my $neigh = $self->{neigh_map}{$hash} ||= [];
1412     if (my $info = $neigh->[$tile]) {
1413     my ($flags, $x, $y, $w, $h, $hash) = @$info;
1414    
1415     $self->flood_fill ($block, $gx, $gy, "$path$tile", $hash, $flags)
1416     if $x >= $x0 && $x + $w < $x1 && $y >= $y0 && $y + $h < $y1;
1417    
1418     } else {
1419     $self->send_mapinfo ("spatial $path$tile", sub {
1420     my ($mode, $flags, $x, $y, $w, $h, $hash) = @_;
1421    
1422     return if $mode ne "spatial";
1423    
1424     $x += $MAP->ox;
1425     $y += $MAP->oy;
1426    
1427     $self->load_map ($hash, $x, $y)
1428     unless $self->{neigh_map}{$hash}[5]++;#d#
1429    
1430     $neigh->[$tile] = [$flags, $x, $y, $w, $h, $hash];
1431    
1432     $self->flood_fill ($block, $gx, $gy, "$path$tile", $hash, $flags)
1433     if $x >= $x0 && $x + $w < $x1 && $y >= $y0 && $y + $h < $y1;
1434     });
1435     }
1436     }
1437     }
1438    
1439     sub conn::map_change {
1440     my ($self, $mode, $flags, $x, $y, $w, $h, $hash) = @_;
1441    
1442     $self->flush_map;
1443    
1444     my ($ox, $oy) = ($::MAP->ox, $::MAP->oy);
1445    
1446     my $mapmapw = $MAPMAP->{w};
1447     my $mapmaph = $MAPMAP->{h};
1448    
1449     $self->{neigh_rect} = [
1450     $ox - $mapmapw * 0.5, $oy - $mapmapw * 0.5,
1451     $ox + $mapmapw * 0.5 + $w, $oy + $mapmapw * 0.5 + $h,
1452     ];
1453    
1454     delete $self->{neigh_grid};
1455    
1456     $x += $ox;
1457     $y += $oy;
1458    
1459     $self->{map_info} = [$hash, $x, $y, $w, $h];
1460    
1461     (my $map = $hash) =~ s/^.*?\/([^\/]+)$/\1/;
1462     $STATWIDS->{map}->set_text ("Map: " . $map);
1463    
1464     $self->load_map ($hash, $x, $y);
1465     $self->flood_fill (0, 0, 0, "", $hash, $flags);
1466     }
1467    
1468     sub conn::face_find {
1469     my ($self, $facenum, $face) = @_;
1470    
1471     my $hash = "$face->{chksum},$face->{name}";
1472    
1473     my $id = $FACEMAP->get ($hash);
1474    
1475     unless ($id) {
1476     # create new id for face
1477     # I love transactions
1478     for (1..100) {
1479     my $txn = $CFClient::DB_ENV->txn_begin;
1480     my $status = $FACEMAP->db_get (id => $id, BerkeleyDB::DB_RMW);
1481     if ($status == 0 || $status == BerkeleyDB::DB_NOTFOUND) {
1482     $id = ($id || 16) + 1;
1483     if ($FACEMAP->put (id => $id) == 0
1484     && $FACEMAP->put ($hash => $id) == 0) {
1485     $txn->txn_commit;
1486    
1487     goto gotid;
1488     }
1489     }
1490     $txn->abort;
1491     }
1492    
1493     CFClient::fatal "maximum number of transaction retries reached - database problems?";
1494     }
1495    
1496     gotid:
1497     $face->{id} = $id;
1498     $MAP->set_face ($facenum => $id);
1499     $self->{faceid}[$facenum] = $id;#d#
1500    
1501     my $face = $TILECACHE->get ($id);
1502    
1503     if ($face) {
1504     #$self->face_prefetch;
1505     $face
1506     } else {
1507     my $tex = $self->{noface};
1508     $MAP->set_texture ($id, @$tex{qw(name w h s t)}, @{$tex->{minified}});
1509     undef
1510     };
1511     }
1512    
1513     sub conn::face_update {
1514     my ($self, $facenum, $face) = @_;
1515    
1516     $TILECACHE->put ($face->{id} => $face->{image}); #TODO: try to avoid duplicate writes
1517    
1518     $self->set_texture ($face->{id} => delete $face->{image});
1519     }
1520    
1521     sub conn::set_texture {
1522     my ($self, $id, $data) = @_;
1523    
1524     $self->{texture}[$id] ||= do {
1525     my $tex =
1526     new_from_image CFClient::Texture
1527     $data, minify => 1, mipmap => 1;
1528    
1529     $MAP->set_texture ($id, @$tex{qw(name w h s t)}, @{$tex->{minified}});
1530     $MAPWIDGET->update;
1531    
1532     $tex
1533     };
1534     }
1535    
1536     sub conn::sound_play {
1537     my ($self, $x, $y, $soundnum, $type) = @_;
1538    
1539     $SDL_MIXER
1540     or return;
1541    
1542     my $chunk = $AUDIO_CHUNKS{$SOUNDS[$soundnum]}
1543     or return;
1544    
1545     $chunk->play;
1546     # warn "sound $x,$y,$soundnum,$type\n";#d#
1547     }
1548    
1549     my $LAST_QUERY; # server is stupid, stupid, stupid
1550    
1551     sub conn::query {
1552     my ($self, $flags, $prompt) = @_;
1553    
1554     $prompt = $LAST_QUERY unless length $prompt;
1555     $LAST_QUERY = $prompt;
1556    
1557     my $dialog = new CFClient::UI::FancyFrame
1558     title => "Query",
1559     child => my $vbox = new CFClient::UI::VBox;
1560    
1561     $vbox->add (new CFClient::UI::Label
1562     max_w => $::WIDTH * 0.4,
1563     ellipsise => 0,
1564     text => $prompt);
1565    
1566     if ($flags & Crossfire::Protocol::CS_QUERY_YESNO) {
1567     $vbox->add (my $hbox = new CFClient::HBox);
1568     $hbox->add (new CFClient::Button
1569     text => "No",
1570     connect_activate => sub {
1571     $self->send ("reply n");
1572     $dialog->destroy;
1573     $MAPWIDGET->focus_in;
1574     }
1575     );
1576     $hbox->add (new CFClient::Button
1577     text => "Yes",
1578     connect_activate => sub {
1579     $self->send ("reply y");
1580     $dialog->destroy;
1581     },
1582     );
1583    
1584     $dialog->focus_in;
1585    
1586     } elsif ($flags & Crossfire::Protocol::CS_QUERY_SINGLECHAR) {
1587     $dialog->{tooltip} = "Press a key (click on the entry to make sure it has keyboard focus)";
1588     $vbox->add (my $entry = new CFClient::UI::Entry
1589     connect_changed => sub {
1590     $self->send ("reply $_[1]");
1591     $dialog->destroy;
1592     },
1593     );
1594    
1595     $entry->focus_in;
1596    
1597     } else {
1598     $dialog->{tooltip} = "Enter the reply and press return (click on the entry to make sure it has keyboard focus)";
1599    
1600     $vbox->add (my $entry = new CFClient::UI::Entry
1601     $flags & Crossfire::Protocol::CS_QUERY_HIDEINPUT ? (hiddenchar => "*") : (),
1602     connect_activate => sub {
1603     $self->send ("reply $_[1]");
1604     $dialog->destroy;
1605     },
1606     );
1607    
1608     $entry->focus_in;
1609     }
1610    
1611     $dialog->show_centered;
1612     }
1613    
1614     sub conn::drawinfo {
1615     my ($self, $color, $text) = @_;
1616    
1617     my @color = (
1618     [1.00, 1.00, 1.00], #[0.00, 0.00, 0.00],
1619     [1.00, 1.00, 1.00],
1620     [0.50, 0.50, 1.00], #[0.00, 0.00, 0.55]
1621     [1.00, 0.00, 0.00],
1622     [1.00, 0.54, 0.00],
1623     [0.11, 0.56, 1.00],
1624     [0.93, 0.46, 0.00],
1625     [0.18, 0.54, 0.34],
1626     [0.56, 0.73, 0.56],
1627     [0.80, 0.80, 0.80],
1628     [0.55, 0.41, 0.13],
1629     [0.99, 0.77, 0.26],
1630     [0.74, 0.65, 0.41],
1631     );
1632    
1633     my $time = sprintf "%02d:%02d:%02d", (localtime time)[2,1,0];
1634    
1635     $text = CFClient::UI::Label::escape $text;
1636     $text =~ s/\[b\](.*?)\[\/b\]/<b>\1<\/b>/g;
1637     $text =~ s/\[color=(.*?)\](.*?)\[\/color\]/<span foreground='\1'>\2<\/span>/g;
1638    
1639     $LOGVIEW->add_paragraph ($color[$color],
1640     join "\n", map "$time $_", split /\n/, $text);
1641    
1642     $STATUSBOX->add ($text,
1643     group => $text,
1644     fg => $color[$color],
1645     timeout => 10,
1646     tooltip_font => $::FONT_FIXED,
1647     );
1648     }
1649    
1650     sub conn::drawextinfo {
1651     my ($self, $color, $type, $subtype, $message) = @_;
1652    
1653     $self->drawinfo ($color, $message);
1654     }
1655    
1656     sub conn::spell_add {
1657     my ($self, $spell) = @_;
1658    
1659     # TODO
1660     # create a widget dynamically, using spell face (CF::Protocol downloads them)
1661     $MAPWIDGET->add_command ("invoke $spell->{name}", CFClient::UI::Label::escape $spell->{message});
1662     $MAPWIDGET->add_command ("cast $spell->{name}", CFClient::UI::Label::escape $spell->{message});
1663     }
1664    
1665     sub conn::spell_delete {
1666     my ($self, $spell) = @_;
1667     }
1668    
1669     sub conn::addme_success {
1670     my ($self) = @_;
1671    
1672     $self->send ("command output-sync $CFG->{output_sync}");
1673     $self->send ("command output-count $CFG->{output_count}");
1674    
1675     my $parser = new Pod::POM;
1676     my $pod = $parser->parse_file (CFClient::find_rcfile "pod/skill_help.pod");
1677    
1678     my %skill_tooltip;
1679    
1680     for my $head2 ($pod->head2) {
1681     $skill_tooltip{$head2->title} = CFClient::pod_to_pango $head2->content;
1682     }
1683    
1684     for my $skill (values %{$self->{skill_info}}) {
1685     $MAPWIDGET->add_command ("ready_skill $skill",
1686     (CFClient::UI::Label::escape "Ready the skill '$skill'\n\n")
1687     . $skill_tooltip{$skill});
1688     $MAPWIDGET->add_command ("use_skill $skill",
1689     (CFClient::UI::Label::escape "Immediately use the skill '$skill'\n\n")
1690     . $skill_tooltip{$skill});
1691     }
1692     }
1693    
1694     sub conn::eof {
1695     $MAPWIDGET->clr_commands;
1696    
1697     stop_game;
1698     }
1699    
1700     sub conn::image_info {
1701     my ($self, $numfaces) = @_;
1702    
1703     $self->{num_faces} = $numfaces;
1704     $self->{face_prefetch} = [1 .. $numfaces];
1705     $self->face_prefetch;
1706     }
1707    
1708     sub conn::face_prefetch {
1709     my ($self) = @_;
1710    
1711     return unless $CFG->{face_prefetch};
1712    
1713     if ($self->{num_faces}) {
1714     return if @{ $self->{send_queue} || [] };
1715     my $todo = @{ $self->{face_prefetch} }
1716     or return;
1717    
1718     my ($face) = splice @{ $self->{face_prefetch} }, + rand @{ $self->{face_prefetch} }, 1, ();
1719    
1720     $self->send ("requestinfo image_sums $face $face");
1721    
1722     $STATUSBOX->add (CFClient::UI::Label::escape "prefetching $todo",
1723     group => "prefetch", timeout => 2, fg => [1, 1, 0, 0.5]);
1724     } elsif (!exists $self->{num_faces}) {
1725     $self->send ("requestinfo image_info");
1726    
1727     $self->{num_faces} = 0;
1728    
1729     $STATUSBOX->add (CFClient::UI::Label::escape "starting to prefetch",
1730     group => "prefetch", timeout => 2, fg => [1, 1, 0, 0.5]);
1731     }
1732     }
1733    
1734     # check once/second for faces that need to be prefetched
1735     # this should, of course, only run on demand, but
1736     # SDL forces worse things on us....
1737    
1738     Event->timer (after => 1, interval => 0.25, cb => sub {
1739     $CONN->face_prefetch
1740     if $CONN;
1741     });
1742    
1743     sub update_floorbox {
1744     $CFClient::UI::ROOT->on_refresh ($FLOORBOX => sub {
1745     return unless $CONN;
1746    
1747     $FLOORBOX->clear;
1748 root 1.2 $FLOORBOX->add (0, 1, new CFClient::UI::Empty expand => 1);
1749 root 1.1
1750 root 1.2 my $row;
1751 root 1.1 for (@{ $CONN->{container}{0} }) {
1752 root 1.2 if (++$row < 7) {
1753 root 1.3 local $_->{face_widget}; # hack to force recreation of widget
1754     local $_->{desc_widget}; # hack to force recreation of widget
1755 root 1.2 CFClient::Item::update_widgets $_;
1756 root 1.3
1757 root 1.2 $FLOORBOX->add (0, $row, $_->{face_widget});
1758     $FLOORBOX->add (1, $row, $_->{desc_widget});
1759 root 1.1 } else {
1760     $FLOORBOX->add (new CFClient::UI::Label text => "More...");
1761     last;
1762     }
1763     }
1764     });
1765    
1766     $WANT_REFRESH++;
1767     }
1768    
1769     sub conn::container_add {
1770     my ($self, $tag, $items) = @_;
1771    
1772     #d# print "container_add: container $tag ($self->{player}{tag})\n";
1773    
1774     if ($tag == 0) {
1775     update_floorbox;
1776     $OPENCONT = 0;
1777     $INVR_LBL->set_text ("Floor");
1778     $INVR->set_items ($self->{container}{0});
1779     } elsif ($tag == $self->{player}{tag}) {
1780     $INVR_LBL->set_text ("Player");
1781     $INV->set_items ($self->{container}{$self->{player}{tag}})
1782     } else {
1783     $OPENCONT = $tag;
1784     $INVR_LBL->set_text (CFClient::UI::InventoryItem::_item_to_desc ($self->{item}->{$OPENCONT}));
1785     $INVR->set_items ($self->{container}{$tag});
1786     }
1787    
1788     # $self-<{player}{tag} => player inv
1789     #use PApp::Util; warn PApp::Util::dumpval $self->{container}{$self->{player}{tag}};
1790     }
1791    
1792     sub conn::container_clear {
1793     my ($self, $tag) = @_;
1794    
1795     #d# print "container_clear: container $tag ($self->{player}{tag})\n";
1796    
1797     if ($tag == 0) {
1798     update_floorbox;
1799     $OPENCONT = 0;
1800     $INVR_LBL->set_text ("Floor");
1801     $INVR->set_items ($self->{container}{0});
1802     } elsif ($tag == $self->{player}{tag}) {
1803     $INVR_LBL->set_text ("Player");
1804     $INV->set_items ($self->{container}{$tag})
1805     } else {
1806     $OPENCONT = $tag;
1807     $INVR_LBL->set_text (CFClient::UI::InventoryItem::_item_to_desc ($self->{item}->{$OPENCONT}));
1808     $INVR->set_items ($self->{container}{$tag});
1809     }
1810    
1811     # use PApp::Util; warn PApp::Util::dumpval $self->{container}{0};
1812     }
1813    
1814     sub conn::item_delete {
1815     my ($self, @items) = @_;
1816    
1817     for (@items) {
1818     #d# print "item_delete: $_->{tag} from $_->{container} ($self->{player}{tag})\n";
1819    
1820     if ($_->{container} == 0) {
1821     update_floorbox;
1822     $OPENCONT = 0;
1823     $INVR_LBL->set_text ("Floor");
1824     $INVR->set_items ($self->{container}{0});
1825     } elsif ($_->{container} == $self->{player}{tag}) {
1826     $INVR_LBL->set_text ("Player");
1827     $INV->set_items ($self->{container}{$self->{player}{tag}})
1828     } else {
1829     $OPENCONT = $_->{container};
1830     $INVR_LBL->set_text (CFClient::UI::InventoryItem::_item_to_desc ($self->{item}->{$OPENCONT}));
1831     $INVR->set_items ($self->{container}{$_->{container}});
1832     }
1833     }
1834     }
1835    
1836     sub conn::item_update {
1837     my ($self, $item) = @_;
1838    
1839     #d# print "item_update: $item->{tag} in $item->{container} ($self->{player}{tag}) ($OPENCONT)\n";
1840    
1841     if ($item->{tag} == $OPENCONT && not ($item->{flags} & Crossfire::Protocol::F_OPEN)) {
1842     $OPENCONT = 0;
1843     $INVR_LBL->set_text ("Floor");
1844     $INVR->set_items ($self->{container}{0});
1845    
1846     $item->{widget}->update_item
1847     if $item->{widget};
1848     } else {
1849     if ($item->{container} == 0) {
1850     update_floorbox;
1851     $OPENCONT = 0;
1852     $INVR_LBL->set_text ("Floor");
1853     $INVR->set_items ($self->{container}{0});
1854     } elsif ($item->{container} == $self->{player}{tag}) {
1855     $INV->set_items ($self->{container}{$item->{container}})
1856     }
1857     }
1858     }
1859    
1860     %SDL_CB = (
1861     CFClient::SDL_QUIT => sub {
1862     Event::unloop -1;
1863     },
1864     CFClient::SDL_VIDEORESIZE => sub {
1865     },
1866     CFClient::SDL_VIDEOEXPOSE => sub {
1867     CFClient::UI::full_refresh;
1868     },
1869     CFClient::SDL_ACTIVEEVENT => sub {
1870     # printf "active %x %x\n", $SDL_EV->active_gain, $SDL_EV->active_state;#d#
1871     },
1872     CFClient::SDL_KEYDOWN => sub {
1873     if ($_[0]{mod} & CFClient::KMOD_ALT && $_[0]{sym} == 13) {
1874     # alt-enter
1875     video_shutdown;
1876     $CFG->{fullscreen} = !$CFG->{fullscreen};
1877     video_init;
1878     } else {
1879     CFClient::UI::feed_sdl_key_down_event ($_[0]);
1880     }
1881     },
1882     CFClient::SDL_KEYUP => \&CFClient::UI::feed_sdl_key_up_event,
1883     CFClient::SDL_MOUSEMOTION => \&CFClient::UI::feed_sdl_motion_event,
1884     CFClient::SDL_MOUSEBUTTONDOWN => \&CFClient::UI::feed_sdl_button_down_event,
1885     CFClient::SDL_MOUSEBUTTONUP => \&CFClient::UI::feed_sdl_button_up_event,
1886     CFClient::SDL_USEREVENT => sub {
1887     if ($_[0]{code} == 1) {
1888     audio_channel_finished $_[0]{data1};
1889     } elsif ($_[0]{code} == 0) {
1890     audio_music_finished;
1891     }
1892     },
1893     );
1894    
1895     #############################################################################
1896    
1897     $SIG{INT} = $SIG{TERM} = sub { exit };
1898    
1899     {
1900     local $SIG{__DIE__} = sub { CFClient::fatal $_[0] };
1901    
1902     CFClient::read_cfg "$Crossfire::VARDIR/pclientrc";
1903    
1904     $TILECACHE = CFClient::db_table "tilecache";
1905     $FACEMAP = CFClient::db_table "facemap";
1906    
1907     my %DEF_CFG = (
1908     sdl_mode => 0,
1909     width => 640,
1910     height => 480,
1911     fullscreen => 0,
1912     fast => 0,
1913     map_scale => 1,
1914     fow_enable => 1,
1915     fow_intensity => 0.45,
1916     fow_smooth => 0,
1917     gui_fontsize => 1,
1918     log_fontsize => 1,
1919     gauge_fontsize=> 1,
1920     gauge_size => 0.35,
1921     stat_fontsize => 1,
1922     mapsize => 100,
1923     host => "crossfire.schmorp.de",
1924     say_command => 'say',
1925     audio_enable => 1,
1926     bgm_enable => 1,
1927     bgm_volume => 0.25,
1928     face_prefetch => 0,
1929     output_sync => 1,
1930     output_count => 1,
1931     );
1932    
1933     while (my ($k, $v) = each %DEF_CFG) {
1934     $CFG->{$k} = $v unless exists $CFG->{$k};
1935     }
1936    
1937     sdl_init;
1938    
1939     @SDL_MODES = reverse
1940     grep $_->[0] >= 640 && $_->[1] >= 480,
1941     CFClient::SDL_ListModes;
1942    
1943     @SDL_MODES or CFClient::fatal "Unable to find a usable video mode\n(hardware accelerated opengl fullscreen)";
1944    
1945     $CFG->{sdl_mode} = 0 if $CFG->{sdl_mode} > @SDL_MODES;
1946    
1947     {
1948     my @fonts = map CFClient::find_rcfile "fonts/$_", qw(
1949     DejaVuSans.ttf
1950     DejaVuSansMono.ttf
1951     DejaVuSans-Bold.ttf
1952     DejaVuSansMono-Bold.ttf
1953     DejaVuSans-Oblique.ttf
1954     DejaVuSansMono-Oblique.ttf
1955     DejaVuSans-BoldOblique.ttf
1956     DejaVuSansMono-BoldOblique.ttf
1957     );
1958    
1959     CFClient::add_font $_ for @fonts;
1960    
1961     CFClient::pango_init;
1962    
1963     $FONT_PROP = new_from_file CFClient::Font $fonts[0];
1964     $FONT_FIXED = new_from_file CFClient::Font $fonts[1];
1965    
1966     $FONT_PROP->make_default;
1967     }
1968    
1969     # compare mono (ft) vs. rgba (cairo)
1970     # ft - 1.8s, cairo 3s, even in alpha-only mode
1971     # for my $rgba (0..1) {
1972     # my $t1 = Time::HiRes::time;
1973     # for (1..1000) {
1974     # my $layout = CFClient::Layout->new ($rgba);
1975     # $layout->set_text ("hallo" x 100);
1976     # $layout->render;
1977     # }
1978     # my $t2 = Time::HiRes::time;
1979     # warn $t2-$t1;
1980     # }
1981    
1982     video_init;
1983     audio_init;
1984     }
1985    
1986     Event::loop;
1987    
1988     END { CFClient::SDL_Quit }
1989    
1990     =head1 NAME
1991    
1992     pclient - A Crossfire+ and Crossfire game client
1993    
1994     =head1 SYNOPSIS
1995    
1996     Just run it - no commandline arguments are supported.
1997    
1998     =head1 USAGE
1999    
2000     Pclient utilises OpenGL for all UI elements and the game. It is supposed to be used
2001     fullscreen and interactively.
2002    
2003     =head1 AUTHOR
2004    
2005     Marc Lehmann <crossfire@schmorp.de>, Robin Redeker <elmex@ta-sa.org>
2006    
2007    
2008