add wraparound support to C2 physics
[openc2e.git] / qtgui / qtopenc2e.cpp
blob32d67f60f3b27e4f804b80dcc543469b9ac98269
1 /*
2 This program is free software; you can redistribute it and/or modify
3 it under the terms of the GNU General Public License as published by
4 the Free Software Foundation; either version 2 of the License, or
5 (at your option) any later version.
7 This program is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY; without even the implied warranty of
9 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 GNU General Public License for more details.
12 You should have received a copy of the GNU General Public License along
13 with this program; if not, write to the Free Software Foundation, Inc.,
14 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 #include "World.h"
18 #include "qtopenc2e.h"
19 #include <QtGui>
20 #include "openc2eview.h"
21 #include "Engine.h"
22 #include "AudioBackend.h"
23 #include "MetaRoom.h"
25 #include "Hatchery.h"
26 #include "AgentInjector.h"
27 #include "BrainViewer.h"
28 #include "CreatureGrapher.h"
30 #include "Creature.h"
31 #include "SkeletalCreature.h"
32 #include "PointerAgent.h"
34 #include "version.h"
36 // Constructor which creates the main window.
38 QtOpenc2e::QtOpenc2e(boost::shared_ptr<QtBackend> backend) {
39 viewport = new openc2eView(this, backend);
40 setCentralWidget(viewport);
42 connect(this, SIGNAL(creatureChanged()), this, SLOT(onCreatureChange()));
44 // idle timer
45 // TODO: should prbly have a background thread to do this?
46 ourTimer = new QTimer(this);
47 connect(ourTimer, SIGNAL(timeout()), this, SLOT(tick()));
48 ourTimer->start();
50 (void)statusBar();
51 std::string titlebar = engine.getGameName() + " - openc2e ";
52 #ifdef DEV_BUILD
53 titlebar += "(development build)";
54 #else
55 titlebar += "(" RELEASE_VERSION ")";
56 #endif
57 setWindowTitle(titlebar.c_str());
58 resize(800, 600);
60 /* File menu */
62 exitAct = new QAction(tr("&Exit"), this);
63 exitAct->setStatusTip(tr("Exit openc2e"));
64 connect(exitAct, SIGNAL(triggered()), this, SLOT(close()));
66 fileMenu = menuBar()->addMenu(tr("&File"));
67 fileMenu->addAction(exitAct);
69 /* View menu */
71 viewMenu = menuBar()->addMenu(tr("&View"));
73 toggleScrollbarsAct = new QAction(tr("Show &Scrollbars"), this);
74 toggleScrollbarsAct->setCheckable(true);
75 connect(toggleScrollbarsAct, SIGNAL(triggered()), this, SLOT(toggleShowScrollbars()));
76 viewMenu->addAction(toggleScrollbarsAct);
78 // only enable scrollbars for c1/c2, by default
79 toggleScrollbarsAct->setChecked(engine.version < 3);
80 toggleShowScrollbars();
82 /* Control menu */
84 controlMenu = menuBar()->addMenu(tr("C&ontrol"));
85 connect(controlMenu, SIGNAL(aboutToShow()), this, SLOT(updateMenus()));
87 pauseAct = new QAction(tr("&Pause"), this);
88 connect(pauseAct, SIGNAL(triggered()), this, SLOT(togglePause()));
89 controlMenu->addAction(pauseAct);
91 muteAct = new QAction(tr("&Mute"), this);
92 muteAct->setCheckable(true);
93 connect(muteAct, SIGNAL(triggered()), this, SLOT(toggleMute()));
94 controlMenu->addAction(muteAct);
96 controlMenu->addSeparator();
98 fastSpeedAct = new QAction(tr("&Fast speed"), this);
99 fastSpeedAct->setCheckable(true);
100 connect(fastSpeedAct, SIGNAL(triggered()), this, SLOT(toggleFastSpeed()));
101 controlMenu->addAction(fastSpeedAct);
103 displayUpdatesAct = new QAction(tr("Slow &display updates"), this);
104 displayUpdatesAct->setCheckable(true);
105 displayUpdatesAct->setEnabled(false);
106 connect(displayUpdatesAct, SIGNAL(triggered()), this, SLOT(toggleDisplayUpdates()));
107 controlMenu->addAction(displayUpdatesAct);
109 autokillAct = new QAction(tr("&Autokill"), this);
110 autokillAct->setCheckable(true);
111 autokillAct->setChecked(world.autokill);
112 connect(autokillAct, SIGNAL(triggered()), this, SLOT(toggleAutokill()));
113 controlMenu->addAction(autokillAct);
115 /* Debug menu */
117 debugMenu = menuBar()->addMenu(tr("&Debug"));
118 connect(debugMenu, SIGNAL(aboutToShow()), this, SLOT(updateMenus()));
120 showMapAct = new QAction(tr("Show &Map"), this);
121 showMapAct->setCheckable(true);
122 connect(showMapAct, SIGNAL(triggered()), this, SLOT(toggleShowMap()));
123 debugMenu->addAction(showMapAct);
125 newNornAct = new QAction(tr("Create a new (debug) &Norn"), this);
126 if (engine.version > 2) newNornAct->setEnabled(false); // TODO
127 connect(newNornAct, SIGNAL(triggered()), this, SLOT(newNorn()));
128 debugMenu->addAction(newNornAct);
130 newEggAct = new QAction(tr("Create a random &egg"), this);
131 if (engine.version > 1) newEggAct->setEnabled(false); // TODO
132 connect(newEggAct, SIGNAL(triggered()), this, SLOT(newEgg()));
133 debugMenu->addAction(newEggAct);
135 /* Tools menu */
137 toolsMenu = menuBar()->addMenu(tr("&Tools"));
139 hatcheryAct = new QAction(tr("&Hatchery"), this);
140 connect(hatcheryAct, SIGNAL(triggered()), this, SLOT(showHatchery()));
141 toolsMenu->addAction(hatcheryAct);
142 if (engine.version > 1) hatcheryAct->setEnabled(false);
144 agentInjectorAct = new QAction(tr("&Agent Injector"), this);
145 connect(agentInjectorAct, SIGNAL(triggered()), this, SLOT(showAgentInjector()));
146 toolsMenu->addAction(agentInjectorAct);
148 brainViewerAct = new QAction(tr("&Brain Viewer"), this);
149 connect(brainViewerAct, SIGNAL(triggered()), this, SLOT(showBrainViewer()));
150 toolsMenu->addAction(brainViewerAct);
152 if (engine.version == 1)
153 hatchery = new Hatchery(this);
154 agentInjector = new AgentInjector(this);
155 brainViewer = new BrainViewer(this);
156 connect(this, SIGNAL(ticked()), brainViewer, SLOT(onTick()));
158 creatureGrapher = new CreatureGrapher(this);
159 connect(this, SIGNAL(ticked()), creatureGrapher, SLOT(onCreatureTick())); // TODO
160 creatureGrapherDock = new QDockWidget(this);
161 creatureGrapherDock->hide();
162 creatureGrapherDock->setWidget(creatureGrapher);
163 creatureGrapherDock->setFloating(true);
164 creatureGrapherDock->resize(QSize(300, 300));
165 creatureGrapherDock->setWindowTitle(tr("Creature Grapher"));
166 toolsMenu->addAction(creatureGrapherDock->toggleViewAction());
168 /* Creatures menu */
170 creaturesMenu = menuBar()->addMenu(tr("&Creatures"));
171 connect(creaturesMenu, SIGNAL(aboutToShow()), this, SLOT(updateCreaturesMenu()));
173 /* Help menu */
175 menuBar()->addSeparator();
177 aboutAct = new QAction(tr("&About"), this);
178 aboutAct->setStatusTip(tr("Find out about openc2e"));
179 connect(aboutAct, SIGNAL(triggered()), this, SLOT(about()));
181 helpMenu = menuBar()->addMenu(tr("&Help"));
182 helpMenu->addAction(aboutAct);
185 QtOpenc2e::~QtOpenc2e() {
188 monikerData &monikerDataFor(AgentRef a) {
189 shared_ptr<class genomeFile> g = a->getSlot(0);
190 assert(g);
191 std::string moniker = world.history.findMoniker(g);
192 return world.history.getMoniker(moniker);
195 std::string creatureNameFor(AgentRef a) {
196 return monikerDataFor(a).name;
199 void QtOpenc2e::selectCreature() {
200 QObject *src = sender();
201 QAction *srcaction = dynamic_cast<QAction *>(src);
202 assert(srcaction);
204 Agent *a = (Agent *)srcaction->data().value<void *>();
206 for (std::list<boost::shared_ptr<Agent> >::iterator i = world.agents.begin(); i != world.agents.end(); i++) {
207 boost::shared_ptr<Agent> p = *i;
208 if (!p) continue; // grr, but needed
210 if (a == p.get()) {
211 world.selectCreature(p);
212 return;
216 // looks like the creature disappeared from under us..
219 void QtOpenc2e::updateCreaturesMenu() {
220 creaturesMenu->clear();
222 for (std::list<boost::shared_ptr<Agent> >::iterator i = world.agents.begin(); i != world.agents.end(); i++) {
223 boost::shared_ptr<Agent> p = *i;
224 if (!p) continue; // grr, but needed
226 CreatureAgent *a = dynamic_cast<CreatureAgent *>(p.get());
227 if (a) {
228 // TODO: add breed?
229 std::string creaturename = creatureNameFor(a);
230 if (creaturename.empty()) creaturename = "<Unnamed>";
231 creaturename += std::string(" (") + (a->getCreature()->isFemale() ? "Female" : "Male") + ")";
233 // create a new action with menu as parent, so it'll be destroyed on clear()
234 QAction *creatureSelectAct = new QAction(creaturename.c_str(), creaturesMenu);
235 creatureSelectAct->setData(QVariant::fromValue((void *)a));
237 creatureSelectAct->setCheckable(true);
238 if (world.selectedcreature == p) creatureSelectAct->setChecked(true);
239 connect(creatureSelectAct, SIGNAL(triggered()), this, SLOT(selectCreature()));
241 if (monikerDataFor(a).getStatus() != borncreature)
242 creatureSelectAct->setDisabled(true);
244 creaturesMenu->addAction(creatureSelectAct);
248 if (creaturesMenu->isEmpty()) {
249 QAction *dummyAct = new QAction("<none available>", creaturesMenu);
250 dummyAct->setEnabled(false);
251 creaturesMenu->addAction(dummyAct);
255 void QtOpenc2e::onCreatureChange() {
256 std::string titlebar = engine.getGameName() + " - openc2e ";
257 #ifdef DEV_BUILD
258 titlebar += "(development build)";
259 #else
260 titlebar += "(" RELEASE_VERSION ")";
261 #endif
262 if (world.selectedcreature) {
263 oldcreaturename = creatureNameFor(world.selectedcreature);
265 if (oldcreaturename.empty())
266 titlebar += " - <Unnamed>";
267 else
268 titlebar += " - " + oldcreaturename;
271 setWindowTitle(titlebar.c_str());
274 void QtOpenc2e::tick() {
275 // set refreshdisplay occasionally, for updates when dorendering is false
276 if (world.worldtickcount % world.ticktime == 0) // every 10 in-world seconds, with default times
277 engine.refreshdisplay = true;
279 bool didtick = engine.tick();
281 int y = world.camera.getY();
282 int x = world.camera.getX();
283 viewport->tick();
284 viewport->horizontalScrollBar()->setValue(x - world.camera.getMetaRoom()->x());
285 viewport->verticalScrollBar()->setValue(y - world.camera.getMetaRoom()->y());
287 if (engine.done) close();
289 if (didtick) {
290 if (world.selectedcreature != selectedcreature) {
291 selectedcreature = world.selectedcreature;
292 emit creatureChanged();
293 } else if (world.selectedcreature) {
294 // pick up name changes
295 if (creatureNameFor(world.selectedcreature) != oldcreaturename)
296 onCreatureChange();
299 // TODO: emit creatureTicked() if necessary
301 emit ticked();
304 if (viewport->needsRender()) {
305 viewport->viewport()->repaint();
308 unsigned int i = engine.msUntilTick();
309 if (i != 0 || ourTimer->interval() != 0) // TODO: things get a bit annoyingly unresponsive without this
310 ourTimer->setInterval(i);
313 // action handlers
315 void QtOpenc2e::updateMenus() {
316 showMapAct->setChecked(world.showrooms);
317 fastSpeedAct->setChecked(engine.fastticks);
318 displayUpdatesAct->setChecked(!engine.dorendering);
319 autokillAct->setChecked(world.autokill);
320 muteAct->setChecked(engine.audio->isMuted());
321 if (world.paused) pauseAct->setText("&Play");
322 else pauseAct->setText("&Pause");
325 void QtOpenc2e::about() {
326 std::string abouttxt = "An open-source game engine to run the Creatures "
327 "series of games.\nVersion: ";
328 #ifdef DEV_BUILD
329 abouttxt += "development build";
330 #else
331 abouttxt += RELEASE_VERSION;
332 #endif
333 abouttxt += " (built " __DATE__ " " __TIME__ ")";
334 QMessageBox::about(this, tr("openc2e"), abouttxt.c_str());
337 void QtOpenc2e::showHatchery() {
338 hatchery->show();
339 hatchery->activateWindow();
342 void QtOpenc2e::showAgentInjector() {
343 agentInjector->show();
344 agentInjector->activateWindow();
347 void QtOpenc2e::showBrainViewer() {
348 brainViewer->show();
349 brainViewer->activateWindow();
352 void QtOpenc2e::toggleShowMap() {
353 world.showrooms = !world.showrooms;
356 void QtOpenc2e::toggleShowScrollbars() {
357 if (toggleScrollbarsAct->isChecked()) {
358 viewport->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
359 viewport->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
360 } else {
361 viewport->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
362 viewport->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
366 void QtOpenc2e::toggleFastSpeed() {
367 engine.fastticks = !engine.fastticks;
368 displayUpdatesAct->setEnabled(engine.fastticks);
369 if (!engine.fastticks) {
370 engine.dorendering = true;
371 displayUpdatesAct->setChecked(false);
375 void QtOpenc2e::toggleDisplayUpdates() {
376 engine.dorendering = !engine.dorendering;
379 void QtOpenc2e::toggleAutokill() {
380 world.autokill = !world.autokill;
383 void QtOpenc2e::togglePause() {
384 world.paused = !world.paused;
387 void QtOpenc2e::toggleMute() {
388 engine.audio->setMute(!engine.audio->isMuted());
391 #undef slots
392 void QtOpenc2e::newNorn() {
393 if (engine.version > 2) return; // TODO: fixme
395 std::string genomefile = "test";
396 shared_ptr<genomeFile> genome;
397 try {
398 genome = world.loadGenome(genomefile);
399 } catch (creaturesException &e) {
400 QMessageBox::warning(this, tr("Couldn't load genome file:"), e.prettyPrint().c_str());
401 return;
404 if (!genome) {
406 return;
409 SkeletalCreature *a = new SkeletalCreature(4);
411 int sex = 1 + (int) (2.0 * (rand() / (RAND_MAX + 1.0)));
412 oldCreature *c;
413 try {
414 if (engine.version == 1) c = new c1Creature(genome, (sex == 2), 0, a);
415 else c = new c2Creature(genome, (sex == 2), 0, a);
416 } catch (creaturesException &e) {
417 delete a;
418 QMessageBox::warning(this, tr("Couldn't create creature:"), e.prettyPrint().c_str());
419 return;
422 a->setCreature(c);
423 a->finishInit();
425 // if you make this work for c2e, you should probably set sane attributes here?
427 a->slots[0] = genome;
428 world.newMoniker(genome, genomefile, a);
429 world.history.getMoniker(world.history.findMoniker(genome)).moveToCreature(a);
431 // TODO: set it dreaming
433 c->born();
435 world.hand()->addCarried(a);
438 void QtOpenc2e::newEgg() {
439 std::string eggscript;
440 /* create the egg obj */
441 eggscript = boost::str(boost::format("new: simp eggs 8 %d 2000 0\n") % ((rand() % 6) * 8));
442 /* set the pose and the correct class/attributes */
443 eggscript += "pose 3\nsetv clas 33882624\nsetv attr 67\n";
444 /* create the genome */
445 eggscript += boost::str(boost::format("new: gene tokn dad%d tokn mum%d obv0\n") % (1 + rand() % 6) % (1 + rand() % 6));
446 /* set the gender */
447 eggscript += "setv obv1 0\n";
448 /* start the clock */
449 eggscript += "tick 2400\n";
450 /* move it into place */
451 eggscript += boost::str(boost::format("mvto %d 870\n") % (2600 + rand() % 200));
453 std::string err = engine.executeNetwork(eggscript);
454 if (err.size())
455 QMessageBox::warning(this, tr("Couldn't create egg:"), err.c_str());
458 Creature *QtOpenc2e::getSelectedCreature() {
459 if (world.selectedcreature) {
460 CreatureAgent *a = dynamic_cast<CreatureAgent *>(world.selectedcreature.get());
461 if (a) {
462 return a->getCreature();
466 return 0;