Added reload of levels on F7 (Update levelpack) to ease the test of changes.
[enigmagame.git] / src / client.cc
blobf43293bba511d5fce6fc039a9a559063847a9b42
1 /*
2 * Copyright (C) 2004 Daniel Heck
3 * Copyright (C) 2006,2007,2008,2009,2010 Ronald Lamprecht
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 #include "client.hh"
22 #include "game.hh"
23 #include "display.hh"
24 #include "options.hh"
25 #include "server.hh"
26 #include "gui/HelpMenu.hh"
27 #include "main.hh"
28 #include "gui/GameMenu.hh"
29 #include "SoundEngine.hh"
30 #include "SoundEffectManager.hh"
31 #include "MusicManager.hh"
32 #include "player.hh"
33 #include "world.hh"
34 #include "nls.hh"
35 #include "StateManager.hh"
36 #include "lev/Index.hh"
37 #include "lev/PersistentIndex.hh"
38 #include "lev/Proxy.hh"
39 #include "lev/RatingManager.hh"
40 #include "lev/ScoreManager.hh"
42 #include "ecl_sdl.hh"
44 #include "enet/enet.h"
46 #include <cctype>
47 #include <cstring>
48 #include <cassert>
49 #include <algorithm>
50 #include <iostream>
52 using namespace enigma::client;
53 using namespace ecl;
54 using namespace std;
56 #include "client_internal.hh"
58 /* -------------------- Auxiliary functions -------------------- */
60 namespace
62 /*! Display a message and change the current mouse speed. */
63 void set_mousespeed (double speed)
65 int s = round_nearest<int>(speed);
66 options::SetMouseSpeed (s);
67 s = round_nearest<int> (options::GetMouseSpeed ());
68 Msg_ShowText(strf(_("Mouse speed: %d"), s), false, 2.0);
71 /*! Generate the message that is displayed when the level starts. */
72 std::string displayedLevelInfo (lev::Proxy *level) {
73 std::string text;
74 std::string tmp;
76 tmp = level->getLocalizedString("title");
77 if (tmp.empty())
78 tmp = _("Another nameless level");
79 text = string("\"")+ tmp +"\"";
80 tmp = level->getAuthor();
81 if (!tmp.empty())
82 text += _(" by ") + tmp;
83 tmp = level->getLocalizedString("subtitle");
84 if (!tmp.empty() && tmp != "subtitle")
85 text += string(" - \"")+ tmp + "\"";
86 tmp = level->getCredits(false);
87 if (!tmp.empty())
88 text += string(" - Credits: ")+ tmp;
89 tmp = level->getDedication(false);
90 if (!tmp.empty())
91 text += string(" - Dedication: ")+ tmp;
92 return text;
97 /* -------------------- Variables -------------------- */
99 namespace
101 Client client_instance;
102 const char HSEP = '^'; // history separator (use character that user cannot use)
105 #define CLIENT client_instance
107 /* -------------------- Client class -------------------- */
109 Client::Client()
110 : m_state (cls_idle),
111 m_levelname(),
112 m_hunt_against_time(0),
113 m_cheater(false),
114 m_user_input()
116 m_network_host = 0;
119 Client::~Client()
121 network_stop();
124 void Client::init() {
125 std::string command;
126 for (int i = 0; i < 10; i++) {
127 command = app.state->getString(ecl::strf("CommandHistory#%d", i).c_str());
128 if (command.size() > 0)
129 commandHistory.push_back(command);
130 else
131 break;
135 void Client::shutdown() {
136 for (int i = 0; i < commandHistory.size(); i++)
137 app.state->setProperty(ecl::strf("CommandHistory#%d", i).c_str(), commandHistory[i].c_str());
140 bool Client::network_start()
142 if (m_network_host)
143 return true;
145 m_network_host = enet_host_create (NULL,
146 1 /* only allow 1 outgoing connection */,
147 57600 / 8 /* 56K modem with 56 Kbps downstream bandwidth */,
148 14400 / 8 /* 56K modem with 14 Kbps upstream bandwidth */);
150 if (m_network_host == NULL) {
151 fprintf (stderr,
152 "An error occurred while trying to create an ENet client host.\n");
153 return false;
157 // ----- Connect to server
159 ENetAddress sv_address;
160 ENetPeer *m_server;
162 /* Connect to some.server.net:1234. */
163 enet_address_set_host (&sv_address, "localhost");
164 sv_address.port = 12345;
166 /* Initiate the connection, allocating the two channels 0 and 1. */
167 m_server = enet_host_connect (m_network_host, &sv_address, 2);
169 if (m_server == NULL) {
170 fprintf (stderr,
171 "No available peers for initiating an ENet connection.\n");
172 return false;
175 // Wait up to 5 seconds for the connection attempt to succeed.
176 ENetEvent event;
177 if (enet_host_service (m_network_host, &event, 5000) > 0 &&
178 event.type == ENET_EVENT_TYPE_CONNECT)
180 fprintf (stderr, "Connection to some.server.net:1234 succeeded.");
181 return true;
183 else
185 /* Either the 5 seconds are up or a disconnect event was */
186 /* received. Reset the peer in the event the 5 seconds */
187 /* had run out without any significant event. */
188 enet_peer_reset (m_server);
189 m_server = 0;
191 fprintf (stderr, "Connection to localhost:12345 failed.");
192 return false;
196 void Client::network_stop ()
198 if (m_network_host)
199 enet_host_destroy (m_network_host);
200 if (m_server)
201 enet_peer_reset (m_server);
205 /* ---------- Event handling ---------- */
207 void Client::handle_events()
209 SDL_Event e;
210 while (SDL_PollEvent(&e)) {
211 switch (e.type) {
212 case SDL_KEYDOWN:
213 on_keydown(e);
214 break;
215 case SDL_MOUSEMOTION:
216 if (abs(e.motion.xrel) > 300 || abs(e.motion.yrel) > 300) {
217 fprintf(stderr, "mouse event with %i, %i\n", e.motion.xrel, e.motion.yrel);
219 else
220 server::Msg_MouseForce (options::GetDouble("MouseSpeed") *
221 V2 (e.motion.xrel, e.motion.yrel));
222 break;
223 case SDL_MOUSEBUTTONDOWN:
224 case SDL_MOUSEBUTTONUP:
225 on_mousebutton(e);
226 break;
227 case SDL_ACTIVEEVENT: {
228 update_mouse_button_state();
229 if (e.active.gain == 0 && !video::IsFullScreen())
230 show_menu(false);
231 break;
234 case SDL_VIDEOEXPOSE: {
235 display::RedrawAll(video::GetScreen());
236 break;
239 case SDL_QUIT:
240 client::Msg_Command("abort");
241 app.bossKeyPressed = true;
242 break;
247 void Client::update_mouse_button_state()
249 int b = SDL_GetMouseState(0, 0);
250 player::InhibitPickup((b & SDL_BUTTON(1)) || (b & SDL_BUTTON(3)));
253 void Client::on_mousebutton(SDL_Event &e)
255 if (e.button.state == SDL_PRESSED) {
256 if (e.button.button == 1) {
257 // left mousebutton -> activate first item in inventory
258 server::Msg_ActivateItem ();
260 else if (e.button.button == 3|| e.button.button == 4) {
261 // right mousebutton, wheel down -> rotate inventory
262 rotate_inventory(+1);
264 else if (e.button.button == 5) {
265 // wheel down -> inverse rotate inventory
266 rotate_inventory(-1);
269 update_mouse_button_state();
272 void Client::rotate_inventory (int direction)
274 m_user_input = "";
275 STATUSBAR->hide_text();
276 player::RotateInventory(direction);
279 /* -------------------- Console related -------------------- */
281 void Client::process_userinput() {
282 // no addition of existing commands to history
283 if (consoleIndex == 1) {
284 for (int i = 0; i < commandHistory.size(); i++) {
285 if (newCommand == commandHistory[i]) {
286 // take existing history command instead of new command
287 consoleIndex = i + 2;
288 break;
292 // resort history with selected command at bottom
293 if (consoleIndex == 1) {
294 if (commandHistory.size() < 10)
295 commandHistory.push_back(std::string(""));
296 for (int i = 8; i >= 0; i--) {
297 if (i < commandHistory.size() - 1)
298 commandHistory[i+1] = commandHistory[i];
300 } else if (consoleIndex > 1) {
301 newCommand = commandHistory[consoleIndex - 2];
302 for (int i = consoleIndex - 3; i >= 0; i--) {
303 if (i < commandHistory.size())
304 commandHistory[i+1] = commandHistory[i];
306 } else { // document history or inventory
307 return;
309 commandHistory[0] = newCommand;
310 newCommand = "";
311 consoleIndex = 0;
312 STATUSBAR->hide_text();
313 server::Msg_Command(commandHistory[0]);
316 void Client::user_input_append(char c) {
317 if (consoleIndex <= 0) {
318 consoleIndex = 1;
319 newCommand = c;
320 } else if (consoleIndex == 1) {
321 newCommand += c;
322 } else {
323 newCommand = commandHistory[consoleIndex - 2] + c;
324 consoleIndex = 1;
326 Msg_ShowText(newCommand, false);
329 void Client::user_input_backspace() {
330 if (consoleIndex == 1) {
331 newCommand.erase(newCommand.size() - 1, 1);
332 if (!newCommand.empty())
333 Msg_ShowText(newCommand, false);
334 else {
335 consoleIndex = 0;
336 STATUSBAR->hide_text();
338 } else if (consoleIndex > 1) {
339 newCommand = commandHistory[consoleIndex - 2];
340 newCommand.erase(newCommand.size() - 1, 1);
341 if (!newCommand.empty()) {
342 consoleIndex = 1;
343 Msg_ShowText(newCommand, false);
344 } else {
345 consoleIndex = 0;
346 STATUSBAR->hide_text();
351 void Client::user_input_previous() {
352 if (consoleIndex < 0) {
353 ++consoleIndex;
354 int docIndex = documentHistory.size() + consoleIndex;
355 if (docIndex < documentHistory.size()) {
356 Msg_ShowText(documentHistory[docIndex], true);
357 } else {
358 consoleIndex = 0;
359 STATUSBAR->hide_text();
361 } else if (consoleIndex == 0) {
362 if (newCommand.length() > 0) {
363 consoleIndex = 1;
364 Msg_ShowText(newCommand, false);
365 } else if (commandHistory.size() > 0) {
366 consoleIndex = 2;
367 Msg_ShowText(commandHistory[0], false);
369 } else if (consoleIndex <= commandHistory.size()) {
370 ++consoleIndex;
371 Msg_ShowText(commandHistory[consoleIndex - 2], false);
372 } else { // top of history or new command without history
373 consoleIndex = 0;
374 STATUSBAR->hide_text();
378 void Client::user_input_next() {
379 if (consoleIndex <= 0) {
380 --consoleIndex;
381 int docIndex = documentHistory.size() + consoleIndex;
382 if (docIndex >= 0) {
383 Msg_ShowText(documentHistory[docIndex], true);
384 } else {
385 consoleIndex = 0;
386 STATUSBAR->hide_text();
388 } else if (consoleIndex == 1 || (consoleIndex == 2 && newCommand.size() == 0)) {
389 consoleIndex = 0;
390 STATUSBAR->hide_text();
391 } else if (consoleIndex > 1) {
392 --consoleIndex;
393 Msg_ShowText(consoleIndex == 1 ? newCommand : commandHistory[consoleIndex - 2], false);
397 void Client::on_keydown(SDL_Event &e)
399 SDLKey keysym = e.key.keysym.sym;
400 SDLMod keymod = e.key.keysym.mod;
402 if (keymod & KMOD_CTRL) {
403 switch (keysym) {
404 case SDLK_a:
405 server::Msg_Command ("restart");
406 break;
407 case SDLK_F3:
408 if (keymod & KMOD_SHIFT) {
409 // force a reload from file
410 lev::Proxy::releaseCache();
411 server::Msg_Command ("restart");
413 default:
414 break;
417 else if (keymod & KMOD_ALT) {
418 switch (keysym) {
419 case SDLK_x:
420 abort(); break;
421 case SDLK_t:
422 if (enigma::WizardMode) {
423 Screen *scr = video::GetScreen();
424 ecl::TintRect(scr->get_surface (), display::GetGameArea(),
425 100, 100, 100, 0);
426 scr->update_all();
428 break;
429 case SDLK_s:
430 if (enigma::WizardMode) {
431 server::Msg_Command ("god");
433 break;
434 case SDLK_RETURN:
436 video::TempInputGrab (false);
437 video::ToggleFullscreen ();
438 sdl::FlushEvents();
440 break;
441 default:
442 break;
445 else if (keymod & KMOD_META) {
446 switch (keysym) {
447 case SDLK_q: // Mac OS X application quit sequence
448 app.bossKeyPressed = true;
449 abort();
450 break;
451 default:
452 break;
455 else {
456 switch (keysym) {
457 case SDLK_ESCAPE:
458 if (keymod & KMOD_SHIFT) {
459 app.bossKeyPressed = true;
460 abort();
461 } else {
462 show_menu(true);
464 break;
465 case SDLK_LEFT: set_mousespeed(options::GetMouseSpeed() - 1); break;
466 case SDLK_RIGHT: set_mousespeed(options::GetMouseSpeed() + 1); break;
467 case SDLK_TAB: rotate_inventory(+1); break;
468 case SDLK_F1: show_help(); break;
469 case SDLK_F2:
470 // display hint
471 break;
472 case SDLK_F3:
473 if (keymod & KMOD_SHIFT)
474 server::Msg_Command ("restart");
475 else
476 server::Msg_Command ("suicide");
477 break;
479 case SDLK_F4: Msg_AdvanceLevel(lev::ADVANCE_STRICTLY); break;
480 case SDLK_F5: Msg_AdvanceLevel(lev::ADVANCE_UNSOLVED); break;
481 case SDLK_F6: Msg_JumpBack(); break;
483 case SDLK_F10: {
484 lev::Proxy *level = server::LoadedProxy;
485 std::string basename = std::string("screenshots/") +
486 level->getLocalSubstitutionLevelPath();
487 std::string fname = basename + ".png";
488 std::string fullPath;
489 int i = 1;
490 while (app.resourceFS->findFile(fname, fullPath)) {
491 fname = basename + ecl::strf("#%d", i++) + ".png";
493 std::string savePath = app.userImagePath + "/" + fname;
494 video::Screenshot(savePath);
495 break;
497 case SDLK_RETURN: process_userinput(); break;
498 case SDLK_BACKSPACE: user_input_backspace(); break;
499 case SDLK_UP: user_input_previous(); break;
500 case SDLK_DOWN: user_input_next(); break;
501 default:
502 if (e.key.keysym.unicode && (e.key.keysym.unicode & 0xff80) == 0) {
503 char ascii = static_cast<char>(e.key.keysym.unicode & 0x7f);
504 if (isalnum (ascii) ||
505 strchr(" .-!\"$%&/()=?{[]}\\#'+*~_,;.:<>|", ascii)) // don't add '^' or change history code
507 user_input_append(ascii);
511 break;
516 static const char *helptext_ingame[] = {
517 N_("Left mouse button:"), N_("Activate/drop leftmost inventory item"),
518 N_("Right mouse button:"), N_("Rotate inventory items"),
519 N_("Escape:"), N_("Show game menu"),
520 N_("Shift+Escape:"), N_("Quit game immediately"),
521 N_("F1:"), N_("Show this help"),
522 N_("F3:"), N_("Kill current marble"),
523 N_("Shift+F3:"), N_("Restart the current level"),
524 N_("F4:"), N_("Skip to next level"),
525 N_("F5:"), 0, // see below
526 N_("F6:"), N_("Jump back to last level"),
527 N_("F10:"), N_("Make screenshot"),
528 N_("Left/right arrow:"), N_("Change mouse speed"),
529 N_("Alt+x:"), N_("Return to level menu"),
530 // N_("Alt+Return:"), N_("Switch between fullscreen and window"),
534 void Client::show_help()
536 server::Msg_Pause (true);
537 video::TempInputGrab grab(false);
539 helptext_ingame[17] = app.state->getInt("NextLevelMode") == lev::NEXT_LEVEL_NOT_BEST
540 ? _("Skip to next level for best score hunt")
541 : _("Skip to next unsolved level");
543 video::ShowMouse();
544 gui::displayHelp(helptext_ingame, 200);
545 video::HideMouse();
547 update_mouse_button_state();
548 if (m_state == cls_game)
549 display::RedrawAll(video::GetScreen());
551 server::Msg_Pause (false);
552 game::ResetGameTimer();
554 if (app.state->getInt("NextLevelMode") == lev::NEXT_LEVEL_NOT_BEST)
555 server::Msg_Command ("restart"); // inhibit cheating
560 void Client::show_menu(bool isESC) {
561 if (isESC && server::LastMenuTime != 0.0 && server::LevelTime - server::LastMenuTime < 0.3) {
562 return; // protection against ESC D.o.S. attacks
564 if (isESC && server::LastMenuTime != 0.0 && server::LevelTime - server::LastMenuTime < 0.35) {
565 server::MenuCount++;
566 if (server::MenuCount > 10)
567 mark_cheater();
570 server::Msg_Pause (true);
572 ecl::Screen *screen = video::GetScreen();
574 video::TempInputGrab grab (false);
576 video::ShowMouse();
578 int x, y;
579 display::GetReferencePointCoordinates(&x, &y);
580 enigma::gui::GameMenu(x, y).manage();
582 video::HideMouse();
583 update_mouse_button_state();
584 if (m_state == cls_game)
585 display::RedrawAll(screen);
587 server::Msg_Pause (false);
588 game::ResetGameTimer();
590 if (isESC) // protection against ESC D.o.S. attacks
591 server::LastMenuTime = server::LevelTime;
594 void Client::draw_screen()
596 switch (m_state) {
597 case cls_error: {
598 Screen *scr = video::GetScreen();
599 GC gc (scr->get_surface());
600 blit(gc, 0,0, enigma::GetImage("menu_bg", ".jpg"));
601 Font *f = enigma::GetFont("menufont");
603 vector<string> lines;
605 ecl::split_copy (m_error_message, '\n', back_inserter(lines));
606 int x = 60;
607 int y = 60;
608 int yskip = 25;
609 const video::VMInfo *vminfo = video::GetInfo();
610 int width = vminfo->width - 120;
611 for (unsigned i=0; i<lines.size(); ) {
612 std::string::size_type breakPos = ecl::breakString (f, lines[i],
613 " ", width);
614 f->render(gc, x, y, lines[i].substr(0,breakPos).c_str());
615 y += yskip;
616 if (breakPos != lines[i].size()) {
617 // process rest of line
618 lines[i] = lines[i].substr(breakPos);
619 } else {
620 // process next line
621 i++;
624 scr->update_all();
625 scr->flush_updates();
626 break;
628 default:
629 break;
634 std::string Client::init_hunted_time()
636 std::string hunted;
637 m_hunt_against_time = 0;
638 if (app.state->getInt("NextLevelMode") == lev::NEXT_LEVEL_NOT_BEST) {
639 lev::Index *ind = lev::Index::getCurrentIndex();
640 lev::ScoreManager *scm = lev::ScoreManager::instance();
641 lev::Proxy *curProxy = ind->getCurrent();
642 lev::RatingManager *ratingMgr = lev::RatingManager::instance();
644 int difficulty = app.state->getInt("Difficulty");
645 int wr_time = ratingMgr->getBestScore(curProxy, difficulty);
646 int best_user_time = scm->getBestUserScore(curProxy, difficulty);
648 if (best_user_time>0 && (wr_time == -1 || best_user_time<wr_time)) {
649 m_hunt_against_time = best_user_time;
650 hunted = "you";
652 else if (wr_time>0) {
653 m_hunt_against_time = wr_time;
654 hunted = ratingMgr->getBestScoreHolder(curProxy, difficulty);
657 // STATUSBAR->set_timerstart(-m_hunt_against_time);
659 return hunted;
662 void Client::tick (double dtime)
664 const double timestep = 0.01; // 10ms
666 switch (m_state) {
667 case cls_idle:
668 break;
670 case cls_preparing_game: {
671 video::TransitionEffect *fx = m_effect.get();
672 if (fx && !fx->finished()) {
673 fx->tick (dtime);
675 else {
676 m_effect.reset();
677 server::Msg_StartGame();
679 m_state = cls_game;
680 m_timeaccu = 0;
681 m_total_game_time = 0;
682 sdl::FlushEvents();
684 break;
687 case cls_game:
688 if (app.state->getInt("NextLevelMode") == lev::NEXT_LEVEL_NOT_BEST) {
689 int old_second = round_nearest<int> (m_total_game_time);
690 int second = round_nearest<int> (m_total_game_time + dtime);
692 if (m_hunt_against_time && old_second <= m_hunt_against_time) {
693 if (second > m_hunt_against_time) { // happens exactly once when par has passed by
694 lev::Index *ind = lev::Index::getCurrentIndex();
695 lev::ScoreManager *scm = lev::ScoreManager::instance();
696 lev::Proxy *curProxy = ind->getCurrent();
697 lev::RatingManager *ratingMgr = lev::RatingManager::instance();
698 int difficulty = app.state->getInt("Difficulty");
699 int wr_time = ratingMgr->getBestScore(curProxy, difficulty);
700 int best_user_time = scm->getBestUserScore(curProxy, difficulty);
701 string message;
703 if (wr_time>0 && (best_user_time<0 || best_user_time>wr_time)) {
704 message = string(_("Too slow for ")) +
705 ratingMgr->getBestScoreHolder(curProxy, difficulty) +
706 ".. [Ctrl-A]";
708 else {
709 message = string(_("You are slow today.. [Ctrl-A]"));
712 client::Msg_PlaySound("shatter", 1.0);
713 Msg_ShowText(message, true, 2.0);
715 else {
716 if (old_second<second && // tick every second
717 (second >= (m_hunt_against_time-5) || // at least 5 seconds
718 second >= round_nearest<int> (m_hunt_against_time*.8))) // or the last 20% before par
720 client::Msg_PlaySound("pickup", 1.0);
726 m_total_game_time += dtime;
727 STATUSBAR->set_time (m_total_game_time);
728 // fall through
729 case cls_finished: {
730 m_timeaccu += dtime;
731 for (;m_timeaccu >= timestep; m_timeaccu -= timestep) {
732 display::Tick (timestep);
734 display::Redraw(video::GetScreen());
735 handle_events();
736 break;
739 case cls_gamemenu:
740 break;
741 case cls_gamehelp:
742 break;
743 case cls_abort:
744 break;
745 case cls_error:
747 SDL_Event e;
748 while (SDL_PollEvent(&e)) {
749 switch (e.type) {
750 case SDL_QUIT:
751 app.bossKeyPressed = true;
752 // fall through
753 case SDL_KEYDOWN:
754 client::Msg_Command("abort");
755 break;
759 break;
763 void Client::level_finished()
765 lev::Index *ind = lev::Index::getCurrentIndex();
766 lev::ScoreManager *scm = lev::ScoreManager::instance();
767 lev::Proxy *curProxy = ind->getCurrent();
768 lev::RatingManager *ratingMgr = lev::RatingManager::instance();
769 int difficulty = app.state->getInt("Difficulty");
770 int wr_time = ratingMgr->getBestScore(curProxy, difficulty);
771 int best_user_time = scm->getBestUserScore(curProxy, difficulty);
772 int par_time = ratingMgr->getParScore(curProxy, difficulty);
774 int level_time = round_nearest<int> (m_total_game_time);
776 string text;
777 bool timehunt_restart = false;
779 std::string par_name = ratingMgr->getBestScoreHolder(curProxy, difficulty);
780 for (int cut = 2; par_name.length() > 55; cut++)
781 par_name = ratingMgr->getBestScoreHolder(curProxy, difficulty, cut);
783 if (wr_time > 0) {
784 if (best_user_time<0 || best_user_time>wr_time) {
785 if (level_time == wr_time)
786 text = string(_("Exactly the world record of "))+par_name+"!";
787 else if (level_time<wr_time)
788 text = _("Great! A new world record!");
791 if (text.length() == 0 && best_user_time>0) {
792 if (level_time == best_user_time) {
793 text = _("Again your personal record...");
794 if (app.state->getInt("NextLevelMode") == lev::NEXT_LEVEL_NOT_BEST)
795 timehunt_restart = true; // when hunting yourself: Equal is too slow
797 else if (level_time<best_user_time)
798 if (par_time >= 0 && level_time <= par_time)
799 text = _("New personal record - better than par!");
800 else if (par_time >= 0)
801 text = _("New personal record, but over par!");
802 else
803 text = _("New personal record!");
806 if (app.state->getInt("NextLevelMode") == lev::NEXT_LEVEL_NOT_BEST &&
807 (wr_time>0 || best_user_time>0))
809 bool with_par = best_user_time == -1 || (wr_time >0 && wr_time<best_user_time);
810 int behind = level_time - (with_par ? wr_time : best_user_time);
812 if (behind>0) {
813 if (best_user_time>0 && level_time<best_user_time && with_par) {
814 text = _("Your record, ");
816 else {
817 text = "";
819 text += ecl::timeformat(behind);
820 if (with_par)
821 text += _("behind world record.");
822 else
823 text += _("behind your record.");
825 timehunt_restart = true; // time hunt failed -> repeat level
829 if (text.length() == 0) {
830 if (par_time >= 0 && level_time <= par_time)
831 text = _("Level finished - better than par!");
832 else if (par_time >= 0)
833 text = _("Level finished, but over par!");
834 else
835 text = _("Level finished!");
837 if (m_cheater)
838 text += _(" Cheater!");
840 Msg_ShowText(text, false);
842 if (!m_cheater) {
843 scm->updateUserScore(curProxy, difficulty, level_time);
845 // save score (just in case Enigma crashes when loading next level)
846 lev::ScoreManager::instance()->save();
850 if (timehunt_restart)
851 server::Msg_Command("restart");
852 else
853 m_state = cls_finished;
856 void Client::level_loaded(bool isRestart)
858 lev::Index *ind = lev::Index::getCurrentIndex();
859 lev::ScoreManager *scm = lev::ScoreManager::instance();
860 lev::Proxy *curProxy = ind->getCurrent();
862 // update window title
863 video::SetCaption(ecl::strf(_("Enigma pack %s - level #%d: %s"), ind->getName().c_str(),
864 ind->getCurrentLevel(), curProxy->getTitle().c_str()).c_str());
866 std::string hunted = init_hunted_time(); // sets m_hunt_against_time (used below)
867 documentHistory.clear();
868 consoleIndex = 0;
870 // show level information (name, author, etc.)
871 std::string displayed_info = "";
872 if (m_hunt_against_time>0) {
873 if (hunted == "you")
874 displayed_info = _("Your record: ");
875 else
876 displayed_info = _("World record to beat: ");
877 displayed_info += ecl::timeformat(m_hunt_against_time);
878 //+ _(" by ") +hunted;
879 // makes the string too long in many levels
880 Msg_ShowDocument(displayed_info, true, 4.0);
881 } else {
882 displayed_info = displayedLevelInfo(curProxy);
883 Msg_ShowDocument(displayed_info, true, 2.0);
886 sound::StartLevelMusic();
888 // start screen transition
889 GC gc(video::BackBuffer());
890 display::DrawAll(gc);
892 m_effect.reset (video::MakeEffect ((isRestart ? video::TM_SQUARES :
893 video::TM_PUSH_RANDOM), video::BackBuffer()));
894 m_cheater = false;
895 m_state = cls_preparing_game;
899 void Client::handle_message (Message *m) { // @@@ unused
900 switch (m->type) {
901 case CLMSG_LEVEL_LOADED:
903 break;
904 default:
905 fprintf (stderr, "Unhandled client event: %d\n", m->type);
906 break;
910 void Client::error (const string &text)
912 m_error_message = text;
913 m_state = cls_error;
914 draw_screen();
917 void Client::registerDocument(std::string text) {
918 documentHistory.push_back(text);
919 consoleIndex = -1;
922 void Client::finishedText() {
923 consoleIndex = 0;
926 /* -------------------- Functions -------------------- */
928 void client::ClientInit() {
929 CLIENT.init();
932 void client::ClientShutdown() {
933 CLIENT.shutdown();
936 bool client::NetworkStart()
938 return CLIENT.network_start();
941 void client::Msg_LevelLoaded(bool isRestart)
943 CLIENT.level_loaded(isRestart);
946 void client::Tick (double dtime) {
947 CLIENT.tick (dtime);
948 sound::Tick (dtime);
951 void client::Stop() {
952 CLIENT.stop ();
955 void client::Msg_AdvanceLevel (lev::LevelAdvanceMode mode) {
957 lev::Index *ind = lev::Index::getCurrentIndex();
958 // log last played level
959 lev::PersistentIndex::addCurrentToHistory();
961 if (ind->advanceLevel(mode)) {
962 // now we may advance
963 server::Msg_LoadLevel(ind->getCurrent(), false);
965 else
966 client::Msg_Command("abort");
969 void client::Msg_JumpBack() {
970 // log last played level
971 lev::PersistentIndex::addCurrentToHistory();
972 server::Msg_JumpBack();
975 bool client::AbortGameP() {
976 return CLIENT.abort_p();
979 void client::Msg_Command(const string& cmd) {
980 if (cmd == "abort") {
981 CLIENT.abort();
983 else if (cmd == "level_finished") {
984 client::Msg_PlaySound("finished", 1.0);
985 CLIENT.level_finished();
987 else if (cmd == "cheater") {
988 CLIENT.mark_cheater();
990 else if (cmd == "easy_going") {
991 CLIENT.easy_going();
993 else {
994 enigma::Log << "Warning: Client received unknown command '" << cmd << "'\n";
998 void client::Msg_PlayerPosition (unsigned iplayer, const V2 &pos)
1000 if (iplayer == (unsigned)player::CurrentPlayer()) {
1001 sound::SetListenerPosition (pos);
1002 display::SetReferencePoint (pos);
1006 void client::Msg_PlaySound (const std::string &wavfile,
1007 const ecl::V2 &pos,
1008 double relative_volume)
1010 sound::EmitSoundEvent (wavfile.c_str(), pos, relative_volume);
1013 void client::Msg_PlaySound (const std::string &wavfile, double relative_volume)
1015 sound::EmitSoundEvent (wavfile.c_str(), V2(), relative_volume);
1018 void client::Msg_Sparkle (const ecl::V2 &pos) {
1019 display::AddEffect (pos, "ring-anim", true);
1023 void client::Msg_ShowText(const std::string &text, bool scrolling, double duration) {
1024 STATUSBAR->show_text (text, scrolling, duration);
1027 void client::Msg_ShowDocument(const std::string &text, bool scrolling, double duration) {
1028 CLIENT.registerDocument(text);
1029 Msg_ShowText(text, scrolling, duration);
1032 void client::Msg_FinishedText() {
1033 CLIENT.finishedText();
1036 void client::Msg_Error (const std::string &text)
1038 CLIENT.error (text);