tweak C1-era CARR
[openc2e.git] / World.cpp
blobdbaef7b71da8c97ee1e4622393b1e5f037f1e2d9
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<std::pair<boost::shared_ptr<AudioSource>, bool> >::iterator si = uncontrolled_sounds.begin();
193 while (si != uncontrolled_sounds.end()) {
194 std::list<std::pair<boost::shared_ptr<AudioSource>, bool> >::iterator next = si; next++;
195 if (si->first->getState() != SS_PLAY) {
196 // sound is stopped, so release our reference
197 uncontrolled_sounds.erase(si);
198 } else {
199 if (si->second) {
200 // follow viewport
201 si->first->setPos(world.camera.getXCentre(), world.camera.getYCentre(), 0);
202 } else {
203 // mute/unmute off-screen uncontrolled audio if necessary
204 float x, y, z;
205 si->first->getPos(x, y, z);
206 si->first->setMute(world.camera.getMetaRoom() != world.map.metaRoomAt(x, y));
210 si = next;
213 // Tick all agents, deleting as necessary.
214 std::list<boost::shared_ptr<Agent> >::iterator i = agents.begin();
215 while (i != agents.end()) {
216 boost::shared_ptr<Agent> a = *i;
217 if (!a) {
218 std::list<boost::shared_ptr<Agent> >::iterator i2 = i;
219 i2++;
220 agents.erase(i);
221 i = i2;
222 continue;
224 i++;
225 a->tick();
228 // Process the script queue.
229 std::list<scriptevent> newqueue;
230 for (std::list<scriptevent>::iterator i = scriptqueue.begin(); i != scriptqueue.end(); i++) {
231 boost::shared_ptr<Agent> agent = i->agent.lock();
232 if (agent) {
233 if (engine.version < 3) {
234 // only try running a collision script if the agent doesn't have a running script
235 // TODO: we don't really understand how script interruption in c1/c2 works
236 if (agent->vm && !agent->vm->stopped() && i->scriptno == 6) {
237 continue;
240 agent->fireScript(i->scriptno, i->from, i->p[0], i->p[1]);
243 scriptqueue.clear();
244 scriptqueue = newqueue;
246 tickcount++;
247 worldtickcount++;
249 if (engine.version == 2) {
250 if (worldtickcount % 3600 == 0) {
251 timeofday++;
252 if (timeofday == 5) { // 5 parts of the day
253 timeofday = 0;
254 dayofseason++;
256 if (dayofseason == 4) { // 4 days per season
257 dayofseason = 0;
258 season++;
260 if (season == 4) { // 4 seasons per year
261 season = 0;
262 year++;
267 world.map.tick();
269 // TODO: correct behaviour? hrm :/
270 world.hand()->velx.setFloat(world.hand()->velx.getFloat() / 2.0f);
271 world.hand()->vely.setFloat(world.hand()->vely.getFloat() / 2.0f);
274 Agent *World::agentAt(unsigned int x, unsigned int y, bool obey_all_transparency, bool needs_mouseable) {
275 CompoundPart *p = partAt(x, y, obey_all_transparency, needs_mouseable);
276 if (p)
277 return p->getParent();
278 else
279 return 0;
282 CompoundPart *World::partAt(unsigned int x, unsigned int y, bool obey_all_transparency, bool needs_mouseable) {
283 Agent *transagent = 0;
284 if (!obey_all_transparency)
285 transagent = agentAt(x, y, true, needs_mouseable);
287 for (std::multiset<CompoundPart *, partzorder>::iterator i = zorder.begin(); i != zorder.end(); i++) {
288 int ax = (int)(x - (*i)->getParent()->x);
289 int ay = (int)(y - (*i)->getParent()->y);
290 if ((*i)->x <= ax) if ((*i)->y <= ay) if (((*i) -> x + (int)(*i)->getWidth()) >= ax) if (((*i) -> y + (int)(*i)->getHeight()) >= ay)
291 if ((*i)->getParent() != theHand) {
292 SpritePart *s = dynamic_cast<SpritePart *>(*i);
293 if (s && s->isTransparent() && obey_all_transparency)
294 // transparent parts in C1/C2 are scenery
295 // TODO: always true? you can't sekritly set parts to be transparent in C2?
296 if (engine.version < 3 || s->transparentAt(ax - s->x, ay - s->y))
297 continue;
298 if (needs_mouseable && !((*i)->getParent()->mouseable()))
299 continue;
300 if (!obey_all_transparency)
301 if ((*i)->getParent() != transagent)
302 continue;
303 return *i;
307 return 0;
310 void World::setUNID(Agent *whofor, int unid) {
311 assert(whofor->shared_from_this() == unidmap[unid].lock() || unidmap[unid].expired());
312 whofor->unid = unid;
313 unidmap[unid] = whofor->shared_from_this();
316 int World::newUNID(Agent *whofor) {
317 do {
318 int unid = rand();
319 if (unid && unidmap[unid].expired()) {
320 setUNID(whofor, unid);
321 return unid;
323 } while (1);
326 void World::freeUNID(int unid) {
327 unidmap.erase(unid);
330 shared_ptr<Agent> World::lookupUNID(int unid) {
331 if (unid == 0) return shared_ptr<Agent>();
332 return unidmap[unid].lock();
335 void World::drawWorld() {
336 drawWorld(&camera, engine.backend->getMainSurface());
339 void World::drawWorld(Camera *cam, Surface *surface) {
340 assert(surface);
342 MetaRoom *m = cam->getMetaRoom();
343 if (!m) {
344 // Whoops - the room we're in vanished, or maybe we were never in one?
345 // Try to get a new one ...
346 m = map.getFallbackMetaroom();
347 if (!m)
348 throw creaturesException("drawWorld() couldn't find any metarooms");
349 cam->goToMetaRoom(m->id);
351 int adjustx = cam->getX();
352 int adjusty = cam->getY();
353 shared_ptr<creaturesImage> bkgd = m->getBackground(""); // TODO
355 // TODO: work out what c2e does when it doesn't have a background..
356 if (!bkgd) return;
358 assert(bkgd->numframes() > 0);
360 int sprwidth = bkgd->width(0);
361 int sprheight = bkgd->height(0);
363 // draw the blk
364 for (unsigned int i = 0; i < (m->fullheight() / sprheight); i++) {
365 for (unsigned int j = 0; j < (m->fullwidth() / sprwidth); j++) {
366 // figure out which block number to use
367 unsigned int whereweare = j * (m->fullheight() / sprheight) + i;
369 // make one pass for non-wraparound rooms, or two passes for wraparound ones
370 // TODO: implement this in a more sensible way, or at least optimise it
371 for (unsigned int z = 0; z < (m->wraparound() ? 2 : 1); z++) {
372 int destx = (j * sprwidth) - adjustx + m->x();
373 int desty = (i * sprheight) - adjusty + m->y();
375 // if we're on the second pass, render to the *right* of the normal area
376 if (z == 1) destx += m->width();
378 // if the block's on screen, render it.
379 if ((destx >= -sprwidth) && (desty >= -sprheight) &&
380 (destx - sprwidth <= (int)surface->getWidth()) &&
381 (desty - sprheight <= (int)surface->getHeight()))
382 surface->render(bkgd, whereweare, destx, desty, false, 0, false, true);
387 // render all the agents
388 for (std::multiset<renderable *, renderablezorder>::iterator i = renders.begin(); i != renders.end(); i++) {
389 if ((*i)->showOnRemoteCameras() || cam == &camera) {
390 // three-pass for wraparound rooms, the third since agents often straddle the boundary
391 // TODO: same as above with background rendering
392 for (unsigned int z = 0; z < (m->wraparound() ? 3 : 1); z++) {
393 int newx = -adjustx, newy = -adjusty;
394 if (z == 1) newx += m->width();
395 else if (z == 2) newx -= m->width();
396 (*i)->render(surface, newx, newy);
401 // render port connection lines. TODO: these should be rendered as some kind
402 // of renderable, not directly like this.
403 for (std::list<boost::shared_ptr<Agent> >::iterator i = world.agents.begin(); i != world.agents.end(); i++) {
404 boost::shared_ptr<Agent> a = *i;
405 if (!a) continue;
406 for (std::map<unsigned int, boost::shared_ptr<OutputPort> >::iterator p = a->outports.begin();
407 p != a->outports.end(); p++) {
408 for (PortConnectionList::iterator c = p->second->dests.begin(); c != p->second->dests.end(); c++) {
409 if (!c->first) continue;
410 InputPort *target = c->first->inports[c->second].get();
411 surface->renderLine(a->x + p->second->x - adjustx, a->y + p->second->y - adjusty,
412 c->first->x + target->x - adjustx, c->first->y + target->y - adjusty, 0x00ff00ff);
417 if (showrooms) {
418 shared_ptr<Room> r = map.roomAt(hand()->x, hand()->y);
419 for (std::vector<shared_ptr<Room> >::iterator i = cam->getMetaRoom()->rooms.begin();
420 i != cam->getMetaRoom()->rooms.end(); i++) {
421 unsigned int col = 0xFFFF00CC;
422 if (*i == r) col = 0xFF00FFCC;
423 else if (r) {
424 if ((**i).doors.find(r) != (**i).doors.end())
425 col = 0x00FFFFCC;
428 // rooms don't wrap over the boundary, so just draw twice
429 for (unsigned int z = 0; z < (m->wraparound() ? 2 : 1); z++) {
430 int newx = adjustx;
431 if (z == 1)
432 newx -= m->width();
434 (*i)->renderBorders(surface, newx, adjusty, col);
439 if (hand()->holdingWire) {
440 if (!hand()->wireOriginAgent) {
441 hand()->holdingWire = 0;
442 } else {
443 int x, y;
444 if (hand()->holdingWire == 1) {
445 // holding from outport
446 OutputPort *out = hand()->wireOriginAgent->outports[hand()->wireOriginID].get();
447 x = out->x; y = out->y;
448 } else {
449 // holding from inport
450 InputPort *in = hand()->wireOriginAgent->inports[hand()->wireOriginID].get();
451 x = in->x; y = in->y;
453 surface->renderLine(x + hand()->wireOriginAgent->x - adjustx,
454 y + hand()->wireOriginAgent->y - adjusty, hand()->x - adjustx, hand()->y - adjusty, 0x00ff00ff);
458 surface->renderDone();
461 void World::executeInitScript(fs::path p) {
462 assert(fs::exists(p));
463 assert(!fs::is_directory(p));
465 std::string x = p.native_file_string();
466 std::ifstream s(x.c_str());
467 assert(s.is_open());
468 //std::cout << "executing script " << x << "...\n";
469 //std::cout.flush(); std::cerr.flush();
470 try {
471 caosScript script(gametype, x);
472 script.parse(s);
473 caosVM vm(0);
474 script.installScripts();
475 vm.runEntirely(script.installer);
476 } catch (creaturesException &e) {
477 std::cerr << "exec of \"" << p.leaf() << "\" failed due to exception " << e.prettyPrint() << std::endl;
478 } catch (std::exception &e) {
479 std::cerr << "exec of \"" << p.leaf() << "\" failed due to exception " << e.what() << std::endl;
481 std::cout.flush(); std::cerr.flush();
484 void World::executeBootstrap(fs::path p) {
485 if (!fs::is_directory(p)) {
486 executeInitScript(p);
487 return;
490 std::vector<fs::path> scripts;
492 fs::directory_iterator fsend;
493 for (fs::directory_iterator d(p); d != fsend; ++d) {
494 if ((!fs::is_directory(*d)) && (fs::extension(*d) == ".cos"))
495 scripts.push_back(*d);
498 std::sort(scripts.begin(), scripts.end());
499 for (std::vector<fs::path>::iterator i = scripts.begin(); i != scripts.end(); i++)
500 executeInitScript(*i);
503 void World::executeBootstrap(bool switcher) {
504 if (engine.version < 3) {
505 // read from Eden.sfc
507 if (data_directories.size() == 0)
508 throw creaturesException("C1/2 can't run without data directories!");
510 // TODO: case-sensitivity for the lose
511 fs::path edenpath(data_directories[0] / "/Eden.sfc");
512 if (fs::exists(edenpath) && !fs::is_directory(edenpath)) {
513 SFCFile sfc;
514 std::ifstream f(edenpath.native_directory_string().c_str(), std::ios::binary);
515 f >> std::noskipws;
516 sfc.read(&f);
517 sfc.copyToWorld();
518 return;
519 } else
520 throw creaturesException("couldn't find file Eden.sfc, required for C1/2");
523 // TODO: this code is possibly wrong with multiple bootstrap directories
524 std::multimap<std::string, fs::path> bootstraps;
526 for (std::vector<fs::path>::iterator i = data_directories.begin(); i != data_directories.end(); i++) {
527 assert(fs::exists(*i));
528 assert(fs::is_directory(*i));
529 fs::path b(*i / "/Bootstrap/");
530 if (fs::exists(b) && fs::is_directory(b)) {
531 fs::directory_iterator fsend;
532 // iterate through each bootstrap directory
533 for (fs::directory_iterator d(b); d != fsend; ++d) {
534 if (fs::exists(*d) && fs::is_directory(*d)) {
535 std::string s = (*d).leaf();
536 // TODO: cvillage has switcher code in 'Startup', so i included it here too
537 if (s == "000 Switcher" || s == "Startup") {
538 if (!switcher) continue;
539 } else {
540 if (switcher) continue;
543 bootstraps.insert(std::pair<std::string, fs::path>(s, *d));
549 for (std::multimap<std::string, fs::path>::iterator i = bootstraps.begin(); i != bootstraps.end(); i++) {
550 executeBootstrap(i->second);
554 void World::initCatalogue() {
555 for (std::vector<fs::path>::iterator i = data_directories.begin(); i != data_directories.end(); i++) {
556 assert(fs::exists(*i));
557 assert(fs::is_directory(*i));
559 fs::path c(*i / "/Catalogue/");
560 if (fs::exists(c) && fs::is_directory(c))
561 catalogue.initFrom(c);
565 #include "PathResolver.h"
566 std::string World::findFile(std::string name) {
567 // Go backwards, so we find files in more 'modern' directories first..
568 for (int i = data_directories.size() - 1; i != -1; i--) {
569 fs::path p = data_directories[i];
570 std::string r = (p / fs::path(name, fs::native)).native_directory_string();
571 if (resolveFile(r))
572 return r;
575 return "";
578 std::vector<std::string> World::findFiles(std::string dir, std::string wild) {
579 std::vector<std::string> possibles;
581 // Go backwards, so we find files in more 'modern' directories first..
582 for (int i = data_directories.size() - 1; i != -1; i--) {
583 fs::path p = data_directories[i];
584 std::string r = (p / fs::path(dir, fs::native)).native_directory_string();
585 std::vector<std::string> results = findByWildcard(r, wild);
586 possibles.insert(possibles.end(), results.begin(), results.end()); // merge results
589 return possibles;
592 std::string World::getUserDataDir() {
593 return (data_directories.end() - 1)->native_directory_string();
596 void World::selectCreature(boost::shared_ptr<Agent> a) {
597 if (a) {
598 CreatureAgent *c = dynamic_cast<CreatureAgent *>(a.get());
599 caos_assert(c);
602 if (selectedcreature != a) {
603 for (std::list<boost::shared_ptr<Agent> >::iterator i = world.agents.begin(); i != world.agents.end(); i++) {
604 if (!*i) continue;
605 (*i)->queueScript(120, 0, caosVar(a), caosVar(selectedcreature)); // selected creature changed
608 selectedcreature = a;
612 shared_ptr<genomeFile> World::loadGenome(std::string &genefile) {
613 std::vector<std::string> possibles = findFiles("/Genetics/", genefile + ".gen");
614 if (possibles.empty()) return shared_ptr<genomeFile>();
615 genefile = possibles[(int)((float)possibles.size() * (rand() / (RAND_MAX + 1.0)))];
617 shared_ptr<genomeFile> p(new genomeFile());
618 std::ifstream gfile(genefile.c_str(), std::ios::binary);
619 caos_assert(gfile.is_open());
620 gfile >> std::noskipws;
621 gfile >> *(p.get());
623 return p;
626 void World::newMoniker(shared_ptr<genomeFile> g, std::string genefile, AgentRef agent) {
627 std::string d = history.newMoniker(g);
628 world.history.getMoniker(d).addEvent(2, "", genefile);
629 world.history.getMoniker(d).moveToAgent(agent);
632 std::string World::generateMoniker(std::string basename) {
633 if (engine.version < 3) {
634 /* old-style monikers are four characters in a format like 9GVC */
635 unsigned int n = 1 + (unsigned int)(9.0 * (rand() / (RAND_MAX + 1.0)));
636 std::string moniker = boost::str(boost::format("%d") % n);
637 for (unsigned int i = 0; i < 3; i++) {
638 unsigned int n = (unsigned int)(26.0 * (rand() / (RAND_MAX + 1.0)));
639 moniker += boost::str(boost::format("%c") % (char)('A' + n));
641 return moniker;
644 // TODO: is there a better way to handle this? incoming basename is from catalogue files..
645 if (basename.size() != 4) {
646 std::cout << "World::generateMoniker got passed '" << basename << "' as a basename which isn't 4 characters, so ignoring it" << std::endl;
647 basename = "xxxx";
650 std::string x = basename;
651 for (unsigned int i = 0; i < 4; i++) {
652 unsigned int n = (unsigned int) (0xfffff * (rand() / (RAND_MAX + 1.0)));
653 x = x + "-" + boost::str(boost::format("%05x") % n);
656 return x;
659 boost::shared_ptr<AudioSource> World::playAudio(std::string filename, AgentRef agent, bool controlled, bool loop, bool followviewport) {
660 if (filename.size() == 0) return boost::shared_ptr<AudioSource>();
662 boost::shared_ptr<AudioSource> sound = engine.audio->newSource();
663 if (!sound) return boost::shared_ptr<AudioSource>();
665 AudioClip clip = engine.audio->loadClip(filename);
666 if (!clip) {
667 // note that more specific error messages can be thrown by implementations of loadClip
668 if (engine.version < 3) return boost::shared_ptr<AudioSource>(); // creatures 1 and 2 ignore non-existent audio clips
669 throw creaturesException("failed to load audio clip " + filename);
672 sound->setClip(clip);
674 if (loop) {
675 assert(controlled);
676 sound->setLooping(true);
679 if (agent) {
680 assert(!followviewport);
682 agent->updateAudio(sound);
683 if (controlled)
684 agent->sound = sound;
685 else
686 uncontrolled_sounds.push_back(std::pair<boost::shared_ptr<class AudioSource>, bool>(sound, false));
687 } else {
688 assert(!controlled);
690 // TODO: handle non-agent sounds
691 sound->setPos(world.camera.getXCentre(), world.camera.getYCentre(), 0);
692 uncontrolled_sounds.push_back(std::pair<boost::shared_ptr<class AudioSource>, bool>(sound, followviewport));
695 sound->play();
697 return sound;
700 int World::findCategory(unsigned char family, unsigned char genus, unsigned short species) {
701 if (!catalogue.hasTag("Agent Classifiers")) return -1;
703 const std::vector<std::string> &t = catalogue.getTag("Agent Classifiers");
705 for (unsigned int i = 0; i < t.size(); i++) {
706 std::string buffer = boost::str(boost::format("%d %d %d") % (int)family % (int)genus % (int)species);
707 if (t[i] == buffer) return i;
708 buffer = boost::str(boost::format("%d %d 0") % (int)family % (int)genus);
709 if (t[i] == buffer) return i;
710 buffer = boost::str(boost::format("%d 0 0") % (int)family);
711 if (t[i] == buffer) return i;
712 // leave it here: 0 0 0 would be silly to have in Agent Classifiers.
715 return -1;
718 /* vim: set noet: */