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