1 /*
2 * Copyright (C) 2002-2020 by the Widelands Development 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 2
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 *
18 */
19
20 #include "logic/map_objects/tribes/battle.h"
21
22 #include <memory>
23
24 #include "base/log.h"
25 #include "base/wexception.h"
26 #include "io/fileread.h"
27 #include "io/filewrite.h"
28 #include "logic/game.h"
29 #include "logic/map_objects/tribes/soldier.h"
30 #include "logic/player.h"
31 #include "map_io/map_object_loader.h"
32 #include "map_io/map_object_saver.h"
33
34 namespace Widelands {
35
36 namespace {
37 BattleDescr g_battle_descr("battle", "Battle");
38 }
39
descr() const40 const BattleDescr& Battle::descr() const {
41 return g_battle_descr;
42 }
43
Battle()44 Battle::Battle()
45 : MapObject(&g_battle_descr),
46 first_(nullptr),
47 second_(nullptr),
48 creationtime_(0),
49 readyflags_(0),
50 damage_(0),
51 first_strikes_(true),
52 last_attack_hits_(false) {
53 }
54
Battle(Game & game,Soldier * first_soldier,Soldier * second_soldier)55 Battle::Battle(Game& game, Soldier* first_soldier, Soldier* second_soldier)
56 : MapObject(&g_battle_descr),
57 first_(first_soldier),
58 second_(second_soldier),
59 creationtime_(0),
60 readyflags_(0),
61 damage_(0),
62 first_strikes_(true),
63 last_attack_hits_(false) {
64 assert(first_soldier->get_owner() != second_soldier->get_owner());
65 {
66 StreamWrite& ss = game.syncstream();
67 ss.unsigned_8(SyncEntry::kBattle);
68 ss.unsigned_32(first_soldier->serial());
69 ss.unsigned_32(second_soldier->serial());
70 }
71
72 // Ensures only live soldiers engage in a battle
73 assert(first_soldier->get_current_health() && second_soldier->get_current_health());
74
75 init(game);
76 }
77
init(EditorGameBase & egbase)78 bool Battle::init(EditorGameBase& egbase) {
79 MapObject::init(egbase);
80
81 creationtime_ = egbase.get_gametime();
82
83 Game& game = dynamic_cast<Game&>(egbase);
84
85 if (Battle* battle = first_->get_battle()) {
86 battle->cancel(game, *first_);
87 }
88 first_->set_battle(game, this);
89 if (Battle* battle = second_->get_battle()) {
90 battle->cancel(game, *second_);
91 }
92 second_->set_battle(game, this);
93 return true;
94 }
95
cleanup(EditorGameBase & egbase)96 void Battle::cleanup(EditorGameBase& egbase) {
97 if (first_) {
98 first_->set_battle(dynamic_cast<Game&>(egbase), nullptr);
99 first_ = nullptr;
100 }
101 if (second_) {
102 second_->set_battle(dynamic_cast<Game&>(egbase), nullptr);
103 second_ = nullptr;
104 }
105
106 MapObject::cleanup(egbase);
107 }
108
109 /**
110 * Called by one of the soldiers if it has to cancel the battle immediately.
111 */
cancel(Game & game,Soldier & soldier)112 void Battle::cancel(Game& game, Soldier& soldier) {
113 if (&soldier == first_) {
114 first_ = nullptr;
115 soldier.set_battle(game, nullptr);
116 } else if (&soldier == second_) {
117 second_ = nullptr;
118 soldier.set_battle(game, nullptr);
119 } else
120 return;
121
122 schedule_destroy(game);
123 }
124
locked(Game & game)125 bool Battle::locked(Game& game) {
126 if (!first_ || !second_)
127 return false;
128 if (game.get_gametime() - creationtime_ < 1000)
129 return true; // don't change battles around willy-nilly
130 return first_->get_position() == second_->get_position();
131 }
132
opponent(const Soldier & soldier) const133 Soldier* Battle::opponent(const Soldier& soldier) const {
134 assert(first_ == &soldier || second_ == &soldier);
135 Soldier* other_soldier = first_ == &soldier ? second_ : first_;
136 return other_soldier;
137 }
138
get_pending_damage(const Soldier * for_whom) const139 unsigned int Battle::get_pending_damage(const Soldier* for_whom) const {
140 if (for_whom == (first_strikes_ ? first_ : second_)) {
141 return damage_;
142 }
143 return 0;
144 }
145
146 // TODO(unknown): Couldn't this code be simplified tremendously by doing all scheduling
147 // for one soldier and letting the other sleep until the battle is over?
148 // Could be, but we need to be able change the animations of the soldiers
149 // easily without unneeded hacks, and this code is not so difficult, only it
150 // had some translations errors
get_battle_work(Game & game,Soldier & soldier)151 void Battle::get_battle_work(Game& game, Soldier& soldier) {
152 // Identify what soldier is calling the routine
153 uint8_t const this_soldier_is = &soldier == first_ ? 1 : 2;
154
155 assert(first_->get_battle() == this || second_->get_battle() == this);
156
157 // Created this four 'states' of the battle:
158 // *First time entered, one enters :
159 // oneReadyToFight, mark readyflags_ as he is ready to fight
160 // *Next time, the opponent enters:
161 // bothReadyToFight, mark readyflags_ as 3 (round fighted)
162 // *Next time, the first enters again:
163 // roundFought, reset readyflags_
164 // *Opponent not on field yet, so one enters :
165 // waitingForOpponent, if others are false
166
167 bool const oneReadyToFight = (readyflags_ == 0);
168 bool const roundFought = (readyflags_ == 3);
169 bool const bothReadyToFight = ((this_soldier_is | readyflags_) == 3) && (!roundFought);
170 bool const waitingForOpponent = !(oneReadyToFight || roundFought || bothReadyToFight);
171 std::string what_anim;
172
173 // Apply pending damage
174 if (damage_ && oneReadyToFight) {
175 // Current attacker is last defender, so damage goes to current attacker
176 if (first_strikes_) {
177 first_->damage(damage_);
178 } else {
179 second_->damage(damage_);
180 }
181 damage_ = 0;
182 }
183
184 if (soldier.get_current_health() < 1) {
185 molog("[battle] soldier %u lost the battle\n", soldier.serial());
186 soldier.get_owner()->count_casualty();
187 opponent(soldier)->get_owner()->count_kill();
188 soldier.start_task_die(game);
189 molog("[battle] waking up winner %d\n", opponent(soldier)->serial());
190 opponent(soldier)->send_signal(game, "wakeup");
191 return schedule_destroy(game);
192 }
193
194 if (!first_ || !second_) {
195 return soldier.skip_act();
196 }
197
198 // Here is a timeout to prevent battle freezes
199 if (waitingForOpponent && (game.get_gametime() - creationtime_) > 90 * 1000) {
200 molog(
201 "[battle] soldier %u waiting for opponent %u too long (%5d sec), cancelling battle...\n",
202 soldier.serial(), opponent(soldier)->serial(),
203 (game.get_gametime() - creationtime_) / 1000);
204 cancel(game, soldier);
205 return;
206 }
207
208 // So both soldiers are alive; are we ready to trade the next blow?
209 //
210 // This code choses one of 3 codepaths:
211 // *oneReadyToFight : This soldier is ready, but his opponent isn't, so
212 // wait until opponent "wakeup"-us
213 // *bothReadyToFight: Opponent is ready to fight, so calculate this round
214 // and set flags to 'roundFought', so the opponent
215 // will know that it need to set proper animation
216 // *roundFought : Opponent has calculated this round, so this soldier
217 // only need to set his proper animation.
218 //
219 if (oneReadyToFight) {
220 // My opponent is not ready to battle. Idle until he wakes me up.
221 assert(readyflags_ == 0);
222 readyflags_ = this_soldier_is;
223 assert(readyflags_ == this_soldier_is);
224
225 what_anim = this_soldier_is == 1 ? "evade_success_e" : "evade_success_w";
226 return soldier.start_task_idle(
227 game, soldier.descr().get_rand_anim(game, what_anim, &soldier), 10);
228 }
229 if (bothReadyToFight) {
230 // Our opponent is waiting for us to fight.
231 // Time for one of us to hurt the other. Which one is on turn is decided
232 // by calculate_round.
233 assert((readyflags_ == 1 && this_soldier_is == 2) ||
234 (readyflags_ == 2 && this_soldier_is == 1));
235
236 // Both are now ready, mark flags, so our opponent can get new animation
237 readyflags_ = 3;
238 assert(readyflags_ == 3);
239
240 // Resolve combat turn
241 calculate_round(game);
242
243 // Wake up opponent, so he could update his animation
244 opponent(soldier)->send_signal(game, "wakeup");
245 }
246
247 if (roundFought) {
248 // Both of us were already ready. That means that we already fought and
249 // it is time to wait until both become ready.
250 readyflags_ = 0;
251 }
252
253 // The function calculate_round inverts value of first_strikes_, so
254 // attacker will be the first_ when first_strikes_ = false and
255 // attacker will be second_ when first_strikes_ = true
256 molog("[battle] (%u) vs (%u) is %d, first strikes %d, last hit %d\n", soldier.serial(),
257 opponent(soldier)->serial(), this_soldier_is, first_strikes_, last_attack_hits_);
258
259 bool shorten_animation = false;
260 if (this_soldier_is == 1) {
261 if (first_strikes_) {
262 if (last_attack_hits_) {
263 what_anim = "evade_failure_e";
264 shorten_animation = true;
265 } else {
266 what_anim = "evade_success_e";
267 }
268 } else {
269 if (last_attack_hits_) {
270 what_anim = "attack_success_e";
271 } else {
272 what_anim = "attack_failure_e";
273 }
274 }
275 } else {
276 if (first_strikes_) {
277 if (last_attack_hits_) {
278 what_anim = "attack_success_w";
279 } else {
280 what_anim = "attack_failure_w";
281 }
282 } else {
283 if (last_attack_hits_) {
284 what_anim = "evade_failure_w";
285 shorten_animation = true;
286 } else {
287 what_anim = "evade_success_w";
288 }
289 }
290 }
291 // If the soldier will die as soon as the animation is complete, don't
292 // show it for the full length to prevent overlooping (bug 1817664)
293 shorten_animation &= damage_ >= soldier.get_current_health();
294 molog("[battle] Starting animation %s for soldier %d\n", what_anim.c_str(), soldier.serial());
295 soldier.start_task_idle(game, soldier.descr().get_rand_anim(game, what_anim, &soldier),
296 shorten_animation ? 850 : 1000);
297 }
298
calculate_round(Game & game)299 void Battle::calculate_round(Game& game) {
300 assert(!damage_);
301
302 Soldier* attacker;
303 Soldier* defender;
304
305 if (first_strikes_) {
306 attacker = first_;
307 defender = second_;
308 } else {
309 attacker = second_;
310 defender = first_;
311 }
312
313 first_strikes_ = !first_strikes_;
314
315 uint32_t const hit = game.logic_rand() % 100;
316 if (hit > defender->get_evade()) {
317 // Attacker hits!
318 last_attack_hits_ = true;
319
320 assert(attacker->get_min_attack() <= attacker->get_max_attack());
321 uint32_t const attack =
322 attacker->get_min_attack() +
323 (game.logic_rand() % (1 + attacker->get_max_attack() - attacker->get_min_attack()));
324 damage_ = attack - (attack * defender->get_defense()) / 100;
325 } else {
326 // Defender evaded
327 last_attack_hits_ = false;
328 }
329 }
330
331 /*
332 ==============================
333
334 Load/Save support
335
336 ==============================
337 */
338
339 constexpr uint8_t kCurrentPacketVersion = 2;
340
load(FileRead & fr)341 void Battle::Loader::load(FileRead& fr) {
342 MapObject::Loader::load(fr);
343
344 Battle& battle = get<Battle>();
345
346 battle.creationtime_ = fr.signed_32();
347 battle.readyflags_ = fr.unsigned_8();
348 battle.first_strikes_ = fr.unsigned_8();
349 battle.damage_ = fr.unsigned_32();
350 first_ = fr.unsigned_32();
351 second_ = fr.unsigned_32();
352 }
353
load_pointers()354 void Battle::Loader::load_pointers() {
355 Battle& battle = get<Battle>();
356 try {
357 MapObject::Loader::load_pointers();
358 if (first_)
359 try {
360 battle.first_ = &mol().get<Soldier>(first_);
361 } catch (const WException& e) {
362 throw wexception("soldier 1 (%u): %s", first_, e.what());
363 }
364 if (second_)
365 try {
366 battle.second_ = &mol().get<Soldier>(second_);
367 } catch (const WException& e) {
368 throw wexception("soldier 2 (%u): %s", second_, e.what());
369 }
370 } catch (const WException& e) {
371 throw wexception("battle: %s", e.what());
372 }
373 }
374
save(EditorGameBase & egbase,MapObjectSaver & mos,FileWrite & fw)375 void Battle::save(EditorGameBase& egbase, MapObjectSaver& mos, FileWrite& fw) {
376 fw.unsigned_8(HeaderBattle);
377 fw.unsigned_8(kCurrentPacketVersion);
378
379 MapObject::save(egbase, mos, fw);
380
381 fw.signed_32(creationtime_);
382 fw.unsigned_8(readyflags_);
383 fw.unsigned_8(first_strikes_);
384 fw.unsigned_32(damage_);
385
386 // And now, the serials of the soldiers !
387 fw.unsigned_32(first_ ? mos.get_object_file_index(*first_) : 0);
388 fw.unsigned_32(second_ ? mos.get_object_file_index(*second_) : 0);
389 }
390
load(EditorGameBase & egbase,MapObjectLoader & mol,FileRead & fr)391 MapObject::Loader* Battle::load(EditorGameBase& egbase, MapObjectLoader& mol, FileRead& fr) {
392 std::unique_ptr<Loader> loader(new Loader);
393
394 try {
395 // Header has been peeled away by caller
396
397 uint8_t const packet_version = fr.unsigned_8();
398 if (packet_version == kCurrentPacketVersion) {
399 loader->init(egbase, mol, *new Battle);
400 loader->load(fr);
401 } else {
402 throw UnhandledVersionError("Battle", packet_version, kCurrentPacketVersion);
403 }
404 } catch (const std::exception& e) {
405 throw wexception("Loading Battle: %s", e.what());
406 }
407
408 return loader.release();
409 }
410 } // namespace Widelands
411