1 // SuperTuxKart - a fun racing game with go-kart
2 // Copyright (C) 2018 SuperTuxKart-Team
3 //
4 // This program is free software; you can redistribute it and/or
5 // modify it under the terms of the GNU General Public License
6 // as published by the Free Software Foundation; either version 3
7 // of the License, or (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with this program; if not, write to the Free Software
16 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17
18 #include "modes/capture_the_flag.hpp"
19 #include "audio/sfx_base.hpp"
20 #include "io/file_manager.hpp"
21 #include "items/powerup.hpp"
22 #include "graphics/irr_driver.hpp"
23 #include "guiengine/engine.hpp"
24 #include "karts/abstract_kart.hpp"
25 #include "karts/controller/controller.hpp"
26 #include "karts/kart_model.hpp"
27 #include "modes/ctf_flag.hpp"
28 #include "network/network_config.hpp"
29 #include "network/network_string.hpp"
30 #include "network/protocols/game_events_protocol.hpp"
31 #include "network/server_config.hpp"
32 #include "network/stk_host.hpp"
33 #include "physics/triangle_mesh.hpp"
34 #include "states_screens/race_gui.hpp"
35 #include "tracks/track.hpp"
36 #include "tracks/track_object_manager.hpp"
37 #include "utils/string_utils.hpp"
38 #include "utils/translation.hpp"
39
40 #include <algorithm>
41
42 const float g_capture_length = 2.0f;
43 const int g_captured_score = 10;
44
45 // ----------------------------------------------------------------------------
46 /** Constructor. Sets up the clock mode etc.
47 */
CaptureTheFlag()48 CaptureTheFlag::CaptureTheFlag() : FreeForAll()
49 {
50 m_red_flag_node = m_blue_flag_node = NULL;
51 m_red_flag_indicator = m_blue_flag_indicator = NULL;
52 m_red_flag_mesh = m_blue_flag_mesh = NULL;
53 m_scored_sound = NULL;
54 #ifndef SERVER_ONLY
55 if (GUIEngine::isNoGraphics())
56 return;
57
58 file_manager->pushTextureSearchPath(
59 file_manager->getAsset(FileManager::MODEL,""), "models");
60 m_red_flag_mesh = irr_driver->getAnimatedMesh
61 (file_manager->getAsset(FileManager::MODEL, "red_flag.spm"));
62 m_blue_flag_mesh = irr_driver->getAnimatedMesh
63 (file_manager->getAsset(FileManager::MODEL, "blue_flag.spm"));
64 assert(m_red_flag_mesh);
65 assert(m_blue_flag_mesh);
66 irr_driver->grabAllTextures(m_red_flag_mesh);
67 irr_driver->grabAllTextures(m_blue_flag_mesh);
68 file_manager->popTextureSearchPath();
69 m_scored_sound = SFXManager::get()->createSoundSource("goal_scored");
70 #endif
71 } // CaptureTheFlag
72
73 // ----------------------------------------------------------------------------
~CaptureTheFlag()74 CaptureTheFlag::~CaptureTheFlag()
75 {
76 #ifndef SERVER_ONLY
77 if (GUIEngine::isNoGraphics())
78 return;
79
80 m_red_flag_node->drop();
81 m_blue_flag_node->drop();
82 irr_driver->dropAllTextures(m_red_flag_mesh);
83 irr_driver->dropAllTextures(m_blue_flag_mesh);
84 irr_driver->removeMeshFromCache(m_red_flag_mesh);
85 irr_driver->removeMeshFromCache(m_blue_flag_mesh);
86 m_scored_sound->deleteSFX();
87 #endif
88 } // ~CaptureTheFlag
89
90 // ----------------------------------------------------------------------------
init()91 void CaptureTheFlag::init()
92 {
93 FreeForAll::init();
94 const btTransform& orig_red = Track::getCurrentTrack()->getRedFlag();
95 const btTransform& orig_blue = Track::getCurrentTrack()->getBlueFlag();
96
97 m_red_flag = std::make_shared<CTFFlag>(FC_RED, orig_red);
98 m_blue_flag = std::make_shared<CTFFlag>(FC_BLUE, orig_blue);
99 if (NetworkConfig::get()->isNetworking())
100 {
101 m_red_flag->rewinderAdd();
102 m_blue_flag->rewinderAdd();
103 }
104
105 #ifndef SERVER_ONLY
106 if (GUIEngine::isNoGraphics())
107 return;
108
109 m_red_flag_node = irr_driver->addAnimatedMesh(m_red_flag_mesh, "red_flag");
110 m_blue_flag_node = irr_driver->addAnimatedMesh(m_blue_flag_mesh,
111 "blue_flag");
112 assert(m_red_flag_node);
113 assert(m_blue_flag_node);
114 m_red_flag_node->grab();
115 m_blue_flag_node->grab();
116
117 std::string red_path =
118 file_manager->getAsset(FileManager::GUI_ICON, "red_arrow.png");
119 std::string blue_path =
120 file_manager->getAsset(FileManager::GUI_ICON, "blue_arrow.png");
121
122 m_red_flag_indicator = irr_driver->addBillboard(
123 core::dimension2df(1.5f, 1.5f), red_path, NULL);
124 m_red_flag_indicator->setPosition(Vec3(
125 orig_red(Vec3(0.0f, 2.5f, 0.0f))).toIrrVector());
126 m_blue_flag_indicator = irr_driver->addBillboard(
127 core::dimension2df(1.5f, 1.5f), blue_path, NULL);
128 m_blue_flag_indicator->setPosition(Vec3(
129 orig_blue(Vec3(0.0f, 2.5f, 0.0f))).toIrrVector());
130
131 m_red_flag->initFlagRenderInfo(m_red_flag_node);
132 m_blue_flag->initFlagRenderInfo(m_blue_flag_node);
133 #endif
134 } // init
135
136 // ----------------------------------------------------------------------------
reset(bool restart)137 void CaptureTheFlag::reset(bool restart)
138 {
139 // 5 bits for kart id (with -1 and -2 flag status)
140 if (m_karts.size() > 29)
141 Log::fatal("CaptureTheFlag", "Too many karts");
142
143 FreeForAll::reset(restart);
144 m_red_scores = m_blue_scores = 0;
145 m_swatter_reset_kart_ticks.clear();
146 m_last_captured_flag_ticks = 0;
147 m_red_flag_status = m_blue_flag_status = CTFFlag::IN_BASE;
148 m_red_flag->resetToBase();
149 m_blue_flag->resetToBase();
150 #ifndef SERVER_ONLY
151 if (GUIEngine::isNoGraphics())
152 return;
153
154 if (m_red_flag_node)
155 m_red_flag->updateFlagGraphics(m_red_flag_node);
156 if (m_blue_flag_node)
157 m_blue_flag->updateFlagGraphics(m_blue_flag_node);
158 #endif
159 } // reset
160
161 // ----------------------------------------------------------------------------
updateGraphics(float dt)162 void CaptureTheFlag::updateGraphics(float dt)
163 {
164 FreeForAll::updateGraphics(dt);
165
166 #ifndef SERVER_ONLY
167 if (m_red_flag_node)
168 m_red_flag->updateFlagGraphics(m_red_flag_node);
169 if (m_blue_flag_node)
170 m_blue_flag->updateFlagGraphics(m_blue_flag_node);
171 if (m_red_flag_indicator)
172 m_red_flag_indicator->setVisible(!m_red_flag->isInBase());
173 if (m_blue_flag_indicator)
174 m_blue_flag_indicator->setVisible(!m_blue_flag->isInBase());
175
176 core::stringw msg;
177 // Don't show flag has been returned message if
178 // a point has been scored recently
179 const bool scored_recently =
180 getTicksSinceStart() > m_last_captured_flag_ticks &&
181 getTicksSinceStart() - m_last_captured_flag_ticks < stk_config->time2Ticks(2.0f);
182 if (m_red_flag_status != m_red_flag->getStatus())
183 {
184 if (m_red_flag->getHolder() != -1)
185 {
186 AbstractKart* kart = getKart(m_red_flag->getHolder());
187 const core::stringw& name = kart->getController()->getName();
188 // I18N: Show when a player gets the red flag in CTF
189 msg = _("%s has the red flag!", name);
190 if (kart->getController()->isLocalPlayerController())
191 SFXManager::get()->quickSound("wee");
192 }
193 else if (m_red_flag->isInBase() && !scored_recently)
194 {
195 // I18N: Show when the red flag is returned to its base in CTF
196 msg = _("The red flag has returned!");
197 }
198 m_red_flag_status = m_red_flag->getStatus();
199 }
200 else if (m_blue_flag_status != m_blue_flag->getStatus())
201 {
202 if (m_blue_flag->getHolder() != -1)
203 {
204 AbstractKart* kart = getKart(m_blue_flag->getHolder());
205 const core::stringw& name = kart->getController()->getName();
206 // I18N: Show when a player gets the blue flag in CTF
207 msg = _("%s has the blue flag!", name);
208 if (kart->getController()->isLocalPlayerController())
209 SFXManager::get()->quickSound("wee");
210 }
211 else if (m_blue_flag->isInBase() && !scored_recently)
212 {
213 // I18N: Show when the blue flag is returned to its base in CTF
214 msg = _("The blue flag has returned!");
215 }
216 m_blue_flag_status = m_blue_flag->getStatus();
217 }
218 if (m_race_gui && !msg.empty())
219 m_race_gui->addMessage(msg, NULL, 1.5f);
220 #endif
221 } // updateGraphics
222
223 // ----------------------------------------------------------------------------
update(int ticks)224 void CaptureTheFlag::update(int ticks)
225 {
226 FreeForAll::update(ticks);
227
228 for (auto it = m_swatter_reset_kart_ticks.begin();
229 it != m_swatter_reset_kart_ticks.end();)
230 {
231 if (it->second < getTicksSinceStart() - stk_config->time2Ticks(8.0f))
232 {
233 it = m_swatter_reset_kart_ticks.erase(it);
234 }
235 else
236 {
237 if (it->second == getTicksSinceStart())
238 {
239 AbstractKart* kart = m_karts[it->first].get();
240 if (kart->isEliminated() || !kart->isSquashed())
241 {
242 it++;
243 continue;
244 }
245 unsigned int index = getRescuePositionIndex(kart);
246 btTransform t = getRescueTransform(index);
247 t.setOrigin(t.getOrigin() + t.getBasis().getColumn(1) * 3.0f);
248 kart->getBody()->setLinearVelocity(Vec3(0.0f));
249 kart->getBody()->setAngularVelocity(Vec3(0.0f));
250 kart->getBody()->proceedToTransform(t);
251 kart->setTrans(t);
252 kart->getPowerup()->reset();
253 static_cast<SmoothNetworkBody*>(kart)->reset();
254 }
255 it++;
256 }
257 }
258
259 // Update new flags position
260 m_red_flag->update(ticks);
261 m_blue_flag->update(ticks);
262
263 if (m_red_flag->getHolder() != -1 && m_blue_flag->isInBase() &&
264 (m_blue_flag->getBaseOrigin() - m_red_flag->getOrigin()).length() <
265 g_capture_length)
266 {
267 // Blue team scored
268 if (!NetworkConfig::get()->isNetworking() ||
269 NetworkConfig::get()->isServer())
270 {
271 int red_holder = m_red_flag->getHolder();
272 int new_kart_scores = m_scores.at(red_holder) + g_captured_score;
273 int new_blue_scores = m_blue_scores + 1;
274 m_scores.at(red_holder) = new_kart_scores;
275 if (NetworkConfig::get()->isServer())
276 {
277 NetworkString p(PROTOCOL_GAME_EVENTS);
278 p.setSynchronous(true);
279 p.addUInt8(GameEventsProtocol::GE_CTF_SCORED)
280 .addUInt8((int8_t)red_holder)
281 .addUInt8(0/*red_team_scored*/)
282 .addUInt16((int16_t)new_kart_scores)
283 .addUInt8((uint8_t)m_red_scores)
284 .addUInt8((uint8_t)new_blue_scores);
285 STKHost::get()->sendPacketToAllPeers(&p, true);
286 }
287 ctfScored(red_holder, false/*red_team_scored*/, new_kart_scores,
288 m_red_scores, new_blue_scores);
289 }
290 m_last_captured_flag_ticks = World::getWorld()->getTicksSinceStart();
291 m_red_flag->resetToBase(RaceManager::get()->getFlagDeactivatedTicks());
292 }
293 else if (m_blue_flag->getHolder() != -1 && m_red_flag->isInBase() &&
294 (m_red_flag->getBaseOrigin() - m_blue_flag->getOrigin()).length() <
295 g_capture_length)
296 {
297 // Red team scored
298 if (!NetworkConfig::get()->isNetworking() ||
299 NetworkConfig::get()->isServer())
300 {
301 int blue_holder = m_blue_flag->getHolder();
302 int new_kart_scores = m_scores.at(blue_holder) + g_captured_score;
303 int new_red_scores = m_red_scores + 1;
304 m_scores.at(blue_holder) = new_kart_scores;
305 if (NetworkConfig::get()->isServer())
306 {
307 NetworkString p(PROTOCOL_GAME_EVENTS);
308 p.setSynchronous(true);
309 p.addUInt8(GameEventsProtocol::GE_CTF_SCORED)
310 .addUInt8((int8_t)blue_holder)
311 .addUInt8(1/*red_team_scored*/)
312 .addUInt16((int16_t)new_kart_scores)
313 .addUInt8((uint8_t)new_red_scores)
314 .addUInt8((uint8_t)m_blue_scores);
315 STKHost::get()->sendPacketToAllPeers(&p, true);
316 }
317 ctfScored(blue_holder, true/*red_team_scored*/, new_kart_scores,
318 new_red_scores, m_blue_scores);
319 }
320 m_last_captured_flag_ticks = World::getWorld()->getTicksSinceStart();
321 m_blue_flag->resetToBase(RaceManager::get()->getFlagDeactivatedTicks());
322 }
323
324 // Test if red or blue flag is touched
325 for (auto& k : m_karts)
326 {
327 if (k->isEliminated() || k->getKartAnimation() || k->isSquashed())
328 continue;
329
330 if (m_red_flag->canBeCaptured() &&
331 (k->getXYZ() - m_red_flag->getOrigin()).length() <
332 g_capture_length)
333 {
334 uint8_t kart_id = (uint8_t)k->getWorldKartId();
335 if (getKartTeam(kart_id) == KART_TEAM_RED)
336 {
337 if (!m_red_flag->isInBase())
338 {
339 // Return the flag
340 m_red_flag->resetToBase(
341 RaceManager::get()->getFlagDeactivatedTicks());
342 }
343 }
344 else
345 {
346 // Get the flag
347 m_red_flag->setCapturedByKart(kart_id);
348 }
349 }
350 if (m_blue_flag->canBeCaptured() &&
351 (k->getXYZ() - m_blue_flag->getOrigin()).length() <
352 g_capture_length)
353 {
354 uint8_t kart_id = (uint8_t)k->getWorldKartId();
355 if (getKartTeam(kart_id) == KART_TEAM_BLUE)
356 {
357 if (!m_blue_flag->isInBase())
358 {
359 // Return the flag
360 m_blue_flag->resetToBase(
361 RaceManager::get()->getFlagDeactivatedTicks());
362 }
363 }
364 else
365 {
366 // Get the flag
367 m_blue_flag->setCapturedByKart(kart_id);
368 }
369 }
370 }
371 } // update
372
373 // ----------------------------------------------------------------------------
getRedHolder() const374 int CaptureTheFlag::getRedHolder() const
375 {
376 return m_red_flag->getHolder();
377 } // getRedHolder
378
379 // ----------------------------------------------------------------------------
getBlueHolder() const380 int CaptureTheFlag::getBlueHolder() const
381 {
382 return m_blue_flag->getHolder();
383 } // getBlueHolder
384
385 // ----------------------------------------------------------------------------
isRedFlagInBase() const386 bool CaptureTheFlag::isRedFlagInBase() const
387 {
388 return m_red_flag->isInBase();
389 } // isRedFlagInBase
390
391 // ----------------------------------------------------------------------------
isBlueFlagInBase() const392 bool CaptureTheFlag::isBlueFlagInBase() const
393 {
394 return m_blue_flag->isInBase();
395 } // isBlueFlagInBase
396
397 // ----------------------------------------------------------------------------
getRedFlag() const398 const Vec3& CaptureTheFlag::getRedFlag() const
399 {
400 return m_red_flag->getOrigin();
401 } // getRedFlag
402
403 // ----------------------------------------------------------------------------
getBlueFlag() const404 const Vec3& CaptureTheFlag::getBlueFlag() const
405 {
406 return m_blue_flag->getOrigin();
407 } // getBlueFlag
408
409 // ----------------------------------------------------------------------------
ctfScored(int kart_id,bool red_team_scored,int new_kart_score,int new_red_score,int new_blue_score)410 void CaptureTheFlag::ctfScored(int kart_id, bool red_team_scored,
411 int new_kart_score, int new_red_score,
412 int new_blue_score)
413 {
414 m_scores.at(kart_id) = new_kart_score;
415 AbstractKart* kart = getKart(kart_id);
416 core::stringw scored_msg;
417 const core::stringw& name = kart->getController()->getName();
418 m_red_scores = new_red_score;
419 m_blue_scores = new_blue_score;
420 if (red_team_scored)
421 {
422 scored_msg = _("%s captured the blue flag!", name);
423 }
424 else
425 {
426 scored_msg = _("%s captured the red flag!", name);
427 }
428 #ifndef SERVER_ONLY
429 // Don't set animation and show message if receiving in live join
430 if (isStartPhase() || GUIEngine::isNoGraphics())
431 return;
432 if (m_race_gui)
433 m_race_gui->addMessage(scored_msg, NULL, 3.0f);
434 kart->getKartModel()
435 ->setAnimation(KartModel::AF_WIN_START, true/*play_non_loop*/);
436 m_scored_sound->play();
437 #endif
438 } // ctfScored
439
440 // ----------------------------------------------------------------------------
getDroppedFlagTrans(const btTransform & kt,btTransform * out) const441 bool CaptureTheFlag::getDroppedFlagTrans(const btTransform& kt,
442 btTransform* out) const
443 {
444 Vec3 from = kt.getOrigin() + kt.getBasis().getColumn(1);
445 Vec3 to = kt.getOrigin() + kt.getBasis().getColumn(1) * -10000.0f;
446 Vec3 hit_point, normal;
447 const Material* material = NULL;
448
449 // From TerrainInfo::update
450 const TriangleMesh &tm = Track::getCurrentTrack()->getTriangleMesh();
451 bool ret = tm.castRay(from, to, &hit_point, &material, &normal,
452 /*interpolate*/false);
453 if (!ret || material == NULL)
454 return false;
455
456 Track::getCurrentTrack()->getTrackObjectManager()->castRay
457 (from, to, &hit_point, &material, &normal, /*interpolate*/false);
458 *out = btTransform(shortestArcQuat(Vec3(0, 1, 0), normal),
459 hit_point);
460 return true;
461 } // getDroppedFlagTrans
462
463 // ----------------------------------------------------------------------------
getColor(unsigned int kart_id) const464 video::SColor CaptureTheFlag::getColor(unsigned int kart_id) const
465 {
466 return getKartTeam(kart_id) == KART_TEAM_RED ?
467 video::SColor(255, 255, 0, 0) : video::SColor(255, 0, 0, 255);
468 } // getColor
469
470 // ----------------------------------------------------------------------------
isRaceOver()471 bool CaptureTheFlag::isRaceOver()
472 {
473 if (m_unfair_team)
474 return true;
475
476 if (NetworkConfig::get()->isNetworking() &&
477 NetworkConfig::get()->isClient())
478 return false;
479
480 if ((m_count_down_reached_zero && RaceManager::get()->hasTimeTarget()) ||
481 (m_red_scores >= RaceManager::get()->getHitCaptureLimit() ||
482 m_blue_scores >= RaceManager::get()->getHitCaptureLimit()))
483 return true;
484
485 return false;
486 } // isRaceOver
487
488 // ----------------------------------------------------------------------------
loseFlagForKart(int kart_id)489 void CaptureTheFlag::loseFlagForKart(int kart_id)
490 {
491 if (!(m_red_flag->getHolder() == kart_id ||
492 m_blue_flag->getHolder() == kart_id))
493 return;
494
495 bool drop_red_flag = m_red_flag->getHolder() == kart_id;
496 btTransform dropped_trans = drop_red_flag ?
497 m_red_flag->getBaseTrans() :
498 m_blue_flag->getBaseTrans();
499 bool succeed = getDroppedFlagTrans(getKart(kart_id)->getTrans(),
500 &dropped_trans);
501 if (drop_red_flag)
502 {
503 if (succeed)
504 m_red_flag->dropFlagAt(dropped_trans);
505 else
506 {
507 m_red_flag->resetToBase(
508 RaceManager::get()->getFlagDeactivatedTicks());
509 }
510 }
511 else
512 {
513 if (succeed)
514 m_blue_flag->dropFlagAt(dropped_trans);
515 else
516 {
517 m_blue_flag->resetToBase(
518 RaceManager::get()->getFlagDeactivatedTicks());
519 }
520 }
521 } // loseFlagForKart
522
523 // ----------------------------------------------------------------------------
kartHit(int kart_id,int hitter)524 bool CaptureTheFlag::kartHit(int kart_id, int hitter)
525 {
526 if (isRaceOver())
527 return false;
528
529 if (!NetworkConfig::get()->isNetworking() ||
530 NetworkConfig::get()->isServer())
531 handleScoreInServer(kart_id, hitter);
532
533 loseFlagForKart(kart_id);
534 return true;
535 } // kartHit
536
537 //-----------------------------------------------------------------------------
getRescuePositionIndex(AbstractKart * kart)538 unsigned int CaptureTheFlag::getRescuePositionIndex(AbstractKart *kart)
539 {
540 return m_kart_position_map.at(kart->getWorldKartId());
541 } // getRescuePositionIndex
542
543 // ----------------------------------------------------------------------------
544 /** Returns the internal identifier for this race.
545 */
getIdent() const546 const std::string& CaptureTheFlag::getIdent() const
547 {
548 return IDENT_CTF;
549 } // getIdent
550
551 // ----------------------------------------------------------------------------
saveCompleteState(BareNetworkString * bns,STKPeer * peer)552 void CaptureTheFlag::saveCompleteState(BareNetworkString* bns, STKPeer* peer)
553 {
554 FreeForAll::saveCompleteState(bns, peer);
555 bns->addUInt32(m_red_scores).addUInt32(m_blue_scores);
556 } // saveCompleteState
557
558 // ----------------------------------------------------------------------------
restoreCompleteState(const BareNetworkString & b)559 void CaptureTheFlag::restoreCompleteState(const BareNetworkString& b)
560 {
561 FreeForAll::restoreCompleteState(b);
562 m_red_scores = b.getUInt32();
563 m_blue_scores = b.getUInt32();
564 } // restoreCompleteState
565