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