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