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