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