1 ////////////////////////////////////////////////////////////////////////////////
2 //    Scorched3D (c) 2000-2011
3 //
4 //    This file is part of Scorched3D.
5 //
6 //    Scorched3D 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 2 of the License, or
9 //    (at your option) any later version.
10 //
11 //    Scorched3D 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 along
17 //    with this program; if not, write to the Free Software Foundation, Inc.,
18 //    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 ////////////////////////////////////////////////////////////////////////////////
20 
21 #include <actions/TanketMovement.h>
22 #include <actions/TargetFalling.h>
23 #include <actions/ShotProjectile.h>
24 #include <actions/CameraPositionAction.h>
25 #include <engine/ScorchedContext.h>
26 #include <engine/ActionController.h>
27 #include <weapons/WeaponMoveTank.h>
28 #include <weapons/AccessoryStore.h>
29 #include <landscapemap/LandscapeMaps.h>
30 #include <landscapedef/LandscapeDefn.h>
31 #include <landscapedef/LandscapeTex.h>
32 #include <landscapemap/MovementMap.h>
33 #include <landscapemap/DeformLandscape.h>
34 #ifndef S3D_SERVER
35 	#include <landscape/Smoke.h>
36 	#include <landscape/Landscape.h>
37 	#include <image/ImageStore.h>
38 	#include <GLEXT/GLImageModifier.h>
39 	#include <sound/Sound.h>
40 #endif
41 #include <target/TargetContainer.h>
42 #include <tank/Tank.h>
43 #include <tank/TankModelContainer.h>
44 #include <tanket/TanketAccessories.h>
45 #include <tank/TankViewPoints.h>
46 #include <tank/TankModelStore.h>
47 #include <target/TargetDamage.h>
48 #include <target/TargetLife.h>
49 #include <target/TargetState.h>
50 #include <target/TargetSpace.h>
51 #include <common/OptionsScorched.h>
52 #include <common/Defines.h>
53 
54 static const int NoMovementTransitions = 4;
55 
TanketMovement(WeaponFireContext & weaponContext,WeaponMoveTank * weapon,int positionX,int positionY)56 TanketMovement::TanketMovement(WeaponFireContext &weaponContext,
57 	WeaponMoveTank *weapon,
58 	int positionX, int positionY) :
59 	Action(weaponContext.getInternalContext().getReferenced()),
60 	weaponContext_(weaponContext),
61 	positionX_(positionX), positionY_(positionY),
62 	timePassed_(0), weapon_(weapon),
63 	remove_(false), moving_(true), moveSoundSource_(0),
64 	smokeCounter_(0.1f, 0.1f), stepCount_(0),
65 	vPoint_(0)
66 {
67 }
68 
~TanketMovement()69 TanketMovement::~TanketMovement()
70 {
71 #ifndef S3D_SERVER
72 	if (!context_->getServerMode())
73 	{
74 		delete moveSoundSource_;
75 		moveSoundSource_ = 0;
76 	}
77 #endif
78 	if (vPoint_) vPoint_->decrementReference();
79 }
80 
init()81 void TanketMovement::init()
82 {
83 	Tanket *tanket = context_->getTargetContainer().getTanketById(weaponContext_.getPlayerId());
84 	if (!tanket) return;
85 
86 	tanket->getTargetState().setMoving(this);
87 
88 	startPosition_ = tanket->getLife().getTargetPosition();
89 
90 	// Start the tank movement sound
91 #ifndef S3D_SERVER
92 	if (!context_->getServerMode() &&
93 		tanket->getType() == Target::TypeTank)
94 	{
95 		vPoint_ = new TankViewPointProvider();
96 		vPoint_->setValues(startPosition_);
97 		vPoint_->incrementReference();
98 
99 		CameraPositionAction *positionAction = new CameraPositionAction(
100 			weaponContext_.getPlayerId(),
101 			vPoint_,
102 			5,
103 			10,
104 			false);
105 		context_->getActionController().addAction(positionAction);
106 
107 		SoundBuffer *moveSound =
108 			Sound::instance()->fetchOrCreateBuffer(
109 				S3D::getModFile("data/wav/movement/tankmove.wav"));
110 		moveSoundSource_ = new VirtualSoundSource(VirtualSoundPriority::eAction, true, false);
111 		moveSoundSource_->setPosition(tanket->getLife().getTargetPosition().asVector());
112 		moveSoundSource_->play(moveSound);
113 	}
114 #endif // #ifndef S3D_SERVER
115 
116 	// As with everything to do with movement
117 	// The xy position is stored as an unsigned int
118 	// to save space, z is calculated from the landscape
119 	// Lower 32 bits = y position
120 	// Upper 32 bits = x positions
121 	std::list<unsigned int> positions;
122 	MovementMap mmap(
123 		tanket,
124 		*context_);
125 	FixedVector pos(positionX_, positionY_, 0);
126 	mmap.calculatePosition(pos, mmap.getFuel(weapon_));
127 
128 	MovementMap::MovementMapEntry entry =
129 		mmap.getEntry(positionX_, positionY_);
130 	if (entry.type == MovementMap::eMovement)
131 	{
132 		// Add the end (destination) point to the list of points for the tank
133 		// to visit
134 		unsigned int pt = (positionX_ << 16) | (positionY_ & 0xffff);
135 		positions.push_front(pt);
136 
137 		// Work backward to the source point and pre-pend them onto the
138 		// this of points
139 		while (entry.srcEntry)
140 		{
141 			pt = entry.srcEntry;
142 			unsigned int x = pt >> 16;
143 			unsigned int y = pt & 0xffff;
144 			positions.push_front(pt);
145 			entry = mmap.getEntry(x, y);
146 		}
147 	}
148 
149 	// Expand these positions into a interpolated set of positions with
150 	// x, y and z
151 	std::list<unsigned int>::iterator itor;
152 	for (itor = positions.begin();
153 		itor != positions.end();)
154 	{
155 		unsigned int fistpt = (*itor);
156 		++itor;
157 
158 		if (itor != positions.end())
159 		{
160 			unsigned int secpt = (*itor);
161 
162 			int firstx = int(fistpt >> 16);
163 			int firsty = int(fistpt & 0xffff);
164 			int secx = int(secpt >> 16);
165 			int secy = int(secpt & 0xffff);
166 			int diffX = secx - firstx;
167 			int diffY = secy - firsty;
168 			fixed ang = (atan2x(fixed(diffY), fixed(diffX)) / fixed::XPI * 180) - 90;
169 
170 			for (int i=0; i<NoMovementTransitions; i++)
171 			{
172 				fixed currentX = fixed(firstx) + fixed(diffX)/fixed(NoMovementTransitions)*fixed(i+1);
173 				fixed currentY = fixed(firsty) + fixed(diffY)/fixed(NoMovementTransitions)*fixed(i+1);
174 				expandedPositions_.push_back(
175 					PositionEntry(
176 						firstx, firsty,
177 						secx, secy,
178 						currentX, currentY,
179 						ang, (i==(NoMovementTransitions-1))));
180 			}
181 		}
182 	}
183 
184 	// If this weapon is set to use a constant amount of fuel then use this amount
185 	if (weapon_->getUseFuel() > 0)
186 	{
187 		tanket->getAccessories().rm(weapon_->getParent(), weapon_->getUseFuel());
188 	}
189 }
190 
getActionDetails()191 std::string TanketMovement::getActionDetails()
192 {
193 	return S3D::formatStringBuffer("%u %i,%i %s",
194 		weaponContext_.getPlayerId(), positionX_, positionY_,
195 		weapon_->getParent()->getName());
196 }
197 
simulate(fixed frameTime,bool & remove)198 void TanketMovement::simulate(fixed frameTime, bool &remove)
199 {
200 	if (!remove_)
201 	{
202 		if (moving_)
203 		{
204 			simulationMove(frameTime);
205 		}
206 	}
207 	else
208 	{
209 		remove = true;
210 	}
211 
212 #ifndef S3D_SERVER
213 	if (remove && moveSoundSource_)
214 	{
215 		moveSoundSource_->stop();
216 	}
217 #endif // #ifndef S3D_SERVER
218 
219 	Action::simulate(frameTime, remove);
220 }
221 
simulationMove(fixed frameTime)222 void TanketMovement::simulationMove(fixed frameTime)
223 {
224 	Tanket *tanket =
225 		context_->getTargetContainer().getTanketById(weaponContext_.getPlayerId());
226 	if (tanket)
227 	{
228 		// Stop moving if the tank is dead
229 		if (tanket->getAlive())
230 		{
231 			// Check to see if this tank is falling
232 			// If it is then we wait until the fall is over
233 			if (!tanket->getTargetState().getFalling() &&
234 				tanket->getTargetState().getMoving() == this)
235 			{
236 				// Add a smoke trail
237 				// Check if we are not on the server
238 #ifndef S3D_SERVER
239 				if (!context_->getServerMode() &&
240 					tanket->getType() == Target::TypeTank)
241 				{
242 					Tank *tank = (Tank *) tanket;
243 
244 					// Check if this tank type allows smoke trails
245 					TankModel *model = tank->getModelContainer().getTankModel();
246 					if (model && model->getMovementSmoke())
247 					{
248 						if (smokeCounter_.nextDraw(frameTime.asFloat()))
249 						{
250 							Landscape::instance()->getSmoke().addSmoke(
251 								tanket->getLife().getFloatPosition()[0],
252 								tanket->getLife().getFloatPosition()[1],
253 								tanket->getLife().getFloatPosition()[2]);
254 						}
255 					}
256 				}
257 #endif // S3D_SERVER
258 
259 				// Move the tank one position every stepTime seconds
260 				// i.e. 1/stepTime positions a second
261 				timePassed_ += frameTime;
262 				fixed stepTime = weapon_->getStepTime();
263 				while (timePassed_ >= stepTime)
264 				{
265 					timePassed_ -= stepTime;
266 					if (!expandedPositions_.empty())
267 					{
268 						moveTanket(tanket);
269 					}
270 					else break;
271 				}
272 
273 				if (expandedPositions_.empty()) moving_ = false;
274 			}
275 		}
276 		else moving_ = false;
277 	}
278 	else moving_ = false;
279 
280 	if (moving_ == false)
281 	{
282 		if (tanket)
283 		{
284 			if (tanket->getTargetState().getMoving() == this)
285 			{
286 				tanket->getTargetState().setMoving(0);
287 			}
288 			tanket->getLife().setRotation(0);
289 			if (tanket->getAlive())
290 			{
291 				// Move the tank to the final position
292 				DeformLandscape::flattenArea(*context_, tanket->getLife().getTargetPosition());
293 			}
294 		}
295 
296 		remove_ = true;
297 	}
298 }
299 
moveTanket(Tanket * tanket)300 void TanketMovement::moveTanket(Tanket *tanket)
301 {
302 	fixed x = expandedPositions_.front().x;
303 	fixed y = expandedPositions_.front().y;
304 	fixed a = expandedPositions_.front().ang;
305 	bool useF = expandedPositions_.front().useFuel;
306 
307 	int firstx = expandedPositions_.front().firstX;
308 	int firsty = expandedPositions_.front().firstY;
309 	fixed firstz = context_->getLandscapeMaps().getGroundMaps().getHeight(firstx, firsty);
310 
311 	int secondx = expandedPositions_.front().secondX;
312 	int secondy = expandedPositions_.front().secondY;
313 	fixed secondz = context_->getLandscapeMaps().getGroundMaps().getHeight(secondx, secondy);
314 	fixed z = context_->getLandscapeMaps().getGroundMaps().getInterpHeight(x, y);
315 	expandedPositions_.pop_front();
316 
317 	// Form the new tank position
318 	FixedVector newPos(x, y, z);
319 
320 	// Check we are not trying to climb to high (this may be due
321 	// to the landscape changing after we started move)
322 	if (secondz - firstz > context_->getOptionsGame().getMaxClimbingDistance())
323 	{
324 		expandedPositions_.clear();
325 		return;
326 	}
327 
328 	// Check to see we are not moving into water with a movement restriction
329 	// in place
330 	if (context_->getOptionsGame().getMovementRestriction() ==
331 		OptionsGame::MovementRestrictionLand ||
332 		context_->getOptionsGame().getMovementRestriction() ==
333 		OptionsGame::MovementRestrictionLandOrAbove)
334 	{
335 		fixed waterHeight = -10;
336 		LandscapeTex &tex = *context_->getLandscapeMaps().getDefinitions().getTex();
337 		if (tex.border->getType() == LandscapeTexType::eWater)
338 		{
339 			LandscapeTexBorderWater *water =
340 				(LandscapeTexBorderWater *) tex.border;
341 			waterHeight = water->height;
342 		}
343 
344 		if (context_->getOptionsGame().getMovementRestriction() ==
345 			OptionsGame::MovementRestrictionLandOrAbove)
346 		{
347 			if (waterHeight > startPosition_[2] - fixed(true, 1000))
348 			{
349 				waterHeight = startPosition_[2] - fixed(true, 1000);
350 			}
351 		}
352 
353 		if (secondz < waterHeight)
354 		{
355 			expandedPositions_.clear();
356 			return;
357 		}
358 	}
359 
360 	// Check this new position is allowed
361 	if (!MovementMap::allowedPosition(*context_, tanket, newPos))
362 	{
363 		expandedPositions_.clear();
364 		return;
365 	}
366 
367 	// Move the tank to this new position
368 	// Use up one unit of fuel
369 	// -1 means use 1 unit of fuel per movement square
370 	if (useF && (weapon_->getUseFuel() == -1))
371 	{
372 		tanket->getAccessories().rm(weapon_->getParent(), 1);
373 	}
374 
375 	// Actually move the tank
376 	tanket->getLife().setRotation(a);
377 	tanket->getLife().setTargetPosition(newPos);
378 
379 	// Remove the targets that this tank "drives over"
380 	std::map<unsigned int, Target *> collisionTargets;
381 	context_->getTargetSpace().getCollisionSet(
382 		tanket->getLife().getTargetPosition(), 3, collisionTargets, false);
383 	std::map<unsigned int, Target *>::iterator itor;
384 	for (itor = collisionTargets.begin();
385 		itor != collisionTargets.end();
386 		++itor)
387 	{
388 		// Check that this is a target we have driven over
389 		// and we can destroy it
390 		Target *target = (*itor).second;
391 		if (target->getType() != Target::TypeTank &&
392 			target->getTargetState().getDriveOverToDestroy())
393 		{
394 			// Kill the target we've driven over
395 			TargetDamage::damageTarget(*context_,
396 				weapon_, target->getPlayerId(), weaponContext_,
397 				target->getLife().getLife(),
398 				false, false, false);
399 
400 			// Do a small explosion where we remove this target
401 			Accessory *accessory =
402 				context_->getAccessoryStore().findByPrimaryAccessoryName("DriveOverDestroy");
403 			if (accessory && accessory->getType() == AccessoryPart::AccessoryWeapon)
404 			{
405 				Weapon *weapon = (Weapon *) accessory->getAction();
406 				weapon->fire(*context_,
407 					weaponContext_,
408 					tanket->getLife().getTargetPosition(),
409 					FixedVector::getNullVector());
410 			}
411 		}
412 	}
413 
414 	// Add tracks
415 #ifndef S3D_SERVER
416 	if (!context_->getServerMode() &&
417 		tanket->getType() == Target::TypeTank)
418 	{
419 		Tank *tank = (Tank *) tanket;
420 		stepCount_++;
421 		if (stepCount_ % 5 == 0)
422 		{
423 			TankModel *model = tank->getModelContainer().getTankModel();
424 			if (model)
425 			{
426 				Image image;
427 				if (firstx == secondx)
428 				{
429 					image = ImageStore::instance()->
430 						loadImage(model->getTracksVId());
431 				}
432 				else if (firsty == secondy)
433 				{
434 					image = ImageStore::instance()->
435 						loadImage(model->getTracksHId());
436 				}
437 				else if (firsty - secondy == firstx - secondx)
438 				{
439 					image = ImageStore::instance()->
440 						loadImage(model->getTracksVHId());
441 				}
442 				else
443 				{
444 					image = ImageStore::instance()->
445 						loadImage(model->getTracksHVId());
446 				}
447 
448 				ImageModifier::addBitmapToLandscape(
449 					*context_,
450 					image,
451 					newPos[0].asFloat(),
452 					newPos[1].asFloat(),
453 					0.04f, 0.04f,
454 					true);
455 			}
456 		}
457 
458 		if (vPoint_) vPoint_->setValues(newPos);
459 	}
460 
461 	if (moveSoundSource_) moveSoundSource_->setPosition(newPos.asVector());
462 
463 #endif // #ifndef S3D_SERVER
464 }
465