1 /*
2 * Copyright 2010-2014 OpenXcom Developers.
3 *
4 * This file is part of OpenXcom.
5 *
6 * OpenXcom is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * OpenXcom is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with OpenXcom. If not, see <http://www.gnu.org/licenses/>.
18 */
19 #include <assert.h>
20 #include <fstream>
21 #include <sstream>
22 #include "BattlescapeGenerator.h"
23 #include "TileEngine.h"
24 #include "Inventory.h"
25 #include "../Savegame/SavedGame.h"
26 #include "../Savegame/SavedBattleGame.h"
27 #include "../Savegame/Tile.h"
28 #include "../Savegame/ItemContainer.h"
29 #include "../Savegame/Base.h"
30 #include "../Savegame/BaseFacility.h"
31 #include "../Savegame/Soldier.h"
32 #include "../Savegame/BattleUnit.h"
33 #include "../Savegame/BattleItem.h"
34 #include "../Savegame/Ufo.h"
35 #include "../Savegame/Craft.h"
36 #include "../Savegame/Node.h"
37 #include "../Engine/RNG.h"
38 #include "../Engine/Exception.h"
39 #include "../Ruleset/MapBlock.h"
40 #include "../Ruleset/MapDataSet.h"
41 #include "../Ruleset/RuleUfo.h"
42 #include "../Ruleset/RuleCraft.h"
43 #include "../Ruleset/RuleTerrain.h"
44 #include "../Ruleset/RuleInventory.h"
45 #include "../Ruleset/Ruleset.h"
46 #include "../Ruleset/MapData.h"
47 #include "../Ruleset/MCDPatch.h"
48 #include "../Ruleset/Armor.h"
49 #include "../Ruleset/Unit.h"
50 #include "../Ruleset/AlienRace.h"
51 #include "../Ruleset/AlienDeployment.h"
52 #include "../Ruleset/RuleBaseFacility.h"
53 #include "../Resource/XcomResourcePack.h"
54 #include "../Engine/Game.h"
55 #include "../Engine/Language.h"
56 #include "../Engine/CrossPlatform.h"
57 #include "../Engine/Options.h"
58 #include "../Savegame/Vehicle.h"
59 #include "../Savegame/TerrorSite.h"
60 #include "../Savegame/AlienBase.h"
61 #include "../Savegame/EquipmentLayoutItem.h"
62 #include "CivilianBAIState.h"
63 #include "AlienBAIState.h"
64 #include "Pathfinding.h"
65
66 namespace OpenXcom
67 {
68
69 /**
70 * Sets up a BattlescapeGenerator.
71 * @param game pointer to Game object.
72 */
BattlescapeGenerator(Game * game)73 BattlescapeGenerator::BattlescapeGenerator(Game *game) : _game(game), _save(game->getSavedGame()->getSavedBattle()), _res(_game->getResourcePack()), _craft(0), _ufo(0), _base(0), _terror(0), _alienBase(0), _terrain(0),
74 _mapsize_x(0), _mapsize_y(0), _mapsize_z(0), _worldTexture(0), _worldShade(0), _unitSequence(0), _craftInventoryTile(0), _alienRace(""), _alienItemLevel(0), _baseInventory(false), _craftX(0), _craftY(0), _craftZ(0)
75 {
76 _allowAutoLoadout = !Options::disableAutoEquip;
77 }
78
79 /**
80 * Deletes the BattlescapeGenerator.
81 */
~BattlescapeGenerator()82 BattlescapeGenerator::~BattlescapeGenerator()
83 {
84
85 }
86
87 /**
88 * Sets the XCom craft involved in the battle.
89 * @param craft Pointer to XCom craft.
90 */
setCraft(Craft * craft)91 void BattlescapeGenerator::setCraft(Craft *craft)
92 {
93 _craft = craft;
94 _craft->setInBattlescape(true);
95 }
96
97 /**
98 * Sets the ufo involved in the battle.
99 * @param ufo Pointer to UFO.
100 */
setUfo(Ufo * ufo)101 void BattlescapeGenerator::setUfo(Ufo *ufo)
102 {
103 _ufo = ufo;
104 _ufo->setInBattlescape(true);
105 }
106
107 /**
108 * Sets the world texture where a ufo crashed. This is used to determine the terrain.
109 * @param texture Texture id of the polygon on the globe.
110 */
setWorldTexture(int texture)111 void BattlescapeGenerator::setWorldTexture(int texture)
112 {
113 if (texture < 0) texture = 0;
114 _worldTexture = texture;
115 }
116
117 /**
118 * Sets the world shade where a ufo crashed. This is used to determine the battlescape light level.
119 * @param shade Shade of the polygon on the globe.
120 */
setWorldShade(int shade)121 void BattlescapeGenerator::setWorldShade(int shade)
122 {
123 if (shade > 15) shade = 15;
124 if (shade < 0) shade = 0;
125 _worldShade = shade;
126 }
127
128 /**
129 * Sets the alien race on the mission. This is used to determine the various alien types to spawn.
130 * @param alienRace Alien (main) race.
131 */
setAlienRace(const std::string & alienRace)132 void BattlescapeGenerator::setAlienRace(const std::string &alienRace)
133 {
134 _alienRace = alienRace;
135 }
136
137 /**
138 * Sets the alien item level. This is used to determine how advanced the equipment of the aliens will be.
139 * note: this only applies to "New Battle" type games. we intentionally don't alter the month for those,
140 * because we're using monthsPassed -1 for new battle in other sections of code.
141 * - this value should be from 0 to the size of the itemLevel array in the ruleset (default 9).
142 * - at a certain number of months higher item levels appear more and more and lower ones will gradually disappear
143 * @param alienItemLevel AlienItemLevel.
144 */
setAlienItemlevel(int alienItemLevel)145 void BattlescapeGenerator::setAlienItemlevel(int alienItemLevel)
146 {
147 _alienItemLevel = alienItemLevel;
148 }
149
150 /**
151 * Sets the XCom base involved in the battle.
152 * @param base Pointer to XCom base.
153 */
setBase(Base * base)154 void BattlescapeGenerator::setBase(Base *base)
155 {
156 _base = base;
157 _base->setInBattlescape(true);
158 }
159
160 /**
161 * Sets the terror site involved in the battle.
162 * @param terror Pointer to terror site.
163 */
setTerrorSite(TerrorSite * terror)164 void BattlescapeGenerator::setTerrorSite(TerrorSite *terror)
165 {
166 _terror = terror;
167 _terror->setInBattlescape(true);
168 }
169
170
171 /**
172 * Switches an existing battlescapesavegame to a new stage.
173 */
nextStage()174 void BattlescapeGenerator::nextStage()
175 {
176 // kill all enemy units, or those not in endpoint area (if aborted)
177 for (std::vector<BattleUnit*>::iterator j = _save->getUnits()->begin(); j != _save->getUnits()->end(); ++j)
178 {
179 if ((_game->getSavedGame()->getSavedBattle()->isAborted() && !(*j)->isInExitArea(END_POINT))
180 || (*j)->getOriginalFaction() == FACTION_HOSTILE)
181 {
182 (*j)->instaKill();
183 }
184 if ((*j)->getTile())
185 {
186 (*j)->getTile()->setUnit(0);
187 }
188 (*j)->setTile(0);
189 (*j)->setPosition(Position(-1,-1,-1), false);
190 }
191
192 while (_game->getSavedGame()->getSavedBattle()->getSide() != FACTION_PLAYER)
193 {
194 _game->getSavedGame()->getSavedBattle()->endTurn();
195 }
196 _save->resetTurnCounter();
197
198 AlienDeployment *ruleDeploy = _game->getRuleset()->getDeployment(_save->getMissionType());
199 ruleDeploy->getDimensions(&_mapsize_x, &_mapsize_y, &_mapsize_z);
200 size_t pick = RNG::generate(0, ruleDeploy->getTerrains().size() -1);
201 _terrain = _game->getRuleset()->getTerrain(ruleDeploy->getTerrains().at(pick));
202 _worldShade = ruleDeploy->getShade();
203
204 _save->initMap(_mapsize_x, _mapsize_y, _mapsize_z);
205 generateMap();
206 int highestSoldierID = 0;
207 bool selectedFirstSoldier = false;
208 for (std::vector<BattleUnit*>::iterator j = _save->getUnits()->begin(); j != _save->getUnits()->end(); ++j)
209 {
210 if ((*j)->getOriginalFaction() == FACTION_PLAYER)
211 {
212 if (!(*j)->isOut())
213 {
214 (*j)->convertToFaction(FACTION_PLAYER);
215 (*j)->setTurnsSinceSpotted(255);
216 (*j)->getVisibleTiles()->clear();
217 if (!selectedFirstSoldier && (*j)->getGeoscapeSoldier())
218 {
219 _save->setSelectedUnit(*j);
220 selectedFirstSoldier = true;
221 }
222 Node* node = _save->getSpawnNode(NR_XCOM, (*j));
223 if (node)
224 {
225 _save->setUnitPosition((*j), node->getPosition());
226 if (!_craftInventoryTile)
227 {
228 _craftInventoryTile = (*j)->getTile();
229 }
230 _craftInventoryTile->setUnit(*j);
231 (*j)->setVisible(false);
232 if ((*j)->getId() > highestSoldierID)
233 {
234 highestSoldierID = (*j)->getId();
235 }
236 }
237 else if (placeUnitNearFriend(*j))
238 {
239 if ((*j)->getId() > highestSoldierID)
240 {
241 highestSoldierID = (*j)->getId();
242 }
243 _craftInventoryTile->setUnit(*j);
244 (*j)->setVisible(false);
245 }
246 }
247 }
248 }
249
250 // remove all items not belonging to our soldiers from the map.
251 for (std::vector<BattleItem*>::iterator j = _save->getItems()->begin(); j != _save->getItems()->end(); ++j)
252 {
253 if (!(*j)->getOwner() || (*j)->getOwner()->getId() > highestSoldierID)
254 {
255 (*j)->setTile(0);
256 }
257 }
258 _unitSequence = _save->getUnits()->back()->getId() + 1;
259 deployAliens(_game->getRuleset()->getAlienRace(_alienRace), ruleDeploy);
260
261 deployCivilians(ruleDeploy->getCivilians());
262
263 for (int i = 0; i < _save->getMapSizeXYZ(); ++i)
264 {
265 if (_save->getTiles()[i]->getMapData(MapData::O_FLOOR) &&
266 (_save->getTiles()[i]->getMapData(MapData::O_FLOOR)->getSpecialType() == START_POINT ||
267 (_save->getTiles()[i]->getPosition().z == 1 &&
268 _save->getTiles()[i]->getMapData(MapData::O_FLOOR)->isGravLift() &&
269 _save->getTiles()[i]->getMapData(MapData::O_OBJECT))))
270 _save->getTiles()[i]->setDiscovered(true, 2);
271 }
272 _save->setGlobalShade(_worldShade);
273 _save->getTileEngine()->calculateSunShading();
274 _save->getTileEngine()->calculateTerrainLighting();
275 _save->getTileEngine()->calculateUnitLighting();
276 _save->getTileEngine()->recalculateFOV();
277 }
278
279 /**
280 * Starts the generator; it fills up the battlescapesavegame with data.
281 */
run()282 void BattlescapeGenerator::run()
283 {
284 AlienDeployment *ruleDeploy = _game->getRuleset()->getDeployment(_ufo?_ufo->getRules()->getType():_save->getMissionType());
285
286 ruleDeploy->getDimensions(&_mapsize_x, &_mapsize_y, &_mapsize_z);
287
288 _unitSequence = BattleUnit::MAX_SOLDIER_ID; // geoscape soldier IDs should stay below this number
289
290 if (ruleDeploy->getTerrains().empty())
291 {
292 double lat = 0;
293 if (_ufo) lat = _ufo->getLatitude();
294 _terrain = getTerrain(_worldTexture, lat);
295 }
296 else
297 {
298 size_t pick = RNG::generate(0, ruleDeploy->getTerrains().size() -1);
299 _terrain = _game->getRuleset()->getTerrain(ruleDeploy->getTerrains().at(pick));
300 }
301
302 if (ruleDeploy->getShade() != -1)
303 {
304 _worldShade = ruleDeploy->getShade();
305 }
306
307 // creates the tile objects
308 _save->initMap(_mapsize_x, _mapsize_y, _mapsize_z);
309 _save->initUtilities(_res);
310
311 // lets generate the map now and store it inside the tile objects
312 generateMap();
313
314 if (_craft != 0 || _base != 0)
315 {
316 deployXCOM();
317 }
318
319 deployAliens(_game->getRuleset()->getAlienRace(_alienRace), ruleDeploy);
320
321 deployCivilians(ruleDeploy->getCivilians());
322
323 fuelPowerSources();
324
325 if (_save->getMissionType() == "STR_UFO_CRASH_RECOVERY")
326 {
327 explodePowerSources();
328 }
329
330 if (_save->getMissionType() == "STR_BASE_DEFENSE")
331 {
332 for (int i = 0; i < _save->getMapSizeXYZ(); ++i)
333 {
334 _save->getTiles()[i]->setDiscovered(true, 2);
335 }
336
337 _save->calculateModuleMap();
338 }
339
340 if (_save->getMissionType() == "STR_ALIEN_BASE_ASSAULT" || _save->getMissionType() == "STR_MARS_THE_FINAL_ASSAULT")
341 {
342 for (int i = 0; i < _save->getMapSizeXYZ(); ++i)
343 {
344 if (_save->getTiles()[i]->getMapData(MapData::O_FLOOR) &&
345 (_save->getTiles()[i]->getMapData(MapData::O_FLOOR)->getSpecialType() == START_POINT ||
346 (_save->getTiles()[i]->getPosition().z == 1 &&
347 _save->getTiles()[i]->getMapData(MapData::O_FLOOR)->isGravLift() &&
348 _save->getTiles()[i]->getMapData(MapData::O_OBJECT))))
349 _save->getTiles()[i]->setDiscovered(true, 2);
350 }
351 }
352
353 // set shade (alien bases are a little darker, sites depend on worldshade)
354 _save->setGlobalShade(_worldShade);
355
356 _save->getTileEngine()->calculateSunShading();
357 _save->getTileEngine()->calculateTerrainLighting();
358 _save->getTileEngine()->calculateUnitLighting();
359 _save->getTileEngine()->recalculateFOV();
360 }
361
362 /**
363 * Deploys all the X-COM units and equipment based
364 * on the Geoscape base / craft.
365 * @param inventoryTile The tile to place all the extra equipment on.
366 */
deployXCOM()367 void BattlescapeGenerator::deployXCOM()
368 {
369 RuleInventory *ground = _game->getRuleset()->getInventory("STR_GROUND");
370
371 if (_craft != 0)
372 _base = _craft->getBase();
373
374 // add vehicles that are in the craft - a vehicle is actually an item, which you will never see as it is converted to a unit
375 // however the item itself becomes the weapon it "holds".
376 if (!_baseInventory)
377 {
378 if (_craft != 0)
379 {
380 for (std::vector<Vehicle*>::iterator i = _craft->getVehicles()->begin(); i != _craft->getVehicles()->end(); ++i)
381 {
382 BattleUnit *unit = addXCOMVehicle(*i);
383 if (unit && !_save->getSelectedUnit())
384 _save->setSelectedUnit(unit);
385 }
386 }
387 else if (_base != 0)
388 {
389 // add vehicles that are in the base inventory
390 for (std::vector<Vehicle*>::iterator i = _base->getVehicles()->begin(); i != _base->getVehicles()->end(); ++i)
391 {
392 BattleUnit *unit = addXCOMVehicle(*i);
393 if (unit && !_save->getSelectedUnit())
394 _save->setSelectedUnit(unit);
395 }
396 }
397 }
398
399 // add soldiers that are in the craft or base
400 for (std::vector<Soldier*>::iterator i = _base->getSoldiers()->begin(); i != _base->getSoldiers()->end(); ++i)
401 {
402 if ((_craft != 0 && (*i)->getCraft() == _craft) ||
403 (_craft == 0 && (*i)->getWoundRecovery() == 0 && ((*i)->getCraft() == 0 || (*i)->getCraft()->getStatus() != "STR_OUT")))
404 {
405 BattleUnit *unit = addXCOMUnit(new BattleUnit(*i, FACTION_PLAYER));
406 if (unit && !_save->getSelectedUnit())
407 _save->setSelectedUnit(unit);
408 }
409 }
410
411 // maybe we should assign all units to the first tile of the skyranger before the inventory pre-equip and then reassign them to their correct tile afterwards?
412 // fix: make them invisible, they are made visible afterwards.
413 for (std::vector<BattleUnit*>::iterator i = _save->getUnits()->begin(); i != _save->getUnits()->end(); ++i)
414 {
415 if ((*i)->getFaction() == FACTION_PLAYER)
416 {
417 _craftInventoryTile->setUnit(*i);
418 (*i)->setVisible(false);
419 }
420 }
421
422 if (_craft != 0)
423 {
424 // add items that are in the craft
425 for (std::map<std::string, int>::iterator i = _craft->getItems()->getContents()->begin(); i != _craft->getItems()->getContents()->end(); ++i)
426 {
427 for (int count = 0; count < i->second; count++)
428 {
429 _craftInventoryTile->addItem(new BattleItem(_game->getRuleset()->getItem(i->first), _save->getCurrentItemId()), ground);
430 }
431 }
432 }
433 else
434 {
435 // add items that are in the base
436 for (std::map<std::string, int>::iterator i = _base->getItems()->getContents()->begin(); i != _base->getItems()->getContents()->end();)
437 {
438 // only put items in the battlescape that make sense (when the item got a sprite, it's probably ok)
439 RuleItem *rule = _game->getRuleset()->getItem(i->first);
440 if (rule->getBigSprite() > -1 && rule->getBattleType() != BT_NONE && rule->getBattleType() != BT_CORPSE && !rule->isFixed() && _game->getSavedGame()->isResearched(rule->getRequirements()))
441 {
442 for (int count = 0; count < i->second; count++)
443 {
444 _craftInventoryTile->addItem(new BattleItem(_game->getRuleset()->getItem(i->first), _save->getCurrentItemId()), ground);
445 }
446 std::map<std::string, int>::iterator tmp = i;
447 ++i;
448 _base->getItems()->removeItem(tmp->first, tmp->second);
449 }
450 else
451 {
452 ++i;
453 }
454 }
455 // add items from crafts in base
456 for (std::vector<Craft*>::iterator c = _base->getCrafts()->begin(); c != _base->getCrafts()->end(); ++c)
457 {
458 if ((*c)->getStatus() == "STR_OUT")
459 continue;
460 for (std::map<std::string, int>::iterator i = (*c)->getItems()->getContents()->begin(); i != (*c)->getItems()->getContents()->end(); ++i)
461 {
462 for (int count = 0; count < i->second; count++)
463 {
464 _craftInventoryTile->addItem(new BattleItem(_game->getRuleset()->getItem(i->first), _save->getCurrentItemId()), ground);
465 }
466 }
467 }
468 }
469
470 // equip soldiers based on equipment-layout
471 for (std::vector<BattleItem*>::iterator i = _craftInventoryTile->getInventory()->begin(); i != _craftInventoryTile->getInventory()->end(); ++i)
472 {
473 // don't let the soldiers take extra ammo yet
474 if ((*i)->getRules()->getBattleType() == BT_AMMO)
475 continue;
476 placeItemByLayout(*i);
477 }
478
479 // load weapons before loadouts take extra clips.
480 loadWeapons();
481
482 for (std::vector<BattleItem*>::iterator i = _craftInventoryTile->getInventory()->begin(); i != _craftInventoryTile->getInventory()->end(); ++i)
483 {
484 // we only need to distribute extra ammo at this point.
485 if ((*i)->getRules()->getBattleType() != BT_AMMO)
486 continue;
487 placeItemByLayout(*i);
488 }
489
490
491 // auto-equip soldiers (only soldiers without layout)
492 for (int pass = 0; pass != 4; ++pass)
493 {
494 for (std::vector<BattleItem*>::iterator j = _craftInventoryTile->getInventory()->begin(); j != _craftInventoryTile->getInventory()->end();)
495 {
496 if ((*j)->getSlot() == ground)
497 {
498 bool add = false;
499
500 switch (pass)
501 {
502 // priority 1: rifles.
503 case 0:
504 add = (*j)->getRules()->isRifle();
505 break;
506 // priority 2: pistols (assuming no rifles were found).
507 case 1:
508 add = (*j)->getRules()->isPistol();
509 break;
510 // priority 3: ammunition.
511 case 2:
512 add = (*j)->getRules()->getBattleType() == BT_AMMO;
513 break;
514 // priority 4: leftovers.
515 case 3:
516 add = !(*j)->getRules()->isPistol() &&
517 !(*j)->getRules()->isRifle() &&
518 ((*j)->getRules()->getBattleType() != BT_FLARE || _worldShade >= 9);
519 break;
520 default:
521 break;
522 }
523
524 if (add)
525 {
526 for (std::vector<BattleUnit*>::iterator i = _save->getUnits()->begin(); i != _save->getUnits()->end(); ++i)
527 {
528 if (!(*i)->hasInventory() || !(*i)->getGeoscapeSoldier() || !(*i)->getGeoscapeSoldier()->getEquipmentLayout()->empty())
529 {
530 continue;
531 }
532 // let's not be greedy, we'll only take a second extra clip
533 // if everyone else has had a chance to take a first.
534 bool allowSecondClip = (pass == 3);
535
536 if (addItem(*j, *i, allowSecondClip))
537 {
538 j = _craftInventoryTile->getInventory()->erase(j);
539 add = false;
540 break;
541 }
542 }
543 if (!add)
544 {
545 continue;
546 }
547 }
548 }
549 ++j;
550 }
551 }
552 // clean up moved items
553 for (std::vector<BattleItem*>::iterator i = _craftInventoryTile->getInventory()->begin(); i != _craftInventoryTile->getInventory()->end();)
554 {
555 if ((*i)->getSlot() != ground)
556 {
557 i = _craftInventoryTile->getInventory()->erase(i);
558 }
559 else
560 {
561 _save->getItems()->push_back(*i);
562 ++i;
563 }
564 }
565 }
566
567 /**
568 * Adds an XCom vehicle to the game.
569 * Sets the correct turret depending on the ammo type.
570 * @param v Pointer to the Vehicle.
571 * @return Pointer to the spawned unit.
572 */
addXCOMVehicle(Vehicle * v)573 BattleUnit *BattlescapeGenerator::addXCOMVehicle(Vehicle *v)
574 {
575 std::string vehicle = v->getRules()->getType();
576 Unit *rule = _game->getRuleset()->getUnit(vehicle);
577 BattleUnit *unit = addXCOMUnit(new BattleUnit(rule, FACTION_PLAYER, _unitSequence++, _game->getRuleset()->getArmor(rule->getArmor()), 0));
578 if (unit)
579 {
580 BattleItem *item = new BattleItem(_game->getRuleset()->getItem(vehicle), _save->getCurrentItemId());
581 addItem(item, unit);
582 if(!v->getRules()->getCompatibleAmmo()->empty())
583 {
584 std::string ammo = v->getRules()->getCompatibleAmmo()->front();
585 BattleItem *ammoItem = new BattleItem(_game->getRuleset()->getItem(ammo), _save->getCurrentItemId());
586 addItem(ammoItem, unit);
587 ammoItem->setAmmoQuantity(v->getAmmo());
588 }
589 unit->setTurretType(v->getRules()->getTurretType());
590 }
591 return unit;
592 }
593
594 /**
595 * Adds a soldier to the game and places him on a free spawnpoint.
596 * Spawnpoints are either tiles in case of an XCom craft that landed.
597 * Or they are mapnodes in case there's no craft.
598 * @param soldier Pointer to the Soldier.
599 * @return Pointer to the spawned unit.
600 */
addXCOMUnit(BattleUnit * unit)601 BattleUnit *BattlescapeGenerator::addXCOMUnit(BattleUnit *unit)
602 {
603 // unit->setId(_unitCount++);
604
605 if (_craft == 0 || _save->getMissionType() == "STR_ALIEN_BASE_ASSAULT" || _save->getMissionType() == "STR_MARS_THE_FINAL_ASSAULT")
606 {
607 Node* node = _save->getSpawnNode(NR_XCOM, unit);
608 if (node)
609 {
610 _save->setUnitPosition(unit, node->getPosition());
611 _craftInventoryTile = _save->getTile(node->getPosition());
612 unit->setDirection(RNG::generate(0,7));
613 _save->getUnits()->push_back(unit);
614 _save->getTileEngine()->calculateFOV(unit);
615 unit->deriveRank();
616 return unit;
617 }
618 else if (_save->getMissionType() != "STR_BASE_DEFENSE")
619 {
620 if (placeUnitNearFriend(unit))
621 {
622 _craftInventoryTile = _save->getTile(unit->getPosition());
623 unit->setDirection(RNG::generate(0,7));
624 _save->getUnits()->push_back(unit);
625 _save->getTileEngine()->calculateFOV(unit);
626 unit->deriveRank();
627 return unit;
628 }
629 }
630 }
631 else if (_craft && !_craft->getRules()->getDeployment().empty() && !_baseInventory)
632 {
633 for (std::vector<std::vector<int> >::const_iterator i = _craft->getRules()->getDeployment().begin(); i != _craft->getRules()->getDeployment().end(); ++i)
634 {
635 Position pos = Position((*i)[0] + (_craftX * 10), (*i)[1] + (_craftY * 10), (*i)[2] + _craftZ);
636 int dir = (*i)[3];
637
638 if (canPlaceXCOMUnit(_save->getTile(pos)))
639 {
640 if (_save->setUnitPosition(unit, pos))
641 {
642 _save->getUnits()->push_back(unit);
643 unit->setDirection(dir);
644 unit->deriveRank();
645 return unit;
646 }
647 }
648 }
649 }
650 else
651 {
652 for (int i = 0; i < _mapsize_x * _mapsize_y * _mapsize_z; i++)
653 {
654 if (canPlaceXCOMUnit(_save->getTiles()[i]))
655 {
656 if (_save->setUnitPosition(unit, _save->getTiles()[i]->getPosition()))
657 {
658 _save->getUnits()->push_back(unit);
659 unit->deriveRank();
660 return unit;
661 }
662 }
663 }
664 }
665 delete unit;
666 return 0;
667 }
668
669 /**
670 * Checks if a soldier/tank can be placed on a given tile.
671 * @param tile the given tile.
672 * @return whether the unit can be placed here.
673 */
canPlaceXCOMUnit(Tile * tile)674 bool BattlescapeGenerator::canPlaceXCOMUnit(Tile *tile)
675 {
676 // to spawn an xcom soldier, there has to be a tile, with a floor, with the starting point attribute and no object in the way
677 if (tile &&
678 tile->getMapData(MapData::O_FLOOR) &&
679 tile->getMapData(MapData::O_FLOOR)->getSpecialType() == START_POINT &&
680 !tile->getMapData(MapData::O_OBJECT) &&
681 tile->getMapData(MapData::O_FLOOR)->getTUCost(MT_WALK) < 255)
682 {
683 if (_craftInventoryTile == 0)
684 _craftInventoryTile = tile;
685
686 return true;
687 }
688 return false;
689 }
690
691 /**
692 * Deploys the aliens, according to the alien deployment rules.
693 * @param race Pointer to the alien race.
694 * @param deployment Pointer to the deployment rules.
695 */
deployAliens(AlienRace * race,AlienDeployment * deployment)696 void BattlescapeGenerator::deployAliens(AlienRace *race, AlienDeployment *deployment)
697 {
698 int month;
699 if (_game->getSavedGame()->getMonthsPassed() != -1)
700 {
701 month =
702 ((size_t) _game->getSavedGame()->getMonthsPassed()) > _game->getRuleset()->getAlienItemLevels().size() - 1 ? // if
703 _game->getRuleset()->getAlienItemLevels().size() - 1 : // then
704 _game->getSavedGame()->getMonthsPassed() ; // else
705 }
706 else
707 {
708 month = _alienItemLevel;
709 }
710 for (std::vector<DeploymentData>::iterator d = deployment->getDeploymentData()->begin(); d != deployment->getDeploymentData()->end(); ++d)
711 {
712 std::string alienName = race->getMember((*d).alienRank);
713
714 int quantity;
715
716 if (_game->getSavedGame()->getDifficulty() < DIFF_VETERAN)
717 quantity = (*d).lowQty + RNG::generate(0, (*d).dQty); // beginner/experienced
718 else if (_game->getSavedGame()->getDifficulty() < DIFF_SUPERHUMAN)
719 quantity = (*d).lowQty+(((*d).highQty-(*d).lowQty)/2) + RNG::generate(0, (*d).dQty); // veteran/genius
720 else
721 quantity = (*d).highQty + RNG::generate(0, (*d).dQty); // super (and beyond?)
722
723 for (int i = 0; i < quantity; i++)
724 {
725 bool outside = RNG::generate(0,99) < (*d).percentageOutsideUfo;
726 if (_ufo == 0)
727 outside = false;
728 Unit *rule = _game->getRuleset()->getUnit(alienName);
729 BattleUnit *unit = addAlien(rule, (*d).alienRank, outside);
730 int itemLevel = _game->getRuleset()->getAlienItemLevels().at(month).at(RNG::generate(0,9));
731 if (unit)
732 {
733 // terrorist alien's equipment is a special case - they are fitted with a weapon which is the alien's name with suffix _WEAPON
734 if (rule->isLivingWeapon())
735 {
736 std::string terroristWeapon = rule->getRace().substr(4);
737 terroristWeapon += "_WEAPON";
738 RuleItem *ruleItem = _game->getRuleset()->getItem(terroristWeapon);
739 if (ruleItem)
740 {
741 BattleItem *item = new BattleItem(ruleItem, _save->getCurrentItemId());
742 if (!addItem(item, unit))
743 {
744 delete item;
745 }
746 else
747 {
748 unit->setTurretType(item->getRules()->getTurretType());
749 }
750 }
751 }
752 else
753 {
754 for (std::vector<std::string>::iterator it = (*d).itemSets.at(itemLevel).items.begin(); it != (*d).itemSets.at(itemLevel).items.end(); ++it)
755 {
756 RuleItem *ruleItem = _game->getRuleset()->getItem((*it));
757 if (ruleItem)
758 {
759 BattleItem *item = new BattleItem(ruleItem, _save->getCurrentItemId());
760 if (!addItem(item, unit))
761 {
762 delete item;
763 }
764 }
765 }
766 }
767 }
768 }
769 }
770 }
771
772
773
774 /**
775 * Adds an alien to the game and places him on a free spawnpoint.
776 * @param rules Pointer to the Unit which holds info about the alien .
777 * @param alienRank The rank of the alien, used for spawn point search.
778 * @param outside Whether the alien should spawn outside or inside the UFO.
779 * @return Pointer to the created unit.
780 */
addAlien(Unit * rules,int alienRank,bool outside)781 BattleUnit *BattlescapeGenerator::addAlien(Unit *rules, int alienRank, bool outside)
782 {
783 int difficulty = (int)(_game->getSavedGame()->getDifficulty());
784 BattleUnit *unit = new BattleUnit(rules, FACTION_HOSTILE, _unitSequence++, _game->getRuleset()->getArmor(rules->getArmor()), difficulty);
785 Node *node = 0;
786
787 /* following data is the order in which certain alien ranks spawn on certain node ranks */
788 /* note that they all can fall back to rank 0 nodes - which is scout (outside ufo) */
789
790 for (int i = 0; i < 7 && node == 0; i++)
791 {
792 if (outside)
793 node = _save->getSpawnNode(0, unit); // when alien is instructed to spawn outside, we only look for node 0 spawnpoints
794 else
795 node = _save->getSpawnNode(Node::nodeRank[alienRank][i], unit);
796 }
797
798 if (node && _save->setUnitPosition(unit, node->getPosition()))
799 {
800 unit->setAIState(new AlienBAIState(_game->getSavedGame()->getSavedBattle(), unit, node));
801 unit->setRankInt(alienRank);
802 int dir = _save->getTileEngine()->faceWindow(node->getPosition());
803 Position craft = _game->getSavedGame()->getSavedBattle()->getUnits()->at(0)->getPosition();
804 if (_save->getTileEngine()->distance(node->getPosition(), craft) <= 20 && RNG::percent(20 * difficulty))
805 dir = unit->directionTo(craft);
806 if (dir != -1)
807 unit->setDirection(dir);
808 else
809 unit->setDirection(RNG::generate(0,7));
810
811 if (!difficulty)
812 {
813 unit->halveArmor();
814 }
815
816 // we only add a unit if it has a node to spawn on.
817 // (stops them spawning at 0,0,0)
818 _save->getUnits()->push_back(unit);
819 }
820 else
821 {
822 delete unit;
823 unit = 0;
824 }
825
826 return unit;
827 }
828
829 /**
830 * Adds a civilian to the game and places him on a free spawnpoint.
831 * @param rules Pointer to the Unit which holds info about the civilian.
832 * @return Pointer to the created unit.
833 */
addCivilian(Unit * rules)834 BattleUnit *BattlescapeGenerator::addCivilian(Unit *rules)
835 {
836 BattleUnit *unit = new BattleUnit(rules, FACTION_NEUTRAL, _unitSequence++, _game->getRuleset()->getArmor(rules->getArmor()), 0);
837 Node *node = _save->getSpawnNode(0, unit);
838
839 if (node)
840 {
841 _save->setUnitPosition(unit, node->getPosition());
842 unit->setAIState(new CivilianBAIState(_game->getSavedGame()->getSavedBattle(), unit, node));
843 unit->setDirection(RNG::generate(0,7));
844
845 // we only add a unit if it has a node to spawn on.
846 // (stops them spawning at 0,0,0)
847 _save->getUnits()->push_back(unit);
848 }
849 else if (placeUnitNearFriend(unit))
850 {
851 unit->setAIState(new CivilianBAIState(_game->getSavedGame()->getSavedBattle(), unit, node));
852 unit->setDirection(RNG::generate(0,7));
853 _save->getUnits()->push_back(unit);
854 }
855 else
856 {
857 delete unit;
858 unit = 0;
859 }
860
861 return unit;
862 }
863
864 /**
865 * Places an item on an XCom soldier based on equipment layout.
866 * @param item Pointer to the Item.
867 * @return Pointer to the Item.
868 */
placeItemByLayout(BattleItem * item)869 bool BattlescapeGenerator::placeItemByLayout(BattleItem *item)
870 {
871 RuleInventory *ground = _game->getRuleset()->getInventory("STR_GROUND");
872 if (item->getSlot() == ground)
873 {
874 bool loaded;
875 RuleInventory *righthand = _game->getRuleset()->getInventory("STR_RIGHT_HAND");
876
877 // find the first soldier with a matching layout-slot
878 for (std::vector<BattleUnit*>::iterator i = _save->getUnits()->begin(); i != _save->getUnits()->end(); ++i)
879 {
880 // skip the vehicles, we need only X-Com soldiers WITH equipment-layout
881 if ((*i)->getArmor()->getSize() > 1 || !(*i)->getGeoscapeSoldier() || (*i)->getGeoscapeSoldier()->getEquipmentLayout()->empty())
882 {
883 continue;
884 }
885
886 // find the first matching layout-slot which is not already occupied
887 std::vector<EquipmentLayoutItem*> *layoutItems = (*i)->getGeoscapeSoldier()->getEquipmentLayout();
888 for (std::vector<EquipmentLayoutItem*>::iterator j = layoutItems->begin(); j != layoutItems->end(); ++j)
889 {
890 if (item->getRules()->getType() != (*j)->getItemType()
891 || (*i)->getItem((*j)->getSlot(), (*j)->getSlotX(), (*j)->getSlotY())) continue;
892
893 if ((*j)->getAmmoItem() == "NONE")
894 {
895 loaded = true;
896 }
897 else
898 {
899 loaded = false;
900 // maybe we find the layout-ammo on the ground to load it with
901 for (std::vector<BattleItem*>::iterator k = _craftInventoryTile->getInventory()->begin(); (!loaded) && k != _craftInventoryTile->getInventory()->end(); ++k)
902 {
903 if ((*k)->getRules()->getType() == (*j)->getAmmoItem() && (*k)->getSlot() == ground
904 && item->setAmmoItem((*k)) == 0)
905 {
906 _save->getItems()->push_back(*k);
907 (*k)->setSlot(righthand);
908 loaded = true;
909 // note: soldier is not owner of the ammo, we are using this fact when saving equipments
910 }
911 }
912 }
913 // only place the weapon onto the soldier when it's loaded with its layout-ammo (if any)
914 if (loaded)
915 {
916 item->moveToOwner((*i));
917 item->setSlot(_game->getRuleset()->getInventory((*j)->getSlot()));
918 item->setSlotX((*j)->getSlotX());
919 item->setSlotY((*j)->getSlotY());
920 if (Options::includePrimeStateInSavedLayout &&
921 (item->getRules()->getBattleType() == BT_GRENADE ||
922 item->getRules()->getBattleType() == BT_PROXIMITYGRENADE))
923 {
924 item->setFuseTimer((*j)->getFuseTimer());
925 }
926 _save->getItems()->push_back(item);
927 return true;
928 }
929 }
930 }
931 }
932 return false;
933 }
934
935 /**
936 * Adds an item to an XCom soldier (auto-equip).
937 * @param item Pointer to the Item.
938 * @param unit Pointer to the Unit.
939 * @param allowSecondClip allow the unit to take a second clip or not. (only applies to xcom soldiers, aliens are allowed regardless of this flag)
940 * @return if the item was placed or not.
941 */
addItem(BattleItem * item,BattleUnit * unit,bool allowSecondClip)942 bool BattlescapeGenerator::addItem(BattleItem *item, BattleUnit *unit, bool allowSecondClip)
943 {
944 RuleInventory *ground = _game->getRuleset()->getInventory("STR_GROUND");
945 RuleInventory *rightHand = _game->getRuleset()->getInventory("STR_RIGHT_HAND");
946 bool placed = false;
947 bool loaded = false;
948 BattleItem *weapon = unit->getItem("STR_RIGHT_HAND");
949 int weight = 0;
950
951 // tanks and aliens don't care about weight or multiple items,
952 // their loadouts are defined in the rulesets and more or less set in stone.
953 if (unit->getFaction() == FACTION_PLAYER && unit->hasInventory())
954 {
955 weight = unit->getCarriedWeight() + item->getRules()->getWeight();
956 if (item->getAmmoItem() && item->getAmmoItem() != item)
957 {
958 weight += item->getAmmoItem()->getRules()->getWeight();
959 }
960 // allow all weapons to be loaded by avoiding this check,
961 // they'll return false later anyway if the unit has something in his hand.
962 if (item->getRules()->getCompatibleAmmo()->empty())
963 {
964 int tally = 0;
965 for (std::vector<BattleItem*>::iterator i = unit->getInventory()->begin(); i != unit->getInventory()->end(); ++i)
966 {
967 if (item->getRules()->getType() == (*i)->getRules()->getType())
968 {
969 if (allowSecondClip && item->getRules()->getBattleType() == BT_AMMO)
970 {
971 tally++;
972 if (tally == 2)
973 {
974 return false;
975 }
976 }
977 else
978 {
979 // we already have one, thanks.
980 return false;
981 }
982 }
983 }
984 }
985 }
986
987 switch (item->getRules()->getBattleType())
988 {
989 case BT_FIREARM:
990 case BT_MELEE:
991 if (item->getAmmoItem() || unit->getFaction() != FACTION_PLAYER || !unit->hasInventory())
992 {
993 loaded = true;
994 }
995
996 if (loaded && (unit->getGeoscapeSoldier() == 0 || _allowAutoLoadout))
997 {
998 if (!unit->getItem("STR_RIGHT_HAND") && unit->getStats()->strength * 0.66 >= weight) // weight is always considered 0 for aliens
999 {
1000 item->moveToOwner(unit);
1001 item->setSlot(rightHand);
1002 placed = true;
1003 }
1004 }
1005 break;
1006 case BT_AMMO:
1007 // no weapon, or our weapon takes no ammo, or this ammo isn't compatible.
1008 // we won't be needing this. move on.
1009 if (!weapon || weapon->getRules()->getCompatibleAmmo()->empty() ||
1010 std::find(weapon->getRules()->getCompatibleAmmo()->begin(),
1011 weapon->getRules()->getCompatibleAmmo()->end(),
1012 item->getRules()->getType()) == weapon->getRules()->getCompatibleAmmo()->end())
1013 {
1014 break;
1015 }
1016 // xcom weapons will already be loaded, aliens and tanks, however, get their ammo added afterwards.
1017 // so let's try to load them here.
1018 if ((weapon->getRules()->isFixed() || unit->getFaction() != FACTION_PLAYER) &&
1019 !weapon->getAmmoItem() &&
1020 weapon->setAmmoItem(item) == 0)
1021 {
1022 item->setSlot(rightHand);
1023 placed = true;
1024 break;
1025 }
1026 default:
1027 if ((unit->getGeoscapeSoldier() == 0 || _allowAutoLoadout))
1028 {
1029 if (unit->getStats()->strength >= weight) // weight is always considered 0 for aliens
1030 {
1031 for (std::vector<std::string>::const_iterator i = _game->getRuleset()->getInvsList().begin(); i != _game->getRuleset()->getInvsList().end() && !placed; ++i)
1032 {
1033 RuleInventory *slot = _game->getRuleset()->getInventory(*i);
1034 if (slot->getType() == INV_SLOT)
1035 {
1036 for (std::vector<RuleSlot>::iterator j = slot->getSlots()->begin(); j != slot->getSlots()->end() && !placed; ++j)
1037 {
1038 if (!Inventory::overlapItems(unit, item, slot, j->x, j->y) && slot->fitItemInSlot(item->getRules(), j->x, j->y))
1039 {
1040 item->moveToOwner(unit);
1041 item->setSlot(slot);
1042 item->setSlotX(j->x);
1043 item->setSlotY(j->y);
1044 placed = true;
1045 break;
1046 }
1047 }
1048 }
1049 }
1050 }
1051 }
1052 break;
1053 }
1054
1055 if (placed)
1056 {
1057 _save->getItems()->push_back(item);
1058 }
1059 item->setXCOMProperty(unit->getFaction() == FACTION_PLAYER);
1060
1061 return placed;
1062 }
1063
1064 /**
1065 * Generates a map (set of tiles) for a new battlescape game.
1066 */
generateMap()1067 void BattlescapeGenerator::generateMap()
1068 {
1069 int x = 0, y = 0;
1070 int blocksToDo = 0;
1071 std::vector< std::vector<MapBlock*> > blocks;
1072 std::vector< std::vector<bool> > storageBlocks;
1073 std::vector< std::vector<bool> > landingzone;
1074 std::vector< std::vector<int> > segments;
1075 int ufoX = 0, ufoY = 0;
1076 bool placed = false;
1077
1078 MapBlock* dummy = new MapBlock("dummy", 0, 0, MT_DEFAULT);
1079 MapBlock* craftMap = 0;
1080 MapBlock* ufoMap = 0;
1081
1082 int mapDataSetIDOffset = 0;
1083 int craftDataSetIDOffset = 0;
1084
1085 blocks.resize((_mapsize_x / 10), std::vector<MapBlock*>((_mapsize_y / 10)));
1086 landingzone.resize((_mapsize_x / 10), std::vector<bool>((_mapsize_y / 10),false));
1087 storageBlocks.resize((_mapsize_x / 10), std::vector<bool>((_mapsize_y / 10),false));
1088 segments.resize((_mapsize_x / 10), std::vector<int>((_mapsize_y / 10),0));
1089
1090 blocksToDo = (_mapsize_x / 10) * (_mapsize_y / 10);
1091
1092 /* Determine UFO landingzone (do this first because ufo is generally bigger) */
1093 if (_ufo != 0)
1094 {
1095 // pick a random ufo mapblock, can have all kinds of sizes
1096 ufoMap = _ufo->getRules()->getBattlescapeTerrainData()->getRandomMapBlock(999, MT_DEFAULT);
1097
1098 ufoX = RNG::generate(0, (_mapsize_x / 10) - ufoMap->getSizeX() / 10);
1099 ufoY = RNG::generate(0, (_mapsize_y / 10) - ufoMap->getSizeY() / 10);
1100
1101 for (int i = 0; i < ufoMap->getSizeX() / 10; ++i)
1102 {
1103 for (int j = 0; j < ufoMap->getSizeY() / 10; ++j)
1104 {
1105 landingzone[ufoX + i][ufoY + j] = true;
1106 blocks[ufoX + i][ufoY + j] = _terrain->getRandomMapBlock(10, MT_LANDINGZONE);
1107 blocksToDo--;
1108 }
1109 }
1110 }
1111
1112 /* Determine Craft landingzone */
1113 /* alien base assault has no craft landing zone */
1114 if (_craft != 0 && (_save->getMissionType() != "STR_ALIEN_BASE_ASSAULT") && ( _save->getMissionType() != "STR_MARS_THE_FINAL_ASSAULT"))
1115 {
1116 // pick a random craft mapblock, can have all kinds of sizes
1117 craftMap = _craft->getRules()->getBattlescapeTerrainData()->getRandomMapBlock(999, MT_DEFAULT);
1118 while (!placed)
1119 {
1120 _craftX = RNG::generate(0, (_mapsize_x/10)- craftMap->getSizeX() / 10);
1121 _craftY = RNG::generate(0, (_mapsize_y/10)- craftMap->getSizeY() / 10);
1122 placed = true;
1123 // check if this place is ok
1124 for (int i = 0; i < craftMap->getSizeX() / 10; ++i)
1125 {
1126 for (int j = 0; j < craftMap->getSizeY() / 10; ++j)
1127 {
1128 if (landingzone[_craftX + i][_craftY + j])
1129 {
1130 placed = false; // whoops the ufo is already here, try again
1131 }
1132 }
1133 }
1134 // if ok, allocate it
1135 if (placed)
1136 {
1137 for (int i = 0; i < craftMap->getSizeX() / 10; ++i)
1138 {
1139 for (int j = 0; j < craftMap->getSizeY() / 10; ++j)
1140 {
1141 landingzone[_craftX + i][_craftY + j] = true;
1142 blocks[_craftX + i][_craftY + j] = _terrain->getRandomMapBlock(10, MT_LANDINGZONE);
1143 blocksToDo--;
1144 }
1145 }
1146 }
1147 }
1148 }
1149
1150 /* determine positioning of the urban terrain roads */
1151 if (_save->getMissionType() == "STR_TERROR_MISSION")
1152 {
1153 int roadStyle = RNG::generate(0,99);
1154 std::vector<int> roadChances = _terrain->getRoadTypeOdds();
1155 bool EWRoad = roadStyle < roadChances.at(0);
1156 bool NSRoad = !EWRoad && roadStyle < roadChances.at(0) + roadChances.at(1);
1157 bool TwoRoads = !EWRoad && !NSRoad;
1158 int roadX = _craftX;
1159 int roadY = _craftY;
1160 // make sure the road(s) are not crossing the craft landing site
1161 while ((roadX >= _craftX && roadX < _craftX + (craftMap->getSizeX() / 10)) || (roadY >= _craftY && roadY < _craftY + (craftMap->getSizeY() / 10)))
1162 {
1163 roadX = RNG::generate(0, (_mapsize_x/10)- 1);
1164 roadY = RNG::generate(0, (_mapsize_y/10)- 1);
1165 }
1166 if (TwoRoads)
1167 {
1168 // put a crossing on the X,Y position and fill the rest with roads
1169 blocks[roadX][roadY] = _terrain->getRandomMapBlock(10, MT_CROSSING);
1170 blocksToDo--;
1171 EWRoad = true;
1172 NSRoad = true;
1173 }
1174 if (EWRoad)
1175 {
1176 while (x < (_mapsize_x / 10))
1177 {
1178 if (blocks[x][roadY] == 0)
1179 {
1180 blocks[x][roadY] = _terrain->getRandomMapBlock(10, MT_EWROAD);
1181 blocksToDo--;
1182 }
1183 x++;
1184 }
1185 }
1186 if (NSRoad)
1187 {
1188 while (y < (_mapsize_y / 10))
1189 {
1190 if (blocks[roadX][y] == 0)
1191 {
1192 blocks[roadX][y] = _terrain->getRandomMapBlock(10, MT_NSROAD);
1193 blocksToDo--;
1194 }
1195 y++;
1196 }
1197 }
1198 }
1199 /* determine positioning of base modules */
1200 else if (_save->getMissionType() == "STR_BASE_DEFENSE")
1201 {
1202 for (std::vector<BaseFacility*>::const_iterator i = _base->getFacilities()->begin(); i != _base->getFacilities()->end(); ++i)
1203 {
1204 if ((*i)->getBuildTime() == 0)
1205 {
1206 int num = 0;
1207 for (int y = (*i)->getY(); y < (*i)->getY() + (*i)->getRules()->getSize(); ++y)
1208 {
1209 for (int x = (*i)->getX(); x < (*i)->getX() + (*i)->getRules()->getSize(); ++x)
1210 {
1211 // lots of crazy stuff here, which is for the hangars (or other large base facilities one may create)
1212 std::string mapname = (*i)->getRules()->getMapName();
1213 std::ostringstream newname;
1214 newname << mapname.substr(0, mapname.size()-2); // strip of last 2 digits
1215 int mapnum = atoi(mapname.substr(mapname.size()-2, 2).c_str()); // get number
1216 mapnum += num;
1217 if (mapnum < 10) newname << 0;
1218 newname << mapnum;
1219 blocks[x][y] = _terrain->getMapBlock(newname.str());
1220 storageBlocks[x][y] = ((*i)->getRules()->getStorage() > 0);
1221 num++;
1222 }
1223 }
1224 }
1225 }
1226
1227 // fill with dirt
1228 for (int i = 0; i < (_mapsize_x / 10); ++i)
1229 {
1230 for (int j = 0; j < (_mapsize_y / 10); ++j)
1231 {
1232 if (blocks[i][j] == 0)
1233 {
1234 blocks[i][j] = _terrain->getRandomMapBlock(10, MT_DIRT);
1235 }
1236 }
1237 }
1238
1239 blocksToDo = 0;
1240 }
1241 /* determine positioning of base modules */
1242 else if (_save->getMissionType() == "STR_ALIEN_BASE_ASSAULT" || _save->getMissionType() == "STR_MARS_THE_FINAL_ASSAULT")
1243 {
1244 int randX = RNG::generate(0, (_mapsize_x/10)- 2);
1245 int randY = RNG::generate(0, (_mapsize_y/10)- 2);
1246 // add the command center
1247 blocks[randX][randY] = _terrain->getRandomMapBlock(20, (_save->getMissionType() == "STR_MARS_THE_FINAL_ASSAULT")?MT_FINALCOMM:MT_UBASECOMM);
1248 blocksToDo--;
1249 // mark mapblocks as used
1250 blocks[randX + 1][randY] = dummy;
1251 blocksToDo--;
1252 blocks[randX + 1][randY + 1] = dummy;
1253 blocksToDo--;
1254 blocks[randX][randY + 1] = dummy;
1255 blocksToDo--;
1256 // add two lifts (not on top of the command center)
1257 for (int i = 0; i < 2; i++)
1258 {
1259 while (blocks[randX][randY] != NULL)
1260 {
1261 randX = RNG::generate(0, (_mapsize_x/10)- 1);
1262 randY = RNG::generate(0, (_mapsize_y/10)- 1);
1263 }
1264 // add the lift
1265 blocks[randX][randY] = _terrain->getRandomMapBlock(10, MT_XCOMSPAWN);
1266 blocksToDo--;
1267 }
1268 }
1269 else if (_save->getMissionType() == "STR_MARS_CYDONIA_LANDING")
1270 {
1271 int randX = RNG::generate(0, (_mapsize_x/10)- 2);
1272 int randY = RNG::generate(0, (_mapsize_y/10)- 2);
1273 // add one lift
1274 while (blocks[randX][randY] != NULL || landingzone[randX][randY])
1275 {
1276 randX = RNG::generate(0, (_mapsize_x/10)- 1);
1277 randY = RNG::generate(0, (_mapsize_y/10)- 1);
1278 }
1279 // add the lift
1280 blocks[randX][randY] = _terrain->getRandomMapBlock(10, MT_XCOMSPAWN);
1281 blocksToDo--;
1282 }
1283
1284 x = 0;
1285 y = 0;
1286 int maxLarge = _terrain->getLargeBlockLimit();
1287 int curLarge = 0;
1288 int tries = 0;
1289 while (curLarge != maxLarge && tries <= 50)
1290 {
1291 int randX = RNG::generate(0, (_mapsize_x/10)- 2);
1292 int randY = RNG::generate(0, (_mapsize_y/10)- 2);
1293 if (!blocks[randX][randY] && !blocks[randX + 1][randY] && !blocks[randX + 1][randY + 1] && !blocks[randX][randY + 1]
1294 && !landingzone[randX][randY] && !landingzone[randX + 1][randY] && !landingzone[randX][randY + 1] && !landingzone[randX + 1][randY + 1])
1295 {
1296 blocks[randX][randY] = _terrain->getRandomMapBlock(20, MT_DEFAULT, true);
1297 blocksToDo--;
1298 // mark mapblocks as used
1299 blocks[randX + 1][randY] = dummy;
1300 blocksToDo--;
1301 blocks[randX + 1][randY + 1] = dummy;
1302 blocksToDo--;
1303 blocks[randX][randY + 1] = dummy;
1304 blocksToDo--;
1305 curLarge++;
1306 }
1307 tries++;
1308 }
1309 /* Random map generation for crash/landing sites */
1310 while (blocksToDo)
1311 {
1312 if (blocks[x][y] == 0)
1313 {
1314 if ((_save->getMissionType() == "STR_ALIEN_BASE_ASSAULT" || _save->getMissionType() == "STR_MARS_THE_FINAL_ASSAULT") && RNG::generate(0,100) > 60)
1315 {
1316 blocks[x][y] = _terrain->getRandomMapBlock(10, MT_CROSSING);
1317 }
1318 else
1319 {
1320 blocks[x][y] = _terrain->getRandomMapBlock(10, landingzone[x][y]?MT_LANDINGZONE:MT_DEFAULT);
1321 }
1322 blocksToDo--;
1323 x++;
1324 }
1325 else
1326 {
1327 x++;
1328 }
1329
1330 if (x >= (_mapsize_x / 10)) // reach the end
1331 {
1332 x = 0;
1333 y++;
1334 }
1335 }
1336
1337 //reset the "times used" fields.
1338 _terrain->resetMapBlocks();
1339
1340 for (std::vector<MapDataSet*>::iterator i = _terrain->getMapDataSets()->begin(); i != _terrain->getMapDataSets()->end(); ++i)
1341 {
1342 (*i)->loadData();
1343 if (_game->getRuleset()->getMCDPatch((*i)->getName()))
1344 {
1345 _game->getRuleset()->getMCDPatch((*i)->getName())->modifyData(*i);
1346 }
1347 _save->getMapDataSets()->push_back(*i);
1348 mapDataSetIDOffset++;
1349 }
1350
1351 /* now load them up */
1352 int segment = 0;
1353 for (int itY = 0; itY < (_mapsize_y / 10); itY++)
1354 {
1355 for (int itX = 0; itX < (_mapsize_x / 10); itX++)
1356 {
1357 segments[itX][itY] = segment;
1358 if (blocks[itX][itY] != 0 && blocks[itX][itY] != dummy)
1359 {
1360 loadMAP(blocks[itX][itY], itX * 10, itY * 10, _terrain, 0);
1361 if (!landingzone[itX][itY])
1362 {
1363 loadRMP(blocks[itX][itY], itX * 10, itY * 10, segment++);
1364 }
1365 }
1366 }
1367 }
1368
1369 /* making passages between blocks in a base map */
1370 if (_save->getMissionType() == "STR_BASE_DEFENSE" || _save->getMissionType() == "STR_ALIEN_BASE_ASSAULT" || _save->getMissionType() == "STR_MARS_THE_FINAL_ASSAULT")
1371 {
1372 int ewallfix = 14;
1373 int swallfix = 13;
1374 int ewallfixSet = 1;
1375 int swallfixSet = 1;
1376 if (_save->getMissionType() == "STR_ALIEN_BASE_ASSAULT" || _save->getMissionType() == "STR_MARS_THE_FINAL_ASSAULT")
1377 {
1378 ewallfix = 17; // north wall
1379 swallfix = 18; // west wall
1380 ewallfixSet = 2;
1381 swallfixSet = 2;
1382 }
1383
1384 MapDataSet *mds = _terrain->getMapDataSets()->at(ewallfixSet);
1385 MapBlock *dirt = _terrain->getRandomMapBlock(10, MT_DIRT);
1386 for (int i = 0; i < (_mapsize_x / 10); ++i)
1387 {
1388 for (int j = 0; j < (_mapsize_y / 10); ++j)
1389 {
1390 if (blocks[i][j] == dirt)
1391 continue;
1392
1393 // general stores - there is where the items are put
1394 if (storageBlocks[i][j])
1395 {
1396 for (int k = i * 10; k != (i + 1) * 10; ++k)
1397 {
1398 for (int l = j * 10; l != (j + 1) * 10; ++l)
1399 {
1400 // we only want every other tile, giving us a "checkerboard" pattern
1401 if ((k+l) % 2 == 0)
1402 {
1403 Tile *t = _save->getTile(Position(k,l,1));
1404 Tile *tEast = _save->getTile(Position(k+1,l,1));
1405 Tile *tSouth = _save->getTile(Position(k,l+1,1));
1406 if (t && t->getMapData(MapData::O_FLOOR) && !t->getMapData(MapData::O_OBJECT) &&
1407 tEast && !tEast->getMapData(MapData::O_WESTWALL) &&
1408 tSouth && !tSouth->getMapData(MapData::O_NORTHWALL))
1409 {
1410 _save->getStorageSpace().push_back(Position(k, l, 1));
1411 }
1412 }
1413 }
1414 }
1415 // let's put the inventory tile on the lower floor, just to be safe.
1416 _craftInventoryTile = _save->getTile(Position((i*10)+5,(j*10)+5,0));
1417 }
1418
1419 // drill east
1420 if (i < (_mapsize_x / 10)-1
1421 && blocks[i+1][j] != dirt
1422 && _save->getTile(Position((i*10)+9,(j*10)+4,0))->getMapData(MapData::O_OBJECT)
1423 && (!_save->getTile(Position((i*10)+8,(j*10)+4,0))->getMapData(MapData::O_OBJECT)
1424 || (_save->getTile(Position((i*10)+9,(j*10)+4,0))->getMapData(MapData::O_OBJECT)
1425 != _save->getTile(Position((i*10)+8,(j*10)+4,0))->getMapData(MapData::O_OBJECT))))
1426 {
1427 // remove stuff
1428 _save->getTile(Position((i*10)+9,(j*10)+3,0))->setMapData(0, -1, -1, MapData::O_WESTWALL);
1429 _save->getTile(Position((i*10)+9,(j*10)+3,0))->setMapData(0, -1, -1, MapData::O_OBJECT);
1430 _save->getTile(Position((i*10)+9,(j*10)+4,0))->setMapData(0, -1, -1, MapData::O_WESTWALL);
1431 _save->getTile(Position((i*10)+9,(j*10)+4,0))->setMapData(0, -1, -1, MapData::O_OBJECT);
1432 _save->getTile(Position((i*10)+9,(j*10)+5,0))->setMapData(0, -1, -1, MapData::O_WESTWALL);
1433 _save->getTile(Position((i*10)+9,(j*10)+5,0))->setMapData(0, -1, -1, MapData::O_OBJECT);
1434 if (_save->getTile(Position((i*10)+9,(j*10)+2,0))->getMapData(MapData::O_OBJECT))
1435 {
1436 //wallfix
1437 _save->getTile(Position((i*10)+9,(j*10)+3,0))->setMapData(mds->getObjects()->at(ewallfix), ewallfix, ewallfixSet, MapData::O_NORTHWALL);
1438 _save->getTile(Position((i*10)+9,(j*10)+6,0))->setMapData(mds->getObjects()->at(ewallfix), ewallfix, ewallfixSet, MapData::O_NORTHWALL);
1439 }
1440 if (_save->getMissionType() == "STR_ALIEN_BASE_ASSAULT" || _save->getMissionType() == "STR_MARS_THE_FINAL_ASSAULT")
1441 {
1442 //wallcornerfix
1443 if (!_save->getTile(Position((i*10)+10,(j*10)+3,0))->getMapData(MapData::O_NORTHWALL))
1444 {
1445 _save->getTile(Position(((i+1)*10),(j*10)+3,0))->setMapData(mds->getObjects()->at(swallfix+1), swallfix+1, swallfixSet, MapData::O_OBJECT);
1446 }
1447 //floorfix
1448 _save->getTile(Position((i*10)+9,(j*10)+3,0))->setMapData(_terrain->getMapDataSets()->at(1)->getObjects()->at(63), 63, 1, MapData::O_FLOOR);
1449 _save->getTile(Position((i*10)+9,(j*10)+4,0))->setMapData(_terrain->getMapDataSets()->at(1)->getObjects()->at(63), 63, 1, MapData::O_FLOOR);
1450 _save->getTile(Position((i*10)+9,(j*10)+5,0))->setMapData(_terrain->getMapDataSets()->at(1)->getObjects()->at(63), 63, 1, MapData::O_FLOOR);
1451 }
1452 // remove more stuff
1453 _save->getTile(Position(((i+1)*10),(j*10)+3,0))->setMapData(0, -1, -1, MapData::O_WESTWALL);
1454 _save->getTile(Position(((i+1)*10),(j*10)+4,0))->setMapData(0, -1, -1, MapData::O_WESTWALL);
1455 _save->getTile(Position(((i+1)*10),(j*10)+5,0))->setMapData(0, -1, -1, MapData::O_WESTWALL);
1456 }
1457 // drill south
1458 if (j < (_mapsize_y / 10)-1
1459 && blocks[i][j+1] != dirt
1460 && _save->getTile(Position((i*10)+4,(j*10)+9,0))->getMapData(MapData::O_OBJECT)
1461 && (!_save->getTile(Position((i*10)+4,(j*10)+8,0))->getMapData(MapData::O_OBJECT)
1462 || (_save->getTile(Position((i*10)+4,(j*10)+9,0))->getMapData(MapData::O_OBJECT)
1463 != _save->getTile(Position((i*10)+4,(j*10)+8,0))->getMapData(MapData::O_OBJECT))))
1464 {
1465 // remove stuff
1466 _save->getTile(Position((i*10)+3,(j*10)+9,0))->setMapData(0, -1, -1, MapData::O_NORTHWALL);
1467 _save->getTile(Position((i*10)+3,(j*10)+9,0))->setMapData(0, -1, -1, MapData::O_OBJECT);
1468 _save->getTile(Position((i*10)+4,(j*10)+9,0))->setMapData(0, -1, -1, MapData::O_NORTHWALL);
1469 _save->getTile(Position((i*10)+4,(j*10)+9,0))->setMapData(0, -1, -1, MapData::O_OBJECT);
1470 _save->getTile(Position((i*10)+5,(j*10)+9,0))->setMapData(0, -1, -1, MapData::O_NORTHWALL);
1471 _save->getTile(Position((i*10)+5,(j*10)+9,0))->setMapData(0, -1, -1, MapData::O_OBJECT);
1472 if (_save->getTile(Position((i*10)+2,(j*10)+9,0))->getMapData(MapData::O_OBJECT))
1473 {
1474 // wallfix
1475 _save->getTile(Position((i*10)+3,(j*10)+9,0))->setMapData(mds->getObjects()->at(swallfix), swallfix, swallfixSet, MapData::O_WESTWALL);
1476 _save->getTile(Position((i*10)+6,(j*10)+9,0))->setMapData(mds->getObjects()->at(swallfix), swallfix, swallfixSet, MapData::O_WESTWALL);
1477 }
1478 if (_save->getMissionType() == "STR_ALIEN_BASE_ASSAULT" || _save->getMissionType() == "STR_MARS_THE_FINAL_ASSAULT")
1479 {
1480 // wallcornerfix
1481 if (!_save->getTile(Position((i*10)+3,(j*10)+10,0))->getMapData(MapData::O_WESTWALL))
1482 {
1483 _save->getTile(Position((i*10)+3,((j+1)*10),0))->setMapData(mds->getObjects()->at(swallfix+1), swallfix+1, swallfixSet, MapData::O_OBJECT);
1484 }
1485 // floorfix
1486 _save->getTile(Position((i*10)+3,(j*10)+9,0))->setMapData(_terrain->getMapDataSets()->at(1)->getObjects()->at(63), 63, 1, MapData::O_FLOOR);
1487 _save->getTile(Position((i*10)+4,(j*10)+9,0))->setMapData(_terrain->getMapDataSets()->at(1)->getObjects()->at(63), 63, 1, MapData::O_FLOOR);
1488 _save->getTile(Position((i*10)+5,(j*10)+9,0))->setMapData(_terrain->getMapDataSets()->at(1)->getObjects()->at(63), 63, 1, MapData::O_FLOOR);
1489 }
1490 // remove more stuff
1491 _save->getTile(Position((i*10)+3,(j+1)*10,0))->setMapData(0, -1, -1, MapData::O_NORTHWALL);
1492 _save->getTile(Position((i*10)+4,(j+1)*10,0))->setMapData(0, -1, -1, MapData::O_NORTHWALL);
1493 _save->getTile(Position((i*10)+5,(j+1)*10,0))->setMapData(0, -1, -1, MapData::O_NORTHWALL);
1494 }
1495 }
1496 }
1497 }
1498
1499
1500 if (_ufo != 0)
1501 {
1502 for (std::vector<MapDataSet*>::iterator i = _ufo->getRules()->getBattlescapeTerrainData()->getMapDataSets()->begin(); i != _ufo->getRules()->getBattlescapeTerrainData()->getMapDataSets()->end(); ++i)
1503 {
1504 (*i)->loadData();
1505 if (_game->getRuleset()->getMCDPatch((*i)->getName()))
1506 {
1507 _game->getRuleset()->getMCDPatch((*i)->getName())->modifyData(*i);
1508 }
1509 _save->getMapDataSets()->push_back(*i);
1510 craftDataSetIDOffset++;
1511 }
1512 loadMAP(ufoMap, ufoX * 10, ufoY * 10, _ufo->getRules()->getBattlescapeTerrainData(), mapDataSetIDOffset);
1513 loadRMP(ufoMap, ufoX * 10, ufoY * 10, Node::UFOSEGMENT);
1514 for (int i = 0; i < ufoMap->getSizeX() / 10; ++i)
1515 {
1516 for (int j = 0; j < ufoMap->getSizeY() / 10; j++)
1517 {
1518 segments[ufoX + i][ufoY + j] = Node::UFOSEGMENT;
1519 }
1520 }
1521 }
1522
1523 if (_craft != 0 && (_save->getMissionType() != "STR_ALIEN_BASE_ASSAULT") && (_save->getMissionType() != "STR_MARS_THE_FINAL_ASSAULT"))
1524 {
1525 for (std::vector<MapDataSet*>::iterator i = _craft->getRules()->getBattlescapeTerrainData()->getMapDataSets()->begin(); i != _craft->getRules()->getBattlescapeTerrainData()->getMapDataSets()->end(); ++i)
1526 {
1527 (*i)->loadData();
1528 if (_game->getRuleset()->getMCDPatch((*i)->getName()))
1529 {
1530 _game->getRuleset()->getMCDPatch((*i)->getName())->modifyData(*i);
1531 }
1532 _save->getMapDataSets()->push_back(*i);
1533 }
1534 loadMAP(craftMap, _craftX * 10, _craftY * 10, _craft->getRules()->getBattlescapeTerrainData(), mapDataSetIDOffset + craftDataSetIDOffset, true, true);
1535 loadRMP(craftMap, _craftX * 10, _craftY * 10, Node::CRAFTSEGMENT);
1536 for (int i = 0; i < craftMap->getSizeX() / 10; ++i)
1537 {
1538 for (int j = 0; j < craftMap->getSizeY() / 10; j++)
1539 {
1540 segments[_craftX + i][_craftY + j] = Node::CRAFTSEGMENT;
1541 }
1542 }
1543 }
1544
1545 /* attach nodelinks to each other */
1546 for (std::vector<Node*>::iterator i = _save->getNodes()->begin(); i != _save->getNodes()->end(); ++i)
1547 {
1548 Node *node = (*i);
1549 int segmentX = node->getPosition().x / 10;
1550 int segmentY = node->getPosition().y / 10;
1551 int neighbourSegments[4];
1552 int neighbourDirections[4] = { -2, -3, -4, -5 };
1553 int neighbourDirectionsInverted[4] = { -4, -5, -2, -3 };
1554
1555 if (segmentX == (_mapsize_x / 10)-1)
1556 neighbourSegments[0] = -1;
1557 else
1558 neighbourSegments[0] = segments[segmentX+1][segmentY];
1559 if (segmentY == (_mapsize_y / 10)-1)
1560 neighbourSegments[1] = -1;
1561 else
1562 neighbourSegments[1] = segments[segmentX][segmentY+1];
1563 if (segmentX == 0)
1564 neighbourSegments[2] = -1;
1565 else
1566 neighbourSegments[2] = segments[segmentX-1][segmentY];
1567 if (segmentY == 0)
1568 neighbourSegments[3] = -1;
1569 else
1570 neighbourSegments[3] = segments[segmentX][segmentY-1];
1571
1572 for (std::vector<int>::iterator j = node->getNodeLinks()->begin(); j != node->getNodeLinks()->end(); ++j )
1573 {
1574 for (int n = 0; n < 4; n++)
1575 {
1576 if (*j == neighbourDirections[n])
1577 {
1578 for (std::vector<Node*>::iterator k = _save->getNodes()->begin(); k != _save->getNodes()->end(); ++k)
1579 {
1580 if ((*k)->getSegment() == neighbourSegments[n])
1581 {
1582 for (std::vector<int>::iterator l = (*k)->getNodeLinks()->begin(); l != (*k)->getNodeLinks()->end(); ++l )
1583 {
1584 if (*l == neighbourDirectionsInverted[n])
1585 {
1586 *l = node->getID();
1587 *j = (*k)->getID();
1588 }
1589 }
1590 }
1591 }
1592 }
1593 }
1594 }
1595 }
1596
1597 delete dummy;
1598 }
1599
1600
1601 /**
1602 * Loads an XCom format MAP file into the tiles of the battlegame.
1603 * @param mapblock Pointer to MapBlock.
1604 * @param xoff Mapblock offset in X direction.
1605 * @param yoff Mapblock offset in Y direction.
1606 * @param save Pointer to the current SavedBattleGame.
1607 * @param terrain Pointer to the Terrain rule.
1608 * @param discovered Whether or not this mapblock is discovered (eg. landingsite of the XCom plane).
1609 * @return int Height of the loaded mapblock (this is needed for spawpoint calculation...)
1610 * @sa http://www.ufopaedia.org/index.php?title=MAPS
1611 * @note Y-axis is in reverse order.
1612 */
loadMAP(MapBlock * mapblock,int xoff,int yoff,RuleTerrain * terrain,int mapDataSetOffset,bool discovered,bool craft)1613 int BattlescapeGenerator::loadMAP(MapBlock *mapblock, int xoff, int yoff, RuleTerrain *terrain, int mapDataSetOffset, bool discovered, bool craft)
1614 {
1615 int sizex, sizey, sizez;
1616 int x = xoff, y = yoff, z = 0;
1617 char size[3];
1618 unsigned char value[4];
1619 std::ostringstream filename;
1620 filename << "MAPS/" << mapblock->getName() << ".MAP";
1621 int terrainObjectID;
1622
1623 // Load file
1624 std::ifstream mapFile (CrossPlatform::getDataFile(filename.str()).c_str(), std::ios::in| std::ios::binary);
1625 if (!mapFile)
1626 {
1627 throw Exception(filename.str() + " not found");
1628 }
1629
1630 mapFile.read((char*)&size, sizeof(size));
1631 sizey = (int)size[0];
1632 sizex = (int)size[1];
1633 sizez = (int)size[2];
1634
1635 if (sizez > _save->getMapSizeZ())
1636 {
1637 throw Exception("Height of map too big for this mission");
1638 }
1639
1640 z += sizez - 1;
1641 mapblock->setSizeZ(sizez);
1642
1643 for (int i = _mapsize_z-1; i >0; i--)
1644 {
1645 // check if there is already a layer - if so, we have to move Z up
1646 MapData *floor = _save->getTile(Position(x, y, i))->getMapData(MapData::O_FLOOR);
1647 if (floor != 0)
1648 {
1649 z += i;
1650 if (craft)
1651 {
1652 _craftZ = i;
1653 }
1654 break;
1655 }
1656 }
1657
1658 if (z > (_save->getMapSizeZ()-1))
1659 {
1660 throw Exception("Something is wrong in your map definitions");
1661 }
1662
1663 while (mapFile.read((char*)&value, sizeof(value)))
1664 {
1665 for (int part = 0; part < 4; part++)
1666 {
1667 terrainObjectID = (int)((unsigned char)value[part]);
1668 if (terrainObjectID>0)
1669 {
1670 int mapDataSetID = mapDataSetOffset;
1671 int mapDataID = terrainObjectID;
1672 MapData *md = terrain->getMapData(&mapDataID, &mapDataSetID);
1673 _save->getTile(Position(x, y, z))->setMapData(md, mapDataID, mapDataSetID, part);
1674 }
1675 // if the part is empty and it's not a floor, remove it
1676 // it prevents growing grass in UFOs
1677 if (terrainObjectID == 0 && part > 0)
1678 {
1679 _save->getTile(Position(x, y, z))->setMapData(0, -1, -1, part);
1680 }
1681 }
1682 _save->getTile(Position(x, y, z))->setDiscovered(discovered, 2);
1683
1684 x++;
1685
1686 if (x == (sizex + xoff))
1687 {
1688 x = xoff;
1689 y++;
1690 }
1691 if (y == (sizey + yoff))
1692 {
1693 y = yoff;
1694 z--;
1695 }
1696 }
1697
1698 if (!mapFile.eof())
1699 {
1700 throw Exception("Invalid MAP file");
1701 }
1702
1703 mapFile.close();
1704
1705 return sizez;
1706 }
1707
1708 /**
1709 * Loads an XCom format RMP file into the spawnpoints of the battlegame.
1710 * @param mapblock Pointer to MapBlock.
1711 * @param xoff Mapblock offset in X direction.
1712 * @param yoff Mapblock offset in Y direction.
1713 * @param segment Mapblock segment.
1714 * @sa http://www.ufopaedia.org/index.php?title=ROUTES
1715 */
loadRMP(MapBlock * mapblock,int xoff,int yoff,int segment)1716 void BattlescapeGenerator::loadRMP(MapBlock *mapblock, int xoff, int yoff, int segment)
1717 {
1718 int id = 0;
1719 char value[24];
1720 std::ostringstream filename;
1721 filename << "ROUTES/" << mapblock->getName() << ".RMP";
1722
1723 // Load file
1724 std::ifstream mapFile (CrossPlatform::getDataFile(filename.str()).c_str(), std::ios::in| std::ios::binary);
1725 if (!mapFile)
1726 {
1727 throw Exception(filename.str() + " not found");
1728 }
1729
1730 size_t nodeOffset = _save->getNodes()->size();
1731
1732 while (mapFile.read((char*)&value, sizeof(value)))
1733 {
1734 if( (int)value[0] < mapblock->getSizeY() && (int)value[1] < mapblock->getSizeX() && (int)value[2] < _mapsize_z )
1735 {
1736 Node *node = new Node(nodeOffset + id, Position(xoff + (int)value[1], yoff + (int)value[0], mapblock->getSizeZ() - 1 - (int)value[2]), segment, (int)value[19], (int)value[20], (int)value[21], (int)value[22], (int)value[23]);
1737 for (int j=0;j<5;++j)
1738 {
1739 int connectID = (int)((signed char)value[4 + j*3]);
1740 if (connectID > -1)
1741 {
1742 connectID += nodeOffset;
1743 }
1744 node->getNodeLinks()->push_back(connectID);
1745 }
1746 _save->getNodes()->push_back(node);
1747 }
1748 id++;
1749 }
1750
1751 if (!mapFile.eof())
1752 {
1753 throw Exception("Invalid RMP file");
1754 }
1755
1756 mapFile.close();
1757 }
1758
1759 /**
1760 * Fill power sources with an elerium-115 object.
1761 */
fuelPowerSources()1762 void BattlescapeGenerator::fuelPowerSources()
1763 {
1764 for (int i = 0; i < _save->getMapSizeXYZ(); ++i)
1765 {
1766 if (_save->getTiles()[i]->getMapData(MapData::O_OBJECT)
1767 && _save->getTiles()[i]->getMapData(MapData::O_OBJECT)->getSpecialType() == UFO_POWER_SOURCE)
1768 {
1769 BattleItem *elerium = new BattleItem(_game->getRuleset()->getItem("STR_ELERIUM_115"), _save->getCurrentItemId());
1770 _save->getItems()->push_back(elerium);
1771 _save->getTiles()[i]->addItem(elerium, _game->getRuleset()->getInventory("STR_GROUND"));
1772 }
1773 }
1774 }
1775
1776
1777 /**
1778 * When a UFO crashes, there is a 75% chance for each powersource to explode.
1779 */
explodePowerSources()1780 void BattlescapeGenerator::explodePowerSources()
1781 {
1782 for (int i = 0; i < _save->getMapSizeXYZ(); ++i)
1783 {
1784 if (_save->getTiles()[i]->getMapData(MapData::O_OBJECT)
1785 && _save->getTiles()[i]->getMapData(MapData::O_OBJECT)->getSpecialType() == UFO_POWER_SOURCE && RNG::percent(75))
1786 {
1787 Position pos;
1788 pos.x = _save->getTiles()[i]->getPosition().x*16;
1789 pos.y = _save->getTiles()[i]->getPosition().y*16;
1790 pos.z = (_save->getTiles()[i]->getPosition().z*24) +12;
1791 _save->getTileEngine()->explode(pos, 180+RNG::generate(0,70), DT_HE, 10);
1792 }
1793 }
1794 Tile *t = _save->getTileEngine()->checkForTerrainExplosions();
1795 while (t)
1796 {
1797 Position p = Position(t->getPosition().x * 16, t->getPosition().y * 16, t->getPosition().z * 24);
1798 p += Position(8,8,0);
1799 _save->getTileEngine()->explode(p, t->getExplosive(), DT_HE, t->getExplosive() / 10);
1800 t = _save->getTileEngine()->checkForTerrainExplosions();
1801 }
1802 }
1803
1804 /**
1805 * Spawns 1-16 civilians on a terror mission.
1806 * @param max Maximum number of civilians to spawn.
1807 */
deployCivilians(int max)1808 void BattlescapeGenerator::deployCivilians(int max)
1809 {
1810 if (max)
1811 {
1812 // inevitably someone will point out that ufopaedia says 0-16 civilians.
1813 // to that person: i looked at the code and it says otherwise.
1814 // 0 civilians would only be a possibility if there were already 80 units,
1815 // or no spawn nodes for civilians.
1816 int number = RNG::generate(max/2, max);
1817
1818 if (number > 0)
1819 {
1820 for (int i = 0; i < number; ++i)
1821 {
1822 size_t pick = RNG::generate(0, _terrain->getCivilianTypes().size() -1);
1823 addCivilian(_game->getRuleset()->getUnit(_terrain->getCivilianTypes().at(pick)));
1824 }
1825 }
1826 }
1827 }
1828
1829 /**
1830 * Sets the alien base involved in the battle.
1831 * @param base Pointer to alien base.
1832 */
setAlienBase(AlienBase * base)1833 void BattlescapeGenerator::setAlienBase(AlienBase *base)
1834 {
1835 _alienBase = base;
1836 _alienBase->setInBattlescape(true);
1837 }
1838
1839 /**
1840 * Places a unit near a friendly unit.
1841 * @param unit Pointer to the unit in question.
1842 * @return If we successfully placed the unit.
1843 */
placeUnitNearFriend(BattleUnit * unit)1844 bool BattlescapeGenerator::placeUnitNearFriend(BattleUnit *unit)
1845 {
1846 Position entryPoint = Position(-1, -1, -1);
1847 int tries = 100;
1848 while (entryPoint == Position(-1, -1, -1) && tries)
1849 {
1850 BattleUnit* k = _save->getUnits()->at(RNG::generate(0, _save->getUnits()->size()-1));
1851 if (k->getFaction() == unit->getFaction() && k->getPosition() != Position(-1, -1, -1) && k->getArmor()->getSize() == 1)
1852 {
1853 entryPoint = k->getPosition();
1854 }
1855 --tries;
1856 }
1857 if (tries && _save->placeUnitNearPosition(unit, entryPoint))
1858 {
1859 return true;
1860 }
1861 return false;
1862 }
1863
1864
1865 /**
1866 * Gets battlescape terrain using globe texture and latitude.
1867 * @param tex Globe texture.
1868 * @param lat Latitude.
1869 * @return Pointer to ruleterrain.
1870 */
getTerrain(int tex,double lat)1871 RuleTerrain *BattlescapeGenerator::getTerrain(int tex, double lat)
1872 {
1873 RuleTerrain *t = 0;
1874 const std::vector<std::string> &terrains = _game->getRuleset()->getTerrainList();
1875 for (std::vector<std::string>::const_iterator i = terrains.begin(); i != terrains.end(); ++i)
1876 {
1877 t = _game->getRuleset()->getTerrain(*i);
1878 for (std::vector<int>::iterator j = t->getTextures()->begin(); j != t->getTextures()->end(); ++j )
1879 {
1880 if (*j == tex && (t->getHemisphere() == 0 || (t->getHemisphere() < 0 && lat < 0) || (t->getHemisphere() > 0 && lat >= 0)))
1881 {
1882 return t;
1883 }
1884 }
1885 }
1886
1887 assert(0 && "No matching terrain for globe texture");
1888 return t;
1889 }
1890
1891 /**
1892 * Creates a mini-battle-save for managing inventory from the Geoscape.
1893 * Kids, don't try this at home!
1894 * @param craft Pointer to craft to manage.
1895 */
runInventory(Craft * craft)1896 void BattlescapeGenerator::runInventory(Craft *craft)
1897 {
1898 // we need to fake a map for soldier placement
1899 _baseInventory = true;
1900 int soldiers = craft->getNumSoldiers();
1901 _mapsize_x = soldiers;
1902 _mapsize_y = 1;
1903 _mapsize_z = 1;
1904 _save->initMap(_mapsize_x, _mapsize_y, _mapsize_z);
1905 MapDataSet *set = new MapDataSet("dummy");
1906 MapData *data = new MapData(set);
1907 for (int i = 0; i < soldiers; ++i)
1908 {
1909 Tile *tile = _save->getTiles()[i];
1910 tile->setMapData(data, 0, 0, MapData::O_FLOOR);
1911 tile->getMapData(MapData::O_FLOOR)->setSpecialType(START_POINT, 0);
1912 tile->getMapData(MapData::O_FLOOR)->setTUWalk(0);
1913 tile->getMapData(MapData::O_FLOOR)->setFlags(false, false, false, 0, false, false, false, false, false);
1914 }
1915
1916 // ok now generate the battleitems for inventory
1917 setCraft(craft);
1918 deployXCOM();
1919 delete data;
1920 delete set;
1921 }
1922
1923 /**
1924 * Loads all XCom weaponry before anything else is distributed.
1925 */
loadWeapons()1926 void BattlescapeGenerator::loadWeapons()
1927 {
1928 // let's try to load this weapon, whether we equip it or not.
1929 for (std::vector<BattleItem*>::iterator i = _craftInventoryTile->getInventory()->begin(); i != _craftInventoryTile->getInventory()->end(); ++i)
1930 {
1931 if (!(*i)->getRules()->isFixed() &&
1932 !(*i)->getRules()->getCompatibleAmmo()->empty() &&
1933 (*i)->getAmmoItem() == 0 &&
1934 ((*i)->getRules()->getBattleType() == BT_FIREARM || (*i)->getRules()->getBattleType() == BT_MELEE))
1935 {
1936 bool loaded = false;
1937 for (std::vector<BattleItem*>::iterator j = _craftInventoryTile->getInventory()->begin(); j != _craftInventoryTile->getInventory()->end() && !loaded; ++j)
1938 {
1939 if ((*j)->getSlot() == _game->getRuleset()->getInventory("STR_GROUND") && (*i)->setAmmoItem((*j)) == 0)
1940 {
1941 _save->getItems()->push_back(*j);
1942 (*j)->setXCOMProperty(true);
1943 (*j)->setSlot(_game->getRuleset()->getInventory("STR_RIGHT_HAND"));
1944 loaded = true;
1945 }
1946 }
1947 }
1948 }
1949 for (std::vector<BattleItem*>::iterator i = _craftInventoryTile->getInventory()->begin(); i != _craftInventoryTile->getInventory()->end();)
1950 {
1951 if ((*i)->getSlot() != _game->getRuleset()->getInventory("STR_GROUND"))
1952 {
1953 i = _craftInventoryTile->getInventory()->erase(i);
1954 continue;
1955 }
1956 ++i;
1957 }
1958
1959 }
1960 }
1961