ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.10
Committed: Fri May 26 18:28:23 2006 UTC (18 years ago) by root
Branch: MAIN
Changes since 1.9: +2 -1 lines
Log Message:
unbundled CFClient::Texture

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