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.
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"
30 #include "AudioBackend.h"
34 #include "Catalogue.h"
36 #include <boost/format.hpp>
37 #include <boost/filesystem/convenience.hpp>
38 namespace fs
= boost::filesystem
;
46 race
= 50; // sensible default?
47 pace
= 0.0f
; // sensible default?
48 quitting
= saving
= false;
57 for (std::vector
<caosVM
*>::iterator i
= vmpool
.begin(); i
!= vmpool
.end(); i
++)
61 // annoyingly, if we put this in the constructor, the catalogue isn't available yet
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]);
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)
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.
81 shared_ptr
<creaturesImage
> img
;
83 img
= gallery
.getImage("hand"); // as used in C3 and DS
85 img
= gallery
.getImage("syst"); // as used in C1, C2 and CV
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
;
92 if (engine
.version
> 2)
93 std::cout
<< "Couldn't find a valid \"Pointer Information\" catalogue tag, and 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
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
;
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)
124 timeofday
= dayofseason
= season
= year
= 0;
127 void World::shutdown() {
131 caosVM
*World::getVM(Agent
*a
) {
132 if (vmpool
.empty()) {
133 return new caosVM(a
);
135 caosVM
*x
= vmpool
.back();
142 void World::freeVM(caosVM
*v
) {
147 void World::queueScript(unsigned short event
, AgentRef agent
, AgentRef from
, caosVar p0
, caosVar 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?).
165 CompoundAgent
*c
= dynamic_cast<CompoundAgent
*>(focusagent
.get());
167 TextEntryPart
*p
= dynamic_cast<TextEntryPart
*>(c
->part(focuspart
));
176 focusagent
= p
->getParent();
182 if (saving
) {} // TODO: save
184 // due to destruction ordering we must explicitly destroy all agents here
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
);
201 si
->first
->setPos(world
.camera
.getXCentre(), world
.camera
.getYCentre(), 0);
203 // mute/unmute off-screen uncontrolled audio if necessary
205 si
->first
->getPos(x
, y
, z
);
206 si
->first
->setMute(world
.camera
.getMetaRoom() != world
.map
.metaRoomAt(x
, y
));
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
;
218 std::list
<boost::shared_ptr
<Agent
> >::iterator i2
= i
;
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();
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) {
240 agent
->fireScript(i
->scriptno
, i
->from
, i
->p
[0], i
->p
[1]);
244 scriptqueue
= newqueue
;
249 if (engine
.version
== 2) {
250 if (worldtickcount
% 3600 == 0) {
252 if (timeofday
== 5) { // 5 parts of the day
256 if (dayofseason
== 4) { // 4 days per season
260 if (season
== 4) { // 4 seasons per year
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
);
277 return p
->getParent();
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
))
298 if (needs_mouseable
&& !((*i
)->getParent()->mouseable()))
300 if (!obey_all_transparency
)
301 if ((*i
)->getParent() != transagent
)
310 void World::setUNID(Agent
*whofor
, int unid
) {
311 assert(whofor
->shared_from_this() == unidmap
[unid
].lock() || unidmap
[unid
].expired());
313 unidmap
[unid
] = whofor
->shared_from_this();
316 int World::newUNID(Agent
*whofor
) {
319 if (unid
&& unidmap
[unid
].expired()) {
320 setUNID(whofor
, unid
);
326 void World::freeUNID(int 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
) {
342 MetaRoom
*m
= cam
->getMetaRoom();
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();
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..
358 assert(bkgd
->numframes() > 0);
360 int sprwidth
= bkgd
->width(0);
361 int sprheight
= bkgd
->height(0);
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
;
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);
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;
424 if ((**i
).doors
.find(r
) != (**i
).doors
.end())
428 // rooms don't wrap over the boundary, so just draw twice
429 for (unsigned int z
= 0; z
< (m
->wraparound() ? 2 : 1); z
++) {
434 (*i
)->renderBorders(surface
, newx
, adjusty
, col
);
439 if (hand()->holdingWire
) {
440 if (!hand()->wireOriginAgent
) {
441 hand()->holdingWire
= 0;
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
;
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());
468 //std::cout << "executing script " << x << "...\n";
469 //std::cout.flush(); std::cerr.flush();
471 caosScript
script(gametype
, x
);
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
);
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
)) {
514 std::ifstream
f(edenpath
.native_directory_string().c_str(), std::ios::binary
);
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;
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();
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
592 std::string
World::getUserDataDir() {
593 return (data_directories
.end() - 1)->native_directory_string();
596 void World::selectCreature(boost::shared_ptr
<Agent
> a
) {
598 CreatureAgent
*c
= dynamic_cast<CreatureAgent
*>(a
.get());
602 if (selectedcreature
!= a
) {
603 for (std::list
<boost::shared_ptr
<Agent
> >::iterator i
= world
.agents
.begin(); i
!= world
.agents
.end(); i
++) {
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
;
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
));
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
;
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
);
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
);
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
);
676 sound
->setLooping(true);
680 assert(!followviewport
);
682 agent
->updateAudio(sound
);
684 agent
->sound
= sound
;
686 uncontrolled_sounds
.push_back(std::pair
<boost::shared_ptr
<class AudioSource
>, bool>(sound
, false));
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
));
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.