xoreos  0.0.5
campaign.cpp
Go to the documentation of this file.
1 /* xoreos - A reimplementation of BioWare's Aurora engine
2  *
3  * xoreos is the legal property of its developers, whose names
4  * can be found in the AUTHORS file distributed with this source
5  * distribution.
6  *
7  * xoreos is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 3
10  * of the License, or (at your option) any later version.
11  *
12  * xoreos is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with xoreos. If not, see <http://www.gnu.org/licenses/>.
19  */
20 
25 #include "src/common/scopedptr.h"
26 #include "src/common/util.h"
27 #include "src/common/strutil.h"
28 #include "src/common/error.h"
29 #include "src/common/filepath.h"
30 #include "src/common/readfile.h"
31 #include "src/common/xml.h"
32 
33 #include "src/aurora/2dareg.h"
34 #include "src/aurora/gff4file.h"
35 
36 #include "src/graphics/camera.h"
37 
42 
43 namespace Engines {
44 
45 namespace DragonAge {
46 
47 static const uint32 kCIFID = MKTAG('C', 'I', 'F', ' ');
48 static const uint32 kVersion01 = MKTAG('V', '0', '.', '1');
49 
50 using ::Aurora::GFF4File;
51 using ::Aurora::GFF4Struct;
53 
54 using namespace ::Aurora::GFF4FieldNamesEnum;
55 
56 
57 Campaign::RIMNode::RIMNode(const RIMNode *p) : parent(p) {
58 }
59 
60 
61 Campaign::Campaign(Game &game, const Common::UString &cifPath,
62  const Common::UString &manifestPath, const Common::UString &addinBase) :
63  Object(kObjectTypeModule), _game(&game), _cifPath(cifPath),
64  _addinBase(addinBase), _enabled(false), _bioware(false), _needsAuth(false),
65  _priority(0xFFFFFFFF), _format(0xFFFFFFFF), _state(0xFFFFFFFF),
66  _loaded(false), _pc(0) {
67 
68  _entryPosition[0] = 0.0;
69  _entryPosition[1] = 0.0;
70  _entryPosition[2] = 0.0;
71 
72  _entryOrientation[0] = 0.0;
73  _entryOrientation[1] = 0.0;
74  _entryOrientation[2] = 0.0;
75 
76  read(cifPath, manifestPath);
77 }
78 
80  try {
81  unload();
82  } catch (...) {
83  }
84 }
85 
87  return _uid;
88 }
89 
91  return _name;
92 }
93 
95  return _description;
96 }
97 
99  return _extends;
100 }
101 
102 void Campaign::read(const Common::UString &cifPath, const Common::UString &manifestPath) {
103  if (Common::FilePath::getStem(cifPath) == "singleplayer") {
104  // Original campaign
105 
106  if (!manifestPath.empty())
107  throw Common::Exception("Original campaign with a manifest.xml?!?");
108 
109  _uid = "Single Player";
110 
111  _enabled = true;
112 
113  _state = 2;
114  _format = 1;
115 
116  } else {
117  // Expansion, DLC or community module
118 
119  if (manifestPath.empty())
120  throw Common::Exception("DLC without a manifest.xml");
121  }
122 
123  readCIFStatic(cifPath);
124  readManifest(manifestPath);
125 
126  if (_uid.empty())
127  throw Common::Exception("No UID");
128 
129  if ((_state != 2) || (_format != 1))
130  throw Common::Exception("Unsupported manifest format (%u.%u)", _format, _state);
131 
132  if (_extends.empty() && _entryArea.empty())
133  throw Common::Exception("Playable campaign without an entry area");
134 
135  if (_needsAuth)
136  throw Common::Exception("TODO: Needs authorization");
137 }
138 
140  Common::ReadFile *cifFile = new Common::ReadFile(path);
141 
142  GFF4File cif(cifFile, kCIFID);
143  if (cif.getTypeVersion() != kVersion01)
144  throw Common::Exception("Unsupported CIF version %s", Common::debugTag(cif.getTypeVersion()).c_str());
145 
146  const GFF4Struct &cifTop = cif.getTopLevel();
147 
157 
167 
168  _entryArea = cifTop.getString(kGFF4CampaignCIFEntryArea);
169  _entryAreaList = cifTop.getString(kGFF4CampaignCIFEntryAreaList);
170  _entryScript = cifTop.getString(kGFF4CampaignCIFEntryScript);
172 
173  cifTop.getVector3(kGFF4CampaignCIFEntryPosition,
175  cifTop.getVector3(kGFF4CampaignCIFEntryOrientation,
177 
178  cifTop.getString(kGFF4CampaignCIFPackagesList, _packages);
179 }
180 
181 Campaign::RIMNode *Campaign::readRIMs(const GFF4Struct &node, const RIMNode *parent) {
182  Common::ScopedPtr<RIMNode> rim(new RIMNode(parent));
183 
184  // General node information
185  rim->tag = node.getString(kGFF4RimTreeNodeTag);
186  rim->area = node.getString(kGFF4RimTreeNodeResRef);
187 
188  // RIM files for this node
189  std::vector<Common::UString> rims;
190  node.getString(kGFF4RimTreeRimList, rims);
191 
192  if (!rims.empty()) {
193  if (rims.size() != 2)
194  throw Common::Exception("RIMList of node \"%s\" (\"%s\") with length != 2 (%u)",
195  rim->tag.c_str(), rim->area.c_str(), (uint) rims.size());
196 
197  rim->environment = rims[0];
198  rim->rim = rims[1];
199  }
200 
201  // Decend into child nodes
202  const GFF4List &children = node.getList(kGFF4RimTreeChildList);
203  rim->children.reserve(children.size());
204 
205  for (GFF4List::const_iterator c = children.begin(); c != children.end(); ++c)
206  if (*c)
207  rim->children.push_back(readRIMs(**c, rim.get()));
208 
209  // If this node defines an area, remember it
210  if (!rim->area.empty()) {
211  _areaMap.insert(std::make_pair(rim->area, rim.get()));
212 
213  _areas.push_back(rim->area);
214  }
215 
216  return rim.release();
217 }
218 
220  if (path.empty())
221  return;
222 
223  Common::ReadFile manifest(path);
224  Common::XMLParser xml(manifest, true, path);
225 
226  const Common::XMLNode &root = xml.getRoot();
227 
228  if ((root.getName() != "manifest") || (root.getProperty("type") != "AddIn"))
229  throw Common::Exception("manifest.xml is not an AddIn manifest: \"%s\", \"%s\"",
230  root.getName().c_str(), root.getProperty("type").c_str());
231 
232  const Common::XMLNode *addinList = root.findChild("addinslist");
233  if (!addinList)
234  throw Common::Exception("Manifest has no AddInList");
235 
236  const Common::XMLNode *addinItem = 0;
237 
238  const Common::XMLNode::Children &addinItems = addinList->getChildren();
239  for (Common::XMLNode::Children::const_iterator c = addinItems.begin(); c != addinItems.end(); ++c) {
240  if ((*c)->getName() == "addinitem") {
241  if (addinItem)
242  throw Common::Exception("Manifest has more than one AddInItem");
243 
244  addinItem = *c;
245  }
246  }
247 
248  if (!addinItem)
249  throw Common::Exception("Manifest has no AddInItem");
250 
251  _uid = addinItem->getProperty("uid");
252  _tag = addinItem->getProperty("name");
253  _extends = addinItem->getProperty("extendedmoduleuid");
254 
255  Common::parseString(addinItem->getProperty("enabled" , "0"), _enabled);
256  Common::parseString(addinItem->getProperty("bioware" , "0"), _bioware);
257  Common::parseString(addinItem->getProperty("requiresauthorization", "0"), _needsAuth);
258 
259  Common::parseString(addinItem->getProperty("priority", "0xFFFFFFFF"), _priority);
260 
261  Common::parseString(addinItem->getProperty("state" , "0xFFFFFFFF"), _state);
262  Common::parseString(addinItem->getProperty("format", "0xFFFFFFFF"), _format);
263 }
264 
265 bool Campaign::isEnabled() const {
266  return _enabled;
267 }
268 
269 bool Campaign::isBioWare() const {
270  return _bioware;
271 }
272 
273 bool Campaign::needsAuth() const {
274  return _needsAuth;
275 }
276 
278  static const Common::UString kEmptyString;
279 
280  AreaMap::const_iterator a = _areaMap.find(area);
281  if (a != _areaMap.end())
282  return a->second->rim;
283 
284  return kEmptyString;
285 }
286 
287 const std::vector<Common::UString> &Campaign::getAreas() const {
288  return _areas;
289 }
290 
292  return _currentArea.get();
293 }
294 
296  return _pc;
297 }
298 
299 bool Campaign::isLoaded() const {
300  return _loaded;
301 }
302 
304  _game->loadTexturePack("/packages/core" , 0, _resources, kTextureQualityHigh);
305  _game->loadTexturePack("/modules/single player", 500, _resources, kTextureQualityHigh);
306 
307  for (size_t i = 0; i < _packages.size(); i++) {
308  const Common::UString dir = "/packages/" + _packages[i];
309 
310  _game->loadResources (dir, 1000 + i * 500, _resources);
311  _game->loadTalkTables (dir, 1000 + i * 500, _tlks);
312  _game->loadTexturePack(dir, 1000 + i * 500, _resources, kTextureQualityHigh);
313  }
314 
315  if (!_addinBase.empty()) {
316  _game->loadResources ("/addins/" + _addinBase + "/core" , 10000, _resources);
317  _game->loadTalkTables ("/addins/" + _addinBase + "/core" , 10000, _tlks);
318  _game->loadTexturePack("/addins/" + _addinBase + "/core" , 10000, _resources, kTextureQualityHigh);
319 
320  _game->loadResources ("/addins/" + _addinBase + "/module", 10500, _resources);
321  _game->loadTalkTables ("/addins/" + _addinBase + "/module", 10500, _tlks);
322  _game->loadTexturePack("/addins/" + _addinBase + "/module", 10500, _resources, kTextureQualityHigh);
323  }
324 }
325 
327  Common::ReadFile *cifFile = new Common::ReadFile(path);
328 
329  GFF4File cif(cifFile, kCIFID);
330  if (cif.getTypeVersion() != kVersion01)
331  throw Common::Exception("Unsupported CIF version %s", Common::debugTag(cif.getTypeVersion()).c_str());
332 
333  const GFF4Struct &cifTop = cif.getTopLevel();
334 
335  const GFF4Struct *rimRoot = cifTop.getStruct(kGFF4RimTreeRootNode);
336  if (rimRoot)
337  _rimRoot.reset(readRIMs(*rimRoot));
338 
339  readVarTable(cifTop);
340  readScript(cifTop);
341  enableEvents(true);
342 }
343 
345  status("Loading campaign \"%s\" (\"%s\", \"%s\")", _tag.c_str(), _uid.c_str(), _name.getString().c_str());
346 
347  loadResources();
349 
350  _loaded = true;
351 
353  loadArea();
354 }
355 
357  leave();
358 
359  _currentArea.reset();
360  _rimRoot.reset();
361 
362  _areaMap.clear();
363  _areas.clear();
364 
365  clearObjects();
366  TwoDAReg.clear();
367 
369 
371 
372  _loaded = false;
373 }
374 
375 void Campaign::enterArea(bool startArea) {
376  if (!_currentArea)
377  return;
378 
379  float entryPosX, entryPosY, entryPosZ, entryOrientX, entryOrientY, entryOrientZ;
380 
381  if (startArea) {
382  entryPosX = _entryPosition[0];
383  entryPosY = _entryPosition[1];
384  entryPosZ = _entryPosition[2];
385 
386  entryOrientX = _entryOrientation[0];
387  entryOrientY = _entryOrientation[1];
388  entryOrientZ = _entryOrientation[2];
389  } else {
390  float orientX, orientY, orientZ, orientAngle;
391 
392  _currentArea->getEntryLocation(entryPosX, entryPosY, entryPosZ, orientX, orientY, orientZ, orientAngle);
393 
394  entryOrientX = 0.0f;
395  entryOrientY = 0.0f;
396  entryOrientZ = 0.0f;
397  }
398 
399  CameraMan.reset();
400  CameraMan.setPosition(entryPosX, entryPosY, entryPosZ + 1.8f);
401  CameraMan.setOrientation(entryOrientX + 90.0f, entryOrientY, entryOrientZ);
402  CameraMan.update();
403 
404  _eventQueue.clear();
405 
406  _currentArea->runScript(kEventTypeEnter , _currentArea.get(), _pc);
408 
409  _currentArea->show();
410 
413 
414  status("Entered area \"%s\" (\"%s\")", _currentArea->getTag().c_str(), _currentArea->getName().getString().c_str());
415 }
416 
418  if (!isLoaded())
419  throw Common::Exception("Campaign::enter(): Not loaded?!?");
420 
423 
424  _pc = &pc;
425 
426  runScript(kEventTypeEnter, this, _pc);
427 
428  enterArea(true);
429 }
430 
432  leaveArea();
433 
434  if (_pc)
435  runScript(kEventTypeExit, this, _pc);
436 
437  _pc = 0;
438 }
439 
441  if (!_currentArea)
442  return;
443 
444  if (_pc)
445  _currentArea->runScript(kEventTypeExit, _currentArea.get(), _pc);
446 
447  _currentArea->hide();
448 }
449 
451  leaveArea();
452 
453  _currentArea.reset();
454 
455  clearObjects();
456 }
457 
459  unloadArea();
460  if (_newArea.empty())
461  return;
462 
463  AreaMap::const_iterator area = _areaMap.find(_newArea);
464  if (area == _areaMap.end())
465  throw Common::Exception("Area \"%s\" does not exist in this campaign", _newArea.c_str());
466 
467  _currentArea.reset(new Area(*this, area->second->area, area->second->environment, area->second->rim));
468 }
469 
471  if (_currentArea && (_currentArea->getResRef() == _newArea))
472  return true;
473 
474  loadArea();
475  if (!_currentArea)
476  return false;
477 
478  enterArea();
479  return true;
480 }
481 
482 void Campaign::addEvent(const Events::Event &event) {
483  _eventQueue.push_back(event);
484 }
485 
487  if (!isLoaded())
488  return;
489 
490  changeArea();
491 
492  if (!isLoaded())
493  return;
494 
495  handleEvents();
496 }
497 
499  for (EventQueue::const_iterator event = _eventQueue.begin(); event != _eventQueue.end(); ++event)
500  _currentArea->addEvent(*event);
501 
502  _eventQueue.clear();
503 
504  _currentArea->processEventQueue();
505 }
506 
508  _newArea = area;
509 }
510 
511 void Campaign::movePC(float x, float y, float z) {
512  if (_pc)
513  _pc->setPosition(x, y, z);
514 
515  // Roughly head position
516  CameraMan.setPosition(x, y, z + 1.8f);
517  CameraMan.update();
518 }
519 
520 void Campaign::movePC(const Common::UString &area, float x, float y, float z) {
521  movePC(area);
522  movePC(x, y, z);
523 }
524 
525 } // End of namespace DragonAge
526 
527 } // End of namespace Engines
#define MKTAG(a0, a1, a2, a3)
A wrapper macro used around four character constants, like &#39;DATA&#39;, to ensure portability.
Definition: endianness.h:140
void setString(Language language, LanguageGender gender, const Common::UString &str)
Set the string of that language.
Definition: locstring.cpp:95
void setPosition(float x, float y, float z)
Set the creature&#39;s position.
Definition: creature.cpp:90
static void loadTexturePack(const Common::UString &dir, uint32 priority, ChangeList &res, TextureQuality quality)
Definition: game.cpp:124
A class holding an UTF-8 string.
Definition: ustring.h:48
Class to parse a ReadStream into a simple XML tree.
Definition: xml.h:51
A localized string.
Definition: locstring.h:43
std::vector< Common::UString > _packages
Definition: campaign.h:149
Area * getCurrentArea() const
Return the area the PC is currently in.
Definition: campaign.cpp:291
The context holding a Dragon Age: Origins campaign.
static const uint32 kVersion01
Definition: campaign.cpp:48
const UString & getName() const
Definition: xml.cpp:120
bool isLoaded() const
Is this campaign currently loaded?
Definition: campaign.cpp:299
Common::UString _entryClientScript
Definition: campaign.h:144
virtual void enter()
The cursor entered the object.
Definition: object.cpp:161
A creature in a Dragon Age: Origins area.
void readCIFStatic(const Common::UString &path)
Definition: campaign.cpp:139
Camera management.
void unload()
Unload the campaign after playing.
Definition: campaign.cpp:356
void enterArea(bool startArea=false)
Definition: campaign.cpp:375
A simple streaming file reading class.
Definition: readfile.h:40
void readCIFDynamic(const Common::UString &path)
Definition: campaign.cpp:326
Common::UString _addinBase
Definition: campaign.h:126
const std::vector< Common::UString > & getAreas() const
Definition: campaign.cpp:287
bool isEnabled() const
Is this Campaign enabled?
Definition: campaign.cpp:265
Utility templates and functions for working with strings and streams.
const Common::UString & getString(Language language, LanguageGender gender=kLanguageGenderCurrent) const
Get the string of that language.
Definition: locstring.cpp:82
std::vector< Common::UString > _areas
Definition: campaign.h:154
const Aurora::LocString & getDescription() const
Definition: campaign.cpp:94
Exception that provides a stack of explanations.
Definition: error.h:36
RIMNode * readRIMs(const Aurora::GFF4Struct &node, const RIMNode *parent=0)
A simple scoped smart pointer template.
SDL_Event Event
Definition: types.h:42
void deindexResources(Common::ChangeID &changeID)
Remove previously added resources from the ResourceManager.
Definition: resources.cpp:164
const Aurora::LocString & getName() const
Definition: campaign.cpp:90
The context holding a Dragon Age: Origins area.
std::vector< const GFF4Struct * > GFF4List
Definition: types.h:453
void leave()
Leave the campaign, ending it.
Definition: campaign.cpp:431
Handling version V4.0/V4.1 of BioWare&#39;s GFFs (generic file format).
Basic exceptions to throw.
Aurora::LocString _description
Definition: campaign.h:129
const char * c_str() const
Return the (utf8 encoded) string data.
Definition: ustring.cpp:249
The context handling the gameplay in Dragon Age: Origins.
Utility templates and functions.
Common::ScopedPtr< Area > _currentArea
The current area.
Definition: campaign.h:165
Common::UString _newArea
The new area to enter.
Definition: campaign.h:164
Creature * getPC() const
Return the currently playing PC.
Definition: campaign.cpp:295
Common::UString _extends
Definition: campaign.h:131
void load()
Load the campaign for playing.
Definition: campaign.cpp:344
void processEventQueue()
Process the current event queue.
Definition: campaign.cpp:486
bool isBioWare() const
Is this an original campaign by BioWare?
Definition: campaign.cpp:269
void read(const Common::UString &cifPath, const Common::UString &manifestPath)
Definition: campaign.cpp:102
XML parsing helpers, using libxml2.
bool empty() const
Is the string empty?
Definition: ustring.cpp:245
#define TwoDAReg
Shortcut for accessing the 2da registry.
Definition: 2dareg.h:101
StackException Exception
Definition: error.h:59
The global 2DA registry.
A scoped plain pointer, allowing pointer-y access and normal deletion.
Definition: scopedptr.h:120
UString getProperty(const UString &name, const UString &def="") const
Return a certain property on this node.
Definition: xml.cpp:148
const Common::UString & getAreaRIM(const Common::UString &area) const
Return the RIM file containing this area.
Definition: campaign.cpp:277
Common::UString _entryArea
Definition: campaign.h:141
An area in Dragon Age: Origins, holding all objects and rooms within, as well as general area propert...
Definition: area.h:64
Common::ScopedPtr< RIMNode > _rimRoot
Definition: campaign.h:151
static const uint32 kCIFID
Definition: campaign.cpp:47
Implementing the stream reading interfaces for files.
void readVarTable(const Aurora::GFF3List &varTable)
const XMLNode * findChild(const UString &name) const
Find a child node by name.
Definition: xml.cpp:136
static void unloadTalkTables(ChangeList &changes)
Unload this set of talk tables.
Definition: game.cpp:192
static void loadResources(const Common::UString &dir, uint32 priority, ChangeList &res)
Load all game resource archives found in this directory.
Definition: game.cpp:105
static UString getStem(const UString &p)
Return a file name&#39;s stem.
Definition: filepath.cpp:87
Common::UString _entryAreaList
Definition: campaign.h:142
Common::UString _uid
Definition: campaign.h:123
Common::UString _entryScript
Definition: campaign.h:143
uint32_t uint32
Definition: types.h:204
UString debugTag(uint32 tag, bool trim)
Create an elaborate string from an integer tag, for debugging purposes.
Definition: strutil.cpp:117
Common::UString _tag
Definition: object.h:56
Aurora::LocString _name
Definition: campaign.h:128
void movePC(const Common::UString &area)
Move the player character to this area.
Definition: campaign.cpp:507
void loadTalkTables(const Common::UString &dir, uint32 priority, ChangeList &res)
Load all talk tables in the current language found in this directory.
Definition: game.cpp:135
void status(const char *s,...)
Definition: util.cpp:52
const XMLNode & getRoot() const
Return the XML root node.
Definition: xml.cpp:108
Common::UString _cifPath
Definition: campaign.h:125
#define CameraMan
Shortcut for accessing the camera manager.
Definition: camera.h:83
void readScript(const Aurora::GFF3Struct &gff)
Definition: container.cpp:90
void addEvent(const Events::Event &event)
Add a single event for consideration into the area event queue.
Definition: campaign.cpp:482
void readManifest(const Common::UString &path)
Definition: campaign.cpp:219
A node in the RIM tree.
Definition: campaign.h:101
static const Common::UString kEmptyString
Definition: talkman.cpp:129
Campaign(Game &game, const Common::UString &cifPath="", const Common::UString &manifestPath="", const Common::UString &addinBase="")
Definition: campaign.cpp:61
bool needsAuth() const
Does this campaign need authorization from BioWare?
Definition: campaign.cpp:273
const Common::UString & getExtendsUID() const
Return the UID of the campaign this campaign/content extends, if any.
Definition: campaign.cpp:98
bool runScript(EventType event, const Aurora::NWScript::ObjectReference owner=Aurora::NWScript::ObjectReference(), const Aurora::NWScript::ObjectReference triggerer=Aurora::NWScript::ObjectReference())
Definition: container.cpp:104
void parseString(const UString &str, T &value, bool allowEmpty)
Parse a string into any POD integer, float/double or bool type.
Definition: strutil.cpp:215
void enableEvents(bool enabled)
Enable/Disable the handling of all events.
Definition: container.cpp:76
Utility class for manipulating file paths.
unsigned int uint
Definition: types.h:211
const Common::UString & getUID() const
Return the unique ID of this campaign.
Definition: campaign.cpp:86
Creature * _pc
The player character we use.
Definition: campaign.h:162