1 // nazghul - an old-school RPG engine
2 // Copyright (C) 2002, 2003 Gordon McNutt
3 //
4 // This program is free software; you can redistribute it and/or modify it
5 // under the terms of the GNU General Public License as published by the Free
6 // Software Foundation; either version 2 of the License, or (at your option)
7 // any later version.
8 //
9 // This program is distributed in the hope that it will be useful, but WITHOUT
10 // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12 // more details.
13 //
14 // You should have received a copy of the GNU General Public License along with
15 // this program; if not, write to the Free Foundation, Inc., 59 Temple Place,
16 // Suite 330, Boston, MA 02111-1307 USA
17 //
18 // Gordon McNutt
19 // gmcnutt@users.sourceforge.net
20 //
21
22 #include "../config.h" /* for USE_SKILLS */
23 #include "ctrl.h"
24 #include "dice.h"
25 #include "event.h"
26 #include "foogod.h"
27 #include "cmd.h"
28 #include "cmdwin.h"
29 #include "map.h"
30 #include "place.h"
31 #include "player.h"
32 #include "Party.h"
33 #include "character.h"
34 #include "combat.h"
35 #include "terrain.h"
36 #include "sched.h"
37 #include "session.h"
38 #include "log.h"
39 #include "factions.h"
40 #include "kern_intvar.h"
41 #include "nazghul.h" // for DeveloperMode
42
43 #ifndef CONFIG_DIAGONAL_MOVEMENT
44 #define CONFIG_DIAGONAL_MOVEMENT 1
45 #endif
46
47 int G_latency_start = 0;
48 int G_turnaround_start = 0;
49 int G_turnaround = 0;
50
ctrl_party_key_handler(struct KeyHandler * kh,int key,int keymod)51 static int ctrl_party_key_handler(struct KeyHandler *kh, int key, int keymod)
52 {
53 int dir;
54
55 class PlayerParty *party = (class PlayerParty*)kh->data;
56
57 Session->subject = player_party;
58
59 G_latency_start = SDL_GetTicks();
60
61 /* Commands which are only enabled in developer mode */
62 if (DeveloperMode) {
63 switch (key) {
64
65 case KEY_CTRL_T:
66 cmd_terraform(party->getPlace(), party->getX(), party->getY());
67 break;
68
69 case KEY_CTRL_O:
70 cmd_save_current_place(party->getPlace());
71 break;
72
73 case KEY_CTRL_Z:
74 mapTogglePeering();
75 break;
76 }
77 }
78
79 switch (key) {
80
81 #if CONFIG_DIAGONAL_MOVEMENT
82 case KEY_NORTHWEST:
83 case KEY_NORTHEAST:
84 case KEY_SOUTHWEST:
85 case KEY_SOUTHEAST:
86 #endif /* CONFIG_DIAGONAL_MOVEMENT */
87 case KEY_NORTH:
88 case KEY_WEST:
89 case KEY_EAST:
90 case KEY_SOUTH:
91 {
92 dir = keyToDirection(key);
93 party->move(directionToDx(dir), directionToDy(dir));
94 mapSetDirty();
95 dir = keyToDirection(key);
96 }
97 break;
98
99 #if CONFIG_DIAGONAL_MOVEMENT
100 case KEY_SHIFT_NORTHWEST:
101 case KEY_SHIFT_NORTHEAST:
102 case KEY_SHIFT_SOUTHWEST:
103 case KEY_SHIFT_SOUTHEAST:
104 #endif /* CONFIG_DIAGONAL_MOVEMENT */
105 case KEY_SHIFT_NORTH:
106 case KEY_SHIFT_EAST:
107 case KEY_SHIFT_SOUTH:
108 case KEY_SHIFT_WEST:
109
110 // ----------------------------------------------------
111 // Pan the camera.
112 // ----------------------------------------------------
113
114 key &= ~KEY_SHIFT; /* clear shift bit */
115 dir = keyToDirection(key);
116 mapMoveCamera(directionToDx(dir), directionToDy(dir));
117 mapSetDirty();
118 break;
119
120 case 'a':
121 cmdAttack();
122 break;
123 case 'b':
124 party->board_vehicle();
125 break;
126 case 'c':
127 cmdCastSpell(NULL);
128 break;
129 case 'f':
130 cmdFire();
131 break;
132 case 'g':
133 cmdGet(party);
134 break;
135 case 'h':
136 // SAM: Adding (H)andle command...
137 cmdHandle(NULL);
138 break;
139 case 'k':
140 cmd_camp_in_wilderness(party);
141 break;
142 case 'l':
143 cmdLoiter(party);
144 break;
145 case 'm':
146 cmdMixReagents(NULL);
147 break;
148 case 'n':
149 cmdNewOrder();
150 break;
151 case 'o':
152 cmdOpen(NULL);
153 break;
154 case 'q':
155 cmdQuit();
156 break;
157 case 'r':
158 cmdReady(NULL);
159 break;
160 case 's':
161 cmdSearch(0);
162 break;
163 case 't':
164 cmdTalk(NULL);
165 break;
166 case 'u':
167 cmdUse(NULL, CMD_SELECT_MEMBER|CMD_PRINT_MEMBER);
168 break;
169 #ifdef USE_SKILLS
170 case 'y':
171 cmdYuse(NULL);
172 break;
173 #endif
174 case 'x':
175 cmdXamine(party);
176 break;
177 case 'z':
178 cmdZtats(party->getMemberAtIndex(0));
179 break;
180 case '@':
181 // SAM: 'AT' command for party-centric information
182 cmdAT(NULL);
183 break;
184 case ' ':
185 party->endTurn();
186 log_msg("Pass");
187 break;
188 case '>':
189 // This key was chosen to be a cognate for '>' in
190 // NetHack and other roguelike games.
191 cmdZoomIn();
192 party->endTurn();
193 break;
194 case '?':
195 cmdHelp();
196 break;
197
198 case KEY_CTRL_S:
199 cmdSave();
200 break;
201
202 case KEY_CTRL_R:
203 cmdReload();
204 Session->subject = NULL;
205 return true;
206
207 case SDLK_F10:
208 cmdSettings();
209 break;
210
211 default:
212 break;
213 } // switch(key)
214
215 /* Prep cmdwin for next prompt */
216 cmdwin_clear();
217
218 Session->subject = NULL;
219
220 party->absorbMemberAPDebt();
221
222 /* Return true when done processing commands. */
223 if(!party->isTurnEnded())
224 cmdwin_push("Party [%d ap]:",party->getActionPoints());
225
226 return party->isTurnEnded();
227
228 }
229
ctrl_wander(class Object * object)230 void ctrl_wander(class Object *object)
231 {
232 // *** left off here ***
233
234 int dx = 0, dy = 0;
235
236 if (object->isStationary())
237 return;
238
239 /* Roll for direction */
240 dx = rand() % 3 - 1;
241 if (!dx)
242 dy = rand() % 3 - 1;
243
244 if (dx || dy) {
245
246 int newx, newy;
247 newx = object->getX() + dx;
248 newy = object->getY() + dy;
249
250 if (! object->canWanderTo(newx, newy))
251 return;
252
253 // ------------------------------------------------------------
254 // Do a quick check here if this would take the character off
255 // the map. If so, then don't do it. Can't have NPC's wandering
256 // off out of town...
257 // ------------------------------------------------------------
258
259 if (place_off_map(object->getPlace(), object->getX() + dx,
260 object->getY() + dy) ||
261 place_is_hazardous(object->getPlace(), object->getX() + dx,
262 object->getY() + dy))
263 return;
264
265 object->move(dx, dy);
266 }
267 }
268
ctrl_party_ai(class Party * party)269 void ctrl_party_ai(class Party *party)
270 {
271 int d;
272
273 /* Check if this party is friendly to the player or if the player is
274 * not around */
275 if (! are_hostile(party, player_party) ||
276 Place != party->getPlace()) {
277 // This party is friendly to the player, so just wander for now
278 // (later I'll add schedules).
279 ctrl_wander(party);
280 return;
281 }
282
283 /* Check if the player is _on this spot_. Yes, this can happen under
284 * current game rules. If a player enters a portal and an npc is on the
285 * destination then... */
286 if (party->getX() == player_party->getX() &&
287 party->getY() == player_party->getY()) {
288
289 struct move_info info;
290 struct combat_info cinfo;
291
292 memset(&info, 0, sizeof(info));
293 info.place = party->getPlace();
294 info.x = party->getX();
295 info.y = party->getY();
296 info.dx = party->getDx();
297 info.dy = party->getDy();
298 info.px = player_party->getX();
299 info.py = player_party->getY();
300 info.npc_party = party;
301
302 if (!info.dx && !info.dy)
303 info.dx = 1;
304 else if (info.dx && info.dy)
305 info.dy = 0;
306
307 memset(&cinfo, 0, sizeof(cinfo));
308 cinfo.defend = true;
309 cinfo.move = &info;
310
311 combat_enter(&cinfo);
312 party->endTurn();
313 return;
314 }
315
316 /* get distance to player */
317 d = place_walking_distance(party->getPlace(), party->getX(),
318 party->getY(),
319 player_party->getX(), player_party->getY());
320
321 /* if adjacent attack */
322 if (1==d) {
323 int dx=0, dy=0;
324 place_get_direction_vector(party->getPlace(),
325 party->getX(), party->getY(),
326 player_party->getX(), player_party->getY(),
327 &dx, &dy);
328 if (party->attackPlayer(dx, dy)) {
329 return;
330 }
331 }
332
333 if (d > party->getVisionRadius()) {
334 ctrl_wander(party);
335 return;
336 }
337
338 if (d > 1 && party->attack_with_ordnance(d)) {
339 return;
340 }
341
342 if (!party->gotoSpot(player_party->getX(), player_party->getY())) {
343 ctrl_wander(party);
344 return;
345 }
346 }
347
ctrl_calc_to_hit(class Character * character,class ArmsType * weapon,int penalty)348 static int ctrl_calc_to_hit(class Character *character,
349 class ArmsType *weapon,
350 int penalty)
351 {
352 int base = dice_roll("1d20");
353 int weaponBonus = dice_roll(weapon->getToHitDice());
354 int attackBonus = character->getAttackBonus(weapon);
355 int val = base + weaponBonus + attackBonus + penalty;
356
357 log_continue("to-hit: %d=%d+%d+%d", val, base, weaponBonus,
358 attackBonus);
359 if (penalty >= 0) {
360 log_continue("+");
361 }
362 log_continue("%d\n", penalty);
363
364 return val;
365 }
366
ctrl_calc_to_defend(class Character * target)367 static int ctrl_calc_to_defend(class Character *target)
368 {
369 int base = target->getDefend();
370 int bonus = target->getAvoidBonus();
371 int val = base + bonus;
372
373 log_continue("to-def: %d=%d+%d\n", val, base, bonus);
374
375 return val;
376 }
377
ctrl_calc_damage(class Character * character,class ArmsType * weapon,char critical)378 static int ctrl_calc_damage(class Character *character,
379 class ArmsType *weapon,
380 char critical)
381 {
382 int weaponDamage = dice_roll(weapon->getDamageDice());
383 int characterBonus = character->getDamageBonus(weapon);
384 int memberBonus = 0;
385 int criticalBonus = 0;
386
387 if (character->isPlayerControlled()) {
388 memberBonus = dice_roll("1d4");
389 }
390
391 if (critical) {
392 criticalBonus = character->getDamageBonus(weapon);
393 }
394
395 int val = weaponDamage + characterBonus + criticalBonus + memberBonus;
396
397 log_continue("damage: %d=%d+%d", val, weaponDamage, characterBonus);
398 if (memberBonus) {
399 log_continue("+%d", memberBonus);
400 }
401 if (criticalBonus) {
402 log_continue("+%d", criticalBonus);
403 }
404 log_continue("\n");
405
406 return val;
407 }
408
ctrl_calc_armor(class Character * target,int critical)409 static int ctrl_calc_armor(class Character *target, int critical)
410 {
411 int armor = 0;
412
413 if (! critical) {
414 armor = target->getArmor();
415 }
416
417 log_continue(" armor: %d\n", armor);
418
419 return armor;
420 }
421
ctrl_attack_done(class Character * character,class ArmsType * weapon,class Character * target)422 static void ctrl_attack_done(class Character *character, class ArmsType *weapon,
423 class Character *target)
424 {
425 character->runHook(OBJ_HOOK_ATTACK_DONE, "pp", weapon, target);
426 character->useAmmo(weapon);
427 }
428
ctrl_do_attack(class Character * character,class ArmsType * weapon,class Character * target,int to_hit_penalty)429 void ctrl_do_attack(class Character *character, class ArmsType *weapon,
430 class Character *target, int to_hit_penalty)
431 {
432 int hit;
433 int def;
434 int damage;
435 int armor;
436 int critical = 0;
437 int misx;
438 int misy;
439 bool miss;
440
441 /* Reduce the diplomacy rating between the attacker's and target's
442 * factions */
443 harm_relations(character, target);
444
445 log_begin("^c%c%s^cw attacks ^c%c%s^cw with %s: "
446 , (are_hostile(character, player_party)?'r':'g')
447 , character->getName()
448 , (are_hostile(target, player_party)?'r':'g')
449 , target->getName()
450 , weapon->getName()
451 );
452
453 if (weapon->canOnAttack())
454 {
455 weapon->onAttack(NULL,character);
456 }
457
458 miss = ! weapon->fire(target, character->getX(), character->getY(), &misx, &misy);
459 ctrl_attack_done(character, weapon, target);
460
461 if (miss)
462 {
463 log_end("obstructed!");
464 weapon->fireHitLoc(character, NULL, character->getPlace(),misx,misy,-1);
465 return;
466 }
467
468 /* Roll to hit. */
469 log_continue("\n");
470 hit = ctrl_calc_to_hit(character, weapon, to_hit_penalty);
471 def = ctrl_calc_to_defend(target);
472 if (hit < def)
473 {
474 log_end("evaded!");
475 weapon->fireHitLoc(character, NULL, character->getPlace(),misx,misy,-1);
476 return;
477 }
478
479 /* roll for critical hit */
480 if (20 <= (dice_roll("1d20")
481 + logBase2(character->getBaseAttackBonus(weapon)))) {
482 critical = 1;
483 log_continue("^c+yCritical hit!^c-\n");
484 }
485
486 /* roll for damage */
487 damage = ctrl_calc_damage(character, weapon, critical);
488 armor = ctrl_calc_armor(target, critical);
489 damage -= armor;
490 damage = max(damage, 0);
491
492 if (damage <= 0)
493 {
494 log_end("blocked!");
495 weapon->fireHitLoc(character, target, character->getPlace(),misx,misy,0);
496 return;
497 }
498
499 // the damage() method may destroy the target, so bump the refcount
500 // since we still need the target through the end of this function
501 obj_inc_ref(target);
502 target->damage(damage);
503
504 weapon->fireHitLoc(character, target, character->getPlace(),misx,misy,damage);
505
506 log_end("%s!", target->getWoundDescription());
507
508 /* If the target was killed then add xp to the attacker */
509 if (target->isDead()) {
510 character->addExperience(target->getExperienceValue());
511 }
512
513 obj_dec_ref(target);
514 }
515
516
517 static class Character *
ctrl_get_interfering_hostile(class Character * character)518 ctrl_get_interfering_hostile(class Character *character)
519 {
520 static int dx_to_neighbor[] = { 0, -1, 0, 1 };
521 static int dy_to_neighbor[] = { -1, 0, 1, 0 };
522 class Character *near;
523 int i;
524
525 for (i = 0; i < 4; i++) {
526 near = (class Character*)place_get_object(
527 character->getPlace(),
528 character->getX() + dx_to_neighbor[i],
529 character->getY() + dy_to_neighbor[i],
530 being_layer);
531
532 if (near &&
533 are_hostile(near, character) &&
534 !near->isIncapacitated()) {
535 return near;
536 }
537 }
538
539 return NULL;
540 }
541
542 struct nearest_hostile_info {
543 class Character *origin;
544 class Character *nearest;
545 int min_distance;
546 int range;
547 struct list suggest; /* for ctrl_attack_ui */
548 };
549
ctrl_suggest_visitor(class Object * obj,void * data)550 static void ctrl_suggest_visitor(class Object *obj, void *data)
551 {
552 struct nearest_hostile_info *info = (struct nearest_hostile_info*)data;
553 class Character *npc = 0;
554 int dist = 0;
555 struct location_list *entry = 0;
556
557 if (being_layer!=obj->getLayer())
558 return;
559
560 npc = (class Character*)obj;
561
562 if (! are_hostile(npc, info->origin))
563 return;
564
565 if (! npc->isVisible() && ! Reveal)
566 return;
567
568 if (! place_in_los(info->origin->getPlace(),
569 info->origin->getX(),
570 info->origin->getY(),
571 info->origin->getPlace(),
572 obj->getX(),
573 obj->getY()))
574 return;
575
576 if (info->range < (dist = place_flying_distance(info->origin->getPlace(),
577 info->origin->getX(),
578 info->origin->getY(),
579 obj->getX(),
580 obj->getY())))
581 return;
582
583 /* Add it to the list */
584 entry = (struct location_list*)malloc(sizeof(*entry));
585 assert(entry);
586 entry->x = obj->getX();
587 entry->y = obj->getY();
588 list_add_tail(&info->suggest, &entry->list);
589
590 /* Keep track of the nearest as we go. */
591 if (! info->nearest
592 || (dist < info->min_distance)) {
593 info->min_distance = dist;
594 info->nearest = npc;
595 }
596
597 printf("Added %s at [%d %d]\n", obj->getName(), obj->getX(), obj->getY());
598 }
599
ctrl_del_suggest_list(struct list * head)600 static void ctrl_del_suggest_list(struct list *head)
601 {
602 struct list *entry = head->next;
603 while (entry != head) {
604 struct location_list *tmp =
605 (struct location_list*)entry;
606 entry = entry->next;
607 list_remove(&tmp->list);
608 free(tmp);
609 }
610 }
611
612
ctrl_attack_ui(class Character * character)613 static void ctrl_attack_ui(class Character *character)
614 {
615 int x;
616 int y;
617 class ArmsType *weapon;
618 class Character *target;
619 struct terrain *terrain;
620 class Object *mech;
621
622 // If in follow mode, when the leader attacks automatically switch to
623 // turn-based mode.
624 if (player_party->getPartyControlMode() == PARTY_CONTROL_FOLLOW &&
625 player_party->getSize() > 1) {
626 log_msg("Switching from Follow to Round Robin Mode.\n");
627 player_party->enableRoundRobinMode();
628 }
629
630 // Loop over all readied weapons
631 int armsIndex=0;
632 int this_is_nth_attack = 1;
633 int this_wpn_AP;
634 for (weapon = character->enumerateWeapons(&armsIndex); weapon != NULL;
635 weapon = character->getNextWeapon(&armsIndex)) {
636 struct nearest_hostile_info info;
637
638 // prompt the user
639 cmdwin_clear();
640 cmdwin_spush("Attack");
641
642 // Determine AP for this (potential) attack,
643 // as a discount may be applied for dual weapon attacks and such,
644 // and we need the discounted figure to display in the UI:
645 this_wpn_AP = weapon->getRequiredActionPoints();
646 if (this_is_nth_attack == 1) {
647 // 1st weapon attack (usual case), no AP cost adjustments
648 }
649 else if (this_is_nth_attack == 2) {
650 // 2nd weapon attack (dual weapon, 2nd weapon)
651 int mult = kern_intvar_get("AP_MULT12:second_wpn_attack");
652 this_wpn_AP = (int) (this_wpn_AP * mult) / 12;
653 }
654 else if (this_is_nth_attack >= 3) {
655 // 3rd+ weapon attack (unusual case for multi-limbed beings...)
656 int mult = kern_intvar_get("AP_MULT12:third_plus_wpn_attack");
657 this_wpn_AP = (int) (this_wpn_AP * mult) / 12;
658 }
659
660 //log_msg("DEBUG: wpn = %s (AP=%d-->%d), remaining AP=%d\n",
661 // weapon->getName(), weapon->getRequiredActionPoints(), this_wpn_AP,
662 // character->getActionPoints() );
663
664 if (weapon->isMissileWeapon()) {
665 // SAM: It would be nice to get ammo name, too...
666 cmdwin_spush("%s (%d AP, range %d, %d ammo)",
667 weapon->getName(),
668 this_wpn_AP,
669 weapon->getRange(),
670 character->hasAmmo(weapon));
671 }
672 else if (weapon->isThrownWeapon()) {
673 // SAM: It would be nice to get ammo name, too...
674 cmdwin_spush("%s (%d AP, range %d, %d left)",
675 weapon->getName(),
676 this_wpn_AP,
677 weapon->getRange(),
678 character->hasAmmo(weapon));
679 }
680 else {
681 cmdwin_spush("%s (%d AP, reach %d)",
682 weapon->getName(),
683 this_wpn_AP,
684 weapon->getRange() );
685 }
686
687
688 // Check ammo
689 if (!character->hasAmmo(weapon)) {
690 cmdwin_spush("no ammo!");
691 log_msg("%s: %s - no ammo!\n",
692 character->getName(),
693 weapon->getName());
694 continue;
695 }
696
697 /* Check the four adjacent tiles for hostiles who will
698 * interfere with a missile weapon */
699 if (weapon->isMissileWeapon()) {
700 class Character *near;
701 near = ctrl_get_interfering_hostile(character);
702 if (near) {
703 cmdwin_spush("blocked!");
704 log_msg("%s: %s - blocked by %s!\n",
705 character->getName(),
706 weapon->getName(),
707 near->getName());
708 continue;
709 }
710 }
711
712 /* Build the list of suggested targets. */
713 memset(&info, 0, sizeof(info));
714 info.origin = character;
715 info.range = weapon->getRange();
716 list_init(&info.suggest);
717 place_for_each_object(character->getPlace(),
718 ctrl_suggest_visitor,
719 &info);
720
721 /* Get the default target. It's important to do this every time
722 * the loop because the last iteration may have killed the
723 * previous target, or it may be out of range of the
724 * weapon. The getAttackTarget routine will reevaluate the
725 * current target. */
726 target = character->getAttackTarget(weapon);
727
728 /* If the target is the character that means there is no
729 * default target now. Select the nearest one is the new
730 * default. */
731 if (target==character) {
732 if (! list_empty(&info.suggest)) {
733 assert(info.nearest);
734 target = info.nearest;
735 }
736 }
737
738 assert(target);
739
740 // select the target location
741 x = target->getX();
742 y = target->getY();
743
744 // SAM:
745 // select_target() might be a more elegant place to put
746 // logic to prevent (or require confirm of) attacking self,
747 // party members, etc.
748 if (-1 == select_target(character->getX(),
749 character->getY(),
750 &x, &y,
751 weapon->getRange(),
752 &info.suggest)) {
753 cmdwin_spush("abort!");
754 continue;
755 }
756
757 /* Cleanup the suggestion list */
758 ctrl_del_suggest_list(&info.suggest);
759
760 // Find the new target under the cursor
761 target = (class Character *)
762 place_get_object(character->getPlace(), x, y,
763 being_layer);
764 character->setAttackTarget(target);
765 if (target == NULL) {
766
767 /* Attack the terrain */
768 terrain = place_get_terrain(character->getPlace(),
769 x, y);
770
771 cmdwin_spush(" %s", terrain->name);
772
773 log_begin("%s: %s - ", character->getName()
774 , weapon->getName()
775 );
776
777 int misx = x;
778 int misy = y;
779
780 this_is_nth_attack++;
781
782 if (weapon->canOnAttack())
783 {
784 weapon->onAttack(NULL,character);
785 }
786
787 bool miss = ! weapon->fire(character->getPlace(),
788 character->getX(),
789 character->getY(),
790 &misx,
791 &misy);
792
793 if (miss)
794 {
795 log_end("obstructed!");
796 }
797
798 weapon->fireHitLoc(character, NULL, character->getPlace(),misx,misy,-1);
799
800 ctrl_attack_done(character, weapon, NULL);
801
802 if (!miss)
803 {
804 /* Check for a mech */
805 mech = place_get_object(character->getPlace(), x, y,
806 mech_layer);
807 if (mech && mech->getName()) {
808 log_end("%s hit!", mech->getName());
809 mech->attack(character);
810 } else {
811 log_end("%s hit!", terrain->name);
812 }
813 }
814
815 }
816 else if (target == character) {
817 /* Targeting the self is taken to mean "switch to my
818 * next weapon". This allows players to quickly jump to
819 * the next weapon if no target is in range and they
820 * aren't interested in attacking the ground. */
821 cmdwin_spush("skip weapon!");
822 // no attack made, so don't increment this_is_nth_attack
823 continue;
824 } else {
825 // confirmed_attack_ally:
826
827 // in combat all npc parties and the player party
828 // should be removed, so only characters reside at the
829 // being layer
830 assert(target->isType(CHARACTER_ID));
831
832 cmdwin_spush("%s", target->getName());
833
834
835 // If the npc is not hostile then get player confirmation.
836 if (! are_hostile(character, target)) {
837 int yesno;
838 cmdwin_spush("attack non-hostile");
839 cmdwin_spush("<y/n>");
840 getkey(&yesno, yesnokey);
841 cmdwin_pop();
842 if (yesno == 'n') {
843 cmdwin_spush("no");
844 continue;
845 }
846 cmdwin_spush("yes");
847 }
848
849 // Strike the target
850 this_is_nth_attack++;
851 ctrl_do_attack(character, weapon, target, character->getToHitPenalty());
852
853 // If we hit a party member then show their new hit
854 // points in the status window
855 if (target->isPlayerControlled())
856 statusRepaint();
857 }
858
859 /* Warn the user if out of ammo. Originally this code used
860 * character->getCurrentWeapon() instead of weapon, that may
861 * still be okay now that getToHitPenalty() is outside of this
862 * loop, but not sure why it' would be preferred. */
863 if (! character->hasAmmo(weapon))
864 log_msg("%s : %s now out of ammo\n",
865 character->getName(), weapon->getName());
866
867 character->decActionPoints(this_wpn_AP);
868 //log_msg("DEBUG: after attack, used %d, remaining AP=%d\n",
869 // this_wpn_AP, character->getActionPoints() );
870 }
871
872 }
873
ctrl_move_character(class Character * character,int dir)874 static void ctrl_move_character(class Character *character, int dir)
875 {
876 enum MoveResult move_result;
877 const char *result = NULL;
878 const char *dirstr = directionToString(dir);
879 int dx;
880 int dy;
881
882 dx = directionToDx(dir);
883 dy = directionToDy(dir);
884
885 /* try to move */
886 move_result = character->move(dx, dy);
887
888 /* recenter (harmless if move failed) */
889 mapCenterView(character->getView(), character->getX(),
890 character->getY());
891
892 /* if moved ok then no message to print */
893 if (MovedOk == move_result) {
894 return;
895 }
896
897 /* otherwise we'll print something */
898 log_begin("");
899
900 switch (move_result) {
901 case OffMap:
902 result = "no place to go!";
903 break;
904 case ExitedMap:
905 result = "exit!";
906 character->endTurn();
907 break;
908 case EngagedEnemy:
909 cmdwin_spush("enter combat!");
910 break;
911 case WasOccupied:
912 result = "occupied!";
913 break;
914 case WasImpassable:
915 {
916 /* If the move failed because something impassable is there
917 * then check for a mech and try to handle it. This is good
918 * enough to automatically open unlocked doors.
919 */
920
921 class Object *mech;
922 int newx, newy;
923
924 newx = character->getX() + dx;
925 newy = character->getY() + dy;
926
927 mech = place_get_object(character->getPlace(), newx, newy,
928 mech_layer);
929
930 if (mech && mech->getObjectType()->canHandle())
931 {
932 mech->getObjectType()->handle(mech, character);
933 character->decActionPoints(kern_intvar_get("AP_COST:handle_mechanism"));
934 mapSetDirty();
935 result = "handled!";
936 }
937 else if (mech && mech->getObjectType()->canBump())
938 {
939 result = "";
940 mech->getObjectType()->bump(mech, character);
941 mapSetDirty();
942 }
943 else
944 {
945 result = "impassable!";
946 }
947 }
948 break;
949 case SlowProgress:
950 result = "slow progress!";
951 break;
952 case SwitchedOccupants:
953 result = "switch!";
954 break;
955 case NotFollowMode:
956 result = "must be in follow mode!";
957 break;
958 case CantRendezvous:
959 result = "party can't rendezvous!";
960 break;
961 case CouldNotSwitchOccupants:
962 result = "can't switch places!";
963 break;
964 default:
965 break;
966 }
967
968 log_continue("%s: %s - %s", character->getName(), dirstr, result);
969 log_end("");
970 }
971
ctrl_character_key_handler(struct KeyHandler * kh,int key,int keymod)972 static int ctrl_character_key_handler(struct KeyHandler *kh, int key,
973 int keymod)
974 {
975 extern int G_latency_start;
976 int dir;
977
978 class Character *character = (class Character *) kh->data;
979 class Object *portal;
980
981 G_latency_start = SDL_GetTicks();
982
983 Session->subject = character;
984
985 /* First process commands which should not be affected by the keystroke
986 * hooks. */
987
988 /* Commands which are only enabled in developer mode */
989 if (DeveloperMode) {
990 switch (key) {
991
992 case KEY_CTRL_T:
993 cmd_terraform(character->getPlace(), character->getX(),
994 character->getY());
995 break;
996
997 case KEY_CTRL_O:
998 cmd_save_current_place(character->getPlace() );
999 break;
1000
1001 case KEY_CTRL_Z:
1002 mapTogglePeering();
1003 break;
1004
1005 case KEY_CTRL_E:
1006 cmdDeveloperEval(Session);
1007 break;
1008 }
1009 }
1010
1011 switch (key) {
1012
1013 case KEY_CTRL_S:
1014 cmdSave();
1015 break;
1016
1017 case KEY_CTRL_R:
1018 cmdReload();
1019 Session->subject = NULL;
1020 return true;
1021
1022 case SDLK_F10:
1023 cmdSettings();
1024 break;
1025
1026 case 'f':
1027 if (cmdToggleFollowMode()) {
1028 if (! character->isLeader()) {
1029 character->endTurn();
1030 }
1031 } else {
1032 foogod_set_title("Round Robin: %s", character->getName());
1033 foogodRepaint();
1034 }
1035 break;
1036
1037 case SDLK_1:
1038 case SDLK_2:
1039 case SDLK_3:
1040 case SDLK_4:
1041 case SDLK_5:
1042 case SDLK_6:
1043 case SDLK_7:
1044 case SDLK_8:
1045 case SDLK_9:
1046 if (cmdSetSoloMode(key - SDLK_1)) {
1047 character->endTurn();
1048 }
1049 break;
1050
1051 case SDLK_0:
1052 // ----------------------------------------------------
1053 // Exit solo mode.
1054 // ----------------------------------------------------
1055 player_party->enableRoundRobinMode();
1056 //in general, switching to roundrobin mode means
1057 //you need all the turns you gan get, so dont waste this one
1058 //character->endTurn();
1059 break;
1060
1061 default:
1062 break;
1063 }
1064
1065 // Don't run the keystroke hook until we get here. Keystroke
1066 // effects should not affect the special ctrl charactes
1067 // (otherwise something like being stuck in a web can prevent a
1068 // user from reloading a game).
1069 character->runHook(OBJ_HOOK_KEYSTROKE, 0);
1070 if (character->isTurnEnded()) {
1071 Session->subject = NULL;
1072 return true;
1073 }
1074
1075
1076 switch (key) {
1077
1078 #if CONFIG_DIAGONAL_MOVEMENT
1079 case KEY_NORTHWEST:
1080 case KEY_NORTHEAST:
1081 case KEY_SOUTHWEST:
1082 case KEY_SOUTHEAST:
1083 #endif /* CONFIG_DIAGONAL_MOVEMENT */
1084 case KEY_NORTH:
1085 case KEY_WEST:
1086 case KEY_EAST:
1087 case KEY_SOUTH:
1088 dir = keyToDirection(key);
1089 ctrl_move_character(character, dir);
1090 break;
1091
1092
1093 #if CONFIG_DIAGONAL_MOVEMENT
1094 case KEY_SHIFT_NORTHWEST:
1095 case KEY_SHIFT_NORTHEAST:
1096 case KEY_SHIFT_SOUTHWEST:
1097 case KEY_SHIFT_SOUTHEAST:
1098 #endif /* CONFIG_DIAGONAL_MOVEMENT */
1099 case KEY_SHIFT_NORTH:
1100 case KEY_SHIFT_EAST:
1101 case KEY_SHIFT_SOUTH:
1102 case KEY_SHIFT_WEST:
1103
1104 // ----------------------------------------------------
1105 // Pan the camera.
1106 // ----------------------------------------------------
1107
1108 key &= ~KEY_SHIFT; /* clear shift bit */
1109 dir = keyToDirection(key);
1110 mapMoveCamera(directionToDx(dir), directionToDy(dir));
1111 mapSetDirty();
1112 break;
1113
1114
1115 case 'a':
1116 ctrl_attack_ui(character);
1117 break;
1118
1119 case 'c':
1120 cmdCastSpell(character);
1121 break;
1122
1123 case 'd':
1124 cmdDrop(character);
1125 break;
1126 case 'e':
1127
1128 // ----------------------------------------------------
1129 // Enter a portal. For this to work a portal must exist
1130 // here, the party must be in follow mode, and all the
1131 // party members must be able to rendezvous at this
1132 // character's position.
1133 // ----------------------------------------------------
1134
1135 portal = place_get_object(character->getPlace(),
1136 character->getX(),
1137 character->getY(),
1138 mech_layer);
1139 if (!portal || !portal->canEnter()) {
1140 break;
1141 }
1142
1143 log_begin_group();
1144 portal->enter(character);
1145 log_end_group();
1146
1147 break;
1148
1149
1150 case 'g':
1151 cmdGet(character);
1152 break;
1153 case 'h':
1154 cmdHandle(character);
1155 break;
1156 case 'k':
1157 cmd_camp_in_town(character);
1158 break;
1159 case 'l':
1160 cmdLoiter(character);
1161 break;
1162 case 'm':
1163 cmdMixReagents(character);
1164 break;
1165 case 'n':
1166 cmdNewOrder();
1167 break;
1168 case 'o':
1169 cmdOpen(character);
1170 break;
1171 case 'q':
1172 cmdQuit();
1173 break;
1174 case 'r':
1175 if (player_party->getPartyControlMode()==PARTY_CONTROL_FOLLOW)
1176 cmdReady(NULL);
1177 else
1178 cmdReady(character);
1179 break;
1180 case 's':
1181 cmdSearch(character);
1182 break;
1183 case 't':
1184 cmdTalk(character);
1185 break;
1186 case 'u':
1187 cmdUse(character, 0);
1188 break;
1189 case 'x':
1190 cmdXamine(character);
1191 break;
1192 #ifdef USE_SKILLS
1193 case 'y':
1194 cmdYuse(character);
1195 break;
1196 #endif
1197 case 'z':
1198 cmdZtats(character);
1199 break;
1200 case '@':
1201 cmdAT(character);
1202 break;
1203 case ' ':
1204 log_msg("Pass");
1205 character->endTurn();
1206 break;
1207 case '?':
1208 cmdHelp();
1209 break;
1210
1211 case SDLK_ESCAPE:
1212 // SAM: Removed the ESC-to-exit-combat keybinding.
1213 // Using ESC for both exit-from-UI-things and exit-combat-map
1214 // meant it was easy to exit, and miss your loot.
1215 //
1216 if (!place_is_wilderness_combat(character->getPlace()))
1217 {
1218 // log_msg("");
1219 break;
1220 }
1221
1222 if (place_contains_hostiles(character->getPlace(), character))
1223 {
1224 // log_msg("");
1225 break;
1226 }
1227 log_msg("To exit this combat map, use '<', \nor walk off the edge of the map.");
1228 break;
1229
1230 case '<':
1231 // case SDLK_ESCAPE // SAM: Removed the ESC-to-exit-combat keybinding, see above.
1232 // ----------------------------------------------------
1233 // Quick exit from wilderness combat. The current place
1234 // must be the special wildernss combat place and it
1235 // must be empty of hostile characters or this fails.
1236 // ----------------------------------------------------
1237
1238 if (!place_is_wilderness_combat(character->getPlace()))
1239 {
1240 log_msg("Must use an exit!");
1241 break;
1242 }
1243
1244 if (place_contains_hostiles(character->getPlace(),
1245 character))
1246 {
1247 log_msg("Not while foes remain!");
1248 break;
1249 }
1250
1251 // ----------------------------------------------------
1252 // This next call is to make sure the "Victory" and
1253 // "Defeated" messages are printed properly. I don't
1254 // *think* it has any other interesting side-effects in
1255 // this case.
1256 // ----------------------------------------------------
1257
1258 combat_analyze_results_of_last_turn();
1259
1260 // ----------------------------------------------------
1261 // Remove all party members.
1262 // ----------------------------------------------------
1263
1264 player_party->removeMembers();
1265
1266 character->endTurn();
1267
1268 break;
1269
1270 default:
1271 break;
1272 }
1273
1274 cmdwin_clear();
1275
1276 if (!character->isTurnEnded()) {
1277 cmdwin_push("%s:", character->getName());
1278 }
1279
1280 Session->subject = NULL;
1281
1282 return character->isTurnEnded();
1283 }
1284
ctrl_too_close_to_target(class Character * character,class Character * target)1285 static int ctrl_too_close_to_target(class Character *character,
1286 class Character *target)
1287 {
1288 int distance;
1289
1290 distance = place_flying_distance(character->getPlace(),
1291 character->getX(), character->getY(),
1292 target->getX(), target->getY());
1293
1294 if (distance > 1)
1295 return 0;
1296
1297 int armsIndex=0;
1298 for (class ArmsType * weapon = character->enumerateWeapons(&armsIndex);
1299 weapon != NULL; weapon = character->getNextWeapon(&armsIndex)) {
1300
1301 /* if npc has at least one melee weapon then not too close */
1302 if (character->hasAmmo(weapon) &&
1303 ! weapon->isMissileWeapon()) {
1304 return 0;
1305 }
1306 }
1307
1308 return 1;
1309 }
1310
1311 #if 0
1312 static void ctrl_unready_all_weapons(class Character *character)
1313 {
1314 int armsIndex = 0;
1315 for (class ArmsType * weapon = character->enumerateWeapons(&armsIndex);
1316 weapon != NULL; weapon = character->getNextWeapon(&armsIndex)) {
1317 character->unready(weapon);
1318 }
1319 }
1320
1321 static void ctrl_ready_melee_weapons(class Character *character)
1322 {
1323 class Container *container = character->getContainer();
1324 struct inv_entry *ie;
1325 }
1326
1327 static int ctrl_switch_to_melee_weapon(class Character *character)
1328 {
1329 /* unready all weapons */
1330 ctrl_unready_all_weapons(character);
1331
1332 /* ready any melee weapons */
1333 ctrl_ready_melee_weapons(character);
1334
1335 /* ready any shields if hands are left open */
1336
1337 /* ready any missile weapons if hands are left open */
1338
1339 }
1340 #endif
1341
ctrl_try_move_toward(class Character * character,int dx,int dy)1342 static int ctrl_try_move_toward(class Character *character, int dx, int dy)
1343 {
1344 int x = character->getX() + dx;
1345 int y = character->getY() + dy;
1346
1347 if (place_is_passable(character->getPlace(), x, y, character, 0)) {
1348 switch (character->move(dx, dy)) {
1349 case MovedOk:
1350 case ExitedMap:
1351 case SwitchedOccupants:
1352 return 1;
1353 default:
1354 return 0;
1355 }
1356 }
1357
1358 return 0;
1359 }
1360
ctrl_move_away_from_target(class Character * character,class Character * target)1361 static int ctrl_move_away_from_target(class Character *character,
1362 class Character *target)
1363 {
1364 /* first try moving directly away from the target */
1365 int dx = character->getX() - target->getX();
1366 int dy = character->getY() - target->getY();
1367
1368 if (character->isStationary())
1369 return 0;
1370
1371 /* normalize vector */
1372 dx = dx > 0 ? 1 : (dx < 0 ? -1 : 0);
1373 dy = dy > 0 ? 1 : (dy < 0 ? -1 : 0);
1374
1375 /* disallow diagonal moves */
1376 if (dx == 0 || dy == 0) {
1377 if (ctrl_try_move_toward(character, dx, dy))
1378 return 1;
1379 }
1380
1381 /* try another vector */
1382 if (dx != 0) {
1383 if (ctrl_try_move_toward(character, 0, 1))
1384 return 1;
1385 if (ctrl_try_move_toward(character, 0, -1))
1386 return 1;
1387 }
1388
1389 if (dy != 0) {
1390 if (ctrl_try_move_toward(character, 1, 0))
1391 return 1;
1392 if (ctrl_try_move_toward(character, -1, 0))
1393 return 1;
1394 }
1395
1396 return 0;
1397 }
1398
ctrl_attack_target(class Character * character,class Character * target)1399 static bool ctrl_attack_target(class Character *character,
1400 class Character *target)
1401 {
1402 int distance;
1403 bool attacked = false;
1404
1405 distance = place_flying_distance(character->getPlace(),
1406 character->getX(), character->getY(),
1407 target->getX(), target->getY());
1408
1409 int armsIndex = 0;
1410 int this_is_nth_attack = 0;
1411 int slowest_attack_AP = 0;
1412 int total_AP = 0;
1413 int this_wpn_AP;
1414 for (class ArmsType * weapon = character->enumerateWeapons(&armsIndex);
1415 weapon != NULL; weapon = character->getNextWeapon(&armsIndex)) {
1416
1417 // log_msg("DEBUG: wpn = %s (AP=%d), remaining AP=%d\n",
1418 // weapon->getName(), weapon->getRequiredActionPoints(), character->getActionPoints() );
1419 if (distance > weapon->getRange()) {
1420 continue;
1421 }
1422
1423 if (!character->hasAmmo(weapon)) {
1424 continue;
1425 }
1426
1427 if (distance <= 1 && weapon->isMissileWeapon()) {
1428 // Handle missile weapon interference
1429 continue;
1430 }
1431
1432 this_is_nth_attack++;
1433 ctrl_do_attack(character, weapon, target, character->getToHitPenalty());
1434
1435 // sum up AP requirement
1436 this_wpn_AP = weapon->getRequiredActionPoints();
1437 if (this_wpn_AP > slowest_attack_AP)
1438 {
1439 slowest_attack_AP = this_wpn_AP;
1440 }
1441 total_AP += this_wpn_AP;
1442 attacked = true;
1443
1444 if (this_is_nth_attack == 1) {
1445 // 1st weapon attack (usual case)
1446 }
1447 else if (this_is_nth_attack == 2) {
1448 // 2st weapon attack (dual weapon, 2nd weapon)
1449 int mult = kern_intvar_get("AP_MULT12:second_wpn_attack");
1450 this_wpn_AP = (int) (this_wpn_AP * mult) / 12;
1451 }
1452 else if (this_is_nth_attack >= 3) {
1453 // 3rd+ weapon attack (unusual case for multi-limbed beings...)
1454 int mult = kern_intvar_get("AP_MULT12:third_plus_wpn_attack");
1455 this_wpn_AP = (int) (this_wpn_AP * mult) / 12;
1456 }
1457 character->decActionPoints(this_wpn_AP);
1458 //log_msg("DEBUG: after attack, used %d, remaining AP=%d\n",
1459 // this_wpn_AP, character->getActionPoints() );
1460 statusRepaint();
1461
1462 if (target->isDead())
1463 break;
1464
1465 // If the AP use is not over the multi-weapon extra allowance, continue:
1466 int threshold = kern_intvar_get("AP_THRESHOLD:multi_attack_overage");
1467 if (character->getActionPoints() + threshold < 0) {
1468 //log_msg("DEBUG: AP = %d, threshold = %d -- breaking multi-attack\n",
1469 // character->getActionPoints(), threshold );
1470 break;
1471 }
1472 }
1473
1474 if (character->needToRearm())
1475 character->armThyself();
1476
1477 return attacked;
1478 }
1479
1480 /* Data structure used by the ctrl_is_valid_target visitor function below */
1481 struct ctrl_select_target_data {
1482 class Character *target; /* best target found so far */
1483 class Character *attacker; /* attacking character */
1484 int distance; /* distance to 'target' */
1485 };
1486
1487 /*****************************************************************************
1488 * ctrl_is_valid_target - check if 'obj' is a valid target for 'attacker'
1489 *****************************************************************************/
ctrl_is_valid_target(class Character * attacker,class Object * obj)1490 static int ctrl_is_valid_target(class Character *attacker, class Object *obj)
1491 {
1492 /* Skip NULL */
1493 if (! obj)
1494 return 0;
1495
1496 /* Skip non-characters */
1497 if (! obj->isType(CHARACTER_ID))
1498 return 0;
1499
1500 /* Skip the attacker */
1501 if (obj == attacker)
1502 return 0;
1503
1504 /* Skip dead beings */
1505 if (obj->isDead())
1506 return 0;
1507
1508 /* Skip non-hostiles */
1509 if (! are_hostile(attacker, (Being*)obj))
1510 return 0;
1511
1512 /* Skip non-visible objects */
1513 if (! attacker->canSee(obj))
1514 return 0;
1515
1516 /* Skip off-map beings (is this even possible?) */
1517 if (! obj->isOnMap())
1518 return 0;
1519
1520 return 1;
1521 }
1522
1523 /*****************************************************************************
1524 * ctrl_select_target_visitor - find and remember the closest valid target for
1525 * an attacker
1526 *****************************************************************************/
ctrl_select_target_visitor(struct node * node,void * parm)1527 static void ctrl_select_target_visitor(struct node *node, void *parm)
1528 {
1529 class Object *obj;
1530 struct ctrl_select_target_data *data;
1531 int distance;
1532
1533 /* Extract the typed variables from the generic parms */
1534 obj = (class Object*)node->ptr;
1535 data = (struct ctrl_select_target_data *)parm;
1536
1537 /* Check if this object makes a valid target */
1538 if (! ctrl_is_valid_target(data->attacker, obj))
1539 return;
1540
1541 /* Compute the distance from attacker to obj */
1542 distance = place_flying_distance(data->attacker->getPlace(),
1543 data->attacker->getX(),
1544 data->attacker->getY(),
1545 obj->getX(),
1546 obj->getY());
1547
1548 /* Memoize the closest target and its distance */
1549 if (distance < data->distance) {
1550 data->distance = distance;
1551 data->target = (class Character*)obj;
1552 }
1553 }
1554
1555 /*****************************************************************************
1556 * ctrl_select_target - use a heuristic to pick a target for an attacker
1557 *****************************************************************************/
ctrl_select_target(class Character * character)1558 static class Character * ctrl_select_target(class Character *character)
1559 {
1560 struct ctrl_select_target_data data;
1561
1562 /* Initialize the search data. */
1563 data.attacker = character;
1564 data.target = NULL;
1565 data.distance = place_max_distance(character->getPlace()) + 1;
1566
1567 /* Search all objects in the current place for targets. */
1568 node_foldr(place_get_all_objects(character->getPlace()),
1569 ctrl_select_target_visitor,
1570 &data);
1571
1572 /* Check if one was found */
1573 if (data.target) {
1574 character->setAttackTarget((class Character*)data.target);
1575 return data.target;
1576 }
1577
1578 /* Try the old one */
1579 if (ctrl_is_valid_target(character,
1580 character->getAttackTarget(NULL)))
1581 return character->getAttackTarget(NULL);
1582
1583 /* No valid targets */
1584 character->setAttackTarget(NULL);
1585 return NULL;
1586 }
1587
1588
ctrl_idle(class Character * character)1589 static void ctrl_idle(class Character *character)
1590 {
1591 class Character *target;
1592
1593 if (character->getAI()) {
1594
1595 /* closure returns true if it handled the turn, otherwise fall
1596 * through to standard AI */
1597 if (closure_exec(character->getAI(), "p", character))
1598 return;
1599
1600 if (character->getActionPoints() <= 0)
1601 return;
1602 }
1603
1604 // -------------------------------------------------------------------
1605 // If they see an enemy they'll engage. Otherwise they just wander
1606 // uselessly within the rectangular area imposed by their schedule (or
1607 // freely if they have no schedule).
1608 // -------------------------------------------------------------------
1609
1610 target = ctrl_select_target(character);
1611 if (!target) {
1612 ctrl_wander(character);
1613 return;
1614 }
1615
1616 // -------------------------------------------------------------------
1617 // A bit confusing here next. If the NPC can't see a target I still let
1618 // them pathfind. Why? Because the target might be "remembered" - maybe
1619 // they were visible last turn and they just stepped out of LOS.
1620 // -------------------------------------------------------------------
1621
1622 // note isOnMap checks so that if something has removed the character
1623 // from the map (or killed it) as a result of its move attempt then
1624 // you should quit having it wander around
1625
1626 if (!character->canSee(target))
1627 {
1628 if (! character->pathfindTo(target->getPlace(),
1629 target->getX(),
1630 target->getY()))
1631 {
1632 if (character->isOnMap())
1633 {
1634 ctrl_wander(character);
1635 }
1636 return;
1637 }
1638 }
1639
1640 if (ctrl_too_close_to_target(character, target)) {
1641 if (! ctrl_move_away_from_target(character, target))
1642 /*if (! ctrl_switch_to_melee_weapon(character))*/
1643 ctrl_wander(character);
1644 return;
1645 }
1646
1647 // -------------------------------------------------------------------
1648 // Then try force.
1649 // -------------------------------------------------------------------
1650
1651 if (!ctrl_attack_target(character, target))
1652 {
1653 if ((! character->pathfindTo(target->getPlace(),
1654 target->getX(),
1655 target->getY()))
1656 && character->isOnMap())
1657 {
1658 ctrl_wander(character);
1659 }
1660 }
1661 }
1662
ctrl_character_ai(class Character * character)1663 void ctrl_character_ai(class Character *character)
1664 {
1665 if (character->isFleeing()
1666 && character->flee()) {
1667 return;
1668 }
1669 ctrl_idle(character);
1670 }
1671
ctrl_character_ui(class Character * character)1672 void ctrl_character_ui(class Character *character)
1673 {
1674 struct KeyHandler kh;
1675 /* Setup cmdwin prompt for first entry to the control loop */
1676 cmdwin_clear();
1677 cmdwin_push("%s:", character->getName());
1678
1679 /* Push the key handler and enter the event loop until character is
1680 * done with turn */
1681 kh.fx = &ctrl_character_key_handler;
1682 kh.data = character;
1683 eventPushKeyHandler(&kh);
1684 G_turnaround = SDL_GetTicks() - G_turnaround_start;
1685 eventHandle();
1686 G_turnaround_start = SDL_GetTicks();
1687 eventPopKeyHandler();
1688 mapUpdate(REPAINT_IF_DIRTY);
1689 }
1690
ctrl_party_ui(class PlayerParty * party)1691 void ctrl_party_ui(class PlayerParty *party)
1692 {
1693 struct KeyHandler kh;
1694
1695 /* ready the cmdwin prompt */
1696 cmdwin_clear();
1697 cmdwin_push("Party [%d ap]:",party->getActionPoints());
1698
1699 kh.fx = &ctrl_party_key_handler;
1700 kh.data = party;
1701 eventPushKeyHandler(&kh);
1702 G_turnaround = SDL_GetTicks() - G_turnaround_start;
1703 eventHandle();
1704 G_turnaround_start = SDL_GetTicks();
1705 eventPopKeyHandler();
1706 mapUpdate(REPAINT_IF_DIRTY);
1707 }
1708