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
<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
);
199 // mute/unmute off-screen uncontrolled audio if necessary
201 (*si
)->getPos(x
, y
, z
);
202 (*si
)->setMute(world
.camera
.getMetaRoom() != world
.map
.metaRoomAt(x
, y
));
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
;
213 std::list
<boost::shared_ptr
<Agent
> >::iterator i2
= i
;
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();
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) {
235 agent
->fireScript(i
->scriptno
, i
->from
, i
->p
[0], i
->p
[1]);
239 scriptqueue
= newqueue
;
244 if (engine
.version
== 2) {
245 if (worldtickcount
% 3600 == 0) {
247 if (timeofday
== 5) { // 5 parts of the day
251 if (dayofseason
== 4) { // 4 days per season
255 if (season
== 4) { // 4 seasons per year
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
);
272 return p
->getParent();
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
))
293 if (needs_mouseable
&& !((*i
)->getParent()->mouseable()))
295 if (!obey_all_transparency
)
296 if ((*i
)->getParent() != transagent
)
305 void World::setUNID(Agent
*whofor
, int unid
) {
306 assert(whofor
->shared_from_this() == unidmap
[unid
].lock() || unidmap
[unid
].expired());
308 unidmap
[unid
] = whofor
->shared_from_this();
311 int World::newUNID(Agent
*whofor
) {
314 if (unid
&& unidmap
[unid
].expired()) {
315 setUNID(whofor
, unid
);
321 void World::freeUNID(int 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
) {
337 MetaRoom
*m
= cam
->getMetaRoom();
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();
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..
353 assert(bkgd
->numframes() > 0);
355 int sprwidth
= bkgd
->width(0);
356 int sprheight
= bkgd
->height(0);
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
;
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);
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;
419 if ((**i
).doors
.find(r
) != (**i
).doors
.end())
423 // rooms don't wrap over the boundary, so just draw twice
424 for (unsigned int z
= 0; z
< (m
->wraparound() ? 2 : 1); z
++) {
429 (*i
)->renderBorders(surface
, newx
, adjusty
, col
);
434 if (hand()->holdingWire
) {
435 if (!hand()->wireOriginAgent
) {
436 hand()->holdingWire
= 0;
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
;
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());
463 //std::cout << "executing script " << x << "...\n";
464 //std::cout.flush(); std::cerr.flush();
466 caosScript
script(gametype
, x
);
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
);
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
)) {
509 std::ifstream
f(edenpath
.native_directory_string().c_str(), std::ios::binary
);
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;
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();
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
587 std::string
World::getUserDataDir() {
588 return (data_directories
.end() - 1)->native_directory_string();
591 void World::selectCreature(boost::shared_ptr
<Agent
> a
) {
593 CreatureAgent
*c
= dynamic_cast<CreatureAgent
*>(a
.get());
597 if (selectedcreature
!= a
) {
598 for (std::list
<boost::shared_ptr
<Agent
> >::iterator i
= world
.agents
.begin(); i
!= world
.agents
.end(); i
++) {
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
;
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
));
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
;
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
);
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
);
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
);
671 sound
->setLooping(true);
675 agent
->updateAudio(sound
);
677 agent
->sound
= sound
;
679 uncontrolled_sounds
.push_back(sound
);
683 // TODO: handle non-agent sounds
684 sound
->setPos(world
.camera
.getXCentre(), world
.camera
.getYCentre(), 0);
685 uncontrolled_sounds
.push_back(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.