1 /***************************************************************************
2  *      Mechanized Assault and Exploration Reloaded Projectfile            *
3  *                                                                         *
4  *   This program is free software; you can redistribute it and/or modify  *
5  *   it under the terms of the GNU General Public License as published by  *
6  *   the Free Software Foundation; either version 2 of the License, or     *
7  *   (at your option) any later version.                                   *
8  *                                                                         *
9  *   This program is distributed in the hope that it will be useful,       *
10  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
11  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
12  *   GNU General Public License for more details.                          *
13  *                                                                         *
14  *   You should have received a copy of the GNU General Public License     *
15  *   along with this program; if not, write to the                         *
16  *   Free Software Foundation, Inc.,                                       *
17  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
18  ***************************************************************************/
19 
20 #include <algorithm>
21 
22 #include "drawingcache.h"
23 
24 #include "game/data/units/building.h"
25 #include "game/logic/client.h"
26 #include "loaddata.h"
27 #include "game/data/player/player.h"
28 #include "settings.h"
29 #include "game/data/units/vehicle.h"
30 #include "game/data/map/map.h"
31 #include "ui/graphical/game/animations/animationtimer.h"
32 #include "ui/graphical/framecounter.h"
33 
sDrawingCacheEntry()34 sDrawingCacheEntry::sDrawingCacheEntry()
35 {}
36 
sDrawingCacheEntry(sDrawingCacheEntry && other)37 sDrawingCacheEntry::sDrawingCacheEntry(sDrawingCacheEntry&& other) :
38 	BaseN(other.BaseN),
39 	BaseBN(other.BaseBN),
40 	BaseE(other.BaseE),
41 	BaseBE(other.BaseBE),
42 	BaseS(other.BaseS),
43 	BaseBS(other.BaseBS),
44 	BaseW(other.BaseW),
45 	BaseBW(other.BaseBW),
46 	clan(other.clan),
47 	frame(other.frame),
48 	flightHigh(other.flightHigh),
49 	big(other.big),
50 	isBuilding(other.isBuilding),
51 	isClearing(other.isClearing),
52 	stealth(other.stealth),
53 	water(other.water),
54 	id(other.id),
55 	owner(other.owner),
56 	dir(other.dir),
57 	zoom(other.zoom),
58 	lastUsed(other.lastUsed),
59 	surface(std::move(other.surface))
60 {}
61 
operator =(sDrawingCacheEntry && other)62 sDrawingCacheEntry& sDrawingCacheEntry::operator=(sDrawingCacheEntry&& other)
63 {
64 	BaseN = other.BaseN;
65 	BaseBN = other.BaseBN;
66 	BaseE = other.BaseE;
67 	BaseBE = other.BaseBE;
68 	BaseS = other.BaseS;
69 	BaseBS = other.BaseBS;
70 	BaseW = other.BaseW;
71 	BaseBW = other.BaseBW;
72 	clan = other.clan;
73 
74 	frame = other.frame;
75 	flightHigh = other.flightHigh;
76 	big = other.big;
77 	isBuilding = other.isBuilding;
78 	isClearing = other.isClearing;
79 	stealth = other.stealth;
80 	water = other.water;
81 
82 	id = other.id;
83 	owner = other.owner;
84 	dir = other.dir;
85 	zoom = other.zoom;
86 	lastUsed = other.lastUsed;
87 
88 	surface = std::move(other.surface);
89 
90 	return *this;
91 }
92 
init(const cVehicle & vehicle,const cMap & map,const cPlayer * player,unsigned long long animationTime,double zoom_,unsigned long long frameNr)93 void sDrawingCacheEntry::init (const cVehicle& vehicle, const cMap& map, const cPlayer* player, unsigned long long animationTime, double zoom_, unsigned long long frameNr)
94 {
95 	dir = vehicle.dir;
96 	owner = vehicle.getOwner();
97 	isBuilding = vehicle.isUnitBuildingABuilding();
98 	isClearing = vehicle.isUnitClearing();
99 	flightHigh = vehicle.getFlightHeight();
100 	big = vehicle.data.isBig;
101 	id = vehicle.data.ID;
102 	if (vehicle.data.animationMovement)
103 		frame = vehicle.WalkFrame;
104 	else
105 		frame = animationTime % 4;
106 
107 	water = map.isWaterOrCoast (vehicle.getPosition()) && !map.getField (vehicle.getPosition()).getBaseBuilding();
108 
109 	bool isOnWaterAndNotCoast = map.isWater (vehicle.getPosition());
110 	//if the vehicle can also drive on land, we have to check, whether there is a brige, platform, etc.
111 	//because the vehicle will drive on the bridge
112 	cBuilding* building = map.getField (vehicle.getPosition()).getBaseBuilding();
113 	if (vehicle.data.factorGround > 0 && building
114 		&& (building->data.surfacePosition == sUnitData::SURFACE_POS_ABOVE_SEA
115 			|| building->data.surfacePosition == sUnitData::SURFACE_POS_BASE
116 			|| building->data.surfacePosition == sUnitData::SURFACE_POS_ABOVE_BASE))
117 	{
118 		isOnWaterAndNotCoast = false;
119 	}
120 	if ((vehicle.data.isStealthOn & TERRAIN_SEA) && isOnWaterAndNotCoast && vehicle.detectedByPlayerList.empty() && vehicle.getOwner() == player)
121 		stealth = true;
122 	else
123 		stealth = false;
124 
125 	zoom = zoom_;
126 	lastUsed = frameNr;
127 
128 	//determine needed size of the surface
129 	int height = (int) std::max (vehicle.uiData->img_org[vehicle.dir]->h * zoom, vehicle.uiData->shw_org[vehicle.dir]->h * zoom);
130 	int width  = (int) std::max (vehicle.uiData->img_org[vehicle.dir]->w * zoom, vehicle.uiData->shw_org[vehicle.dir]->w * zoom);
131 	if (vehicle.getFlightHeight() > 0)
132 	{
133 		int shwOff = ((int) (Round (vehicle.uiData->img_org[vehicle.dir]->w * zoom) * (vehicle.getFlightHeight() / 64.0f)));
134 		height += shwOff;
135 		width  += shwOff;
136 	}
137 	if (vehicle.isUnitClearing() || vehicle.isUnitBuildingABuilding())
138 	{
139 		width  = 130;
140 		height = 130;
141 	}
142 	surface = AutoSurface (SDL_CreateRGBSurface (0, width, height, 32, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000));
143 
144 	SDL_FillRect (surface.get(), nullptr, SDL_MapRGBA (surface->format, 0, 0, 0, 0));
145 }
146 
init(const cBuilding & building,double zoom_,unsigned long long frameNr)147 void sDrawingCacheEntry::init (const cBuilding& building, double zoom_, unsigned long long frameNr)
148 {
149 	BaseN  = building.BaseN;
150 	BaseBN = building.BaseBN;
151 	BaseE  = building.BaseE;
152 	BaseBE = building.BaseBE;
153 	BaseS  = building.BaseS;
154 	BaseBS = building.BaseBS;
155 	BaseW  = building.BaseW;
156 	BaseBW = building.BaseBW;
157 	dir = building.dir;
158 	owner = building.getOwner();
159 	id = building.data.ID;
160 	clan = building.getOwner()->getClan();
161 
162 	zoom = zoom_;
163 	lastUsed = frameNr;
164 
165 	//determine needed size of the surface
166 	int height = (int) std::max (building.uiData->img_org->h * zoom, building.uiData->shw_org->h * zoom);
167 	int width  = (int) std::max (building.uiData->img_org->w * zoom, building.uiData->shw_org->w * zoom);
168 	if (building.data.hasFrames) width = (int) (building.uiData->shw_org->w * zoom);
169 
170 	surface = AutoSurface (SDL_CreateRGBSurface (0, width, height, 32, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000));
171 
172 	SDL_FillRect (surface.get(), nullptr, SDL_MapRGBA (surface->format, 0, 0, 0, 0));
173 }
174 
cDrawingCache(std::shared_ptr<const cFrameCounter> frameCounter_)175 cDrawingCache::cDrawingCache (std::shared_ptr<const cFrameCounter> frameCounter_) :
176 	frameCounter (frameCounter_),
177 	player (nullptr)
178 {
179 	//assert (animationTimer != nullptr);
180 
181 	cacheHits = 0;
182 	cacheMisses = 0;
183 	notCached = 0;
184 	cacheSize = 0;
185 	const std::size_t maxCacheSize = cSettings::getInstance().getCacheSize(); //set cache size from config
186 	cachedImages.resize (maxCacheSize);
187 }
188 
setPlayer(const cPlayer * player_)189 void cDrawingCache::setPlayer (const cPlayer* player_)
190 {
191 	player = player_;
192 }
193 
getCachedImage(const cBuilding & building,double zoom,unsigned long long animationTime)194 SDL_Surface* cDrawingCache::getCachedImage (const cBuilding& building, double zoom, unsigned long long animationTime)
195 {
196 	if (!canCache (building)) return nullptr;
197 
198 	for (unsigned int i = 0; i < cacheSize; i++)
199 	{
200 		sDrawingCacheEntry& entry = cachedImages[i];
201 
202 		// check whether the entry's properties are equal to the building
203 		if (entry.id != building.data.ID) continue;
204 		if (entry.owner != building.getOwner()) continue;
205 		if (building.SubBase)
206 		{
207 			if (building.BaseN != entry.BaseN ||
208 				building.BaseE != entry.BaseE ||
209 				building.BaseS != entry.BaseS ||
210 				building.BaseW != entry.BaseW) continue;
211 
212 			if (building.data.isBig)
213 			{
214 				if (building.BaseBN != entry.BaseBN ||
215 					building.BaseBE != entry.BaseBE ||
216 					building.BaseBS != entry.BaseBS ||
217 					building.BaseBW != entry.BaseBW) continue;
218 			}
219 
220 		}
221 		if (building.data.hasFrames && !building.data.isAnimated)
222 		{
223 			if (entry.dir != building.dir) continue;
224 		}
225 		if (entry.zoom != zoom) continue;
226 
227 		if (building.data.hasClanLogos && building.getOwner()->getClan() != entry.clan) continue;
228 
229 		//cache hit!
230 		cacheHits++;
231 		entry.lastUsed = frameCounter->getFrame();
232 		return entry.surface.get();
233 	}
234 
235 	//cache miss!
236 	cacheMisses++;
237 	return nullptr;
238 }
239 
getCachedImage(const cVehicle & vehicle,double zoom,const cMap & map,unsigned long long animationTime)240 SDL_Surface* cDrawingCache::getCachedImage (const cVehicle& vehicle, double zoom, const cMap& map, unsigned long long animationTime)
241 {
242 	if (!canCache (vehicle)) return nullptr;
243 
244 	for (unsigned int i = 0; i < cacheSize; i++)
245 	{
246 		sDrawingCacheEntry& entry = cachedImages[i];
247 
248 		// check whether the entry's properties are equal to the building
249 		if (entry.id != vehicle.data.ID) continue;
250 		if (entry.owner != vehicle.getOwner()) continue;
251 		if (entry.big != vehicle.data.isBig) continue;
252 		if (entry.isBuilding != vehicle.isUnitBuildingABuilding()) continue;
253 		if (entry.isClearing != vehicle.isUnitClearing()) continue;
254 
255 		if (entry.flightHigh != vehicle.getFlightHeight()) continue;
256 		if (entry.dir != vehicle.dir) continue;
257 
258 		if (vehicle.data.animationMovement)
259 		{
260 			if (entry.frame != vehicle.WalkFrame) continue;
261 		}
262 
263 		if (vehicle.isUnitBuildingABuilding() || vehicle.isUnitClearing())
264 		{
265 			if (entry.frame != animationTime % 4) continue;
266 		}
267 
268 		if (entry.zoom != zoom) continue;
269 
270 		bool water = map.isWaterOrCoast (vehicle.getPosition()) && !map.getField (vehicle.getPosition()).getBaseBuilding();
271 		if (vehicle.isUnitBuildingABuilding())
272 		{
273 			if (water != entry.water) continue;
274 		}
275 
276 		//check the stealth flag
277 		bool stealth = false;
278 
279 		bool isOnWaterAndNotCoast = map.isWater (vehicle.getPosition());
280 		const cBuilding* building = map.getField (vehicle.getPosition()).getBaseBuilding();
281 		if (vehicle.data.factorGround > 0 && building
282 			&& (building->data.surfacePosition == sUnitData::SURFACE_POS_ABOVE_SEA
283 				|| building->data.surfacePosition == sUnitData::SURFACE_POS_BASE
284 				|| building->data.surfacePosition == sUnitData::SURFACE_POS_ABOVE_BASE))
285 		{
286 			isOnWaterAndNotCoast = false;
287 		}
288 
289 		if ((vehicle.data.isStealthOn & TERRAIN_SEA) && isOnWaterAndNotCoast && vehicle.detectedByPlayerList.empty() && vehicle.getOwner() == player)
290 			stealth = true;
291 
292 		if (entry.stealth != stealth) continue;
293 
294 		//cache hit!
295 		cacheHits++;
296 		entry.lastUsed = frameCounter->getFrame();
297 		return entry.surface.get();
298 	}
299 
300 	//cache miss!
301 
302 	cacheMisses++;
303 	return nullptr;
304 }
305 
createNewEntry(const cBuilding & building,double zoom,unsigned long long animationTime)306 SDL_Surface* cDrawingCache::createNewEntry (const cBuilding& building, double zoom, unsigned long long animationTime)
307 {
308 	if (!canCache (building)) return nullptr;
309 
310 	if (cacheSize < cachedImages.size())   //cache hasn't reached the max size, so allocate a new entry
311 	{
312 		sDrawingCacheEntry& entry = cachedImages[cacheSize];
313 		cacheSize++;
314 
315 		//set properties of the cached image
316 		entry.init (building, zoom, frameCounter->getFrame());
317 
318 		return entry.surface.get();
319 	}
320 
321 	//try to find an old entry to reuse
322 	int oldest = 0;
323 	for (unsigned int i = 0; i < cacheSize; i++)
324 	{
325 		if (cachedImages[i].lastUsed < cachedImages[oldest].lastUsed)
326 			oldest = i;
327 	}
328 
329 	if (frameCounter->getFrame() - cachedImages[oldest].lastUsed > 5)
330 	{
331 
332 		//entry has not been used for 5 frames. Use it for the new entry.
333 		sDrawingCacheEntry& entry = cachedImages[oldest];
334 
335 		//set properties of the cached image
336 		entry.init (building, zoom, frameCounter->getFrame());
337 
338 		return entry.surface.get();
339 	}
340 
341 	//there are no old entries in the cache.
342 	return nullptr;
343 }
344 
createNewEntry(const cVehicle & vehicle,double zoom,const cMap & map,unsigned long long animationTime)345 SDL_Surface* cDrawingCache::createNewEntry (const cVehicle& vehicle, double zoom, const cMap& map, unsigned long long animationTime)
346 {
347 	if (!canCache (vehicle))
348 		return nullptr;
349 
350 	if (cacheSize < cachedImages.size())   //cache hasn't reached the max size, so allocate a new entry
351 	{
352 		sDrawingCacheEntry& entry = cachedImages[cacheSize];
353 
354 		//set properties of the cached image
355 		entry.init (vehicle, map, player, animationTime, zoom, frameCounter->getFrame());
356 
357 		cacheSize++;
358 		return entry.surface.get();
359 	}
360 
361 	//try to find an old entry to reuse
362 	int oldest = 0;
363 	for (unsigned int i = 0; i < cacheSize; i++)
364 	{
365 		if (cachedImages[i].lastUsed < cachedImages[oldest].lastUsed)
366 			oldest = i;
367 	}
368 
369 	if (frameCounter->getFrame() - cachedImages[oldest].lastUsed > 5)
370 	{
371 
372 		//entry has not been used for 5 frames. Use it for the new entry.
373 		sDrawingCacheEntry& entry = cachedImages[oldest];
374 
375 		//set properties of the cached image
376 		entry.init (vehicle, map, player, animationTime, zoom, frameCounter->getFrame());
377 		return entry.surface.get();
378 	}
379 
380 	//there are no old entries in the cache.
381 	return nullptr;
382 }
383 
flush()384 void cDrawingCache::flush()
385 {
386 	cacheSize = 0;
387 }
388 
canCache(const cBuilding & building)389 bool cDrawingCache::canCache (const cBuilding& building)
390 {
391 	if (!building.getOwner() ||
392 		building.alphaEffectValue ||
393 		building.data.isAnimated)
394 	{
395 		notCached++;
396 		return false;
397 	}
398 	return true;
399 }
400 
canCache(const cVehicle & vehicle)401 bool cDrawingCache::canCache (const cVehicle& vehicle)
402 {
403 	if ((vehicle.isUnitBuildingABuilding() || vehicle.isUnitClearing()) && vehicle.job)
404 	{
405 		notCached++;
406 		return false;
407 	}
408 
409 	if (vehicle.alphaEffectValue)
410 	{
411 		notCached++;
412 		return false;
413 	}
414 
415 	if (vehicle.getFlightHeight() > 0 && vehicle.getFlightHeight() < 64)
416 	{
417 		notCached++;
418 		return false;
419 	}
420 
421 	if (vehicle.isUnitBuildingABuilding() && vehicle.data.isBig && vehicle.bigBetonAlpha < 254)
422 	{
423 		notCached++;
424 		return false;
425 	}
426 	return true;
427 }
428 
resetStatistics()429 void cDrawingCache::resetStatistics()
430 {
431 	cacheMisses = 0;
432 	cacheHits = 0;
433 	notCached = 0;
434 }
435 
getMaxCacheSize() const436 int cDrawingCache::getMaxCacheSize() const
437 {
438 	return cachedImages.size();
439 }
440 
setMaxCacheSize(unsigned int newSize)441 void cDrawingCache::setMaxCacheSize (unsigned int newSize)
442 {
443 	cachedImages.clear();
444 	cachedImages.resize (newSize);
445 	cacheSize = 0;
446 
447 	cSettings::getInstance().setCacheSize (newSize);
448 }
449 
getCacheSize() const450 int cDrawingCache::getCacheSize() const
451 {
452 	return cacheSize;
453 }
454 
getCacheHits() const455 int cDrawingCache::getCacheHits() const
456 {
457 	return cacheHits;
458 }
459 
getCacheMisses() const460 int cDrawingCache::getCacheMisses() const
461 {
462 	return cacheMisses;
463 }
464 
getNotCached() const465 int cDrawingCache::getNotCached() const
466 {
467 	return notCached / 2;
468 }
469