1 /***************************************************************************
2 zen.cpp - description
3 -------------------
4 project : OpenCity
5 codename : ZeN server
6 begin : november 26th, 2006
7 copyright : (C) 2006-2008 by Duong Khang NGUYEN
8 email : neoneurone @ gmail com
9
10 $Id: zen.cpp 375 2008-10-28 14:47:15Z neoneurone $
11 ***************************************************************************/
12
13 /***************************************************************************
14 * *
15 * This program is free software; you can redistribute it and/or modify *
16 * it under the terms of the GNU General Public License as published by *
17 * the Free Software Foundation; either version 2 of the License, or *
18 * any later version. *
19 * *
20 ***************************************************************************/
21
22 // Useful enumerations
23 #include "opencity_direction.h"
24 #include "opencity_structure_type.h"
25
26 // OpenCity headers
27 #include "zen.h"
28 #include "city.h" // The heart of the project
29 #include "conf.h" // Parser for .conf file
30 #include "agentpolice.h" // MAS testing
31 #include "agentdemonstrator.h"
32
33 // Global settings
34 #include "globalvar.h"
35 extern GlobalVar gVars;
36
37 // Libraries headers
38 #include "SDL_image.h"
39 #include "binreloc.h" // BinReloc routines from AutoPackage
40 #include "tinyxml/tinyxml.h"
41 #include "SimpleOpt.h" // Simple command line argument parser
42
43 // Standard headers
44 #include <cmath> // For log10
45 #include <cstdlib> // For getenv
46 #include <ctime> // For time
47
48
49 /*=====================================================================*/
50 /* LOCAL MACROS */
51 /*=====================================================================*/
52 #ifndef __WIN32__
53 #include <sys/stat.h> // mkdir
54 #else
55 // Win32 specifics
56 #include <shlobj.h> // Windows shell technologies
57 #define DATADIR "C:/Program Files"
58 #define SYSCONFDIR DATADIR
59 #endif
60
61 // Window's settings
62 #define OC_WINDOW_POS_X 20
63 #define OC_WINDOW_POS_Y 20
64 #define OC_WINDOW_WIDTH 750
65 #define OC_WINDOW_HEIGHT 560
66 #define OC_WINDOW_BPP_DEFAULT 32 // OC uses this by default
67 #define OC_WINDOW_BPP_16 16
68 #define OC_FULLSCREEN_WIDTH 1024
69 #define OC_FULLSCREEN_HEIGHT 768
70
71 // Exit code
72 #define OC_CONFIG_NOT_FOUND -1
73 #define OC_CONFIG_PARSE_ERROR -2
74
75 // Settings file
76 #define OC_CONFIG_FILE_FILENAME "config/opencity.xml"
77
78 // Others macros
79 #define OC_WINDOW_NAME PACKAGE VERSION
80 #define OC_PROGRAM_NAME "OpenCity ZeN server application"
81
82
83
84 /*=====================================================================*/
85 /* LOCAL VARIABLES */
86 /*=====================================================================*/
87 /// The current user interface is pointed by this pointer
88 static UI* uipCurrentUI = NULL;
89
90 /// Set to true when the user request to quit the program
91 static bool boolQuit = false;
92 static bool bRestart = false;
93
94 /// Static so that the others can not access this
95 static string sDataDir = "";
96 static string sSaveDir = "";
97 static string sConfigDir = "";
98
99
100 /*=====================================================================*/
ocActive(const SDL_ActiveEvent & e)101 void ocActive( const SDL_ActiveEvent & e)
102 {
103 OPENCITY_DEBUG( "Active event received" );
104
105 if (e.state & SDL_APPACTIVE) {
106 gVars.gboolActive = (e.gain == 1);
107 }
108 }
109
110
111 /*=====================================================================*/
ocQuit(const int quit_code)112 void ocQuit( const int quit_code )
113 {
114 cout << "Quit requested, quit code is : " << quit_code
115 << endl
116 << "Bye bye !"
117 << endl;
118 boolQuit = true;
119 }
120
121
122 /*=====================================================================*/
ocRestart()123 void ocRestart()
124 {
125 cout << "Restart with a new city from scratch. " << endl;
126 bRestart = true;
127 }
128
129
130 /*=====================================================================*/
ocProcessSDLEvents(void)131 void ocProcessSDLEvents( void )
132 {
133 static SDL_Event event;
134
135 // Grab all the events off the queue.
136 while( SDL_PollEvent( &event ) ) {
137
138 switch( event.type ) {
139 case SDL_ACTIVEEVENT:
140 ocActive( event.active );
141 break;
142
143 case SDL_QUIT:
144 // Handle quit requests (like Ctrl-c).
145 cout << "Quit requested, stoping " << OC_PROGRAM_NAME << "..." << endl;
146 boolQuit = true;
147 break;
148 }
149 }
150 }
151
152
153 /*=====================================================================*/
ocSetNewUI(UI * pcNewUI)154 void ocSetNewUI( UI * pcNewUI)
155 {
156 uipCurrentUI = pcNewUI;
157 }
158
159
160 /*=====================================================================*/
161 string
formatPath(const string & rcsPath)162 formatPath(const string& rcsPath)
163 {
164 string result = rcsPath;
165
166 if (result.size() > 0) {
167 // Delete all quotes "
168 string::size_type pos;
169 while ( (pos = result.find( '\"' )) != result.npos ) {
170 result.erase( pos );
171 }
172 // Append the "/" to path
173 if (result[ result.size()-1 ] != '/')
174 result += '/';
175 }
176 else {
177 result = "/";
178 }
179
180 return result;
181 }
182
183
184 /*=====================================================================*/
parseArg(int argc,char * argv[])185 void parseArg(int argc, char *argv[])
186 {
187 enum {
188 OPT_DATADIR,
189 OPT_CONFDIR,
190 OPT_HELP
191 };
192
193 CSimpleOpt::SOption g_rgOptions[] = {
194 { OPT_DATADIR, (char*)"--datadir", SO_REQ_SEP },
195 { OPT_DATADIR, (char*)"-dd", SO_REQ_SEP },
196 { OPT_CONFDIR, (char*)"--confdir", SO_REQ_SEP },
197 { OPT_CONFDIR, (char*)"-cd", SO_REQ_SEP },
198 { OPT_HELP, (char*)"--help", SO_NONE },
199 { OPT_HELP, (char*)"-h", SO_NONE },
200 SO_END_OF_OPTIONS // END
201 };
202
203 CSimpleOpt args(argc, argv, g_rgOptions, SO_O_EXACT | SO_O_NOSLASH | SO_O_SHORTARG | SO_O_CLUMP );
204 while (args.Next()) {
205 switch (args.LastError()) {
206 case SO_OPT_INVALID:
207 cout << "<OPTION> " << args.OptionText() << " unrecognized" << endl;
208 break;
209 case SO_OPT_MULTIPLE:
210 cout << "<OPTION> " << args.OptionText() << " matched multiple options" << endl;
211 break;
212 case SO_ARG_INVALID:
213 cout << "<OPTION> " << args.OptionText() << " does not accept any argument" << endl;
214 break;
215 case SO_ARG_INVALID_TYPE:
216 cout << "<OPTION> " << args.OptionText() << " has an invalid argument format" << endl;
217 break;
218 case SO_ARG_MISSING:
219 cout << "<OPTION> " << args.OptionText() << " requires an argument" << endl;
220 break;
221 case SO_ARG_INVALID_DATA:
222 cout << "<OPTION> " << args.OptionText() << " has an invalid argument data" << endl;
223 break;
224 case SO_SUCCESS:
225 cout << "<OPTION> " << args.OptionText() << " detected" << endl;
226 break;
227 }
228
229 // Exit the program on error
230 if (args.LastError() != SO_SUCCESS) {
231 cout << "Try " << argv[0] << " --help for usage information" << endl;
232 exit( -1 );
233 }
234
235 switch (args.OptionId()) {
236 case OPT_DATADIR:
237 sDataDir = formatPath(args.OptionArg());
238 cout << "<OPTION> DataDir is: \"" << sDataDir << "\"" << endl;
239 break;
240
241 case OPT_CONFDIR:
242 sConfigDir = formatPath(args.OptionArg());
243 cout << "<OPTION> ConfDir is: \"" << sConfigDir << "\"" << endl;
244 break;
245
246 case OPT_HELP:
247 cout << "Usage: " << argv[0]
248 << " [-dd|--datadir newDataPath] [-cd|--confdir newConfigPath]" << endl << endl;
249 cout << "Warning: the command option overwrite the config file settings."
250 << endl;
251 exit( -2 );
252 break;
253 } // switch
254 } // while
255 }
256
257
258 /*=====================================================================*/
displayStatus(const string & str)259 void displayStatus( const string & str )
260 {
261 cout << str << endl;
262 }
263
264
265 /*=====================================================================*/
serverMode()266 int serverMode()
267 {
268 // Initialize the video system in order to capture Ctrl-C !
269 SDL_Init(SDL_INIT_VIDEO);
270
271 // Create the mutex first
272 gVars.gpmutexSim = SDL_CreateMutex();
273
274 // Create the global renderer in order to use its text rendering functions
275 // gVars.gpRenderer = new Renderer( gVars.guiCityWidth, gVars.guiCityLength );
276
277 // AudioManager's initialization
278 // displayStatus( "Looking for GPU freezing system... ");
279 // gVars.gpAudioMgr = new AudioManager();
280
281 // Create the other required global managers
282 displayStatus( "Initializing the vibration detector..." );
283 gVars.gpMapMaker = new MapGen::MapMaker(
284 gVars.guiCityWidth, gVars.guiCityLength,
285 gVars.gsGeneratorHeightMap,
286 gVars.guiGeneratorMapType,
287 gVars.guiGeneratorWaterType,
288 gVars.guiGeneratorMapShapeType,
289 gVars.guiGeneratorTreeDensityType,
290 gVars.guiGeneratorSeed
291 );
292
293 displayStatus( "Activating embedded GPS...");
294 gVars.gpMapMgr = new Map( gVars.guiCityWidth, gVars.guiCityLength );
295
296 // displayStatus( "Calibrating earthquake subsystem...");
297 // gVars.gpGraphicMgr = new GraphicManager();
298
299 displayStatus( "Shaking DNA mixer thread...");
300 gVars.gpPropertyMgr = new PropertyManager();
301
302 displayStatus( "Mounting intergalactic hyperlink ...");
303 gVars.gpNetworking = new Networking();
304
305 displayStatus( "Initializing the particle handler ...");
306 gVars.gpMoveMgr = new MovementManager( gVars.gpGraphicMgr, gVars.gpMapMgr );
307
308
309 // Create a new city map
310 City* pNewCity = new City( gVars.guiCityWidth, gVars.guiCityLength, false );
311 if (pNewCity == NULL) {
312 OPENCITY_FATAL( "Error while creating new city" );
313 return (-15);
314 }
315
316 // Start the server
317 boolQuit = (gVars.gpNetworking->StartServer() == OC_NET_OK) ? false : true;
318 if (!boolQuit)
319 displayStatus( "Online world conquero activated");
320 else
321 displayStatus( "Online world conquero activation has failed" );
322
323 // The main loop
324 while (!boolQuit) {
325 // Run the city at the LAST_SPEED (default parameter)
326 cout << ".";
327 (void)gVars.gpNetworking->ProcessServerData();
328 ocProcessSDLEvents();
329 pNewCity->Run();
330
331 cout.flush();
332 SDL_Delay( gVars.guiMsPerFrame );
333 }
334
335 // Stop the zen server and close the network connection
336 (void)gVars.gpNetworking->StopServer();
337 gVars.gpNetworking->Close();
338
339 // WARNING: the deleting/creating order is very important !
340 delete pNewCity;
341
342 delete gVars.gpMoveMgr;
343 delete gVars.gpNetworking;
344 delete gVars.gpPropertyMgr;
345 // delete gVars.gpGraphicMgr;
346 delete gVars.gpMapMgr;
347
348 // Close the audio device then delete the audio manager
349 // gVars.gpAudioMgr->CloseAudio();
350 // delete gVars.gpAudioMgr;
351
352 // delete gVars.gpRenderer;
353
354 // Delete the simulators' mutex now
355 SDL_DestroyMutex( gVars.gpmutexSim );
356
357 gVars.gpVideoSrf = NULL;
358 SDL_Quit(); // WARNING: Calls free() on an invalid pointer. Detected by glibc
359 return 0;
360 }
361
362
363 /*=====================================================================*/
printCopyright()364 void printCopyright() {
365 // Output the copyright text
366 cout << "Welcome to " << PACKAGE << " version " << VERSION << endl;
367 cout << "Copyright (C) by Duong Khang NGUYEN. All rights reserved." << endl;
368 cout << " web : http://www.opencity.info" << endl;
369 cout << " email: neoneurone @ gmail com" << endl << endl;
370
371 cout << "This program is released under the terms of" << endl;
372 cout << "GNU General Public License (See the COPYING file for more details)" << endl << endl;
373
374 cout << "Starting " << OC_PROGRAM_NAME << "..." << endl << endl;
375 }
376
377
378 /*=====================================================================*/
379 /** Return the save directory.
380 \return The pointer to the absolute directory. The caller must free
381 the pointer if it's not used anymore.
382 */
findSaveDir()383 char* findSaveDir()
384 {
385 char* ret = NULL;
386
387 #ifndef __WIN32__
388 // Get the home directory from the environment variable
389 char* env = getenv("HOME");
390 if (env != NULL) {
391 ret = (char*)malloc( strlen(env) + 1 );
392 strcpy( ret, env );
393 }
394 #else
395 // Find the directory: "C:\Documents and Settings\username\Application Data"
396 // Required shell DLL version: 5.0 or later
397 // header: shlobj.h
398 // lib: shell32.lib ?
399 // dll: shell32.dll
400
401 TCHAR szPath[MAX_PATH];
402
403 if(SUCCEEDED(
404 SHGetFolderPath(NULL, CSIDL_APPDATA|CSIDL_FLAG_CREATE, NULL, 0, szPath)
405 )) {
406 ret = (char*)malloc( strlen(szPath) + 1 );
407 strcpy( ret, szPath );
408 }
409 #endif
410
411 // The required save directory does not exist, use the current directory
412 if (ret == NULL) {
413 ret = (char*)malloc( 2 );
414 strcpy( ret, "." );
415 }
416
417 return ret;
418 }
419
420
421 /*=====================================================================*/
422 /** Try to detect and set the datadir, the confdir and the savedir using
423 BinReloc library and win32 standard function
424 */
detectProgramPath()425 void detectProgramPath()
426 {
427 char* pTemp = NULL;
428 BrInitError brError;
429
430 // IF the datadir is not set THEN try to get it from BinReloc routines
431 if (sDataDir == "") {
432 // Default system directory settings
433 sDataDir = DATADIR;
434
435 // Init the BinReloc routines
436 if (br_init(&brError) != 1) {
437 OPENCITY_INFO(
438 "Failed to initialized BinReloc routines to search for the datadir. " <<
439 "The error was: " << brError
440 );
441 }
442
443 // Construct the datadir from the prefix
444 pTemp = br_find_data_dir( sDataDir.c_str() );
445 sDataDir = pTemp;
446 sDataDir += "/";
447 sDataDir += PACKAGE;
448 sDataDir = formatPath( sDataDir );
449 free(pTemp);
450 }
451
452 // IF the configdir is not set THEN try to get it from BinReloc routines
453 if (sConfigDir == "") {
454 // Default system directory settings
455 sConfigDir = SYSCONFDIR;
456
457 // Init the BinReloc routines
458 if (br_init(&brError) != 1) {
459 OPENCITY_INFO(
460 "Failed to initialized BinReloc routines to search for the confdir. " <<
461 "The error was: " << brError
462 );
463 }
464
465 // Construct the pkgsysconfdir from the prefix
466 pTemp = br_find_etc_dir( sConfigDir.c_str() );
467 sConfigDir = pTemp;
468 sConfigDir += "/";
469 sConfigDir += PACKAGE;
470 sConfigDir = formatPath( sConfigDir );
471 free(pTemp);
472 }
473
474 // IF the save directory is not set the find it
475 if (sSaveDir == "") {
476 pTemp = findSaveDir();
477 sSaveDir = pTemp;
478 free(pTemp);
479 #ifndef __WIN32__
480 sSaveDir += "/.opencity/";
481 mkdir( sSaveDir.c_str(), 0755 );
482 #else
483 // Win32 uses \ as directory separtor
484 sSaveDir += "\\opencity\\";
485 CreateDirectory( sSaveDir.c_str(), NULL );
486 // Replace \ by /
487 string::size_type pos;
488 while ( (pos = sSaveDir.find( '\\' )) != sSaveDir.npos ) {
489 sSaveDir.replace( pos, 1, "/" );
490 }
491 #endif
492 }
493
494 // Print out some information
495 OPENCITY_INFO( "Detected data directory : " << sDataDir );
496 OPENCITY_INFO( "Detected config directory : " << sConfigDir );
497 OPENCITY_INFO( "Detected save directory : " << sSaveDir );
498 }
499
500
501 /*=====================================================================*/
502 /** Read the OpenCity's main settings file "opencity.xml"
503 \return "" if OK, otherwise the error description
504 0: if OK
505 OC_CONFIG_NOT_FOUND: the config file has not been found
506 OC_CONFIG_PARSE_ERROR: the was a parse error
507 */
readSettings()508 string readSettings()
509 {
510 string errorString = "";
511 TiXmlDocument settings;
512
513 // Now try to open the config file then read it
514 OPENCITY_INFO(
515 "Reading XML config file: \"" << ocConfigDirPrefix(OC_CONFIG_FILE_FILENAME) << "\""
516 );
517
518 // Load the settings file
519 string fn = ocConfigDirPrefix(OC_CONFIG_FILE_FILENAME);
520 if (!settings.LoadFile(fn)) {
521 errorString = settings.ErrorDesc();
522 return errorString;
523 }
524
525 // Error testing
526 if (settings.Error()) {
527 errorString = settings.ErrorDesc();
528 return errorString;
529 }
530
531 // Get the root element
532 TiXmlNode* pRoot = settings.RootElement();
533 if (pRoot == NULL) {
534 errorString = settings.ErrorDesc();
535 return errorString;
536 }
537
538 // Parse the settings
539 TiXmlElement* pElement = pRoot->FirstChildElement();
540 int i = 0;
541 while (pElement != NULL)
542 {
543 // Debug
544 // cout << i++ << "||" << *pElement << std::endl;
545 // "city" element, read the city's size
546 if (pElement->ValueStr() == "city") {
547 TiXmlElement* pChild = pElement->FirstChildElement();
548 while (pChild != NULL) {
549 cout << i++ << "||" << *pChild << std::endl;
550 if (pChild->ValueStr() == "width") {
551 pChild->QueryIntAttribute("value", (int*)&gVars.guiCityWidth);
552 }
553 else if (pChild->ValueStr() == "length") {
554 pChild->QueryIntAttribute("value", (int*)&gVars.guiCityLength);
555 }
556 pChild = pChild->NextSiblingElement();
557 }
558 }
559 // "msPerFrame" element
560 else if (pElement->ValueStr() == "msPerFrame") {
561 pElement->QueryIntAttribute("value", (int*)&gVars.guiMsPerFrame);
562 }
563 // "zenServer" element
564 else if (pElement->ValueStr() == "zenServer") {
565 if (pElement->GetText() != NULL)
566 gVars.gsZenServer = pElement->GetText();
567 }
568
569 pElement = pElement->NextSiblingElement();
570 }
571
572 return errorString;
573 }
574
575
576 /*=====================================================================*/
initGlobalVar()577 void initGlobalVar()
578 {
579 // Config file and command line options
580 gVars.gboolUseAudio = true;
581 gVars.gboolFullScreen = false;
582 gVars.gboolServerMode = false;
583 gVars.guiCityWidth = OC_CITY_W;
584 gVars.guiCityLength = OC_CITY_L;
585 gVars.guiMsPerFrame = OC_MS_PER_FRAME;
586 gVars.guiScreenWidth = OC_WINDOW_WIDTH;
587 gVars.guiScreenHeight = OC_WINDOW_HEIGHT;
588 gVars.guiVideoBpp = OC_WINDOW_BPP_DEFAULT;
589
590 gVars.gsGeneratorHeightMap = "";
591 gVars.guiGeneratorSeed = time(NULL);
592 gVars.guiGeneratorMapType = MapGen::MapMaker::PLAIN;
593 gVars.guiGeneratorWaterType = MapGen::MapMaker::LAKE;
594 gVars.guiGeneratorMapShapeType = MapGen::MapMaker::NONE;
595 gVars.guiGeneratorTreeDensityType = MapGen::MapMaker::SPARSE;
596
597 gVars.gfMsSimDelayMax = 0;
598 gVars.gsZenServer = "localhost";
599
600 // Application status
601 gVars.gboolActive = true; // the application is active at start
602
603 // The mutex that all the simulators depend on
604 gVars.gpmutexSim = NULL;
605
606 // The famous renderer
607 gVars.gpRenderer = NULL;
608
609 // Datamanagers
610 gVars.gpAudioMgr = NULL; // global Audio Manager
611 gVars.gpGraphicMgr = NULL; // global Graphic Manager
612 gVars.gpPropertyMgr = NULL; // global Property Manager
613 gVars.gpMapMaker = NULL; // global map maker
614 gVars.gpMapMgr = NULL; // global height Map Manager
615 gVars.gpNetworking = NULL; // global networking support class
616 gVars.gpPathFinder = NULL; // global pathfinder class
617 gVars.gpMoveMgr = NULL; // global movement manager
618
619 // Multi-Agent System
620 gVars.gpKernel = NULL; // global MAS Kernel
621 gVars.gpEnvironment = NULL; // global Environement class
622
623 // The SDL video surface
624 gVars.gpVideoSrf = NULL; // global video screen surface
625 }
626
627
628 /*=====================================================================*/
629 #ifdef __WIN32__
630 extern "C"
631 #endif
main(int argc,char * argv[])632 int main(int argc, char *argv[])
633 {
634 // Initialize the global settings variable to the default values
635 initGlobalVar();
636
637 // Print out the copyright
638 printCopyright();
639
640 // Parse the command-line options
641 parseArg( argc, argv );
642
643 // Detect the main path: sDataDir and sSaveDir
644 detectProgramPath();
645
646 // Read the application settings from the XML settings file
647 string errorDesc = readSettings();
648 if (errorDesc != "") {
649 OPENCITY_FATAL(
650 "The was an error while loading the settings file: \"" << errorDesc << "\"" << endl
651 << "If the main config file \"" << OC_CONFIG_FILE_FILENAME << "\" has not been found then" << endl
652 << "try to specify the data directory with ""--datadir"" "
653 << "and the configuration directory with ""--confdir""." << endl
654 << "For example:" << endl
655 << " " << argv[0] << " --datadir \"/absolute/path/to/opencity/data\" "
656 << "--confdir \"/absolute/path/to/opencity/conf\"" << endl
657 << "or" << endl
658 << " " << argv[0] << " --datadir \"../relative/path/to/opencity/data\" "
659 << "--confdir \"../relative/path/to/opencity/conf\"" << endl
660 );
661 exit(OC_CONFIG_NOT_FOUND);
662 }
663
664 // Initialization of global variables
665 uipCurrentUI = NULL;
666 gVars.gfMsSimDelayMax = log10((OC_FLOAT)gVars.guiCityWidth*gVars.guiCityLength + 1) * OC_MS_GLOBAL_LOG_FACTOR;
667
668 // Initialize the random number generator
669 srand( time(NULL) );
670
671 // Launch the server
672 int returnCode = serverMode();
673
674 return returnCode;
675 }
676
677
678 /*=====================================================================*/
679 /* GLOBAL FUNCTIONS */
680 /*=====================================================================*/
681 string
ocDataDirPrefix(const string & s)682 ocDataDirPrefix( const string& s )
683 {
684 return sDataDir + s;
685 }
686
687
688 /*=====================================================================*/
689 string
ocConfigDirPrefix(const string & s)690 ocConfigDirPrefix( const string& s )
691 {
692 return sConfigDir + s;
693 }
694
695
696 /*=====================================================================*/
697 string
ocSaveDirPrefix(const string & s)698 ocSaveDirPrefix( const string& s )
699 {
700 return sSaveDir + s;
701 }
702
703
704 /*=====================================================================*/
ocStrVersion()705 string ocStrVersion()
706 {
707 std::ostringstream oss;
708
709 oss << OC_VERSION << "." << OC_PATCHLEVEL << "." << OC_SUBLEVEL;
710 return oss.str();
711 }
712
713
714 /*=====================================================================*/
ocLongVersion()715 long ocLongVersion()
716 {
717 long lVersion = 0;
718
719 lVersion = OC_VERSION*65536 + OC_PATCHLEVEL*256 + OC_SUBLEVEL;
720 return lVersion;
721 }
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745