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 "ui/graphical/game/temp/unitdrawingengine.h"
23 #include "ui/graphical/game/animations/animationtimer.h"
24 #include "ui/graphical/game/unitselection.h"
25 #include "video.h"
26 #include "game/data/units/building.h"
27 #include "game/data/units/vehicle.h"
28 #include "game/data/player/player.h"
29 #include "game/data/map/map.h"
30 #include "utility/random.h"
31 #include "utility/drawing.h"
32 #include "utility/box.h"
33 
34 //--------------------------------------------------------------------------
cUnitDrawingEngine(std::shared_ptr<cAnimationTimer> animationTimer_,std::shared_ptr<const cFrameCounter> frameCounter)35 cUnitDrawingEngine::cUnitDrawingEngine (std::shared_ptr<cAnimationTimer> animationTimer_, std::shared_ptr<const cFrameCounter> frameCounter) :
36 	animationTimer (std::move (animationTimer_)),
37 	drawingCache (frameCounter),
38 	blinkColor (cRgbColor::white()),
39 	shouldDrawHits (false),
40 	shouldDrawStatus (false),
41 	shouldDrawAmmo (false),
42 	shouldDrawColor (false)
43 {
44 	signalConnectionManager.connect (animationTimer->triggered100ms, std::bind (&cUnitDrawingEngine::rotateBlinkColor, this));
45 }
46 
47 //--------------------------------------------------------------------------
setDrawHits(bool drawHits)48 void cUnitDrawingEngine::setDrawHits (bool drawHits)
49 {
50 	shouldDrawHits = drawHits;
51 }
52 
53 //--------------------------------------------------------------------------
setDrawStatus(bool drawStatus)54 void cUnitDrawingEngine::setDrawStatus (bool drawStatus)
55 {
56 	shouldDrawStatus = drawStatus;
57 }
58 
59 //--------------------------------------------------------------------------
setDrawAmmo(bool drawAmmo)60 void cUnitDrawingEngine::setDrawAmmo (bool drawAmmo)
61 {
62 	shouldDrawAmmo = drawAmmo;
63 }
64 
65 //--------------------------------------------------------------------------
setDrawColor(bool drawColor)66 void cUnitDrawingEngine::setDrawColor (bool drawColor)
67 {
68 	shouldDrawColor = drawColor;
69 }
70 
71 //--------------------------------------------------------------------------
drawUnit(const cBuilding & building,SDL_Rect destination,float zoomFactor,const cUnitSelection * unitSelection,const cPlayer * player)72 void cUnitDrawingEngine::drawUnit (const cBuilding& building, SDL_Rect destination, float zoomFactor, const cUnitSelection* unitSelection, const cPlayer* player)
73 {
74 	unsigned long long animationTime = animationTimer->getAnimationTime(); //call getAnimationTime only once in this method and save the result,
75 	//to avoid a changing time within this method
76 
77 	SDL_Rect dest = {0, 0, 0, 0};
78 	bool bDraw = false;
79 	SDL_Surface* drawingSurface = drawingCache.getCachedImage (building, zoomFactor, animationTime);
80 	if (drawingSurface == nullptr)
81 	{
82 		// no cached image found. building needs to be redrawn.
83 		bDraw = true;
84 		drawingSurface = drawingCache.createNewEntry (building, zoomFactor, animationTime);
85 	}
86 
87 	if (drawingSurface == nullptr)
88 	{
89 		// image will not be cached. So blitt directly to the screen buffer.
90 		dest = destination;
91 		drawingSurface = cVideo::buffer;
92 	}
93 
94 	if (bDraw)
95 	{
96 		building.render (animationTime, drawingSurface, dest, zoomFactor, cSettings::getInstance().isShadows(), true);
97 	}
98 
99 	// now check, whether the image has to be blitted to screen buffer
100 	if (drawingSurface != cVideo::buffer)
101 	{
102 		dest = destination;
103 		SDL_BlitSurface (drawingSurface, nullptr, cVideo::buffer, &dest);
104 
105 		// all following graphic operations are drawn directly to buffer
106 		dest = destination;
107 	}
108 
109 	if (!building.getOwner()) return;
110 
111 	// draw the effect if necessary
112 	if (building.data.powerOnGraphic && cSettings::getInstance().isAnimations() && (building.isUnitWorking() || !building.data.canWork))
113 	{
114 		SDL_Rect tmp = dest;
115 		SDL_SetSurfaceAlphaMod (building.uiData->eff.get(), building.effectAlpha);
116 
117 		CHECK_SCALING (*building.uiData->eff, *building.uiData->eff_org, zoomFactor);
118 		SDL_BlitSurface (building.uiData->eff.get(), nullptr, cVideo::buffer, &tmp);
119 	}
120 
121 	// draw the mark, when a build order is finished
122 	if (building.getOwner() == player && ((!building.isBuildListEmpty() && !building.isUnitWorking() && building.getBuildListItem (0).getRemainingMetal() <= 0) ||
123 										  (building.data.canResearch && building.getOwner()->isCurrentTurnResearchAreaFinished (building.getResearchArea()))))
124 	{
125 		const cRgbColor finishedMarkColor = cRgbColor::green();
126 		const cBox<cPosition> d (cPosition (dest.x + 2, dest.y + 2), cPosition (dest.x + 2 + (building.data.isBig ? 2 * destination.w - 3 : destination.w - 3), dest.y + 2 + (building.data.isBig ? 2 * destination.h - 3 : destination.h - 3)));
127 
128 		drawRectangle (*cVideo::buffer, d, finishedMarkColor.exchangeGreen (255 - 16 * (animationTime % 0x8)), 3);
129 	}
130 
131 #if 0
132 	// disabled color-frame for buildings
133 	//   => now it's original game behavior - see ticket #542 (GER) = FIXED
134 	// but maybe as setting interresting
135 	//   => ticket #784 (ENG) (so I just commented it) = TODO
136 
137 	// draw a colored frame if necessary
138 	if (shouldDrawColor)
139 	{
140 		const Uint32 color = 0xFF000000 | *static_cast<Uint32*> (building.owner->getColorSurface()->pixels);
141 		SDL_Rect d = {Sint16 (dest.x + 1), Sint16 (dest.y + 1), building.data.isBig ? 2 * destination.w - 1 : destination.w - 1, building.data.isBig ? 2 * destination.h - 1 : destination.h - 1};
142 
143 		DrawRectangle (cVideo::buffer, d, color, 1);
144 	}
145 #endif
146 	// draw the seleted-unit-flash-frame for bulidings
147 	if (unitSelection && &building == unitSelection->getSelectedBuilding())
148 	{
149 		Uint16 maxX = building.data.isBig ? destination.w  * 2 : destination.w;
150 		Uint16 maxY = building.data.isBig ? destination.h  * 2 : destination.h;
151 		const int len = maxX / 4;
152 		maxX -= 3;
153 		maxY -= 3;
154 		const cBox<cPosition> d (cPosition (dest.x + 2, dest.y + 2), cPosition (dest.x + 2 + maxX, dest.y + 2 + maxY));
155 
156 		drawSelectionCorner (*cVideo::buffer, d, blinkColor, len);
157 	}
158 
159 	// draw health bar
160 	if (shouldDrawHits)
161 	{
162 		drawHealthBar (building, destination);
163 	}
164 
165 	// draw ammo bar
166 	if (shouldDrawAmmo && (!player || building.getOwner() == player) && building.data.canAttack && building.data.getAmmoMax() > 0)
167 	{
168 		drawMunBar (building, destination);
169 	}
170 
171 	// draw status
172 	if (shouldDrawStatus)
173 	{
174 		drawStatus (building, destination);
175 	}
176 }
177 
178 //--------------------------------------------------------------------------
drawUnit(const cVehicle & vehicle,SDL_Rect destination,float zoomFactor,const cMap & map,const cUnitSelection * unitSelection,const cPlayer * player)179 void cUnitDrawingEngine::drawUnit (const cVehicle& vehicle, SDL_Rect destination, float zoomFactor, const cMap& map, const cUnitSelection* unitSelection, const cPlayer* player)
180 {
181 	unsigned long long animationTime = animationTimer->getAnimationTime(); //call getAnimationTime only once in this method and save the result,
182 	//to avoid a changing time within this method
183 
184 	// calculate screen position
185 	int ox = (int) (vehicle.getMovementOffset().x() * zoomFactor);
186 	int oy = (int) (vehicle.getMovementOffset().y() * zoomFactor);
187 
188 	destination.x += ox;
189 	destination.y += oy;
190 
191 	if (vehicle.getFlightHeight() > 0)
192 	{
193 		destination.x += vehicle.ditherX;
194 		destination.y += vehicle.ditherY;
195 	}
196 
197 	SDL_Rect dest;
198 	dest.x = dest.y = 0;
199 	bool bDraw = false;
200 	SDL_Surface* drawingSurface = drawingCache.getCachedImage (vehicle, zoomFactor, map, animationTime);
201 	if (drawingSurface == nullptr)
202 	{
203 		// no cached image found. building needs to be redrawn.
204 		bDraw = true;
205 		drawingSurface = drawingCache.createNewEntry (vehicle, zoomFactor, map, animationTime);
206 	}
207 
208 	if (drawingSurface == nullptr)
209 	{
210 		// image will not be cached. So blitt directly to the screen buffer.
211 		dest = destination;
212 		drawingSurface = cVideo::buffer;
213 	}
214 
215 	if (bDraw)
216 	{
217 		vehicle.render (&map, animationTime, player, drawingSurface, dest, zoomFactor, cSettings::getInstance().isShadows());
218 	}
219 
220 	// now check, whether the image has to be blitted to screen buffer
221 	if (drawingSurface != cVideo::buffer)
222 	{
223 		dest = destination;
224 		SDL_BlitSurface (drawingSurface, nullptr, cVideo::buffer, &dest);
225 	}
226 
227 	// draw overlay if necessary:
228 	vehicle.drawOverlayAnimation (animationTime, cVideo::buffer, destination, zoomFactor);
229 
230 	// remove the dithering for the following operations
231 	if (vehicle.getFlightHeight() > 0)
232 	{
233 		destination.x -= vehicle.ditherX;
234 		destination.y -= vehicle.ditherY;
235 	}
236 
237 	// remove movement offset for working units
238 	if (vehicle.isUnitBuildingABuilding() || vehicle.isUnitClearing())
239 	{
240 		destination.x -= ox;
241 		destination.y -= oy;
242 	}
243 
244 	// draw indication, when building is complete
245 	if (vehicle.isUnitBuildingABuilding() && vehicle.getBuildTurns() == 0 && vehicle.getOwner() == player && !vehicle.BuildPath)
246 	{
247 		const cRgbColor finishedMarkColor = cRgbColor::green();
248 		const cBox<cPosition> d (cPosition (destination.x + 2, destination.y + 2), cPosition (destination.x + 2 + (vehicle.data.isBig ? 2 * destination.w - 3 : destination.w - 3), destination.y + 2 + (vehicle.data.isBig ? 2 * destination.h - 3 : destination.h - 3)));
249 
250 		drawRectangle (*cVideo::buffer, d, finishedMarkColor.exchangeGreen (255 - 16 * (animationTime % 0x8)), 3);
251 	}
252 
253 	// Draw the colored frame if necessary
254 	if (shouldDrawColor)
255 	{
256 		const cBox<cPosition> d (cPosition (destination.x + 1, destination.y + 1), cPosition (destination.x + 1 + (vehicle.data.isBig ? 2 * destination.w - 1 : destination.w - 1), destination.y + 1 + (vehicle.data.isBig ? 2 * destination.h - 1 : destination.h - 1)));
257 
258 		drawRectangle (*cVideo::buffer, d, vehicle.getOwner()->getColor().getColor());
259 	}
260 
261 	// draw the group selected frame if necessary
262 	if (unitSelection && unitSelection->getSelectedUnitsCount() > 1 && unitSelection->isSelected (vehicle))
263 	{
264 		const cRgbColor groupSelectionColor = cRgbColor::yellow();
265 		const cBox<cPosition> d (cPosition (destination.x + 2, destination.y + 2), cPosition (destination.x + 2 + (vehicle.data.isBig ? 2 * destination.w - 3 : destination.w - 3), destination.y + 2 + (vehicle.data.isBig ? 2 * destination.h - 3 : destination.h - 3)));
266 
267 		drawRectangle (*cVideo::buffer, d, groupSelectionColor, 1);
268 	}
269 	// draw the seleted-unit-flash-frame for vehicles
270 	if (unitSelection && &vehicle == unitSelection->getSelectedVehicle())
271 	{
272 		Uint16 maxX = vehicle.data.isBig ? destination.w * 2 : destination.w;
273 		Uint16 maxY = vehicle.data.isBig ? destination.h * 2 : destination.h;
274 		const int len = maxX / 4;
275 		maxX -= 3;
276 		maxY -= 3;
277 		const cBox<cPosition> d (cPosition (destination.x + 2, destination.y + 2), cPosition (destination.x + 2 + maxX, destination.y + 2 + maxY));
278 
279 		drawSelectionCorner (*cVideo::buffer, d, blinkColor, len);
280 	}
281 
282 	// draw health bar
283 	if (shouldDrawHits)
284 	{
285 		drawHealthBar (vehicle, destination);
286 	}
287 
288 	// draw ammo bar
289 	if (shouldDrawAmmo && (!player || vehicle.getOwner() == player) && vehicle.data.canAttack)
290 	{
291 		drawMunBar (vehicle, destination);
292 	}
293 
294 	// draw status info
295 	if (shouldDrawStatus)
296 	{
297 		drawStatus (vehicle, destination);
298 	}
299 
300 }
301 
302 //--------------------------------------------------------------------------
drawHealthBar(const cUnit & unit,SDL_Rect destination)303 void cUnitDrawingEngine::drawHealthBar (const cUnit& unit, SDL_Rect destination)
304 {
305 	SDL_Rect r1;
306 	r1.x = destination.x + destination.w / 10 + 1;
307 	r1.y = destination.y + destination.h / 10;
308 	r1.w = destination.w * 8 / 10;
309 	r1.h = destination.h / 8;
310 
311 	if (unit.data.isBig)
312 	{
313 		r1.w += destination.w;
314 		r1.h *= 2;
315 	}
316 
317 	if (r1.h <= 2)
318 		r1.h = 3;
319 
320 	SDL_Rect r2;
321 	r2.x = r1.x + 1;
322 	r2.y = r1.y + 1;
323 	r2.w = (int) (((float) (r1.w - 2) / unit.data.getHitpointsMax()) * unit.data.getHitpoints());
324 	r2.h = r1.h - 2;
325 
326 	SDL_FillRect (cVideo::buffer, &r1, 0xFF000000);
327 
328 	Uint32 color;
329 	if (unit.data.getHitpoints() > unit.data.getHitpointsMax() / 2)
330 		color = 0xFF04AE04; // green
331 	else if (unit.data.getHitpoints() > unit.data.getHitpointsMax() / 4)
332 		color = 0xFFDBDE00; // orange
333 	else
334 		color = 0xFFE60000; // red
335 	SDL_FillRect (cVideo::buffer, &r2, color);
336 }
337 
338 //--------------------------------------------------------------------------
drawMunBar(const cUnit & unit,SDL_Rect destination)339 void cUnitDrawingEngine::drawMunBar (const cUnit& unit, SDL_Rect destination)
340 {
341 	SDL_Rect r1;
342 	r1.x = destination.x + destination.w / 10 + 1;
343 	r1.y = destination.y + destination.h / 10 + destination.h / 8;
344 	r1.w = destination.w * 8 / 10;
345 	r1.h = destination.h / 8;
346 
347 	if (r1.h <= 2)
348 	{
349 		r1.y += 1;
350 		r1.h = 3;
351 	}
352 
353 	SDL_Rect r2;
354 	r2.x = r1.x + 1;
355 	r2.y = r1.y + 1;
356 	r2.w = (int) (((float) (r1.w - 2) / unit.data.getAmmoMax()) * unit.data.getAmmo());
357 	r2.h = r1.h - 2;
358 
359 	SDL_FillRect (cVideo::buffer, &r1, 0xFF000000);
360 
361 	if (unit.data.getAmmo() > unit.data.getAmmoMax() / 2)
362 		SDL_FillRect (cVideo::buffer, &r2, 0xFF04AE04);
363 	else if (unit.data.getAmmo() > unit.data.getAmmoMax() / 4)
364 		SDL_FillRect (cVideo::buffer, &r2, 0xFFDBDE00);
365 	else
366 		SDL_FillRect (cVideo::buffer, &r2, 0xFFE60000);
367 }
368 
369 //--------------------------------------------------------------------------
drawStatus(const cUnit & unit,SDL_Rect destination)370 void cUnitDrawingEngine::drawStatus (const cUnit& unit, SDL_Rect destination)
371 {
372 	SDL_Rect speedSymbol = {244, 97, 8, 10};
373 	SDL_Rect shotsSymbol = {254, 97, 5, 10};
374 	SDL_Rect disabledSymbol = {150, 109, 25, 25};
375 	SDL_Rect dest;
376 
377 	if (unit.isDisabled())
378 	{
379 		if (destination.w < 25)
380 			return;
381 		dest.x = destination.x + destination.w / 2 - 12;
382 		dest.y = destination.y + destination.h / 2 - 12;
383 		if (unit.data.isBig)
384 		{
385 			dest.y += (destination.h / 2);
386 			dest.x += (destination.w / 2);
387 		}
388 		SDL_BlitSurface (GraphicsData.gfx_hud_stuff.get(), &disabledSymbol, cVideo::buffer, &dest);
389 	}
390 	else
391 	{
392 		dest.y = destination.y + destination.h - 11;
393 		dest.x = destination.x + destination.w / 2 - 4;
394 		if (unit.data.isBig)
395 		{
396 			dest.y += (destination.h / 2);
397 			dest.x += (destination.w / 2);
398 		}
399 		if (unit.data.getSpeed() >= 4)
400 		{
401 			if (unit.data.getShots())
402 				dest.x -= destination.w / 4;
403 
404 			SDL_Rect destCopy = dest;
405 			SDL_BlitSurface (GraphicsData.gfx_hud_stuff.get(), &speedSymbol, cVideo::buffer, &destCopy);
406 		}
407 
408 		dest.x = destination.x + destination.w / 2 - 4;
409 		if (unit.data.getShots())
410 		{
411 			if (unit.data.getSpeed())
412 				dest.x += destination.w / 4;
413 			SDL_BlitSurface (GraphicsData.gfx_hud_stuff.get(), &shotsSymbol, cVideo::buffer, &dest);
414 		}
415 	}
416 }
417 
418 //--------------------------------------------------------------------------
rotateBlinkColor()419 void cUnitDrawingEngine::rotateBlinkColor()
420 {
421 	static bool dec = true;
422 	if (dec)
423 	{
424 		blinkColor.r -= 0x0A;
425 		blinkColor.g -= 0x0A;
426 		blinkColor.b -= 0x0A;
427 		if (blinkColor.r <= 0xA0) dec = false;
428 	}
429 	else
430 	{
431 		blinkColor.r += 0x0A;
432 		blinkColor.g += 0x0A;
433 		blinkColor.b += 0x0A;
434 		if (blinkColor.r >= (0xFF - 0x0A)) dec = true;
435 	}
436 }
437