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 <target/TargetDamage.h>
22 #include <actions/TargetFalling.h>
23 #include <actions/TankSay.h>
24 #include <actions/CameraPositionAction.h>
25 #include <actions/Resurrection.h>
26 #ifndef S3D_SERVER
27 #include <sprites/TextActionRenderer.h>
28 #endif
29 #include <common/OptionsScorched.h>
30 #include <common/Defines.h>
31 #include <common/ChannelManager.h>
32 #include <common/StatsLogger.h>
33 #include <weapons/AccessoryStore.h>
34 #include <weapons/Shield.h>
35 #include <landscapemap/LandscapeMaps.h>
36 #include <engine/ScorchedContext.h>
37 #include <engine/ActionController.h>
38 #include <engine/Simulator.h>
39 #include <target/TargetContainer.h>
40 #include <tank/TankTeamScore.h>
41 #include <tank/TankScore.h>
42 #include <tank/TankState.h>
43 #include <tankai/TankAI.h>
44 #include <tanket/TanketShotInfo.h>
45 #include <target/TargetShield.h>
46 #include <target/TargetLife.h>
47 #include <target/TargetParachute.h>
48 #include <target/TargetState.h>
49 #include <tankai/TankAIStrings.h>
50 #include <lang/LangResource.h>
51
damageTarget(ScorchedContext & context,Weapon * weapon,unsigned int damagedPlayerId,WeaponFireContext & weaponContext,fixed damage,bool useShieldDamage,bool checkFall,bool shieldOnlyDamage)52 void TargetDamage::damageTarget(ScorchedContext &context,
53 Weapon *weapon,
54 unsigned int damagedPlayerId, WeaponFireContext &weaponContext,
55 fixed damage, bool useShieldDamage, bool checkFall,
56 bool shieldOnlyDamage)
57 {
58 if (context.getOptionsGame().getActionSyncCheck())
59 {
60 context.getSimulator().addSyncCheck(
61 S3D::formatStringBuffer("%u %s %s",
62 damagedPlayerId, damage.asQuickString(), weapon->getParent()->getName()));
63 }
64
65 if (!context.getServerMode())
66 {
67 Target *damagedTarget =
68 context.getTargetContainer().getTargetById(damagedPlayerId);
69 if (damagedTarget && damagedTarget->getType() == Target::TypeTank)
70 {
71 TankViewPointProvider *vPoint = new TankViewPointProvider();
72 vPoint->setValues(damagedTarget->getLife().getTargetPosition());
73 CameraPositionAction *pos = new CameraPositionAction(
74 weaponContext.getPlayerId(),
75 vPoint,
76 4,
77 15,
78 false);
79 context.getActionController().addAction(pos);
80 }
81 }
82
83 unsigned int firedPlayerId = weaponContext.getPlayerId();
84
85 // Find the tank that has been damaged
86 Target *damagedTarget =
87 context.getTargetContainer().getTargetById(damagedPlayerId);
88 if (!damagedTarget || !damagedTarget->getAlive()) return;
89
90 // Tell this tanks ai that is has been hurt by another tank
91 if (damagedTarget->getType() != Target::TypeTarget)
92 {
93 // Tell all AIs about this collision
94 std::map<unsigned int, Tanket *> tankets =
95 context.getTargetContainer().getTankets();
96 std::map<unsigned int, Tanket *>::iterator itor;
97 for (itor = tankets.begin();
98 itor != tankets.end();
99 ++itor)
100 {
101 Tanket *tanket = (*itor).second;
102 TankAI *ai = tanket->getTankAI();
103 if (ai)
104 {
105 if (tanket->getAlive())
106 {
107 ai->tankHurt(weapon, damage.asFloat(),
108 damagedTarget->getPlayerId(),
109 firedPlayerId);
110 }
111 }
112 }
113 }
114
115 // Add the collision action
116 addDamageAction(context, weaponContext, damagedTarget, damagedTarget->getCollisionAction());
117
118 // Remove any damage from shield first
119 if (damage > 0)
120 {
121 fixed shieldDamage = 0;
122 Accessory *sh = damagedTarget->getShield().getCurrentShield();
123 if (sh && useShieldDamage)
124 {
125 Shield *shield = (Shield *) sh->getAction();
126 fixed shieldPowerRequired =
127 damage * shield->getHitPenetration();
128 fixed shieldPower =
129 damagedTarget->getShield().getShieldPower();
130 if (shieldPower > shieldPowerRequired)
131 {
132 shieldPower -= shieldPowerRequired;
133 damage = 0;
134 }
135 else
136 {
137 fixed p = shieldPower / shield->getHitPenetration();
138 shieldPower = 0;
139 damage -= p;
140 }
141
142 damagedTarget->getShield().setShieldPower(shieldPower);
143 }
144 }
145
146 // Remove the remaining damage from the tank
147 if (damage > 0 && !shieldOnlyDamage)
148 {
149 #ifndef S3D_SERVER
150 if (!context.getServerMode() &&
151 damagedTarget->getTargetState().getDisplayDamage())
152 {
153 Vector position = damagedTarget->getLife().getFloatPosition();
154 position[0] += RAND * 5.0f - 2.5f;
155 position[1] += RAND * 5.0f - 2.5f;
156 position[2] += RAND * 5.0f - 2.5f;
157
158 Vector redColor(0.75f, 0.0f, 0.0f);
159 context.getActionController().addAction(
160 new SpriteAction(
161 new TextActionRenderer(
162 position,
163 redColor,
164 S3D::formatStringBuffer("%.0f", damage.asFloat()))));
165 }
166 #endif // #ifndef S3D_SERVER
167
168 // Remove the life
169 if (damage > damagedTarget->getLife().getLife()) damage =
170 damagedTarget->getLife().getLife();
171 damagedTarget->getLife().setLife(damagedTarget->getLife().getLife() - damage);
172 if (context.getOptionsGame().getLimitPowerByHealth() &&
173 damagedTarget->getType() != Target::TypeTarget)
174 {
175 Tanket *damagedTank = (Tanket *) damagedTarget;
176 damagedTank->getShotInfo().changePower(0, true);
177 }
178
179 if (context.getOptionsGame().getActionSyncCheck())
180 {
181 context.getSimulator().addSyncCheck(
182 S3D::formatStringBuffer("TargetDamage: %u %u %s",
183 damagedTarget->getPlayerId(),
184 weaponContext.getPlayerId(),
185 damagedTarget->getLife().getLife().asQuickString()));
186 }
187
188 // Check if the tank is dead
189 bool killedTank = (damagedTarget->getLife().getLife() == 0);
190
191 // Add any score got from this endevour
192 // Should always be a tank that has fired
193 Tank *firedTank =
194 context.getTargetContainer().getTankById(firedPlayerId);
195 if (firedTank && damagedTarget->getType() == Target::TypeTank)
196 {
197 Tank *damagedTank = (Tank *) damagedTarget;
198
199 // Add this tank as a tank that assisted in the kill
200 damagedTank->getScore().getHurtBy().insert(firedTank->getPlayerId());
201
202 // Calculate team kills
203 bool selfKill = (damagedPlayerId == firedPlayerId);
204 bool teamKill = ((context.getOptionsGame().getTeams() > 1) &&
205 (firedTank->getTeam() == damagedTank->getTeam()));
206
207 if (!killedTank)
208 {
209 // Calculate money won for not killing this tank
210 int moneyPerHit =
211 context.getOptionsGame().getMoneyWonPerHitPoint() *
212 weapon->getArmsLevel();
213 if (context.getOptionsGame().getMoneyPerHealthPoint())
214 moneyPerHit = (moneyPerHit * damage.asInt()) / 100;
215 if (selfKill || teamKill) moneyPerHit *= -1;
216
217 firedTank->getScore().setMoney(
218 firedTank->getScore().getMoney() + moneyPerHit);
219 }
220 else
221 {
222 int moneyPerKill =
223 context.getOptionsGame().getMoneyWonPerKillPoint() *
224 weapon->getArmsLevel();
225 if (!selfKill && !teamKill)
226 {
227 // Note this is done before turn kills is updated
228 // so for the first kill turn kills will be 0
229 // i.e. no multikill bonus for 1st kill
230 moneyPerKill +=
231 context.getOptionsGame().getMoneyWonPerMultiKillPoint() *
232 weapon->getArmsLevel() *
233 weaponContext.getInternalContext().getKillCount();
234 }
235 if (context.getOptionsGame().getMoneyPerHealthPoint())
236 moneyPerKill = (moneyPerKill * damage.asInt()) / 100;
237 int scorePerKill = context.getOptionsGame().getScorePerKill();
238
239 int moneyPerAssist =
240 context.getOptionsGame().getMoneyWonPerAssistPoint() *
241 weapon->getArmsLevel();
242 int scorePerAssist = context.getOptionsGame().getScorePerAssist();
243
244 // Update kills and score
245 if (selfKill || teamKill)
246 {
247 firedTank->getScore().setKills(
248 firedTank->getScore().getKills() - 1);
249 firedTank->getScore().setMoney(
250 firedTank->getScore().getMoney() - moneyPerKill);
251 firedTank->getScore().setScore(
252 firedTank->getScore().getScore() - scorePerKill);
253
254 if (firedTank->getTeam() > 0)
255 {
256 context.getTankTeamScore().addScore(
257 -scorePerKill, firedTank->getTeam());
258 }
259 }
260 else
261 {
262 firedTank->getScore().setKills(
263 firedTank->getScore().getKills() + 1);
264 firedTank->getScore().setMoney(
265 firedTank->getScore().getMoney() + moneyPerKill);
266 firedTank->getScore().setScore(
267 firedTank->getScore().getScore() + scorePerKill);
268
269 weaponContext.getInternalContext().setKillCount(
270 weaponContext.getInternalContext().getKillCount() + 1);
271
272 if (firedTank->getTeam() > 0)
273 {
274 context.getTankTeamScore().addScore(
275 scorePerKill, firedTank->getTeam());
276 }
277 }
278
279 // Update assists
280 std::set<unsigned int> &hurtBy =
281 damagedTank->getScore().getHurtBy();
282 std::set<unsigned int>::iterator itor;
283 for (itor = hurtBy.begin();
284 itor != hurtBy.end();
285 ++itor)
286 {
287 unsigned int hurtByPlayer = (*itor);
288 Tank *hurtByTank =
289 context.getTargetContainer().getTankById(hurtByPlayer);
290 if (!hurtByTank) continue;
291
292 // Only score when the tank does not hurt itself
293 if (hurtByTank == damagedTank) continue;
294
295 // You don't get an assist for your kill
296 if (hurtByTank == firedTank) continue;
297
298 // or a team member
299 if ((context.getOptionsGame().getTeams() > 1) &&
300 (hurtByTank->getTeam() == damagedTank->getTeam())) continue;
301
302 // Update assist score
303 hurtByTank->getScore().setAssists(
304 hurtByTank->getScore().getAssists() + 1);
305 hurtByTank->getScore().setMoney(
306 hurtByTank->getScore().getMoney() + moneyPerAssist);
307 hurtByTank->getScore().setScore(
308 hurtByTank->getScore().getScore() + scorePerAssist);
309
310 if (hurtByTank->getTeam() > 0)
311 {
312 context.getTankTeamScore().addScore(
313 scorePerAssist, hurtByTank->getTeam());
314 }
315 }
316 }
317 }
318
319 if (killedTank)
320 {
321 // The tank has died, make it blow up etc.
322 calculateDeath(context, weaponContext, weapon, damagedPlayerId);
323
324 if (damagedTarget->getType() == Target::TypeTank)
325 {
326 // The tank is now dead
327 Tank *damagedTank = (Tank *) damagedTarget;
328 damagedTank->getState().setState(TankState::sDead);
329
330 // This tank has lost a life
331 if (damagedTank->getState().getMaxLives() > 0)
332 {
333 damagedTank->getState().setLives(
334 damagedTank->getState().getLives() - 1);
335 }
336 Resurrection::checkResurection(&context, damagedTank);
337 }
338 }
339 }
340
341 // Check if the tank needs to fall
342 if (checkFall && damagedTarget->getAlive())
343 {
344 // The tank is not dead check if it needs to fall
345 FixedVector &position = damagedTarget->getLife().getTargetPosition();
346 if (context.getLandscapeMaps().getGroundMaps().
347 getInterpHeight(position[0], position[1]) < position[2])
348 {
349 // Check this tank is not already falling
350 if (!damagedTarget->getTargetState().getFalling())
351 {
352 Parachute *parachute = 0;
353 Accessory *paraAccessory =
354 damagedTarget->getParachute().getCurrentParachute();
355 if (paraAccessory)
356 {
357 parachute = (Parachute *) paraAccessory->getAction();
358 }
359
360 // Tank falling
361 context.getActionController().addAction(
362 new TargetFalling(weapon, damagedPlayerId, weaponContext, parachute));
363 }
364 }
365 }
366
367 // DO LAST
368 // If the tank is a target, remove the target
369 if (!damagedTarget->getAlive() &&
370 damagedTarget->getType() != Target::TypeTank)
371 {
372 Target *removedTarget =
373 context.getTargetContainer().
374 removeTarget(damagedTarget->getPlayerId());
375 if (context.getOptionsGame().getActionSyncCheck())
376 {
377 context.getSimulator().addSyncCheck(
378 S3D::formatStringBuffer("RemoveTarget : %u %s",
379 removedTarget->getPlayerId(),
380 removedTarget->getCStrName().c_str()));
381 }
382
383 delete removedTarget;
384 }
385 }
386
calculateDeath(ScorchedContext & context,WeaponFireContext & weaponContext,Weapon * weapon,unsigned int damagedPlayerId)387 void TargetDamage::calculateDeath(ScorchedContext &context, WeaponFireContext &weaponContext,
388 Weapon *weapon, unsigned int damagedPlayerId)
389 {
390 Target *killedTarget =
391 context.getTargetContainer().getTargetById(damagedPlayerId);
392 if (!killedTarget) return;
393
394 // Log the death
395 logDeath(context, weaponContext, weapon, damagedPlayerId);
396
397 // Add the tank death explosion
398 // Make the tank explode in one of many ways
399 addDamageAction(context, weaponContext, killedTarget, killedTarget->getDeathAction());
400 }
401
addDamageAction(ScorchedContext & context,WeaponFireContext & originalWeaponContext,Target * target,Weapon * weapon)402 void TargetDamage::addDamageAction(ScorchedContext &context, WeaponFireContext &originalWeaponContext,
403 Target *target, Weapon *weapon)
404 {
405 if (weapon)
406 {
407 if (context.getOptionsGame().getActionSyncCheck())
408 {
409 context.getSimulator().addSyncCheck(
410 S3D::formatStringBuffer("DeathAction: %s",
411 weapon->getParent()->getName()));
412 }
413
414 FixedVector position = target->getLife().getTargetPosition();
415 FixedVector velocity;
416 WeaponFireContext newWeaponContext(originalWeaponContext.getPlayerId(),
417 originalWeaponContext.getInternalContext().getSelectPositionX(),
418 originalWeaponContext.getInternalContext().getSelectPositionY(),
419 originalWeaponContext.getInternalContext().getVelocityVector(),
420 originalWeaponContext.getInternalContext().getReferenced(),
421 false);
422 weapon->fire(context, newWeaponContext, position, velocity);
423 StatsLogger::instance()->weaponFired(weapon, true);
424 }
425 }
426
logDeath(ScorchedContext & context,WeaponFireContext & weaponContext,Weapon * weapon,unsigned int damagedPlayerId)427 void TargetDamage::logDeath(ScorchedContext &context, WeaponFireContext &weaponContext,
428 Weapon *weapon, unsigned int damagedPlayerId)
429 {
430 unsigned int firedPlayerId = weaponContext.getPlayerId();
431
432 Target *killedTarget =
433 context.getTargetContainer().getTargetById(damagedPlayerId);
434 if (killedTarget->getType() != Target::TypeTank) return;
435
436 Tank *killedTank = (Tank *) killedTarget;
437
438 if (killedTank->getDestinationId() == 0)
439 {
440 const char *line = context.getTankAIStrings().getDeathLine(context);
441 if (line)
442 {
443 context.getActionController().addAction(
444 new TankSay(killedTank->getPlayerId(),
445 LANG_STRING(line)));
446 }
447 }
448
449 Tank *firedTank = 0;
450 if (firedPlayerId != 0) firedTank = context.getTargetContainer().getTankById(firedPlayerId);
451 else
452 {
453 Vector white(1.0f, 1.0f, 1.0f);
454 static Tank envTank(context, 0, 0,
455 LANG_STRING("Environment"),
456 white);
457 envTank.setUniqueId("Environment");
458 firedTank = &envTank;
459 }
460
461 if (firedTank)
462 {
463 if (damagedPlayerId == firedPlayerId)
464 {
465 int skillChange = context.getOptionsGame().getSkillForSelfKill();
466 fixed weight = (fixed(weapon->getArmsLevel()) / 10) + 1;
467 skillChange = (fixed(skillChange) * weight).asInt();
468
469 firedTank->getScore().setSkill(firedTank->getScore().getSkill() + skillChange);
470
471 StatsLogger::instance()->
472 tankSelfKilled(firedTank, weapon);
473 StatsLogger::instance()->weaponKilled(weapon, !weaponContext.getInternalContext().getUpdateStats());
474 {
475 ChannelText text("combat",
476 LANG_RESOURCE_3(
477 "TANK_KILLED_SELF",
478 "[p:{0}] killed self with a [w:{1}] ({2} skill)",
479 firedTank->getTargetName(),
480 weapon->getParent()->getName(),
481 S3D::formatStringBuffer("%i", skillChange)));
482 ChannelManager::showText(context, text);
483 }
484 }
485 else if ((context.getOptionsGame().getTeams() > 1) &&
486 (firedTank->getTeam() == killedTank->getTeam()))
487 {
488 int skillChange = context.getOptionsGame().getSkillForTeamKill();
489 fixed weight = (fixed(weapon->getArmsLevel()) / 10) + 1;
490 skillChange = (fixed(skillChange) * weight).asInt();
491
492 firedTank->getScore().setSkill(firedTank->getScore().getSkill() + skillChange);
493
494 StatsLogger::instance()->
495 tankTeamKilled(firedTank, killedTank, weapon);
496 StatsLogger::instance()->weaponKilled(weapon, !weaponContext.getInternalContext().getUpdateStats());
497 {
498 ChannelText text("combat",
499 LANG_RESOURCE_4(
500 "TANK_KILLED_TEAM",
501 "[p:{0}] team killed [p:{1}] with a [w:{2}] ({3} skill)",
502 firedTank->getTargetName(),
503 killedTank->getTargetName(),
504 weapon->getParent()->getName(),
505 S3D::formatStringBuffer("%i", skillChange)));
506 ChannelManager::showText(context, text);
507 }
508 }
509 else
510 {
511 int kbonus = 0; // Killer
512 int vbonus = 0; // Victim
513 if (firedTank->getPlayerId() != 0 && killedTank->getPlayerId() != 0)
514 {
515 int kskill = firedTank->getScore().getSkill();
516 int vskill = killedTank->getScore().getSkill();
517
518 if (kskill > vskill)
519 {
520 // killer is better than the victim
521 kbonus = ((kskill + vskill)*(kskill + vskill)) / (kskill*kskill);
522 vbonus = kbonus * vskill / (vskill + kskill);
523 }
524 else
525 {
526 // the victim is better than the killer
527 kbonus = ((vskill + kskill)*(vskill + kskill)) / (vskill*vskill) * vskill / kskill;
528 vbonus = kbonus * (vskill + 1000) / (vskill + kskill);
529 }
530
531 if (vbonus > context.getOptionsGame().getMaxSkillLost())
532 {
533 vbonus = context.getOptionsGame().getMaxSkillLost();
534 }
535 if (kbonus > context.getOptionsGame().getMaxSkillGained())
536 {
537 kbonus = context.getOptionsGame().getMaxSkillGained();
538 }
539
540 //$vbonus = $vskill if $vbonus > $vskill;
541 //$kbonus = $kskill if $kbonus > $kskill;
542 fixed weight = (fixed(weapon->getArmsLevel()) / 10) + 5;
543 kbonus = (fixed(kbonus) * weight).asInt();
544 vbonus = (fixed(vbonus) * weight).asInt();
545 }
546 firedTank->getScore().setSkill(firedTank->getScore().getSkill() + kbonus);
547 killedTank->getScore().setSkill(killedTank->getScore().getSkill() - vbonus);
548
549 StatsLogger::instance()->
550 tankKilled(firedTank, killedTank, weapon);
551 StatsLogger::instance()->weaponKilled(weapon, !weaponContext.getInternalContext().getUpdateStats());
552 {
553 if (weaponContext.getInternalContext().getKillCount() > 1)
554 {
555 ChannelText text("combat",
556 LANG_RESOURCE_5(
557 "TANK_KILLED_MULTIOTHER",
558 "[p:{0}] multi-killed [p:{1}] with a [w:{2}] ({3}, {4} skill)",
559 firedTank->getTargetName(),
560 killedTank->getTargetName(),
561 weapon->getParent()->getName(),
562 S3D::formatStringBuffer("%i", kbonus),
563 S3D::formatStringBuffer("%i", -vbonus)));
564 ChannelManager::showText(context, text);
565 }
566 else
567 {
568 ChannelText text("combat",
569 LANG_RESOURCE_5(
570 "TANK_KILLED_OTHER",
571 "[p:{0}] killed [p:{1}] with a [w:{2}] ({3}, {4} skill)",
572 firedTank->getTargetName(),
573 killedTank->getTargetName(),
574 weapon->getParent()->getName(),
575 S3D::formatStringBuffer("%i", kbonus),
576 S3D::formatStringBuffer("%i", -vbonus)));
577 ChannelManager::showText(context, text);
578 }
579 }
580 }
581 }
582 }
583