try to make build portable: remove SDL_mixer dependency, remove -f from cp command...
[openc2e.git] / World.cpp
blob9d826ededa2261758840d6c2f0c934ab1fe9adb6
1 /*
2 * World.cpp
3 * openc2e
5 * Created by Alyssa Milburn on Tue May 25 2004.
6 * Copyright (c) 2004-2008 Alyssa Milburn. All rights reserved.
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2 of the License, or (at your option) any later version.
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
20 #include "World.h"
21 #include "Engine.h"
22 #include "caosVM.h" // for setupCommandPointers()
23 #include "caosScript.h"
24 #include "PointerAgent.h"
25 #include "CompoundAgent.h" // for setFocus
26 #include <limits.h> // for MAXINT
27 #include "creaturesImage.h"
28 #include "CreatureAgent.h"
29 #include "Backend.h"
30 #include "AudioBackend.h"
31 #include "SFCFile.h"
32 #include "Room.h"
33 #include "MetaRoom.h"
34 #include "Catalogue.h"
36 #include <boost/format.hpp>
37 #include <boost/filesystem/convenience.hpp>
38 namespace fs = boost::filesystem;
40 World world;
42 World::World() {
43 ticktime = 50;
44 tickcount = 0;
45 worldtickcount = 0;
46 race = 50; // sensible default?
47 pace = 0.0f; // sensible default?
48 quitting = saving = false;
49 theHand = 0;
50 showrooms = false;
51 autokill = false;
52 autostop = false;
55 World::~World() {
56 agents.clear();
57 for (std::vector<caosVM *>::iterator i = vmpool.begin(); i != vmpool.end(); i++)
58 delete *i;
61 // annoyingly, if we put this in the constructor, the catalogue isn't available yet
62 void World::init() {
63 // First, try initialising the mouse cursor from the catalogue tag.
64 if (engine.version > 2 && catalogue.hasTag("Pointer Information")) {
65 const std::vector<std::string> &pointerinfo = catalogue.getTag("Pointer Information");
66 if (pointerinfo.size() >= 3) {
67 shared_ptr<creaturesImage> img = gallery.getImage(pointerinfo[2]);
68 if (img) {
69 theHand = new PointerAgent(pointerinfo[2]);
70 theHand->finishInit();
71 // TODO: set family/genus/species based on the first entry (normally "2 1 1")
72 // TODO: work out what second entry is ("2 2" normally?! "7 7" in CV)
73 } else {
74 std::cout << "There was a seemingly-useful \"Pointer Information\" catalogue tag provided, but sprite file '" << pointerinfo[2] << " ' doesn't exist!" << std::endl;
79 // If for some reason we failed to do that (missing/bad catalogue tag? missing file?), try falling back to a sane default.
80 if (!theHand) {
81 shared_ptr<creaturesImage> img;
82 if (gametype == "c3")
83 img = gallery.getImage("hand"); // as used in C3 and DS
84 else
85 img = gallery.getImage("syst"); // as used in C1, C2 and CV
86 if (img) {
87 theHand = new PointerAgent(img->getName());
88 theHand->finishInit();
89 if (engine.version > 2)
90 std::cout << "Warning: No valid \"Pointer Information\" catalogue tag, defaulting to '" << img->getName() << "'." << std::endl;
91 } else {
92 if (engine.version > 2)
93 std::cout << "Couldn't find a valid \"Pointer Information\" catalogue tag, and c";
94 else
95 std::cout << "C";
96 std::cout << "ouldn't find a pointer sprite, so not creating the pointer agent." << std::endl;
100 // *** set defaults for non-zero GAME engine variables
101 // TODO: this should be doing during world init, rather than global init
102 // TODO: not complete
103 caosVar v;
104 variables.clear();
106 // core engine bits
107 v.setInt(1); variables["engine_debug_keys"] = v;
108 v.setInt(1); variables["engine_full_screen_toggle"] = v;
109 v.setInt(9998); variables["engine_plane_for_lines"] = v;
110 v.setInt(6); variables["engine_zlib_compression"] = v;
112 // creature pregnancy
113 v.setInt(1); variables["engine_multiple_birth_maximum"] = v;
114 v.setFloat(0.5f); variables["engine_multiple_birth_identical_chance"] = v;
116 // port lines
117 v.setFloat(600.0f); variables["engine_distance_before_port_line_warns"] = v;
118 v.setFloat(800.0f); variables["engine_distance_before_port_line_snaps"] = v;
120 // adjust to default tick rate for C1/C2 if necessary
121 if (engine.version < 3)
122 ticktime = 100;
124 timeofday = dayofseason = season = year = 0;
127 void World::shutdown() {
128 map.Reset();
131 caosVM *World::getVM(Agent *a) {
132 if (vmpool.empty()) {
133 return new caosVM(a);
134 } else {
135 caosVM *x = vmpool.back();
136 vmpool.pop_back();
137 x->setOwner(a);
138 return x;
142 void World::freeVM(caosVM *v) {
143 v->setOwner(0);
144 vmpool.push_back(v);
147 void World::queueScript(unsigned short event, AgentRef agent, AgentRef from, caosVar p0, caosVar p1) {
148 scriptevent e;
150 assert(agent);
152 e.scriptno = event;
153 e.agent = agent;
154 e.from = from;
155 e.p[0] = p0;
156 e.p[1] = p1;
158 scriptqueue.push_back(e);
161 // TODO: eventually, the part should be referenced via a weak_ptr, maaaaybe?
162 void World::setFocus(TextEntryPart *p) {
163 // Unfocus the current agent. Not sure if c2e always does this (what if the agent/part is bad?).
164 if (focusagent) {
165 CompoundAgent *c = dynamic_cast<CompoundAgent *>(focusagent.get());
166 assert(c);
167 TextEntryPart *p = dynamic_cast<TextEntryPart *>(c->part(focuspart));
168 if (p)
169 p->loseFocus();
172 if (!p)
173 focusagent.clear();
174 else {
175 p->gainFocus();
176 focusagent = p->getParent();
177 focuspart = p->id;
181 void World::tick() {
182 if (saving) {} // TODO: save
183 if (quitting) {
184 // due to destruction ordering we must explicitly destroy all agents here
185 agents.clear();
186 exit(0);
189 // Notify the audio backend about our current viewpoint center.
190 engine.audio->setViewpointCenter(world.camera.getXCentre(), world.camera.getYCentre());
192 std::list<boost::shared_ptr<AudioSource> >::iterator si = uncontrolled_sounds.begin();
193 while (si != uncontrolled_sounds.end()) {
194 std::list<boost::shared_ptr<AudioSource> >::iterator next = si; next++;
195 if ((*si)->getState() != SS_PLAY) {
196 // sound is stopped, so release our reference
197 uncontrolled_sounds.erase(si);
198 } else {
199 // mute/unmute off-screen uncontrolled audio if necessary
200 float x, y, z;
201 (*si)->getPos(x, y, z);
202 (*si)->setMute(world.camera.getMetaRoom() != world.map.metaRoomAt(x, y));
205 si = next;
208 // Tick all agents, deleting as necessary.
209 std::list<boost::shared_ptr<Agent> >::iterator i = agents.begin();
210 while (i != agents.end()) {
211 boost::shared_ptr<Agent> a = *i;
212 if (!a) {
213 std::list<boost::shared_ptr<Agent> >::iterator i2 = i;
214 i2++;
215 agents.erase(i);
216 i = i2;
217 continue;
219 i++;
220 a->tick();
223 // Process the script queue.
224 std::list<scriptevent> newqueue;
225 for (std::list<scriptevent>::iterator i = scriptqueue.begin(); i != scriptqueue.end(); i++) {
226 boost::shared_ptr<Agent> agent = i->agent.lock();
227 if (agent) {
228 if (engine.version < 3) {
229 // only try running a script if the agent doesn't have a running script
230 // TODO: we don't really understand how script interruption in c1/c2 works
231 if (agent->vm && !agent->vm->stopped()) {
232 // TODO: is this sensible? (avoiding re-queueing c1/c2 script 9 and only script 9)
233 if (i->scriptno != 9) newqueue.push_back(*i);
234 continue;
237 agent->fireScript(i->scriptno, i->from, i->p[0], i->p[1]);
240 scriptqueue.clear();
241 scriptqueue = newqueue;
243 tickcount++;
244 worldtickcount++;
246 if (engine.version == 2) {
247 if (worldtickcount % 3600 == 0) {
248 timeofday++;
249 if (timeofday == 5) { // 5 parts of the day
250 timeofday = 0;
251 dayofseason++;
253 if (dayofseason == 4) { // 4 days per season
254 dayofseason = 0;
255 season++;
257 if (season == 4) { // 4 seasons per year
258 season = 0;
259 year++;
264 world.map.tick();
266 // TODO: correct behaviour? hrm :/
267 world.hand()->velx.setFloat(world.hand()->velx.getFloat() / 2.0f);
268 world.hand()->vely.setFloat(world.hand()->vely.getFloat() / 2.0f);
271 Agent *World::agentAt(unsigned int x, unsigned int y, bool obey_all_transparency, bool needs_mouseable) {
272 CompoundPart *p = partAt(x, y, obey_all_transparency, needs_mouseable);
273 if (p)
274 return p->getParent();
275 else
276 return 0;
279 CompoundPart *World::partAt(unsigned int x, unsigned int y, bool obey_all_transparency, bool needs_mouseable) {
280 Agent *transagent = 0;
281 if (!obey_all_transparency)
282 transagent = agentAt(x, y, true, needs_mouseable);
284 for (std::multiset<CompoundPart *, partzorder>::iterator i = zorder.begin(); i != zorder.end(); i++) {
285 int ax = (int)(x - (*i)->getParent()->x);
286 int ay = (int)(y - (*i)->getParent()->y);
287 if ((*i)->x <= ax) if ((*i)->y <= ay) if (((*i) -> x + (int)(*i)->getWidth()) >= ax) if (((*i) -> y + (int)(*i)->getHeight()) >= ay)
288 if ((*i)->getParent() != theHand) {
289 SpritePart *s = dynamic_cast<SpritePart *>(*i);
290 if (s && s->isTransparent() && obey_all_transparency)
291 // transparent parts in C1/C2 are scenery
292 // TODO: always true? you can't sekritly set parts to be transparent in C2?
293 if (engine.version < 3 || s->transparentAt(ax - s->x, ay - s->y))
294 continue;
295 if (needs_mouseable && !((*i)->getParent()->mouseable()))
296 continue;
297 if (!obey_all_transparency)
298 if ((*i)->getParent() != transagent)
299 continue;
300 return *i;
304 return 0;
307 void World::setUNID(Agent *whofor, int unid) {
308 assert(whofor->shared_from_this() == unidmap[unid].lock() || unidmap[unid].expired());
309 whofor->unid = unid;
310 unidmap[unid] = whofor->shared_from_this();
313 int World::newUNID(Agent *whofor) {
314 do {
315 int unid = rand();
316 if (unid && unidmap[unid].expired()) {
317 setUNID(whofor, unid);
318 return unid;
320 } while (1);
323 void World::freeUNID(int unid) {
324 unidmap.erase(unid);
327 shared_ptr<Agent> World::lookupUNID(int unid) {
328 if (unid == 0) return shared_ptr<Agent>();
329 return unidmap[unid].lock();
332 void World::drawWorld() {
333 drawWorld(&camera, engine.backend->getMainSurface());
336 void World::drawWorld(Camera *cam, Surface *surface) {
337 assert(surface);
339 MetaRoom *m = cam->getMetaRoom();
340 if (!m) {
341 // Whoops - the room we're in vanished, or maybe we were never in one?
342 // Try to get a new one ...
343 m = map.getFallbackMetaroom();
344 if (!m)
345 throw creaturesException("drawWorld() couldn't find any metarooms");
346 cam->goToMetaRoom(m->id);
348 int adjustx = cam->getX();
349 int adjusty = cam->getY();
350 shared_ptr<creaturesImage> bkgd = m->getBackground(""); // TODO
352 // TODO: work out what c2e does when it doesn't have a background..
353 if (!bkgd) return;
355 assert(bkgd->numframes() > 0);
357 int sprwidth = bkgd->width(0);
358 int sprheight = bkgd->height(0);
360 // draw the blk
361 for (unsigned int i = 0; i < (m->fullheight() / sprheight); i++) {
362 for (unsigned int j = 0; j < (m->fullwidth() / sprwidth); j++) {
363 // figure out which block number to use
364 unsigned int whereweare = j * (m->fullheight() / sprheight) + i;
366 // make one pass for non-wraparound rooms, or two passes for wraparound ones
367 // TODO: implement this in a more sensible way, or at least optimise it
368 for (unsigned int z = 0; z < (m->wraparound() ? 2 : 1); z++) {
369 int destx = (j * sprwidth) - adjustx + m->x();
370 int desty = (i * sprheight) - adjusty + m->y();
372 // if we're on the second pass, render to the *right* of the normal area
373 if (z == 1) destx += m->width();
375 // if the block's on screen, render it.
376 if ((destx >= -sprwidth) && (desty >= -sprheight) &&
377 (destx - sprwidth <= (int)surface->getWidth()) &&
378 (desty - sprheight <= (int)surface->getHeight()))
379 surface->render(bkgd, whereweare, destx, desty, false, 0, false, true);
384 // render all the agents
385 for (std::multiset<renderable *, renderablezorder>::iterator i = renders.begin(); i != renders.end(); i++) {
386 if ((*i)->showOnRemoteCameras() || cam == &camera) {
387 // three-pass for wraparound rooms, the third since agents often straddle the boundary
388 // TODO: same as above with background rendering
389 for (unsigned int z = 0; z < (m->wraparound() ? 3 : 1); z++) {
390 int newx = -adjustx, newy = -adjusty;
391 if (z == 1) newx += m->width();
392 else if (z == 2) newx -= m->width();
393 (*i)->render(surface, newx, newy);
398 if (showrooms) {
399 shared_ptr<Room> r = map.roomAt(hand()->x, hand()->y);
400 for (std::vector<shared_ptr<Room> >::iterator i = cam->getMetaRoom()->rooms.begin();
401 i != cam->getMetaRoom()->rooms.end(); i++) {
402 unsigned int col = 0xFFFF00CC;
403 if (*i == r) col = 0xFF00FFCC;
404 else if (r) {
405 if ((**i).doors.find(r) != (**i).doors.end())
406 col = 0x00FFFFCC;
409 // rooms don't wrap over the boundary, so just draw twice
410 for (unsigned int z = 0; z < (m->wraparound() ? 2 : 1); z++) {
411 int newx = adjustx;
412 if (z == 1)
413 newx -= m->width();
415 (*i)->renderBorders(surface, newx, adjusty, col);
420 surface->renderDone();
423 void World::executeInitScript(fs::path p) {
424 assert(fs::exists(p));
425 assert(!fs::is_directory(p));
427 std::string x = p.native_file_string();
428 std::ifstream s(x.c_str());
429 assert(s.is_open());
430 //std::cout << "executing script " << x << "...\n";
431 //std::cout.flush(); std::cerr.flush();
432 try {
433 caosScript script(gametype, x);
434 script.parse(s);
435 caosVM vm(0);
436 script.installScripts();
437 vm.runEntirely(script.installer);
438 } catch (creaturesException &e) {
439 std::cerr << "exec of \"" << p.leaf() << "\" failed due to exception " << e.prettyPrint() << std::endl;
440 } catch (std::exception &e) {
441 std::cerr << "exec of \"" << p.leaf() << "\" failed due to exception " << e.what() << std::endl;
443 std::cout.flush(); std::cerr.flush();
446 void World::executeBootstrap(fs::path p) {
447 if (!fs::is_directory(p)) {
448 executeInitScript(p);
449 return;
452 std::vector<fs::path> scripts;
454 fs::directory_iterator fsend;
455 for (fs::directory_iterator d(p); d != fsend; ++d) {
456 if ((!fs::is_directory(*d)) && (fs::extension(*d) == ".cos"))
457 scripts.push_back(*d);
460 std::sort(scripts.begin(), scripts.end());
461 for (std::vector<fs::path>::iterator i = scripts.begin(); i != scripts.end(); i++)
462 executeInitScript(*i);
465 void World::executeBootstrap(bool switcher) {
466 if (engine.version < 3) {
467 // read from Eden.sfc
469 if (data_directories.size() == 0)
470 throw creaturesException("C1/2 can't run without data directories!");
472 // TODO: case-sensitivity for the lose
473 fs::path edenpath(data_directories[0] / "/Eden.sfc");
474 if (fs::exists(edenpath) && !fs::is_directory(edenpath)) {
475 SFCFile sfc;
476 std::ifstream f(edenpath.native_directory_string().c_str(), std::ios::binary);
477 f >> std::noskipws;
478 sfc.read(&f);
479 sfc.copyToWorld();
480 return;
481 } else
482 throw creaturesException("couldn't find file Eden.sfc, required for C1/2");
485 // TODO: this code is possibly wrong with multiple bootstrap directories
486 std::multimap<std::string, fs::path> bootstraps;
488 for (std::vector<fs::path>::iterator i = data_directories.begin(); i != data_directories.end(); i++) {
489 assert(fs::exists(*i));
490 assert(fs::is_directory(*i));
491 fs::path b(*i / "/Bootstrap/");
492 if (fs::exists(b) && fs::is_directory(b)) {
493 fs::directory_iterator fsend;
494 // iterate through each bootstrap directory
495 for (fs::directory_iterator d(b); d != fsend; ++d) {
496 if (fs::exists(*d) && fs::is_directory(*d)) {
497 std::string s = (*d).leaf();
498 // TODO: cvillage has switcher code in 'Startup', so i included it here too
499 if (s == "000 Switcher" || s == "Startup") {
500 if (!switcher) continue;
501 } else {
502 if (switcher) continue;
505 bootstraps.insert(std::pair<std::string, fs::path>(s, *d));
511 for (std::multimap<std::string, fs::path>::iterator i = bootstraps.begin(); i != bootstraps.end(); i++) {
512 executeBootstrap(i->second);
516 void World::initCatalogue() {
517 for (std::vector<fs::path>::iterator i = data_directories.begin(); i != data_directories.end(); i++) {
518 assert(fs::exists(*i));
519 assert(fs::is_directory(*i));
521 fs::path c(*i / "/Catalogue/");
522 if (fs::exists(c) && fs::is_directory(c))
523 catalogue.initFrom(c);
527 #include "PathResolver.h"
528 std::string World::findFile(std::string name) {
529 // Go backwards, so we find files in more 'modern' directories first..
530 for (int i = data_directories.size() - 1; i != -1; i--) {
531 fs::path p = data_directories[i];
532 std::string r = (p / fs::path(name, fs::native)).native_directory_string();
533 if (resolveFile(r))
534 return r;
537 return "";
540 std::vector<std::string> World::findFiles(std::string dir, std::string wild) {
541 std::vector<std::string> possibles;
543 // Go backwards, so we find files in more 'modern' directories first..
544 for (int i = data_directories.size() - 1; i != -1; i--) {
545 fs::path p = data_directories[i];
546 std::string r = (p / fs::path(dir, fs::native)).native_directory_string();
547 std::vector<std::string> results = findByWildcard(r, wild);
548 possibles.insert(possibles.end(), results.begin(), results.end()); // merge results
551 return possibles;
554 std::string World::getUserDataDir() {
555 return (data_directories.end() - 1)->native_directory_string();
558 void World::selectCreature(boost::shared_ptr<Agent> a) {
559 if (a) {
560 CreatureAgent *c = dynamic_cast<CreatureAgent *>(a.get());
561 caos_assert(c);
564 if (selectedcreature != a) {
565 for (std::list<boost::shared_ptr<Agent> >::iterator i = world.agents.begin(); i != world.agents.end(); i++) {
566 if (!*i) continue;
567 (*i)->queueScript(120, 0, caosVar(a), caosVar(selectedcreature)); // selected creature changed
570 selectedcreature = a;
574 shared_ptr<genomeFile> World::loadGenome(std::string &genefile) {
575 std::vector<std::string> possibles = findFiles("/Genetics/", genefile + ".gen");
576 if (possibles.empty()) return shared_ptr<genomeFile>();
577 genefile = possibles[(int)((float)possibles.size() * (rand() / (RAND_MAX + 1.0)))];
579 shared_ptr<genomeFile> p(new genomeFile());
580 std::ifstream gfile(genefile.c_str(), std::ios::binary);
581 caos_assert(gfile.is_open());
582 gfile >> std::noskipws;
583 gfile >> *(p.get());
585 return p;
588 void World::newMoniker(shared_ptr<genomeFile> g, std::string genefile, AgentRef agent) {
589 std::string d = history.newMoniker(g);
590 world.history.getMoniker(d).addEvent(2, "", genefile);
591 world.history.getMoniker(d).moveToAgent(agent);
594 std::string World::generateMoniker(std::string basename) {
595 // TODO: is there a better way to handle this? incoming basename is from catalogue files..
596 if (basename.size() != 4) {
597 std::cout << "World::generateMoniker got passed '" << basename << "' as a basename which isn't 4 characters, so ignoring it" << std::endl;
598 basename = "xxxx";
601 std::string x = basename;
602 for (unsigned int i = 0; i < 4; i++) {
603 unsigned int n = (unsigned int) (0xfffff * (rand() / (RAND_MAX + 1.0)));
604 x = x + "-" + boost::str(boost::format("%05x") % n);
607 return x;
610 boost::shared_ptr<AudioSource> World::playAudio(std::string filename, AgentRef agent, bool controlled, bool loop) {
611 if (filename.size() == 0) return boost::shared_ptr<AudioSource>();
613 boost::shared_ptr<AudioSource> sound = engine.audio->newSource();
614 if (!sound) return boost::shared_ptr<AudioSource>();
616 AudioClip clip = engine.audio->loadClip(filename);
617 if (!clip) {
618 // note that more specific error messages can be thrown by implementations of loadClip
619 if (engine.version < 3) return boost::shared_ptr<AudioSource>(); // creatures 1 and 2 ignore non-existent audio clips
620 throw creaturesException("failed to load audio clip " + filename);
623 sound->setClip(clip);
625 if (loop) {
626 assert(controlled);
627 sound->setLooping(true);
630 if (agent) {
631 agent->updateAudio(sound);
632 if (controlled)
633 agent->sound = sound;
634 else
635 uncontrolled_sounds.push_back(sound);
636 } else {
637 assert(!controlled);
639 // TODO: handle non-agent sounds
640 sound->setPos(world.camera.getXCentre(), world.camera.getYCentre(), 0);
641 uncontrolled_sounds.push_back(sound);
644 sound->play();
646 return sound;
649 int World::findCategory(unsigned char family, unsigned char genus, unsigned short species) {
650 if (!catalogue.hasTag("Agent Classifiers")) return -1;
652 const std::vector<std::string> &t = catalogue.getTag("Agent Classifiers");
654 for (unsigned int i = 0; i < t.size(); i++) {
655 std::string buffer = boost::str(boost::format("%d %d %d") % (int)family % (int)genus % (int)species);
656 if (t[i] == buffer) return i;
657 buffer = boost::str(boost::format("%d %d 0") % (int)family % (int)genus);
658 if (t[i] == buffer) return i;
659 buffer = boost::str(boost::format("%d 0 0") % (int)family);
660 if (t[i] == buffer) return i;
661 // leave it here: 0 0 0 would be silly to have in Agent Classifiers.
664 return -1;
667 /* vim: set noet: */