3 // helper class to find good respawn positions
5 struct ExaminedLocation
: public GridPos
{
7 ExaminedLocation(GridPos p
) : GridPos(p
) {}
9 bool operator<(const ExaminedLocation
& other
) const {
10 return (x
== other
.x
) ? y
<other
.y
: x
<other
.x
;
14 typedef std::set
<ExaminedLocation
> ExaminedLocations
;
16 class FreeRespawnLocationFinder
18 ExaminedLocations checked
;
19 ExaminedLocations blocked
;
20 ExaminedLocations candidates
;
22 const Actor
&actor_to_set
;
24 V2 preferred_position
;
29 static const double MAX_DISTANCE_WANTED
;
31 static bool is_marble(const string
& k
) {
32 // true if kind 'k' is a marble
33 return k
== "ac-blackball" || k
== "ac-whiteball" || k
== "ac-whiteball-small";
36 static bool is_respawn_floor(const string
& k
) {
37 // true if marble may appear on floors of kind 'k'
41 k
!= "fl-space"; // player cannot be moved on fl-space
44 static bool is_respawn_item(const string
& k
) {
45 // true if marble may appear on items of kind 'k'
47 k
!= "it-laserbeam" &&
48 k
!= "it-burnable-ignited";
51 static bool search_through_stone(const Stone
& st
) {
52 if (st
.is_movable() || st
.is_floating()) return true;
54 const string
& k
= st
.get_kind();
55 return k
== "st-puzzle";
58 static double wanted_distance_to(const string
& k
) {
59 // returns the size of the gap wanted between a marble and an actor of kind 'k'
61 if (k
== "ac-rotor") return MAX_DISTANCE_WANTED
;
62 if (k
== "ac-top") return 3.0;
63 if (k
== "ac-killerball" || k
== "ac-bug") return 1.5;
67 double distance_wanted_to(const Actor
& a
) {
70 if (actor_is_marble
) dist
= wanted_distance_to(a
.get_kind());
71 else if (is_marble(a
.get_kind())) dist
= wanted_distance_to(actor_to_set
.get_kind());
73 ASSERT(dist
<= MAX_DISTANCE_WANTED
, XLevelRuntime
, "FreeRespawnLocationFinder: distance_wanted_to too large ");
77 bool enemyActorAt(const V2
& p
) {
78 vector
<Actor
*> found_actors
;
79 double range
= get_radius (&actor_to_set
) + MAX_DISTANCE_WANTED
+ Actor::get_max_radius();
81 if (GetActorsInRange(p
, range
, found_actors
)) {
82 bool found_near_enemy
= false;
83 double min_enemy_gap
= 1000.0;
85 for (vector
<Actor
*>::const_iterator ai
= found_actors
.begin();
86 ai
!= found_actors
.end();
91 if (a
!= &actor_to_set
) {
92 double distance
= length(p
- a
->get_pos());
93 double gap_between
= distance
- get_radius (&actor_to_set
) - get_radius(a
);
94 double wanted_gap
= distance_wanted_to(*a
);
96 if (gap_between
< wanted_gap
)
97 found_near_enemy
= true;
99 if (gap_between
< min_enemy_gap
)
100 min_enemy_gap
= gap_between
;
104 if (found_near_enemy
) {
105 if (min_enemy_gap
<999.0) {
106 if (min_enemy_gap
> max_enemy_gap
) {
107 max_enemy_gap
= min_enemy_gap
;
113 return found_near_enemy
;
119 void examine(GridPos p
) {
120 if (checked
.find(p
) != checked
.end()) return; // already examined
121 checked
.insert(p
); // never check again
123 Floor
*fl
= GetFloor(p
);
124 if (!fl
|| !is_respawn_floor(fl
->get_kind())) return; // bad floor
126 bool may_respawn
= true;
127 bool continue_search
= true;
129 Item
*it
= GetItem(p
);
130 if (it
&& !is_respawn_item(it
->get_kind())) may_respawn
= false; // bad item
132 Stone
*st
= GetStone(p
);
134 if (!search_through_stone(*st
)) continue_search
= false;
138 if (may_respawn
) { // may be a candidate -> check for enemy actors
139 if (enemyActorAt(p
.center())) may_respawn
= false;
142 if (continue_search
) blocked
.insert(p
);
143 if (may_respawn
) candidates
.insert(p
);
148 FreeRespawnLocationFinder(V2 p
, const Actor
& actor
)
149 : actor_to_set(actor
)
150 , preferred_position(p
)
151 , max_enemy_gap(-1000.0)
153 actor_is_marble
= is_marble(actor_to_set
.get_kind());
154 ExaminedLocations affected
; // all locations affected by current respawn position
156 double radius
= get_radius (&actor_to_set
);
157 int xmin
= int(p
[0]-radius
);
158 int xmax
= int(p
[0]+radius
);
159 int ymin
= int(p
[1]-radius
);
160 int ymax
= int(p
[1]+radius
);
162 for (int x
= xmin
; x
<= xmax
; ++x
) {
163 for (int y
= ymin
; y
<= ymax
; ++y
) {
164 affected
.insert(GridPos(x
, y
));
169 for (ExaminedLocations::const_iterator ai
= affected
.begin(); ai
!= affected
.end(); ++ai
) {
173 if (candidates
.size() != affected
.size()) { // if any affected location may not be used for respawning
174 // choose alternate respawn location
176 blocked
= affected
; // start with all affected positions
178 while (candidates
.empty()) {
179 ExaminedLocations curr_blocked
;
180 swap(curr_blocked
, blocked
);
182 if (curr_blocked
.empty()) {
183 break; // no chance to find a candidate
186 for (ExaminedLocations::const_iterator bl
= curr_blocked
.begin(); bl
!= curr_blocked
.end(); ++bl
) {
187 examine(move(*bl
, NORTH
));
188 examine(move(*bl
, SOUTH
));
189 examine(move(*bl
, EAST
));
190 examine(move(*bl
, WEST
));
194 if (candidates
.empty()) { // no better location -> take least worse tested location
195 if (max_enemy_gap
> 0.0) {
196 preferred_position
= max_gap_pos
;
199 else { // a better location has been found
200 ExaminedLocations::const_iterator c
= candidates
.begin();
201 advance(c
, IntegerRand(0, int (candidates
.size()-1)));
203 ASSERT(c
!= candidates
.end(), XLevelRuntime
, "FreeRespawnLocationFinder: list of candidates corrupt");
204 preferred_position
= c
->center();
209 V2
get_position() const { return preferred_position
; }
215 const double FreeRespawnLocationFinder::MAX_DISTANCE_WANTED
= 5.0;