Merge branch 'master' into gosu-head
[Tsunagari.git] / src / main.cpp
blob66aa8aef832a55bee17bea26c6a65c91466ca52f
1 /******************************
2 ** Tsunagari Tile Engine **
3 ** main.cpp **
4 ** Copyright 2011 OmegaSDG **
5 ******************************/
7 #include <ostream>
8 #include <stdarg.h>
9 #include <stdio.h>
10 #include <string>
11 #include <string.h>
13 #include <boost/scoped_ptr.hpp>
14 #include <boost/shared_ptr.hpp>
15 #include <libxml/parser.h>
16 #include <libxml/tree.h>
17 #include <popt.h>
19 #include "common.h"
20 #include "config.h"
21 #include "log.h"
22 #include "window.h"
24 #ifndef LIBXML_TREE_ENABLED
25 # error Tree must be enabled in libxml2
26 #endif
28 char* customConf;
29 char* verbosity;
30 int cache_ttl = 300;
31 char* size;
33 struct poptOption optionsTable[] = {
34 {"conf", 'c', POPT_ARG_STRING, &customConf, 'c', "Client config file to use", "CONFFILE"},
35 {"verbosity", 'v', POPT_ARG_STRING, &verbosity, 'v', "Log message level", "ERROR,DEVEL,DEBUG"},
36 {"cache-ttl", 't', POPT_ARG_INT, &verbosity, 't', "Resource cache time-to-live", "SECONDS"},
37 {"size", 's', POPT_ARG_STRING, &size, 's', "Window dimensions", "WxH"},
38 {"fullscreen", 'f', POPT_ARG_NONE, 0, 'f', "Run in fullscreen mode", NULL},
39 {"window", 'w', POPT_ARG_NONE, 0, 'w', "Run in windowed mode", NULL},
40 {"query", 'q', POPT_ARG_NONE, 0, 'q', "Query compiled-in engine defaults", NULL},
41 POPT_AUTOHELP
42 {NULL, 0, 0, NULL, 0}
45 /**
46 * Values needed prior to creating the GameWindow.
48 struct ClientValues {
49 std::string world;
50 coord_t windowsize;
51 bool fullscreen;
52 tern cache_enabled;
53 tern cache_ttl;
54 message_mode_t loglevel;
57 static void xmlErrorCb(void*, const char* msg, ...)
59 char buf[512];
60 va_list ap;
61 va_start(ap, msg);
62 sprintf(buf, msg, va_arg(ap, char*));
63 Log::err(CLIENT_CONF_FILE, buf);
64 va_end(ap);
67 /**
68 * Load the values we need to start initializing the game from an XML file.
70 * We need to know what size window to create and which World to load. This
71 * information will be stored in a XML file which we parse here.
73 * @param filename Name of the XML-encoded file to load from.
75 * @return ClientValues object if successful
77 static ClientValues* parseConfig(const char* filename)
79 xmlDoc* doc = NULL;
80 xmlNode* root = NULL;
82 doc = xmlReadFile(filename, NULL, XML_PARSE_NOBLANKS);
84 if (!doc) {
85 Log::err(filename, "Could not parse file");
86 return NULL;
89 boost::shared_ptr<void> alwaysFreeTheDoc(doc, xmlFreeDoc);
91 xmlValidCtxt ctxt;
92 ctxt.error = xmlErrorCb;
93 if (!xmlValidateDocument(&ctxt, doc)) {
94 Log::err(filename, "XML document does not follow DTD");
95 return NULL;
98 root = xmlDocGetRootElement(doc);
99 xmlNode* node = root->xmlChildrenNode; // <client>
100 xmlChar* str;
102 /* Extract from XML object:
103 * - name of World to load
104 * - width, height, fullscreen-ness of Window
106 ClientValues* conf = new ClientValues;
107 node = node->xmlChildrenNode;
108 while (node != NULL) {
109 if (!xmlStrncmp(node->name, BAD_CAST("world"), 6)) {
110 xmlChar* str = xmlNodeGetContent(node);
111 conf->world = (char*)str;
113 else if (!xmlStrncmp(node->name, BAD_CAST("window"), 7)) {
114 str = xmlGetProp(node, BAD_CAST("x"));
115 conf->windowsize.x = atol((char*)str); // atol
117 str = xmlGetProp(node, BAD_CAST("y"));
118 conf->windowsize.y = atol((char*)str); // atol
120 str = xmlGetProp(node, BAD_CAST("fullscreen"));
121 conf->fullscreen = parseBool((char*)str);
123 else if (!xmlStrncmp(node->name, BAD_CAST("cache"), 6)) {
124 str = xmlGetProp(node, BAD_CAST("enabled"));
125 conf->cache_enabled = (tern)parseBool((char*)str);
126 str = xmlGetProp(node, BAD_CAST("ttl"));
127 conf->cache_ttl = (tern)parseBool((char*)str);
129 else if (!xmlStrncmp(node->name, BAD_CAST("logging"), 8)) {
130 str = xmlGetProp(node, BAD_CAST("level"));
131 if (!strcmp((char*)str, "error"))
132 conf->loglevel = MM_SILENT;
133 else if (!strcmp((char*)str, "devel"))
134 conf->loglevel = MM_DEVELOPER;
135 else if (!strcmp((char*)str, "debug"))
136 conf->loglevel = MM_DEBUG;
138 node = node->next;
141 return conf;
144 static void initLibraries()
147 * This initializes the library and checks for potential ABI mismatches
148 * between the version it was compiled for and the actual shared
149 * library used.
151 LIBXML_TEST_VERSION
154 static void cleanupLibraries()
156 // Clean the XML library.
157 xmlCleanupParser();
160 static void usage(poptContext optCon, const char* msg)
162 poptPrintUsage(optCon, stderr, 0);
163 std::cerr << msg;
164 exit(1);
167 static void defaultsQuery()
169 std::cerr << "CLIENT_CONF_FILE: "
170 << CLIENT_CONF_FILE << std::endl;
171 std::cerr << "MESSAGE_MODE: ";
172 if (MESSAGE_MODE == MM_DEBUG)
173 std::cerr << "MM_DEBUG" << std::endl;
174 else if (MESSAGE_MODE == MM_DEVELOPER)
175 std::cerr << "MM_DEVELOPER" << std::endl;
176 else
177 std::cerr << "MM_ERROR" << std::endl;
178 std::cerr << "ROGUELIKE_PERSIST_DELAY_INIT: "
179 << ROGUELIKE_PERSIST_DELAY_INIT << std::endl;
180 std::cerr << "ROGUELIKE_PERSIST_DELAY_CONSECUTIVE: "
181 << ROGUELIKE_PERSIST_DELAY_CONSECUTIVE << std::endl;
182 std::cerr << "CACHE_EMPTY_TTL: "
183 << CACHE_EMPTY_TTL << std::endl;
187 * Load client config and instantiate window.
189 * The client config tells us our window parameters along with which World
190 * we're going to load. The GameWindow class then loads and plays the game.
192 int main(int argc, char** argv)
194 initLibraries();
196 ClientValues* conf = parseConfig(CLIENT_CONF_FILE);
197 poptContext optCon = poptGetContext(NULL, argc, (const char**)argv, optionsTable, 0);
198 poptSetOtherOptionHelp(optCon, "[WORLD FILE]");
200 std::vector<std::string> dimensions;
201 int c;
202 while ((c = poptGetNextOpt(optCon)) >= 0) {
203 switch (c) {
204 case 'c':
205 delete conf;
206 conf = parseConfig(customConf);
207 if (!conf)
208 return 1;
209 break;
210 case 'v':
211 if (!strcmp(verbosity, "error"))
212 conf->loglevel = MM_SILENT;
213 else if (!strcmp(verbosity, "devel"))
214 conf->loglevel = MM_DEVELOPER;
215 else if (!strcmp(verbosity, "debug"))
216 conf->loglevel = MM_DEBUG;
217 else
218 usage(optCon, "Log level must be one of (error, devel, debug)\n");
219 break;
220 case 't':
221 // FIXME: requires resource cache
222 break;
223 case 's':
224 dimensions = splitStr(size, "x");
225 if (dimensions.size() != 2)
226 usage(optCon, "Dimensions must be in form WxH: e.g. 800x600\n");
227 conf->windowsize.x = atoi(dimensions[0].c_str());
228 conf->windowsize.y = atoi(dimensions[1].c_str());
229 break;
230 case 'f':
231 conf->fullscreen = true;
232 break;
233 case 'w':
234 conf->fullscreen = false;
235 break;
236 case 'q':
237 defaultsQuery();
238 return 0;
239 break;
243 if (!conf)
244 return 1;
246 const char* customWorld = poptGetArg(optCon);
247 if (customWorld)
248 conf->world = customWorld;
249 if (poptPeekArg(optCon))
250 usage(optCon, "Specify a single world: e.g. babysfirst.world\n");
251 poptFreeContext(optCon);
253 if (conf->loglevel)
254 Log::setMode(conf->loglevel);
256 GameWindow window((unsigned)conf->windowsize.x,
257 (unsigned)conf->windowsize.y,
258 conf->fullscreen);
259 if (window.init(conf->world))
260 window.show();
262 delete conf;
264 cleanupLibraries();
266 return 0;