Added reload of levels on F7 (Update levelpack) to ease the test of changes.
[enigmagame.git] / src / actors_internal.hh
blobdc12dfdad897b65268b51f48bb3305a64a299308
1 namespace
3 // helper class to find good respawn positions
5 struct ExaminedLocation : public GridPos {
6 public:
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;
23 bool actor_is_marble;
24 V2 preferred_position;
26 double max_enemy_gap;
27 V2 max_gap_pos;
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'
38 return
39 k != "fl-abyss" &&
40 k != "fl-water" &&
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'
46 return
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;
64 return 0.3;
67 double distance_wanted_to(const Actor& a) {
68 double dist = 0.3;
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 ");
74 return dist;
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();
87 ++ai)
89 Actor *a = *ai;
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;
108 max_gap_pos = p;
113 return found_near_enemy;
116 return false;
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);
133 if (st) {
134 if (!search_through_stone(*st)) continue_search = false;
135 may_respawn = 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);
146 public:
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) {
170 examine(*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; }
213 namespace
215 const double FreeRespawnLocationFinder::MAX_DISTANCE_WANTED = 5.0;