/*
* Copyright 2010 OpenXcom Developers.
*
* This file is part of OpenXcom.
*
* OpenXcom is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* OpenXcom is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OpenXcom. If not, see .
*/
#include "DogfightState.h"
#include
#include "../Engine/Game.h"
#include "../Resource/ResourcePack.h"
#include "../Engine/Palette.h"
#include "../Engine/Screen.h"
#include "../Engine/Language.h"
#include "../Engine/Font.h"
#include "../Engine/SurfaceSet.h"
#include "../Engine/Surface.h"
#include "../Interface/ImageButton.h"
#include "../Interface/Text.h"
#include "../Engine/Timer.h"
#include "Globe.h"
#include "../Savegame/SavedGame.h"
#include "../Savegame/Craft.h"
#include "../Ruleset/RuleCraft.h"
#include "../Savegame/CraftWeapon.h"
#include "../Ruleset/RuleCraftWeapon.h"
#include "../Savegame/Ufo.h"
#include "../Ruleset/RuleUfo.h"
#include "../Engine/Music.h"
#include "../Engine/RNG.h"
#include "../Engine/SoundSet.h"
#include "../Engine/Sound.h"
namespace OpenXcom
{
/**
* Initializes all the elements in the Dogfight window.
* @param game Pointer to the core game.
* @param globe Pointer to the Geoscape globe.
* @param craft Pointer to the craft intercepting.
* @param ufo Pointer to the UFO being intercepted.
*/
DogfightState::DogfightState(Game *game, Globe *globe, Craft *craft, Ufo *ufo) : State(game), _globe(globe), _craft(craft), _ufo(ufo), _timeout(50), _currentDist(640), _targetDist(560), _w1Dist(), _w2Dist(), _end(false), _destroy(false)
{
_targetRadius = _currentRadius = _ufo->getRules()->getRadius();
_screen = false;
// Create objects
_window = new Surface(160, 96, 80, 52);
_battle = new Surface(77, 74, 83, 55);
_weapon1 = new Surface(15, 17, 84, 104);
_range1 = new Surface(21, 74, 99, 55);
_weapon2 = new Surface(15, 17, 144, 104);
_range2 = new Surface(21, 74, 123, 55);
_damage = new Surface(22, 25, 173, 92);
_btnMinimize = new InteractiveSurface(12, 12, 80, 52);
_preview = new InteractiveSurface(160, 96, 80, 52);
_btnStandoff = new ImageButton(36, 15, 163, 56);
_btnCautious = new ImageButton(36, 15, 200, 56);
_btnStandard = new ImageButton(36, 15, 163, 72);
_btnAggressive = new ImageButton(36, 15, 200, 72);
_btnDisengage = new ImageButton(36, 15, 200, 88);
_btnUfo = new ImageButton(36, 17, 200, 104);
_txtAmmo1 = new Text(16, 9, 84, 122);
_txtAmmo2 = new Text(16, 9, 144, 122);
_txtDistance = new Text(40, 9, 196, 124);
_txtStatus = new Text(150, 9, 84, 137);
_animTimer = new Timer(30);
_moveTimer = new Timer(20);
_w1Timer = new Timer(0);
_w2Timer = new Timer(0);
_mode = _btnStandoff;
add(_window);
add(_battle);
add(_weapon1);
add(_range1);
add(_weapon2);
add(_range2);
add(_damage);
add(_btnMinimize);
add(_btnStandoff);
add(_btnCautious);
add(_btnStandard);
add(_btnAggressive);
add(_btnDisengage);
add(_btnUfo);
add(_txtAmmo1);
add(_txtAmmo2);
add(_txtDistance);
add(_preview);
add(_txtStatus);
// Set up objects
Surface *graphic = _game->getResourcePack()->getSurface("INTERWIN.DAT");
graphic->setX(0);
graphic->setY(0);
graphic->getCrop()->x = 0;
graphic->getCrop()->y = 0;
graphic->getCrop()->w = 160;
graphic->getCrop()->h = 96;
_window->drawRect(graphic->getCrop(), 15);
graphic->blit(_window);
_preview->drawRect(graphic->getCrop(), 15);
graphic->getCrop()->y = 96;
graphic->getCrop()->h = 15;
graphic->blit(_preview);
graphic->setY(67);
graphic->getCrop()->y = 111;
graphic->getCrop()->h = 29;
graphic->blit(_preview);
graphic->setY(15);
graphic->getCrop()->y = 140 + 52 * _ufo->getRules()->getSprite();
graphic->getCrop()->h = 52;
graphic->blit(_preview);
_preview->setVisible(false);
_preview->onMouseClick((ActionHandler)&DogfightState::previewClick);
_btnMinimize->onMouseClick((ActionHandler)&DogfightState::btnMinimizeClick);
_btnStandoff->copy(_window);
_btnStandoff->setColor(Palette::blockOffset(5)+4);
_btnStandoff->setGroup(&_mode);
_btnStandoff->onMouseClick((ActionHandler)&DogfightState::btnStandoffClick);
_btnCautious->copy(_window);
_btnCautious->setColor(Palette::blockOffset(5)+4);
_btnCautious->setGroup(&_mode);
_btnCautious->onMouseClick((ActionHandler)&DogfightState::btnCautiousClick);
_btnStandard->copy(_window);
_btnStandard->setColor(Palette::blockOffset(5)+4);
_btnStandard->setGroup(&_mode);
_btnStandard->onMouseClick((ActionHandler)&DogfightState::btnStandardClick);
_btnAggressive->copy(_window);
_btnAggressive->setColor(Palette::blockOffset(5)+4);
_btnAggressive->setGroup(&_mode);
_btnAggressive->onMouseClick((ActionHandler)&DogfightState::btnAggressiveClick);
_btnDisengage->copy(_window);
_btnDisengage->setColor(Palette::blockOffset(5)+4);
_btnDisengage->onMouseClick((ActionHandler)&DogfightState::btnDisengageClick);
_btnDisengage->setGroup(&_mode);
_btnUfo->copy(_window);
_btnUfo->setColor(Palette::blockOffset(5)+4);
_btnUfo->onMouseClick((ActionHandler)&DogfightState::btnUfoClick);
_btnUfo->copy(_window);
_btnUfo->setColor(Palette::blockOffset(5)+4);
_txtAmmo1->setColor(Palette::blockOffset(5)+9);
_txtAmmo2->setColor(Palette::blockOffset(5)+9);
_txtDistance->setColor(Palette::blockOffset(5)+9);
_txtDistance->setText(L"640");
_txtStatus->setColor(Palette::blockOffset(5)+9);
_txtStatus->setText(_game->getLanguage()->getString("STR_STANDOFF"));
SurfaceSet *set = _game->getResourcePack()->getSurfaceSet("INTICON.PCK");
for (int i = 0; i < _craft->getRules()->getWeapons(); i++)
{
CraftWeapon *w = _craft->getWeapons()->at(i);
if (w == 0)
continue;
Surface *weapon = 0, *range = 0;
Text *ammo = 0;
int x1, x2;
if (i == 0)
{
weapon = _weapon1;
range = _range1;
ammo = _txtAmmo1;
x1 = 2;
x2 = 0;
}
else
{
weapon = _weapon2;
range = _range2;
ammo = _txtAmmo2;
x1 = 0;
x2 = 18;
}
// Draw weapon icon
Surface *frame = set->getFrame(w->getRules()->getSprite() + 5);
frame->setX(0);
frame->setY(0);
frame->blit(weapon);
// Draw ammo
std::wstringstream ss;
ss << w->getAmmo();
ammo->setText(ss.str());
// Draw range (1 km = 1 pixel)
Uint8 color = Palette::blockOffset(7) - 1;
range->lock();
int rangeY = range->getHeight() - w->getRules()->getRange(), connectY = 57;
for (int x = x1; x <= x1 + 18; x += 2)
{
range->setPixel(x, rangeY, color);
}
int minY = 0, maxY = 0;
if (rangeY < connectY)
{
minY = rangeY;
maxY = connectY;
}
else if (rangeY > connectY)
{
minY = connectY;
maxY = rangeY;
}
for (int y = minY; y <= maxY; y++)
{
range->setPixel(x1 + x2, y, color);
}
for (int x = x2; x <= x2 + 2; x++)
{
range->setPixel(x, connectY, color);
}
range->unlock();
}
if (!(_craft->getRules()->getWeapons() > 0 && _craft->getWeapons()->at(0) != 0))
{
_weapon1->setVisible(false);
_range1->setVisible(false);
_txtAmmo1->setVisible(false);
}
if (!(_craft->getRules()->getWeapons() > 1 && _craft->getWeapons()->at(1) != 0))
{
_weapon2->setVisible(false);
_range2->setVisible(false);
_txtAmmo2->setVisible(false);
}
Surface *frame = set->getFrame(_craft->getRules()->getSprite() + 11);
frame->setX(0);
frame->setY(0);
frame->blit(_damage);
_animTimer->onTimer((StateHandler)&DogfightState::animate);
_animTimer->start();
_moveTimer->onTimer((StateHandler)&DogfightState::move);
_moveTimer->start();
_w1Timer->onTimer((StateHandler)&DogfightState::fireWeapon1);
_w2Timer->onTimer((StateHandler)&DogfightState::fireWeapon2);
// Set music
_game->getResourcePack()->getMusic("GMINTER")->play();
}
/**
* Deletes timers.
*/
DogfightState::~DogfightState()
{
delete _animTimer;
delete _moveTimer;
delete _w1Timer;
delete _w2Timer;
}
/**
* Runs the dogfighter timers.
*/
void DogfightState::think()
{
_animTimer->think(this, 0);
_moveTimer->think(this, 0);
_w1Timer->think(this, 0);
_w2Timer->think(this, 0);
}
/**
* Animates the window with a palette effect.
*/
void DogfightState::animate()
{
SDL_Color* pal = _window->getPalette();
SDL_Color newpal[16];
for (int i = 0; i < 15; i++)
{
newpal[i] = pal[Palette::blockOffset(7) + i + 1];
}
newpal[15] = pal[Palette::blockOffset(7)];
_window->setPalette(newpal, Palette::blockOffset(7), 16);
// Clears text after a while
if (_timeout == 0)
{
_txtStatus->setText(L"");
}
else
{
_timeout--;
}
}
/**
* Moves the craft towards the UFO according to
* the current interception mode.
*/
void DogfightState::move()
{
// Update distance
if (_currentDist < _targetDist)
{
_currentDist += 4;
}
else if (_currentDist > _targetDist)
{
_currentDist -= 2;
}
std::wstringstream ss;
ss << _currentDist;
_txtDistance->setText(ss.str());
for (int i = 0; i < _craft->getRules()->getWeapons(); i++)
{
CraftWeapon *w = _craft->getWeapons()->at(i);
if (w == 0)
continue;
std::vector *wDist = 0;
Timer *wTimer = 0;
if (i == 0)
{
wDist = &_w1Dist;
wTimer = _w1Timer;
}
else
{
wDist = &_w2Dist;
wTimer = _w2Timer;
}
for (std::vector::iterator d = wDist->begin(); d != wDist->end(); d++)
{
// Update weapons
(*d) += 8;
// Handle weapon damage
if ((*d) >= _currentDist)
{
int acc = RNG::generate(1, 100);
if (acc <= w->getRules()->getAccuracy() && !_ufo->isCrashed())
{
int damage = RNG::generate(w->getRules()->getDamage() / 2, w->getRules()->getDamage());
_ufo->setDamage(_ufo->getDamage() + damage);
setStatus("STR_UFO_HIT");
_currentRadius += 4;
_game->getResourcePack()->getSoundSet("GEO.CAT")->getSound(12)->play();
}
d = wDist->erase(d);
if (d == wDist->end())
break;
}
}
// Handle weapon firing
if (!wTimer->isRunning() && _currentDist <= w->getRules()->getRange() * 8 && w->getAmmo() > 0 && _mode != _btnStandoff && _mode != _btnDisengage && !_ufo->isCrashed())
{
wTimer->start();
if (i == 0)
{
fireWeapon1();
}
else
{
fireWeapon2();
}
}
else if (wTimer->isRunning() && (_currentDist > w->getRules()->getRange() * 8 || w->getAmmo() == 0 || _mode == _btnStandoff || _mode == _btnDisengage || _ufo->isCrashed()))
{
wTimer->stop();
if (w->getAmmo() == 0)
{
if (_mode == _btnCautious)
{
minimumDistance();
}
else if (_mode == _btnStandard)
{
maximumDistance();
}
}
}
}
_battle->clear();
// Draw UFO
if (_targetRadius < _currentRadius)
{
_currentRadius--;
}
if (_currentRadius > 0)
{
for (int r = _currentRadius; r >= 0; r--)
{
_battle->drawCircle(_battle->getWidth() / 2, _battle->getHeight() - _currentDist / 8, r, Palette::blockOffset(7) + 4 + r);
}
}
// Draw weapon shots
for (int i = 0; i < _craft->getRules()->getWeapons(); i++)
{
std::vector *wDist = 0;
int off = 0;
if (i == 0)
{
wDist = &_w1Dist;
off = -1;
}
else
{
wDist = &_w2Dist;
off = 1;
}
for (std::vector::iterator d = wDist->begin(); d != wDist->end(); d++)
{
for (int j = -2; j <= 0; j++)
{
_battle->setPixel(_battle->getWidth() / 2 + off, _battle->getHeight() - (*d) / 8 + j, Palette::blockOffset(7));
}
}
}
// Check when battle is over
if ((_currentDist > 640 && _mode == _btnDisengage) || (_timeout == 0 && _ufo->isCrashed()))
{
_craft->returnToBase();
if (_destroy)
{
// Disengage any other craft
while (_ufo->getFollowers()->size() > 0)
{
for (std::vector::iterator i = _ufo->getFollowers()->begin(); i != _ufo->getFollowers()->end(); i++)
{
Craft *c = dynamic_cast(*i);
if (c)
{
c->returnToBase();
break;
}
}
}
// Clear UFO
for (std::vector::iterator i = _game->getSavedGame()->getUfos()->begin(); i != _game->getSavedGame()->getUfos()->end(); i++)
{
if (*i == _ufo)
{
delete *i;
_game->getSavedGame()->getUfos()->erase(i);
break;
}
}
}
_game->popState();
}
if (!_end && _ufo->isCrashed())
{
if (_ufo->isDestroyed())
{
setStatus("STR_UFO_DESTROYED");
_game->getResourcePack()->getSoundSet("GEO.CAT")->getSound(11)->play();
_destroy = true;
}
else
{
setStatus("STR_UFO_CRASH_LANDS");
_game->getResourcePack()->getSoundSet("GEO.CAT")->getSound(10)->play();
if (!_globe->insideLand(_ufo->getLongitude(), _ufo->getLatitude()))
{
_destroy = true;
}
}
_targetRadius = 0;
_end = true;
_ufo->setSpeed(0);
}
}
/**
* Fires a shot from the first weapon
* equipped on the craft.
*/
void DogfightState::fireWeapon1()
{
_w1Dist.push_back(8);
CraftWeapon *w1 = _craft->getWeapons()->at(0);
w1->setAmmo(w1->getAmmo() - 1);
std::wstringstream ss;
ss << w1->getAmmo();
_txtAmmo1->setText(ss.str());
_game->getResourcePack()->getSoundSet("GEO.CAT")->getSound(w1->getRules()->getSound())->play();
}
/**
* Fires a shot from the second weapon
* equipped on the craft.
*/
void DogfightState::fireWeapon2()
{
_w2Dist.push_back(8);
CraftWeapon *w2= _craft->getWeapons()->at(1);
w2->setAmmo(w2->getAmmo() - 1);
std::wstringstream ss;
ss << w2->getAmmo();
_txtAmmo2->setText(ss.str());
_game->getResourcePack()->getSoundSet("GEO.CAT")->getSound(w2->getRules()->getSound())->play();
}
/**
* Sets the craft to the minimum distance
* required to fire a weapon.
*/
void DogfightState::minimumDistance()
{
int max = 0;
for (std::vector::iterator i = _craft->getWeapons()->begin(); i < _craft->getWeapons()->end(); i++)
{
if (*i == 0)
continue;
if ((*i)->getRules()->getRange() > max && (*i)->getAmmo() > 0)
{
max = (*i)->getRules()->getRange();
}
}
if (max == 0)
{
_targetDist = 560;
}
else
{
_targetDist = max * 8;
}
}
/**
* Sets the craft to the maximum distance
* required to fire a weapon.
*/
void DogfightState::maximumDistance()
{
int min = 1000;
for (std::vector::iterator i = _craft->getWeapons()->begin(); i < _craft->getWeapons()->end(); i++)
{
if (*i == 0)
continue;
if ((*i)->getRules()->getRange() < min && (*i)->getAmmo() > 0)
{
min = (*i)->getRules()->getRange();
}
}
if (min == 1000)
{
_targetDist = 560;
}
else
{
_targetDist = min * 8;
}
}
/**
* Updates the status text and restarts
* the text timeout counter.
* @param status New status text.
*/
void DogfightState::setStatus(std::string status)
{
_txtStatus->setText(_game->getLanguage()->getString(status));
_timeout = 50;
}
/**
* Minimizes the dogfight window.
* @param action Pointer to an action.
*/
void DogfightState::btnMinimizeClick(Action *action)
{
}
/**
* Switches to Standoff mode (maximum range).
* @param action Pointer to an action.
*/
void DogfightState::btnStandoffClick(Action *action)
{
if (!_ufo->isCrashed())
{
setStatus("STR_STANDOFF");
_targetDist = 560;
}
}
/**
* Switches to Cautious mode (maximum weapon range).
* @param action Pointer to an action.
*/
void DogfightState::btnCautiousClick(Action *action)
{
if (!_ufo->isCrashed())
{
setStatus("STR_CAUTIOUS_ATTACK");
if (_craft->getNumWeapons() > 0 && _craft->getWeapons()->at(0) != 0)
{
_w1Timer->setInterval(_craft->getWeapons()->at(0)->getRules()->getCautiousReload() * 75);
}
if (_craft->getNumWeapons() > 1 && _craft->getWeapons()->at(1) != 0)
{
_w2Timer->setInterval(_craft->getWeapons()->at(1)->getRules()->getCautiousReload() * 75);
}
minimumDistance();
}
}
/**
* Switches to Standard mode (minimum weapon range).
* @param action Pointer to an action.
*/
void DogfightState::btnStandardClick(Action *action)
{
if (!_ufo->isCrashed())
{
setStatus("STR_STANDARD_ATTACK");
if (_craft->getNumWeapons() > 0 && _craft->getWeapons()->at(0) != 0)
{
_w1Timer->setInterval(_craft->getWeapons()->at(0)->getRules()->getStandardReload() * 75);
}
if (_craft->getNumWeapons() > 1 && _craft->getWeapons()->at(1) != 0)
{
_w2Timer->setInterval(_craft->getWeapons()->at(1)->getRules()->getStandardReload() * 75);
}
maximumDistance();
}
}
/**
* Switches to Aggressive mode (minimum range).
* @param action Pointer to an action.
*/
void DogfightState::btnAggressiveClick(Action *action)
{
if (!_ufo->isCrashed())
{
setStatus("STR_AGGRESSIVE_ATTACK");
if (_craft->getNumWeapons() > 0 && _craft->getWeapons()->at(0) != 0)
{
_w1Timer->setInterval(_craft->getWeapons()->at(0)->getRules()->getAggressiveReload() * 75);
}
if (_craft->getNumWeapons() > 1 && _craft->getWeapons()->at(1) != 0)
{
_w2Timer->setInterval(_craft->getWeapons()->at(1)->getRules()->getAggressiveReload() * 75);
}
_targetDist = 64;
}
}
/**
* Disengages from the UFO.
* @param action Pointer to an action.
*/
void DogfightState::btnDisengageClick(Action *action)
{
if (!_ufo->isCrashed())
{
setStatus("STR_DISENGAGING");
_targetDist = 800;
}
}
/**
* Shows a front view of the UFO.
* @param action Pointer to an action.
*/
void DogfightState::btnUfoClick(Action *action)
{
_preview->setVisible(true);
// Disable all other buttons to prevent misclicks
_btnStandoff->setVisible(false);
_btnCautious->setVisible(false);
_btnStandard->setVisible(false);
_btnAggressive->setVisible(false);
_btnDisengage->setVisible(false);
_btnUfo->setVisible(false);
_btnMinimize->setVisible(false);
}
/**
* Shows a front view of the UFO.
* @param action Pointer to an action.
*/
void DogfightState::previewClick(Action *action)
{
_preview->setVisible(false);
// Reenable all other buttons to prevent misclicks
_btnStandoff->setVisible(true);
_btnCautious->setVisible(true);
_btnStandard->setVisible(true);
_btnAggressive->setVisible(true);
_btnDisengage->setVisible(true);
_btnUfo->setVisible(true);
_btnMinimize->setVisible(true);
}
}