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