1 /*
2 * combat.h - Combat scheduling.
3 *
4 * Copyright (C) 2000-2013 The Exult Team
5 *
6 * This program 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 * This program 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
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 */
20
21 #ifdef HAVE_CONFIG_H
22 # include <config.h>
23 #endif
24
25 #include <memory>
26
27 #include "actors.h"
28 #include "combat.h"
29 #include "combat_opts.h"
30 #include "gamewin.h"
31 #include "gameclk.h"
32 #include "gamemap.h"
33 #include "paths.h"
34 #include "Astar.h"
35 #include "actions.h"
36 #include "items.h"
37 #include "effects.h"
38 #include "Audio.h"
39 #include "ready.h"
40 #include "game.h"
41 #include "monstinf.h"
42 #include "ucmachine.h"
43 #include "game.h"
44 #include "Gump_manager.h"
45 #include "spellbook.h"
46 #include "animate.h"
47 #include "ucsched.h"
48 #include "ucscriptop.h"
49 #include "cheat.h"
50 #include "ammoinf.h"
51 #include "weaponinf.h"
52 #include "ready.h"
53 #include "usefuns.h"
54
55 using std::cout;
56 using std::endl;
57 using std::rand;
58 using std::list;
59
60 unsigned long Combat_schedule::battle_time = static_cast<unsigned long>(-30000);
61 unsigned long Combat_schedule::battle_end_time = 0;
62
63 bool Combat::paused = false;
64 int Combat::difficulty = 0;
65 Combat::Mode Combat::mode = Combat::original;
66 bool Combat::show_hits = false;
67 bool Combat::charmed_more_difficult = false;
68
69 extern bool combat_trace;
70
71 const unsigned int dex_to_attack = 30;
72
not_in_melee_range(const Weapon_info * winf,int dist,int reach)73 static inline bool not_in_melee_range(
74 const Weapon_info *winf, // 0 for monsters or natural weaponry.
75 int dist,
76 int reach
77 ) {
78 if (!winf)
79 return dist > reach;
80 return (winf->get_uses() == Weapon_info::ranged) || (dist > reach);
81 }
82
83 /*
84 * Is a given ammo shape in a given family.
85 */
86
In_ammo_family(int shnum,int family)87 bool In_ammo_family(int shnum, int family) {
88 if (shnum == family)
89 return true;
90 const Ammo_info *ainf = ShapeID::get_info(shnum).get_ammo_info();
91 return ainf != nullptr && ainf->get_family_shape() == family;
92 }
93
94 /*
95 * Start music if battle has recently started.
96 */
97
start_battle()98 void Combat_schedule::start_battle(
99 ) {
100 if (started_battle)
101 return;
102 // But only if Avatar is main char.
103 if (gwin->get_camera_actor() != gwin->get_main_actor())
104 return;
105 unsigned long curtime = Game::get_ticks();
106 // If this is the avatar, and it has been at least .5 minute since last
107 // start, then change music. This allows the danger music to be heard.
108 Game_object *target = npc->get_target();
109 if (npc == gwin->get_main_actor() && curtime - battle_time >= 30000 &&
110 (!opponents.empty() || (target && target->as_actor()))) {
111 Audio::get_ptr()->start_music_combat((rand() % 2) ?
112 CSAttacked1 : CSAttacked2, false);
113 battle_time = curtime;
114 battle_end_time = curtime - 1;
115 }
116 started_battle = true;
117 }
118
119 /*
120 * This (static) method is called when a monster dies. It checks to
121 * see if there are still hostile NPC's around. If not, it plays
122 * 'victory' music.
123 */
124
monster_died()125 void Combat_schedule::monster_died(
126 ) {
127 if (battle_end_time >= battle_time)// Battle raging?
128 return; // No, it's over.
129 Actor_vector nearby; // Get all nearby NPC's.
130 gwin->get_nearby_npcs(nearby);
131 for (auto *actor : nearby) {
132 if (!actor->is_dead() &&
133 actor->get_attack_mode() != Actor::flee &&
134 actor->get_effective_alignment() >= Actor::evil)
135 return; // Still possible enemies.
136 }
137 battle_end_time = Game::get_ticks();
138 // Figure #seconds battle lasted.
139 unsigned long len = (battle_end_time - battle_time) / 1000;
140 bool hard = len > 15u && (rand() % 60u < len);
141 Audio::get_ptr()->start_music_combat(hard ? CSBattle_Over
142 : CSVictory, false);
143 }
144
145 /*
146 * This (static) method is called to stop attacking a given NPC.
147 * This can happen because the NPC died, fell asleep or became
148 * invisible.
149 */
150
stop_attacking_npc(Game_object * npc)151 void Combat_schedule::stop_attacking_npc(
152 Game_object *npc
153 ) {
154 Actor_vector nearby; // Get all nearby NPC's.
155 gwin->get_nearby_npcs(nearby);
156 for (auto *actor : nearby) {
157 if (actor->get_target() == npc)
158 actor->set_target(nullptr);
159 }
160 }
161
162 /*
163 * This (static) method is called to stop attacking a given NPC.
164 * This can happen because the NPC died or fell asleep.
165 */
166
stop_attacking_invisible(Game_object * npc)167 void Combat_schedule::stop_attacking_invisible(
168 Game_object *npc
169 ) {
170 Actor_vector nearby; // Get all nearby NPC's.
171 gwin->get_nearby_npcs(nearby);
172 for (auto *actor : nearby) {
173 if (actor->get_target() == npc && !actor->can_see_invisible())
174 actor->set_target(nullptr);
175 }
176 }
177
178 /*
179 * Can a given shape teleport? summon? turn invisible?
180 */
181
Can_teleport(Actor * npc)182 static bool Can_teleport(
183 Actor *npc
184 ) {
185 if (npc->get_flag(Obj_flags::no_spell_casting))
186 return false;
187 return npc->get_info().can_teleport();
188 }
189
Can_summon(Actor * npc)190 static bool Can_summon(
191 Actor *npc
192 ) {
193 if (npc->get_flag(Obj_flags::no_spell_casting))
194 return false;
195 return npc->get_info().can_summon();
196 }
197
Can_be_invisible(Actor * npc)198 static bool Can_be_invisible(
199 Actor *npc
200 ) {
201 if (npc->get_flag(Obj_flags::no_spell_casting))
202 return false;
203 return npc->get_info().can_be_invisible();
204 }
205
206 /*
207 * Certain monsters (wisps, mages) can teleport during battle.
208 */
209
teleport()210 bool Combat_schedule::teleport(
211 ) {
212 Game_object *trg = npc->get_target(); // Want to get close to targ.
213 if (!trg)
214 return false;
215 unsigned int curtime = SDL_GetTicks();
216 if (curtime < teleport_time)
217 return false;
218 teleport_time = curtime + 2000 + rand() % 2000;
219 Tile_coord dest = trg->get_tile();
220 dest.tx += 4 - rand() % 8;
221 dest.ty += 4 - rand() % 8;
222 dest = Map_chunk::find_spot(dest, 3, npc, 1);
223 if (dest.tx == -1)
224 return false; // No spot found.
225 Tile_coord src = npc->get_tile();
226 if (dest.distance(src) > 7 && rand() % 2 != 0)
227 return false; // Got to give Avatar a chance to
228 // get away.
229 // Create fire-field where he was.
230 src.tz = npc->get_chunk()->get_highest_blocked(src.tz,
231 src.tx % c_tiles_per_chunk, src.ty % c_tiles_per_chunk);
232 if (src.tz < 0)
233 src.tz = 0;
234 eman->add_effect(std::make_unique<Fire_field_effect>(src));
235 int sfx = Audio::game_sfx(43);
236 Audio::get_ptr()->play_sound_effect(sfx, npc); // The weird noise.
237 npc->move(dest.tx, dest.ty, dest.tz);
238 // Show the stars.
239 eman->add_effect(std::make_unique<Sprites_effect>(7, npc, 0, 0, 0, 0));
240 return true;
241 }
242
243 /*
244 * Some monsters can do the "summon" spell.
245 */
246
summon()247 bool Combat_schedule::summon(
248 ) {
249 ucmachine->call_usecode(SummonSpellUsecode,
250 npc, Usecode_machine::double_click);
251 npc->start_std(); // Back into queue.
252 return true;
253 }
254
255 /*
256 * Some can turn invisible.
257 */
258
be_invisible()259 bool Combat_schedule::be_invisible(
260 ) {
261 Object_sfx::Play(npc, Audio::game_sfx(44));
262 gwin->get_effects()->add_effect(
263 std::make_unique<Sprites_effect>(12, npc, 0, 0, 0, 0, 0, -1));
264 npc->set_flag(Obj_flags::invisible);
265 npc->add_dirty();
266 npc->start_std(); // Back into queue.
267 return true;
268 }
269
270 /*
271 * Off-screen?
272 */
273
Off_screen(Game_window * gwin,Game_object * npc)274 inline bool Off_screen(
275 Game_window *gwin,
276 Game_object *npc
277 ) {
278 // See if off screen.
279 Tile_coord t = npc->get_tile();
280 TileRect screen = gwin->get_win_tile_rect().enlarge(2);
281 return !screen.has_world_point(t.tx, t.ty);
282 }
283
is_enemy(int align,int other)284 bool Combat_schedule::is_enemy(
285 int align, int other
286 ) {
287 switch (align) {
288 case Actor::good:
289 return other == Actor::evil
290 || other == Actor::chaotic;
291 case Actor::evil:
292 return other == Actor::good
293 || other == Actor::chaotic;
294 case Actor::neutral:
295 return false;
296 case Actor::chaotic:
297 return other == Actor::evil
298 || other == Actor::good;
299 }
300 return true; // This should never happen.
301 }
302
303 /*
304 * Find nearby opponents in the 9 surrounding chunks.
305 */
306
find_opponents()307 void Combat_schedule::find_opponents(
308 ) {
309 opponents.clear();
310 Game_window *gwin = Game_window::get_instance();
311 Actor_vector nearby;
312 Actor_vector sleeping; // Get all nearby NPC's.
313 gwin->get_nearby_npcs(nearby);
314 Actor *avatar = gwin->get_main_actor();
315 nearby.push_back(avatar); // Incl. Avatar!
316 bool charmed_avatar = Combat::charmed_more_difficult &&
317 avatar->get_effective_alignment() != Actor::good;
318 // See if we're a party member.
319 bool in_party = npc->is_in_party() || npc == avatar;
320 int npc_align = npc->get_effective_alignment();
321 bool see_invisible = npc->can_see_invisible();
322 for (auto *actor : nearby) {
323 if (actor->is_dead() || (!see_invisible && actor->get_flag(Obj_flags::invisible)))
324 continue; // Dead or invisible.
325 if (is_enemy(npc_align, actor->get_effective_alignment())) {
326 if (actor->get_flag(Obj_flags::asleep)) {
327 // sleeping
328 sleeping.push_back(actor);
329 if (combat_trace)
330 cout << npc->get_name() << " pushed back(asleep,1) " << actor->get_name() << endl;
331 } else {
332 opponents.push_back(actor->weak_from_this());
333 if (combat_trace)
334 cout << npc->get_name() << " pushed back(1) " << actor->get_name() << endl;
335 }
336 } else if (in_party) { // Attacking party member?
337 Game_object *t = actor->get_target();
338 if (!t)
339 continue;
340 // Is actor attacking a party member?
341 if ((t->get_flag(Obj_flags::in_party) || t == avatar) &&
342 t->as_actor() && is_enemy(npc_align, t->as_actor()->get_effective_alignment())) {
343 opponents.push_back(actor->weak_from_this());
344 if (combat_trace)
345 cout << npc->get_name() << " pushed back(2) " << actor->get_name() << endl;
346 }
347 int oppressor = actor->get_oppressor();
348 if (oppressor < 0)
349 continue;
350 Actor *oppr = gwin->get_npc(oppressor);
351 assert(oppr != nullptr);
352 // Is actor being attacked by a party member?
353 if ((oppr->get_flag(Obj_flags::in_party) || oppr == avatar) &&
354 is_enemy(npc_align, oppr->get_effective_alignment())) {
355 opponents.push_back(actor->weak_from_this());
356 if (combat_trace)
357 cout << npc->get_name() << " pushed back(3) " << actor->get_name() << endl;
358 }
359 }
360 }
361 // None found? Use Avatar's if same effective alignment
362 if (opponents.empty() && in_party &&
363 avatar->get_effective_alignment() == npc_align) {
364 Game_object *opp = avatar->get_target();
365 Actor *oppnpc = opp ? opp->as_actor() : nullptr;
366 if (oppnpc && oppnpc != npc
367 && oppnpc->get_schedule_type() == Schedule::combat) {
368 opponents.push_back(oppnpc->weak_from_this());
369 if (combat_trace)
370 cout << npc->get_name() << " pushed back (4) " << oppnpc->get_name() << endl;
371 }
372 }
373 // Still none? Try the sleeping/unconscious foes.
374 if (opponents.empty() && !sleeping.empty()
375 && (npc != avatar || charmed_avatar)) {
376 for (auto *act : sleeping) {
377 opponents.push_front(weak_from_obj(act));
378 }
379 if (combat_trace)
380 cout << npc->get_name() << " pushed back asleep opponents" << endl;
381 }
382 }
383
384 /*
385 * Find 'protected' party member's attackers.
386 *
387 * Output: ->attacker in opponents, or opponents.end() if not found.
388 */
389
find_protected_attacker()390 list<Game_object_weak>::iterator Combat_schedule::find_protected_attacker(
391 ) {
392 if (!npc->is_in_party()) // Not in party?
393 return opponents.end();
394 Game_window *gwin = Game_window::get_instance();
395 Actor *party[9]; // Get entire party, including Avatar.
396 int cnt = gwin->get_party(party, 1);
397 Actor *prot_actor = nullptr;
398 for (int i = 0; i < cnt; i++)
399 if (party[i]->is_combat_protected()) {
400 prot_actor = party[i];
401 break;
402 }
403 if (!prot_actor) // Not found?
404 return opponents.end();
405 // Find closest attacker.
406 int best_dist = 4 * c_tiles_per_chunk;
407 auto best_opp = opponents.end();
408 for (auto it = opponents.begin();
409 it != opponents.end(); ++it) {
410 Actor_shared opp = std::static_pointer_cast<Actor>((*it).lock());
411 int dist;
412 if (opp && opp->get_target() == prot_actor &&
413 (dist = npc->distance(opp.get())) < best_dist) {
414 best_dist = dist;
415 best_opp = it;
416 }
417 }
418 if (best_opp == opponents.end())
419 return opponents.end();
420 if (failures < 5 && yelled && rand() % 2 && npc != prot_actor) {
421 if (npc->is_goblin())
422 npc->say(goblin_will_help);
423 else if (can_yell)
424 npc->say(first_will_help, last_will_help);
425 }
426 return best_opp;
427 }
428
429 /*
430 * Find a foe.
431 *
432 * Output: Opponent that was found.
433 */
434
find_foe(int mode)435 Game_object *Combat_schedule::find_foe(
436 int mode // Mode to use.
437 ) {
438 if (combat_trace) {
439 cout << "'" << npc->get_name() << "' is looking for a foe" <<
440 endl;
441 }
442 int new_align = npc->get_effective_alignment();
443 if (new_align != alignment) {
444 // Alignment changed.
445 opponents.clear();
446 alignment = new_align;
447 }
448 Actor *avatar = gwin->get_main_actor();
449 bool charmed_avatar = Combat::charmed_more_difficult &&
450 avatar->get_effective_alignment() != Actor::good;
451 // Remove any that died.
452 opponents.erase(std::remove_if(opponents.begin(), opponents.end(),
453 [this, avatar, charmed_avatar](const Game_object_weak& curr) {
454 Actor_shared actor = std::static_pointer_cast<Actor>(curr.lock());
455 return !actor || actor->is_dead() ||
456 (npc == avatar && !charmed_avatar &&
457 actor->get_flag(Obj_flags::asleep));
458 }
459 ), opponents.end());
460 if (opponents.empty()) { // No more from last scan?
461 find_opponents(); // Find all nearby.
462 if (practice_target) // For dueling.
463 return practice_target;
464 }
465 auto new_opp_link = opponents.end();
466 switch (static_cast<Actor::Attack_mode>(mode)) {
467 case Actor::weakest: {
468 int str;
469 int least_str = 100;
470 for (auto it = opponents.begin();
471 it != opponents.end(); ++it) {
472 Actor_shared opp = std::static_pointer_cast<Actor>((*it).lock());
473 if (!opp)
474 continue;
475 str = opp->get_property(Actor::strength);
476 if (str < least_str) {
477 least_str = str;
478 new_opp_link = it;
479 }
480 }
481 break;
482 }
483 case Actor::strongest: {
484 int str;
485 int best_str = -100;
486 for (auto it = opponents.begin();
487 it != opponents.end(); ++it) {
488 Actor_shared opp = std::static_pointer_cast<Actor>((*it).lock());
489 if (!opp)
490 continue;
491 str = opp->get_property(Actor::strength);
492 if (str > best_str) {
493 best_str = str;
494 new_opp_link = it;
495 }
496 }
497 break;
498 }
499 case Actor::nearest: {
500 int best_dist = 4 * c_tiles_per_chunk;
501 for (auto it = opponents.begin();
502 it != opponents.end(); ++it) {
503 Actor_shared opp = std::static_pointer_cast<Actor>((*it).lock());
504 if (!opp)
505 continue;
506 int dist = npc->distance(opp.get());
507 if (opp->get_attack_mode() == Actor::flee)
508 dist += 16; // Avoid fleeing.
509 if (dist < best_dist) {
510 best_dist = dist;
511 new_opp_link = it;
512 }
513 }
514 break;
515 }
516 case Actor::protect:
517 new_opp_link = find_protected_attacker();
518 if (new_opp_link != opponents.end())
519 break; // Found one.
520 // FALLTHROUGH
521 case Actor::random:
522 default: // Default to random.
523 if (!opponents.empty())
524 new_opp_link = opponents.begin();
525 break;
526 }
527 Actor_shared new_opponent;
528 if (new_opp_link != opponents.end()) {
529 new_opponent = std::static_pointer_cast<Actor>((*new_opp_link).lock());
530 opponents.erase(new_opp_link);
531 }
532 return new_opponent ? new_opponent.get() : nullptr;
533 }
534
535 /*
536 * Find a foe.
537 *
538 * Output: Opponent that was found.
539 */
540
find_foe()541 inline Game_object *Combat_schedule::find_foe(
542 ) {
543 if (npc->get_attack_mode() == Actor::manual)
544 return nullptr; // Find it yourself.
545 return find_foe(static_cast<int>(npc->get_attack_mode()));
546 }
547
548 /*
549 * Back off if we're closer than our weapon's range.
550 */
551
back_off(Actor * npc,Game_object * attacker)552 void Combat_schedule::back_off(
553 Actor *npc,
554 Game_object *attacker
555 ) {
556 int points;
557 int weapon_shape;
558 Game_object *weapon;
559 const Weapon_info *winf = npc->get_weapon(points, weapon_shape, weapon);
560 int weapon_dist = winf ? winf->get_range() : 3;
561 int attacker_dist = npc->distance(attacker);
562 Game_object *opponent = npc->get_target();
563 if (opponent == attacker && weapon_dist <= attacker_dist)
564 return; // Stay within our weapon's range.
565 Tile_coord npc_tile = npc->get_tile();
566 Tile_coord attacker_tile = attacker->get_tile();
567 int dx = npc_tile.tx - attacker_tile.ty;
568 int dy = npc_tile.ty - attacker_tile.ty;
569 dx = dx < 0 ? -1 : dx > 0 ? 1 : 0;
570 dy = dy < 0 ? -1 : dy > 0 ? 1 : 0;
571 Tile_coord spots[3];
572 if (dx) {
573 if (dy) {
574 spots[0] = npc_tile + Tile_coord(dx, 0, 0);
575 spots[1] = npc_tile + Tile_coord(dx, dy, 0);
576 spots[2] = npc_tile + Tile_coord(0, dy, 0);
577 } else {
578 spots[0] = npc_tile + Tile_coord(dx, 0, 0);
579 spots[1] = npc_tile + Tile_coord(dx, -1, 0);
580 spots[2] = npc_tile + Tile_coord(dx, 1, 0);
581 }
582 } else { // dx == 0;
583 spots[0] = npc_tile + Tile_coord(0, dy, 0);
584 spots[1] = npc_tile + Tile_coord(-1, dy, 0);
585 spots[2] = npc_tile + Tile_coord(1, dy, 0);
586 }
587 int ind = rand()%3;
588 if (npc->is_blocked(spots[ind])) {
589 ind = (ind + 1)%3;
590 if (npc->is_blocked(spots[ind])) {
591 ind = (ind + 1)%3;
592 if (npc->is_blocked(spots[ind]))
593 return;
594 }
595 }
596 npc->move(spots[ind]);
597 int dir = npc->get_facing_direction(attacker);
598 npc->change_frame(npc->get_dir_framenum(dir, Actor::standing));
599 cout << "***" << npc->get_name() << " is backing off" << endl;
600 }
601
602 /*
603 * Handle the 'approach' state.
604 */
605
approach_foe(bool for_projectile)606 void Combat_schedule::approach_foe(
607 bool for_projectile // Want to attack with projectile.
608 // FOR NOW: Called as last resort,
609 // and we try to reach target.
610 ) {
611 int points;
612 const Weapon_info *winf = npc->get_weapon(points, weapon_shape, weapon);
613 int dist = for_projectile ? 1 : winf ? winf->get_range() : 3;
614 Game_object *opponent = npc->get_target();
615 // Find opponent.
616 if (!opponent && !(opponent = find_foe())) {
617 failures++;
618 npc->start(200, 400); // Try again in 2/5 sec.
619 return; // No one left to fight.
620 }
621 npc->set_target(opponent);
622 Actor::Attack_mode mode = npc->get_attack_mode();
623 Game_window *gwin = Game_window::get_instance();
624 // Time to run?
625 const Monster_info *minf = npc->get_info().get_monster_info();
626 if ((!minf || !minf->cant_die()) &&
627 (mode == Actor::flee ||
628 (mode != Actor::berserk &&
629 (npc->get_type_flags()&MOVE_ALL) != 0 &&
630 npc != gwin->get_main_actor() &&
631 npc->get_property(Actor::health) < 3))) {
632 run_away();
633 return;
634 }
635 if (rand() % 4 == 0 && Can_teleport(npc) && // Try 1/4 to teleport.
636 teleport()) {
637 start_battle();
638 npc->start_std();
639 return;
640 }
641 PathFinder *path = new Astar();
642 // Try this for now:
643 Monster_pathfinder_client cost(npc, dist, opponent);
644 Tile_coord pos = npc->get_tile();
645 if (!path->NewPath(pos, opponent->get_tile(), &cost)) {
646 // Failed? Try nearest opponent.
647 failures++;
648 bool retry_ok = false;
649 if (npc->get_attack_mode() != Actor::manual) {
650 Game_object *closest = find_foe(Actor::nearest);
651 if (!closest) {
652 // No one nearby.
653 if (combat_trace)
654 cout << npc->get_name() << " has no opponents nearby."
655 << endl;
656 npc->set_target(nullptr);
657 retry_ok = false;
658 } else if (closest != opponent) {
659 opponent = closest;
660 npc->set_target(opponent);
661 Monster_pathfinder_client cost(npc, dist,
662 opponent);
663 retry_ok = (opponent != nullptr && path->NewPath(
664 pos, opponent->get_tile(), &cost));
665 }
666 }
667 if (!retry_ok) {
668 delete path; // Really failed. Try again in
669 // after wandering.
670 // Just try to walk towards opponent.
671 Tile_coord pos = npc->get_tile();
672 Tile_coord topos = opponent->get_tile();
673 int dirx = topos.tx > pos.tx ? 2
674 : (topos.tx < pos.tx ? -2 : (rand() % 3 - 1));
675 int diry = topos.ty > pos.ty ? 2
676 : (topos.ty < pos.ty ? -2 : (rand() % 3 - 1));
677 pos.tx += dirx * (1 + rand() % 4);
678 pos.ty += diry * (1 + rand() % 4);
679 npc->walk_to_tile(pos, 2 * gwin->get_std_delay(),
680 500 + rand() % 500);
681 failures++;
682 return;
683 }
684 }
685 failures = 0; // Clear count. We succeeded.
686 start_battle(); // Music if first time.
687 if (combat_trace) {
688 cout << npc->get_name() << " is pursuing " << opponent->get_name()
689 << endl;
690 }
691 // First time (or 256th), visible?
692 if (!yelled && gwin->add_dirty(npc)) {
693 yelled++;
694 if (can_yell && rand() % 2) { // Half the time.
695 if (npc->is_goblin()) // Goblin?
696 npc->say(goblin_to_battle);
697 else if (can_yell)
698 npc->say(first_to_battle, last_to_battle);
699 }
700 }
701 int extra_delay = 0;
702 // Walk there, & check half-way.
703 npc->set_action(new Approach_actor_action(path, opponent,
704 for_projectile));
705 // Start walking. Delay a bit if
706 // opponent is off-screen.
707 npc->start(gwin->get_std_delay(), extra_delay +
708 (Off_screen(gwin, opponent) ?
709 5 * gwin->get_std_delay() : gwin->get_std_delay()));
710 }
711
712 /*
713 * Check for a useful weapon at a given ready-spot.
714 */
715
Get_usable_weapon(Actor * npc,int index)716 static Game_object *Get_usable_weapon(
717 Actor *npc,
718 int index // Ready-spot to check.
719 ) {
720 Game_object *bobj = npc->get_readied(index);
721 if (!bobj)
722 return nullptr;
723 const Shape_info &info = bobj->get_info();
724 const Weapon_info *winf = info.get_weapon_info();
725 if (!winf)
726 return nullptr; // Not a weapon.
727 Game_object *aobj; // Check ranged first.
728 int need_ammo = npc->get_weapon_ammo(bobj->get_shapenum(),
729 winf->get_ammo_consumed(), winf->get_projectile(),
730 true, &aobj);
731 if (need_ammo) {
732 if (!aobj) // Try melee.
733 need_ammo = npc->get_weapon_ammo(bobj->get_shapenum(),
734 winf->get_ammo_consumed(), winf->get_projectile(),
735 false, &aobj);
736 if (need_ammo && !aobj)
737 return nullptr;
738 }
739 if (info.get_ready_type() == both_hands &&
740 npc->get_readied(rhand) != nullptr)
741 return nullptr; // Needs two free hands.
742 return bobj;
743 }
744
745 /*
746 * Swap weapon with the one in the belt.
747 *
748 * Output: 1 if successful.
749 */
750
Swap_weapons(Actor * npc)751 static int Swap_weapons(
752 Actor *npc
753 ) {
754 int index = belt;
755 Game_object *bobj = Get_usable_weapon(npc, index);
756 if (!bobj) {
757 index = back_2h;
758 bobj = Get_usable_weapon(npc, index);
759 if (!bobj) {
760 // Do thorough search for NPC's.
761 if (!npc->is_in_party())
762 return npc->ready_best_weapon();
763 else
764 return 0;
765 }
766 }
767 Game_object *oldweap = npc->get_readied(lhand);
768 if (oldweap)
769 npc->remove(oldweap);
770 npc->remove(bobj);
771 npc->add(bobj, true); // Should go into weapon hand.
772 if (oldweap) // Put old where new one was.
773 npc->add_readied(oldweap, index, true, true);
774 return 1;
775 }
776
777 // Just move around a bit.
wander_for_attack()778 void Combat_schedule::wander_for_attack(
779 ) {
780 Tile_coord pos = npc->get_tile();
781 Game_object *opponent = npc->get_target();
782 int dir = npc->get_direction(opponent);
783
784 dir += rand()%2 ? 2 : 6; // A perpendicular direction.
785 dir %= 8;
786 int tries = 3;
787 while (tries--) {
788 int cnt = 2 + rand()%3;
789 while (cnt--)
790 pos = pos.get_neighbor(dir);
791 // Find a free spot.
792 Tile_coord dest = Map_chunk::find_spot(pos, 3, npc, 1);
793 if (dest.tx != -1 && npc->walk_path_to_tile(dest,
794 gwin->get_std_delay(), rand() % 1000))
795 return; // Success.
796 }
797 // Failed? Try again a little later.
798 npc->start(250, rand() % 3000);
799 }
800
801 /*
802 * Begin a strike at the opponent.
803 */
804
start_strike()805 void Combat_schedule::start_strike(
806 ) {
807 Game_object *opponent = npc->get_target();
808 bool check_lof = !no_blocking;
809 // Get difference in lift.
810 const Weapon_info *winf = weapon_shape >= 0 ?
811 ShapeID::get_info(weapon_shape).get_weapon_info() : nullptr;
812 int dist = npc->distance(opponent);
813 int reach;
814 if (!winf) {
815 const Monster_info *minf = npc->get_info().get_monster_info();
816 reach = minf ? minf->get_reach() : Monster_info::get_default()->get_reach();
817 } else
818 reach = winf->get_range();
819 bool ranged = not_in_melee_range(winf, dist, reach);
820 // Out of range?
821 if (!spellbook && npc->get_effective_range(winf, reach) < dist) {
822 state = approach;
823 approach_foe(); // Get a path.
824 return;
825 } else if (spellbook || ranged) {
826 bool weapon_dead = false;
827 if (spellbook)
828 weapon_dead = !spellbook->can_do_spell(npc);
829 else if (winf) {
830 // See if we can fire spell/projectile.
831 Game_object *ammo = nullptr;
832 int need_ammo = npc->get_weapon_ammo(weapon_shape,
833 winf->get_ammo_consumed(), winf->get_projectile(),
834 ranged, &ammo);
835 if (need_ammo && !ammo && !npc->ready_ammo())
836 weapon_dead = true;
837 }
838 if (weapon_dead) {
839 // Out of ammo/reagents/charges.
840 if (npc->get_schedule_type() != Schedule::duel) {
841 // Look in pack for ammo.
842 if (Swap_weapons(npc))
843 Combat_schedule::set_weapon();
844 else
845 set_hand_to_hand();
846 }
847 if (!npc->get_info().has_strange_movement())
848 npc->change_frame(npc->get_dir_framenum(
849 Actor::standing));
850 state = approach;
851 npc->set_target(nullptr);
852 npc->start(200, 500);
853 return;
854 }
855 state = fire; // Clear to go.
856 } else {
857 check_lof = (reach > 1);
858 state = strike;
859 }
860 // At this point, we're within range, with state set.
861 if (check_lof &&
862 !Fast_pathfinder_client::is_straight_path(npc, opponent)) {
863 state = approach;
864 wander_for_attack();
865 return;
866 }
867 if (!started_battle)
868 start_battle(); // Play music if first time.
869 // Some battle cries. Guessing at where to do it, and how often.
870 if (yelled && !(rand() % 20)) {
871 if (npc->is_goblin())
872 npc->say(first_goblin_taunt, last_goblin_taunt);
873 else if (can_yell)
874 npc->say(first_taunt, last_taunt);
875 }
876 if (combat_trace) {
877 cout << npc->get_name() << " attacks " << opponent->get_name() << endl;
878 }
879 int dir = npc->get_direction(opponent);
880 signed char frames[12]; // Get frames to show.
881 int cnt = npc->get_attack_frames(weapon_shape, ranged, dir, frames);
882 if (cnt)
883 npc->set_action(new Frames_actor_action(frames, cnt, gwin->get_std_delay()));
884 npc->start(); // Get back into time queue.
885 int sfx = -1; // Play sfx.
886 if (winf)
887 sfx = winf->get_sfx();
888 if (sfx < 0 || !winf) {
889 const Monster_info *minf = ShapeID::get_info(
890 npc->get_shapenum()).get_monster_info();
891 if (minf)
892 sfx = minf->get_hitsfx();
893 }
894 if (sfx >= 0) {
895 int delay = ranged ? cnt : cnt / 2;
896 Object_sfx::Play(npc, sfx, delay * gwin->get_std_delay());
897 }
898 dex_points -= dex_to_attack;
899 }
900
901 /*
902 * This static method causes the NPC to attack a given target/tile
903 * using the given weapon shape as weapon. This does not add
904 * an attack animation; rather, it is the actual strike attempt.
905 *
906 * This function is called from (a) an intrinsic, (b) a script opcode
907 * or (c) the combat schedule.
908 *
909 * Output: Returns false the attack cannot be realized (no ammo,
910 * out of range, etc.) or if a melee attack misses, true otherwise.
911 */
912
attack_target(Game_object * attacker,Game_object * target,Tile_coord const & tile,int weapon,bool combat)913 bool Combat_schedule::attack_target(
914 Game_object *attacker, // Who/what is attacking.
915 Game_object *target, // Who/what is being attacked.
916 Tile_coord const &tile, // What tile is under fire, if no target.
917 int weapon, // What is being used as weapon.
918 // or < 0 for none.
919 bool combat // We got here from combat schedule.
920 ) {
921 // Bail out if no attacker or if no target and no valid tile.
922 if (!attacker || (!target && tile.tx == -1))
923 return false;
924
925 // Do not proceed if target is dead.
926 Actor *att = attacker->as_actor();
927 if (att && att->is_dead())
928 return false;
929 bool flash_mouse = !combat && att && gwin->get_main_actor() == att
930 && att->get_attack_mode() != Actor::manual;
931
932 const Shape_info &info = ShapeID::get_info(weapon);
933 const Weapon_info *winf = weapon >= 0 ? info.get_weapon_info() : nullptr;
934
935 int reach;
936 int family = -1; // Ammo, is needed, is the weapon itself.
937 int proj = -1; // This is what we will use as projectile sprite.
938 if (!winf) {
939 const Monster_info *minf = attacker->get_info().get_monster_info();
940 reach = minf ? minf->get_reach() : Monster_info::get_default()->get_reach();
941 } else {
942 reach = winf->get_range();
943 proj = winf->get_projectile();
944 family = winf->get_ammo_consumed();
945 }
946 int dist = target ? attacker->distance(target) : attacker->distance(tile);
947 bool ranged = not_in_melee_range(winf, dist, reach);
948 // Out of range?
949 if (attacker->get_effective_range(winf, reach) < dist) {
950 // We are out of range.
951 if (flash_mouse)
952 Mouse::mouse->flash_shape(Mouse::outofrange);
953 return false;
954 }
955
956 // See if we need ammo.
957 Game_object *ammo = nullptr;
958 int need_ammo = attacker->get_weapon_ammo(weapon, family,
959 proj, ranged, &ammo);
960 Game_object_shared ammo_keep = shared_from_obj(ammo);
961 if (need_ammo && !ammo) {
962 if (flash_mouse)
963 Mouse::mouse->flash_shape(Mouse::outofammo);
964 // We don't have ammo, so bail out.
965 return false;
966 }
967
968 // proj == -3 means use weapon shape for projectile sprite.
969 if (proj == -3)
970 proj = weapon;
971 const Ammo_info *ainf;
972 int basesprite;
973 if (need_ammo && family >= 0) {
974 // ammo should be nonzero here.
975 ainf = ammo->get_info().get_ammo_info();
976 basesprite = ammo->get_shapenum();
977 } else {
978 ainf = info.get_ammo_info();
979 basesprite = weapon;
980 }
981 if (ainf) {
982 int sprite = ainf->get_sprite_shape();
983 if (sprite == -3)
984 proj = basesprite;
985 else if (sprite != -1 && sprite != ainf->get_family_shape())
986 proj = sprite;
987 } else
988 ainf = Ammo_info::get_default(); // So we don't need to keep checking.
989
990 // By now, proj should be >=0 or -1 for none.
991 assert(proj >= -1);
992 if (!winf) // So we don't have to keep checking.
993 winf = Weapon_info::get_default();
994 if (need_ammo) {
995 // We should only ever get here for containers and NPCs.
996 // Also, ammo should never be zero in this branch.
997 bool need_new_weapon = false;
998 bool ready = att ? att->find_readied(ammo) >= 0 : false;
999
1000 // Time to use up ammo.
1001 if (winf->uses_charges()) {
1002 if (ammo->get_info().has_quality())
1003 ammo->set_quality(ammo->get_quality() - need_ammo);
1004 if (winf->delete_depleted() &&
1005 (!ammo->get_quality() || !ammo->get_info().has_quality())) {
1006 // Call unready usecode if needed.
1007 if (att)
1008 att->remove(ammo);
1009 ammo->remove_this();
1010 need_new_weapon = true;
1011 }
1012 } else {
1013 int quant = ammo->get_quantity();
1014 // Call unready usecode if needed.
1015 if (att && quant == need_ammo)
1016 att->remove(ammo);
1017 ammo->modify_quantity(-need_ammo, &need_new_weapon);
1018 }
1019
1020 if (att && need_new_weapon && ready) {
1021 // Readied weapon was depleted; we need a new one.
1022 if (winf->returns() || ainf->returns()) {
1023 // Weapon will return, so wait for it.
1024 if (combat) {
1025 // We got here due to combat schedule.
1026 auto *sched =
1027 dynamic_cast<Combat_schedule *>(att->get_schedule());
1028 if (sched) // May not need this check.
1029 sched->set_state(wait_return);
1030 }
1031 }
1032 // Try readying ammo first.
1033 else if (att && !att->ready_ammo()) {
1034 // Need new weapon.
1035 att->ready_best_weapon();
1036 // Tell schedule about it.
1037 att->get_schedule()->set_weapon(true);
1038 }
1039 }
1040 }
1041
1042 Actor *trg = target ? target->as_actor() : nullptr;
1043 bool trg_party = trg ? trg->is_in_party() : false;
1044 bool att_party = att ? att->is_in_party() : false;
1045 int attval = att ? att->get_effective_prop(static_cast<int>(Actor::combat)) : 0;
1046 // These two give the correct statistics:
1047 attval += (winf->lucky() ? 3 : 0);
1048 attval += (ainf->lucky() ? 3 : 0);
1049 int bias = trg_party ? Combat::difficulty :
1050 (att_party ? -Combat::difficulty : 0);
1051 attval += 2 * bias; // Apply all bias to the attack value.
1052 if (ranged) {
1053 int uses = winf->get_uses();
1054 attval += 6;
1055 // This seems reasonably close to how the originals do it,
1056 // although the error bands of the statistics are too wide here.
1057 if (uses == Weapon_info::poor_thrown)
1058 attval -= dist;
1059 else if (uses == Weapon_info::good_thrown)
1060 attval -= dist / 2;
1061 // We need to pass the attack value here to guard against
1062 // the possibility of the attacker's combat be lowered
1063 // (e.g., due to being paralyzed) while the projectile is
1064 // in flight and before it hits.
1065 std::unique_ptr<Projectile_effect> projectile;
1066 if (target)
1067 projectile = std::make_unique<Projectile_effect>(attacker, target, weapon,
1068 ammo ? ammo->get_shapenum() : proj, proj, attval);
1069 else
1070 projectile = std::make_unique<Projectile_effect>(attacker, tile, weapon,
1071 ammo ? ammo->get_shapenum() : proj, proj, attval);
1072 gwin->get_effects()->add_effect(std::move(projectile));
1073 return true;
1074 } else if (target) {
1075 // Do nothing when attacking tiles in melee.
1076 bool autohit = winf->autohits() || ainf->autohits();
1077 // godmode effects:
1078 if (cheat.in_god_mode())
1079 autohit = trg_party ? false : (att_party ? true : autohit);
1080 if (!autohit && !target->try_to_hit(attacker, attval))
1081 return false; // Missed.
1082 target->play_hit_sfx(weapon, false);
1083 //if (weapon == 704) // Powder keg.
1084 if (info.is_explosive()) { // Powder keg.
1085 // Blow up *instead*.
1086 Tile_coord offset(0, 0, target->get_info().get_3d_height() / 2);
1087 eman->add_effect(std::make_unique<Explosion_effect>(target->get_tile() + offset,
1088 target, 0, weapon, -1, attacker));
1089 } else {
1090 Game_object_weak trg_check = weak_from_obj(trg);
1091 target->attacked(attacker, weapon,
1092 ammo ? ammo->get_shapenum() : -1, false);
1093 if (trg && !trg_check.expired())
1094 back_off(trg, attacker);
1095 }
1096 return true;
1097 }
1098 return false;
1099 }
1100
1101 /*
1102 * Run away.
1103 */
1104
run_away()1105 void Combat_schedule::run_away(
1106 ) {
1107 Game_window *gwin = Game_window::get_instance();
1108 fleed++;
1109 // Might be nice to run from opp...
1110 int rx = rand(); // Get random position away from here.
1111 int ry = rand();
1112 int dirx = 2 * (rx % 2) - 1; // Get 1 or -1.
1113 int diry = 2 * (ry % 2) - 1;
1114 Tile_coord pos = npc->get_tile();
1115 pos.tx += dirx * (8 + rx % 8);
1116 pos.ty += diry * (8 + ry % 8);
1117 npc->walk_to_tile(pos, gwin->get_std_delay(), 0);
1118 if (fleed == 1 && !npc->get_flag(Obj_flags::tournament) &&
1119 rand() % 3 && gwin->add_dirty(npc)) {
1120 yelled++;
1121 if (npc->is_goblin()) {
1122 if (rand() % 4)
1123 npc->say(goblin_flee_screaming);
1124 else
1125 npc->say(first_goblin_flee, last_goblin_flee);
1126 } else if (can_yell) {
1127 if (rand() % 4)
1128 npc->say(flee_screaming);
1129 else
1130 npc->say(first_flee, last_flee);
1131 }
1132 }
1133 }
1134
1135 /*
1136 * See if a spellbook is readied with a spell
1137 * available.
1138 *
1139 * Output: ->spellbook if so, else nullptr.
1140 */
1141
readied_spellbook()1142 Spellbook_object *Combat_schedule::readied_spellbook(
1143 ) {
1144 Spellbook_object *book = nullptr;
1145 // Check both hands.
1146 Game_object *obj = npc->get_readied(lhand);
1147 if (obj && obj->get_info().get_shape_class() == Shape_info::spellbook) {
1148 book = static_cast<Spellbook_object *>(obj);
1149 if (book->can_do_spell(npc))
1150 return book;
1151 }
1152 obj = npc->get_readied(rhand);
1153 if (obj && obj->get_info().get_shape_class() == Shape_info::spellbook) {
1154 book = static_cast<Spellbook_object *>(obj);
1155 if (book->can_do_spell(npc))
1156 return book;
1157 }
1158 return nullptr;
1159 }
1160
1161 /*
1162 * Set weapon 'max_range' and 'ammo'. Ready a new weapon if needed.
1163 */
1164
set_weapon(bool removed)1165 void Combat_schedule::set_weapon(
1166 bool removed // The weapon was just removed.
1167 ) {
1168 int points;
1169 spellbook = nullptr;
1170 const Weapon_info *info = npc->get_weapon(points, weapon_shape, weapon);
1171 if (!removed &&
1172 // Not dragging?
1173 !gwin->is_dragging() &&
1174 // And not dueling?
1175 npc->get_schedule_type() != Schedule::duel &&
1176 state != wait_return) { // And not waiting for boomerang.
1177 if (!info && // No weapon?
1178 !(spellbook = readied_spellbook())) { // No spellbook?
1179 npc->ready_best_weapon();
1180 info = npc->get_weapon(points, weapon_shape, weapon);
1181 } else
1182 npc->ready_best_shield();
1183 }
1184 if (!info) { // Still nothing.
1185 if (spellbook) // Did we find a spellbook?
1186 no_blocking = true;
1187 else // Don't do this if using spellbook.
1188 set_hand_to_hand();
1189 } else {
1190 no_blocking = false;
1191 }
1192 if (state == strike || state == fire)
1193 state = approach; // Got to restart attack.
1194 }
1195
1196
1197 /*
1198 * Set for hand-to-hand combat (no weapon).
1199 */
1200
set_hand_to_hand()1201 void Combat_schedule::set_hand_to_hand(
1202 ) {
1203 weapon = nullptr;
1204 weapon_shape = -1;
1205 no_blocking = false;
1206 // Put aside weapon.
1207 Game_object *weapon = npc->get_readied(lhand);
1208 if (weapon) {
1209 Game_object_shared keep = weapon->shared_from_this();
1210 npc->remove(weapon);
1211 if (!npc->add_readied(weapon, belt, true) &&
1212 !npc->add_readied(weapon, back_2h, true) &&
1213 !npc->add_readied(weapon, back_shield, true) &&
1214 !npc->add_readied(weapon, rhand, true) &&
1215 !npc->add_readied(weapon, backpack, true))
1216 npc->add(weapon, false, false, true);
1217 }
1218 }
1219
1220 /*
1221 * See if we need a new opponent.
1222 */
1223
Need_new_opponent(Game_window * gwin,Actor * npc)1224 inline int Need_new_opponent(
1225 Game_window *gwin,
1226 Actor *npc
1227 ) {
1228 Game_object *opponent = npc->get_target();
1229 Actor *act;
1230 bool see_invisible = npc->can_see_invisible();
1231 // Nonexistent or dead?
1232 if (!opponent ||
1233 ((act = opponent->as_actor()) != nullptr && act->is_dead()) ||
1234 // Or invisible?
1235 (!see_invisible && opponent->get_flag(Obj_flags::invisible)
1236 && rand() % 4 == 0))
1237 return 1;
1238 // See if off screen.
1239 return Off_screen(gwin, opponent) && !Off_screen(gwin, npc);
1240 }
1241
1242 /*
1243 * Create.
1244 */
1245
Combat_schedule(Actor * n,Schedule_types prev_sched)1246 Combat_schedule::Combat_schedule(
1247 Actor *n,
1248 Schedule_types
1249 prev_sched
1250 ) : Schedule(n), state(initial), prev_schedule(prev_sched),
1251 practice_target(nullptr), weapon(nullptr), weapon_shape(-1), spellbook(nullptr),
1252 no_blocking(false), yelled(0), started_battle(false), fleed(0),
1253 failures(0), teleport_time(0), summon_time(0),
1254 dex_points(0), alignment(n->get_effective_alignment()) {
1255 Combat_schedule::set_weapon();
1256 // Cache some data.
1257 can_yell = npc->can_speak();
1258 unsigned int curtime = SDL_GetTicks();
1259 summon_time = curtime + 4000;
1260 invisible_time = curtime + 4500;
1261 }
1262
1263
1264 /*
1265 * Previous action is finished.
1266 */
1267
now_what()1268 void Combat_schedule::now_what(
1269 ) {
1270 Game_window *gwin = Game_window::get_instance();
1271 if (state == initial) { // Do NOTHING in initial state so
1272 // usecode can, e.g., set opponent.
1273 // Way far away (50 tiles)?
1274 if (npc->distance(gwin->get_camera_actor()) > 50) {
1275 npc->set_dormant();
1276 return; // Just go dormant.
1277 }
1278 state = approach;
1279 npc->start(200, 200);
1280 return;
1281 }
1282 if (npc->get_flag(Obj_flags::asleep)) {
1283 npc->start(200, 1000); // Check again in a second.
1284 return;
1285 }
1286 // Running away?
1287 if (npc->get_attack_mode() == Actor::flee) {
1288 // If not in combat, stop running.
1289 const Monster_info *minf = npc->get_info().get_monster_info();
1290 if (minf && minf->cant_die())
1291 npc->set_attack_mode(Actor::nearest);
1292 else if (fleed > 2 && !gwin->in_combat() &&
1293 npc->get_party_id() >= 0)
1294 // WARNING: Destroys ourself.
1295 npc->set_schedule_type(Schedule::follow_avatar);
1296 else
1297 run_away();
1298 return;
1299 }
1300 // Check if opponent still breathes.
1301 if (Need_new_opponent(gwin, npc)) {
1302 npc->set_target(nullptr);
1303 state = approach;
1304 }
1305 Game_object *opponent = npc->get_target();
1306 switch (state) { // Note: state's action has finished.
1307 case approach:
1308 if (!opponent)
1309 approach_foe();
1310 else if (dex_points >= dex_to_attack) {
1311 int effint = npc->get_effective_prop(
1312 Actor::intelligence);
1313 if (!npc->get_flag(Obj_flags::invisible) &&
1314 Can_be_invisible(npc) && rand() % 300 < effint) {
1315 (void) be_invisible();
1316 dex_points -= dex_to_attack;
1317 } else if (Can_summon(npc) && rand() % 600 < effint &&
1318 summon())
1319 dex_points -= dex_to_attack;
1320 else
1321 start_strike();
1322 } else {
1323 dex_points += npc->get_property(Actor::dexterity);
1324 npc->start_std();
1325 }
1326 break;
1327 case strike: { // He hasn't moved away?
1328 state = approach;
1329 // Back into queue.
1330 npc->start_std();
1331 Actor_shared safenpc = std::static_pointer_cast<Actor>(
1332 npc->shared_from_this());
1333 // Change back to ready frame.
1334 int delay = gwin->get_std_delay();
1335 // Neither slime nor BG sea serpent?
1336 if (!npc->get_info().has_strange_movement()) {
1337 auto frame =
1338 static_cast<signed char>(npc->get_dir_framenum(Actor::ready_frame));
1339 npc->set_action(new Frames_actor_action(&frame, 1, delay));
1340 } else if (!npc->is_slime()) { // Sea serpent?
1341 signed char frames[] = {
1342 static_cast<signed char>(npc->get_dir_framenum(3)),
1343 static_cast<signed char>(npc->get_dir_framenum(2)),
1344 static_cast<signed char>(npc->get_dir_framenum(1))
1345 };
1346 npc->set_action(new Frames_actor_action(frames, sizeof(frames), delay));
1347 }
1348 npc->start(delay, delay);
1349 if (attack_target(npc, opponent, Tile_coord(-1, -1, 0), weapon_shape, true)) {
1350 // Strike but once at objects.
1351 Game_object *newtarg = safenpc->get_target();
1352 if (newtarg && !newtarg->as_actor())
1353 safenpc->set_target(nullptr);
1354 return; // We may no longer exist!
1355 }
1356 break;
1357 }
1358 case fire: { // Range weapon.
1359 failures = 0;
1360 state = approach;
1361 if (spellbook) {
1362 // Cast the spell.
1363 if (!spellbook->do_spell(npc, true))
1364 Combat_schedule::set_weapon();
1365 } else
1366 attack_target(npc, opponent, Tile_coord(-1, -1, 0), weapon_shape, true);
1367
1368 int delay = 1;
1369 if (spellbook) {
1370 Usecode_script *scr = Usecode_script::find(npc);
1371 // Warning: assuming that the most recent script for the
1372 // actor is the spellcasting script.
1373 delay += (scr ? scr->get_length() : 0) + 2;
1374 }
1375 delay *= gwin->get_std_delay();
1376 // Change back to ready frame.
1377 // Neither slime nor BG sea serpent?
1378 if (!npc->get_info().has_strange_movement()) {
1379 auto frame =
1380 static_cast<signed char>(npc->get_dir_framenum(Actor::ready_frame));
1381 npc->set_action(new Frames_actor_action(&frame, 1, delay));
1382 } else if (!npc->is_slime()) { // Sea serpent?
1383 signed char frames[] = {
1384 static_cast<signed char>(npc->get_dir_framenum(3)),
1385 static_cast<signed char>(npc->get_dir_framenum(2)),
1386 static_cast<signed char>(npc->get_dir_framenum(1))
1387 };
1388 npc->set_action(new Frames_actor_action(frames, sizeof(frames), delay));
1389 }
1390 npc->start(gwin->get_std_delay(), delay);
1391
1392 // Strike but once at objects.
1393 Game_object *newtarg = npc->get_target();
1394 if (newtarg && !newtarg->as_actor()) {
1395 npc->set_target(nullptr);
1396 return; // We may no longer exist!
1397 }
1398 break;
1399 }
1400 case wait_return: // Boomerang should have returned.
1401 state = approach;
1402 dex_points += npc->get_property(Actor::dexterity);
1403 npc->start_std();
1404 break;
1405 default:
1406 break;
1407 }
1408 if (failures > 5 && npc != gwin->get_camera_actor()) {
1409 // Too many failures. Give up for now.
1410 if (combat_trace) {
1411 cout << npc->get_name() << " is giving up" << endl;
1412 }
1413 if (npc->get_party_id() >= 0) {
1414 // Party member.
1415 npc->walk_to_tile(
1416 gwin->get_main_actor()->get_tile(),
1417 gwin->get_std_delay());
1418 // WARNING: Destroys ourself.
1419 npc->set_schedule_type(Schedule::follow_avatar);
1420 } else if (!gwin->get_game_rect().intersects(
1421 gwin->get_shape_rect(npc))) {
1422 // Off screen? Stop trying.
1423 gwin->get_tqueue()->remove(npc);
1424 npc->set_dormant();
1425 } else if (npc->get_alignment() == Actor::good &&
1426 prev_schedule != Schedule::combat) {
1427 // Return to normal schedule.
1428 npc->update_schedule(gclock->get_hour() / 3);
1429 if (npc->get_schedule_type() == Schedule::combat)
1430 npc->set_schedule_type(prev_schedule);
1431 } else {
1432 // Wander randomly.
1433 Tile_coord t = npc->get_tile();
1434 int dist = 2 + rand() % 3;
1435 int newx = t.tx - dist + rand() % (2 * dist);
1436 int newy = t.ty - dist + rand() % (2 * dist);
1437 // Wait a bit.
1438 npc->walk_to_tile(newx, newy, t.tz,
1439 2 * gwin->get_std_delay(), rand() % 1000);
1440 }
1441 }
1442 }
1443
1444 /*
1445 * Npc just went dormant (probably off-screen).
1446 */
1447
im_dormant()1448 void Combat_schedule::im_dormant(
1449 ) {
1450 if (npc->get_effective_alignment() == Actor::good &&
1451 prev_schedule != npc->get_schedule_type() && npc->is_monster())
1452 // Good, so end combat.
1453 npc->set_schedule_type(prev_schedule);
1454 }
1455
1456 /*
1457 * Leaving combat.
1458 */
1459
ending(int)1460 void Combat_schedule::ending(
1461 int /* newtype */
1462 ) {
1463 Game_window *gwin = Game_window::get_instance();
1464 if (gwin->get_main_actor() == npc &&
1465 // Not if called from usecode.
1466 !gwin->get_usecode()->in_usecode()) {
1467 // See if being a coward.
1468 find_opponents();
1469 bool found = false; // Find a close-by enemy.
1470 for (auto& opponent : opponents) {
1471 Actor_shared opp = std::static_pointer_cast<Actor>(opponent.lock());
1472 if (opp && opp->distance(npc) < (c_screen_tile_size / 2 - 2) &&
1473 Fast_pathfinder_client::is_grabable(npc, opp.get())) {
1474 found = true;
1475 break;
1476 }
1477 }
1478 if (found)
1479 Audio::get_ptr()->start_music_combat(CSRun_Away,
1480 false);
1481 }
1482 }
1483
1484
1485 /*
1486 * Create duel schedule.
1487 */
1488
Duel_schedule(Actor * n)1489 Duel_schedule::Duel_schedule(
1490 Actor *n
1491 ) : Combat_schedule(n, duel), start(n->get_tile()),
1492 attacks(0) {
1493 started_battle = true; // Avoid playing music.
1494 }
1495
1496 /*
1497 * Ready a bow-and-arrows.
1498 */
1499
Ready_duel_weapon(Actor * npc,int wshape,int ashape)1500 void Ready_duel_weapon(
1501 Actor *npc,
1502 int wshape, // Weapon shape.
1503 int ashape // Ammo shape, or -1.
1504 ) {
1505 Game_map *gmap = Game_window::get_instance()->get_map();
1506 Game_object *weap = npc->get_readied(lhand);
1507 if (!weap || weap->get_shapenum() != wshape) {
1508 // Need a bow.
1509 Game_object_shared newweap;
1510 Game_object_shared keep;
1511 Game_object_shared newkeep;
1512 Game_object *found =
1513 npc->find_item(wshape, c_any_qual, c_any_framenum);
1514 if (found) { // Have it?
1515 newweap = found->shared_from_this();
1516 newweap->remove_this(&newkeep);
1517 } else // Create new one.
1518 newweap = gmap->create_ireg_object(wshape, 0);
1519 if (weap) // Remove old item.
1520 weap->remove_this(&keep);
1521 npc->add(newweap.get(), true); // Should go in correct spot.
1522 if (weap)
1523 npc->add(weap, true);
1524 }
1525 if (ashape == -1) // No ammo needed.
1526 return;
1527 // Now provide 1-3 arrows.
1528 Game_object *aobj = npc->get_readied(quiver);
1529 if (aobj)
1530 aobj->remove_this(); // Toss current ammo.
1531 Game_object_shared arrows = gmap->create_ireg_object(ashape, 0);
1532 int extra = rand() % 3; // Add 1 or 2.
1533 if (extra)
1534 arrows->modify_quantity(extra);
1535 npc->add(arrows.get(), true); // Should go to right spot.
1536 }
1537
1538 /*
1539 * Find dueling opponents.
1540 */
1541
find_opponents()1542 void Duel_schedule::find_opponents(
1543 ) {
1544 opponents.clear();
1545 attacks = 0;
1546 practice_target = nullptr;
1547 int r = rand() % 3;
1548 if (r == 0) { // First look for practice targets.
1549 // Archery target:
1550 practice_target = npc->find_closest(735);
1551 if (practice_target) // Need bow-and-arrows.
1552 Ready_duel_weapon(npc, 597, 722);
1553 }
1554 if (!practice_target) { // Fencing dummy or dueling opponent.
1555 Ready_duel_weapon(npc, 602, -1);
1556 if (r == 1)
1557 practice_target = npc->find_closest(860);
1558 }
1559 Combat_schedule::set_weapon();
1560 if (practice_target) {
1561 npc->set_target(practice_target);
1562 return; // Just use that.
1563 }
1564 Actor_vector vec; // Find all nearby NPC's.
1565 npc->find_nearby_actors(vec, c_any_shapenum, 24);
1566 for (auto *opp : vec) {
1567 Game_object *oppopp = opp->get_target();
1568 if (opp != npc && opp->get_schedule_type() == duel &&
1569 (!oppopp || oppopp == npc))
1570 opponents.push_back(opp->weak_from_this());
1571 }
1572 }
1573
1574 /*
1575 * Previous action is finished.
1576 */
1577
now_what()1578 void Duel_schedule::now_what(
1579 ) {
1580 if (state == strike || state == fire) {
1581 attacks++;
1582 // Practice target full?
1583 if (practice_target && practice_target->get_shapenum() == 735
1584 && practice_target->get_framenum() > 0 &&
1585 practice_target->get_framenum() % 3 == 0) {
1586 attacks = 0; // Break off.
1587 //++++++Should walk there.
1588 practice_target->change_frame(0);
1589 }
1590 } else {
1591 Combat_schedule::now_what();
1592 return;
1593 }
1594 if (attacks % 8 == 0) { // Time to break off.
1595 npc->set_target(nullptr);
1596 Tile_coord pos = start;
1597 pos.tx += rand() % 24 - 12;
1598 pos.ty += rand() % 24 - 12;
1599 // Find a free spot.
1600 Tile_coord dest = Map_chunk::find_spot(pos, 3, npc, 1);
1601 if (dest.tx == -1 || !npc->walk_path_to_tile(dest,
1602 gwin->get_std_delay(), rand() % 2000))
1603 // Failed? Try again a little later.
1604 npc->start(250, rand() % 3000);
1605 } else
1606 Combat_schedule::now_what();
1607 }
1608
1609 /*
1610 * Pause/unpause while in combat.
1611 */
1612
toggle_pause()1613 void Combat::toggle_pause(
1614 ) {
1615 if (!paused && mode == original)
1616 return; // Not doing that sort of thing.
1617 if (paused) {
1618 resume(); // Text is probably for debugging.
1619 eman->center_text("Combat resumed");
1620 } else {
1621 gwin->get_tqueue()->pause(SDL_GetTicks());
1622 eman->center_text("Combat paused");
1623 paused = true;
1624 }
1625 }
1626
1627 /*
1628 * Resume.
1629 */
1630
resume()1631 void Combat::resume(
1632 ) {
1633 if (!paused)
1634 return;
1635 gwin->get_tqueue()->resume(SDL_GetTicks());
1636 paused = false;
1637 }
1638