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 DragonAge2 {
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) == "campaign_base") {
104  // Original campaign
105 
106  if (!manifestPath.empty())
107  throw Common::Exception("Original campaign with a manifest.xml?!?");
108 
109  _uid = "campaign_base";
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() != 3)
194  throw Common::Exception("RIMList of node \"%s\" (\"%s\") with length != 3 (%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  rim->rimFXE = rims[2];
200  }
201 
202  // Decend into child nodes
203  const GFF4List &children = node.getList(kGFF4RimTreeChildList);
204  rim->children.reserve(children.size());
205 
206  for (GFF4List::const_iterator c = children.begin(); c != children.end(); ++c)
207  if (*c)
208  rim->children.push_back(readRIMs(**c, rim.get()));
209 
210  // If this node defines an area, remember it
211  if (!rim->area.empty()) {
212  _areaMap.insert(std::make_pair(rim->area, rim.get()));
213 
214  _areas.push_back(rim->area);
215  }
216 
217  return rim.release();
218 }
219 
221  if (path.empty())
222  return;
223 
224  Common::ReadFile manifest(path);
225  Common::XMLParser xml(manifest, true, path);
226 
227  const Common::XMLNode &root = xml.getRoot();
228 
229  if ((root.getName() != "manifest") || (root.getProperty("type") != "AddIn"))
230  throw Common::Exception("manifest.xml is not an AddIn manifest: \"%s\", \"%s\"",
231  root.getName().c_str(), root.getProperty("type").c_str());
232 
233  const Common::XMLNode *addinList = root.findChild("addinslist");
234  if (!addinList)
235  throw Common::Exception("Manifest has no AddInList");
236 
237  const Common::XMLNode *addinItem = 0;
238 
239  const Common::XMLNode::Children &addinItems = addinList->getChildren();
240  for (Common::XMLNode::Children::const_iterator c = addinItems.begin(); c != addinItems.end(); ++c) {
241  if ((*c)->getName() == "addinitem") {
242  if (addinItem)
243  throw Common::Exception("Manifest has more than one AddInItem");
244 
245  addinItem = *c;
246  }
247  }
248 
249  if (!addinItem)
250  throw Common::Exception("Manifest has no AddInItem");
251 
252  _uid = addinItem->getProperty("uid");
253  _tag = addinItem->getProperty("name");
254  _extends = addinItem->getProperty("extendedmoduleuid");
255 
256  Common::parseString(addinItem->getProperty("enabled" , "0"), _enabled);
257  Common::parseString(addinItem->getProperty("bioware" , "0"), _bioware);
258  Common::parseString(addinItem->getProperty("requiresauthorization", "0"), _needsAuth);
259 
260  Common::parseString(addinItem->getProperty("priority", "0xFFFFFFFF"), _priority);
261 
262  Common::parseString(addinItem->getProperty("state" , "0xFFFFFFFF"), _state);
263  Common::parseString(addinItem->getProperty("format", "0xFFFFFFFF"), _format);
264 }
265 
266 bool Campaign::isEnabled() const {
267  return _enabled;
268 }
269 
270 bool Campaign::isBioWare() const {
271  return _bioware;
272 }
273 
274 bool Campaign::needsAuth() const {
275  return _needsAuth;
276 }
277 
279  static const Common::UString kEmptyString;
280 
281  AreaMap::const_iterator a = _areaMap.find(area);
282  if (a != _areaMap.end())
283  return a->second->rim;
284 
285  return kEmptyString;
286 }
287 
288 const std::vector<Common::UString> &Campaign::getAreas() const {
289  return _areas;
290 }
291 
293  return _currentArea.get();
294 }
295 
297  return _pc;
298 }
299 
300 bool Campaign::isLoaded() const {
301  return _loaded;
302 }
303 
305  _game->loadTexturePack("/packages/core" , 0, _resources, kTextureQualityHigh);
306  _game->loadTexturePack("/modules/campaign_base", 500, _resources, kTextureQualityHigh);
307 
308  for (size_t i = 0; i < _packages.size(); i++) {
309  const Common::UString dir = "/packages/" + _packages[i];
310 
311  _game->loadResources (dir, 1000 + i * 500, _resources);
312  _game->loadTalkTables (dir, 1000 + i * 500, _tlks);
313  _game->loadTexturePack(dir, 1000 + i * 500, _resources, kTextureQualityHigh);
314  }
315 
316  if (!_addinBase.empty()) {
317  _game->loadResources ("/addins/" + _addinBase + "/core" , 10000, _resources);
318  _game->loadTalkTables ("/addins/" + _addinBase + "/core" , 10000, _tlks);
319  _game->loadTexturePack("/addins/" + _addinBase + "/core" , 10000, _resources, kTextureQualityHigh);
320 
321  _game->loadResources ("/addins/" + _addinBase + "/module", 10500, _resources);
322  _game->loadTalkTables ("/addins/" + _addinBase + "/module", 10500, _tlks);
323  _game->loadTexturePack("/addins/" + _addinBase + "/module", 10500, _resources, kTextureQualityHigh);
324  }
325 }
326 
328  Common::ReadFile *cifFile = new Common::ReadFile(path);
329 
330  GFF4File cif(cifFile, kCIFID);
331  if (cif.getTypeVersion() != kVersion01)
332  throw Common::Exception("Unsupported CIF version %s", Common::debugTag(cif.getTypeVersion()).c_str());
333 
334  const GFF4Struct &cifTop = cif.getTopLevel();
335 
336  const GFF4Struct *rimRoot = cifTop.getStruct(kGFF4RimTreeRootNode);
337  if (rimRoot)
338  _rimRoot.reset(readRIMs(*rimRoot));
339 
340  readVarTable(cifTop);
341  readScript(cifTop);
342  enableEvents(true);
343 }
344 
346  status("Loading campaign \"%s\" (\"%s\", \"%s\")", _tag.c_str(), _uid.c_str(), _name.getString().c_str());
347 
348  loadResources();
350 
351  _loaded = true;
352 
354  loadArea();
355 }
356 
358  leave();
359 
360  _currentArea.reset();
361  _rimRoot.reset();
362 
363  _areaMap.clear();
364  _areas.clear();
365 
366  clearObjects();
367  TwoDAReg.clear();
368 
370 
372 
373  _loaded = false;
374 }
375 
376 void Campaign::enterArea(bool startArea) {
377  if (!_currentArea)
378  return;
379 
380  float entryPosX, entryPosY, entryPosZ, entryOrientX, entryOrientY, entryOrientZ;
381 
382  if (startArea) {
383  entryPosX = _entryPosition[0];
384  entryPosY = _entryPosition[1];
385  entryPosZ = _entryPosition[2];
386 
387  entryOrientX = _entryOrientation[0];
388  entryOrientY = _entryOrientation[1];
389  entryOrientZ = _entryOrientation[2];
390  } else {
391  float orientX, orientY, orientZ, orientAngle;
392 
393  _currentArea->getEntryLocation(entryPosX, entryPosY, entryPosZ, orientX, orientY, orientZ, orientAngle);
394 
395  entryOrientX = 0.0f;
396  entryOrientY = 0.0f;
397  entryOrientZ = 0.0f;
398  }
399 
400  CameraMan.reset();
401  CameraMan.setPosition(entryPosX, entryPosY, entryPosZ + 1.8f);
402  CameraMan.setOrientation(entryOrientX + 90.0f, entryOrientY, entryOrientZ);
403  CameraMan.update();
404 
405  _eventQueue.clear();
406 
407  _currentArea->runScript(kEventTypeEnter , _currentArea.get(), _pc);
409 
410  _currentArea->show();
411 
414 
415  status("Entered area \"%s\" (\"%s\")", _currentArea->getTag().c_str(), _currentArea->getName().getString().c_str());
416 }
417 
419  if (!isLoaded())
420  throw Common::Exception("Campaign::enter(): Not loaded?!?");
421 
424 
425  _pc = &pc;
426 
427  runScript(kEventTypeEnter, this, _pc);
428 
429  enterArea(true);
430 }
431 
433  leaveArea();
434 
435  if (_pc)
436  runScript(kEventTypeExit, this, _pc);
437 
438  _pc = 0;
439 }
440 
442  if (!_currentArea)
443  return;
444 
445  if (_pc)
446  _currentArea->runScript(kEventTypeExit, _currentArea.get(), _pc);
447 
448  _currentArea->hide();
449 }
450 
452  leaveArea();
453 
454  _currentArea.reset();
455 
456  clearObjects();
457 }
458 
460  unloadArea();
461  if (_newArea.empty())
462  return;
463 
464  AreaMap::const_iterator area = _areaMap.find(_newArea);
465  if (area == _areaMap.end())
466  throw Common::Exception("Area \"%s\" does not exist in this campaign", _newArea.c_str());
467 
468  _currentArea.reset(new Area(*this, area->second->area, area->second->environment, area->second->rim));
469 }
470 
472  if (_currentArea && (_currentArea->getResRef() == _newArea))
473  return true;
474 
475  loadArea();
476  if (!_currentArea)
477  return false;
478 
479  enterArea();
480  return true;
481 }
482 
483 void Campaign::addEvent(const Events::Event &event) {
484  _eventQueue.push_back(event);
485 }
486 
488  if (!isLoaded())
489  return;
490 
491  changeArea();
492 
493  if (!isLoaded())
494  return;
495 
496  handleEvents();
497 }
498 
500  for (EventQueue::const_iterator event = _eventQueue.begin(); event != _eventQueue.end(); ++event)
501  _currentArea->addEvent(*event);
502 
503  _eventQueue.clear();
504 
505  _currentArea->processEventQueue();
506 }
507 
509  _newArea = area;
510 }
511 
512 void Campaign::movePC(float x, float y, float z) {
513  if (_pc)
514  _pc->setPosition(x, y, z);
515 
516  // Roughly head position
517  CameraMan.setPosition(x, y, z + 1.8f);
518  CameraMan.update();
519 }
520 
521 void Campaign::movePC(const Common::UString &area, float x, float y, float z) {
522  movePC(area);
523  movePC(x, y, z);
524 }
525 
526 } // End of namespace DragonAge2
527 
528 } // End of namespace Engines
static const uint32 kCIFID
Definition: campaign.cpp:47
static const uint32 kVersion01
Definition: campaign.cpp:48
#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
A node in the RIM tree.
Definition: campaign.h:101
const Common::UString & getExtendsUID() const
Return the UID of the campaign this campaign/content extends, if any.
Definition: campaign.cpp:98
void leave()
Leave the campaign, ending it.
Definition: campaign.cpp:432
void readCIFStatic(const Common::UString &path)
Definition: campaign.cpp:139
A creature in a Dragon Age II area.
static void unloadTalkTables(ChangeList &changes)
Unload this set of talk tables.
Definition: game.cpp:220
void setString(Language language, LanguageGender gender, const Common::UString &str)
Set the string of that language.
Definition: locstring.cpp:95
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
virtual void enter()
The cursor entered the object.
Definition: object.cpp:161
Common::UString _entryClientScript
Definition: campaign.h:145
Creature * getPC() const
Return the currently playing PC.
Definition: campaign.cpp:296
const UString & getName() const
Definition: xml.cpp:120
const Aurora::LocString & getDescription() const
Definition: campaign.cpp:94
The context holding a Dragon Age II area.
bool isEnabled() const
Is this Campaign enabled?
Definition: campaign.cpp:266
Creature * _pc
The player character we use.
Definition: campaign.h:163
Camera management.
A simple streaming file reading class.
Definition: readfile.h:40
void setPosition(float x, float y, float z)
Set the creature&#39;s position.
Definition: creature.cpp:89
void enableEvents(bool enabled)
Enable/Disable the handling of all events.
Definition: container.cpp:75
An area in Dragon Age II, holding all objects and rooms within, as well as general area properties li...
Definition: area.h:64
The context handling the gameplay in Dragon Age II.
void read(const Common::UString &cifPath, const Common::UString &manifestPath)
Definition: campaign.cpp:102
bool runScript(EventType event, const Aurora::NWScript::ObjectReference owner=Aurora::NWScript::ObjectReference(), const Aurora::NWScript::ObjectReference triggerer=Aurora::NWScript::ObjectReference())
Definition: container.cpp:100
bool isLoaded() const
Is this campaign currently loaded?
Definition: campaign.cpp:300
Utility templates and functions for working with strings and streams.
bool isBioWare() const
Is this an original campaign by BioWare?
Definition: campaign.cpp:270
const Common::UString & getString(Language language, LanguageGender gender=kLanguageGenderCurrent) const
Get the string of that language.
Definition: locstring.cpp:82
Common::ScopedPtr< Area > _currentArea
The current area.
Definition: campaign.h:166
Exception that provides a stack of explanations.
Definition: error.h:36
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
std::vector< Common::UString > _packages
Definition: campaign.h:150
std::vector< const GFF4Struct * > GFF4List
Definition: types.h:453
const Common::UString & getAreaRIM(const Common::UString &area) const
Return the RIM file containing this area.
Definition: campaign.cpp:278
Handling version V4.0/V4.1 of BioWare&#39;s GFFs (generic file format).
Area * getCurrentArea() const
Return the area the PC is currently in.
Definition: campaign.cpp:292
Basic exceptions to throw.
const char * c_str() const
Return the (utf8 encoded) string data.
Definition: ustring.cpp:249
Aurora::LocString _description
Definition: campaign.h:130
Utility templates and functions.
Common::UString _extends
Definition: campaign.h:132
Common::ScopedPtr< RIMNode > _rimRoot
Definition: campaign.h:152
void enterArea(bool startArea=false)
Definition: campaign.cpp:376
static void loadTexturePack(const Common::UString &dir, uint32 priority, ChangeList &res, TextureQuality quality)
Definition: game.cpp:139
Common::UString _cifPath
Definition: campaign.h:126
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.
Common::UString _addinBase
Definition: campaign.h:127
A scoped plain pointer, allowing pointer-y access and normal deletion.
Definition: scopedptr.h:120
Common::UString _newArea
The new area to enter.
Definition: campaign.h:165
UString getProperty(const UString &name, const UString &def="") const
Return a certain property on this node.
Definition: xml.cpp:148
Common::UString _entryScript
Definition: campaign.h:144
Implementing the stream reading interfaces for files.
Campaign(Game &game, const Common::UString &cifPath="", const Common::UString &manifestPath="", const Common::UString &addinBase="")
Definition: campaign.cpp:61
The context holding a Dragon Age II campaign.
std::vector< Common::UString > _areas
Definition: campaign.h:155
const XMLNode * findChild(const UString &name) const
Find a child node by name.
Definition: xml.cpp:136
static UString getStem(const UString &p)
Return a file name&#39;s stem.
Definition: filepath.cpp:87
const std::vector< Common::UString > & getAreas() const
Definition: campaign.cpp:288
void movePC(const Common::UString &area)
Move the player character to this area.
Definition: campaign.cpp:508
void unload()
Unload the campaign after playing.
Definition: campaign.cpp:357
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
void status(const char *s,...)
Definition: util.cpp:52
const XMLNode & getRoot() const
Return the XML root node.
Definition: xml.cpp:108
#define CameraMan
Shortcut for accessing the camera manager.
Definition: camera.h:83
void load()
Load the campaign for playing.
Definition: campaign.cpp:345
void readCIFDynamic(const Common::UString &path)
Definition: campaign.cpp:327
RIMNode * readRIMs(const Aurora::GFF4Struct &node, const RIMNode *parent=0)
bool needsAuth() const
Does this campaign need authorization from BioWare?
Definition: campaign.cpp:274
const Common::UString & getUID() const
Return the unique ID of this campaign.
Definition: campaign.cpp:86
static const Common::UString kEmptyString
Definition: talkman.cpp:129
Aurora::LocString _name
Definition: campaign.h:129
const Aurora::LocString & getName() const
Definition: campaign.cpp:90
void processEventQueue()
Process the current event queue.
Definition: campaign.cpp:487
Common::UString _entryAreaList
Definition: campaign.h:143
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:157
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 readVarTable(const Aurora::GFF3List &varTable)
Utility class for manipulating file paths.
void readManifest(const Common::UString &path)
Definition: campaign.cpp:220
unsigned int uint
Definition: types.h:211
void addEvent(const Events::Event &event)
Add a single event for consideration into the area event queue.
Definition: campaign.cpp:483
Common::UString _entryArea
Definition: campaign.h:142
void readScript(const Aurora::GFF3Struct &gff)
Definition: container.cpp:86
void loadResources(const Common::UString &dir, uint32 priority, ChangeList &res)
Load all game resource archives in the current language found in this directory.
Definition: game.cpp:150