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