Initial commit
This commit is contained in:
668
engines/crab/animation/sprite.cpp
Normal file
668
engines/crab/animation/sprite.cpp
Normal file
@@ -0,0 +1,668 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* This code is based on the CRAB engine
|
||||
*
|
||||
* Copyright (c) Arvind Raja Yadav
|
||||
*
|
||||
* Licensed under MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include "crab/animation/sprite.h"
|
||||
#include "crab/input/cursor.h"
|
||||
|
||||
namespace Crab {
|
||||
|
||||
using namespace pyrodactyl::ai;
|
||||
using namespace pyrodactyl::anim;
|
||||
using namespace pyrodactyl::input;
|
||||
using namespace pyrodactyl::event;
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// Purpose: Constructor
|
||||
//------------------------------------------------------------------------
|
||||
Sprite::Sprite() : _imgSize(1, 1), _velMod(1.0f, 1.0f) {
|
||||
_dir = DIRECTION_LEFT;
|
||||
_layer = -1;
|
||||
_image = 0;
|
||||
visible(true);
|
||||
_damageDone = false;
|
||||
_hover = false;
|
||||
// _pathing.SetSprite(this);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// Purpose: Load sprite from XML and animations from the index of all animation files
|
||||
//------------------------------------------------------------------------
|
||||
void Sprite::load(rapidxml::xml_node<char> *node, Common::Array<Common::Path> &animations) {
|
||||
using namespace pyrodactyl::image;
|
||||
|
||||
if (nodeValid(node)) {
|
||||
loadStr(_id, "id", node);
|
||||
|
||||
// The amount by which the sprite x,y should be multiplied
|
||||
int multiply = 1;
|
||||
loadNum(multiply, "multiply", node, false);
|
||||
|
||||
loadNum(_pos.x, "x", node);
|
||||
loadNum(_pos.y, "y", node);
|
||||
|
||||
_pos.x *= multiply;
|
||||
_pos.y *= multiply;
|
||||
|
||||
loadImgKey(_image, "img", node);
|
||||
loadNum(_layer, "layer", node, false);
|
||||
|
||||
Image dat;
|
||||
g_engine->_imageManager->getTexture(_image, dat);
|
||||
_imgSize.x = dat.w();
|
||||
_imgSize.y = dat.h();
|
||||
|
||||
uint index = 0;
|
||||
loadNum(index, "moveset", node);
|
||||
|
||||
if (index < animations.size())
|
||||
_animSet.load(animations[index]);
|
||||
|
||||
_animSet._fight.listAttackMoves(_aiData._fight._attack);
|
||||
|
||||
loadDirection(_dir, node);
|
||||
_clip = _animSet._walk.clip(_dir);
|
||||
_boxV = _animSet._walk.boxV(_dir);
|
||||
|
||||
if (nodeValid("visible", node, false))
|
||||
_visible.load(node->first_node("visible"));
|
||||
|
||||
if (nodeValid("movement", node, false))
|
||||
_aiData._walk.load(node->first_node("movement"));
|
||||
|
||||
if (nodeValid("popup", node, false))
|
||||
_popup.load(node->first_node("popup"));
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// Purpose: Move along x and y axis
|
||||
//------------------------------------------------------------------------
|
||||
void Sprite::move(const SpriteConstant &sc) {
|
||||
if (_target.x == 0.0f && (_vel.x > -sc._tweening && _vel.x < sc._tweening))
|
||||
_vel.x = 0.0f;
|
||||
else {
|
||||
_vel.x += (_target.x - _vel.x) * sc._tweening;
|
||||
_pos.x += _vel.x;
|
||||
}
|
||||
|
||||
if (_target.y == 0.0f && (_vel.y > -sc._tweening && _vel.y < sc._tweening))
|
||||
_vel.y = 0.0f;
|
||||
else {
|
||||
_vel.y += (_target.y - _vel.y) * sc._tweening;
|
||||
_pos.y += _vel.y;
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// Purpose: Resolve collision with shapes excluding the level bounding rectangle
|
||||
//------------------------------------------------------------------------
|
||||
void Sprite::resolveCollide() {
|
||||
// NOTE: we don't check i->intersect here because we do that in the level functions
|
||||
for (const auto &i : _collideData) {
|
||||
Rect bounds = boundRect();
|
||||
if (i._type == SHAPE_POLYGON) {
|
||||
_pos.x -= i._data.x;
|
||||
_pos.y -= i._data.y;
|
||||
} else {
|
||||
Direction d = bounds.resolveY(i._data);
|
||||
if (d == DIRECTION_UP)
|
||||
_pos.y -= i._data.y + i._data.h - _animSet._bounds.y + _animSet.anchorY(_dir) + 1;
|
||||
else if (d == DIRECTION_DOWN)
|
||||
_pos.y -= i._data.y - bounds.h - _animSet._bounds.y + _animSet.anchorY(_dir) - 1;
|
||||
|
||||
d = bounds.resolveX(i._data);
|
||||
if (d == DIRECTION_LEFT)
|
||||
_pos.x -= i._data.x + i._data.w - _animSet._bounds.x + _animSet.anchorX(_dir) + 1;
|
||||
else if (d == DIRECTION_RIGHT)
|
||||
_pos.x -= i._data.x - bounds.w - _animSet._bounds.x + _animSet.anchorX(_dir) - 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the collision data
|
||||
_collideData.clear();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// Purpose: Resolve collision with the level bounding rectangle
|
||||
//------------------------------------------------------------------------
|
||||
void Sprite::resolveInside(Rect collider) {
|
||||
Rect bounds = boundRect();
|
||||
Direction d = bounds.resolveX(collider);
|
||||
|
||||
if (d == DIRECTION_RIGHT)
|
||||
_pos.x = collider.x - _animSet._bounds.x + _animSet.anchorX(_dir) + 1;
|
||||
else if (d == DIRECTION_LEFT)
|
||||
_pos.x = collider.x + collider.w - _animSet._bounds.x - bounds.w + _animSet.anchorX(_dir) - 1;
|
||||
|
||||
bounds = boundRect();
|
||||
d = bounds.resolveY(collider);
|
||||
|
||||
if (d == DIRECTION_DOWN)
|
||||
_pos.y = collider.y - _animSet._bounds.y + _animSet.anchorY(_dir) + 1;
|
||||
else if (d == DIRECTION_UP)
|
||||
_pos.y = collider.y + collider.h - _animSet._bounds.y - bounds.h + _animSet.anchorY(_dir) - 1;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// Purpose: Various rectangles of the sprite
|
||||
// Note: Every sprite in the game uses bottom center coordinates:
|
||||
// draw_x = x - clip.w/2, draw_y = y - clip.h
|
||||
//------------------------------------------------------------------------
|
||||
|
||||
// Used for walking and level geometry collision
|
||||
Rect Sprite::boundRect() {
|
||||
Rect rect;
|
||||
rect.x = _pos.x + _animSet._bounds.x - _animSet.anchorX(_dir);
|
||||
rect.y = _pos.y + _animSet._bounds.y - _animSet.anchorY(_dir);
|
||||
rect.w = _animSet._bounds.w;
|
||||
rect.h = _animSet._bounds.h;
|
||||
return rect;
|
||||
}
|
||||
|
||||
// Used for fighting
|
||||
Rect Sprite::boxV() {
|
||||
Rect rect;
|
||||
rect.x = _pos.x + _boxV.x;
|
||||
rect.y = _pos.y + _boxV.y;
|
||||
rect.w = _boxV.w;
|
||||
rect.h = _boxV.h;
|
||||
return rect;
|
||||
}
|
||||
|
||||
// Used for fighting
|
||||
Rect Sprite::boxD() {
|
||||
Rect rect;
|
||||
rect.x = _pos.x + _boxD.x;
|
||||
rect.y = _pos.y + _boxD.y;
|
||||
rect.w = _boxD.w;
|
||||
rect.h = _boxD.h;
|
||||
return rect;
|
||||
}
|
||||
|
||||
// Used for drawing object notifications over stuff
|
||||
Rect Sprite::posRect() {
|
||||
Rect rect;
|
||||
rect.x = _pos.x - _animSet.anchorX(_dir);
|
||||
rect.y = _pos.y - _animSet.anchorY(_dir);
|
||||
rect.w = _clip.w;
|
||||
rect.h = _clip.h;
|
||||
return rect;
|
||||
}
|
||||
|
||||
// The range rectangle is relative to the bounding rectangle, and needs to be updated according to the actual position
|
||||
// Used for enemy sprite to find their required spot to attack the player
|
||||
Rect Sprite::rangeRect(const Rect &bounds, const Range &range) {
|
||||
Rect rect;
|
||||
rect.x = bounds.x + range._val[_dir].x;
|
||||
rect.y = bounds.y + range._val[_dir].y;
|
||||
rect.w = range._val[_dir].w;
|
||||
rect.h = range._val[_dir].h;
|
||||
|
||||
return rect;
|
||||
}
|
||||
|
||||
// Used for focusing the camera on the sprite
|
||||
Vector2i Sprite::camFocus() {
|
||||
Vector2i v;
|
||||
v.x = _pos.x + _animSet._focus.x;
|
||||
v.y = _pos.y + _animSet._focus.y;
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// Purpose: Draw the sprite
|
||||
//------------------------------------------------------------------------
|
||||
void Sprite::draw(pyrodactyl::event::Info &info, const Rect &camera) {
|
||||
using namespace pyrodactyl::image;
|
||||
using namespace pyrodactyl::text;
|
||||
|
||||
int x = _pos.x - camera.x - _animSet.anchorX(_dir), y = _pos.y - camera.y - _animSet.anchorY(_dir);
|
||||
|
||||
// Draw the shadow image relative to the bottom center of the sprite
|
||||
ShadowOffset sh = _animSet.shadow(_dir);
|
||||
if (sh._valid) {
|
||||
// Draw using custom offset
|
||||
g_engine->_imageManager->draw(x + _clip.w / 2 - _animSet._shadow._size.x + sh.x,
|
||||
y + _clip.h - _animSet._shadow._size.y + sh.y,
|
||||
_animSet._shadow._img);
|
||||
} else {
|
||||
// Draw using default offset
|
||||
g_engine->_imageManager->draw(x + _clip.w / 2 - _animSet._shadow._size.x + _animSet._shadow._offset.x,
|
||||
y + _clip.h - _animSet._shadow._size.y + _animSet._shadow._offset.y,
|
||||
_animSet._shadow._img);
|
||||
}
|
||||
|
||||
g_engine->_imageManager->draw(x, y, _image, &_clip, _animSet.flip(_dir));
|
||||
_imgEff.draw(x, y);
|
||||
|
||||
if (g_engine->_debugDraw & DRAW_SPRITE_BOUNDS) {
|
||||
// Nice boxes for the frames and boxV, boxD
|
||||
Rect bounds = boundRect(), vul = boxV(), dmg = boxD(), debugpos = posRect();
|
||||
bounds.draw(-camera.x, -camera.y);
|
||||
debugpos.draw(-camera.x, -camera.y, 255, 255, 255);
|
||||
dmg.draw(-camera.x, -camera.y, 255, 0, 0);
|
||||
vul.draw(-camera.x, -camera.y, 0, 0, 255);
|
||||
|
||||
FightMove fm;
|
||||
if (_animSet._fight.nextMove(fm)) {
|
||||
Rect actualRange;
|
||||
actualRange.x = bounds.x + fm._ai._range._val[_dir].x;
|
||||
actualRange.y = bounds.y + fm._ai._range._val[_dir].y;
|
||||
actualRange.w = fm._ai._range._val[_dir].w;
|
||||
actualRange.h = fm._ai._range._val[_dir].h;
|
||||
|
||||
actualRange.draw(-camera.x, -camera.y, 255, 0, 255);
|
||||
}
|
||||
}
|
||||
|
||||
if (g_engine->_debugDraw & DRAW_PATHING) {
|
||||
for (const auto &iter : _pathing._vSolution) {
|
||||
bool nextToWall = false;
|
||||
for (const auto &neighbor : iter->_neighborNodes) {
|
||||
if (neighbor->getMovementCost() < 0 || neighbor->getMovementCost() > 1) {
|
||||
nextToWall = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (nextToWall)
|
||||
iter->getRect().draw(-camera.x, -camera.y, 0, 0, 0, 254);
|
||||
else
|
||||
iter->getRect().draw(-camera.x, -camera.y, 200, 200, 0, 254);
|
||||
}
|
||||
|
||||
if (_pathing._goalTile && _pathing._startTile) {
|
||||
_pathing._goalTile->getRect().draw(-camera.x, -camera.y, 0, 0, 200, 254);
|
||||
_pathing._startTile->getRect().draw(-camera.x, -camera.y, 0, 200, 0, 254);
|
||||
}
|
||||
|
||||
Rect destinationRect = Rect((int)_pathing._destination.x - 5,
|
||||
(int)_pathing._destination.y - 5,
|
||||
10,
|
||||
10);
|
||||
destinationRect.draw(-camera.x, -camera.y, 0, 200, 0, 254);
|
||||
}
|
||||
}
|
||||
|
||||
void Sprite::drawPopup(pyrodactyl::ui::ParagraphData &pop, const Rect &camera) {
|
||||
// This is different from draw because we draw the popup centered over the head
|
||||
int x = _pos.x - camera.x - _animSet.anchorX(_dir) + (_animSet._bounds.w / 2);
|
||||
int y = _pos.y - camera.y - _animSet.anchorY(_dir);
|
||||
|
||||
_popup.draw(x, y, pop, camera);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// Purpose: Handle the movement in a level for the player only
|
||||
//------------------------------------------------------------------------
|
||||
void Sprite::handleEvents(Info &info, const Rect &camera, const SpriteConstant &sc, const Common::Event &event) {
|
||||
|
||||
int num = 0;
|
||||
info.statGet(_id, pyrodactyl::stat::STAT_SPEED, num);
|
||||
++num;
|
||||
float playerSpeed = static_cast<float>(num);
|
||||
|
||||
// This is for Diablo style hold-mouse-button-in-direction-of-movement
|
||||
// This is only used if - point and click movement isn't being used, cursor is not inside the hud, the cursor is a normal cursor and the mouse is pressed
|
||||
if (!_aiData._dest._active && !g_engine->_mouse->_insideHud && !g_engine->_mouse->_hover && g_engine->_mouse->pressed()) {
|
||||
// To find where the click is w.r.t sprite, we need to see where it is being drawn
|
||||
int x = _pos.x - camera.x - _animSet.anchorX(_dir), y = _pos.y - camera.y - _animSet.anchorY(_dir);
|
||||
|
||||
// Just use the bound rectangle dimensions
|
||||
Rect b = boundRect();
|
||||
int w = b.w, h = b.h;
|
||||
|
||||
// X axis
|
||||
if (g_engine->_mouse->_motion.x > x + w)
|
||||
xVel(playerSpeed * sc._walkVelMod.x);
|
||||
else if (g_engine->_mouse->_motion.x < x)
|
||||
xVel(-playerSpeed * sc._walkVelMod.x);
|
||||
else
|
||||
xVel(0.0f);
|
||||
|
||||
// Y axis
|
||||
if (g_engine->_mouse->_motion.y > y + h)
|
||||
yVel(playerSpeed * sc._walkVelMod.y);
|
||||
else if (g_engine->_mouse->_motion.y < y)
|
||||
yVel(-playerSpeed * sc._walkVelMod.y);
|
||||
else
|
||||
yVel(0.0f);
|
||||
} else { // Keyboard movement
|
||||
// Disable destination as soon as player presses a direction key
|
||||
// X axis
|
||||
if (g_engine->_inputManager->state(IG_LEFT)) {
|
||||
_aiData._dest._active = false;
|
||||
xVel(-playerSpeed * sc._walkVelMod.x);
|
||||
} else if (g_engine->_inputManager->state(IG_RIGHT)) {
|
||||
_aiData._dest._active = false;
|
||||
xVel(playerSpeed * sc._walkVelMod.x);
|
||||
} else if (!_aiData._dest._active)
|
||||
xVel(0.0f);
|
||||
|
||||
// Y axis
|
||||
if (g_engine->_inputManager->state(IG_UP)) {
|
||||
_aiData._dest._active = false;
|
||||
yVel(-playerSpeed * sc._walkVelMod.y);
|
||||
} else if (g_engine->_inputManager->state(IG_DOWN)) {
|
||||
_aiData._dest._active = false;
|
||||
yVel(playerSpeed * sc._walkVelMod.y);
|
||||
} else if (!_aiData._dest._active)
|
||||
yVel(0.0f);
|
||||
|
||||
}
|
||||
|
||||
updateMove(_input.handleEvents(event));
|
||||
|
||||
// This is to prevent one frame of drawing with incorrect parameters
|
||||
animate(info);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// Purpose: Set destination for sprite movement
|
||||
//------------------------------------------------------------------------
|
||||
void Sprite::setDestPathfinding(const Vector2i &dest, bool reachable) {
|
||||
_aiData.dest(dest, true);
|
||||
_pathing.setDestination(dest, reachable);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// Purpose: Walking animation
|
||||
//------------------------------------------------------------------------
|
||||
void Sprite::walk(const pyrodactyl::people::PersonState &pst) {
|
||||
_imgEff._visible = false;
|
||||
|
||||
bool firstX = true;
|
||||
|
||||
if (_aiData._dest._active) {
|
||||
Rect b = boundRect();
|
||||
if (_pos.x - _aiData._dest.x > -b.w && _pos.x - _aiData._dest.x < b.w)
|
||||
firstX = false;
|
||||
}
|
||||
|
||||
bool reset = _animSet._walk.type(_vel, _dir, pst, firstX);
|
||||
|
||||
if (reset)
|
||||
_animSet._walk.resetClip(_dir);
|
||||
|
||||
walk(reset);
|
||||
}
|
||||
|
||||
void Sprite::walk(const bool &reset) {
|
||||
if (_animSet._walk.updateClip(_dir, reset)) {
|
||||
_clip = _animSet._walk.clip(_dir);
|
||||
_boxV = _animSet._walk.boxV(_dir);
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// Purpose: Decide which animation to play
|
||||
//------------------------------------------------------------------------
|
||||
void Sprite::animate(Info &info) {
|
||||
if (_input.idle())
|
||||
walk(info.state(_id));
|
||||
else
|
||||
updateFrame(info.state(_id));
|
||||
}
|
||||
|
||||
void Sprite::animate(const pyrodactyl::people::PersonState &pst) {
|
||||
walk(pst);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// Purpose: We need to find if the vulnerable area of this sprite collides
|
||||
// with hitbox (usually the damage area of another sprite)
|
||||
//------------------------------------------------------------------------
|
||||
bool Sprite::fightCollide(Rect hitbox, Rect enemyBounds, Range &range, const SpriteConstant &sc) {
|
||||
Rect bounds = boundRect();
|
||||
|
||||
if (range._valid) {
|
||||
Rect actualRange = rangeRect(bounds, range);
|
||||
|
||||
// The second part is a sanity check so the stray hitbox of a sprite 1000 pixels below does not cause damage
|
||||
if (hitbox.collide(actualRange) && abs(bounds.y + bounds.h - enemyBounds.y - enemyBounds.h) < sc._planeW)
|
||||
return true;
|
||||
} else {
|
||||
if (hitbox.collide(bounds) && abs(bounds.y + bounds.h - enemyBounds.y - enemyBounds.h) < sc._planeW)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// Purpose: Update the frame info of the sprite
|
||||
//------------------------------------------------------------------------
|
||||
void Sprite::updateFrame(const pyrodactyl::people::PersonState &pst, const bool &repeat) {
|
||||
FrameUpdateResult res = _animSet._fight.updateFrame(_dir);
|
||||
if (res == FUR_SUCCESS) {
|
||||
assignFrame();
|
||||
} else if (res == FUR_FAIL) {
|
||||
_damageDone = false;
|
||||
stop();
|
||||
|
||||
if (repeat == false)
|
||||
resetFrame(pst);
|
||||
else
|
||||
_animSet._fight.frameIndex(0);
|
||||
}
|
||||
}
|
||||
|
||||
void Sprite::assignFrame() {
|
||||
FightAnimFrame faf;
|
||||
if (_animSet._fight.curFrame(faf, _dir)) {
|
||||
_clip = faf._clip;
|
||||
boxV(faf._boxV);
|
||||
boxD(faf._boxD);
|
||||
|
||||
_pos.x += faf._delta.x;
|
||||
_pos.y += faf._delta.y;
|
||||
|
||||
_input._state = faf._state;
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// Purpose: Update the move info of the player sprite
|
||||
//------------------------------------------------------------------------
|
||||
void Sprite::updateMove(const FightAnimationType &combo) {
|
||||
if (combo != FA_IDLE) {
|
||||
if (_input.idle())
|
||||
forceUpdateMove(combo);
|
||||
else {
|
||||
FightAnimFrame faf;
|
||||
if (_animSet._fight.curFrame(faf, _dir))
|
||||
if (faf._branch)
|
||||
forceUpdateMove(combo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Sprite::forceUpdateMove(const FightAnimationType &combo) {
|
||||
uint index = _animSet._fight.findMove(combo, _input._state);
|
||||
forceUpdateMove(index);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// Purpose: Update the move info of the AI or player sprite
|
||||
//------------------------------------------------------------------------
|
||||
void Sprite::updateMove(const uint &index) {
|
||||
if (_input.idle())
|
||||
forceUpdateMove(index);
|
||||
}
|
||||
|
||||
void Sprite::forceUpdateMove(const uint &index) {
|
||||
if (_animSet._fight.forceUpdate(index, _input, _dir)) {
|
||||
// This sets the sprite input to the current move input
|
||||
_animSet._fight.curCombo(_input);
|
||||
|
||||
stop();
|
||||
assignFrame();
|
||||
} else
|
||||
_input.reset();
|
||||
}
|
||||
//------------------------------------------------------------------------
|
||||
// Purpose: Reset the frame info of the sprite
|
||||
//------------------------------------------------------------------------
|
||||
void Sprite::resetFrame(const pyrodactyl::people::PersonState &pst) {
|
||||
_input.reset();
|
||||
walk(true);
|
||||
_animSet._fight.reset();
|
||||
|
||||
_boxD.w = 0;
|
||||
_boxD.h = 0;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// Purpose: Check if both sprites in the same plane
|
||||
//------------------------------------------------------------------------
|
||||
bool Sprite::damageValid(Sprite &s, const SpriteConstant &sc) {
|
||||
// Get the y coordinates where these sprites are standing
|
||||
float Y = _pos.y + _clip.h, SY = s._pos.y + s._clip.h;
|
||||
|
||||
if (abs(Y - SY) < sc._planeW)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// Purpose: Checks about dealing damage to sprite
|
||||
//------------------------------------------------------------------------
|
||||
void Sprite::calcProperties(Info &info) {
|
||||
_visible.evaluate(info);
|
||||
_animSet._fight.evaluate(info);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// Purpose: Checks about dealing damage to sprite
|
||||
//------------------------------------------------------------------------
|
||||
bool Sprite::takingDamage(Sprite &sp, const SpriteConstant &sc) {
|
||||
if (damageValid(sp, sc))
|
||||
if (boxV().w > 0 && boxV().h > 0 && sp.boxD().w > 0 && sp.boxD().h > 0)
|
||||
if (boxV().collide(sp.boxD()))
|
||||
return true;
|
||||
|
||||
/*Common::String words = NumberToString(BoxV().x) + " " + NumberToString(BoxV().y)+ " " + NumberToString(BoxV().w)
|
||||
+ " " + NumberToString(BoxV().h) + "\n" + NumberToString(sp.BoxD().x) + " " + NumberToString(sp.BoxD().y)
|
||||
+ " " + NumberToString(sp.BoxD().w) + " " + NumberToString(sp.BoxD().h) + "\n";
|
||||
fprintf(stdout,words.c_str());*/
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// Purpose: We know we are taking damage, now is the time to update stats
|
||||
//------------------------------------------------------------------------
|
||||
void Sprite::takeDamage(Info &info, Sprite &s) {
|
||||
using namespace pyrodactyl::stat;
|
||||
using namespace pyrodactyl::music;
|
||||
|
||||
FightMove f;
|
||||
if (s._animSet._fight.curMove(f) && info.personValid(s.id()) && info.personValid(_id)) {
|
||||
int dmg = -1 * (f._eff._dmg + info.personGet(s.id())._stat._val[STAT_ATTACK]._cur - info.personGet(_id)._stat._val[STAT_DEFENSE]._cur);
|
||||
if (dmg >= 0)
|
||||
dmg = -1;
|
||||
|
||||
info.statChange(_id, STAT_HEALTH, dmg);
|
||||
|
||||
int health = 1;
|
||||
info.statGet(_id, STAT_HEALTH, health);
|
||||
|
||||
// Play death animation if dead, hurt animation otherwise
|
||||
if (health <= 0 && f._eff._death != -1)
|
||||
forceUpdateMove(f._eff._death);
|
||||
else if (f._eff._hurt != -1)
|
||||
forceUpdateMove(f._eff._hurt);
|
||||
|
||||
g_engine->_musicManager->playEffect(f._eff._hit, 0);
|
||||
_imgEff = f._eff._img;
|
||||
}
|
||||
|
||||
stop();
|
||||
s._damageDone = true;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// Purpose: We have 2 sprites, *this and s.
|
||||
// Check damage between s.boxV and this->boxD and vice versa
|
||||
//------------------------------------------------------------------------
|
||||
void Sprite::exchangeDamage(Info &info, Sprite &s, const SpriteConstant &sc) {
|
||||
using namespace pyrodactyl::people;
|
||||
using namespace pyrodactyl::stat;
|
||||
|
||||
// This object is taking damage from s
|
||||
if (!s._damageDone && takingDamage(s, sc))
|
||||
takeDamage(info, s);
|
||||
|
||||
// Is the other sprite taking damage from this sprite?
|
||||
if (!_damageDone && s.takingDamage(*this, sc))
|
||||
s.takeDamage(info, *this);
|
||||
|
||||
// We change the animation to dying in order to give time to the death animation to play out
|
||||
int num = 0;
|
||||
info.statGet(s.id(), STAT_HEALTH, num);
|
||||
if (num <= 0) {
|
||||
info.state(s.id(), PST_DYING);
|
||||
info.statChange(s.id(), STAT_HEALTH, 1);
|
||||
}
|
||||
|
||||
info.statGet(_id, STAT_HEALTH, num);
|
||||
if (num <= 0) {
|
||||
info.state(_id, PST_DYING);
|
||||
info.statChange(_id, STAT_HEALTH, 1);
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// Purpose: Update status of ambient dialog via popup object
|
||||
//------------------------------------------------------------------------
|
||||
void Sprite::internalEvents(Info &info, const Common::String &player_id, Common::Array<EventResult> &result, Common::Array<EventSeqInfo> &end_seq) {
|
||||
_popup.internalEvents(info, player_id, result, end_seq);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// Purpose: Save all sprite positions to save file
|
||||
//------------------------------------------------------------------------
|
||||
void Sprite::saveState(rapidxml::xml_document<> &doc, rapidxml::xml_node<char> *root) {
|
||||
root->append_attribute(doc.allocate_attribute("id", _id.c_str()));
|
||||
root->append_attribute(doc.allocate_attribute("x", g_engine->_stringPool->get(_pos.x)));
|
||||
root->append_attribute(doc.allocate_attribute("y", g_engine->_stringPool->get(_pos.y)));
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// Purpose: Load all sprite positions from save file
|
||||
//------------------------------------------------------------------------
|
||||
void Sprite::loadState(rapidxml::xml_node<char> *node) {
|
||||
loadNum(_pos.x, "x", node);
|
||||
loadNum(_pos.y, "y", node);
|
||||
}
|
||||
|
||||
} // End of namespace Crab
|
||||
Reference in New Issue
Block a user