1 #include <cassert>
2 #include <sstream>
3
4 #include "IncExternAI.h"
5 #include "IncGlobalAI.h"
6
7 #define LUA_THREATMAP_DEBUG 0
8
9 CR_BIND(CThreatMap, (NULL))
10 CR_REG_METADATA(CThreatMap, (
11 CR_MEMBER(threatCellsRaw),
12 CR_MEMBER(threatCellsVis),
13 CR_MEMBER(ai),
14 CR_RESERVED(8),
15 CR_POSTLOAD(PostLoad)
16 ))
17
CThreatMap(AIClasses * aic)18 CThreatMap::CThreatMap(AIClasses* aic): ai(aic) {
19 if (ai) {
20 PostLoad();
21
22 #if (LUA_THREATMAP_DEBUG == 1)
23 std::stringstream luaDataStream;
24 // this approach does not work because Spring's loadstring()
25 // function does _not_ run in the global environment, which
26 // means transferring the threatmap values requires an extra
27 // temporary table
28 //
29 // luaDataStream << "threatMapW = " << width << ";\n";
30 // luaDataStream << "threatMapH = " << height << ";\n";
31 //
32 // however, writing to a table declared in GG is fine
33 luaDataStream << "GG.AIThreatMap[\"threatMapSizeX\"] = " << width << ";\n";
34 luaDataStream << "GG.AIThreatMap[\"threatMapSizeZ\"] = " << height << ";\n";
35 luaDataStream << "GG.AIThreatMap[\"threatMapResX\"] = " << THREATRES << ";\n";
36 luaDataStream << "GG.AIThreatMap[\"threatMapResZ\"] = " << THREATRES << ";\n";
37 luaDataStream << "\n";
38 luaDataStream << "local threatMapSizeX = GG.AIThreatMap[\"threatMapSizeX\"];\n";
39 luaDataStream << "local threatMapSizeZ = GG.AIThreatMap[\"threatMapSizeZ\"];\n";
40 luaDataStream << "local threatMapArray = GG.AIThreatMap;\n";
41 luaDataStream << "\n";
42 luaDataStream << "for row = 0, (threatMapSizeZ - 1) do\n";
43 luaDataStream << "\tfor col = 0, (threatMapSizeX - 1) do\n";
44 luaDataStream << "\t\tthreatMapArray[row * threatMapSizeX + col] = 0.0;\n";
45 luaDataStream << "\tend\n";
46 luaDataStream << "end\n";
47 std::string luaDataStr = luaDataStream.str();
48
49 ai->cb->CallLuaRules("[AI::KAIK::ThreatMap::Init]", -1, NULL);
50 ai->cb->CallLuaRules(luaDataStr.c_str(), -1, NULL);
51 #endif
52 }
53
54 currMaxThreat = 0.0f; // maximum threat (normalizer)
55 currSumThreat = 0.0f; // threat summed over all cells
56 currAvgThreat = 0.0f; // average threat over all cells
57 }
58
~CThreatMap()59 CThreatMap::~CThreatMap() {
60 threatCellsRaw.clear();
61 threatCellsVis.clear();
62
63 if (threatMapTexID >= 0) {
64 ai->cb->DebugDrawerDelOverlayTexture(threatMapTexID);
65 }
66 }
67
PostLoad()68 void CThreatMap::PostLoad() {
69 width = ai->cb->GetMapWidth() / THREATRES;
70 height = ai->cb->GetMapHeight() / THREATRES;
71 area = width * height;
72
73 assert(threatCellsRaw.empty());
74 assert(threatCellsVis.empty());
75 threatCellsRaw.resize(area, THREATVAL_BASE);
76 threatCellsVis.resize(area, THREATVAL_BASE);
77
78 threatMapTexID = -1;
79 }
80
ToggleVisOverlay()81 void CThreatMap::ToggleVisOverlay() {
82 if (threatMapTexID < 0) {
83 std::stringstream threatMapLabel;
84 threatMapLabel << "[KAIK][";
85 threatMapLabel << ai->cb->GetMyTeam();
86 threatMapLabel << "][ThreatMap]";
87
88 // /cheat
89 // /debugdrawai
90 // /team N
91 // /spectator
92 // "KAIK::ThreatMap::DBG"
93 threatMapTexID = ai->cb->DebugDrawerAddOverlayTexture(&threatCellsVis[0], width, height);
94
95 ai->cb->DebugDrawerSetOverlayTexturePos(threatMapTexID, 0.50f, 0.25f);
96 ai->cb->DebugDrawerSetOverlayTextureSize(threatMapTexID, 0.40f, 0.40f);
97 ai->cb->DebugDrawerSetOverlayTextureLabel(threatMapTexID, (threatMapLabel.str()).c_str());
98 } else {
99 ai->cb->DebugDrawerDelOverlayTexture(threatMapTexID);
100 threatMapTexID = -1;
101 }
102 }
103
104
105
EnemyCreated(int enemyUnitID)106 void CThreatMap::EnemyCreated(int enemyUnitID) {
107 assert(!threatCellsRaw.empty());
108 assert(ai->ccb->GetUnitDef(enemyUnitID) != NULL);
109
110 EnemyUnit enemyUnit;
111 enemyUnit.id = enemyUnitID;
112 enemyUnit.pos = ai->ccb->GetUnitPos(enemyUnitID);
113 enemyUnit.threat = GetEnemyUnitThreat(enemyUnit);
114 enemyUnit.range = (ai->ut->GetMaxRange(ai->ccb->GetUnitDef(enemyUnitID)) + 100.0f) / (SQUARE_SIZE * THREATRES);
115 enemyUnits[enemyUnitID] = enemyUnit;
116
117 AddEnemyUnit(enemyUnit, 1.0f);
118 }
119
EnemyFinished(int enemyUnitID)120 void CThreatMap::EnemyFinished(int enemyUnitID) {
121 // no-op
122 }
123
EnemyDamaged(int enemyUnitID,int)124 void CThreatMap::EnemyDamaged(int enemyUnitID, int) {
125 assert(!threatCellsRaw.empty());
126
127 std::map<int, EnemyUnit>::iterator it = enemyUnits.find(enemyUnitID);
128
129 if (it != enemyUnits.end()) {
130 EnemyUnit& enemyUnit = it->second;
131
132 DelEnemyUnit(enemyUnit);
133 enemyUnit.threat = GetEnemyUnitThreat(enemyUnit);
134 AddEnemyUnit(enemyUnit, 1.0f);
135 }
136 }
137
EnemyDestroyed(int enemyUnitID,int)138 void CThreatMap::EnemyDestroyed(int enemyUnitID, int) {
139 assert(!threatCellsRaw.empty());
140 std::map<int, EnemyUnit>::const_iterator it = enemyUnits.find(enemyUnitID);
141
142 if (it != enemyUnits.end()) {
143 const EnemyUnit& enemyUnit = it->second;
144
145 DelEnemyUnit(enemyUnit);
146 enemyUnits.erase(enemyUnitID);
147 }
148 }
149
150
151
AddEnemyUnit(const EnemyUnit & e,const float scale)152 void CThreatMap::AddEnemyUnit(const EnemyUnit& e, const float scale) {
153 const int posx = e.pos.x / (SQUARE_SIZE * THREATRES);
154 const int posy = e.pos.z / (SQUARE_SIZE * THREATRES);
155
156 assert(!threatCellsRaw.empty());
157
158 if (!MAPPOS_IN_BOUNDS(e.pos)) {
159 std::stringstream msg;
160 msg << "[CThreatMap::AddEnemyUnit][frame=" << ai->cb->GetCurrentFrame() << "][scale=" << scale << "]\n";
161 msg << "\tposition <" << e.pos.x << ", " << e.pos.z << "> of unit " << e.id;
162 msg << " (health " << ai->ccb->GetUnitHealth(e.id) << ") is out-of-bounds\n";
163 ai->GetLogger()->Log(msg.str());
164 }
165
166 const float threat = e.threat * scale;
167 const float rangeSq = e.range * e.range;
168
169 for (int myx = int(posx - e.range); myx < (posx + e.range); myx++) {
170 if (myx < 0 || myx >= width) {
171 continue;
172 }
173
174 for (int myy = int(posy - e.range); myy < (posy + e.range); myy++) {
175 if (myy < 0 || myy >= height) {
176 continue;
177 }
178
179 const int dxSq = (posx - myx) * (posx - myx);
180 const int dySq = (posy - myy) * (posy - myy);
181
182 if ((dxSq + dySq - 0.5) <= rangeSq) {
183 assert((myy * width + myx) < threatCellsRaw.size());
184 assert((myy * width + myx) < threatCellsVis.size());
185
186 // MicroPather cannot deal with negative costs
187 // (which may arise due to floating-point drift)
188 // nor with zero-cost nodes (see MP::SetMapData,
189 // threat is not used as an additive overlay)
190 threatCellsRaw[myy * width + myx] = std::max(threatCellsRaw[myy * width + myx] + threat, THREATVAL_BASE);
191 threatCellsVis[myy * width + myx] = std::max(threatCellsVis[myy * width + myx] + threat, THREATVAL_BASE);
192
193 currSumThreat += threat;
194 }
195 }
196 }
197
198 currAvgThreat = currSumThreat / area;
199 }
200
DelEnemyUnit(const EnemyUnit & e)201 void CThreatMap::DelEnemyUnit(const EnemyUnit& e) {
202 AddEnemyUnit(e, -1.0f);
203 }
204
205
206
Update()207 void CThreatMap::Update() {
208 currMaxThreat = 0.0f;
209
210 // account for moving units
211 for (std::map<int, EnemyUnit>::iterator it = enemyUnits.begin(); it != enemyUnits.end(); ++it) {
212 EnemyUnit& e = it->second;
213
214 DelEnemyUnit(e);
215 e.pos = ai->ccb->GetUnitPos(e.id);
216 e.threat = GetEnemyUnitThreat(e);
217 AddEnemyUnit(e, 1.0f);
218
219 currMaxThreat = std::max(currMaxThreat, e.threat);
220 }
221
222 // TODO: staggered updates
223 if (threatMapTexID >= 0) {
224 if (currMaxThreat > 0.0f) {
225 for (int i = 0; i < area; i++) {
226 threatCellsVis[i] = (threatCellsRaw[i] - THREATVAL_BASE) / currMaxThreat;
227 }
228
229 ai->cb->DebugDrawerUpdateOverlayTexture(threatMapTexID, &threatCellsVis[0], 0, 0, width, height);
230 }
231 }
232
233 #if (LUA_THREATMAP_DEBUG == 1)
234 {
235 std::string luaDataStr;
236 std::stringstream luaDataStream;
237 luaDataStream << "local threatMapArray = GG.AIThreatMap;\n";
238
239 // just copy the entire map
240 for (int i = 0; i < area; i++) {
241 luaDataStream << "threatMapArray[" << i << "]";
242 luaDataStream << " = ";
243 luaDataStream << (threatCellsRaw[i] - THREATVAL_BASE);
244 luaDataStream << " / ";
245 luaDataStream << currMaxThreat;
246 luaDataStream << ";\n";
247 }
248
249 luaDataStr = luaDataStream.str();
250
251 ai->cb->CallLuaRules("[AI::KAIK::ThreatMap::Update]", -1, NULL);
252 ai->cb->CallLuaRules(luaDataStr.c_str(), -1, NULL);
253 }
254 #endif
255 }
256
257
258
GetEnemyUnitThreat(const EnemyUnit & e) const259 float CThreatMap::GetEnemyUnitThreat(const EnemyUnit& e) const {
260 const UnitDef* ud = ai->ccb->GetUnitDef(e.id);
261
262 // only unarmed units do not register on the threat-map
263 if (ud == NULL || ud->weapons.empty()) {
264 return 0.0f;
265 }
266
267 const float dps = std::min(ai->ut->GetDPS(ud), 2000.0f);
268 const float dpsMod = ai->ccb->GetUnitHealth(e.id) / ai->ccb->GetUnitMaxHealth(e.id);
269
270 return (dps * dpsMod);
271 }
272
ThreatAtThisPoint(const float3 & pos) const273 float CThreatMap::ThreatAtThisPoint(const float3& pos) const {
274 const int z = int(pos.z / (SQUARE_SIZE * THREATRES));
275 const int x = int(pos.x / (SQUARE_SIZE * THREATRES));
276 return (threatCellsRaw[z * width + x] - THREATVAL_BASE);
277 }
278