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 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
);
237 agent
->fireScript(i
->scriptno
, i
->from
, i
->p
[0], i
->p
[1]);
241 scriptqueue
= newqueue
;
246 if (engine
.version
== 2) {
247 if (worldtickcount
% 3600 == 0) {
249 if (timeofday
== 5) { // 5 parts of the day
253 if (dayofseason
== 4) { // 4 days per season
257 if (season
== 4) { // 4 seasons per year
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
);
274 return p
->getParent();
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
))
295 if (needs_mouseable
&& !((*i
)->getParent()->mouseable()))
297 if (!obey_all_transparency
)
298 if ((*i
)->getParent() != transagent
)
307 void World::setUNID(Agent
*whofor
, int unid
) {
308 assert(whofor
->shared_from_this() == unidmap
[unid
].lock() || unidmap
[unid
].expired());
310 unidmap
[unid
] = whofor
->shared_from_this();
313 int World::newUNID(Agent
*whofor
) {
316 if (unid
&& unidmap
[unid
].expired()) {
317 setUNID(whofor
, unid
);
323 void World::freeUNID(int 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
) {
339 MetaRoom
*m
= cam
->getMetaRoom();
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();
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..
355 assert(bkgd
->numframes() > 0);
357 int sprwidth
= bkgd
->width(0);
358 int sprheight
= bkgd
->height(0);
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
);
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;
405 if ((**i
).doors
.find(r
) != (**i
).doors
.end())
409 // rooms don't wrap over the boundary, so just draw twice
410 for (unsigned int z
= 0; z
< (m
->wraparound() ? 2 : 1); z
++) {
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());
430 //std::cout << "executing script " << x << "...\n";
431 //std::cout.flush(); std::cerr.flush();
433 caosScript
script(gametype
, x
);
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
);
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
)) {
476 std::ifstream
f(edenpath
.native_directory_string().c_str(), std::ios::binary
);
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;
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();
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
554 std::string
World::getUserDataDir() {
555 return (data_directories
.end() - 1)->native_directory_string();
558 void World::selectCreature(boost::shared_ptr
<Agent
> a
) {
560 CreatureAgent
*c
= dynamic_cast<CreatureAgent
*>(a
.get());
564 if (selectedcreature
!= a
) {
565 for (std::list
<boost::shared_ptr
<Agent
> >::iterator i
= world
.agents
.begin(); i
!= world
.agents
.end(); i
++) {
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
;
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
;
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
);
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
);
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
);
627 sound
->setLooping(true);
631 agent
->updateAudio(sound
);
633 agent
->sound
= sound
;
635 uncontrolled_sounds
.push_back(sound
);
639 // TODO: handle non-agent sounds
640 sound
->setPos(world
.camera
.getXCentre(), world
.camera
.getYCentre(), 0);
641 uncontrolled_sounds
.push_back(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.