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

File Contents

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