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 #define _USE_MATH_DEFINES
20 #include <assert.h>
21 #include <cmath>
22 #include <climits>
23 #include <set>
24 #include <functional>
25 #include "TileEngine.h"
26 #include <SDL.h>
27 #include "BattleAIState.h"
28 #include "AlienBAIState.h"
29 #include "UnitTurnBState.h"
30 #include "Map.h"
31 #include "Camera.h"
32 #include "../Savegame/SavedGame.h"
33 #include "../Savegame/SavedBattleGame.h"
34 #include "ExplosionBState.h"
35 #include "../Savegame/Tile.h"
36 #include "../Savegame/BattleItem.h"
37 #include "../Savegame/BattleUnit.h"
38 #include "../Savegame/Soldier.h"
39 #include "../Engine/RNG.h"
40 #include "BattlescapeState.h"
41 #include "../Ruleset/MapDataSet.h"
42 #include "../Ruleset/MapData.h"
43 #include "../Ruleset/Unit.h"
44 #include "../Ruleset/RuleSoldier.h"
45 #include "../Ruleset/Armor.h"
46 #include "../Ruleset/Ruleset.h"
47 #include "../Resource/ResourcePack.h"
48 #include "Pathfinding.h"
49 #include "../Engine/Options.h"
50 #include "ProjectileFlyBState.h"
51 #include "../Engine/Logger.h"
52 #include "../fmath.h"
53 
54 namespace OpenXcom
55 {
56 
57 const int TileEngine::heightFromCenter[11] = {0,-2,+2,-4,+4,-6,+6,-8,+8,-12,+12};
58 
59 /**
60  * Sets up a TileEngine.
61  * @param save Pointer to SavedBattleGame object.
62  * @param voxelData List of voxel data.
63  */
TileEngine(SavedBattleGame * save,std::vector<Uint16> * voxelData)64 TileEngine::TileEngine(SavedBattleGame *save, std::vector<Uint16> *voxelData) : _save(save), _voxelData(voxelData), _personalLighting(true)
65 {
66 }
67 
68 /**
69  * Deletes the TileEngine.
70  */
~TileEngine()71 TileEngine::~TileEngine()
72 {
73 
74 }
75 
76 /**
77   * Calculates sun shading for the whole terrain.
78   */
calculateSunShading()79 void TileEngine::calculateSunShading()
80 {
81 	const int layer = 0; // Ambient lighting layer.
82 
83 	for (int i = 0; i < _save->getMapSizeXYZ(); ++i)
84 	{
85 		_save->getTiles()[i]->resetLight(layer);
86 		calculateSunShading(_save->getTiles()[i]);
87 	}
88 }
89 
90 /**
91   * Calculates sun shading for 1 tile. Sun comes from above and is blocked by floors or objects.
92   * @param tile The tile to calculate sun shading for.
93   */
calculateSunShading(Tile * tile)94 void TileEngine::calculateSunShading(Tile *tile)
95 {
96 	const int layer = 0; // Ambient lighting layer.
97 
98 	int power = 15 - _save->getGlobalShade();
99 
100 	// At night/dusk sun isn't dropping shades blocked by roofs
101 	if (_save->getGlobalShade() <= 4)
102 	{
103 		if (verticalBlockage(_save->getTile(Position(tile->getPosition().x, tile->getPosition().y, _save->getMapSizeZ() - 1)), tile, DT_NONE))
104 		{
105 			power -= 2;
106 		}
107 	}
108 
109 	tile->addLight(power, layer);
110 }
111 
112 /**
113   * Recalculates lighting for the terrain: objects,items,fire.
114   */
calculateTerrainLighting()115 void TileEngine::calculateTerrainLighting()
116 {
117 	const int layer = 1; // Static lighting layer.
118 	const int fireLightPower = 15; // amount of light a fire generates
119 
120 	// reset all light to 0 first
121 	for (int i = 0; i < _save->getMapSizeXYZ(); ++i)
122 	{
123 		_save->getTiles()[i]->resetLight(layer);
124 	}
125 
126 	// add lighting of terrain
127 	for (int i = 0; i < _save->getMapSizeXYZ(); ++i)
128 	{
129 		// only floors and objects can light up
130 		if (_save->getTiles()[i]->getMapData(MapData::O_FLOOR)
131 			&& _save->getTiles()[i]->getMapData(MapData::O_FLOOR)->getLightSource())
132 		{
133 			addLight(_save->getTiles()[i]->getPosition(), _save->getTiles()[i]->getMapData(MapData::O_FLOOR)->getLightSource(), layer);
134 		}
135 		if (_save->getTiles()[i]->getMapData(MapData::O_OBJECT)
136 			&& _save->getTiles()[i]->getMapData(MapData::O_OBJECT)->getLightSource())
137 		{
138 			addLight(_save->getTiles()[i]->getPosition(), _save->getTiles()[i]->getMapData(MapData::O_OBJECT)->getLightSource(), layer);
139 		}
140 
141 		// fires
142 		if (_save->getTiles()[i]->getFire())
143 		{
144 			addLight(_save->getTiles()[i]->getPosition(), fireLightPower, layer);
145 		}
146 
147 		for (std::vector<BattleItem*>::iterator it = _save->getTiles()[i]->getInventory()->begin(); it != _save->getTiles()[i]->getInventory()->end(); ++it)
148 		{
149 			if ((*it)->getRules()->getBattleType() == BT_FLARE)
150 			{
151 				addLight(_save->getTiles()[i]->getPosition(), (*it)->getRules()->getPower(), layer);
152 			}
153 		}
154 
155 	}
156 
157 }
158 
159 /**
160   * Recalculates lighting for the units.
161   */
calculateUnitLighting()162 void TileEngine::calculateUnitLighting()
163 {
164 	const int layer = 2; // Dynamic lighting layer.
165 	const int personalLightPower = 15; // amount of light a unit generates
166 	const int fireLightPower = 15; // amount of light a fire generates
167 
168 	// reset all light to 0 first
169 	for (int i = 0; i < _save->getMapSizeXYZ(); ++i)
170 	{
171 		_save->getTiles()[i]->resetLight(layer);
172 	}
173 
174 	for (std::vector<BattleUnit*>::iterator i = _save->getUnits()->begin(); i != _save->getUnits()->end(); ++i)
175 	{
176 		// add lighting of soldiers
177 		if (_personalLighting && (*i)->getFaction() == FACTION_PLAYER && !(*i)->isOut())
178 		{
179 			addLight((*i)->getPosition(), personalLightPower, layer);
180 		}
181 		// add lighting of units on fire
182 		if ((*i)->getFire())
183 		{
184 			addLight((*i)->getPosition(), fireLightPower, layer);
185 		}
186 	}
187 }
188 
189 /**
190  * Adds circular light pattern starting from center and losing power with distance travelled.
191  * @param center Center.
192  * @param power Power.
193  * @param layer Light is separated in 3 layers: Ambient, Static and Dynamic.
194  */
addLight(const Position & center,int power,int layer)195 void TileEngine::addLight(const Position &center, int power, int layer)
196 {
197 	// only loop through the positive quadrant.
198 	for (int x = 0; x <= power; ++x)
199 	{
200 		for (int y = 0; y <= power; ++y)
201 		{
202 			for (int z = 0; z < _save->getMapSizeZ(); z++)
203 			{
204 				int distance = (int)Round(sqrt(float(x*x + y*y)));
205 
206 				if (_save->getTile(Position(center.x + x,center.y + y, z)))
207 					_save->getTile(Position(center.x + x,center.y + y, z))->addLight(power - distance, layer);
208 
209 				if (_save->getTile(Position(center.x - x,center.y - y, z)))
210 					_save->getTile(Position(center.x - x,center.y - y, z))->addLight(power - distance, layer);
211 
212 				if (_save->getTile(Position(center.x - x,center.y + y, z)))
213 					_save->getTile(Position(center.x - x,center.y + y, z))->addLight(power - distance, layer);
214 
215 				if (_save->getTile(Position(center.x + x,center.y - y, z)))
216 					_save->getTile(Position(center.x + x,center.y - y, z))->addLight(power - distance, layer);
217 			}
218 		}
219 	}
220 }
221 
222 /**
223  * Calculates line of sight of a soldier.
224  * @param unit Unit to check line of sight of.
225  * @return True when new aliens are spotted.
226  */
calculateFOV(BattleUnit * unit)227 bool TileEngine::calculateFOV(BattleUnit *unit)
228 {
229 	size_t oldNumVisibleUnits = unit->getUnitsSpottedThisTurn().size();
230 	Position center = unit->getPosition();
231 	Position test;
232 	int direction;
233 	bool swap;
234 	std::vector<Position> _trajectory;
235 	if (Options::strafe && (unit->getTurretType() > -1)) {
236 		direction = unit->getTurretDirection();
237 	}
238 	else
239 	{
240 		direction = unit->getDirection();
241 	}
242 	swap = (direction==0 || direction==4);
243 	int signX[8] = { +1, +1, +1, +1, -1, -1, -1, -1 };
244 	int signY[8] = { -1, -1, -1, +1, +1, +1, -1, -1 };
245 	int y1, y2;
246 
247 	unit->clearVisibleUnits();
248 	unit->clearVisibleTiles();
249 
250 	if (unit->isOut())
251 		return false;
252 	Position pos = unit->getPosition();
253 
254 	if ((unit->getHeight() + unit->getFloatHeight() + -_save->getTile(unit->getPosition())->getTerrainLevel()) >= 24 + 4)
255 	{
256 		++pos.z;
257 	}
258 	for (int x = 0; x <= MAX_VIEW_DISTANCE; ++x)
259 	{
260 		if (direction%2)
261 		{
262 			y1 = 0;
263 			y2 = MAX_VIEW_DISTANCE;
264 		}
265 		else
266 		{
267 			y1 = -x;
268 			y2 = x;
269 		}
270 		for (int y = y1; y <= y2; ++y)
271 		{
272 			for (int z = 0; z < _save->getMapSizeZ(); z++)
273 			{
274 				const int distanceSqr = x*x + y*y;
275 				test.z = z;
276 				if (distanceSqr <= MAX_VIEW_DISTANCE*MAX_VIEW_DISTANCE)
277 				{
278 					test.x = center.x + signX[direction]*(swap?y:x);
279 					test.y = center.y + signY[direction]*(swap?x:y);
280 					if (_save->getTile(test))
281 					{
282 						BattleUnit *visibleUnit = _save->getTile(test)->getUnit();
283 						if (visibleUnit && !visibleUnit->isOut() && visible(unit, _save->getTile(test)))
284 						{
285 							if (unit->getFaction() == FACTION_PLAYER)
286 							{
287 								visibleUnit->getTile()->setVisible(+1);
288 								visibleUnit->setVisible(true);
289 							}
290 							if ((visibleUnit->getFaction() == FACTION_HOSTILE && unit->getFaction() == FACTION_PLAYER)
291 								|| (visibleUnit->getFaction() != FACTION_HOSTILE && unit->getFaction() == FACTION_HOSTILE))
292 							{
293 								unit->addToVisibleUnits(visibleUnit);
294 								unit->addToVisibleTiles(visibleUnit->getTile());
295 
296 								if (unit->getFaction() == FACTION_HOSTILE && visibleUnit->getFaction() != FACTION_HOSTILE)
297 								{
298 									visibleUnit->setTurnsSinceSpotted(0);
299 								}
300 							}
301 						}
302 
303 						if (unit->getFaction() == FACTION_PLAYER)
304 						{
305 							// this sets tiles to discovered if they are in LOS - tile visibility is not calculated in voxelspace but in tilespace
306 							// large units have "4 pair of eyes"
307 							int size = unit->getArmor()->getSize();
308 							for (int xo = 0; xo < size; xo++)
309 							{
310 								for (int yo = 0; yo < size; yo++)
311 								{
312 									Position poso = pos + Position(xo,yo,0);
313 									_trajectory.clear();
314 									int tst = calculateLine(poso, test, true, &_trajectory, unit, false);
315 									size_t tsize = _trajectory.size();
316 									if (tst>127) --tsize; //last tile is blocked thus must be cropped
317 									for (size_t i = 0; i < tsize; i++)
318 									{
319 										Position posi = _trajectory.at(i);
320 										//mark every tile of line as visible (as in original)
321 										//this is needed because of bresenham narrow stroke.
322 										_save->getTile(posi)->setVisible(+1);
323 										_save->getTile(posi)->setDiscovered(true, 2);
324 										// walls to the east or south of a visible tile, we see that too
325 										Tile* t = _save->getTile(Position(posi.x + 1, posi.y, posi.z));
326 										if (t) t->setDiscovered(true, 0);
327 										t = _save->getTile(Position(posi.x, posi.y + 1, posi.z));
328 										if (t) t->setDiscovered(true, 1);
329 									}
330 
331 								}
332 							}
333 						}
334 					}
335 				}
336 			}
337 		}
338 	}
339 
340 	// we only react when there are at least the same amount of visible units as before AND the checksum is different
341 	// this way we stop if there are the same amount of visible units, but a different unit is seen
342 	// or we stop if there are more visible units seen
343 	if (unit->getUnitsSpottedThisTurn().size() > oldNumVisibleUnits && !unit->getVisibleUnits()->empty())
344 	{
345 		return true;
346 	}
347 
348 	return false;
349 
350 }
351 
352 /**
353  * Gets the origin voxel of a unit's eyesight (from just one eye or something? Why is it x+7??
354  * @param currentUnit The watcher.
355  * @return Approximately an eyeball voxel.
356  */
getSightOriginVoxel(BattleUnit * currentUnit)357 Position TileEngine::getSightOriginVoxel(BattleUnit *currentUnit)
358 {
359 	// determine the origin and target voxels for the raytrace
360 	Position originVoxel;
361 	originVoxel = Position((currentUnit->getPosition().x * 16) + 7, (currentUnit->getPosition().y * 16) + 8, currentUnit->getPosition().z*24);
362 	originVoxel.z += -_save->getTile(currentUnit->getPosition())->getTerrainLevel();
363 	originVoxel.z += currentUnit->getHeight() + currentUnit->getFloatHeight() - 1; //one voxel lower (eye level)
364 	Tile *tileAbove = _save->getTile(currentUnit->getPosition() + Position(0,0,1));
365 	if (currentUnit->getArmor()->getSize() > 1)
366 	{
367 		originVoxel.x += 8;
368 		originVoxel.y += 8;
369 		originVoxel.z += 1; //topmost voxel
370 	}
371 	if (originVoxel.z >= (currentUnit->getPosition().z + 1)*24 && (!tileAbove || !tileAbove->hasNoFloor(0)))
372 	{
373 		while (originVoxel.z >= (currentUnit->getPosition().z + 1)*24)
374 		{
375 			originVoxel.z--;
376 		}
377 	}
378 
379 	return originVoxel;
380 }
381 
382 /**
383  * Checks for an opposing unit on this tile.
384  * @param currentUnit The watcher.
385  * @param tile The tile to check for
386  * @return True if visible.
387  */
visible(BattleUnit * currentUnit,Tile * tile)388 bool TileEngine::visible(BattleUnit *currentUnit, Tile *tile)
389 {
390 	// if there is no tile or no unit, we can't see it
391 	if (!tile || !tile->getUnit())
392 	{
393 		return false;
394 	}
395 
396 	// aliens can see in the dark, xcom can see at a distance of 9 or less, further if there's enough light.
397 	if (currentUnit->getFaction() == FACTION_PLAYER &&
398 		distance(currentUnit->getPosition(), tile->getPosition()) > 9 &&
399 		tile->getShade() > MAX_DARKNESS_TO_SEE_UNITS)
400 	{
401 		return false;
402 	}
403 
404 	if (currentUnit->getFaction() == tile->getUnit()->getFaction()) return true; // friendlies are always seen
405 
406 	Position originVoxel = getSightOriginVoxel(currentUnit);
407 
408 	bool unitSeen = false;
409 	// for large units origin voxel is in the middle
410 
411 	Position scanVoxel;
412 	std::vector<Position> _trajectory;
413 	unitSeen = canTargetUnit(&originVoxel, tile, &scanVoxel, currentUnit);
414 
415 	if (unitSeen)
416 	{
417 		// now check if we really see it taking into account smoke tiles
418 		// initial smoke "density" of a smoke grenade is around 15 per tile
419 		// we do density/3 to get the decay of visibility
420 		// so in fresh smoke we should only have 4 tiles of visibility
421 		// this is traced in voxel space, with smoke affecting visibility every step of the way
422 		_trajectory.clear();
423 		calculateLine(originVoxel, scanVoxel, true, &_trajectory, currentUnit);
424 		Tile *t = _save->getTile(currentUnit->getPosition());
425 		size_t visibleDistance = _trajectory.size();
426 		for (size_t i = 0; i < _trajectory.size(); i++)
427 		{
428 			if (t != _save->getTile(Position(_trajectory.at(i).x/16,_trajectory.at(i).y/16, _trajectory.at(i).z/24)))
429 			{
430 				t = _save->getTile(Position(_trajectory.at(i).x/16,_trajectory.at(i).y/16, _trajectory.at(i).z/24));
431 			}
432 			if (t->getFire() == 0)
433 			{
434 				visibleDistance += t->getSmoke() / 3;
435 			}
436 			if (visibleDistance > MAX_VOXEL_VIEW_DISTANCE)
437 			{
438 				unitSeen = false;
439 				break;
440 			}
441 		}
442 	}
443 	return unitSeen;
444 }
445 
446 /**
447  * Checks for how exposed unit is for another unit.
448  * @param originVoxel Voxel of trace origin (eye or gun's barrel).
449  * @param tile The tile to check for.
450  * @param excludeUnit Is self (not to hit self).
451  * @param excludeAllBut [Optional] is unit which is the only one to be considered for ray hits.
452  * @return Degree of exposure (as percent).
453  */
checkVoxelExposure(Position * originVoxel,Tile * tile,BattleUnit * excludeUnit,BattleUnit * excludeAllBut)454 int TileEngine::checkVoxelExposure(Position *originVoxel, Tile *tile, BattleUnit *excludeUnit, BattleUnit *excludeAllBut)
455 {
456 	Position targetVoxel = Position((tile->getPosition().x * 16) + 7, (tile->getPosition().y * 16) + 8, tile->getPosition().z * 24);
457 	Position scanVoxel;
458 	std::vector<Position> _trajectory;
459 	BattleUnit *otherUnit = tile->getUnit();
460 	if (otherUnit == 0) return 0; //no unit in this tile, even if it elevated and appearing in it.
461 	if (otherUnit == excludeUnit) return 0; //skip self
462 
463 	int targetMinHeight = targetVoxel.z - tile->getTerrainLevel();
464 	if (otherUnit)
465 		 targetMinHeight += otherUnit->getFloatHeight();
466 
467 	// if there is an other unit on target tile, we assume we want to check against this unit's height
468 	int heightRange;
469 
470 	int unitRadius = otherUnit->getLoftemps(); //width == loft in default loftemps set
471 	if (otherUnit->getArmor()->getSize() > 1)
472 	{
473 		unitRadius = 3;
474 	}
475 
476 	// vector manipulation to make scan work in view-space
477 	Position relPos = targetVoxel - *originVoxel;
478 	float normal = unitRadius/sqrt((float)(relPos.x*relPos.x + relPos.y*relPos.y));
479 	int relX = floor(((float)relPos.y)*normal+0.5);
480 	int relY = floor(((float)-relPos.x)*normal+0.5);
481 
482 	int sliceTargets[10]={0,0, relX,relY, -relX,-relY};
483 
484 	if (!otherUnit->isOut())
485 	{
486 		heightRange = otherUnit->getHeight();
487 	}
488 	else
489 	{
490 		heightRange = 12;
491 	}
492 
493 	int targetMaxHeight=targetMinHeight+heightRange;
494 	// scan ray from top to bottom  plus different parts of target cylinder
495 	int total=0;
496 	int visible=0;
497 	for (int i = heightRange; i >=0; i-=2)
498 	{
499 		++total;
500 		scanVoxel.z=targetMinHeight+i;
501 		for (int j = 0; j < 2; ++j)
502 		{
503 			scanVoxel.x=targetVoxel.x + sliceTargets[j*2];
504 			scanVoxel.y=targetVoxel.y + sliceTargets[j*2+1];
505 			_trajectory.clear();
506 			int test = calculateLine(*originVoxel, scanVoxel, false, &_trajectory, excludeUnit, true, false, excludeAllBut);
507 			if (test == V_UNIT)
508 			{
509 				//voxel of hit must be inside of scanned box
510 				if (_trajectory.at(0).x/16 == scanVoxel.x/16 &&
511 					_trajectory.at(0).y/16 == scanVoxel.y/16 &&
512 					_trajectory.at(0).z >= targetMinHeight &&
513 					_trajectory.at(0).z <= targetMaxHeight)
514 				{
515 					++visible;
516 				}
517 			}
518 		}
519 	}
520 	return (visible*100)/total;
521 }
522 
523 /**
524  * Checks for another unit available for targeting and what particular voxel.
525  * @param originVoxel Voxel of trace origin (eye or gun's barrel).
526  * @param tile The tile to check for.
527  * @param scanVoxel is returned coordinate of hit.
528  * @param excludeUnit is self (not to hit self).
529  * @param potentialUnit is a hypothetical unit to draw a virtual line of fire for AI. if left blank, this function behaves normally.
530  * @return True if the unit can be targetted.
531  */
canTargetUnit(Position * originVoxel,Tile * tile,Position * scanVoxel,BattleUnit * excludeUnit,BattleUnit * potentialUnit)532 bool TileEngine::canTargetUnit(Position *originVoxel, Tile *tile, Position *scanVoxel, BattleUnit *excludeUnit, BattleUnit *potentialUnit)
533 {
534 	Position targetVoxel = Position((tile->getPosition().x * 16) + 7, (tile->getPosition().y * 16) + 8, tile->getPosition().z * 24);
535 	std::vector<Position> _trajectory;
536 	bool hypothetical = potentialUnit != 0;
537 	if (potentialUnit == 0)
538 	{
539 		potentialUnit = tile->getUnit();
540 		if (potentialUnit == 0) return false; //no unit in this tile, even if it elevated and appearing in it.
541 	}
542 
543 	if (potentialUnit == excludeUnit) return false; //skip self
544 
545 	int targetMinHeight = targetVoxel.z - tile->getTerrainLevel();
546 	targetMinHeight += potentialUnit->getFloatHeight();
547 
548 	int targetMaxHeight = targetMinHeight;
549 	int targetCenterHeight;
550 	// if there is an other unit on target tile, we assume we want to check against this unit's height
551 	int heightRange;
552 
553 	int unitRadius = potentialUnit->getLoftemps(); //width == loft in default loftemps set
554 	int targetSize = potentialUnit->getArmor()->getSize() - 1;
555 	int xOffset = potentialUnit->getPosition().x - tile->getPosition().x;
556 	int yOffset = potentialUnit->getPosition().y - tile->getPosition().y;
557 	if (targetSize > 0)
558 	{
559 		unitRadius = 3;
560 	}
561 	// vector manipulation to make scan work in view-space
562 	Position relPos = targetVoxel - *originVoxel;
563 	float normal = unitRadius/sqrt((float)(relPos.x*relPos.x + relPos.y*relPos.y));
564 	int relX = floor(((float)relPos.y)*normal+0.5);
565 	int relY = floor(((float)-relPos.x)*normal+0.5);
566 
567 	int sliceTargets[10]={0,0, relX,relY, -relX,-relY, relY,-relX, -relY,relX};
568 
569 	if (!potentialUnit->isOut())
570 	{
571 		heightRange = potentialUnit->getHeight();
572 	}
573 	else
574 	{
575 		heightRange = 12;
576 	}
577 
578 	targetMaxHeight += heightRange;
579 	targetCenterHeight=(targetMaxHeight+targetMinHeight)/2;
580 	heightRange/=2;
581 	if (heightRange>10) heightRange=10;
582 	if (heightRange<=0) heightRange=0;
583 
584 	// scan ray from top to bottom  plus different parts of target cylinder
585 	for (int i = 0; i <= heightRange; ++i)
586 	{
587 		scanVoxel->z=targetCenterHeight+heightFromCenter[i];
588 		for (int j = 0; j < 5; ++j)
589 		{
590 			if (i < (heightRange-1) && j>2) break; //skip unnecessary checks
591 			scanVoxel->x=targetVoxel.x + sliceTargets[j*2];
592 			scanVoxel->y=targetVoxel.y + sliceTargets[j*2+1];
593 			_trajectory.clear();
594 			int test = calculateLine(*originVoxel, *scanVoxel, false, &_trajectory, excludeUnit, true, false);
595 			if (test == V_UNIT)
596 			{
597 				for (int x = 0; x <= targetSize; ++x)
598 				{
599 					for (int y = 0; y <= targetSize; ++y)
600 					{
601 						//voxel of hit must be inside of scanned box
602 						if (_trajectory.at(0).x/16 == (scanVoxel->x/16) + x + xOffset &&
603 							_trajectory.at(0).y/16 == (scanVoxel->y/16) + y + yOffset &&
604 							_trajectory.at(0).z >= targetMinHeight &&
605 							_trajectory.at(0).z <= targetMaxHeight)
606 						{
607 							return true;
608 						}
609 					}
610 				}
611 			}
612 			else if (test == V_EMPTY && hypothetical && !_trajectory.empty())
613 			{
614 				return true;
615 			}
616 		}
617 	}
618 	return false;
619 }
620 
621 /**
622  * Checks for a tile part available for targeting and what particular voxel.
623  * @param originVoxel Voxel of trace origin (gun's barrel).
624  * @param tile The tile to check for.
625  * @param part Tile part to check for.
626  * @param scanVoxel Is returned coordinate of hit.
627  * @param excludeUnit Is self (not to hit self).
628  * @return True if the tile can be targetted.
629  */
canTargetTile(Position * originVoxel,Tile * tile,int part,Position * scanVoxel,BattleUnit * excludeUnit)630 bool TileEngine::canTargetTile(Position *originVoxel, Tile *tile, int part, Position *scanVoxel, BattleUnit *excludeUnit)
631 {
632 	static int sliceObjectSpiral[82] = {8,8, 8,6, 10,6, 10,8, 10,10, 8,10, 6,10, 6,8, 6,6, //first circle
633 		8,4, 10,4, 12,4, 12,6, 12,8, 12,10, 12,12, 10,12, 8,12, 6,12, 4,12, 4,10, 4,8, 4,6, 4,4, 6,4, //second circle
634 		8,1, 12,1, 15,1, 15,4, 15,8, 15,12, 15,15, 12,15, 8,15, 4,15, 1,15, 1,12, 1,8, 1,4, 1,1, 4,1}; //third circle
635 	static int westWallSpiral[14] = {0,7, 0,9, 0,6, 0,11, 0,4, 0,13, 0,2};
636 	static int northWallSpiral[14] = {7,0, 9,0, 6,0, 11,0, 4,0, 13,0, 2,0};
637 
638 	Position targetVoxel = Position((tile->getPosition().x * 16), (tile->getPosition().y * 16), tile->getPosition().z * 24);
639 	std::vector<Position> _trajectory;
640 
641 	int *spiralArray;
642 	int spiralCount;
643 
644 	int minZ, maxZ;
645 	bool minZfound = false, maxZfound = false;
646 
647 	if (part == MapData::O_OBJECT)
648 	{
649 		spiralArray = sliceObjectSpiral;
650 		spiralCount = 41;
651 	}
652 	else
653 	if (part == MapData::O_NORTHWALL)
654 	{
655 		spiralArray = northWallSpiral;
656 		spiralCount = 7;
657 	}
658 	else
659 	if (part == MapData::O_WESTWALL)
660 	{
661 		spiralArray = westWallSpiral;
662 		spiralCount = 7;
663 	}
664 	else if (part == MapData::O_FLOOR)
665 	{
666 		spiralArray = sliceObjectSpiral;
667 		spiralCount = 41;
668 		minZfound = true; minZ=0;
669 		maxZfound = true; maxZ=0;
670 	}
671 	else
672 	{
673 		return false;
674 	}
675 
676 // find out height range
677 
678 	if (!minZfound)
679 	{
680 		for (int j = 1; j < 12; ++j)
681 		{
682 			if (minZfound) break;
683 			for (int i = 0; i < spiralCount; ++i)
684 			{
685 				int tX = spiralArray[i*2];
686 				int tY = spiralArray[i*2+1];
687 				if (voxelCheck(Position(targetVoxel.x + tX, targetVoxel.y + tY, targetVoxel.z + j*2),0,true) == part) //bingo
688 				{
689 					if (!minZfound)
690 					{
691 						minZ = j*2;
692 						minZfound = true;
693 						break;
694 					}
695 				}
696 			}
697 		}
698 	}
699 
700 	if (!minZfound) return false;//empty object!!!
701 
702 	if (!maxZfound)
703 	{
704 		for (int j = 10; j >= 0; --j)
705 		{
706 			if (maxZfound) break;
707 			for (int i = 0; i < spiralCount; ++i)
708 			{
709 				int tX = spiralArray[i*2];
710 				int tY = spiralArray[i*2+1];
711 				if (voxelCheck(Position(targetVoxel.x + tX, targetVoxel.y + tY, targetVoxel.z + j*2),0,true) == part) //bingo
712 				{
713 					if (!maxZfound)
714 					{
715 						maxZ = j*2;
716 						maxZfound = true;
717 						break;
718 					}
719 				}
720 			}
721 		}
722 	}
723 
724 	if (!maxZfound) return false;//it's impossible to get there
725 
726 	if (minZ > maxZ) minZ = maxZ;
727 	int rangeZ = maxZ - minZ;
728 	int centerZ = (maxZ + minZ)/2;
729 
730 	for (int j = 0; j <= rangeZ; ++j)
731 	{
732 		scanVoxel->z = targetVoxel.z + centerZ + heightFromCenter[j];
733 		for (int i = 0; i < spiralCount; ++i)
734 		{
735 			scanVoxel->x = targetVoxel.x + spiralArray[i*2];
736 			scanVoxel->y = targetVoxel.y + spiralArray[i*2+1];
737 			_trajectory.clear();
738 			int test = calculateLine(*originVoxel, *scanVoxel, false, &_trajectory, excludeUnit, true);
739 			if (test == part) //bingo
740 			{
741 				if (_trajectory.at(0).x/16 == scanVoxel->x/16 &&
742 					_trajectory.at(0).y/16 == scanVoxel->y/16 &&
743 					_trajectory.at(0).z/24 == scanVoxel->z/24)
744 				{
745 					return true;
746 				}
747 			}
748 		}
749 	}
750 	return false;
751 }
752 
753 /**
754  * Calculates line of sight of a soldiers within range of the Position
755  * (used when terrain has changed, which can reveal new parts of terrain or units).
756  * @param position Position of the changed terrain.
757  */
calculateFOV(const Position & position)758 void TileEngine::calculateFOV(const Position &position)
759 {
760 	for (std::vector<BattleUnit*>::iterator i = _save->getUnits()->begin(); i != _save->getUnits()->end(); ++i)
761 	{
762 		if (distance(position, (*i)->getPosition()) < MAX_VIEW_DISTANCE)
763 		{
764 			calculateFOV(*i);
765 		}
766 	}
767 }
768 
769 /**
770  * Checks if a sniper from the opposing faction sees this unit. The unit with the highest reaction score will be compared with the current unit's reaction score.
771  * If it's higher, a shot is fired when enough time units, a weapon and ammo are available.
772  * @param unit The unit to check reaction fire upon.
773  * @return True if reaction fire took place.
774  */
checkReactionFire(BattleUnit * unit)775 bool TileEngine::checkReactionFire(BattleUnit *unit)
776 {
777 	// reaction fire only triggered when the actioning unit is of the currently playing side, and is still on the map (alive)
778 	if (unit->getFaction() != _save->getSide() || unit->getTile() == 0)
779 	{
780 		return false;
781 	}
782 
783 	std::vector<BattleUnit *> spotters = getSpottingUnits(unit);
784 	bool result = false;
785 
786 	// not mind controlled, or controlled by the player
787 	if (unit->getFaction() == unit->getOriginalFaction()
788 		|| unit->getFaction() != FACTION_HOSTILE)
789 	{
790 		// get the first man up to bat.
791 		BattleUnit *reactor = getReactor(spotters, unit);
792 		// start iterating through the possible reactors until the current unit is the one with the highest score.
793 		while (reactor != unit)
794 		{
795 			if (!tryReactionSnap(reactor, unit))
796 			{
797 				// can't make a reaction snapshot for whatever reason, boot this guy from the vector.
798 				for (std::vector<BattleUnit *>::iterator i = spotters.begin(); i != spotters.end(); ++i)
799 				{
800 					if (*i == reactor)
801 					{
802 						spotters.erase(i);
803 						break;
804 					}
805 				}
806 				// avoid setting result to true, but carry on, just cause one unit can't react doesn't mean the rest of the units in the vector (if any) can't
807 				reactor = getReactor(spotters, unit);
808 				continue;
809 			}
810 			// nice shot, kid. don't get cocky.
811 			reactor = getReactor(spotters, unit);
812 			result = true;
813 		}
814 	}
815 	return result;
816 }
817 
818 /**
819  * Creates a vector of units that can spot this unit.
820  * @param unit The unit to check for spotters of.
821  * @return A vector of units that can see this unit.
822  */
getSpottingUnits(BattleUnit * unit)823 std::vector<BattleUnit *> TileEngine::getSpottingUnits(BattleUnit* unit)
824 {
825 	std::vector<BattleUnit*> spotters;
826 	Tile *tile = unit->getTile();
827 	for (std::vector<BattleUnit*>::const_iterator i = _save->getUnits()->begin(); i != _save->getUnits()->end(); ++i)
828 	{
829 			// not dead/unconscious
830 		if (!(*i)->isOut() &&
831 			// not dying
832 			(*i)->getHealth() != 0 &&
833 			// not about to pass out
834 			(*i)->getStunlevel() < (*i)->getHealth() &&
835 			// not a friend
836 			(*i)->getFaction() != _save->getSide() &&
837 			// closer than 20 tiles
838 			distance(unit->getPosition(), (*i)->getPosition()) <= MAX_VIEW_DISTANCE)
839 		{
840 			Position originVoxel = _save->getTileEngine()->getSightOriginVoxel(*i);
841 			originVoxel.z -= 2;
842 			Position targetVoxel;
843 			AlienBAIState *aggro = dynamic_cast<AlienBAIState*>((*i)->getCurrentAIState());
844 			bool gotHit = (aggro != 0 && aggro->getWasHit());
845 				// can actually see the target Tile, or we got hit
846 			if (((*i)->checkViewSector(unit->getPosition()) || gotHit) &&
847 				// can actually target the unit
848 				canTargetUnit(&originVoxel, tile, &targetVoxel, *i) &&
849 				// can actually see the unit
850 				visible(*i, tile))
851 			{
852 				if ((*i)->getFaction() == FACTION_PLAYER)
853 				{
854 					unit->setVisible(true);
855 				}
856 				(*i)->addToVisibleUnits(unit);
857 				// no reaction on civilian turn.
858 				if (_save->getSide() != FACTION_NEUTRAL &&
859 					canMakeSnap(*i, unit))
860 				{
861 					spotters.push_back(*i);
862 				}
863 			}
864 		}
865 	}
866 	return spotters;
867 }
868 
869 /**
870  * Gets the unit with the highest reaction score from the spotter vector.
871  * @param spotters The vector of spotting units.
872  * @param unit The unit to check scores against.
873  * @return The unit with the highest reactions.
874  */
getReactor(std::vector<BattleUnit * > spotters,BattleUnit * unit)875 BattleUnit* TileEngine::getReactor(std::vector<BattleUnit *> spotters, BattleUnit *unit)
876 {
877 	int bestScore = -1;
878 	BattleUnit *bu = 0;
879 	for (std::vector<BattleUnit *>::iterator i = spotters.begin(); i != spotters.end(); ++i)
880 	{
881 		if (!(*i)->isOut() &&
882 		canMakeSnap(*i, unit) &&
883 		(*i)->getReactionScore() > bestScore)
884 		{
885 			bestScore = (*i)->getReactionScore();
886 			bu = *i;
887 		}
888 	}
889 	if (unit->getReactionScore() <= bestScore)
890 	{
891 		if (bu->getOriginalFaction() == FACTION_PLAYER)
892 		{
893 			bu->addReactionExp();
894 		}
895 	}
896 	else
897 	{
898 		bu = unit;
899 	}
900 	return bu;
901 }
902 
903 /**
904  * Checks the validity of a snap shot performed here.
905  * @param unit The unit to check sight from.
906  * @param target The unit to check sight TO.
907  * @return True if the target is valid.
908  */
canMakeSnap(BattleUnit * unit,BattleUnit * target)909 bool TileEngine::canMakeSnap(BattleUnit *unit, BattleUnit *target)
910 {
911 	BattleItem *weapon = unit->getMainHandWeapon();
912 	// has a weapon
913 	if (weapon &&
914 		// has a melee weapon and is in melee range
915 		((weapon->getRules()->getBattleType() == BT_MELEE &&
916 		validMeleeRange(unit, target, unit->getDirection()) &&
917 		unit->getTimeUnits() > unit->getActionTUs(BA_HIT, weapon)) ||
918 		// has a gun capable of snap shot with ammo
919 		(weapon->getRules()->getBattleType() != BT_MELEE &&
920 		weapon->getRules()->getTUSnap() &&
921 		weapon->getAmmoItem() &&
922 		unit->getTimeUnits() > unit->getActionTUs(BA_SNAPSHOT, weapon))) &&
923 		(unit->getOriginalFaction() != FACTION_PLAYER ||
924 		_save->getGeoscapeSave()->isResearched(weapon->getRules()->getRequirements())))
925 	{
926 		return true;
927 	}
928 	return false;
929 }
930 
931 /**
932  * Attempts to perform a reaction snap shot.
933  * @param unit The unit to check sight from.
934  * @param target The unit to check sight TO.
935  * @return True if the action should (theoretically) succeed.
936  */
tryReactionSnap(BattleUnit * unit,BattleUnit * target)937 bool TileEngine::tryReactionSnap(BattleUnit *unit, BattleUnit *target)
938 {
939 	BattleAction action;
940 	action.cameraPosition = _save->getBattleState()->getMap()->getCamera()->getMapOffset();
941 	action.actor = unit;
942 	action.weapon = unit->getMainHandWeapon();
943 	if (!action.weapon)
944 	{
945 		return false;
946 	}
947 	// reaction fire is ALWAYS snap shot.
948 	action.type = BA_SNAPSHOT;
949 	// unless we're a melee unit.
950 	if (action.weapon->getRules()->getBattleType() == BT_MELEE)
951 	{
952 		action.type = BA_HIT;
953 	}
954 	action.target = target->getPosition();
955 	action.TU = unit->getActionTUs(action.type, action.weapon);
956 
957 	if (action.weapon->getAmmoItem() && action.weapon->getAmmoItem()->getAmmoQuantity() && unit->getTimeUnits() >= action.TU)
958 	{
959 		action.targeting = true;
960 
961 		// hostile units will go into an "aggro" state when they react.
962 		if (unit->getFaction() == FACTION_HOSTILE)
963 		{
964 			AlienBAIState *aggro = dynamic_cast<AlienBAIState*>(unit->getCurrentAIState());
965 			if (aggro == 0)
966 			{
967 				// should not happen, but just in case...
968 				aggro = new AlienBAIState(_save, unit, 0);
969 				unit->setAIState(aggro);
970 			}
971 
972 			if (action.weapon->getAmmoItem()->getRules()->getExplosionRadius() &&
973 				aggro->explosiveEfficacy(action.target, unit, action.weapon->getAmmoItem()->getRules()->getExplosionRadius(), -1) == false)
974 			{
975 				action.targeting = false;
976 			}
977 		}
978 
979 		if (action.targeting && unit->spendTimeUnits(action.TU))
980 		{
981 			action.TU = 0;
982 			_save->getBattleGame()->statePushBack(new UnitTurnBState(_save->getBattleGame(), action));
983 			_save->getBattleGame()->statePushBack(new ProjectileFlyBState(_save->getBattleGame(), action));
984 			return true;
985 		}
986 	}
987 	return false;
988 }
989 
990 /**
991  * Handles bullet/weapon hits.
992  *
993  * A bullet/weapon hits a voxel.
994  * @param center Center of the explosion in voxelspace.
995  * @param power Power of the explosion.
996  * @param type The damage type of the explosion.
997  * @param unit The unit that caused the explosion.
998  * @return The Unit that got hit.
999  */
hit(const Position & center,int power,ItemDamageType type,BattleUnit * unit)1000 BattleUnit *TileEngine::hit(const Position &center, int power, ItemDamageType type, BattleUnit *unit)
1001 {
1002 	Tile *tile = _save->getTile(Position(center.x/16, center.y/16, center.z/24));
1003 	if(!tile)
1004 	{
1005 		return 0;
1006 	}
1007 
1008 	BattleUnit *bu = tile->getUnit();
1009 	int adjustedDamage = 0;
1010 	const int part = voxelCheck(center, unit);
1011 	if (part >= V_FLOOR && part <= V_OBJECT)
1012 	{
1013 		// power 25% to 75%
1014 		const int rndPower = RNG::generate(power/4, (power*3)/4); //RNG::boxMuller(power, power/6)
1015 		if (part == V_OBJECT && _save->getMissionType() == "STR_BASE_DEFENSE")
1016 		{
1017 			if (rndPower >= tile->getMapData(MapData::O_OBJECT)->getArmor() && tile->getMapData(V_OBJECT)->isBaseModule())
1018 			{
1019 				_save->getModuleMap()[(center.x/16)/10][(center.y/16)/10].second--;
1020 			}
1021 		}
1022 		if (tile->damage(part, rndPower))
1023 			_save->setObjectiveDestroyed(true);
1024 	}
1025 	else if (part == V_UNIT)
1026 	{
1027 		int dmgRng = (type == DT_HE || Options::TFTDDamage) ? 50 : 100;
1028 		int min = power * (100 - dmgRng) / 100;
1029 		int max = power * (100 + dmgRng) / 100;
1030 		const int rndPower = RNG::generate(min, max);
1031 		int verticaloffset = 0;
1032 		if (!bu)
1033 		{
1034 			// it's possible we have a unit below the actual tile, when he stands on a stairs and sticks his head out to the next tile
1035 			Tile *below = _save->getTile(Position(center.x/16, center.y/16, (center.z/24)-1));
1036 			if (below)
1037 			{
1038 				BattleUnit *buBelow = below->getUnit();
1039 				if (buBelow)
1040 				{
1041 					bu = buBelow;
1042 					verticaloffset = 24;
1043 				}
1044 			}
1045 		}
1046 		if (bu)
1047 		{
1048 			const int sz = bu->getArmor()->getSize() * 8;
1049 			const Position target = bu->getPosition() * Position(16,16,24) + Position(sz,sz, bu->getFloatHeight() - tile->getTerrainLevel());
1050 			const Position relative = (center - target) - Position(0,0,verticaloffset);
1051 			const int wounds = bu->getFatalWounds();
1052 
1053 			adjustedDamage = bu->damage(relative, rndPower, type);
1054 
1055 			// if it's going to bleed to death and it's not a player, give credit for the kill.
1056 			if (unit && bu->getFaction() != FACTION_PLAYER && wounds < bu->getFatalWounds())
1057 			{
1058 				bu->killedBy(unit->getFaction());
1059 			}
1060 			const int bravery = (110 - bu->getStats()->bravery) / 10;
1061 			const int modifier = bu->getFaction() == FACTION_PLAYER ? _save->getMoraleModifier() : 100;
1062 			const int morale_loss = 100 * (adjustedDamage * bravery / 10) / modifier;
1063 
1064 			bu->moraleChange(-morale_loss);
1065 
1066 			if (bu->getSpecialAbility() == SPECAB_EXPLODEONDEATH && !bu->isOut() && (bu->getHealth() == 0 || bu->getStunlevel() >= bu->getHealth()))
1067 			{
1068 				if (type != DT_STUN && type != DT_HE)
1069 				{
1070 					Position p = Position(bu->getPosition().x * 16, bu->getPosition().y * 16, bu->getPosition().z * 24);
1071 					_save->getBattleGame()->statePushNext(new ExplosionBState(_save->getBattleGame(), p, 0, bu, 0));
1072 				}
1073 			}
1074 
1075 			if (bu->getOriginalFaction() == FACTION_HOSTILE &&
1076 				unit &&
1077 				unit->getOriginalFaction() == FACTION_PLAYER &&
1078 				type != DT_NONE &&
1079 				_save->getBattleGame()->getCurrentAction()->type != BA_HIT &&
1080 				_save->getBattleGame()->getCurrentAction()->type != BA_STUN)
1081 			{
1082 				unit->addFiringExp();
1083 			}
1084 		}
1085 	}
1086 	applyGravity(tile);
1087 	calculateSunShading(); // roofs could have been destroyed
1088 	calculateTerrainLighting(); // fires could have been started
1089 	calculateFOV(center / Position(16,16,24));
1090 	return bu;
1091 }
1092 
1093 /**
1094  * Handles explosions.
1095  *
1096  * HE, smoke and fire explodes in a circular pattern on 1 level only. HE however damages floor tiles of the above level. Not the units on it.
1097  * HE destroys an object if its armor is lower than the explosive power, then it's HE blockage is applied for further propagation.
1098  * See http://www.ufopaedia.org/index.php?title=Explosions for more info.
1099  * @param center Center of the explosion in voxelspace.
1100  * @param power Power of the explosion.
1101  * @param type The damage type of the explosion.
1102  * @param maxRadius The maximum radius othe explosion.
1103  * @param unit The unit that caused the explosion.
1104  */
explode(const Position & center,int power,ItemDamageType type,int maxRadius,BattleUnit * unit)1105 void TileEngine::explode(const Position &center, int power, ItemDamageType type, int maxRadius, BattleUnit *unit)
1106 {
1107 	double centerZ = center.z / 24 + 0.5;
1108 	double centerX = center.x / 16 + 0.5;
1109 	double centerY = center.y / 16 + 0.5;
1110 	int power_, penetration;
1111 	std::set<Tile*> tilesAffected;
1112 	std::pair<std::set<Tile*>::iterator,bool> ret;
1113 
1114 	if (type == DT_IN)
1115 	{
1116 		power /= 2;
1117 	}
1118 
1119 	int exHeight = std::max(0, std::min(3, Options::battleExplosionHeight));
1120 	int vertdec = 1000; //default flat explosion
1121 
1122 	switch (exHeight)
1123 	{
1124 	case 1:
1125 		vertdec = 30;
1126 		break;
1127 	case 2:
1128 		vertdec = 10;
1129 		break;
1130 	case 3:
1131 		vertdec = 5;
1132 	}
1133 
1134 
1135 	for (int fi = -90; fi <= 90; fi += 5)
1136 //	for (int fi = 0; fi <= 0; fi += 10)
1137 	{
1138 		// raytrace every 3 degrees makes sure we cover all tiles in a circle.
1139 		for (int te = 0; te <= 360; te += 3)
1140 		{
1141 			double cos_te = cos(te * M_PI / 180.0);
1142 			double sin_te = sin(te * M_PI / 180.0);
1143 			double sin_fi = sin(fi * M_PI / 180.0);
1144 			double cos_fi = cos(fi * M_PI / 180.0);
1145 
1146 			Tile *origin = _save->getTile(Position(centerX, centerY, centerZ));
1147 			double l = 0;
1148 			double vx, vy, vz;
1149 			int tileX, tileY, tileZ;
1150 			power_ = power + 1;
1151 			penetration = power_;
1152 			while (power_ > 0 && l <= maxRadius)
1153 			{
1154 				vx = centerX + l * sin_te * cos_fi;
1155 				vy = centerY + l * cos_te * cos_fi;
1156 				vz = centerZ + l * sin_fi;
1157 
1158 				tileZ = int(floor(vz));
1159 				tileX = int(floor(vx));
1160 				tileY = int(floor(vy));
1161 
1162 				Tile *dest = _save->getTile(Position(tileX, tileY, tileZ));
1163 				if (!dest) break; // out of map!
1164 
1165 
1166 				// blockage by terrain is deducted from the explosion power
1167 				if (std::abs(l) > 0) // no need to block epicentrum
1168 				{
1169 					power_ -= 10; // explosive damage decreases by 10 per tile
1170 					if (origin->getPosition().z != tileZ) power_ -= vertdec; //3d explosion factor
1171 					if (type == DT_IN)
1172 					{
1173 						int dir;
1174 						Pathfinding::vectorToDirection(origin->getPosition() - dest->getPosition(), dir);
1175 						if (dir != -1 && dir %2) power_ -= 5; // diagonal movement costs an extra 50% for fire.
1176 					}
1177 					penetration = power_ - (horizontalBlockage(origin, dest, type) + verticalBlockage(origin, dest, type)) * 2;
1178 				}
1179 
1180 				if (penetration > 0)
1181 				{
1182 					if (type == DT_HE)
1183 					{
1184 						// explosives do 1/2 damage to terrain and 1/2 up to 3/2 random damage to units (the halving is handled elsewhere)
1185 						dest->setExplosive(power_);
1186 					}
1187 
1188 					ret = tilesAffected.insert(dest); // check if we had this tile already
1189 					if (ret.second)
1190 					{
1191 						int dmgRng = (type == DT_HE || Options::TFTDDamage) ? 50 : 100;
1192 						int min = power_ * (100 - dmgRng) / 100;
1193 						int max = power_ * (100 + dmgRng) / 100;
1194 						BattleUnit *bu = dest->getUnit();
1195 						int wounds = 0;
1196 						if (bu && unit)
1197 						{
1198 							wounds = bu->getFatalWounds();
1199 						}
1200 						switch (type)
1201 						{
1202 						case DT_STUN:
1203 							// power 0 - 200%
1204 							if (bu)
1205 							{
1206 								if (distance(dest->getPosition(), Position(centerX, centerY, centerZ)) < 2)
1207 								{
1208 									bu->damage(Position(0, 0, 0), RNG::generate(min, max), type);
1209 								}
1210 								else
1211 								{
1212 									bu->damage(Position(centerX, centerY, centerZ) - dest->getPosition(), RNG::generate(min, max), type);
1213 								}
1214 							}
1215 							for (std::vector<BattleItem*>::iterator it = dest->getInventory()->begin(); it != dest->getInventory()->end(); ++it)
1216 							{
1217 								if ((*it)->getUnit())
1218 								{
1219 									(*it)->getUnit()->damage(Position(0, 0, 0), RNG::generate(min, max), type);
1220 								}
1221 							}
1222 							break;
1223 						case DT_HE:
1224 							{
1225 								// power 50 - 150%
1226 								if (bu)
1227 								{
1228 									if (distance(dest->getPosition(), Position(centerX, centerY, centerZ)) < 2)
1229 									{
1230 										// ground zero effect is in effect
1231 										bu->damage(Position(0, 0, 0), (int)(RNG::generate(min, max)), type);
1232 									}
1233 									else
1234 									{
1235 										// directional damage relative to explosion position.
1236 										// units above the explosion will be hit in the legs, units lateral to or below will be hit in the torso
1237 										bu->damage(Position(centerX, centerY, centerZ + 5) - dest->getPosition(), (int)(RNG::generate(min, max)), type);
1238 									}
1239 								}
1240 								bool done = false;
1241 								while (!done)
1242 								{
1243 									done = dest->getInventory()->empty();
1244 									for (std::vector<BattleItem*>::iterator it = dest->getInventory()->begin(); it != dest->getInventory()->end(); )
1245 									{
1246 										if (power_ > (*it)->getRules()->getArmor())
1247 										{
1248 											if ((*it)->getUnit() && (*it)->getUnit()->getStatus() == STATUS_UNCONSCIOUS)
1249 												(*it)->getUnit()->instaKill();
1250 											_save->removeItem((*it));
1251 											break;
1252 										}
1253 										else
1254 										{
1255 											++it;
1256 											done = it == dest->getInventory()->end();
1257 										}
1258 									}
1259 								}
1260 							}
1261 							break;
1262 
1263 						case DT_SMOKE:
1264 							// smoke from explosions always stay 6 to 14 turns - power of a smoke grenade is 60
1265 							if (dest->getSmoke() < 10 && dest->getTerrainLevel() > -24)
1266 							{
1267 								dest->setFire(0);
1268 								dest->setSmoke(RNG::generate(7, 15));
1269 							}
1270 							break;
1271 
1272 						case DT_IN:
1273 							if (!dest->isVoid())
1274 							{
1275 								if (dest->getFire() == 0)
1276 								{
1277 									dest->setFire(dest->getFuel() + 1);
1278 									dest->setSmoke(std::max(1, std::min(15 - (dest->getFlammability() / 10), 12)));
1279 								}
1280 								if (bu)
1281 								{
1282 									float resistance = bu->getArmor()->getDamageModifier(DT_IN);
1283 									if (resistance > 0.0)
1284 									{
1285 										bu->damage(Position(0, 0, 12-dest->getTerrainLevel()), RNG::generate(5, 10), DT_IN, true);
1286 										int burnTime = RNG::generate(0, int(5 * resistance));
1287 										if (bu->getFire() < burnTime)
1288 										{
1289 											bu->setFire(burnTime); // catch fire and burn
1290 										}
1291 									}
1292 								}
1293 							}
1294 							break;
1295 						default:
1296 							break;
1297 						}
1298 
1299 						if (unit && bu && bu->getFaction() != unit->getFaction())
1300 						{
1301 							unit->addFiringExp();
1302 							// if it's going to bleed to death and it's not a player, give credit for the kill.
1303 							if (wounds < bu->getFatalWounds() && bu->getFaction() != FACTION_PLAYER)
1304 							{
1305 								bu->killedBy(unit->getFaction());
1306 							}
1307 						}
1308 
1309 					}
1310 				}
1311 				power_ = penetration;
1312 				origin = dest;
1313 				l++;
1314 			}
1315 		}
1316 	}
1317 	// now detonate the tiles affected with HE
1318 
1319 	if (type == DT_HE)
1320 	{
1321 		for (std::set<Tile*>::iterator i = tilesAffected.begin(); i != tilesAffected.end(); ++i)
1322 		{
1323 			if (detonate(*i))
1324 				_save->setObjectiveDestroyed(true);
1325 			applyGravity(*i);
1326 			Tile *j = _save->getTile((*i)->getPosition() + Position(0,0,1));
1327 			if (j)
1328 				applyGravity(j);
1329 		}
1330 	}
1331 
1332 	calculateSunShading(); // roofs could have been destroyed
1333 	calculateTerrainLighting(); // fires could have been started
1334 	calculateFOV(center / Position(16,16,24));
1335 }
1336 
1337 /**
1338  * Applies the explosive power to the tile parts. This is where the actual destruction takes place.
1339  * Must affect 7 objects (6 box sides and the object inside).
1340  * @param tile Tile affected.
1341  * @return True if the objective was destroyed.
1342  */
detonate(Tile * tile)1343 bool TileEngine::detonate(Tile* tile)
1344 {
1345 	int explosive = tile->getExplosive();
1346 	tile->setExplosive(0,true);
1347 	bool objective = false;
1348 	Tile* tiles[7];
1349 	static const int parts[7]={0,1,2,0,1,2,3};
1350 	Position pos = tile->getPosition();
1351 	tiles[0] = _save->getTile(Position(pos.x, pos.y, pos.z+1)); //ceiling
1352 	tiles[1] = _save->getTile(Position(pos.x+1, pos.y, pos.z)); //east wall
1353 	tiles[2] = _save->getTile(Position(pos.x, pos.y+1, pos.z)); //south wall
1354 	tiles[3] = tiles[4] = tiles[5] = tiles[6] = tile;
1355 	if (explosive)
1356 	{
1357 		int remainingPower = explosive;
1358 		int flam = tile->getFlammability();
1359 		int fuel = tile->getFuel() + 1;
1360 		// explosions create smoke which only stays 1 or 2 turns
1361 		tile->setSmoke(std::max(1, std::min(tile->getSmoke() + RNG::generate(0,2), 15)));
1362 		for (int i = 0; i < 7; ++i)
1363 		{
1364 			if(tiles[i] && tiles[i]->getMapData(parts[i]))
1365 			{
1366 				remainingPower = explosive;
1367 				while (remainingPower >= 0 && tiles[i]->getMapData(parts[i]))
1368 				{
1369 					remainingPower -= 2 * tiles[i]->getMapData(parts[i])->getArmor();
1370 					if (remainingPower >= 0)
1371 					{
1372 						int volume = 0;
1373 						// get the volume of the object by checking it's loftemps objects.
1374 						for (int j=0; j < 12; j++)
1375 						{
1376 							if (tiles[i]->getMapData(parts[i])->getLoftID(j) != 0)
1377 								++volume;
1378 						}
1379 
1380 						if (i > 3)
1381 						{
1382 							tiles[i]->setFire(0);
1383 							int smoke = RNG::generate(0, (volume / 2) + 2);
1384 							smoke += (volume / 2) + 1;
1385 							if (smoke > tiles[i]->getSmoke())
1386 							{
1387 								tiles[i]->setSmoke(std::max(0, std::min(smoke, 15)));
1388 							}
1389 						}
1390 
1391 						if (_save->getMissionType() == "STR_BASE_DEFENSE" && i == 6 && tile->getMapData(MapData::O_OBJECT) && tile->getMapData(MapData::O_OBJECT)->isBaseModule())
1392 						{
1393 							_save->getModuleMap()[tile->getPosition().x/10][tile->getPosition().y/10].second--;
1394 						}
1395 						if (tiles[i]->destroy(parts[i]))
1396 						{
1397 							objective = true;
1398 						}
1399 						if (tiles[i]->getMapData(parts[i]))
1400 						{
1401 							flam = tiles[i]->getFlammability();
1402 							fuel = tiles[i]->getFuel() + 1;
1403 						}
1404 					}
1405 				}
1406 				if (i > 3 && 2 * flam < remainingPower && (tile->getMapData(MapData::O_FLOOR) || tile->getMapData(MapData::O_OBJECT)))
1407 				{
1408 					tile->setFire(fuel);
1409 					tile->setSmoke(std::max(1, std::min(15 - (flam / 10), 12)));
1410 				}
1411 			}
1412 		}
1413 	}
1414 
1415 	return objective;
1416 }
1417 
1418 
1419 /**
1420  * Checks for chained explosions.
1421  *
1422  * Chained explosions are explosions which occur after an explosive map object is destroyed.
1423  * May be due a direct hit, other explosion or fire.
1424  * @return tile on which a explosion occurred
1425  */
checkForTerrainExplosions()1426 Tile *TileEngine::checkForTerrainExplosions()
1427 {
1428 	for (int i = 0; i < _save->getMapSizeXYZ(); ++i)
1429 	{
1430 		if (_save->getTiles()[i]->getExplosive())
1431 		{
1432 			return _save->getTiles()[i];
1433 		}
1434 	}
1435 	return 0;
1436 }
1437 
1438 /**
1439  * Calculates the amount of power that is blocked going from one tile to another on a different level.
1440  * Can cross more than one level. Only floor tiles are taken into account.
1441  * @param startTile The tile where the power starts.
1442  * @param endTile The adjacent tile where the power ends.
1443  * @param type The type of power/damage.
1444  * @return Amount of blockage of this power.
1445  */
verticalBlockage(Tile * startTile,Tile * endTile,ItemDamageType type)1446 int TileEngine::verticalBlockage(Tile *startTile, Tile *endTile, ItemDamageType type)
1447 {
1448 	int block = 0;
1449 
1450 	// safety check
1451 	if (startTile == 0 || endTile == 0) return 0;
1452 	int direction = endTile->getPosition().z - startTile->getPosition().z;
1453 
1454 	if (direction == 0 ) return 0;
1455 
1456 	int x = startTile->getPosition().x;
1457 	int y = startTile->getPosition().y;
1458 
1459 	if (direction < 0) // down
1460 	{
1461 		for (int z = startTile->getPosition().z; z > endTile->getPosition().z; z--)
1462 		{
1463 			block += blockage(_save->getTile(Position(x, y, z)), MapData::O_FLOOR, type);
1464 			block += blockage(_save->getTile(Position(x, y, z)), MapData::O_OBJECT, type, Pathfinding::DIR_DOWN);
1465 		}
1466 		if (x != endTile->getPosition().x || y != endTile->getPosition().y)
1467 		{
1468 			x = endTile->getPosition().x;
1469 			y = endTile->getPosition().y;
1470 			int z = startTile->getPosition().z;
1471 			block += horizontalBlockage(startTile, _save->getTile(Position(x, y, z)), type);
1472 			for (; z > endTile->getPosition().z; z--)
1473 			{
1474 				block += blockage(_save->getTile(Position(x, y, z)), MapData::O_FLOOR, type);
1475 				block += blockage(_save->getTile(Position(x, y, z)), MapData::O_OBJECT, type);
1476 			}
1477 		}
1478 	}
1479 	else if (direction > 0) // up
1480 	{
1481 		for (int z = startTile->getPosition().z + 1; z <= endTile->getPosition().z; z++)
1482 		{
1483 			block += blockage(_save->getTile(Position(x, y, z)), MapData::O_FLOOR, type);
1484 			block += blockage(_save->getTile(Position(x, y, z)), MapData::O_OBJECT, type, Pathfinding::DIR_UP);
1485 		}
1486 		if (x != endTile->getPosition().x || y != endTile->getPosition().y)
1487 		{
1488 			x = endTile->getPosition().x;
1489 			y = endTile->getPosition().y;
1490 			int z = startTile->getPosition().z;
1491 			block += horizontalBlockage(startTile, _save->getTile(Position(x, y, z)), type);
1492 			for (z = startTile->getPosition().z + 1; z <= endTile->getPosition().z; z++)
1493 			{
1494 				block += blockage(_save->getTile(Position(x, y, z)), MapData::O_FLOOR, type);
1495 				block += blockage(_save->getTile(Position(x, y, z)), MapData::O_OBJECT, type);
1496 			}
1497 		}
1498 	}
1499 
1500 	return block;
1501 }
1502 
1503 /**
1504  * Calculates the amount of power that is blocked going from one tile to another on the same level.
1505  * @param startTile The tile where the power starts.
1506  * @param endTile The adjacent tile where the power ends.
1507  * @param type The type of power/damage.
1508  * @return Amount of blockage.
1509  */
horizontalBlockage(Tile * startTile,Tile * endTile,ItemDamageType type)1510 int TileEngine::horizontalBlockage(Tile *startTile, Tile *endTile, ItemDamageType type)
1511 {
1512 	static const Position oneTileNorth = Position(0, -1, 0);
1513 	static const Position oneTileEast = Position(1, 0, 0);
1514 	static const Position oneTileSouth = Position(0, 1, 0);
1515 	static const Position oneTileWest = Position(-1, 0, 0);
1516 
1517 	// safety check
1518 	if (startTile == 0 || endTile == 0) return 0;
1519 	if (startTile->getPosition().z != endTile->getPosition().z) return 0;
1520 
1521 	int direction;
1522 	Pathfinding::vectorToDirection(endTile->getPosition() - startTile->getPosition(), direction);
1523 	if (direction == -1) return 0;
1524 	int block = 0;
1525 
1526 	switch(direction)
1527 	{
1528 	case 0:	// north
1529 		block = blockage(startTile, MapData::O_NORTHWALL, type);
1530 		break;
1531 	case 1: // north east
1532 		if (type == DT_NONE) //this is two-way diagonal visiblity check, used in original game
1533 		{
1534 			block = blockage(startTile, MapData::O_NORTHWALL, type) + blockage(endTile, MapData::O_WESTWALL, type); //up+right
1535 			block += blockage(_save->getTile(startTile->getPosition() + oneTileNorth), MapData::O_OBJECT, type, 3);
1536 			if (block == 0) break; //this way is opened
1537 			block = blockage(_save->getTile(startTile->getPosition() + oneTileEast), MapData::O_NORTHWALL, type)
1538 				+ blockage(_save->getTile(startTile->getPosition() + oneTileEast), MapData::O_WESTWALL, type); //right+up
1539 			block += blockage(_save->getTile(startTile->getPosition() + oneTileEast), MapData::O_OBJECT, type, 7);
1540 		}
1541 		else
1542 		{
1543 			block = (blockage(startTile,MapData::O_NORTHWALL, type) + blockage(endTile,MapData::O_WESTWALL, type))/2
1544 				+ (blockage(_save->getTile(startTile->getPosition() + oneTileEast),MapData::O_WESTWALL, type)
1545 				+ blockage(_save->getTile(startTile->getPosition() + oneTileEast),MapData::O_NORTHWALL, type))/2;
1546 
1547 			if (!endTile->getMapData(MapData::O_OBJECT))
1548 			{
1549 				block += (blockage(_save->getTile(startTile->getPosition() + oneTileEast),MapData::O_OBJECT, type, direction)
1550 					+ blockage(_save->getTile(startTile->getPosition() + oneTileNorth),MapData::O_OBJECT, type, 4)
1551 					+ blockage(_save->getTile(startTile->getPosition() + oneTileNorth),MapData::O_OBJECT, type, 2))/2;
1552 			}
1553 		}
1554 		break;
1555 	case 2: // east
1556 		block = blockage(endTile,MapData::O_WESTWALL, type);
1557 		break;
1558 	case 3: // south east
1559 		if (type == DT_NONE)
1560 		{
1561 			block = blockage(_save->getTile(startTile->getPosition() + oneTileSouth), MapData::O_NORTHWALL, type)
1562 				+ blockage(endTile, MapData::O_WESTWALL, type); //down+right
1563 			block += blockage(_save->getTile(startTile->getPosition() + oneTileSouth), MapData::O_OBJECT, type, 1);
1564 			if (block == 0) break; //this way is opened
1565 			block = blockage(_save->getTile(startTile->getPosition() + oneTileEast), MapData::O_WESTWALL, type)
1566 				+ blockage(endTile, MapData::O_NORTHWALL, type); //right+down
1567 			block += blockage(_save->getTile(startTile->getPosition() + oneTileEast), MapData::O_OBJECT, type, 5);
1568 		}
1569 		else
1570 		{
1571 			block = (blockage(endTile,MapData::O_WESTWALL, type) + blockage(endTile,MapData::O_NORTHWALL, type))/2
1572 				+ (blockage(_save->getTile(startTile->getPosition() + oneTileEast),MapData::O_WESTWALL, type)
1573 				+ blockage(_save->getTile(startTile->getPosition() + oneTileSouth),MapData::O_NORTHWALL, type))/2;
1574 
1575 			if (!endTile->getMapData(MapData::O_OBJECT))
1576 			{
1577 				block += (blockage(_save->getTile(startTile->getPosition() + oneTileSouth),MapData::O_OBJECT, type, 2)
1578 					+ blockage(_save->getTile(startTile->getPosition() + oneTileEast),MapData::O_OBJECT, type, 4))/2;
1579 			}
1580 		}
1581 		break;
1582 	case 4: // south
1583 		block = blockage(endTile,MapData::O_NORTHWALL, type);
1584 		break;
1585 	case 5: // south west
1586 		if (type == DT_NONE)
1587 		{
1588 			block = blockage(_save->getTile(startTile->getPosition() + oneTileSouth), MapData::O_NORTHWALL, type)
1589 				+ blockage(_save->getTile(startTile->getPosition() + oneTileSouth), MapData::O_WESTWALL, type); //down+left
1590 			block += blockage(_save->getTile(startTile->getPosition() + oneTileSouth), MapData::O_OBJECT, type, 7);
1591 			if (block == 0) break; //this way is opened
1592 			block = blockage(startTile, MapData::O_WESTWALL, type) + blockage(endTile, MapData::O_NORTHWALL, type); //left+down
1593 			block += blockage(_save->getTile(startTile->getPosition() + oneTileWest), MapData::O_OBJECT, type, 3);
1594 		}
1595 		else
1596 		{
1597 			block = (blockage(endTile,MapData::O_NORTHWALL, type) + blockage(startTile,MapData::O_WESTWALL, type))/2
1598 				+ (blockage(_save->getTile(startTile->getPosition() + oneTileSouth),MapData::O_WESTWALL, type)
1599 				+ blockage(_save->getTile(startTile->getPosition() + oneTileSouth),MapData::O_NORTHWALL, type))/2;
1600 			if (!endTile->getMapData(MapData::O_OBJECT))
1601 			{
1602 				block += (blockage(_save->getTile(startTile->getPosition() + oneTileSouth),MapData::O_OBJECT, type, direction)
1603 					+ blockage(_save->getTile(startTile->getPosition() + oneTileWest),MapData::O_OBJECT, type, 2)
1604 					+ blockage(_save->getTile(startTile->getPosition() + oneTileWest),MapData::O_OBJECT, type, 4))/2;
1605 			}
1606 		}
1607 		break;
1608 	case 6: // west
1609 		block = blockage(startTile,MapData::O_WESTWALL, type);
1610 		break;
1611 	case 7: // north west
1612 
1613 		if (type == DT_NONE)
1614 		{
1615 			block = blockage(startTile, MapData::O_NORTHWALL, type)
1616 				+ blockage(_save->getTile(startTile->getPosition() + oneTileNorth), MapData::O_WESTWALL, type); //up+left
1617 			block += blockage(_save->getTile(startTile->getPosition() + oneTileNorth), MapData::O_OBJECT, type, 5);
1618 			if (block == 0) break; //this way is opened
1619 			block = blockage(startTile, MapData::O_WESTWALL, type)
1620 				+ blockage(_save->getTile(startTile->getPosition() + oneTileWest), MapData::O_NORTHWALL, type); //left+up
1621 			block += blockage(_save->getTile(startTile->getPosition() + oneTileWest), MapData::O_OBJECT, type, 1);
1622 		}
1623 		else
1624 		{
1625 			block = (blockage(startTile,MapData::O_WESTWALL, type) + blockage(startTile,MapData::O_NORTHWALL, type))/2
1626 				+ (blockage(_save->getTile(startTile->getPosition() + oneTileNorth),MapData::O_WESTWALL, type)
1627 				+ blockage(_save->getTile(startTile->getPosition() + oneTileWest),MapData::O_NORTHWALL, type))/2;
1628 
1629 			if (!endTile->getMapData(MapData::O_OBJECT))
1630 			{
1631 				block += (blockage(_save->getTile(startTile->getPosition() + oneTileNorth),MapData::O_OBJECT, type, 4)
1632 					+ blockage(_save->getTile(startTile->getPosition() + oneTileWest),MapData::O_OBJECT, type, 2))/2;
1633 			}
1634 		}
1635 		break;
1636 	}
1637 
1638     block += blockage(startTile,MapData::O_OBJECT, type, direction, true);
1639 	if (type != DT_NONE)
1640 	{
1641 		direction += 4;
1642 		if (direction > 7)
1643 			direction -= 8;
1644 		block += blockage(endTile,MapData::O_OBJECT, type, direction);
1645 	}
1646 	else
1647 	{
1648         if ( block <= 127 )
1649         {
1650             direction += 4;
1651             if (direction > 7)
1652                 direction -= 8;
1653             if (blockage(endTile,MapData::O_OBJECT, type, direction) > 127){
1654                 return -1; //hit bigwall, reveal bigwall tile
1655             }
1656         }
1657 	}
1658 
1659 	return block;
1660 }
1661 
1662 /**
1663  * Calculates the amount this certain wall or floor-part of the tile blocks.
1664  * @param startTile The tile where the power starts.
1665  * @param part The part of the tile the power needs to go through.
1666  * @param type The type of power/damage.
1667  * @param direction Direction the power travels.
1668  * @return Amount of blockage.
1669  */
blockage(Tile * tile,const int part,ItemDamageType type,int direction,bool checkingFromOrigin)1670 int TileEngine::blockage(Tile *tile, const int part, ItemDamageType type, int direction, bool checkingFromOrigin)
1671 {
1672 	int blockage = 0;
1673 
1674 	if (tile == 0) return 0; // probably outside the map here
1675 	if (tile->getMapData(part))
1676 	{
1677 		bool check = true;
1678 		int wall = -1;
1679 		if (direction != -1)
1680 		{
1681 			wall = tile->getMapData(MapData::O_OBJECT)->getBigWall();
1682 
1683 			if (checkingFromOrigin &&
1684 				(wall == Pathfinding::BIGWALLNESW ||
1685 				wall == Pathfinding::BIGWALLNWSE))
1686 			{
1687 				check = false;
1688 			}
1689 			switch (direction)
1690 			{
1691 			case 0: // north
1692 				if (wall == Pathfinding::BIGWALLWEST ||
1693 					wall == Pathfinding::BIGWALLEAST ||
1694 					wall == Pathfinding::BIGWALLSOUTH ||
1695 					wall == Pathfinding::BIGWALLEASTANDSOUTH)
1696 				{
1697 					check = false;
1698 				}
1699 				break;
1700 			case 1: // north east
1701 				if (wall == Pathfinding::BIGWALLWEST ||
1702 					wall == Pathfinding::BIGWALLSOUTH)
1703 				{
1704 					check = false;
1705 				}
1706 				break;
1707 			case 2: // east
1708 				if (wall == Pathfinding::BIGWALLNORTH ||
1709 					wall == Pathfinding::BIGWALLSOUTH ||
1710 					wall == Pathfinding::BIGWALLWEST)
1711 				{
1712 					check = false;
1713 				}
1714 				break;
1715 			case 3: // south east
1716 				if (wall == Pathfinding::BIGWALLNORTH ||
1717 					wall == Pathfinding::BIGWALLWEST)
1718 				{
1719 					check = false;
1720 				}
1721 				break;
1722 			case 4: // south
1723 				if (wall == Pathfinding::BIGWALLWEST ||
1724 					wall == Pathfinding::BIGWALLEAST ||
1725 					wall == Pathfinding::BIGWALLNORTH)
1726 				{
1727 					check = false;
1728 				}
1729 				break;
1730 			case 5: // south west
1731 				if (wall == Pathfinding::BIGWALLNORTH ||
1732 					wall == Pathfinding::BIGWALLEAST)
1733 				{
1734 					check = false;
1735 				}
1736 				break;
1737 			case 6: // west
1738 				if (wall == Pathfinding::BIGWALLNORTH ||
1739 					wall == Pathfinding::BIGWALLSOUTH ||
1740 					wall == Pathfinding::BIGWALLEAST ||
1741 					wall == Pathfinding::BIGWALLEASTANDSOUTH)
1742 				{
1743 					check = false;
1744 				}
1745 				break;
1746 			case 7: // north west
1747 				if (wall == Pathfinding::BIGWALLSOUTH ||
1748 					wall == Pathfinding::BIGWALLEAST ||
1749 					wall == Pathfinding::BIGWALLEASTANDSOUTH)
1750 				{
1751 					check = false;
1752 				}
1753 				break;
1754 			case 8: // up
1755 			case 9: // down
1756 				if (wall != 0 && wall != Pathfinding::BLOCK)
1757 				{
1758 					check = false;
1759 				}
1760 				break;
1761 			default:
1762 				break;
1763 			}
1764 		}
1765 
1766 		if (check)
1767 		{
1768 			// -1 means we have a regular wall, and anything over 0 means we have a bigwall.
1769 			if (type == DT_SMOKE && wall != 0 && !tile->isUfoDoorOpen(part))
1770 			{
1771 				return 256;
1772 			}
1773 			blockage += tile->getMapData(part)->getBlock(type);
1774 		}
1775 	}
1776 
1777 	// open ufo doors are actually still closed behind the scenes
1778 	// so a special trick is needed to see if they are open, if they are, they obviously don't block anything
1779 	if (tile->isUfoDoorOpen(part))
1780 		blockage = 0;
1781 
1782 	return blockage;
1783 }
1784 
1785 /**
1786  * Opens a door (if any) by rightclick, or by walking through it. The unit has to face in the right direction.
1787  * @param unit Unit.
1788  * @param rClick Whether the player right clicked.
1789  * @param dir Direction.
1790  * @return -1 there is no door, you can walk through;
1791  *		  0 normal door opened, make a squeaky sound and you can walk through;
1792  *		  1 ufo door is starting to open, make a whoosh sound, don't walk through;
1793  *		  3 ufo door is still opening, don't walk through it yet. (have patience, futuristic technology...)
1794  *		  4 not enough TUs
1795  *		  5 would contravene fire reserve
1796  */
unitOpensDoor(BattleUnit * unit,bool rClick,int dir)1797 int TileEngine::unitOpensDoor(BattleUnit *unit, bool rClick, int dir)
1798 {
1799 	int door = -1;
1800 	int TUCost = 0;
1801 	int size = unit->getArmor()->getSize();
1802 	int z = unit->getTile()->getTerrainLevel() < -12 ? 1 : 0; // if we're standing on stairs, check the tile above instead.
1803 	if (size > 1 && rClick)
1804 		return door;
1805 	if (dir == -1)
1806 	{
1807 		dir = unit->getDirection();
1808 	}
1809 	for (int x = 0; x < size && door == -1; x++)
1810 	{
1811 		for (int y = 0; y < size && door == -1; y++)
1812 		{
1813 			std::vector<std::pair<Position, int> > checkPositions;
1814 			Tile *tile = _save->getTile(unit->getPosition() + Position(x,y,z));
1815 			if (!tile) continue;
1816 
1817 			switch (dir)
1818 			{
1819 			case 0: // north
1820 				checkPositions.push_back(std::make_pair(Position(0, 0, 0), MapData::O_NORTHWALL)); // origin
1821 				if (x != 0)
1822 				{
1823 					checkPositions.push_back(std::make_pair(Position(0, -1, 0), MapData::O_WESTWALL)); // one tile north
1824 				}
1825 				break;
1826 			case 1: // north east
1827 				checkPositions.push_back(std::make_pair(Position(0, 0, 0), MapData::O_NORTHWALL)); // origin
1828 				checkPositions.push_back(std::make_pair(Position(1, -1, 0), MapData::O_WESTWALL)); // one tile north-east
1829 				if (rClick)
1830 				{
1831 					checkPositions.push_back(std::make_pair(Position(1, 0, 0), MapData::O_WESTWALL)); // one tile east
1832 					checkPositions.push_back(std::make_pair(Position(1, 0, 0), MapData::O_NORTHWALL)); // one tile east
1833 				}
1834 				break;
1835 			case 2: // east
1836 				checkPositions.push_back(std::make_pair(Position(1, 0, 0), MapData::O_WESTWALL)); // one tile east
1837 				break;
1838 			case 3: // south-east
1839 				if (!y)
1840 					checkPositions.push_back(std::make_pair(Position(1, 1, 0), MapData::O_WESTWALL)); // one tile south-east
1841 				if (!x)
1842 					checkPositions.push_back(std::make_pair(Position(1, 1, 0), MapData::O_NORTHWALL)); // one tile south-east
1843 				if (rClick)
1844 				{
1845 					checkPositions.push_back(std::make_pair(Position(1, 0, 0), MapData::O_WESTWALL)); // one tile east
1846 					checkPositions.push_back(std::make_pair(Position(0, 1, 0), MapData::O_NORTHWALL)); // one tile south
1847 				}
1848 				break;
1849 			case 4: // south
1850 				checkPositions.push_back(std::make_pair(Position(0, 1, 0), MapData::O_NORTHWALL)); // one tile south
1851 				break;
1852 			case 5: // south-west
1853 				checkPositions.push_back(std::make_pair(Position(0, 0, 0), MapData::O_WESTWALL)); // origin
1854 				checkPositions.push_back(std::make_pair(Position(-1, 1, 0), MapData::O_NORTHWALL)); // one tile south-west
1855 				if (rClick)
1856 				{
1857 					checkPositions.push_back(std::make_pair(Position(0, 1, 0), MapData::O_WESTWALL)); // one tile south
1858 					checkPositions.push_back(std::make_pair(Position(0, 1, 0), MapData::O_NORTHWALL)); // one tile south
1859 				}
1860 				break;
1861 			case 6: // west
1862 				checkPositions.push_back(std::make_pair(Position(0, 0, 0), MapData::O_WESTWALL)); // origin
1863 				if (y != 0)
1864 				{
1865 					checkPositions.push_back(std::make_pair(Position(-1, 0, 0), MapData::O_NORTHWALL)); // one tile west
1866 				}
1867 				break;
1868 			case 7: // north-west
1869 				checkPositions.push_back(std::make_pair(Position(0, 0, 0), MapData::O_WESTWALL)); // origin
1870 				checkPositions.push_back(std::make_pair(Position(0, 0, 0), MapData::O_NORTHWALL)); // origin
1871 				if (x)
1872 				{
1873 					checkPositions.push_back(std::make_pair(Position(-1, -1, 0), MapData::O_WESTWALL)); // one tile north
1874 				}
1875 				if (y)
1876 				{
1877 					checkPositions.push_back(std::make_pair(Position(-1, -1, 0), MapData::O_NORTHWALL)); // one tile north
1878 				}
1879 				if (rClick)
1880 				{
1881 					checkPositions.push_back(std::make_pair(Position(0, -1, 0), MapData::O_WESTWALL)); // one tile north
1882 					checkPositions.push_back(std::make_pair(Position(-1, 0, 0), MapData::O_NORTHWALL)); // one tile west
1883 				}
1884 				break;
1885 			default:
1886 				break;
1887 			}
1888 
1889 			int part = 0;
1890 			for (std::vector<std::pair<Position, int> >::const_iterator i = checkPositions.begin(); i != checkPositions.end() && door == -1; ++i)
1891 			{
1892 				tile = _save->getTile(unit->getPosition() + Position(x,y,z) + i->first);
1893 				if (tile)
1894 				{
1895 					door = tile->openDoor(i->second, unit, _save->getBattleGame()->getReservedAction());
1896 					if (door != -1)
1897 					{
1898 						part = i->second;
1899 						if (door == 1)
1900 						{
1901 							checkAdjacentDoors(unit->getPosition() + Position(x,y,z) + i->first, i->second);
1902 						}
1903 					}
1904 				}
1905 			}
1906 			if (door == 0 && rClick)
1907 			{
1908 				if (part == MapData::O_WESTWALL)
1909 				{
1910 					part = MapData::O_NORTHWALL;
1911 				}
1912 				else
1913 				{
1914 					part = MapData::O_WESTWALL;
1915 				}
1916 				TUCost = tile->getTUCost(part, unit->getArmor()->getMovementType());
1917 			}
1918 			else if (door == 1 || door == 4)
1919 			{
1920 				TUCost = tile->getTUCost(part, unit->getArmor()->getMovementType());
1921 			}
1922 		}
1923 	}
1924 
1925 	if (TUCost != 0)
1926 	{
1927 		if (_save->getBattleGame()->checkReservedTU(unit, TUCost))
1928 		{
1929 			if (unit->spendTimeUnits(TUCost))
1930 			{
1931 				calculateFOV(unit->getPosition());
1932 				// look from the other side (may be need check reaction fire?)
1933 				std::vector<BattleUnit*> *vunits = unit->getVisibleUnits();
1934 				for (size_t i = 0; i < vunits->size(); ++i)
1935 				{
1936 					calculateFOV(vunits->at(i));
1937 				}
1938 			}
1939 			else return 4;
1940 		}
1941 		else return 5;
1942 	}
1943 
1944 	return door;
1945 }
1946 
1947 /**
1948  * Opens any doors connected to this part at this position,
1949  * Keeps processing til it hits a non-ufo-door.
1950  * @param pos The starting position
1951  * @param part The part to open, defines which direction to check.
1952  */
checkAdjacentDoors(Position pos,int part)1953 void TileEngine::checkAdjacentDoors(Position pos, int part)
1954 {
1955 	Position offset;
1956 	bool westSide = (part == 1);
1957 	for (int i = 1;; ++i)
1958 	{
1959 		offset = westSide ? Position(0,i,0):Position(i,0,0);
1960 		Tile *tile = _save->getTile(pos + offset);
1961 		if (tile && tile->getMapData(part) && tile->getMapData(part)->isUFODoor())
1962 		{
1963 			tile->openDoor(part);
1964 		}
1965 		else break;
1966 	}
1967 	for (int i = -1;; --i)
1968 	{
1969 		offset = westSide ? Position(0,i,0):Position(i,0,0);
1970 		Tile *tile = _save->getTile(pos + offset);
1971 		if (tile && tile->getMapData(part) && tile->getMapData(part)->isUFODoor())
1972 		{
1973 			tile->openDoor(part);
1974 		}
1975 		else break;
1976 	}
1977 }
1978 
1979 /**
1980  * Closes ufo doors.
1981  * @return Whether doors are closed.
1982  */
closeUfoDoors()1983 int TileEngine::closeUfoDoors()
1984 {
1985 	int doorsclosed = 0;
1986 
1987 	// prepare a list of tiles on fire/smoke & close any ufo doors
1988 	for (int i = 0; i < _save->getMapSizeXYZ(); ++i)
1989 	{
1990 		if (_save->getTiles()[i]->getUnit() && _save->getTiles()[i]->getUnit()->getArmor()->getSize() > 1)
1991 		{
1992 			BattleUnit *bu = _save->getTiles()[i]->getUnit();
1993 			Tile *tile = _save->getTiles()[i];
1994 			Tile *oneTileNorth = _save->getTile(tile->getPosition() + Position(0, -1, 0));
1995 			Tile *oneTileWest = _save->getTile(tile->getPosition() + Position(-1, 0, 0));
1996 			if ((tile->isUfoDoorOpen(MapData::O_NORTHWALL) && oneTileNorth && oneTileNorth->getUnit() && oneTileNorth->getUnit() == bu) ||
1997 				(tile->isUfoDoorOpen(MapData::O_WESTWALL) && oneTileWest && oneTileWest->getUnit() && oneTileWest->getUnit() == bu))
1998 			{
1999 				continue;
2000 			}
2001 		}
2002 		doorsclosed += _save->getTiles()[i]->closeUfoDoor();
2003 	}
2004 
2005 	return doorsclosed;
2006 }
2007 
2008 /**
2009  * Calculates a line trajectory, using bresenham algorithm in 3D.
2010  * @param origin Origin (voxel??).
2011  * @param target Target (also voxel??).
2012  * @param storeTrajectory True will store the whole trajectory - otherwise it just stores the last position.
2013  * @param trajectory A vector of positions in which the trajectory is stored.
2014  * @param excludeUnit Excludes this unit in the collision detection.
2015  * @param doVoxelCheck Check against voxel or tile blocking? (first one for units visibility and line of fire, second one for terrain visibility).
2016  * @param onlyVisible Skip invisible units? used in FPS view.
2017  * @param excludeAllBut [Optional] The only unit to be considered for ray hits.
2018  * @return the objectnumber(0-3) or unit(4) or out of map (5) or -1(hit nothing).
2019  */
calculateLine(const Position & origin,const Position & target,bool storeTrajectory,std::vector<Position> * trajectory,BattleUnit * excludeUnit,bool doVoxelCheck,bool onlyVisible,BattleUnit * excludeAllBut)2020 int TileEngine::calculateLine(const Position& origin, const Position& target, bool storeTrajectory, std::vector<Position> *trajectory, BattleUnit *excludeUnit, bool doVoxelCheck, bool onlyVisible, BattleUnit *excludeAllBut)
2021 {
2022 	int x, x0, x1, delta_x, step_x;
2023 	int y, y0, y1, delta_y, step_y;
2024 	int z, z0, z1, delta_z, step_z;
2025 	int swap_xy, swap_xz;
2026 	int drift_xy, drift_xz;
2027 	int cx, cy, cz;
2028 	Position lastPoint(origin);
2029 	int result;
2030 
2031 	//start and end points
2032 	x0 = origin.x;	 x1 = target.x;
2033 	y0 = origin.y;	 y1 = target.y;
2034 	z0 = origin.z;	 z1 = target.z;
2035 
2036 	//'steep' xy Line, make longest delta x plane
2037 	swap_xy = abs(y1 - y0) > abs(x1 - x0);
2038 	if (swap_xy)
2039 	{
2040 		std::swap(x0, y0);
2041 		std::swap(x1, y1);
2042 	}
2043 
2044 	//do same for xz
2045 	swap_xz = abs(z1 - z0) > abs(x1 - x0);
2046 	if (swap_xz)
2047 	{
2048 		std::swap(x0, z0);
2049 		std::swap(x1, z1);
2050 	}
2051 
2052 	//delta is Length in each plane
2053 	delta_x = abs(x1 - x0);
2054 	delta_y = abs(y1 - y0);
2055 	delta_z = abs(z1 - z0);
2056 
2057 	//drift controls when to step in 'shallow' planes
2058 	//starting value keeps Line centred
2059 	drift_xy  = (delta_x / 2);
2060 	drift_xz  = (delta_x / 2);
2061 
2062 	//direction of line
2063 	step_x = 1;  if (x0 > x1) {  step_x = -1; }
2064 	step_y = 1;  if (y0 > y1) {  step_y = -1; }
2065 	step_z = 1;  if (z0 > z1) {  step_z = -1; }
2066 
2067 	//starting point
2068 	y = y0;
2069 	z = z0;
2070 
2071 	//step through longest delta (which we have swapped to x)
2072 	for (x = x0; x != (x1+step_x); x += step_x)
2073 	{
2074 		//copy position
2075 		cx = x;	cy = y;	cz = z;
2076 
2077 		//unswap (in reverse)
2078 		if (swap_xz) std::swap(cx, cz);
2079 		if (swap_xy) std::swap(cx, cy);
2080 
2081 		if (storeTrajectory && trajectory)
2082 		{
2083 			trajectory->push_back(Position(cx, cy, cz));
2084 		}
2085 		//passes through this point?
2086 		if (doVoxelCheck)
2087 		{
2088 			result = voxelCheck(Position(cx, cy, cz), excludeUnit, false, onlyVisible, excludeAllBut);
2089 			if (result != V_EMPTY)
2090 			{
2091 				if (trajectory)
2092 				{ // store the position of impact
2093 					trajectory->push_back(Position(cx, cy, cz));
2094 				}
2095 				return result;
2096 			}
2097 		}
2098 		else
2099 		{
2100             int temp_res = verticalBlockage(_save->getTile(lastPoint), _save->getTile(Position(cx, cy, cz)), DT_NONE);
2101 			result = horizontalBlockage(_save->getTile(lastPoint), _save->getTile(Position(cx, cy, cz)), DT_NONE);
2102             if (result == -1)
2103             {
2104                 if (temp_res > 127)
2105                 {
2106                     result = 0;
2107                 } else {
2108                 return result; // We hit a big wall
2109                 }
2110             }
2111             result += temp_res;
2112 			if (result > 127)
2113 			{
2114 				return result;
2115 			}
2116 
2117 			lastPoint = Position(cx, cy, cz);
2118 		}
2119 		//update progress in other planes
2120 		drift_xy = drift_xy - delta_y;
2121 		drift_xz = drift_xz - delta_z;
2122 
2123 		//step in y plane
2124 		if (drift_xy < 0)
2125 		{
2126 			y = y + step_y;
2127 			drift_xy = drift_xy + delta_x;
2128 
2129 			//check for xy diagonal intermediate voxel step
2130 			if (doVoxelCheck)
2131 			{
2132 				cx = x;	cz = z; cy = y;
2133 				if (swap_xz) std::swap(cx, cz);
2134 				if (swap_xy) std::swap(cx, cy);
2135 				result = voxelCheck(Position(cx, cy, cz), excludeUnit, false, onlyVisible, excludeAllBut);
2136 				if (result != V_EMPTY)
2137 				{
2138 					if (trajectory != 0)
2139 					{ // store the position of impact
2140 						trajectory->push_back(Position(cx, cy, cz));
2141 					}
2142 					return result;
2143 				}
2144 			}
2145 		}
2146 
2147 		//same in z
2148 		if (drift_xz < 0)
2149 		{
2150 			z = z + step_z;
2151 			drift_xz = drift_xz + delta_x;
2152 
2153 			//check for xz diagonal intermediate voxel step
2154 			if (doVoxelCheck)
2155 			{
2156 				cx = x;	cz = z; cy = y;
2157 				if (swap_xz) std::swap(cx, cz);
2158 				if (swap_xy) std::swap(cx, cy);
2159 				result = voxelCheck(Position(cx, cy, cz), excludeUnit, false, onlyVisible,  excludeAllBut);
2160 				if (result != V_EMPTY)
2161 				{
2162 					if (trajectory != 0)
2163 					{ // store the position of impact
2164 						trajectory->push_back(Position(cx, cy, cz));
2165 					}
2166 					return result;
2167 				}
2168 			}
2169 		}
2170 	}
2171 
2172 	return V_EMPTY;
2173 }
2174 
2175 /**
2176  * Calculates a parabola trajectory, used for throwing items.
2177  * @param origin Orign in voxelspace.
2178  * @param target Target in voxelspace.
2179  * @param storeTrajectory True will store the whole trajectory - otherwise it just stores the last position.
2180  * @param trajectory A vector of positions in which the trajectory is stored.
2181  * @param excludeUnit Makes sure the trajectory does not hit the shooter itself.
2182  * @param curvature How high the parabola goes: 1.0 is almost straight throw, 3.0 is a very high throw, to throw over a fence for example.
2183  * @param delta Is the deviation of the angles it should take into account, 0,0,0 is perfection.
2184  * @return The objectnumber(0-3) or unit(4) or out of map (5) or -1(hit nothing).
2185  */
calculateParabola(const Position & origin,const Position & target,bool storeTrajectory,std::vector<Position> * trajectory,BattleUnit * excludeUnit,double curvature,const Position delta)2186 int TileEngine::calculateParabola(const Position& origin, const Position& target, bool storeTrajectory, std::vector<Position> *trajectory, BattleUnit *excludeUnit, double curvature, const Position delta)
2187 {
2188 	double ro = sqrt((double)((target.x - origin.x) * (target.x - origin.x) + (target.y - origin.y) * (target.y - origin.y) + (target.z - origin.z) * (target.z - origin.z)));
2189 
2190 	if (AreSame(ro, 0.0)) return V_EMPTY;//just in case
2191 
2192 	double fi = acos((double)(target.z - origin.z) / ro);
2193 	double te = atan2((double)(target.y - origin.y), (double)(target.x - origin.x));
2194 
2195 	te += (delta.x / ro) / 2 * M_PI; //horizontal magic value
2196 	fi += ((delta.z + delta.y) / ro) / 14 * M_PI * curvature; //another magic value (vertical), to make it in line with fire spread
2197 
2198 	double zA = sqrt(ro)*curvature;
2199 	double zK = 4.0 * zA / ro / ro;
2200 
2201 	int x = origin.x;
2202 	int y = origin.y;
2203 	int z = origin.z;
2204 	int i = 8;
2205 	Position lastPosition = Position(x,y,z);
2206 	while (z > 0)
2207 	{
2208 		x = (int)((double)origin.x + (double)i * cos(te) * sin(fi));
2209 		y = (int)((double)origin.y + (double)i * sin(te) * sin(fi));
2210 		z = (int)((double)origin.z + (double)i * cos(fi) - zK * ((double)i - ro / 2.0) * ((double)i - ro / 2.0) + zA);
2211 		if (storeTrajectory && trajectory)
2212 		{
2213 			trajectory->push_back(Position(x, y, z));
2214 		}
2215 		//passes through this point?
2216 		Position nextPosition = Position(x,y,z);
2217 		int result = calculateLine(lastPosition, nextPosition, false, 0, excludeUnit);
2218 		if (result != V_EMPTY)
2219 		{
2220 			if (lastPosition.z < nextPosition.z)
2221 			{
2222 				result = V_OUTOFBOUNDS;
2223 			}
2224 			if (!storeTrajectory && trajectory != 0)
2225 			{ // store the position of impact
2226 				trajectory->push_back(nextPosition);
2227 			}
2228 			return result;
2229 		}
2230 		lastPosition = Position(x,y,z);
2231 		++i;
2232 	}
2233 	if (!storeTrajectory && trajectory != 0)
2234 	{ // store the position of impact
2235 		trajectory->push_back(Position(x, y, z));
2236 	}
2237 	return V_EMPTY;
2238 }
2239 
2240 /**
2241  * Calculates z "grounded" value for a particular voxel (used for projectile shadow).
2242  * @param voxel The voxel to trace down.
2243  * @return z coord of "ground".
2244  */
castedShade(const Position & voxel)2245 int TileEngine::castedShade(const Position& voxel)
2246 {
2247 	int zstart = voxel.z;
2248 	Position tmpCoord = voxel / Position(16,16,24);
2249 	Tile *t = _save->getTile(tmpCoord);
2250 	while (t && t->isVoid() && !t->getUnit())
2251 	{
2252 		zstart = tmpCoord.z* 24;
2253 		--tmpCoord.z;
2254 		t = _save->getTile(tmpCoord);
2255 	}
2256 
2257 	Position tmpVoxel = voxel;
2258 	int z;
2259 
2260 	for (z = zstart; z>0; z--)
2261 	{
2262 		tmpVoxel.z = z;
2263 		if (voxelCheck(tmpVoxel, 0) != V_EMPTY) break;
2264 	}
2265     return z;
2266 }
2267 
2268 /**
2269  * Traces voxel visibility.
2270  * @param voxel Voxel coordinates.
2271  * @return True if visible.
2272  */
2273 
isVoxelVisible(const Position & voxel)2274 bool TileEngine::isVoxelVisible(const Position& voxel)
2275 {
2276 	int zstart = voxel.z+3; // slight Z adjust
2277 	if ((zstart/24)!=(voxel.z/24))
2278 		return true; // visble!
2279 	Position tmpVoxel = voxel;
2280 	int zend = (zstart/24)*24 +24;
2281 	for (int z = zstart; z<zend; z++)
2282 	{
2283 		tmpVoxel.z=z;
2284 		// only OBJECT can cause additional occlusion (because of any shape)
2285 		if (voxelCheck(tmpVoxel, 0) == V_OBJECT) return false;
2286 		++tmpVoxel.x;
2287 		if (voxelCheck(tmpVoxel, 0) == V_OBJECT) return false;
2288 		++tmpVoxel.y;
2289 		if (voxelCheck(tmpVoxel, 0) == V_OBJECT) return false;
2290 	}
2291     return true;
2292 }
2293 
2294 /**
2295  * Checks if we hit a voxel.
2296  * @param voxel The voxel to check.
2297  * @param excludeUnit Don't do checks on this unit.
2298  * @param excludeAllUnits Don't do checks on any unit.
2299  * @param onlyVisible Whether to consider only visible units.
2300  * @param excludeAllBut If set, the only unit to be considered for ray hits.
2301  * @return The objectnumber(0-3) or unit(4) or out of map (5) or -1 (hit nothing).
2302  */
voxelCheck(const Position & voxel,BattleUnit * excludeUnit,bool excludeAllUnits,bool onlyVisible,BattleUnit * excludeAllBut)2303 int TileEngine::voxelCheck(const Position& voxel, BattleUnit *excludeUnit, bool excludeAllUnits, bool onlyVisible, BattleUnit *excludeAllBut)
2304 {
2305 	Tile *tile = _save->getTile(voxel / Position(16, 16, 24));
2306 	// check if we are not out of the map
2307 	if (tile == 0 || voxel.x < 0 || voxel.y < 0 || voxel.z < 0)
2308 	{
2309 		return V_OUTOFBOUNDS;
2310 	}
2311 	Tile *tileBelow = _save->getTile(tile->getPosition() + Position(0,0,-1));
2312 	if (tile->isVoid() && tile->getUnit() == 0 && (!tileBelow || tileBelow->getUnit() == 0))
2313 	{
2314 		return V_EMPTY;
2315 	}
2316 
2317 	if ((voxel.z % 24 == 0 || voxel.z % 24 == 1) && tile->getMapData(MapData::O_FLOOR) && tile->getMapData(MapData::O_FLOOR)->isGravLift())
2318 	{
2319 		if ((tile->getPosition().z == 0) || (tileBelow && tileBelow->getMapData(MapData::O_FLOOR) && !tileBelow->getMapData(MapData::O_FLOOR)->isGravLift()))
2320 			return V_FLOOR;
2321 	}
2322 
2323 	// first we check terrain voxel data, not to allow 2x2 units stick through walls
2324 	for (int i=0; i< 4; ++i)
2325 	{
2326 		MapData *mp = tile->getMapData(i);
2327 		if (tile->isUfoDoorOpen(i))
2328 			continue;
2329 		if (mp != 0)
2330 		{
2331 			int x = 15 - voxel.x%16;
2332 			int y = voxel.y%16;
2333 			int idx = (mp->getLoftID((voxel.z%24)/2)*16) + y;
2334 			if (_voxelData->at(idx) & (1 << x))
2335 			{
2336 				return i;
2337 			}
2338 		}
2339 	}
2340 
2341 	if (!excludeAllUnits)
2342 	{
2343 		BattleUnit *unit = tile->getUnit();
2344 		// sometimes there is unit on the tile below, but sticks up to this tile with his head,
2345 		// in this case we couldn't have unit standing at current tile.
2346 		if (unit == 0 && tile->hasNoFloor(0))
2347 		{
2348 			tile = _save->getTile(Position(voxel.x/16, voxel.y/16, (voxel.z/24)-1)); //below
2349 			if (tile) unit = tile->getUnit();
2350 		}
2351 
2352 		if (unit != 0 && unit != excludeUnit && (!excludeAllBut || unit == excludeAllBut) && (!onlyVisible || unit->getVisible() ) )
2353 		{
2354 			Position tilepos;
2355 			Position unitpos = unit->getPosition();
2356 			int tz = unitpos.z*24 + unit->getFloatHeight()+(-tile->getTerrainLevel());//bottom
2357 			if ((voxel.z > tz) && (voxel.z <= tz + unit->getHeight()) )
2358 			{
2359 				int x = voxel.x%16;
2360 				int y = voxel.y%16;
2361 				int part = 0;
2362 				if (unit->getArmor()->getSize() > 1)
2363 				{
2364 					tilepos = tile->getPosition();
2365 					part = tilepos.x - unitpos.x + (tilepos.y - unitpos.y)*2;
2366 				}
2367 				int idx = (unit->getLoftemps(part) * 16) + y;
2368 				if (_voxelData->at(idx) & (1 << x))
2369 				{
2370 					return V_UNIT;
2371 				}
2372 			}
2373 		}
2374 	}
2375 	return V_EMPTY;
2376 }
2377 
2378 /**
2379  * Toggles personal lighting on / off.
2380  */
togglePersonalLighting()2381 void TileEngine::togglePersonalLighting()
2382 {
2383 	_personalLighting = !_personalLighting;
2384 	calculateUnitLighting();
2385 }
2386 
2387 /**
2388  * Calculates the distance between 2 points. Rounded up to first INT.
2389  * @param pos1 Position of first square.
2390  * @param pos2 Position of second square.
2391  * @return Distance.
2392  */
distance(const Position & pos1,const Position & pos2) const2393 int TileEngine::distance(const Position &pos1, const Position &pos2) const
2394 {
2395 	int x = pos1.x - pos2.x;
2396 	int y = pos1.y - pos2.y;
2397 	return (int)Round(sqrt(float(x*x + y*y)));
2398 }
2399 
2400 /**
2401  * Calculates the distance squared between 2 points. No sqrt(), not floating point math, and sometimes it's all you need.
2402  * @param pos1 Position of first square.
2403  * @param pos2 Position of second square.
2404  * @param considerZ Whether to consider the z coordinate.
2405  * @return Distance.
2406  */
distanceSq(const Position & pos1,const Position & pos2,bool considerZ) const2407 int TileEngine::distanceSq(const Position &pos1, const Position &pos2, bool considerZ) const
2408 {
2409 	int x = pos1.x - pos2.x;
2410 	int y = pos1.y - pos2.y;
2411 	int z = considerZ ? (pos1.z - pos2.z) : 0;
2412 	return x*x + y*y + z*z;
2413 }
2414 
2415 /**
2416  * Attempts a panic or mind control action.
2417  * @param action Pointer to an action.
2418  * @return Whether it failed or succeeded.
2419  */
psiAttack(BattleAction * action)2420 bool TileEngine::psiAttack(BattleAction *action)
2421 {
2422 	BattleUnit *victim = _save->getTile(action->target)->getUnit();
2423 	if (!victim)
2424 		return false;
2425 	double attackStrength = action->actor->getStats()->psiStrength * action->actor->getStats()->psiSkill / 50.0;
2426 	double defenseStrength = victim->getStats()->psiStrength
2427 		+ ((victim->getStats()->psiSkill > 0) ? 10.0 + victim->getStats()->psiSkill / 5.0 : 10.0);
2428 	double d = distance(action->actor->getPosition(), action->target);
2429 	attackStrength -= d;
2430 	attackStrength += RNG::generate(0,55);
2431 
2432 	if (action->type == BA_MINDCONTROL)
2433 	{
2434 		defenseStrength += 20;
2435 	}
2436 
2437 	action->actor->addPsiExp();
2438 	if (attackStrength > defenseStrength)
2439 	{
2440 		action->actor->addPsiExp();
2441 		action->actor->addPsiExp();
2442 		if (action->type == BA_PANIC)
2443 		{
2444 			int moraleLoss = (110-_save->getTile(action->target)->getUnit()->getStats()->bravery);
2445 			if (moraleLoss > 0)
2446 			_save->getTile(action->target)->getUnit()->moraleChange(-moraleLoss);
2447 		}
2448 		else// if (action->type == BA_MINDCONTROL)
2449 		{
2450 			victim->convertToFaction(action->actor->getFaction());
2451 			calculateFOV(victim->getPosition());
2452 			calculateUnitLighting();
2453 			victim->setTimeUnits(victim->getStats()->tu);
2454 			victim->allowReselect();
2455 			victim->abortTurn(); // resets unit status to STANDING
2456 			// if all units from either faction are mind controlled - auto-end the mission.
2457 			if (_save->getSide() == FACTION_PLAYER && Options::battleAutoEnd && Options::allowPsionicCapture)
2458 			{
2459 				int liveAliens = 0;
2460 				int liveSoldiers = 0;
2461 				_save->getBattleGame()->tallyUnits(liveAliens, liveSoldiers, false);
2462 				if (liveAliens == 0 || liveSoldiers == 0)
2463 				{
2464 					_save->setSelectedUnit(0);
2465 					_save->getBattleGame()->cancelCurrentAction(true);
2466 					_save->getBattleGame()->requestEndTurn();
2467 				}
2468 			}
2469 		}
2470 		return true;
2471 	}
2472 	return false;
2473 }
2474 
2475 /**
2476  * Applies gravity to a tile. Causes items and units to drop.
2477  * @param t Tile.
2478  * @return Tile where the items end up in eventually.
2479  */
applyGravity(Tile * t)2480 Tile *TileEngine::applyGravity(Tile *t)
2481 {
2482 	if (!t || (t->getInventory()->empty() && !t->getUnit())) return t; // skip this if there are no items
2483 
2484 	Position p = t->getPosition();
2485 	Tile *rt = t;
2486 	Tile *rtb;
2487 	BattleUnit *occupant = t->getUnit();
2488 
2489 	if (occupant)
2490 	{
2491 		Position unitpos = occupant->getPosition();
2492 		while (unitpos.z >= 0)
2493 		{
2494 			bool canFall = true;
2495 			for (int y = 0; y < occupant->getArmor()->getSize() && canFall; ++y)
2496 			{
2497 				for (int x = 0; x < occupant->getArmor()->getSize() && canFall; ++x)
2498 				{
2499 					rt = _save->getTile(Position(unitpos.x+x, unitpos.y+y, unitpos.z));
2500 					rtb = _save->getTile(Position(unitpos.x+x, unitpos.y+y, unitpos.z-1)); //below
2501 					if (!rt->hasNoFloor(rtb))
2502 					{
2503 						canFall = false;
2504 					}
2505 				}
2506 			}
2507 			if (!canFall)
2508 				break;
2509 			unitpos.z--;
2510 		}
2511 		if (unitpos != occupant->getPosition())
2512 		{
2513 			if (occupant->getHealth() != 0 && occupant->getStunlevel() < occupant->getHealth())
2514 			{
2515 				if (occupant->getArmor()->getMovementType() == MT_FLY)
2516 				{
2517 					// move to the position you're already in. this will unset the kneeling flag, set teh floating flag, etc.
2518 					occupant->startWalking(occupant->getDirection(), occupant->getPosition(), _save->getTile(occupant->getPosition() + Position(0,0,-1)), true);
2519 					// and set our status to standing (rather than walking or flying) to avoid weirdness.
2520 					occupant->abortTurn();
2521 				}
2522 				else
2523 				{
2524 					occupant->startWalking(Pathfinding::DIR_DOWN, occupant->getPosition() + Position(0,0,-1), _save->getTile(occupant->getPosition() + Position(0,0,-1)), true);
2525 					_save->addFallingUnit(occupant);
2526 				}
2527 			}
2528 			else if (occupant->isOut())
2529 			{
2530 				Position origin = occupant->getPosition();
2531 				for (int y = occupant->getArmor()->getSize()-1; y >= 0; --y)
2532 				{
2533 					for (int x = occupant->getArmor()->getSize()-1; x >= 0; --x)
2534 					{
2535 						_save->getTile(origin + Position(x, y, 0))->setUnit(0);
2536 					}
2537 				}
2538 				occupant->setPosition(unitpos);
2539 			}
2540 		}
2541 	}
2542 	rt = t;
2543 	bool canFall = true;
2544 	while (p.z >= 0 && canFall)
2545 	{
2546 		rt = _save->getTile(p);
2547 		rtb = _save->getTile(Position(p.x, p.y, p.z-1)); //below
2548 		if (!rt->hasNoFloor(rtb))
2549 			canFall = false;
2550 		p.z--;
2551 	}
2552 
2553 	for (std::vector<BattleItem*>::iterator it = t->getInventory()->begin(); it != t->getInventory()->end(); ++it)
2554 	{
2555 		if ((*it)->getUnit() && t->getPosition() == (*it)->getUnit()->getPosition())
2556 		{
2557 			(*it)->getUnit()->setPosition(rt->getPosition());
2558 		}
2559 		if (t != rt)
2560 		{
2561 			rt->addItem(*it, (*it)->getSlot());
2562 		}
2563 	}
2564 
2565 	if (t != rt)
2566 	{
2567 		// clear tile
2568 		t->getInventory()->clear();
2569 	}
2570 
2571 	return rt;
2572 }
2573 
2574 /**
2575  * Validates the melee range between two units.
2576  * @param attacker The attacking unit.
2577  * @param target The unit we want to attack.
2578  * @param dir Direction to check.
2579  * @return True when the range is valid.
2580  */
validMeleeRange(BattleUnit * attacker,BattleUnit * target,int dir)2581 bool TileEngine::validMeleeRange(BattleUnit *attacker, BattleUnit *target, int dir)
2582 {
2583 	return validMeleeRange(attacker->getPosition(), dir, attacker, target, 0);
2584 }
2585 
2586 /**
2587  * Validates the melee range between a tile and a unit.
2588  * @param pos Position to check from.
2589  * @param direction Direction to check.
2590  * @param attacker The attacking unit.
2591  * @param target The unit we want to attack, 0 for any unit.
2592  * @param dest Destination position.
2593  * @return True when the range is valid.
2594  */
validMeleeRange(Position pos,int direction,BattleUnit * attacker,BattleUnit * target,Position * dest)2595 bool TileEngine::validMeleeRange(Position pos, int direction, BattleUnit *attacker, BattleUnit *target, Position *dest)
2596 {
2597 	if (direction < 0 || direction > 7)
2598 	{
2599 		return false;
2600 	}
2601 	Position p;
2602 	int size = attacker->getArmor()->getSize() - 1;
2603 	Pathfinding::directionToVector(direction, &p);
2604 	for (int x = 0; x <= size; ++x)
2605 	{
2606 		for (int y = 0; y <= size; ++y)
2607 		{
2608 			Tile *origin (_save->getTile(Position(pos + Position(x, y, 0))));
2609 			Tile *targetTile (_save->getTile(Position(pos + Position(x, y, 0) + p)));
2610 			Tile *aboveTargetTile (_save->getTile(Position(pos + Position(x, y, 1) + p)));
2611 			Tile *belowTargetTile (_save->getTile(Position(pos + Position(x, y, -1) + p)));
2612 
2613 			if (targetTile && origin)
2614 			{
2615 				if (origin->getTerrainLevel() <= -16 && aboveTargetTile && !aboveTargetTile->hasNoFloor(targetTile))
2616 				{
2617 					targetTile = aboveTargetTile;
2618 				}
2619 				else if (belowTargetTile && targetTile->hasNoFloor(belowTargetTile) && !targetTile->getUnit() && belowTargetTile->getTerrainLevel() <= -16)
2620 				{
2621 					targetTile = belowTargetTile;
2622 				}
2623 				if (targetTile->getUnit())
2624 				{
2625 					if (target == 0 || targetTile->getUnit() == target)
2626 					{
2627 						Position originVoxel = Position(origin->getPosition() * Position(16,16,24))
2628 							+ Position(8,8,attacker->getHeight() + attacker->getFloatHeight() - 4 -origin->getTerrainLevel());
2629 						Position targetVoxel;
2630 						if (canTargetUnit(&originVoxel, targetTile, &targetVoxel, attacker))
2631 						{
2632 							if (dest)
2633 							{
2634 								*dest = targetTile->getPosition();
2635 							}
2636 							return true;
2637 						}
2638 					}
2639 				}
2640 			}
2641 		}
2642 	}
2643 	return false;
2644 }
2645 
2646 /**
2647  * Gets the AI to look through a window.
2648  * @param position Current position.
2649  * @return Direction or -1 when no window found.
2650  */
faceWindow(const Position & position)2651 int TileEngine::faceWindow(const Position &position)
2652 {
2653 	static const Position oneTileEast = Position(1, 0, 0);
2654 	static const Position oneTileSouth = Position(0, 1, 0);
2655 
2656 	Tile *tile = _save->getTile(position);
2657 	if (tile && tile->getMapData(MapData::O_NORTHWALL) && tile->getMapData(MapData::O_NORTHWALL)->getBlock(DT_NONE)==0) return 0;
2658 	tile = _save->getTile(position + oneTileEast);
2659 	if (tile && tile->getMapData(MapData::O_WESTWALL) && tile->getMapData(MapData::O_WESTWALL)->getBlock(DT_NONE)==0) return 2;
2660 	tile = _save->getTile(position + oneTileSouth);
2661 	if (tile && tile->getMapData(MapData::O_NORTHWALL) && tile->getMapData(MapData::O_NORTHWALL)->getBlock(DT_NONE)==0) return 4;
2662 	tile = _save->getTile(position);
2663 	if (tile && tile->getMapData(MapData::O_WESTWALL) && tile->getMapData(MapData::O_WESTWALL)->getBlock(DT_NONE)==0) return 6;
2664 
2665 	return -1;
2666 }
2667 
2668 /**
2669  * Validates a throw action.
2670  * @param action The action to validate.
2671  * @param originVoxel The origin point of the action.
2672  * @param targetVoxel The target point of the action.
2673  * @param curve The curvature of the throw.
2674  * @param voxelType The type of voxel at which this parabola terminates.
2675  * @return Validity of action.
2676  */
validateThrow(BattleAction & action,Position originVoxel,Position targetVoxel,double * curve,int * voxelType)2677 bool TileEngine::validateThrow(BattleAction &action, Position originVoxel, Position targetVoxel, double *curve, int *voxelType)
2678 {
2679 	bool foundCurve = false;
2680 	double curvature = 0.5;
2681 	if (action.type == BA_THROW)
2682 	{
2683 		curvature = std::max(0.48, 1.73 / sqrt(sqrt((double)(action.actor->getStats()->strength) / (double)(action.weapon->getRules()->getWeight()))) + (action.actor->isKneeled()? 0.1 : 0.0));
2684 	}
2685 	Tile *targetTile = _save->getTile(action.target);
2686 	// object blocking - can't throw here
2687 	if ((action.type == BA_THROW
2688 		&& targetTile
2689 		&& targetTile->getMapData(MapData::O_OBJECT)
2690 		&& targetTile->getMapData(MapData::O_OBJECT)->getTUCost(MT_WALK) == 255)
2691 		|| ProjectileFlyBState::validThrowRange(&action, originVoxel, targetTile) == false)
2692 	{
2693 		return false;
2694 	}
2695 
2696 	// we try 8 different curvatures to try and reach our goal.
2697 	int test = V_OUTOFBOUNDS;
2698 	while (!foundCurve && curvature < 5.0)
2699 	{
2700 		std::vector<Position> trajectory;
2701 		test = calculateParabola(originVoxel, targetVoxel, false, &trajectory, action.actor, curvature, Position(0,0,0));
2702 		if (test != V_OUTOFBOUNDS && (trajectory.at(0) / Position(16, 16, 24)) == (targetVoxel / Position(16, 16, 24)))
2703 		{
2704 			if (voxelType)
2705 			{
2706 				*voxelType = test;
2707 			}
2708 			foundCurve = true;
2709 		}
2710 		else
2711 		{
2712 			curvature += 0.5;
2713 		}
2714 	}
2715 	if (curvature >= 5.0)
2716 	{
2717 		return false;
2718 	}
2719 	if (curve)
2720 	{
2721 		*curve = curvature;
2722 	}
2723 
2724 	return true;
2725 }
2726 
2727 /**
2728  * Recalculates FOV of all units in-game.
2729  */
recalculateFOV()2730 void TileEngine::recalculateFOV()
2731 {
2732 	for (std::vector<BattleUnit*>::iterator bu = _save->getUnits()->begin(); bu != _save->getUnits()->end(); ++bu)
2733 	{
2734 		if ((*bu)->getTile() != 0)
2735 		{
2736 			calculateFOV(*bu);
2737 		}
2738 	}
2739 }
2740 
2741 /**
2742  * Returns the direction from origin to target.
2743  * @param origin The origin point of the action.
2744  * @param target The target point of the action.
2745  * @return direction.
2746  */
getDirectionTo(const Position & origin,const Position & target) const2747 int TileEngine::getDirectionTo(const Position &origin, const Position &target) const
2748 {
2749 	double ox = target.x - origin.x;
2750 	double oy = target.y - origin.y;
2751 	double angle = atan2(ox, -oy);
2752 	// divide the pie in 4 angles each at 1/8th before each quarter
2753 	double pie[4] = {(M_PI_4 * 4.0) - M_PI_4 / 2.0, (M_PI_4 * 3.0) - M_PI_4 / 2.0, (M_PI_4 * 2.0) - M_PI_4 / 2.0, (M_PI_4 * 1.0) - M_PI_4 / 2.0};
2754 	int dir = 0;
2755 
2756 	if (angle > pie[0] || angle < -pie[0])
2757 	{
2758 		dir = 4;
2759 	}
2760 	else if (angle > pie[1])
2761 	{
2762 		dir = 3;
2763 	}
2764 	else if (angle > pie[2])
2765 	{
2766 		dir = 2;
2767 	}
2768 	else if (angle > pie[3])
2769 	{
2770 		dir = 1;
2771 	}
2772 	else if (angle < -pie[1])
2773 	{
2774 		dir = 5;
2775 	}
2776 	else if (angle < -pie[2])
2777 	{
2778 		dir = 6;
2779 	}
2780 	else if (angle < -pie[3])
2781 	{
2782 		dir = 7;
2783 	}
2784 	else if (angle < pie[0])
2785 	{
2786 		dir = 0;
2787 	}
2788 	return dir;
2789 }
2790 
2791 /**
2792  * Gets the origin voxel of a certain action.
2793  * @param action Battle action.
2794  * @param tile Pointer to the action tile.
2795  * @return origin position.
2796  */
getOriginVoxel(BattleAction & action,Tile * tile)2797 Position TileEngine::getOriginVoxel(BattleAction &action, Tile *tile)
2798 {
2799 
2800 	const int dirYshift[24] = {1, 3, 9, 15, 15, 13, 7, 1,  1, 1, 7, 13, 15, 15, 9, 3,  1, 2, 8, 14, 15, 14, 8, 2};
2801 	const int dirXshift[24] = {9, 15, 15, 13, 8, 1, 1, 3,  7, 13, 15, 15, 9, 3, 1, 1,  8, 14, 15, 14, 8, 2, 1, 2};
2802 	if (!tile)
2803 	{
2804 		tile = action.actor->getTile();
2805 	}
2806 
2807 	Position origin = tile->getPosition();
2808 	Tile *tileAbove = _save->getTile(origin + Position(0,0,1));
2809 	Position originVoxel = Position(origin.x*16, origin.y*16, origin.z*24);
2810 
2811 	// take into account soldier height and terrain level if the projectile is launched from a soldier
2812 	if (action.actor->getPosition() == origin || action.type != BA_LAUNCH)
2813 	{
2814 		// calculate offset of the starting point of the projectile
2815 		originVoxel.z += -tile->getTerrainLevel();
2816 
2817 		originVoxel.z += action.actor->getHeight() + action.actor->getFloatHeight();
2818 
2819 		if (action.type == BA_THROW)
2820 		{
2821 			originVoxel.z -= 3;
2822 		}
2823 		else
2824 		{
2825 			originVoxel.z -= 4;
2826 		}
2827 
2828 		if (originVoxel.z >= (origin.z + 1)*24)
2829 		{
2830 			if (tileAbove && tileAbove->hasNoFloor(0))
2831 			{
2832 				origin.z++;
2833 			}
2834 			else
2835 			{
2836 				while (originVoxel.z >= (origin.z + 1)*24)
2837 				{
2838 					originVoxel.z--;
2839 				}
2840 				originVoxel.z -= 4;
2841 			}
2842 		}
2843 		int offset = 0;
2844 		if (action.actor->getArmor()->getSize() > 1)
2845 		{
2846 			offset = 16;
2847 		}
2848 		else if(action.weapon == action.weapon->getOwner()->getItem("STR_LEFT_HAND") && !action.weapon->getRules()->isTwoHanded())
2849 		{
2850 			offset = 8;
2851 		}
2852 		int direction = getDirectionTo(origin, action.target);
2853 		originVoxel.x += dirXshift[direction+offset]*action.actor->getArmor()->getSize();
2854 		originVoxel.y += dirYshift[direction+offset]*action.actor->getArmor()->getSize();
2855 	}
2856 	else
2857 	{
2858 		// don't take into account soldier height and terrain level if the projectile is not launched from a soldier(from a waypoint)
2859 		originVoxel.x += 8;
2860 		originVoxel.y += 8;
2861 		originVoxel.z += 16;
2862 	}
2863 	return originVoxel;
2864 }
2865 
2866 /**
2867  * mark a region of the map as "dangerous" for a turn.
2868  * @param pos is the epicenter of the explosion.
2869  * @param radius how far to spread out.
2870  * @param unit the unit that is triggering this action.
2871  */
setDangerZone(Position pos,int radius,BattleUnit * unit)2872 void TileEngine::setDangerZone(Position pos, int radius, BattleUnit *unit)
2873 {
2874 	Tile *tile = _save->getTile(pos);
2875 	if (!tile)
2876 	{
2877 		return;
2878 	}
2879 	// set the epicenter as dangerous
2880 	tile->setDangerous();
2881 	Position originVoxel = (pos * Position(16,16,24)) + Position(8,8,12 + -tile->getTerrainLevel());
2882 	Position targetVoxel;
2883 	for (int x = -radius; x != radius; ++x)
2884 	{
2885 		for (int y = -radius; y != radius; ++y)
2886 		{
2887 			// we can skip the epicenter
2888 			if (x != 0 || y != 0)
2889 			{
2890 				// make sure we're within the radius
2891 				if ((x*x)+(y*y) <= (radius*radius))
2892 				{
2893 					tile = _save->getTile(pos + Position(x,y,0));
2894 					if (tile)
2895 					{
2896 						targetVoxel = ((pos + Position(x,y,0)) * Position(16,16,24)) + Position(8,8,12 + -tile->getTerrainLevel());
2897 						std::vector<Position> trajectory;
2898 						// we'll trace a line here, ignoring all units, to check if the explosion will reach this point
2899 						// granted this won't properly account for explosions tearing through walls, but then we can't really
2900 						// know that kind of information before the fact, so let's have the AI assume that the wall (or tree)
2901 						// is enough to protect them.
2902 						if (calculateLine(originVoxel, targetVoxel, false, &trajectory, unit, true, false, unit) == V_EMPTY)
2903 						{
2904 							if (trajectory.size() && (trajectory.back() / Position(16,16,24)) == pos + Position(x,y,0))
2905 							{
2906 								tile->setDangerous();
2907 							}
2908 						}
2909 					}
2910 				}
2911 			}
2912 		}
2913 	}
2914 }
2915 
2916 }
2917