2 // nazghul - an old-school RPG engine
3 // Copyright (C) 2002, 2003 Gordon McNutt
5 // This program is free software; you can redistribute it and/or modify it
6 // under the terms of the GNU General Public License as published by the Free
7 // Software Foundation; either version 2 of the License, or (at your option)
10 // This program is distributed in the hope that it will be useful, but WITHOUT
11 // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 // You should have received a copy of the GNU General Public License along with
16 // this program; if not, write to the Free Foundation, Inc., 59 Temple Place,
17 // Suite 330, Boston, MA 02111-1307 USA
20 // gmcnutt@users.sourceforge.net
36 #include "Container.h"
43 #include "dup_constants.h"
45 #include "terrain_map.h"
47 #include "formation.h"
50 #include "formation.h"
63 #define FORMATION_H (formation[array_sz(formation) - 1].y)
64 #define N_MAX_NPCS 256 /* arbitrary limit */
66 #define MAX_PLACEMENT_RECTANGLE_W 32
67 #define MAX_PLACEMENT_RECTANGLE_H 16
68 #define SEARCH_QUEUE_SZ 100
70 /* Formation pattern -- party facing north (dx=0,dy=-1), origin at the leader's
73 enum combat_faction_status {
74 COMBAT_FACTION_EXISTS,
76 COMBAT_FACTION_CHARMED,
77 COMBAT_FACTION_CAMPING
83 enum combat_state state;
84 char vmap[7 * 7]; // visited map (used to search for positions)
85 class Vehicle *enemy_vehicle;
87 sound_t *sound_defeat;
88 sound_t *sound_victory;
98 static char rmap[MAX_PLACEMENT_RECTANGLE_W * MAX_PLACEMENT_RECTANGLE_H];
99 static int x_q[SEARCH_QUEUE_SZ];
100 static int y_q[SEARCH_QUEUE_SZ];
104 enum combat_state combat_get_state(void)
109 void combat_set_state(enum combat_state new_state)
111 // --------------------------------------------------------------------
112 // Interesting state transitions:
114 // ====================================================================
115 // old state | new state | result
116 // ====================================================================
117 // COMBAT_STATE_DONE | COMBAT_STATE_FIGHTING | entry to combat
118 // COMBAT_STATE_DONE | COMBAT_STATE_LOOTING | non-hostile
119 // COMBAT_STATE_DONE | COMBAT_STATE_CAMPING | entry to camping
120 // COMBAT_STATE_FIGHTING | COMBAT_STATE_DONE | defeat
121 // COMBAT_STATE_FIGHTING | COMBAT_STATE_LOOTING | victory
122 // COMBAT_STATE_LOOTING | COMBAT_STATE_FIGHTING | hostiles entered
123 // COMBAT_STATE_LOOTING | COMBAT_STATE_DONE | exit normally
124 // COMBAT_STATE_CAMPING | COMBAT_STATE_FIGHTING | ambush
125 // COMBAT_STATE_CAMPING | COMBAT_STATE_DONE | exit camping
126 // ====================================================================
128 // --------------------------------------------------------------------
130 if (Combat.state == new_state)
134 switch (Combat.state) {
136 case COMBAT_STATE_DONE:
138 case COMBAT_STATE_FIGHTING:
139 log_banner("^c+mÀïÆ®^c-");
140 sound_play(Combat.sound_enter, SOUND_MAX_VOLUME);
142 case COMBAT_STATE_LOOTING:
144 case COMBAT_STATE_CAMPING:
153 case COMBAT_STATE_FIGHTING:
155 case COMBAT_STATE_LOOTING:
156 log_banner("^c+g¾¡Íø^c-");
157 sound_play(Combat.sound_victory, SOUND_MAX_VOLUME);
158 player_party->addExperience(COMBAT_VICTORY_XP);
160 case COMBAT_STATE_DONE:
161 log_banner("^c+rÇÔËÌ^c-");
162 sound_play(Combat.sound_defeat, SOUND_MAX_VOLUME);
170 case COMBAT_STATE_LOOTING:
172 case COMBAT_STATE_FIGHTING:
173 log_banner("^c+mÀïÆ®^c-");
174 sound_play(Combat.sound_enter, SOUND_MAX_VOLUME);
176 case COMBAT_STATE_DONE:
184 case COMBAT_STATE_CAMPING:
186 case COMBAT_STATE_FIGHTING:
187 log_banner("^c+mÀïÆ®^c-");
188 sound_play(Combat.sound_enter, SOUND_MAX_VOLUME);
190 case COMBAT_STATE_LOOTING:
191 case COMBAT_STATE_DONE:
204 Combat.state = new_state;
205 session_run_hook(Session, combat_change_hook, "p", Session->player);
208 // returns 0 for ok position, -1 for no position, or a PFLAG type for fallback positions with problems
209 // input includes current best case problemness
210 static int location_is_safe(struct position_info *info, int current)
212 struct astar_node *path;
213 struct astar_search_info as_info;
214 struct terrain *terrain;
218 if (!place_is_passable(info->place, info->px, info->py,
224 if (place_is_occupied(info->place, info->px, info->py)) {
226 returntype= PFLAG_IGNOREBEINGS;
227 if (current & (PFLAG_IGNOREHAZARDS || PFLAG_IGNOREBEINGS || PFLAG_IGNOREFIELDS))
231 // I added the next two checks because a character was getting
232 // positioned over the firepit while camping, and I thought it was
233 // damaging him. Turns out firepits weren't setup to cause fire damage
234 // (oddly), and the character was just starving. I'll leave this here
237 // Is it dangerous? Hack: check for a field and dangerous terrain
238 if (place_get_object(info->place, info->px, info->py, field_layer)) {
239 dbg("possibly dangerous field\n");
240 returntype= PFLAG_IGNOREFIELDS;
241 if (current & (PFLAG_IGNOREHAZARDS || PFLAG_IGNOREFIELDS))
244 terrain = place_get_terrain(info->place, info->px, info->py);
245 if (terrain->effect) {
246 dbg("possibly dangerous terrain\n");
247 returntype= PFLAG_IGNOREHAZARDS;
248 if (current & (PFLAG_IGNOREHAZARDS ))
252 memset(&as_info, 0, sizeof (as_info));
253 if (info->find_party) {
254 // Each member should be able to find a path back to the
255 // party's originating location on the map.
256 dbg("searching for path to party [%d %d]...",
259 as_info.x0 = info->px;
260 as_info.y0 = info->py;
261 as_info.x1 = info->x;
262 as_info.y1 = info->y;
263 as_info.flags = PFLAG_IGNOREBEINGS;
264 as_info.limit_depth = true;
265 as_info.max_depth = 5;
267 path = place_find_path(info->place, &as_info, info->subject);
270 dbg("no path back to party\n");
273 // skip the pathfinding check
278 astar_path_destroy(path);
285 // returns 0 for ok position, -1 for no position, or a PFLAG type for fallback positions with problems
286 // input includes current best case problemness
287 static int combat_search_for_safe_position(struct position_info *info, int currentsafety)
291 static int x_offsets[] = { -1, 1, 0, 0 };
292 static int y_offsets[] = { 0, 0, -1, 1 };
293 int locationsafety=-1;
295 dbg("checking [%d %d]...", info->px, info->py);
297 // translate the map coords into an rmap index
298 index = info->py - info->ry;
300 index += (info->px - info->rx);
302 // If the current location is off-map, outside of the placement
303 // rectangle or already visited then discontinue the search.
305 dbg("already visited [%d]\n", index);
306 return -1; // already visited
308 if (info->px < info->rx || info->px >= info->rx + info->rw ||
309 info->py < info->ry || info->py >= info->ry + info->rh) {
310 dbg("outside the placement area\n");
311 return -1; // outside the placement rect
313 if (place_off_map(info->place, info->px, info->py)) {
315 // return -1; // off map
316 goto enqueue_neighbors;
318 // Mark this location as visited.
321 // If the current location is safe then the search succeeded.
322 locationsafety = location_is_safe(info,currentsafety);
323 if (locationsafety == 0) {
329 // Enqueue the adjacent neighbors onto the search queue.
330 for (i = 0; i < array_sz(x_offsets) && q_tail < SEARCH_QUEUE_SZ; i++) {
331 assert(q_tail < SEARCH_QUEUE_SZ);
332 x_q[q_tail] = info->px + x_offsets[i];
333 y_q[q_tail] = info->py + y_offsets[i];
337 // Return still not found.
338 return locationsafety;
341 // returns 0 for ok position, -1 for no position, or a PFLAG type for fallback positions with problems
342 static int combat_find_safe_position(struct position_info *info)
344 // Here's my new definition of a safe place: a safe place is a tile
345 // within the placement rectangle which is passable to the character in
346 // question and from which the character in question can pathfind to
347 // the edge from which the party entered.
349 // store data on a fallback not-so-safe position
350 int currentsafety = 0;
351 int cur_x = 0, cur_y = 0;
354 // Clear the search queue.
357 // Push the preferred position onto the search queue.
358 x_q[q_tail] = info->px;
359 y_q[q_tail] = info->py;
362 // Run through the search queue until it is exhausted or a safe
363 // position has been found.
364 while (q_head != q_tail) {
366 // Dequeue the next location to check.
367 info->px = x_q[q_head];
368 info->py = y_q[q_head];
371 // If it is ok then we're done.
372 newsafety = combat_search_for_safe_position(info, currentsafety);
375 else if (newsafety != -1)
377 currentsafety = newsafety;
383 if (currentsafety != 0)
387 return currentsafety;
393 static bool myPutNpc(class Character * pm, void *data)
396 struct position_info *info;
399 info = (struct position_info *) data;
404 // In the case where there is more than one NPC party entering combat
405 // this might be called more than once for an NPC. I want to ignore all
406 // but the first call, so check if the NPC is already on the map.
410 // Make sure we don't index beyond the end of the formation array.
411 if (pm->getOrder() >= info->formation->n)
416 pm->setX(info->formation->entry[pm->getOrder()].x);
417 pm->setY(info->formation->entry[pm->getOrder()].y);
419 /* Counterclockwise rotations: x = x * cos - y * sin y = x * sin + y *
422 /* Rotate +90 degrees */
424 pm->setX(pm->getY());
427 else if (info->dx > 0) {
428 /* Rotate -90 degrees */
430 pm->setX(-pm->getY());
433 else if (info->dy > 0) {
434 /* Rotate 180 degrees */
435 pm->setX(-pm->getX());
436 pm->setY(-pm->getY());
439 /* If dy > 1 then the formation is ok as-is. */
440 pm->changeX(info->x);
441 pm->changeY(info->y);
443 // Check if that location will really work. If not then do a DFS
444 // starting from the desired location and see if we can find someplace
447 // initialize the position info for a new search
449 memset(rmap, 0, sizeof (rmap));
451 // set the preferred location
452 info->px = pm->getX();
453 info->py = pm->getY();
455 dbg("Placing %s\n", pm->getName());
457 if (combat_find_safe_position(info) == -1) {
458 // If I can't place a member then I can't place it.
459 dbg("*** Can't place %s ***\n", pm->getName());
465 dbg("Put '%s' at [%d %d]\n", pm->getName(), info->px, info->py);
467 place_add_object(Place, pm);
471 /* Check if we need to go back to fighting */
472 if (combat_get_state() != COMBAT_STATE_FIGHTING &&
473 are_hostile(pm, player_party)) {
474 combat_set_state(COMBAT_STATE_FIGHTING);
480 static void set_party_initial_position(struct position_info *pinfo, int x, int y)
485 // Set the bounds of the placement rectangle. For now I don't care if
486 // it overlaps the edge of the map because the search algorithm will
487 // check for off-map locations.
488 pinfo->rw = MAX_PLACEMENT_RECTANGLE_W;
489 pinfo->rh = MAX_PLACEMENT_RECTANGLE_H;
490 pinfo->rx = pinfo->x - pinfo->rw / 2;
491 pinfo->ry = pinfo->y - pinfo->rh / 2;
493 dbg("Moved party start position to [%d %d]\n", pinfo->x, pinfo->y);;
497 void combat_fill_position_info(struct position_info *info, struct place *place, int x, int y, int dx, int dy, bool defend)
500 // This function will:
501 // * determine the party's coordinates upon entry to combat
502 // * turn the defending party to face the attacker
503 // * specify the placement rectangle for the party members
504 // * set the flags for the placement algorithm
511 if (info->place != Combat.place) {
512 // Occupy the same location and face the same way
526 // Occupy an edge facing the opponent
528 // facing west, occupy east half
529 info->x = place_w(info->place) - place_w(info->place) / 4;
532 // facing east, occupy west half
533 info->x = place_w(info->place) / 4;
536 // facing north or south, center on east-west
537 info->x = place_w(info->place) / 2;
541 // facing north, occupy south
542 info->y = place_h(info->place) - place_h(info->place) / 4;
545 // facing south, occupy north
546 info->y = place_h(info->place) / 4;
549 // facing east or west, center on north-south
550 info->y = place_h(info->place) / 2;
554 set_party_initial_position(info, info->x, info->y);
556 // clear the pmask and search map before first use
557 info->subject = NULL;
558 memset(rmap, 0, sizeof (rmap));
563 bool combat_place_character(class Character * pm, void *data)
565 // Put a party member on the combat map
568 struct position_info *info;
570 info = (struct position_info *) data;
575 if (pm->getOrder() >= info->formation->n)
578 pm->setX(info->formation->entry[pm->getOrder()].x);
579 pm->setY(info->formation->entry[pm->getOrder()].y);
581 /* Counterclockwise rotations: x = x * cos - y * sin y = x * sin + y *
584 /* Rotate +90 degrees */
586 pm->setX(pm->getY());
589 else if (info->dx > 0) {
590 /* Rotate -90 degrees */
592 pm->setX(-pm->getY());
595 else if (info->dy > 0) {
596 /* Rotate 180 degrees */
597 pm->setX(-pm->getX());
598 pm->setY(-pm->getY());
601 /* If dy > 1 then the formation is ok as-is. */
602 pm->changeX(info->x);
603 pm->changeY(info->y);
605 // Check if that location will really work. If not then do a DFS
606 // starting from the desired location and see if we can find someplace
609 // init the position info for a new search
611 memset(rmap, 0, sizeof (rmap));
612 info->px = pm->getX();
613 info->py = pm->getY();
614 dbg("Placing %s\n", pm->getName());
616 if (combat_find_safe_position(info) != 0) {
618 // Ok, so that didn't work. This can happen when the party
619 // leader is right on the map border facing towards the map
620 // center. In this case the find-safe-place alg won't handle
621 // followers that are too deep off the map. If this IS the
622 // party leader, or if upon retry we STILL can't find a safe
623 // place, then screw it - we'll place this character on the
624 // start location (even if we have to end up stacking the whole
627 // Corner case: a portal leads from one lake to another. A
628 // hostile npc party is sitting right on the destination. The
629 // party enters the portal on foot, upon arrival the npc party
630 // attacks. Party members which are on foot get stacked on the
631 // entry point and cannot move or flee. This is an unfriendly
632 // situation, but something of a corner case. Map hackers can
633 // skirt the issue and player's can expect that entering
634 // portals involves an element of danger :). The engine won't
635 // crash and the player can usually get out of the fix by
636 // defeating the npc's.
638 class Character *leader = player_party->get_leader();
641 dbg("Putting %s on start location [%d %d]\n",
642 pm->getName(), info->x, info->y);
647 // init the position info to search again
648 memset(rmap, 0, sizeof (rmap));
649 info->px = leader->getX();
650 info->py = leader->getY();
651 dbg("Retrying %s\n", pm->getName());
653 if (combat_find_safe_position(info) != 0) {
654 dbg("Putting %s on start location "
656 pm->getName(), info->x, info->y);
663 pm->relocate(info->place, info->px, info->py);
668 static bool mySetInitialCameraPosition(class Character * pm, void *data)
671 mapCenterCamera(pm->getX(), pm->getY());
677 static void combat_overlay_vehicle(Vehicle *vehicle, int dx, int dy,
678 struct position_info *pinfo)
681 assert(pinfo->dx || pinfo->dy);
682 assert(!pinfo->dx || !pinfo->dy);
684 int dst_x = 0, dst_y = 0;
687 // facing west, fill east half
688 dst_x = COMBAT_MAP_W / 2;
689 set_party_initial_position(pinfo, (COMBAT_MAP_W*3)/4,COMBAT_MAP_H/2);
692 // facing east, fill west half
693 dst_x = COMBAT_MAP_W / 2 - COMBAT_MAP_W;
694 set_party_initial_position(pinfo, COMBAT_MAP_W/4,COMBAT_MAP_H/2);
697 // facing north, fill south half
698 dst_y = COMBAT_MAP_H / 2;
699 set_party_initial_position(pinfo, COMBAT_MAP_W/2,(COMBAT_MAP_H*3)/4);
702 // facing south, fill north half
703 dst_y = COMBAT_MAP_H / 2 - COMBAT_MAP_H;
704 set_party_initial_position(pinfo, COMBAT_MAP_W/2,COMBAT_MAP_H/4);
708 set_party_initial_position(pinfo, COMBAT_MAP_W/2,COMBAT_MAP_H/2);
711 closure_exec(vehicle->getObjectType()->renderCombat, "ppdd",
712 Place, vehicle, dst_x, dst_y);
717 static void combat_overlay_map(struct terrain_map *map,
718 struct position_info *pinfo, int broadside)
722 assert(pinfo->dx || pinfo->dy);
723 assert(!pinfo->dx || !pinfo->dy);
725 // Clone the map so we can make a rotated copy.
726 map = terrain_map_clone(map, "combat_overlay_map");
728 err("Failed to allocate temporary terrain map");
731 // Rotate the map so that north faces the opponent.
733 terrain_map_rotate(map, vector_to_rotation(pinfo->dy, pinfo->dx));
735 // Position the map against the boundary dividing the map.
737 // facing west, shift map west toward edge
738 x = (place_w(Place)) / 2;
739 y = (place_h(Place) - map->h) / 2;
741 else if (pinfo->dx > 0) {
742 // facing east, shift map east toward edge
743 x = (place_w(Place)) / 2 - map->w;
744 y = (place_h(Place) - map->h) / 2;
746 else if (pinfo->dy < 0) {
747 // facing north, shift map north toward edge
748 x = (place_w(Place) - map->w) / 2;
749 y = (place_h(Place)) / 2;
751 else if (pinfo->dy > 0) {
752 // facing south, shift map south toward edge
753 x = (place_w(Place) - map->w) / 2;
754 y = (place_h(Place)) / 2 - map->h;
758 terrain_map_rotate(map, vector_to_rotation(pinfo->dx,
760 // center the overlayed map
761 x = (place_w(Place) - map->w) / 2;
762 y = (place_h(Place) - map->h) / 2;
768 // Adjust the party's starting position to be centered on the overlap
770 set_party_initial_position(pinfo,
771 x + (map->w /*+ 1*/) / 2,
772 y + (map->h /*+ 1*/) / 2);
774 // Blit the rotated map centered on the given coordinates.
775 terrain_map_blit(Place->terrain_map, x, y, map, 0, 0, map->w, map->h);
776 // terrain_map_print(stdout, INITIAL_INDENTATION, Place->terrain_map);
779 terrain_map_unref(map);
782 static void myPutEnemy(class Party * foe, struct position_info *pinfo)
784 foe->forEachMember(myPutNpc, pinfo);
788 * combat_position_enemy - put an NPC party on the combat map
790 static int combat_position_enemy(class Party * foe, int dx, int dy,
796 assert(foe->getSize());
798 combat_fill_position_info(&foe->pinfo, place, foe->getX(), foe->getY(),
800 foe->pinfo.formation = foe->get_formation();
801 if (!foe->pinfo.formation)
802 foe->pinfo.formation = formation_get_default();
804 Combat.enemy_vehicle = foe->getVehicle();
806 /* Bugfix SF1412060 "NPC attacks while on (but not aboard) ship" */
807 /* Addendum: the foe may not have a place if it is being introduced
808 * into combat "from scratch" (ie, it did not exist on the wilderness
809 * as a party before being imported into combat). */
810 if (! Combat.enemy_vehicle && foe->getPlace()) {
811 Combat.enemy_vehicle = place_get_vehicle(foe->getPlace(),
815 /* Check for a map overlay. */
816 if (Combat.enemy_vehicle
817 && Combat.enemy_vehicle->getObjectType()->renderCombat
818 && Place == Combat.place) {
819 combat_overlay_vehicle(Combat.enemy_vehicle,
828 myPutEnemy(foe, &foe->pinfo);
829 positioned = foe->pinfo.placed;
837 /*****************************************************************************
838 * combat_npc_status_visitor - determine the status of the npc faction(s)
840 * This is a "visitor" function applied to each object in a list of nodes. It's
841 * meant to be applied to every object in a place. It checks for any npc
842 * party members and whether or not they're charmed, and sets the combat status
843 * of the npc faction.
845 * This routine does not distinguish between different NPC factions, it just
846 * looks for anybody hostile to the player.
848 *****************************************************************************/
849 static void combat_hostile_status_visitor(struct node *node, void *data)
851 enum combat_faction_status *status;
854 /* Extract the typed variables from the generic parms */
855 status = (enum combat_faction_status *)data;
856 obj = (class Object*)node->ptr;
858 /* If we already know a hostile faction exists then skip the rest */
859 if (*status == COMBAT_FACTION_EXISTS)
862 /* Skip non-beings */
863 if (! obj_is_being(obj))
866 /* Skip player party members */
867 if (obj->isPlayerPartyMember())
870 /* A hostile npc means a hostile faction still exists */
871 if (are_hostile((Being*)obj, player_party)) {
872 *status = COMBAT_FACTION_EXISTS;
876 /* Check for a charmed hostile */
877 if (are_natively_hostile((Being*)obj, player_party)) {
878 *status = COMBAT_FACTION_CHARMED;
882 /*****************************************************************************
883 * combat_get_hostile_faction_status - check hostile combat status
885 * This is literally a dupe of combat_get_player_faction_status() below.
887 *****************************************************************************/
888 static enum combat_faction_status combat_get_hostile_faction_status(void)
890 enum combat_faction_status stat;
892 /* Assume until proven otherwise that the player faction is gone. */
893 stat = COMBAT_FACTION_GONE;
895 /* Check each object in the current place to determine the status of
896 * the player faction. */
897 node_foldr(place_get_all_objects(Place),
898 combat_hostile_status_visitor,
901 /* Return the discovered status. */
905 /*****************************************************************************
906 * combat_player_status_visitor - determine the status of the player faction
908 * This is a "visitor" function applied to each object in a list of nodes. It's
909 * meant to be applied to every object in a place. It checks for any player
910 * party members and whether or not they're charmed, and sets the combat status
911 * of the player party based on the cumulative results.
913 *****************************************************************************/
914 static void combat_player_status_visitor(struct node *node, void *data)
916 enum combat_faction_status *status;
919 /* Extract the typed variables from the generic parms */
920 status = (enum combat_faction_status *)data;
921 obj = (class Object*)node->ptr;
923 /* If we already know the player is still fighting then skip the rest
925 if (*status == COMBAT_FACTION_EXISTS)
928 /* Skip non-beings */
929 if (! obj_is_being(obj))
932 /* Skip non-party-members */
933 if (! obj->isPlayerPartyMember())
936 /* A non-hostile party member means the player is still fighting. */
937 if (! are_hostile((Being*)obj, player_party)) {
938 *status = COMBAT_FACTION_EXISTS;
942 /* Check if a player party members has been charmed */
943 if (are_natively_hostile((Being*)obj, player_party)) {
944 *status = COMBAT_FACTION_CHARMED;
948 /*****************************************************************************
949 * combat_get_player_faction_status - check player combat status
951 * Loops over all objects to check if any player party members are still around
952 * and whether or not they're charmed.
954 * FIXME: why not just check the player party directly? Or at least merge this
955 * with the combat_get_hostile_faction_status() function above?
957 *****************************************************************************/
958 static enum combat_faction_status combat_get_player_faction_status(void)
960 enum combat_faction_status stat;
962 /* Assume until proven otherwise that the player faction is gone. */
963 stat = COMBAT_FACTION_GONE;
965 /* Check each object in the current place to determine the status of
966 * the player faction. */
967 node_foldr(place_get_all_objects(Place),
968 combat_player_status_visitor,
971 /* Return the discovered status. */
975 void combat_analyze_results_of_last_turn()
978 enum combat_faction_status hostile_faction_status;
979 enum combat_faction_status player_faction_status;
981 // ---------------------------------------------------------------------
982 // Now check for changes in the combat state as a result of the last
983 // turn. Check the status of the hostile party or parties and the
984 // player party. The following table shows the outcome with all
985 // possible combinations of status:
987 // =====================================================
988 // hostiles | player party | result
989 // =====================================================
990 // exist | exist | continue combat
991 // exist | gone | exit combat
992 // exist | charmed | uncharm, continue combat
993 // gone | exist | looting
994 // gone | gone | exit combat
995 // gone | charmed | uncharm, looting
996 // charmed | exist | continue combat
997 // charmed | gone | exit combat
998 // charmed | charmed | uncharm, continue combat
999 // =====================================================
1001 // ---------------------------------------------------------------------
1003 hostile_faction_status = combat_get_hostile_faction_status();
1004 player_faction_status = combat_get_player_faction_status();
1006 switch (player_faction_status) {
1008 case COMBAT_FACTION_EXISTS:
1010 switch (hostile_faction_status) {
1012 case COMBAT_FACTION_EXISTS:
1013 // -----------------------------------------------------
1014 // Both factions exist. Continue or restart fighting.
1015 // -----------------------------------------------------
1016 combat_set_state(COMBAT_STATE_FIGHTING);
1019 case COMBAT_FACTION_CHARMED:
1020 // -----------------------------------------------------
1021 // The hostile faction are all charmed. Tough luck for
1022 // them. Make sure we are fighting. Addendum: can't
1023 // attack them without degrading diplomacy with own
1024 // faction, so declare combat over. When charm wears
1025 // off we'll go back to fighting.
1026 // -----------------------------------------------------
1027 //combat_set_state(COMBAT_STATE_FIGHTING);
1028 combat_set_state(COMBAT_STATE_LOOTING);
1031 case COMBAT_FACTION_GONE:
1032 // -----------------------------------------------------
1033 // No hostiles around. Loot at will.
1034 // -----------------------------------------------------
1035 if (Combat.state != COMBAT_STATE_DONE)
1037 combat_set_state(COMBAT_STATE_LOOTING);
1048 case COMBAT_FACTION_CHARMED:
1050 // -------------------------------------------------------------
1051 // In all of these cases I uncharm the party members. If I
1052 // don't, then I'm risking a deadlock situation where the
1053 // hostiles can't or won't finish off the charmed members, in
1054 // which case the game gets stuck running all the npc's forever
1055 // while the player helplessly watches.
1056 // -------------------------------------------------------------
1058 player_party->unCharmMembers();
1060 switch (hostile_faction_status) {
1062 case COMBAT_FACTION_EXISTS:
1063 combat_set_state(COMBAT_STATE_FIGHTING);
1066 case COMBAT_FACTION_GONE:
1067 // ----------------------------------------------------
1068 // No hostiles around. Loot at will. Uncharm or we'll
1069 // definitely deadlock.
1070 // ----------------------------------------------------
1071 combat_set_state(COMBAT_STATE_LOOTING);
1081 case COMBAT_FACTION_GONE:
1083 // ------------------------------------------------------------
1084 // In all of these cases combat is over. Simple. If combat is
1085 // ocurring in the special combat place then we need to clean
1086 // it up by calling combat_exit().
1087 // ------------------------------------------------------------
1089 combat_set_state(COMBAT_STATE_DONE);
1091 // if (Place == Combat.place)
1096 case COMBAT_FACTION_CAMPING:
1098 switch (hostile_faction_status) {
1100 case COMBAT_FACTION_EXISTS:
1101 // ----------------------------------------------------
1103 // ----------------------------------------------------
1104 combat_set_state(COMBAT_STATE_FIGHTING);
1107 case COMBAT_FACTION_GONE:
1108 // ----------------------------------------------------
1109 // No hostiles around. Change nothing.
1110 // ----------------------------------------------------
1126 * combat_find_and_position_enemy - if this object is a hostile NPC party then
1127 * place it on the combat map
1129 static void combat_find_and_position_enemy(class Object * obj, void *data)
1133 if (!obj->isType(PARTY_ID))
1136 info = (struct v2 *) data;
1137 assert(obj_is_being(obj));
1138 if (are_hostile((Being*)obj, player_party))
1139 combat_set_state(COMBAT_STATE_FIGHTING);
1140 combat_position_enemy((class Party *) obj, info->dx, info->dy, false,
1144 int combatInit(void)
1146 // This is called once at the beginning of the game
1147 memset(&Combat, 0, sizeof (Combat));
1149 /* Initialize the place to safe defaults */
1150 Combat.state = COMBAT_STATE_DONE;
1155 void combat_reset_state(void)
1157 /* Initialize the place to safe defaults */
1158 fprintf(stderr,"combatreset\n");
1159 Combat.state = COMBAT_STATE_DONE;
1162 static void fill_map_half(struct terrain_map *map, int dx, int dy,
1163 struct terrain *terrain)
1168 // facing west, fill east half
1169 terrain_map_fill(map, map->w / 2, 0, (map->w+1) / 2, map->h,
1173 // facing east, fill west half
1174 terrain_map_fill(map, 0, 0, map->w / 2, map->h, terrain);
1177 // facing north, fill south half
1178 terrain_map_fill(map, 0, map->h / 2, map->w, (map->h+1) / 2,
1182 // facing south, fill north half
1183 terrain_map_fill(map, 0, 0, map->w, map->h / 2, terrain);
1188 static void fill_temporary_terrain_map(struct terrain_map *map,
1189 struct place *place, int x, int y,
1192 struct terrain_map *tile_map;
1193 struct terrain *terrain;
1194 int dst_x = 0, dst_y = 0, src_x = 0, src_y = 0, src_w = 0, src_h = 0;
1197 assert(!(dx && dy));
1201 // facing west, fill east half
1206 src_w = (map->w + 1) / 2;
1212 // facing east, fill west half
1217 src_w = (map->w) / 2;
1223 // facing north, fill south half
1229 src_h = (map->h + 1) / 2;
1234 // facing south, fill north half
1240 src_h = (map->h)/ 2;
1243 tile_map = place_get_combat_terrain_map(place, x, y);
1247 // fixme -- instead of crashing at runtime, check for properly
1248 // sized combat maps at load time (this will require the combat
1249 // map dimensions to be also specified at load time or at least
1250 // well-documented for map developers)
1251 assert(tile_map->w >= (src_x + src_w));
1252 assert(tile_map->h >= (src_y + src_h));
1254 // Use the combat map associated with the terrain type.
1255 terrain_map_blit(map, dst_x, dst_y, tile_map, src_x, src_y,
1259 // Fill with the terrain type.
1260 terrain = place_get_terrain(place, x, y);
1261 terrain_map_fill(map, dst_x, dst_y, src_w, src_h, terrain);
1266 static void setup_combat_place_part(struct place *place,
1267 struct terrain* our_terrain, struct terrain* other_terrain,
1268 int dx, int dy, int mapx, int mapy)
1270 int dst_x = 0, dst_y = 0;
1272 if (our_terrain->renderCombat)
1274 fprintf(stderr,"rc\n");
1276 // facing west, fill east half
1277 dst_x = COMBAT_MAP_W / 2;
1280 // facing east, fill west half
1281 dst_x = COMBAT_MAP_W / 2 - COMBAT_MAP_W;
1284 // facing north, fill south half
1285 dst_y = COMBAT_MAP_H / 2;
1288 // facing south, fill north half
1289 dst_y = COMBAT_MAP_H / 2 - COMBAT_MAP_H;
1292 closure_exec(our_terrain->renderCombat, "pppdddd",
1293 place, our_terrain, other_terrain,
1294 dst_x, dst_y, mapx, mapy);
1298 fill_map_half(place->terrain_map, dx, dy, our_terrain);
1302 terrain_map_fill(place->terrain_map, 0, 0, COMBAT_MAP_W, COMBAT_MAP_H, our_terrain);
1307 static void setup_combat_place(struct place *place, struct combat_info
1310 struct terrain *player_terrain;
1311 struct terrain *npc_terrain;
1313 // Determine orientation for both parties
1314 // Also get true locations (after parties have moved to go from diagonal to adjacent)
1315 if (!info->move->npc_party)
1319 info->pc_x = player_party->getX();
1320 info->pc_y = player_party->getY();
1322 else if (info->defend)
1324 info->pc_dx = -info->move->dx;
1325 info->pc_dy = -info->move->dy;
1326 info->pc_x = player_party->getX();
1327 info->pc_y = player_party->getY();
1328 info->npc_x = info->pc_x - info->move->dx;
1329 info->npc_y = info->pc_y - info->move->dy;
1333 info->pc_dx = info->move->dx;
1334 info->pc_dy = info->move->dy;
1335 info->npc_x = info->move->npc_party->getX();
1336 info->npc_y = info->move->npc_party->getY();
1337 info->pc_x = info->npc_x - info->move->dx;
1338 info->pc_y = info->npc_y - info->move->dy;
1341 // get terrains for each
1342 player_terrain=place_get_terrain(player_party->getPlace(), info->pc_x, info->pc_y);
1344 if (info->move->npc_party)
1346 npc_terrain=place_get_terrain(info->move->npc_party->getPlace(), info->npc_x, info->npc_y);
1350 npc_terrain=player_terrain; // some sensible definition makes like easier...
1353 setup_combat_place_part(place, player_terrain, npc_terrain, info->pc_dx, info->pc_dy, info->pc_x, info->pc_y);
1354 if (info->move->npc_party)
1356 setup_combat_place_part(place, npc_terrain, player_terrain, -info->pc_dx, -info->pc_dy, info->npc_x, info->npc_y);
1362 static struct terrain_map *create_temporary_terrain_map(struct combat_info
1365 struct terrain_map *map;
1366 int player_dx, player_dy, npc_dx, npc_dy,pcmap_x,pcmap_y,npcmap_x,npcmap_y;
1369 // Create a map derived partially from the enemy's tile and
1370 // partially from the player's tile.
1372 map = terrain_map_new("tmp_combat_map", COMBAT_MAP_W, COMBAT_MAP_H,
1373 player_party->getPlace()->terrain_map->palette);
1376 terrain_map_fill(map, 0, 0, COMBAT_MAP_W, COMBAT_MAP_H,
1377 place_get_terrain(player_party->getPlace(),player_party->getX(),player_party->getY()));
1380 // Determine orientation for both parties.
1384 player_dx = -info->move->dx;
1385 player_dy = -info->move->dy;
1386 npc_dx = info->move->dx;
1387 npc_dy = info->move->dy;
1388 pcmap_x = player_party->getX();
1389 pcmap_y = player_party->getY();
1390 npcmap_x = pcmap_x - info->move->dx;
1391 npcmap_y = pcmap_y - info->move->dy;
1394 player_dx = info->move->dx;
1395 player_dy = info->move->dy;
1396 npc_dx = -info->move->dx;
1397 npc_dy = -info->move->dy;
1398 npcmap_x = info->move->npc_party->getX();
1399 npcmap_y = info->move->npc_party->getY();
1400 pcmap_x = npcmap_x - info->move->dx;
1401 pcmap_y = npcmap_y - info->move->dy;
1404 // Fill the player's half of the combat map
1405 fill_temporary_terrain_map(map,
1406 player_party->getPlace(),
1408 player_dx, player_dy);
1410 // Fill the npc party's half of the combat map
1411 fill_temporary_terrain_map(map,
1412 info->move->npc_party->getPlace(),
1416 struct terrain_map *party_map =
1417 place_get_combat_terrain_map(player_party->getPlace(),
1418 player_party->getX(),
1419 player_party->getY());
1420 struct terrain_map *npc_party_map =
1421 place_get_combat_terrain_map(info->move->npc_party->getPlace(),
1422 info->move->npc_party->getX(),
1423 info->move->npc_party->getY());
1425 // SAM: It seems that, until we can think of something
1426 // more clever, (palette merging code? yech...)
1427 // that all combat maps will have to have a palette
1428 // in common. It may be more restrictive than I have
1429 // stated, but I would need to research the question...
1432 // Hmmm...ship-to-shore blits a map with a different palette
1433 // onto a map with 'pal_standard', and this code has no way of seeing that.
1434 // Possibly the map blitting should merge the palettes after all...
1435 if (party_map && npc_party_map) {
1436 dbg("maps '%s' '%s', palettes '%s' '%s'\n",
1437 party_map->tag, npc_party_map->tag,
1438 party_map->palette->tag, npc_party_map->palette->tag);
1439 int palette_tags_match = !strcmp(party_map->palette->tag,
1440 npc_party_map->palette->tag);
1441 if (!palette_tags_match) {
1442 dbg("create_temporary_terrain_map() warning: \n"
1443 " Two combat maps (tags '%s' and '%s') \n"
1444 " merging with dissimilar palettes (tags '%s' and '%s').\n"
1445 " (This should work OK now, but be aware...)\n",
1446 party_map->tag, npc_party_map->tag,
1447 party_map->palette->tag,
1448 npc_party_map->palette->tag);
1449 // SAM: New code in palette_print() should enable us to carry on.
1453 struct terrain_map *the_map;
1455 the_map = party_map;
1456 else if (npc_party_map)
1457 the_map = npc_party_map;
1459 // No combat map for either location?
1460 // Use the parent map (wilderness, or whatever) to get a palette.
1461 // Since the terrain fill for each half is based on
1462 // some terrain in the parent map, that palette should be appropriate.
1463 assert(player_party->getPlace()->terrain_map);
1464 the_map = player_party->getPlace()->terrain_map;
1466 map->palette = the_map->palette;
1467 //terrain_map_print(stdout, INITIAL_INDENTATION, map);
1470 /* run all registered terrain blenders on the new map */
1471 list_for_each(&Session->blenders, elem) {
1472 blender_t *blender=outcast(elem, blender_t, list);
1473 terrain_map_blend(map, blender->inf, blender->n_nonsup,
1474 blender->nonsup, blender->range);
1480 static bool position_player_party(struct combat_info *cinfo)
1482 class Vehicle *vehicle;
1484 combat_fill_position_info(&player_party->pinfo, Place,
1485 cinfo->move->x, cinfo->move->y,
1486 cinfo->move->dx, cinfo->move->dy,
1489 player_party->pinfo.formation = player_party->get_formation();
1490 if (!player_party->pinfo.formation)
1491 player_party->pinfo.formation = formation_get_default();
1493 // Check for map overlays. First check if the player is in a vehicle
1495 vehicle = player_party->getVehicle();
1497 vehicle->getObjectType()->renderCombat &&
1498 Place == Combat.place) {
1499 combat_overlay_vehicle(vehicle, cinfo->pc_dx, cinfo->pc_dy,
1500 &player_party->pinfo);
1503 // Next check if the player is OVER (on the map) but not in a vehicle
1504 // on the map. Note: this only applies to non-dungeon combat, and in a
1505 // series of dungeon combats the player party may not have a place
1506 // (because we remove it just below and the calling code does not
1507 // relocate the player party until it returns to a town or wilderness.
1508 else if (player_party->getPlace() &&
1509 (vehicle = place_get_vehicle(player_party->getPlace(),
1510 player_party->getX(),
1511 player_party->getY())) &&
1512 vehicle->getObjectType()->renderCombat) {
1513 // dbg("party overlay, party over vehicle\n");
1514 combat_overlay_vehicle(vehicle, cinfo->pc_dx, cinfo->pc_dy,
1515 &player_party->pinfo);
1517 // Finally, since there is no vehicle map check for a camping map.
1518 else if (cinfo->camping && player_party->campsite_map) {
1519 // dbg("party overlay, party is camping\n");
1520 combat_overlay_map(player_party->campsite_map,
1521 &player_party->pinfo, 0);
1524 if (player_party->isLoitering())
1526 player_party->forceAbortLoitering();
1528 player_party->remove();
1529 player_party->forEachReverseMember(combat_place_character,
1530 &player_party->pinfo);
1535 bool combat_enter(struct combat_info * info)
1537 struct location loc;
1539 if (player_party->allDead()) {
1540 /* Yes, this can happen in some rare circumstances... */
1544 /* Our map-building code assumes 4-neighbor adjacency. If attacking on
1545 * a diagonal, randomly choose a cardinal direction. */
1546 if (info->move->dx && info->move->dy)
1555 // --------------------------------------------------------------------
1556 // Default to the entry point as the combat exit location for the
1558 // --------------------------------------------------------------------
1560 loc.place = info->move->place;
1561 loc.x = info->move->x;
1562 loc.y = info->move->y;
1565 // *** Initialize Combat Globals ***
1567 Combat.enemy_vehicle = NULL;
1569 Session->crosshair->remove();
1571 if (! info->move->place->wilderness) {
1573 // ------------------------------------------------------------
1574 // When not in the wilderness use the current place for combat.
1575 // ------------------------------------------------------------
1576 Place = info->move->place;
1580 // ------------------------------------------------------------
1581 // Create a temporary place for combat in the wilderness. It's
1582 // parent will be the wilderness. We have to set a special flag
1583 // to indicate that it's wilderness combat (used for things
1584 // like exit policy).
1585 // ------------------------------------------------------------
1587 Combat.place = place_new("p_wilderness_combat",
1588 "Wilderness Combat",
1590 create_temporary_terrain_map(info),
1592 info->move->place->underground,
1594 1 // wilderness combat
1597 Combat.place->is_wilderness_combat = 1;
1598 Combat.place->location.place = info->move->place;
1599 Combat.place->location.x = info->move->px;
1600 Combat.place->location.y = info->move->py;
1602 setup_combat_place(Combat.place,info);
1604 place_add_subplace(info->move->place, Combat.place,
1605 info->move->px, info->move->py);
1607 Place = Combat.place;
1612 // *** Position the Player Companions ***
1614 // This is where the map overlays on the player side of the map get
1616 if (!position_player_party(info))
1619 // *** Position the Enemy Party Members ***
1621 if (info->move->npc_party) {
1624 /* combat_position_enemy() will decrement most of the refcounts
1625 * ont he party, keep it alive until we're done */
1626 obj_inc_ref(info->move->npc_party);
1628 if (!combat_position_enemy(info->move->npc_party,
1629 info->move->dx, info->move->dy,
1630 !info->defend, Place)) {
1631 log_begin("*** ÁÓ¼º ***");
1632 log_msg("Áê¼ê¤ò¸«¼º¤Ã¤¿¡ª");
1634 combat_set_state(COMBAT_STATE_LOOTING);
1638 combat_set_state(COMBAT_STATE_FIGHTING);
1641 /* done with it now */
1642 obj_dec_ref(info->move->npc_party);
1643 info->move->npc_party = NULL;
1646 else if (info->camping) {
1648 combat_set_state(COMBAT_STATE_CAMPING);
1653 v2.dx = info->move->dx;
1654 v2.dy = info->move->dy;
1656 combat_set_state(COMBAT_STATE_DONE);
1657 place_for_each_object(Place, combat_find_and_position_enemy,
1661 player_party->forEachMember(mySetInitialCameraPosition, 0);
1663 if (combat_get_state() == COMBAT_STATE_FIGHTING) {
1664 player_party->enableRoundRobinMode();
1665 sound_play(Combat.sound_enter, SOUND_MAX_VOLUME);
1667 else if (combat_get_state() != COMBAT_STATE_CAMPING) {
1668 player_party->enableFollowMode();
1671 // ---------------------------------------------------------------------
1672 // Force a map update. If an npc initiates combat then the event
1673 // handler will not run and do a repaint until the next event.
1674 // ---------------------------------------------------------------------
1679 // ---------------------------------------------------------------------
1680 // Return to the main loop. Combat will continue from there.
1681 // ---------------------------------------------------------------------
1685 char combatGetState(void)
1687 switch (Combat.state) {
1688 case COMBAT_STATE_FIGHTING:
1691 case COMBAT_STATE_DONE:
1694 case COMBAT_STATE_LOOTING:
1697 case COMBAT_STATE_CAMPING:
1705 int combat_add_party(class Party * party, int dx, int dy, int located,
1706 struct place *place, int x, int y)
1713 // Caller has not specified a location so use the normal
1715 added = combat_position_enemy(party, dx, dy, false, place);
1720 // Special case: caller wants to put the party at (x, y). Duplicate the
1721 // code in combat_position_enemy except fill out the position info
1722 // based on caller's request.
1726 memset(&party->pinfo, 0, sizeof (party->pinfo));
1727 party->pinfo.place = place;
1730 party->pinfo.dx = dx;
1731 party->pinfo.dy = dy;
1732 party->pinfo.formation = party->get_formation();
1733 party->pinfo.find_party = true;
1734 if (!party->pinfo.formation)
1735 party->pinfo.formation = formation_get_default();
1736 set_party_initial_position(&party->pinfo, x, y);
1737 myPutEnemy(party, &party->pinfo);
1739 added = party->pinfo.placed;
1747 void combat_exit(void)
1749 struct place *parent = 0;
1752 // Clean up the temporary combat place, if we used one. If we started
1753 // off in combat when we loaded this session then we need to remove
1754 // the temp place and temp map from the session's list of objects
1755 // to save. We can tell that we need to remove them if they have
1756 // non-null handles (those handles are only set by the loader, if
1757 // we create the tmp place during normal game play we don't set them).
1759 // Before destroying the place I have to memorize it's location for the
1761 if (Place->is_wilderness_combat) {
1763 //assert(! Place->handle); // should not be top-level
1764 assert(place_get_parent(Place));
1766 if (Place->terrain_map->handle)
1767 session_rm(Session, Place->terrain_map->handle);
1769 place_remove_and_destroy_all_objects(Place);
1770 parent = place_get_parent(Place);
1771 x = place_get_x(Place);
1772 y = place_get_y(Place);
1773 place_remove_subplace(parent, Place);
1775 // Bugfix: Invalidate the entire map from the vmask cache. If
1776 // you don't do this, then the next time the player enters
1777 // combat and we start looking up vmasks we will find old,
1778 // stale ones from this place, resulting in LOS bugs. That's
1779 // because the keys used by the vmask are built from the name
1780 // of the place, and for the combat map it is always the same
1782 vmask_invalidate(Place, 0, 0, place_w(Place), place_h(Place));
1784 // If this place has a handle it's in the session list and
1785 // needs to be removed before we destroy it, otherwise on
1786 // session reload it will be deleted again.
1788 session_rm(Session, Place->handle);
1790 place_del(Place); // map deleted in here
1793 // ------------------------------------------------------------
1794 // Relocate the player party back to the wilderness. This will
1795 // handle the place switch implicitly by setting the global
1796 // 'Place' pointer to the wilderness.
1797 // ------------------------------------------------------------
1799 player_party->relocate(parent, x, y);
1803 assert(NULL != player_party->getPlace());
1805 // --------------------------------------------------------------------
1806 // Force a map update. Although the map has been marked dirty by now,
1807 // we will not see a repaint until the next event if we do not act
1808 // now. This routine is called from the context of the main play loop,
1809 // not the event loop.
1810 // --------------------------------------------------------------------