1 /*
2  * Copyright 2010-2014 OpenXcom Developers.
3  *
4  * This file is part of OpenXcom.
5  *
6  * OpenXcom is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * OpenXcom is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with OpenXcom.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 #include "BaseView.h"
20 #include <sstream>
21 #include <cmath>
22 #include "../Engine/SurfaceSet.h"
23 #include "../Engine/Action.h"
24 #include "../Savegame/Base.h"
25 #include "../Savegame/BaseFacility.h"
26 #include "../Ruleset/RuleBaseFacility.h"
27 #include "../Savegame/Craft.h"
28 #include "../Ruleset/RuleCraft.h"
29 #include "../Interface/Text.h"
30 #include "../Engine/Palette.h"
31 #include "../Engine/Timer.h"
32 #include "../Engine/Options.h"
33 #include <limits>
34 
35 namespace OpenXcom
36 {
37 
38 /**
39  * Sets up a base view with the specified size and position.
40  * @param width Width in pixels.
41  * @param height Height in pixels.
42  * @param x X position in pixels.
43  * @param y Y position in pixels.
44  */
BaseView(int width,int height,int x,int y)45 BaseView::BaseView(int width, int height, int x, int y) : InteractiveSurface(width, height, x, y), _base(0), _texture(0), _selFacility(0), _big(0), _small(0), _lang(0), _gridX(0), _gridY(0), _selSize(0), _selector(0), _blink(true)
46 {
47 	for (int x = 0; x < BASE_SIZE; ++x)
48 		for (int y = 0; y < BASE_SIZE; ++y)
49 			_facilities[x][y] = 0;
50 
51 	_timer = new Timer(100);
52 	_timer->onTimer((SurfaceHandler)&BaseView::blink);
53 	_timer->start();
54 }
55 
56 /**
57  * Deletes contents.
58  */
~BaseView()59 BaseView::~BaseView()
60 {
61 	delete _selector;
62 	delete _timer;
63 }
64 
65 /**
66  * Changes the various resources needed for text rendering.
67  * The different fonts need to be passed in advance since the
68  * text size can change mid-text, and the language affects
69  * how the text is rendered.
70  * @param big Pointer to large-size font.
71  * @param small Pointer to small-size font.
72  * @param lang Pointer to current language.
73  */
initText(Font * big,Font * small,Language * lang)74 void BaseView::initText(Font *big, Font *small, Language *lang)
75 {
76 	_big = big;
77 	_small = small;
78 	_lang = lang;
79 }
80 
81 /**
82  * Changes the current base to display and
83  * initializes the internal base grid.
84  * @param base Pointer to base to display.
85  */
setBase(Base * base)86 void BaseView::setBase(Base *base)
87 {
88 	_base = base;
89 	_selFacility = 0;
90 
91 	// Clear grid
92 	for (int x = 0; x < BASE_SIZE; ++x)
93 		for (int y = 0; y < BASE_SIZE; ++y)
94 			_facilities[x][y] = 0;
95 
96 	// Fill grid with base facilities
97 	for (std::vector<BaseFacility*>::iterator i = _base->getFacilities()->begin(); i != _base->getFacilities()->end(); ++i)
98 	{
99 		for (int y = (*i)->getY(); y < (*i)->getY() + (*i)->getRules()->getSize(); ++y)
100 		{
101 			for (int x = (*i)->getX(); x < (*i)->getX() + (*i)->getRules()->getSize(); ++x)
102 			{
103 				_facilities[x][y] = *i;
104 			}
105 		}
106 	}
107 
108 	_redraw = true;
109 }
110 
111 /**
112  * Changes the texture to use for drawing
113  * the various base elements.
114  * @param texture Pointer to SurfaceSet to use.
115  */
setTexture(SurfaceSet * texture)116 void BaseView::setTexture(SurfaceSet *texture)
117 {
118 	_texture = texture;
119 }
120 
121 /**
122  * Returns the facility the mouse is currently over.
123  * @return Pointer to base facility (0 if none).
124  */
getSelectedFacility() const125 BaseFacility *BaseView::getSelectedFacility() const
126 {
127 	return _selFacility;
128 }
129 
130 /**
131  * Prevents any mouseover bugs on dismantling base facilities before setBase has had time to update the base.
132  */
resetSelectedFacility()133 void BaseView::resetSelectedFacility()
134 {
135     _facilities[_selFacility->getX()][_selFacility->getY()] = 0;
136     _selFacility = 0;
137 }
138 
139 
140 /**
141  * Returns the X position of the grid square
142  * the mouse is currently over.
143  * @return X position on the grid.
144  */
getGridX() const145 int BaseView::getGridX() const
146 {
147 	return _gridX;
148 }
149 
150 /**
151  * Returns the Y position of the grid square
152  * the mouse is currently over.
153  * @return Y position on the grid.
154  */
getGridY() const155 int BaseView::getGridY() const
156 {
157 	return _gridY;
158 }
159 
160 /**
161  * If enabled, the base view will respond to player input,
162  * highlighting the selected facility.
163  * @param size Facility length (0 disables it).
164  */
setSelectable(int size)165 void BaseView::setSelectable(int size)
166 {
167 	_selSize = size;
168 	if (_selSize > 0)
169 	{
170 		_selector = new Surface(size * GRID_SIZE, size * GRID_SIZE, _x, _y);
171 		_selector->setPalette(getPalette());
172 		SDL_Rect r;
173 		r.w = _selector->getWidth();
174 		r.h = _selector->getHeight();
175 		r.x = 0;
176 		r.y = 0;
177 		_selector->drawRect(&r, Palette::blockOffset(1));
178 		r.w -= 2;
179 		r.h -= 2;
180 		r.x++;
181 		r.y++;
182 		_selector->drawRect(&r, 0);
183 		_selector->setVisible(false);
184 	}
185 	else
186 	{
187 		delete _selector;
188 	}
189 }
190 
191 /**
192  * Returns if a certain facility can be successfully
193  * placed on the currently selected square.
194  * @param rule Facility type.
195  * @return True if placeable, False otherwise.
196  */
isPlaceable(RuleBaseFacility * rule) const197 bool BaseView::isPlaceable(RuleBaseFacility *rule) const
198 {
199 	// Check if square isn't occupied
200 	for (int y = _gridY; y < _gridY + rule->getSize(); ++y)
201 	{
202 		for (int x = _gridX; x < _gridX + rule->getSize(); ++x)
203 		{
204 			if (x < 0 || x >= BASE_SIZE || y < 0 || y >= BASE_SIZE)
205 			{
206 				return false;
207 			}
208 			if (_facilities[x][y] != 0)
209 			{
210 				return false;
211 			}
212 		}
213 	}
214 
215 	bool bq=Options::allowBuildingQueue;
216 
217 	// Check for another facility to connect to
218 	for (int i = 0; i < rule->getSize(); ++i)
219 	{
220 		if ((_gridX > 0 && _facilities[_gridX - 1][_gridY + i] != 0 && (bq || _facilities[_gridX - 1][_gridY + i]->getBuildTime() == 0)) ||
221 			(_gridY > 0 && _facilities[_gridX + i][_gridY - 1] != 0 && (bq || _facilities[_gridX + i][_gridY - 1]->getBuildTime() == 0)) ||
222 			(_gridX + rule->getSize() < BASE_SIZE && _facilities[_gridX + rule->getSize()][_gridY + i] != 0 && (bq || _facilities[_gridX + rule->getSize()][_gridY + i]->getBuildTime() == 0)) ||
223 			(_gridY + rule->getSize() < BASE_SIZE && _facilities[_gridX + i][_gridY + rule->getSize()] != 0 && (bq || _facilities[_gridX + i][_gridY + rule->getSize()]->getBuildTime() == 0)))
224 		{
225 			return true;
226 		}
227 	}
228 
229 	return false;
230 }
231 
232 /**
233  * Returns if the placed facility is placed in queue or not.
234  * @param rule Facility type.
235  * @return True if queued, False otherwise.
236  */
isQueuedBuilding(RuleBaseFacility * rule) const237 bool BaseView::isQueuedBuilding(RuleBaseFacility *rule) const
238 {
239 	for (int i = 0; i < rule->getSize(); ++i)
240 	{
241 		if ((_gridX > 0 && _facilities[_gridX - 1][_gridY + i] != 0 && _facilities[_gridX - 1][_gridY + i]->getBuildTime() == 0) ||
242 			(_gridY > 0 && _facilities[_gridX + i][_gridY - 1] != 0 && _facilities[_gridX + i][_gridY - 1]->getBuildTime() == 0) ||
243 			(_gridX + rule->getSize() < BASE_SIZE && _facilities[_gridX + rule->getSize()][_gridY + i] != 0 && _facilities[_gridX + rule->getSize()][_gridY + i]->getBuildTime() == 0) ||
244 			(_gridY + rule->getSize() < BASE_SIZE && _facilities[_gridX + i][_gridY + rule->getSize()] != 0 && _facilities[_gridX + i][_gridY + rule->getSize()]->getBuildTime() == 0))
245 		{
246 			return false;
247 		}
248 	}
249 	return true;
250 }
251 
252 /**
253  * ReCalculates the remaining build-time of all queued buildings.
254  */
reCalcQueuedBuildings()255 void BaseView::reCalcQueuedBuildings()
256 {
257 	setBase(_base);
258 	std::vector<BaseFacility*> facilities;
259 	for (std::vector<BaseFacility*>::iterator i = _base->getFacilities()->begin(); i != _base->getFacilities()->end(); ++i)
260 		if ((*i)->getBuildTime() > 0)
261 		{
262 			// Set all queued buildings to infinite.
263 			if ((*i)->getBuildTime() > (*i)->getRules()->getBuildTime()) (*i)->setBuildTime(std::numeric_limits<int>::max());
264 			facilities.push_back(*i);
265 		}
266 
267 	// Applying a simple Dijkstra Algorithm
268 	while (!facilities.empty())
269 	{
270 		std::vector<BaseFacility*>::iterator min = facilities.begin();
271 		for (std::vector<BaseFacility*>::iterator i = facilities.begin(); i != facilities.end(); ++i)
272 			if ((*i)->getBuildTime() < (*min)->getBuildTime()) min=i;
273 		BaseFacility* facility=(*min);
274 		facilities.erase(min);
275 		RuleBaseFacility *rule=facility->getRules();
276 		int x=facility->getX(), y=facility->getY();
277 		for (int i = 0; i < rule->getSize(); ++i)
278 		{
279 			if (x > 0) updateNeighborFacilityBuildTime(facility,_facilities[x - 1][y + i]);
280 			if (y > 0) updateNeighborFacilityBuildTime(facility,_facilities[x + i][y - 1]);
281 			if (x + rule->getSize() < BASE_SIZE) updateNeighborFacilityBuildTime(facility,_facilities[x + rule->getSize()][y + i]);
282 			if (y + rule->getSize() < BASE_SIZE) updateNeighborFacilityBuildTime(facility,_facilities[x + i][y + rule->getSize()]);
283 		}
284 	}
285 }
286 
287 /**
288  * Updates the neighborFacility's build time. This is for internal use only (reCalcQueuedBuildings()).
289  * @param facility Pointer to a base facility.
290  * @param neighbor Pointer to a neighboring base facility.
291  */
updateNeighborFacilityBuildTime(BaseFacility * facility,BaseFacility * neighbor)292 void BaseView::updateNeighborFacilityBuildTime(BaseFacility* facility, BaseFacility* neighbor)
293 {
294 	if (0 != facility && 0 != neighbor
295 	&& neighbor->getBuildTime() > neighbor->getRules()->getBuildTime()
296 	&& facility->getBuildTime() + neighbor->getRules()->getBuildTime() < neighbor->getBuildTime())
297 		neighbor->setBuildTime(facility->getBuildTime() + neighbor->getRules()->getBuildTime());
298 }
299 
300 /**
301  * Keeps the animation timers running.
302  */
think()303 void BaseView::think()
304 {
305 	_timer->think(0, this);
306 }
307 
308 /**
309  * Makes the facility selector blink.
310  */
blink()311 void BaseView::blink()
312 {
313 	_blink = !_blink;
314 
315 	if (_selSize > 0)
316 	{
317 		SDL_Rect r;
318 		if (_blink)
319 		{
320 			r.w = _selector->getWidth();
321 			r.h = _selector->getHeight();
322 			r.x = 0;
323 			r.y = 0;
324 			_selector->drawRect(&r, Palette::blockOffset(1));
325 			r.w -= 2;
326 			r.h -= 2;
327 			r.x++;
328 			r.y++;
329 			_selector->drawRect(&r, 0);
330 		}
331 		else
332 		{
333 			r.w = _selector->getWidth();
334 			r.h = _selector->getHeight();
335 			r.x = 0;
336 			r.y = 0;
337 			_selector->drawRect(&r, 0);
338 		}
339 	}
340 }
341 
342 /**
343  * Draws the view of all the facilities in the base, connectors
344  * between them and crafts landed in hangars.
345  */
draw()346 void BaseView::draw()
347 {
348 	Surface::draw();
349 
350 	// Draw grid squares
351 	for (int x = 0; x < BASE_SIZE; ++x)
352 	{
353 		for (int y = 0; y < BASE_SIZE; ++y)
354 		{
355 			Surface *frame = _texture->getFrame(0);
356 			frame->setX(x * GRID_SIZE);
357 			frame->setY(y * GRID_SIZE);
358 			frame->blit(this);
359 		}
360 	}
361 
362 	std::vector<Craft*>::iterator craft = _base->getCrafts()->begin();
363 
364 	for (std::vector<BaseFacility*>::iterator i = _base->getFacilities()->begin(); i != _base->getFacilities()->end(); ++i)
365 	{
366 		// Draw facility shape
367 		int num = 0;
368 		for (int y = (*i)->getY(); y < (*i)->getY() + (*i)->getRules()->getSize(); ++y)
369 		{
370 			for (int x = (*i)->getX(); x < (*i)->getX() + (*i)->getRules()->getSize(); ++x)
371 			{
372 				Surface *frame;
373 
374 				if ((*i)->getBuildTime() == 0)
375 					frame = _texture->getFrame((*i)->getRules()->getSpriteShape() + num);
376 				else
377 					frame = _texture->getFrame((*i)->getRules()->getSpriteShape() + num + 2 + (*i)->getRules()->getSize());
378 
379 				frame->setX(x * GRID_SIZE);
380 				frame->setY(y * GRID_SIZE);
381 				frame->blit(this);
382 
383 				num++;
384 			}
385 		}
386 	}
387 
388 	for (std::vector<BaseFacility*>::iterator i = _base->getFacilities()->begin(); i != _base->getFacilities()->end(); ++i)
389 	{
390 		// Draw connectors
391 		if ((*i)->getBuildTime() == 0)
392 		{
393 			// Facilities to the right
394 			int x = (*i)->getX() + (*i)->getRules()->getSize();
395 			if (x < BASE_SIZE)
396 			{
397 				for (int y = (*i)->getY(); y < (*i)->getY() + (*i)->getRules()->getSize(); ++y)
398 				{
399 					if (_facilities[x][y] != 0 && _facilities[x][y]->getBuildTime() == 0)
400 					{
401 						Surface *frame = _texture->getFrame(7);
402 						frame->setX(x * GRID_SIZE - GRID_SIZE / 2);
403 						frame->setY(y * GRID_SIZE);
404 						frame->blit(this);
405 					}
406 				}
407 			}
408 
409 			// Facilities to the bottom
410 			int y = (*i)->getY() + (*i)->getRules()->getSize();
411 			if (y < BASE_SIZE)
412 			{
413 				for (int x = (*i)->getX(); x < (*i)->getX() + (*i)->getRules()->getSize(); ++x)
414 				{
415 					if (_facilities[x][y] != 0 && _facilities[x][y]->getBuildTime() == 0)
416 					{
417 						Surface *frame = _texture->getFrame(8);
418 						frame->setX(x * GRID_SIZE);
419 						frame->setY(y * GRID_SIZE - GRID_SIZE / 2);
420 						frame->blit(this);
421 					}
422 				}
423 			}
424 		}
425 	}
426 
427 	for (std::vector<BaseFacility*>::iterator i = _base->getFacilities()->begin(); i != _base->getFacilities()->end(); ++i)
428 	{
429 		// Draw facility graphic
430 		int num = 0;
431 		for (int y = (*i)->getY(); y < (*i)->getY() + (*i)->getRules()->getSize(); ++y)
432 		{
433 			for (int x = (*i)->getX(); x < (*i)->getX() + (*i)->getRules()->getSize(); ++x)
434 			{
435 				if ((*i)->getRules()->getSize() == 1)
436 				{
437 					Surface *frame = _texture->getFrame((*i)->getRules()->getSpriteFacility() + num);
438 					frame->setX(x * GRID_SIZE);
439 					frame->setY(y * GRID_SIZE);
440 					frame->blit(this);
441 				}
442 
443 				num++;
444 			}
445 		}
446 
447 		// Draw crafts
448 		if ((*i)->getBuildTime() == 0 && (*i)->getRules()->getCrafts() > 0)
449 		{
450 			if (craft != _base->getCrafts()->end())
451 			{
452 				if ((*craft)->getStatus() != "STR_OUT")
453 				{
454 					Surface *frame = _texture->getFrame((*craft)->getRules()->getSprite() + 33);
455 					frame->setX((*i)->getX() * GRID_SIZE + ((*i)->getRules()->getSize() - 1) * GRID_SIZE / 2 + 2);
456 					frame->setY((*i)->getY() * GRID_SIZE + ((*i)->getRules()->getSize() - 1) * GRID_SIZE / 2 - 4);
457 					frame->blit(this);
458 					(*i)->setCraft(*craft);
459 				}
460 				else
461 				{
462 					(*i)->setCraft(0);
463 				}
464 				++craft;
465 			}
466 			else
467 			{
468 				(*i)->setCraft(0);
469 			}
470 		}
471 
472 		// Draw time remaining
473 		if ((*i)->getBuildTime() > 0)
474 		{
475 			Text *text = new Text(GRID_SIZE * (*i)->getRules()->getSize(), 16, 0, 0);
476 			text->setPalette(getPalette());
477 			text->initText(_big, _small, _lang);
478 			text->setX((*i)->getX() * GRID_SIZE);
479 			text->setY((*i)->getY() * GRID_SIZE + (GRID_SIZE * (*i)->getRules()->getSize() - 16) / 2);
480 			text->setBig();
481 			std::wostringstream ss;
482 			ss << (*i)->getBuildTime();
483 			text->setAlign(ALIGN_CENTER);
484 			text->setColor(Palette::blockOffset(13)+5);
485 			text->setText(ss.str());
486 			text->blit(this);
487 			delete text;
488 		}
489 	}
490 }
491 
492 /**
493  * Blits the base view and selector.
494  * @param surface Pointer to surface to blit onto.
495  */
blit(Surface * surface)496 void BaseView::blit(Surface *surface)
497 {
498 	Surface::blit(surface);
499 	if (_selector != 0)
500 	{
501 		_selector->blit(surface);
502 	}
503 }
504 
505 /**
506  * Selects the facility the mouse is over.
507  * @param action Pointer to an action.
508  * @param state State that the action handlers belong to.
509  */
mouseOver(Action * action,State * state)510 void BaseView::mouseOver(Action *action, State *state)
511 {
512 	_gridX = (int)floor(action->getRelativeXMouse() / (GRID_SIZE * action->getXScale()));
513 	_gridY = (int)floor(action->getRelativeYMouse() / (GRID_SIZE * action->getYScale()));
514 	if (_gridX >= 0 && _gridX < BASE_SIZE && _gridY >= 0 && _gridY < BASE_SIZE)
515 	{
516 		_selFacility = _facilities[_gridX][_gridY];
517 		if (_selSize > 0)
518 		{
519 			if (_gridX + _selSize - 1 < BASE_SIZE && _gridY + _selSize - 1 < BASE_SIZE)
520 			{
521 				_selector->setX(_x + _gridX * GRID_SIZE);
522 				_selector->setY(_y + _gridY * GRID_SIZE);
523 				_selector->setVisible(true);
524 			}
525 			else
526 			{
527 				_selector->setVisible(false);
528 			}
529 		}
530 	}
531 	else
532 	{
533 		_selFacility = 0;
534 		if (_selSize > 0)
535 		{
536 			_selector->setVisible(false);
537 		}
538 	}
539 
540 	InteractiveSurface::mouseOver(action, state);
541 }
542 
543 /**
544  * Deselects the facility.
545  * @param action Pointer to an action.
546  * @param state State that the action handlers belong to.
547  */
mouseOut(Action * action,State * state)548 void BaseView::mouseOut(Action *action, State *state)
549 {
550 	_selFacility = 0;
551 	if (_selSize > 0)
552 	{
553 		_selector->setVisible(false);
554 	}
555 
556 	InteractiveSurface::mouseOut(action, state);
557 }
558 
559 }
560