ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.6
Committed: Thu May 25 21:24:40 2006 UTC (18 years ago) by elmex
Branch: MAIN
Changes since 1.5: +4 -0 lines
Log Message:
fixed a bug in the inventory code

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