ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra-Client/bin/cfplus
Revision: 1.5
Committed: Thu May 25 18:48:45 2006 UTC (18 years ago) by elmex
Branch: MAIN
Changes since 1.4: +40 -33 lines
Log Message:
improved container handling

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     $self
1283     }
1284    
1285     sub conn::stats_update {
1286     my ($self, $stats) = @_;
1287    
1288     if (my $exp = $stats->{Crossfire::Protocol::CS_STAT_EXP64}) {
1289     my $diff = $exp - $self->{prev_exp};
1290     $STATUSBOX->add ("$diff experience gained", group => "experience $diff", fg => [0.5, 1, 0.5, 0.8], timeout => 5)
1291     if exists $self->{prev_exp} && $diff;
1292     $self->{prev_exp} = $exp;
1293     }
1294    
1295     update_stats_window ($stats);
1296     }
1297    
1298     sub conn::user_send {
1299     my ($self, $command) = @_;
1300    
1301     $self->send_command ($command);
1302     status $command;
1303     }
1304    
1305     sub conn::map_scroll {
1306     my ($self, $dx, $dy) = @_;
1307    
1308     $MAP->scroll ($dx, $dy);
1309     }
1310    
1311     sub conn::feed_map1a {
1312     my ($self, $data) = @_;
1313    
1314     # $self->Crossfire::Protocol::feed_map1a ($data);
1315    
1316     $MAP->map1a_update ($data);
1317     $MAPWIDGET->update;
1318     }
1319    
1320     sub conn::flush_map {
1321     my ($self) = @_;
1322    
1323     my $map_info = delete $self->{map_info}
1324     or return;
1325    
1326     my ($hash, $x, $y, $w, $h) = @$map_info;
1327    
1328     my $data = $MAP->get_rect ($x, $y, $w, $h);
1329     $MAPCACHE->put ($hash => Compress::LZF::compress $data);
1330     #warn sprintf "SAVEmap[%s] length %d\n", $hash, length $data;#d#
1331     }
1332    
1333     sub conn::map_clear {
1334     my ($self) = @_;
1335    
1336     $self->flush_map;
1337     delete $self->{neigh_map};
1338    
1339     $MAP->clear;
1340     }
1341    
1342    
1343     sub conn::load_map($$$) {
1344     my ($self, $hash, $x, $y) = @_;
1345    
1346     if (defined (my $data = $MAPCACHE->get ($hash))) {
1347     $data = Compress::LZF::decompress $data;
1348     #warn sprintf "LOADmap[%s,%d,%d] length %d\n", $hash, $x, $y, length $data;#d#
1349     for my $id ($MAP->set_rect ($x, $y, $data)) {
1350     my $data = $TILECACHE->get ($id)
1351     or next;
1352    
1353     $self->set_texture ($id => $data);
1354     }
1355     }
1356     }
1357    
1358     # hardcode /world/world_xxx_xxx map names, the savings are enourmous,
1359     # (server resource,s latency, bandwidth), so this hack is warranted.
1360     # the right fix is to make real tiled maps with an overview file
1361     sub conn::send_mapinfo {
1362     my ($self, $data, $cb) = @_;
1363    
1364     if ($self->{map_info}[0] =~ m%^/world/world_(\d\d\d)_(\d\d\d)$%) {
1365     my ($wx, $wy) = ($1, $2);
1366    
1367     if ($data =~ /^spatial ([1-4]+)$/) {
1368     my @dx = (0, 0, 1, 0, -1);
1369     my @dy = (0, -1, 0, 1, 0);
1370     my ($dx, $dy);
1371    
1372     for (split //, $1) {
1373     $dx += $dx[$_];
1374     $dy += $dy[$_];
1375     }
1376    
1377     $cb->(spatial => 15,
1378     $self->{map_info}[1] - $MAP->ox + $dx * 50,
1379     $self->{map_info}[2] - $MAP->oy + $dy * 50,
1380     50, 50,
1381     sprintf "/world/world_%03d_%03d", $wx + $dx, $wy + $dy
1382     );
1383    
1384     return;
1385     }
1386     }
1387    
1388     $self->Crossfire::Protocol::send_mapinfo ($data, $cb);
1389     }
1390    
1391     # this method does a "flood fill" into every tile direction
1392     # it assumes that tiles are arranged in a rectangular grid,
1393     # i.e. a map is the same as the left of the right map etc.
1394     # failure to comply are harmless and result in display errors
1395     # at worst.
1396     sub conn::flood_fill {
1397     my ($self, $block, $gx, $gy, $path, $hash, $flags) = @_;
1398    
1399     # the server does not allow map paths > 6
1400     return if 7 <= length $path;
1401    
1402     my ($x0, $y0, $x1, $y1) = @{$self->{neigh_rect}};
1403    
1404     for (
1405     [1, 3, 0, -1],
1406     [2, 4, 1, 0],
1407     [3, 1, 0, 1],
1408     [4, 2, -1, 0],
1409     ) {
1410     my ($tile, $tile2, $dx, $dy) = @$_;
1411    
1412     next if $block & (1 << $tile);
1413     my $block = $block | (1 << $tile2);
1414    
1415     my $gx = $gx + $dx;
1416     my $gy = $gy + $dy;
1417    
1418     next unless $flags & (1 << ($tile - 1));
1419     next if $self->{neigh_grid}{$gx, $gy}++;
1420    
1421     my $neigh = $self->{neigh_map}{$hash} ||= [];
1422     if (my $info = $neigh->[$tile]) {
1423     my ($flags, $x, $y, $w, $h, $hash) = @$info;
1424    
1425     $self->flood_fill ($block, $gx, $gy, "$path$tile", $hash, $flags)
1426     if $x >= $x0 && $x + $w < $x1 && $y >= $y0 && $y + $h < $y1;
1427    
1428     } else {
1429     $self->send_mapinfo ("spatial $path$tile", sub {
1430     my ($mode, $flags, $x, $y, $w, $h, $hash) = @_;
1431    
1432     return if $mode ne "spatial";
1433    
1434     $x += $MAP->ox;
1435     $y += $MAP->oy;
1436    
1437     $self->load_map ($hash, $x, $y)
1438     unless $self->{neigh_map}{$hash}[5]++;#d#
1439    
1440     $neigh->[$tile] = [$flags, $x, $y, $w, $h, $hash];
1441    
1442     $self->flood_fill ($block, $gx, $gy, "$path$tile", $hash, $flags)
1443     if $x >= $x0 && $x + $w < $x1 && $y >= $y0 && $y + $h < $y1;
1444     });
1445     }
1446     }
1447     }
1448    
1449     sub conn::map_change {
1450     my ($self, $mode, $flags, $x, $y, $w, $h, $hash) = @_;
1451    
1452     $self->flush_map;
1453    
1454     my ($ox, $oy) = ($::MAP->ox, $::MAP->oy);
1455    
1456     my $mapmapw = $MAPMAP->{w};
1457     my $mapmaph = $MAPMAP->{h};
1458    
1459     $self->{neigh_rect} = [
1460     $ox - $mapmapw * 0.5, $oy - $mapmapw * 0.5,
1461     $ox + $mapmapw * 0.5 + $w, $oy + $mapmapw * 0.5 + $h,
1462     ];
1463    
1464     delete $self->{neigh_grid};
1465    
1466     $x += $ox;
1467     $y += $oy;
1468    
1469     $self->{map_info} = [$hash, $x, $y, $w, $h];
1470    
1471     (my $map = $hash) =~ s/^.*?\/([^\/]+)$/\1/;
1472     $STATWIDS->{map}->set_text ("Map: " . $map);
1473    
1474     $self->load_map ($hash, $x, $y);
1475     $self->flood_fill (0, 0, 0, "", $hash, $flags);
1476     }
1477    
1478     sub conn::face_find {
1479     my ($self, $facenum, $face) = @_;
1480    
1481     my $hash = "$face->{chksum},$face->{name}";
1482    
1483     my $id = $FACEMAP->get ($hash);
1484    
1485     unless ($id) {
1486     # create new id for face
1487     # I love transactions
1488     for (1..100) {
1489     my $txn = $CFClient::DB_ENV->txn_begin;
1490     my $status = $FACEMAP->db_get (id => $id, BerkeleyDB::DB_RMW);
1491     if ($status == 0 || $status == BerkeleyDB::DB_NOTFOUND) {
1492     $id = ($id || 16) + 1;
1493     if ($FACEMAP->put (id => $id) == 0
1494     && $FACEMAP->put ($hash => $id) == 0) {
1495     $txn->txn_commit;
1496    
1497     goto gotid;
1498     }
1499     }
1500     $txn->abort;
1501     }
1502    
1503     CFClient::fatal "maximum number of transaction retries reached - database problems?";
1504     }
1505    
1506     gotid:
1507     $face->{id} = $id;
1508     $MAP->set_face ($facenum => $id);
1509     $self->{faceid}[$facenum] = $id;#d#
1510    
1511     my $face = $TILECACHE->get ($id);
1512    
1513     if ($face) {
1514     #$self->face_prefetch;
1515     $face
1516     } else {
1517     my $tex = $self->{noface};
1518     $MAP->set_texture ($id, @$tex{qw(name w h s t)}, @{$tex->{minified}});
1519     undef
1520     };
1521     }
1522    
1523     sub conn::face_update {
1524     my ($self, $facenum, $face) = @_;
1525    
1526     $TILECACHE->put ($face->{id} => $face->{image}); #TODO: try to avoid duplicate writes
1527    
1528     $self->set_texture ($face->{id} => delete $face->{image});
1529     }
1530    
1531     sub conn::set_texture {
1532     my ($self, $id, $data) = @_;
1533    
1534     $self->{texture}[$id] ||= do {
1535     my $tex =
1536     new_from_image CFClient::Texture
1537     $data, minify => 1, mipmap => 1;
1538    
1539     $MAP->set_texture ($id, @$tex{qw(name w h s t)}, @{$tex->{minified}});
1540     $MAPWIDGET->update;
1541    
1542     $tex
1543     };
1544     }
1545    
1546     sub conn::sound_play {
1547     my ($self, $x, $y, $soundnum, $type) = @_;
1548    
1549     $SDL_MIXER
1550     or return;
1551    
1552     my $chunk = $AUDIO_CHUNKS{$SOUNDS[$soundnum]}
1553     or return;
1554    
1555     $chunk->play;
1556     # warn "sound $x,$y,$soundnum,$type\n";#d#
1557     }
1558    
1559     my $LAST_QUERY; # server is stupid, stupid, stupid
1560    
1561     sub conn::query {
1562     my ($self, $flags, $prompt) = @_;
1563    
1564     $prompt = $LAST_QUERY unless length $prompt;
1565     $LAST_QUERY = $prompt;
1566    
1567     my $dialog = new CFClient::UI::FancyFrame
1568     title => "Query",
1569     child => my $vbox = new CFClient::UI::VBox;
1570    
1571     $vbox->add (new CFClient::UI::Label
1572     max_w => $::WIDTH * 0.4,
1573     ellipsise => 0,
1574     text => $prompt);
1575    
1576     if ($flags & Crossfire::Protocol::CS_QUERY_YESNO) {
1577     $vbox->add (my $hbox = new CFClient::HBox);
1578     $hbox->add (new CFClient::Button
1579     text => "No",
1580     connect_activate => sub {
1581     $self->send ("reply n");
1582     $dialog->destroy;
1583     $MAPWIDGET->focus_in;
1584     }
1585     );
1586     $hbox->add (new CFClient::Button
1587     text => "Yes",
1588     connect_activate => sub {
1589     $self->send ("reply y");
1590     $dialog->destroy;
1591     },
1592     );
1593    
1594     $dialog->focus_in;
1595    
1596     } elsif ($flags & Crossfire::Protocol::CS_QUERY_SINGLECHAR) {
1597     $dialog->{tooltip} = "Press a key (click on the entry to make sure it has keyboard focus)";
1598     $vbox->add (my $entry = new CFClient::UI::Entry
1599     connect_changed => sub {
1600     $self->send ("reply $_[1]");
1601     $dialog->destroy;
1602     },
1603     );
1604    
1605     $entry->focus_in;
1606    
1607     } else {
1608     $dialog->{tooltip} = "Enter the reply and press return (click on the entry to make sure it has keyboard focus)";
1609    
1610     $vbox->add (my $entry = new CFClient::UI::Entry
1611     $flags & Crossfire::Protocol::CS_QUERY_HIDEINPUT ? (hiddenchar => "*") : (),
1612     connect_activate => sub {
1613     $self->send ("reply $_[1]");
1614     $dialog->destroy;
1615     },
1616     );
1617    
1618     $entry->focus_in;
1619     }
1620    
1621     $dialog->show_centered;
1622     }
1623    
1624     sub conn::drawinfo {
1625     my ($self, $color, $text) = @_;
1626    
1627     my @color = (
1628     [1.00, 1.00, 1.00], #[0.00, 0.00, 0.00],
1629     [1.00, 1.00, 1.00],
1630     [0.50, 0.50, 1.00], #[0.00, 0.00, 0.55]
1631     [1.00, 0.00, 0.00],
1632     [1.00, 0.54, 0.00],
1633     [0.11, 0.56, 1.00],
1634     [0.93, 0.46, 0.00],
1635     [0.18, 0.54, 0.34],
1636     [0.56, 0.73, 0.56],
1637     [0.80, 0.80, 0.80],
1638     [0.55, 0.41, 0.13],
1639     [0.99, 0.77, 0.26],
1640     [0.74, 0.65, 0.41],
1641     );
1642    
1643     my $time = sprintf "%02d:%02d:%02d", (localtime time)[2,1,0];
1644    
1645     $text = CFClient::UI::Label::escape $text;
1646     $text =~ s/\[b\](.*?)\[\/b\]/<b>\1<\/b>/g;
1647     $text =~ s/\[color=(.*?)\](.*?)\[\/color\]/<span foreground='\1'>\2<\/span>/g;
1648    
1649     $LOGVIEW->add_paragraph ($color[$color],
1650     join "\n", map "$time $_", split /\n/, $text);
1651    
1652     $STATUSBOX->add ($text,
1653     group => $text,
1654     fg => $color[$color],
1655     timeout => 10,
1656     tooltip_font => $::FONT_FIXED,
1657     );
1658     }
1659    
1660     sub conn::drawextinfo {
1661     my ($self, $color, $type, $subtype, $message) = @_;
1662    
1663     $self->drawinfo ($color, $message);
1664     }
1665    
1666     sub conn::spell_add {
1667     my ($self, $spell) = @_;
1668    
1669     # TODO
1670     # create a widget dynamically, using spell face (CF::Protocol downloads them)
1671     $MAPWIDGET->add_command ("invoke $spell->{name}", CFClient::UI::Label::escape $spell->{message});
1672     $MAPWIDGET->add_command ("cast $spell->{name}", CFClient::UI::Label::escape $spell->{message});
1673     }
1674    
1675     sub conn::spell_delete {
1676     my ($self, $spell) = @_;
1677     }
1678    
1679     sub conn::addme_success {
1680     my ($self) = @_;
1681    
1682     $self->send ("command output-sync $CFG->{output_sync}");
1683     $self->send ("command output-count $CFG->{output_count}");
1684    
1685     my $parser = new Pod::POM;
1686     my $pod = $parser->parse_file (CFClient::find_rcfile "pod/skill_help.pod");
1687    
1688     my %skill_tooltip;
1689    
1690     for my $head2 ($pod->head2) {
1691     $skill_tooltip{$head2->title} = CFClient::pod_to_pango $head2->content;
1692     }
1693    
1694     for my $skill (values %{$self->{skill_info}}) {
1695     $MAPWIDGET->add_command ("ready_skill $skill",
1696     (CFClient::UI::Label::escape "Ready the skill '$skill'\n\n")
1697     . $skill_tooltip{$skill});
1698     $MAPWIDGET->add_command ("use_skill $skill",
1699     (CFClient::UI::Label::escape "Immediately use the skill '$skill'\n\n")
1700     . $skill_tooltip{$skill});
1701     }
1702     }
1703    
1704     sub conn::eof {
1705     $MAPWIDGET->clr_commands;
1706    
1707     stop_game;
1708     }
1709    
1710     sub conn::image_info {
1711     my ($self, $numfaces) = @_;
1712    
1713     $self->{num_faces} = $numfaces;
1714     $self->{face_prefetch} = [1 .. $numfaces];
1715     $self->face_prefetch;
1716     }
1717    
1718     sub conn::face_prefetch {
1719     my ($self) = @_;
1720    
1721     return unless $CFG->{face_prefetch};
1722    
1723     if ($self->{num_faces}) {
1724     return if @{ $self->{send_queue} || [] };
1725     my $todo = @{ $self->{face_prefetch} }
1726     or return;
1727    
1728     my ($face) = splice @{ $self->{face_prefetch} }, + rand @{ $self->{face_prefetch} }, 1, ();
1729    
1730     $self->send ("requestinfo image_sums $face $face");
1731    
1732     $STATUSBOX->add (CFClient::UI::Label::escape "prefetching $todo",
1733     group => "prefetch", timeout => 2, fg => [1, 1, 0, 0.5]);
1734     } elsif (!exists $self->{num_faces}) {
1735     $self->send ("requestinfo image_info");
1736    
1737     $self->{num_faces} = 0;
1738    
1739     $STATUSBOX->add (CFClient::UI::Label::escape "starting to prefetch",
1740     group => "prefetch", timeout => 2, fg => [1, 1, 0, 0.5]);
1741     }
1742     }
1743    
1744     # check once/second for faces that need to be prefetched
1745     # this should, of course, only run on demand, but
1746     # SDL forces worse things on us....
1747    
1748     Event->timer (after => 1, interval => 0.25, cb => sub {
1749     $CONN->face_prefetch
1750     if $CONN;
1751     });
1752    
1753     sub update_floorbox {
1754     $CFClient::UI::ROOT->on_refresh ($FLOORBOX => sub {
1755     return unless $CONN;
1756    
1757     $FLOORBOX->clear;
1758 root 1.2 $FLOORBOX->add (0, 1, new CFClient::UI::Empty expand => 1);
1759 root 1.1
1760 root 1.2 my $row;
1761 root 1.1 for (@{ $CONN->{container}{0} }) {
1762 root 1.2 if (++$row < 7) {
1763 root 1.3 local $_->{face_widget}; # hack to force recreation of widget
1764     local $_->{desc_widget}; # hack to force recreation of widget
1765 root 1.2 CFClient::Item::update_widgets $_;
1766 root 1.3
1767 root 1.2 $FLOORBOX->add (0, $row, $_->{face_widget});
1768     $FLOORBOX->add (1, $row, $_->{desc_widget});
1769 root 1.1 } else {
1770     $FLOORBOX->add (new CFClient::UI::Label text => "More...");
1771     last;
1772     }
1773     }
1774     });
1775    
1776     $WANT_REFRESH++;
1777     }
1778    
1779 elmex 1.5 sub set_opencont {
1780     my ($conn, $tag, $name) = @_;
1781     $conn->{open_container} = $tag;
1782     $INVR_LBL->set_text ($name);
1783     $INVR->set_items ($conn->{container}{$tag});
1784     }
1785    
1786     sub update_container {
1787     my ($tag) = @_;
1788     $INVR->set_items ($::CONN->{container}{$CONN->{open_container}})
1789     if $tag == $CONN->{open_container};
1790     }
1791    
1792 root 1.1 sub conn::container_add {
1793     my ($self, $tag, $items) = @_;
1794    
1795     #d# print "container_add: container $tag ($self->{player}{tag})\n";
1796    
1797     if ($tag == 0) {
1798     update_floorbox;
1799 elmex 1.5 update_container (0);
1800 root 1.1 } elsif ($tag == $self->{player}{tag}) {
1801     $INV->set_items ($self->{container}{$self->{player}{tag}})
1802     } else {
1803 elmex 1.5 update_container ($tag);
1804 root 1.1 }
1805    
1806     # $self-<{player}{tag} => player inv
1807     #use PApp::Util; warn PApp::Util::dumpval $self->{container}{$self->{player}{tag}};
1808     }
1809    
1810     sub conn::container_clear {
1811     my ($self, $tag) = @_;
1812    
1813     #d# print "container_clear: container $tag ($self->{player}{tag})\n";
1814    
1815     if ($tag == 0) {
1816     update_floorbox;
1817     } elsif ($tag == $self->{player}{tag}) {
1818     $INV->set_items ($self->{container}{$tag})
1819     }
1820    
1821     # use PApp::Util; warn PApp::Util::dumpval $self->{container}{0};
1822     }
1823    
1824     sub conn::item_delete {
1825     my ($self, @items) = @_;
1826    
1827     for (@items) {
1828     #d# print "item_delete: $_->{tag} from $_->{container} ($self->{player}{tag})\n";
1829    
1830     if ($_->{container} == 0) {
1831     update_floorbox;
1832 elmex 1.5 update_container ($_->{tag});
1833 root 1.1 } elsif ($_->{container} == $self->{player}{tag}) {
1834     $INV->set_items ($self->{container}{$self->{player}{tag}})
1835     } else {
1836 elmex 1.5 update_container ($_->{tag});
1837 root 1.1 }
1838     }
1839     }
1840    
1841     sub conn::item_update {
1842     my ($self, $item) = @_;
1843    
1844 elmex 1.5 #d# print "item_update: $item->{tag} in $item->{container} ($self->{player}{tag}) ($CONN->{open_container})\n";
1845    
1846     if ($item->{tag} == $self->{player}{tag}) {
1847     $STATWIDS->{weight}->set_text (sprintf "Weight: %.1fkg", $item->{weight} / 1000);
1848     return
1849     }
1850    
1851     CFClient::Item::update_widgets $item;
1852 root 1.1
1853 elmex 1.5 if ($item->{tag} == $CONN->{open_container} && not ($item->{flags} & Crossfire::Protocol::F_OPEN)) {
1854     set_opencont ($CONN, 0, "Floor");
1855 root 1.1
1856 elmex 1.5 } elsif ($item->{flags} & Crossfire::Protocol::F_OPEN) {
1857     set_opencont ($CONN, $item->{tag}, CFClient::Item::desc_string $item);
1858 root 1.1 } else {
1859     if ($item->{container} == 0) {
1860     update_floorbox;
1861     } elsif ($item->{container} == $self->{player}{tag}) {
1862     $INV->set_items ($self->{container}{$item->{container}})
1863     }
1864     }
1865     }
1866    
1867     %SDL_CB = (
1868     CFClient::SDL_QUIT => sub {
1869     Event::unloop -1;
1870     },
1871     CFClient::SDL_VIDEORESIZE => sub {
1872     },
1873     CFClient::SDL_VIDEOEXPOSE => sub {
1874     CFClient::UI::full_refresh;
1875     },
1876     CFClient::SDL_ACTIVEEVENT => sub {
1877     # printf "active %x %x\n", $SDL_EV->active_gain, $SDL_EV->active_state;#d#
1878     },
1879     CFClient::SDL_KEYDOWN => sub {
1880     if ($_[0]{mod} & CFClient::KMOD_ALT && $_[0]{sym} == 13) {
1881     # alt-enter
1882     video_shutdown;
1883     $CFG->{fullscreen} = !$CFG->{fullscreen};
1884     video_init;
1885     } else {
1886     CFClient::UI::feed_sdl_key_down_event ($_[0]);
1887     }
1888     },
1889     CFClient::SDL_KEYUP => \&CFClient::UI::feed_sdl_key_up_event,
1890     CFClient::SDL_MOUSEMOTION => \&CFClient::UI::feed_sdl_motion_event,
1891     CFClient::SDL_MOUSEBUTTONDOWN => \&CFClient::UI::feed_sdl_button_down_event,
1892     CFClient::SDL_MOUSEBUTTONUP => \&CFClient::UI::feed_sdl_button_up_event,
1893     CFClient::SDL_USEREVENT => sub {
1894     if ($_[0]{code} == 1) {
1895     audio_channel_finished $_[0]{data1};
1896     } elsif ($_[0]{code} == 0) {
1897     audio_music_finished;
1898     }
1899     },
1900     );
1901    
1902     #############################################################################
1903    
1904     $SIG{INT} = $SIG{TERM} = sub { exit };
1905    
1906     {
1907     local $SIG{__DIE__} = sub { CFClient::fatal $_[0] };
1908    
1909     CFClient::read_cfg "$Crossfire::VARDIR/pclientrc";
1910    
1911     $TILECACHE = CFClient::db_table "tilecache";
1912     $FACEMAP = CFClient::db_table "facemap";
1913    
1914     my %DEF_CFG = (
1915     sdl_mode => 0,
1916     width => 640,
1917     height => 480,
1918     fullscreen => 0,
1919     fast => 0,
1920     map_scale => 1,
1921     fow_enable => 1,
1922     fow_intensity => 0.45,
1923     fow_smooth => 0,
1924     gui_fontsize => 1,
1925     log_fontsize => 1,
1926     gauge_fontsize=> 1,
1927     gauge_size => 0.35,
1928     stat_fontsize => 1,
1929     mapsize => 100,
1930     host => "crossfire.schmorp.de",
1931     say_command => 'say',
1932     audio_enable => 1,
1933     bgm_enable => 1,
1934     bgm_volume => 0.25,
1935     face_prefetch => 0,
1936     output_sync => 1,
1937     output_count => 1,
1938     );
1939    
1940     while (my ($k, $v) = each %DEF_CFG) {
1941     $CFG->{$k} = $v unless exists $CFG->{$k};
1942     }
1943    
1944     sdl_init;
1945    
1946     @SDL_MODES = reverse
1947     grep $_->[0] >= 640 && $_->[1] >= 480,
1948     CFClient::SDL_ListModes;
1949    
1950     @SDL_MODES or CFClient::fatal "Unable to find a usable video mode\n(hardware accelerated opengl fullscreen)";
1951    
1952     $CFG->{sdl_mode} = 0 if $CFG->{sdl_mode} > @SDL_MODES;
1953    
1954     {
1955     my @fonts = map CFClient::find_rcfile "fonts/$_", qw(
1956     DejaVuSans.ttf
1957     DejaVuSansMono.ttf
1958     DejaVuSans-Bold.ttf
1959     DejaVuSansMono-Bold.ttf
1960     DejaVuSans-Oblique.ttf
1961     DejaVuSansMono-Oblique.ttf
1962     DejaVuSans-BoldOblique.ttf
1963     DejaVuSansMono-BoldOblique.ttf
1964     );
1965    
1966     CFClient::add_font $_ for @fonts;
1967    
1968     CFClient::pango_init;
1969    
1970     $FONT_PROP = new_from_file CFClient::Font $fonts[0];
1971     $FONT_FIXED = new_from_file CFClient::Font $fonts[1];
1972    
1973     $FONT_PROP->make_default;
1974     }
1975    
1976     # compare mono (ft) vs. rgba (cairo)
1977     # ft - 1.8s, cairo 3s, even in alpha-only mode
1978     # for my $rgba (0..1) {
1979     # my $t1 = Time::HiRes::time;
1980     # for (1..1000) {
1981     # my $layout = CFClient::Layout->new ($rgba);
1982     # $layout->set_text ("hallo" x 100);
1983     # $layout->render;
1984     # }
1985     # my $t2 = Time::HiRes::time;
1986     # warn $t2-$t1;
1987     # }
1988    
1989     video_init;
1990     audio_init;
1991     }
1992    
1993     Event::loop;
1994    
1995     END { CFClient::SDL_Quit }
1996    
1997     =head1 NAME
1998    
1999     pclient - A Crossfire+ and Crossfire game client
2000    
2001     =head1 SYNOPSIS
2002    
2003     Just run it - no commandline arguments are supported.
2004    
2005     =head1 USAGE
2006    
2007     Pclient utilises OpenGL for all UI elements and the game. It is supposed to be used
2008     fullscreen and interactively.
2009    
2010     =head1 AUTHOR
2011    
2012     Marc Lehmann <crossfire@schmorp.de>, Robin Redeker <elmex@ta-sa.org>
2013    
2014    
2015