support generating C1/C2-style monikers
[openc2e.git] / World.cpp
blob40a2f48aad61d71b0349c17bf6b102997f85dd0e
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 collision 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() && i->scriptno == 6) {
232 continue;
235 agent->fireScript(i->scriptno, i->from, i->p[0], i->p[1]);
238 scriptqueue.clear();
239 scriptqueue = newqueue;
241 tickcount++;
242 worldtickcount++;
244 if (engine.version == 2) {
245 if (worldtickcount % 3600 == 0) {
246 timeofday++;
247 if (timeofday == 5) { // 5 parts of the day
248 timeofday = 0;
249 dayofseason++;
251 if (dayofseason == 4) { // 4 days per season
252 dayofseason = 0;
253 season++;
255 if (season == 4) { // 4 seasons per year
256 season = 0;
257 year++;
262 world.map.tick();
264 // TODO: correct behaviour? hrm :/
265 world.hand()->velx.setFloat(world.hand()->velx.getFloat() / 2.0f);
266 world.hand()->vely.setFloat(world.hand()->vely.getFloat() / 2.0f);
269 Agent *World::agentAt(unsigned int x, unsigned int y, bool obey_all_transparency, bool needs_mouseable) {
270 CompoundPart *p = partAt(x, y, obey_all_transparency, needs_mouseable);
271 if (p)
272 return p->getParent();
273 else
274 return 0;
277 CompoundPart *World::partAt(unsigned int x, unsigned int y, bool obey_all_transparency, bool needs_mouseable) {
278 Agent *transagent = 0;
279 if (!obey_all_transparency)
280 transagent = agentAt(x, y, true, needs_mouseable);
282 for (std::multiset<CompoundPart *, partzorder>::iterator i = zorder.begin(); i != zorder.end(); i++) {
283 int ax = (int)(x - (*i)->getParent()->x);
284 int ay = (int)(y - (*i)->getParent()->y);
285 if ((*i)->x <= ax) if ((*i)->y <= ay) if (((*i) -> x + (int)(*i)->getWidth()) >= ax) if (((*i) -> y + (int)(*i)->getHeight()) >= ay)
286 if ((*i)->getParent() != theHand) {
287 SpritePart *s = dynamic_cast<SpritePart *>(*i);
288 if (s && s->isTransparent() && obey_all_transparency)
289 // transparent parts in C1/C2 are scenery
290 // TODO: always true? you can't sekritly set parts to be transparent in C2?
291 if (engine.version < 3 || s->transparentAt(ax - s->x, ay - s->y))
292 continue;
293 if (needs_mouseable && !((*i)->getParent()->mouseable()))
294 continue;
295 if (!obey_all_transparency)
296 if ((*i)->getParent() != transagent)
297 continue;
298 return *i;
302 return 0;
305 void World::setUNID(Agent *whofor, int unid) {
306 assert(whofor->shared_from_this() == unidmap[unid].lock() || unidmap[unid].expired());
307 whofor->unid = unid;
308 unidmap[unid] = whofor->shared_from_this();
311 int World::newUNID(Agent *whofor) {
312 do {
313 int unid = rand();
314 if (unid && unidmap[unid].expired()) {
315 setUNID(whofor, unid);
316 return unid;
318 } while (1);
321 void World::freeUNID(int unid) {
322 unidmap.erase(unid);
325 shared_ptr<Agent> World::lookupUNID(int unid) {
326 if (unid == 0) return shared_ptr<Agent>();
327 return unidmap[unid].lock();
330 void World::drawWorld() {
331 drawWorld(&camera, engine.backend->getMainSurface());
334 void World::drawWorld(Camera *cam, Surface *surface) {
335 assert(surface);
337 MetaRoom *m = cam->getMetaRoom();
338 if (!m) {
339 // Whoops - the room we're in vanished, or maybe we were never in one?
340 // Try to get a new one ...
341 m = map.getFallbackMetaroom();
342 if (!m)
343 throw creaturesException("drawWorld() couldn't find any metarooms");
344 cam->goToMetaRoom(m->id);
346 int adjustx = cam->getX();
347 int adjusty = cam->getY();
348 shared_ptr<creaturesImage> bkgd = m->getBackground(""); // TODO
350 // TODO: work out what c2e does when it doesn't have a background..
351 if (!bkgd) return;
353 assert(bkgd->numframes() > 0);
355 int sprwidth = bkgd->width(0);
356 int sprheight = bkgd->height(0);
358 // draw the blk
359 for (unsigned int i = 0; i < (m->fullheight() / sprheight); i++) {
360 for (unsigned int j = 0; j < (m->fullwidth() / sprwidth); j++) {
361 // figure out which block number to use
362 unsigned int whereweare = j * (m->fullheight() / sprheight) + i;
364 // make one pass for non-wraparound rooms, or two passes for wraparound ones
365 // TODO: implement this in a more sensible way, or at least optimise it
366 for (unsigned int z = 0; z < (m->wraparound() ? 2 : 1); z++) {
367 int destx = (j * sprwidth) - adjustx + m->x();
368 int desty = (i * sprheight) - adjusty + m->y();
370 // if we're on the second pass, render to the *right* of the normal area
371 if (z == 1) destx += m->width();
373 // if the block's on screen, render it.
374 if ((destx >= -sprwidth) && (desty >= -sprheight) &&
375 (destx - sprwidth <= (int)surface->getWidth()) &&
376 (desty - sprheight <= (int)surface->getHeight()))
377 surface->render(bkgd, whereweare, destx, desty, false, 0, false, true);
382 // render all the agents
383 for (std::multiset<renderable *, renderablezorder>::iterator i = renders.begin(); i != renders.end(); i++) {
384 if ((*i)->showOnRemoteCameras() || cam == &camera) {
385 // three-pass for wraparound rooms, the third since agents often straddle the boundary
386 // TODO: same as above with background rendering
387 for (unsigned int z = 0; z < (m->wraparound() ? 3 : 1); z++) {
388 int newx = -adjustx, newy = -adjusty;
389 if (z == 1) newx += m->width();
390 else if (z == 2) newx -= m->width();
391 (*i)->render(surface, newx, newy);
396 // render port connection lines. TODO: these should be rendered as some kind
397 // of renderable, not directly like this.
398 for (std::list<boost::shared_ptr<Agent> >::iterator i = world.agents.begin(); i != world.agents.end(); i++) {
399 boost::shared_ptr<Agent> a = *i;
400 if (!a) continue;
401 for (std::map<unsigned int, boost::shared_ptr<OutputPort> >::iterator p = a->outports.begin();
402 p != a->outports.end(); p++) {
403 for (PortConnectionList::iterator c = p->second->dests.begin(); c != p->second->dests.end(); c++) {
404 if (!c->first) continue;
405 InputPort *target = c->first->inports[c->second].get();
406 surface->renderLine(a->x + p->second->x - adjustx, a->y + p->second->y - adjusty,
407 c->first->x + target->x - adjustx, c->first->y + target->y - adjusty, 0x00ff00ff);
412 if (showrooms) {
413 shared_ptr<Room> r = map.roomAt(hand()->x, hand()->y);
414 for (std::vector<shared_ptr<Room> >::iterator i = cam->getMetaRoom()->rooms.begin();
415 i != cam->getMetaRoom()->rooms.end(); i++) {
416 unsigned int col = 0xFFFF00CC;
417 if (*i == r) col = 0xFF00FFCC;
418 else if (r) {
419 if ((**i).doors.find(r) != (**i).doors.end())
420 col = 0x00FFFFCC;
423 // rooms don't wrap over the boundary, so just draw twice
424 for (unsigned int z = 0; z < (m->wraparound() ? 2 : 1); z++) {
425 int newx = adjustx;
426 if (z == 1)
427 newx -= m->width();
429 (*i)->renderBorders(surface, newx, adjusty, col);
434 if (hand()->holdingWire) {
435 if (!hand()->wireOriginAgent) {
436 hand()->holdingWire = 0;
437 } else {
438 int x, y;
439 if (hand()->holdingWire == 1) {
440 // holding from outport
441 OutputPort *out = hand()->wireOriginAgent->outports[hand()->wireOriginID].get();
442 x = out->x; y = out->y;
443 } else {
444 // holding from inport
445 InputPort *in = hand()->wireOriginAgent->inports[hand()->wireOriginID].get();
446 x = in->x; y = in->y;
448 surface->renderLine(x + hand()->wireOriginAgent->x - adjustx,
449 y + hand()->wireOriginAgent->y - adjusty, hand()->x - adjustx, hand()->y - adjusty, 0x00ff00ff);
453 surface->renderDone();
456 void World::executeInitScript(fs::path p) {
457 assert(fs::exists(p));
458 assert(!fs::is_directory(p));
460 std::string x = p.native_file_string();
461 std::ifstream s(x.c_str());
462 assert(s.is_open());
463 //std::cout << "executing script " << x << "...\n";
464 //std::cout.flush(); std::cerr.flush();
465 try {
466 caosScript script(gametype, x);
467 script.parse(s);
468 caosVM vm(0);
469 script.installScripts();
470 vm.runEntirely(script.installer);
471 } catch (creaturesException &e) {
472 std::cerr << "exec of \"" << p.leaf() << "\" failed due to exception " << e.prettyPrint() << std::endl;
473 } catch (std::exception &e) {
474 std::cerr << "exec of \"" << p.leaf() << "\" failed due to exception " << e.what() << std::endl;
476 std::cout.flush(); std::cerr.flush();
479 void World::executeBootstrap(fs::path p) {
480 if (!fs::is_directory(p)) {
481 executeInitScript(p);
482 return;
485 std::vector<fs::path> scripts;
487 fs::directory_iterator fsend;
488 for (fs::directory_iterator d(p); d != fsend; ++d) {
489 if ((!fs::is_directory(*d)) && (fs::extension(*d) == ".cos"))
490 scripts.push_back(*d);
493 std::sort(scripts.begin(), scripts.end());
494 for (std::vector<fs::path>::iterator i = scripts.begin(); i != scripts.end(); i++)
495 executeInitScript(*i);
498 void World::executeBootstrap(bool switcher) {
499 if (engine.version < 3) {
500 // read from Eden.sfc
502 if (data_directories.size() == 0)
503 throw creaturesException("C1/2 can't run without data directories!");
505 // TODO: case-sensitivity for the lose
506 fs::path edenpath(data_directories[0] / "/Eden.sfc");
507 if (fs::exists(edenpath) && !fs::is_directory(edenpath)) {
508 SFCFile sfc;
509 std::ifstream f(edenpath.native_directory_string().c_str(), std::ios::binary);
510 f >> std::noskipws;
511 sfc.read(&f);
512 sfc.copyToWorld();
513 return;
514 } else
515 throw creaturesException("couldn't find file Eden.sfc, required for C1/2");
518 // TODO: this code is possibly wrong with multiple bootstrap directories
519 std::multimap<std::string, fs::path> bootstraps;
521 for (std::vector<fs::path>::iterator i = data_directories.begin(); i != data_directories.end(); i++) {
522 assert(fs::exists(*i));
523 assert(fs::is_directory(*i));
524 fs::path b(*i / "/Bootstrap/");
525 if (fs::exists(b) && fs::is_directory(b)) {
526 fs::directory_iterator fsend;
527 // iterate through each bootstrap directory
528 for (fs::directory_iterator d(b); d != fsend; ++d) {
529 if (fs::exists(*d) && fs::is_directory(*d)) {
530 std::string s = (*d).leaf();
531 // TODO: cvillage has switcher code in 'Startup', so i included it here too
532 if (s == "000 Switcher" || s == "Startup") {
533 if (!switcher) continue;
534 } else {
535 if (switcher) continue;
538 bootstraps.insert(std::pair<std::string, fs::path>(s, *d));
544 for (std::multimap<std::string, fs::path>::iterator i = bootstraps.begin(); i != bootstraps.end(); i++) {
545 executeBootstrap(i->second);
549 void World::initCatalogue() {
550 for (std::vector<fs::path>::iterator i = data_directories.begin(); i != data_directories.end(); i++) {
551 assert(fs::exists(*i));
552 assert(fs::is_directory(*i));
554 fs::path c(*i / "/Catalogue/");
555 if (fs::exists(c) && fs::is_directory(c))
556 catalogue.initFrom(c);
560 #include "PathResolver.h"
561 std::string World::findFile(std::string name) {
562 // Go backwards, so we find files in more 'modern' directories first..
563 for (int i = data_directories.size() - 1; i != -1; i--) {
564 fs::path p = data_directories[i];
565 std::string r = (p / fs::path(name, fs::native)).native_directory_string();
566 if (resolveFile(r))
567 return r;
570 return "";
573 std::vector<std::string> World::findFiles(std::string dir, std::string wild) {
574 std::vector<std::string> possibles;
576 // Go backwards, so we find files in more 'modern' directories first..
577 for (int i = data_directories.size() - 1; i != -1; i--) {
578 fs::path p = data_directories[i];
579 std::string r = (p / fs::path(dir, fs::native)).native_directory_string();
580 std::vector<std::string> results = findByWildcard(r, wild);
581 possibles.insert(possibles.end(), results.begin(), results.end()); // merge results
584 return possibles;
587 std::string World::getUserDataDir() {
588 return (data_directories.end() - 1)->native_directory_string();
591 void World::selectCreature(boost::shared_ptr<Agent> a) {
592 if (a) {
593 CreatureAgent *c = dynamic_cast<CreatureAgent *>(a.get());
594 caos_assert(c);
597 if (selectedcreature != a) {
598 for (std::list<boost::shared_ptr<Agent> >::iterator i = world.agents.begin(); i != world.agents.end(); i++) {
599 if (!*i) continue;
600 (*i)->queueScript(120, 0, caosVar(a), caosVar(selectedcreature)); // selected creature changed
603 selectedcreature = a;
607 shared_ptr<genomeFile> World::loadGenome(std::string &genefile) {
608 std::vector<std::string> possibles = findFiles("/Genetics/", genefile + ".gen");
609 if (possibles.empty()) return shared_ptr<genomeFile>();
610 genefile = possibles[(int)((float)possibles.size() * (rand() / (RAND_MAX + 1.0)))];
612 shared_ptr<genomeFile> p(new genomeFile());
613 std::ifstream gfile(genefile.c_str(), std::ios::binary);
614 caos_assert(gfile.is_open());
615 gfile >> std::noskipws;
616 gfile >> *(p.get());
618 return p;
621 void World::newMoniker(shared_ptr<genomeFile> g, std::string genefile, AgentRef agent) {
622 std::string d = history.newMoniker(g);
623 world.history.getMoniker(d).addEvent(2, "", genefile);
624 world.history.getMoniker(d).moveToAgent(agent);
627 std::string World::generateMoniker(std::string basename) {
628 if (engine.version < 3) {
629 /* old-style monikers are four characters in a format like 9GVC */
630 unsigned int n = 1 + (unsigned int)(9.0 * (rand() / (RAND_MAX + 1.0)));
631 std::string moniker = boost::str(boost::format("%d") % n);
632 for (unsigned int i = 0; i < 3; i++) {
633 unsigned int n = (unsigned int)(26.0 * (rand() / (RAND_MAX + 1.0)));
634 moniker += boost::str(boost::format("%c") % (char)('A' + n));
636 return moniker;
639 // TODO: is there a better way to handle this? incoming basename is from catalogue files..
640 if (basename.size() != 4) {
641 std::cout << "World::generateMoniker got passed '" << basename << "' as a basename which isn't 4 characters, so ignoring it" << std::endl;
642 basename = "xxxx";
645 std::string x = basename;
646 for (unsigned int i = 0; i < 4; i++) {
647 unsigned int n = (unsigned int) (0xfffff * (rand() / (RAND_MAX + 1.0)));
648 x = x + "-" + boost::str(boost::format("%05x") % n);
651 return x;
654 boost::shared_ptr<AudioSource> World::playAudio(std::string filename, AgentRef agent, bool controlled, bool loop) {
655 if (filename.size() == 0) return boost::shared_ptr<AudioSource>();
657 boost::shared_ptr<AudioSource> sound = engine.audio->newSource();
658 if (!sound) return boost::shared_ptr<AudioSource>();
660 AudioClip clip = engine.audio->loadClip(filename);
661 if (!clip) {
662 // note that more specific error messages can be thrown by implementations of loadClip
663 if (engine.version < 3) return boost::shared_ptr<AudioSource>(); // creatures 1 and 2 ignore non-existent audio clips
664 throw creaturesException("failed to load audio clip " + filename);
667 sound->setClip(clip);
669 if (loop) {
670 assert(controlled);
671 sound->setLooping(true);
674 if (agent) {
675 agent->updateAudio(sound);
676 if (controlled)
677 agent->sound = sound;
678 else
679 uncontrolled_sounds.push_back(sound);
680 } else {
681 assert(!controlled);
683 // TODO: handle non-agent sounds
684 sound->setPos(world.camera.getXCentre(), world.camera.getYCentre(), 0);
685 uncontrolled_sounds.push_back(sound);
688 sound->play();
690 return sound;
693 int World::findCategory(unsigned char family, unsigned char genus, unsigned short species) {
694 if (!catalogue.hasTag("Agent Classifiers")) return -1;
696 const std::vector<std::string> &t = catalogue.getTag("Agent Classifiers");
698 for (unsigned int i = 0; i < t.size(); i++) {
699 std::string buffer = boost::str(boost::format("%d %d %d") % (int)family % (int)genus % (int)species);
700 if (t[i] == buffer) return i;
701 buffer = boost::str(boost::format("%d %d 0") % (int)family % (int)genus);
702 if (t[i] == buffer) return i;
703 buffer = boost::str(boost::format("%d 0 0") % (int)family);
704 if (t[i] == buffer) return i;
705 // leave it here: 0 0 0 would be silly to have in Agent Classifiers.
708 return -1;
711 /* vim: set noet: */