1 /* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */
2 
3 #include "SkirmishAIWrapper.h"
4 
5 #include "System/FileSystem/DataDirsAccess.h"
6 #include "System/FileSystem/FileQueryFlags.h"
7 #include "System/FileSystem/FileSystem.h"
8 #include "System/Log/ILog.h"
9 #include "System/Util.h"
10 #include "Sim/Units/Unit.h"
11 #include "Sim/Units/UnitHandler.h"
12 #include "Sim/Misc/TeamHandler.h"
13 #include "ExternalAI/AICallback.h"
14 #include "ExternalAI/AICheats.h"
15 #include "ExternalAI/SkirmishAI.h"
16 #include "ExternalAI/EngineOutHandler.h"
17 #include "ExternalAI/SkirmishAIHandler.h"
18 #include "ExternalAI/SkirmishAILibraryInfo.h"
19 #include "ExternalAI/SkirmishAIData.h"
20 #include "ExternalAI/SSkirmishAICallbackImpl.h"
21 #include "ExternalAI/IAILibraryManager.h"
22 #include "ExternalAI/Interface/AISEvents.h"
23 #include "ExternalAI/Interface/AISCommands.h"
24 #include "ExternalAI/Interface/SSkirmishAILibrary.h"
25 
26 #include <sstream>
27 #include <iostream>
28 #include <fstream>
29 
30 #undef DeleteFile
31 
32 CR_BIND_DERIVED(CSkirmishAIWrapper, CObject, )
33 CR_REG_METADATA(CSkirmishAIWrapper, (
34 	CR_MEMBER(skirmishAIId),
35 	CR_MEMBER(teamId),
36 	CR_MEMBER(cheatEvents),
37 	CR_MEMBER(key),
38 
39 	// handled in PostLoad
40 	CR_IGNORED(initialized),
41 	CR_IGNORED(released),
42 	CR_IGNORED(ai),
43 	CR_IGNORED(callback),
44 	CR_IGNORED(cheats),
45 	CR_IGNORED(c_callback),
46 	CR_IGNORED(info),
47 
48 	CR_SERIALIZER(Serialize),
49 	CR_POSTLOAD(PostLoad)
50 ))
51 
52 /// used only by creg
CSkirmishAIWrapper()53 CSkirmishAIWrapper::CSkirmishAIWrapper():
54 		skirmishAIId(-1),
55 		teamId(-1),
56 		cheatEvents(false),
57 		ai(NULL),
58 		initialized(false),
59 		released(false),
60 		callback(NULL),
61 		cheats(NULL),
62 		c_callback(NULL),
63 		info(NULL)
64 {
65 }
66 
CSkirmishAIWrapper(const int skirmishAIId)67 CSkirmishAIWrapper::CSkirmishAIWrapper(const int skirmishAIId):
68 		skirmishAIId(skirmishAIId),
69 		teamId(-1),
70 		cheatEvents(false),
71 		ai(NULL),
72 		initialized(false),
73 		released(false),
74 		callback(NULL),
75 		cheats(NULL),
76 		c_callback(NULL),
77 		info(NULL)
78 {
79 	const SkirmishAIData* aiData = skirmishAIHandler.GetSkirmishAI(skirmishAIId);
80 
81 	teamId = aiData->team;
82 	SkirmishAIKey keyTmp(aiData->shortName, aiData->version);
83 	key    = aiLibManager->ResolveSkirmishAIKey(keyTmp);
84 
85 	CreateCallback();
86 }
87 
CreateCallback()88 void CSkirmishAIWrapper::CreateCallback() {
89 
90 	if (c_callback == NULL) {
91 		callback = new CAICallback(teamId);
92 		cheats = new CAICheats(this);
93 		c_callback = skirmishAiCallback_getInstanceFor(skirmishAIId, teamId, callback, cheats);
94 	}
95 }
96 
PreDestroy()97 void CSkirmishAIWrapper::PreDestroy() {
98 	callback->noMessages = true;
99 }
100 
~CSkirmishAIWrapper()101 CSkirmishAIWrapper::~CSkirmishAIWrapper() {
102 	if (ai) {
103 		if (initialized && !released) {
104 			Release();
105 		}
106 
107 		delete ai;
108 		ai = NULL;
109 
110 		skirmishAiCallback_release(skirmishAIId);
111 		c_callback = NULL;
112 
113 		delete callback;
114 		callback = NULL;
115 
116 		delete cheats;
117 		cheats = NULL;
118 	}
119 }
120 
Serialize(creg::ISerializer * s)121 void CSkirmishAIWrapper::Serialize(creg::ISerializer* s) {
122 }
123 
PostLoad()124 void CSkirmishAIWrapper::PostLoad() {
125 	//CreateCallback();
126 	LoadSkirmishAI(true);
127 }
128 
129 
130 
LoadSkirmishAI(bool postLoad)131 bool CSkirmishAIWrapper::LoadSkirmishAI(bool postLoad) {
132 
133 	ai = new CSkirmishAI(skirmishAIId, teamId, key, GetCallback());
134 	ai->Init();
135 
136 	// check if initialization went ok
137 	if (skirmishAIHandler.IsLocalSkirmishAIDieing(skirmishAIId)) {
138 		return false;
139 	}
140 
141 	IAILibraryManager* libManager = IAILibraryManager::GetInstance();
142 	libManager->FetchSkirmishAILibrary(key);
143 
144 	const CSkirmishAILibraryInfo* infos = &*libManager->GetSkirmishAIInfos().find(key)->second;
145 	bool loadSupported = (infos->GetInfo(SKIRMISH_AI_PROPERTY_LOAD_SUPPORTED) == "yes");
146 
147 	libManager->ReleaseSkirmishAILibrary(key);
148 
149 
150 	if (postLoad && !loadSupported) {
151 		// fallback code to help the AI if it
152 		// doesn't implement load/save support
153 		Init();
154 		for (size_t a = 0; a < unitHandler->MaxUnits(); a++) {
155 			if (!unitHandler->units[a])
156 				continue;
157 
158 			if (unitHandler->units[a]->team == teamId) {
159 				try {
160 					UnitCreated(a, -1);
161 				} CATCH_AI_EXCEPTION;
162 				if (!unitHandler->units[a]->beingBuilt)
163 					try {
164 						UnitFinished(a);
165 					} CATCH_AI_EXCEPTION;
166 			} else {
167 				if ((unitHandler->units[a]->allyteam == teamHandler->AllyTeam(teamId))
168 						|| teamHandler->Ally(teamHandler->AllyTeam(teamId), unitHandler->units[a]->allyteam)) {
169 					// do nothing
170 				} else {
171 					if (unitHandler->units[a]->losStatus[teamHandler->AllyTeam(teamId)] & (LOS_INRADAR | LOS_INLOS)) {
172 						try {
173 							EnemyEnterRadar(a);
174 						} CATCH_AI_EXCEPTION;
175 					}
176 					if (unitHandler->units[a]->losStatus[teamHandler->AllyTeam(teamId)] & LOS_INLOS) {
177 						try {
178 							EnemyEnterLOS(a);
179 						} CATCH_AI_EXCEPTION;
180 					}
181 				}
182 			}
183 		}
184 	}
185 
186 	return true;
187 }
188 
189 
Init()190 void CSkirmishAIWrapper::Init() {
191 
192 	if (ai == NULL) {
193 		bool loadOk = LoadSkirmishAI(false);
194 		if (!loadOk) {
195 			return;
196 		}
197 	}
198 
199 	SInitEvent evtData = {skirmishAIId, GetCallback()};
200 	int error = ai->HandleEvent(EVENT_INIT, &evtData);
201 	if (error != 0) {
202 		// init failed
203 		LOG_L(L_ERROR, "Failed to handle init event: AI for team %d, error %d",
204 				teamId, error);
205 		skirmishAIHandler.SetLocalSkirmishAIDieing(skirmishAIId, 5 /* = AI failed to init */);
206 	} else {
207 		initialized = true;
208 	}
209 }
210 
Dieing()211 void CSkirmishAIWrapper::Dieing() {
212 
213 	if (ai != NULL) {
214 		ai->Dieing();
215 	}
216 }
217 
Release(int reason)218 void CSkirmishAIWrapper::Release(int reason) {
219 
220 	if (initialized && !released) {
221 		SReleaseEvent evtData = {reason};
222 		ai->HandleEvent(EVENT_RELEASE, &evtData);
223 
224 		released = true;
225 
226 		// further cleanup is done in the destructor
227 	}
228 }
229 
streamCopy(std::istream * in,std::ostream * out)230 static void streamCopy(std::istream* in, std::ostream* out)
231 {
232 	static const size_t buffer_size = 128;
233 
234 	// have to use _new_ here for VS compatibility (no C99 support)
235 	char* buffer = new char[buffer_size];
236 
237 	in->read(buffer, buffer_size);
238 	while (in->good()) {
239 		out->write(buffer, in->gcount());
240 		in->read(buffer, buffer_size);
241 	}
242 	out->write(buffer, in->gcount());
243 
244 	delete[] buffer;
245 }
246 
createTempFileName(const char * action,int teamId,int skirmishAIId)247 static std::string createTempFileName(const char* action, int teamId,
248 		int skirmishAIId) {
249 
250 	static const size_t tmpFileName_size = 1024;
251 
252 	// have to use _new_ here for VS compatibility (no C99 support)
253 	char* tmpFileName = new char[tmpFileName_size];
254 	SNPRINTF(tmpFileName, tmpFileName_size, "%s-team%i_id%i.tmp", action,
255 			teamId, skirmishAIId);
256 	std::string tmpFile = dataDirsAccess.LocateFile(tmpFileName,
257 			FileQueryFlags::WRITE | FileQueryFlags::CREATE_DIRS);
258 	delete[] tmpFileName;
259 	return tmpFile;
260 }
261 
Load(std::istream * load_s)262 void CSkirmishAIWrapper::Load(std::istream* load_s)
263 {
264 	const std::string tmpFile = createTempFileName("load", teamId, skirmishAIId);
265 
266 	std::ofstream tmpFile_s;
267 	tmpFile_s.open(tmpFile.c_str(), std::ios::binary);
268 	streamCopy(load_s, &tmpFile_s);
269 	tmpFile_s.close();
270 
271 	SLoadEvent evtData = {tmpFile.c_str()};
272 	ai->HandleEvent(EVENT_LOAD, &evtData);
273 
274 	FileSystem::DeleteFile(tmpFile);
275 }
276 
Save(std::ostream * save_s)277 void CSkirmishAIWrapper::Save(std::ostream* save_s)
278 {
279 	const std::string tmpFile = createTempFileName("save", teamId, skirmishAIId);
280 
281 	SSaveEvent evtData = {tmpFile.c_str()};
282 	ai->HandleEvent(EVENT_SAVE, &evtData);
283 
284 	if (FileSystem::FileExists(tmpFile)) {
285 		std::ifstream tmpFile_s;
286 		tmpFile_s.open(tmpFile.c_str(), std::ios::binary);
287 		streamCopy(&tmpFile_s, save_s);
288 		tmpFile_s.close();
289 		FileSystem::DeleteFile(tmpFile);
290 	}
291 }
292 
UnitIdle(int unitId)293 void CSkirmishAIWrapper::UnitIdle(int unitId) {
294 	SUnitIdleEvent evtData = {unitId};
295 	ai->HandleEvent(EVENT_UNIT_IDLE, &evtData);
296 }
297 
UnitCreated(int unitId,int builderId)298 void CSkirmishAIWrapper::UnitCreated(int unitId, int builderId) {
299 	SUnitCreatedEvent evtData = {unitId, builderId};
300 	ai->HandleEvent(EVENT_UNIT_CREATED, &evtData);
301 }
302 
UnitFinished(int unitId)303 void CSkirmishAIWrapper::UnitFinished(int unitId) {
304 	SUnitFinishedEvent evtData = {unitId};
305 	ai->HandleEvent(EVENT_UNIT_FINISHED, &evtData);
306 }
307 
UnitDestroyed(int unitId,int attackerUnitId)308 void CSkirmishAIWrapper::UnitDestroyed(int unitId, int attackerUnitId) {
309 
310 	SUnitDestroyedEvent evtData = {unitId, attackerUnitId};
311 	ai->HandleEvent(EVENT_UNIT_DESTROYED, &evtData);
312 }
313 
UnitDamaged(int unitId,int attackerUnitId,float damage,const float3 & dir,int weaponDefId,bool paralyzer)314 void CSkirmishAIWrapper::UnitDamaged(int unitId, int attackerUnitId,
315 		float damage, const float3& dir, int weaponDefId, bool paralyzer) {
316 
317 	SUnitDamagedEvent evtData = {unitId, attackerUnitId, damage,
318 			new float[3], weaponDefId, paralyzer};
319 	dir.copyInto(evtData.dir_posF3);
320 	ai->HandleEvent(EVENT_UNIT_DAMAGED, &evtData);
321 	delete [] evtData.dir_posF3;
322 }
323 
UnitMoveFailed(int unitId)324 void CSkirmishAIWrapper::UnitMoveFailed(int unitId) {
325 	SUnitMoveFailedEvent evtData = {unitId};
326 	ai->HandleEvent(EVENT_UNIT_MOVE_FAILED, &evtData);
327 }
328 
UnitGiven(int unitId,int oldTeam,int newTeam)329 void CSkirmishAIWrapper::UnitGiven(int unitId, int oldTeam, int newTeam) {
330 	SUnitGivenEvent evtData = {unitId, oldTeam, newTeam};
331 	ai->HandleEvent(EVENT_UNIT_GIVEN, &evtData);
332 }
333 
UnitCaptured(int unitId,int oldTeam,int newTeam)334 void CSkirmishAIWrapper::UnitCaptured(int unitId, int oldTeam, int newTeam) {
335 	SUnitCapturedEvent evtData = {unitId, oldTeam, newTeam};
336 	ai->HandleEvent(EVENT_UNIT_CAPTURED, &evtData);
337 }
338 
339 
EnemyCreated(int unitId)340 void CSkirmishAIWrapper::EnemyCreated(int unitId) {
341 	SEnemyCreatedEvent evtData = {unitId};
342 	ai->HandleEvent(EVENT_ENEMY_CREATED, &evtData);
343 }
344 
EnemyFinished(int unitId)345 void CSkirmishAIWrapper::EnemyFinished(int unitId) {
346 	SEnemyFinishedEvent evtData = {unitId};
347 	ai->HandleEvent(EVENT_ENEMY_FINISHED, &evtData);
348 }
349 
EnemyEnterLOS(int unitId)350 void CSkirmishAIWrapper::EnemyEnterLOS(int unitId) {
351 	SEnemyEnterLOSEvent evtData = {unitId};
352 	ai->HandleEvent(EVENT_ENEMY_ENTER_LOS, &evtData);
353 }
354 
EnemyLeaveLOS(int unitId)355 void CSkirmishAIWrapper::EnemyLeaveLOS(int unitId) {
356 	SEnemyLeaveLOSEvent evtData = {unitId};
357 	ai->HandleEvent(EVENT_ENEMY_LEAVE_LOS, &evtData);
358 }
359 
EnemyEnterRadar(int unitId)360 void CSkirmishAIWrapper::EnemyEnterRadar(int unitId) {
361 	SEnemyEnterRadarEvent evtData = {unitId};
362 	ai->HandleEvent(EVENT_ENEMY_ENTER_RADAR, &evtData);
363 }
364 
EnemyLeaveRadar(int unitId)365 void CSkirmishAIWrapper::EnemyLeaveRadar(int unitId) {
366 	SEnemyLeaveRadarEvent evtData = {unitId};
367 	ai->HandleEvent(EVENT_ENEMY_LEAVE_RADAR, &evtData);
368 }
369 
EnemyDestroyed(int enemyUnitId,int attackerUnitId)370 void CSkirmishAIWrapper::EnemyDestroyed(int enemyUnitId, int attackerUnitId) {
371 	SEnemyDestroyedEvent evtData = {enemyUnitId, attackerUnitId};
372 	ai->HandleEvent(EVENT_ENEMY_DESTROYED, &evtData);
373 }
374 
EnemyDamaged(int enemyUnitId,int attackerUnitId,float damage,const float3 & dir,int weaponDefId,bool paralyzer)375 void CSkirmishAIWrapper::EnemyDamaged(int enemyUnitId, int attackerUnitId,
376 		float damage, const float3& dir, int weaponDefId, bool paralyzer) {
377 
378 	SEnemyDamagedEvent evtData = {enemyUnitId, attackerUnitId, damage,
379 			new float[3], weaponDefId, paralyzer};
380 	dir.copyInto(evtData.dir_posF3);
381 	ai->HandleEvent(EVENT_ENEMY_DAMAGED, &evtData);
382 	delete [] evtData.dir_posF3;
383 }
384 
Update(int frame)385 void CSkirmishAIWrapper::Update(int frame) {
386 	SUpdateEvent evtData = {frame};
387 	ai->HandleEvent(EVENT_UPDATE, &evtData);
388 }
389 
SendChatMessage(const char * msg,int fromPlayerId)390 void CSkirmishAIWrapper::SendChatMessage(const char* msg, int fromPlayerId) {
391 	SMessageEvent evtData = {fromPlayerId, msg};
392 	ai->HandleEvent(EVENT_MESSAGE, &evtData);
393 }
394 
SendLuaMessage(const char * inData,const char ** outData)395 void CSkirmishAIWrapper::SendLuaMessage(const char* inData, const char** outData) {
396 	SLuaMessageEvent evtData = {inData /*outData*/};
397 	ai->HandleEvent(EVENT_LUA_MESSAGE, &evtData);
398 }
399 
WeaponFired(int unitId,int weaponDefId)400 void CSkirmishAIWrapper::WeaponFired(int unitId, int weaponDefId) {
401 	SWeaponFiredEvent evtData = {unitId, weaponDefId};
402 	ai->HandleEvent(EVENT_WEAPON_FIRED, &evtData);
403 }
404 
PlayerCommandGiven(const std::vector<int> & playerSelectedUnits,const Command & c,int playerId)405 void CSkirmishAIWrapper::PlayerCommandGiven(
406 	const std::vector<int>& playerSelectedUnits,
407 	const Command& c,
408 	int playerId
409 ) {
410 	const int cCommandId = extractAICommandTopic(&c, unitHandler->MaxUnits());
411 	int* unitIds = new int[playerSelectedUnits.size()];
412 
413 	for (unsigned int i = 0; i < playerSelectedUnits.size(); ++i) {
414 		unitIds[i] = playerSelectedUnits[i];
415 	}
416 
417 	SPlayerCommandEvent evtData = {unitIds, static_cast<int>(playerSelectedUnits.size()), cCommandId, playerId};
418 	ai->HandleEvent(EVENT_PLAYER_COMMAND, &evtData);
419 	delete[] unitIds;
420 }
421 
CommandFinished(int unitId,int commandId,int commandTopicId)422 void CSkirmishAIWrapper::CommandFinished(int unitId, int commandId, int commandTopicId) {
423 	SCommandFinishedEvent evtData = {unitId, commandId, commandTopicId};
424 	ai->HandleEvent(EVENT_COMMAND_FINISHED, &evtData);
425 }
426 
SeismicPing(int allyTeam,int unitId,const float3 & pos,float strength)427 void CSkirmishAIWrapper::SeismicPing(int allyTeam, int unitId,
428 		const float3& pos, float strength) {
429 
430 	SSeismicPingEvent evtData = {new float[3], strength};
431 	pos.copyInto(evtData.pos_posF3);
432 	ai->HandleEvent(EVENT_SEISMIC_PING, &evtData);
433 	delete [] evtData.pos_posF3;
434 }
435 
436 
GetTeamId() const437 int CSkirmishAIWrapper::GetTeamId() const {
438 	return teamId;
439 }
440 
GetKey() const441 const SkirmishAIKey& CSkirmishAIWrapper::GetKey() const {
442 	return key;
443 }
444 
GetCallback() const445 const SSkirmishAICallback* CSkirmishAIWrapper::GetCallback() const {
446 	return c_callback;
447 }
448 
SetCheatEventsEnabled(bool enable)449 void CSkirmishAIWrapper::SetCheatEventsEnabled(bool enable) {
450 	cheatEvents = enable;
451 }
IsCheatEventsEnabled() const452 bool CSkirmishAIWrapper::IsCheatEventsEnabled() const {
453 	return cheatEvents;
454 }
455