summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorIra Rice <irarice@gmail.com>2009-02-10 12:26:57 -0700
committerIra Rice <irarice@gmail.com>2009-02-10 12:26:57 -0700
commit02e60b55b359002ae1f26f36b40f8fa78ea1a708 (patch)
tree0264392528cf174e2275bb79a84472b77c5245d2 /src
parenta8a992ade40c9d68c9faec1cc3a54e7f064fb9d5 (diff)
downloadmana-client-02e60b55b359002ae1f26f36b40f8fa78ea1a708.tar.gz
mana-client-02e60b55b359002ae1f26f36b40f8fa78ea1a708.tar.bz2
mana-client-02e60b55b359002ae1f26f36b40f8fa78ea1a708.tar.xz
mana-client-02e60b55b359002ae1f26f36b40f8fa78ea1a708.zip
Simplified target drawing so that it actually uses the SimpleAnimation
that it creates when initializing the target cursors in the first place. This behavior was carried over in the first place from the Viewport class. Also moved target drawing responsibility from the map to the being being targeted in the first place. This allows for assuring that targets are always drawn below the sprite being targeted (which the previous solution was designed to do, but didn't do correctly). Signed-off-by: Ira Rice <irarice@gmail.com>
Diffstat (limited to 'src')
-rw-r--r--src/being.cpp62
-rw-r--r--src/being.h18
-rw-r--r--src/localplayer.cpp149
-rw-r--r--src/localplayer.h23
-rw-r--r--src/map.cpp29
-rw-r--r--src/map.h6
-rw-r--r--src/simpleanimation.cpp32
-rw-r--r--src/simpleanimation.h10
8 files changed, 162 insertions, 167 deletions
diff --git a/src/being.cpp b/src/being.cpp
index 0ac86d13..5676ec3f 100644
--- a/src/being.cpp
+++ b/src/being.cpp
@@ -28,20 +28,18 @@
#include "effectmanager.h"
#include "game.h"
#include "graphics.h"
-#include "localplayer.h"
#include "log.h"
#include "map.h"
#include "particle.h"
+#include "simpleanimation.h"
#include "sound.h"
#include "text.h"
#include "gui/speechbubble.h"
#include "resources/colordb.h"
-
#include "resources/emotedb.h"
#include "resources/image.h"
-#include "resources/imageset.h"
#include "resources/itemdb.h"
#include "resources/iteminfo.h"
#include "resources/resourcemanager.h"
@@ -88,7 +86,8 @@ Being::Being(int id, int job, Map *map):
mSpriteIDs(VECTOREND_SPRITE, 0),
mSpriteColors(VECTOREND_SPRITE, ""),
mStatusParticleEffects(&mStunParticleEffects, false),
- mChildParticleEffects(&mStatusParticleEffects, false)
+ mChildParticleEffects(&mStatusParticleEffects, false),
+ mUsedTargetCursor(NULL)
{
setMap(map);
@@ -106,7 +105,8 @@ Being::Being(int id, int job, Map *map):
emotionSet.push_back(AnimatedSprite::load(file, variant));
}
- // Hairstyles are encoded as negative numbers. Count how far negative we can go.
+ // Hairstyles are encoded as negative numbers. Count how far negative
+ // we can go.
int hairstyles = 1;
while (ItemDB::get(-hairstyles).getSprite(GENDER_MALE) != "error.xml")
{
@@ -123,6 +123,7 @@ Being::Being(int id, int job, Map *map):
Being::~Being()
{
+ mUsedTargetCursor = NULL;
delete_all(mSprites);
clearPath();
@@ -142,9 +143,7 @@ Being::~Being()
void Being::setDestination(Uint16 destX, Uint16 destY)
{
if (mMap)
- {
setPath(mMap->findPath(mX, mY, destX, destY));
- }
}
void Being::clearPath()
@@ -225,19 +224,13 @@ void Being::takeDamage(int amount)
// Selecting the right color
if (damage == "miss")
- {
font = hitYellowFont;
- }
else
{
if (getType() == MONSTER)
- {
font = hitBlueFont;
- }
else
- {
font = hitRedFont;
- }
}
// Show damage number
@@ -252,13 +245,9 @@ void Being::showCrit()
// Selecting the right color
if (getType() == MONSTER)
- {
font = hitBlueFont;
- }
else
- {
font = hitRedFont;
- }
// Show crit notice
particleEngine->addTextSplashEffect(text, 255, 255, 255, font,
@@ -276,17 +265,13 @@ void Being::setMap(Map *map)
{
// Remove sprite from potential previous map
if (mMap)
- {
mMap->removeSprite(mSpriteIterator);
- }
mMap = map;
// Add sprite to potential new map
if (mMap)
- {
mSpriteIterator = mMap->addSprite(this);
- }
// Clear particle effect list because child particles became invalid
mChildParticleEffects.clear();
@@ -321,9 +306,7 @@ void Being::setAction(Action action)
for (int i = 0; i < VECTOREND_SPRITE; i++)
{
if (mSprites[i])
- {
mSprites[i]->reset();
- }
}
break;
case HURT:
@@ -344,9 +327,7 @@ void Being::setAction(Action action)
for (int i = 0; i < VECTOREND_SPRITE; i++)
{
if (mSprites[i])
- {
mSprites[i]->play(currentAction);
- }
}
mAction = action;
}
@@ -454,18 +435,18 @@ void Being::logic()
if (mEmotion != 0)
{
mEmotionTime--;
- if (mEmotionTime == 0) {
+ if (mEmotionTime == 0)
mEmotion = 0;
- }
}
// Update sprite animations
+ if (mUsedTargetCursor != NULL)
+ mUsedTargetCursor->update(tick_time * 10);
+
for (int i = 0; i < VECTOREND_SPRITE; i++)
{
if (mSprites[i] != NULL)
- {
mSprites[i]->update(tick_time * 10);
- }
}
// Update particle effects
@@ -479,6 +460,14 @@ void Being::draw(Graphics *graphics, int offsetX, int offsetY) const
int px = mPx + offsetX;
int py = mPy + offsetY;
+ if (mUsedTargetCursor != NULL)
+ {
+ const int width = mSprites[BASE_SPRITE]->getWidth();
+ const int height = mSprites[BASE_SPRITE]->getHeight();
+
+ mUsedTargetCursor->draw(graphics, px, py);
+ }
+
for (int i = 0; i < VECTOREND_SPRITE; i++)
{
if (mSprites[i] != NULL)
@@ -517,11 +506,12 @@ void Being::drawSpeech(int offsetX, int offsetY)
mSpeechBubble->setCaption(mName, mNameColor);
- // Not quite centered, but close enough. However, it's not too important to get
- // it right right now, as it doesn't take bubble collision into account yet.
+ // Not quite centered, but close enough. However, it's not too important
+ // to get it right right now, as it doesn't take bubble collision into
+ // account yet.
mSpeechBubble->setText(mSpeech);
- mSpeechBubble->setPosition(px - (mSpeechBubble->getWidth() * 4 / 11), py - 70 -
- (mSpeechBubble->getNumRows()*14));
+ mSpeechBubble->setPosition(px - (mSpeechBubble->getWidth() * 4 / 11),
+ py - 70 - (mSpeechBubble->getNumRows() * 14));
mSpeechBubble->setVisible(true);
}
else if (mSpeechTime > 0 && !config.getValue("speechbubble", 1))
@@ -586,7 +576,6 @@ int Being::getWidth() const
}
}
-
int Being::getHeight() const
{
if (mSprites[BASE_SPRITE])
@@ -602,3 +591,8 @@ int Being::getHeight() const
}
}
+void Being::setTargetAnimation(SimpleAnimation* animation)
+{
+ mUsedTargetCursor = animation;
+ mUsedTargetCursor->reset();
+}
diff --git a/src/being.h b/src/being.h
index dcbd3553..ef02eeb2 100644
--- a/src/being.h
+++ b/src/being.h
@@ -50,6 +50,7 @@ class Map;
class Graphics;
class Particle;
class Position;
+class SimpleAnimation;
class SpeechBubble;
class Text;
@@ -112,7 +113,6 @@ class Being : public Sprite
NUM_TC
};
-
/**
* Directions, to be used as bitmask values
*/
@@ -359,6 +359,16 @@ class Being : public Sprite
*/
void controlParticle(Particle *particle);
+ /**
+ * Sets the target animation for this being.
+ */
+ void setTargetAnimation(SimpleAnimation* animation);
+
+ /**
+ * Untargets the being
+ */
+ void untarget() { mUsedTargetCursor = NULL; }
+
AnimatedSprite* getEmote(int index) { return emotionSet[index]; }
void setEmote(Uint8 emotion, Uint8 emote_time)
@@ -367,9 +377,6 @@ class Being : public Sprite
mEmotionTime = emote_time;
}
- // Target cursor being used by the being
- Image *mTargetCursor;
-
static int getHairColorsNr(void);
static int getHairStylesNr(void);
@@ -444,6 +451,9 @@ class Being : public Sprite
// Speech Bubble components
SpeechBubble *mSpeechBubble;
+ // Target cursor being used
+ SimpleAnimation* mUsedTargetCursor;
+
static int instances; /**< Number of Being instances */
static std::vector<AnimatedSprite*> emotionSet; /**< Emoticons used by beings */
};
diff --git a/src/localplayer.cpp b/src/localplayer.cpp
index d047cc80..3c477b08 100644
--- a/src/localplayer.cpp
+++ b/src/localplayer.cpp
@@ -41,7 +41,6 @@
#include "net/protocol.h"
#include "resources/animation.h"
-#include "resources/image.h"
#include "resources/imageset.h"
#include "resources/resourcemanager.h"
@@ -95,10 +94,10 @@ LocalPlayer::~LocalPlayer()
for (int i = Being::TC_SMALL; i < Being::NUM_TC; i++)
{
- delete mTargetCursorInRange[i];
- delete mTargetCursorOutRange[i];
- mInRangeImages[i]->decRef();
- mOutRangeImages[i]->decRef();
+ delete mTargetCursor[0][i];
+ delete mTargetCursor[1][i];
+ mTargetCursorImages[0][i]->decRef();
+ mTargetCursorImages[1][i]->decRef();
}
}
@@ -119,33 +118,32 @@ void LocalPlayer::logic()
case WALK:
mFrame = (get_elapsed_time(mWalkTime) * 6) / mWalkSpeed;
- if (mFrame >= 6) {
+ if (mFrame >= 6)
nextStep();
- }
break;
case ATTACK:
int frames = 4;
- if ( mEquippedWeapon
- && mEquippedWeapon->getAttackType() == ACTION_ATTACK_BOW)
- {
+ if (mEquippedWeapon &&
+ mEquippedWeapon->getAttackType() == ACTION_ATTACK_BOW)
frames = 5;
- }
+
mFrame = (get_elapsed_time(mWalkTime) * frames) / mAttackSpeed;
- if (mFrame >= frames) {
+
+ if (mFrame >= frames)
nextStep();
- }
+
break;
}
// Actions are allowed once per second
- if (get_elapsed_time(mLastAction) >= 1000) {
+ if (get_elapsed_time(mLastAction) >= 1000)
mLastAction = -1;
- }
+
// Targeting allowed 4 times a second
- if (get_elapsed_time(mLastTarget) >= 250) {
+ if (get_elapsed_time(mLastTarget) >= 250)
mLastTarget = -1;
- }
+
// Remove target if its been on a being for more than a minute
if (get_elapsed_time(mTargetTime) >= 60000)
{
@@ -156,20 +154,20 @@ void LocalPlayer::logic()
if (mTarget)
{
+ // Find whether target is in range
+ const int rangeX = abs(mTarget->mX - mX);
+ const int rangeY = abs(mTarget->mY - mY);
+ const int attackRange = getAttackRange();
+ const int inRange = rangeX > attackRange || rangeY > attackRange ? 1 : 0;
+
+ mTarget->setTargetAnimation(
+ mTargetCursor[inRange][mTarget->getTargetCursorSize()]);
+
if (mTarget->mAction == DEAD)
- {
stopAttack();
- }
+
if (mKeepAttacking && mTarget)
- {
attack(mTarget, true);
- }
-
- for (int i = Being::TC_SMALL; i < Being::NUM_TC; i++)
- {
- player_node->mTargetCursorInRange[i]->update(10);
- player_node->mTargetCursorOutRange[i]->update(10);
- }
}
Being::logic();
@@ -192,13 +190,9 @@ void LocalPlayer::setName(const std::string &name)
}
if (config.getValue("showownname", false) && mMapInitialized)
- {
Player::setName(name);
- }
else
- {
Being::setName(name);
- }
}
void LocalPlayer::nextStep()
@@ -206,15 +200,10 @@ void LocalPlayer::nextStep()
if (mPath.empty())
{
if (mPickUpTarget)
- {
pickUp(mPickUpTarget);
- }
if (mWalkingDir)
- {
walk(mWalkingDir);
- }
-
}
if (mGoingToTarget && mTarget && withinAttackRange(mTarget))
@@ -278,12 +267,15 @@ void LocalPlayer::pickUp(FloorItem *item)
int dx = item->getX() - mX;
int dy = item->getY() - mY;
- if (dx * dx + dy * dy < 4) {
+ if (dx * dx + dy * dy < 4)
+ {
MessageOut outMsg(mNetwork);
outMsg.writeInt16(CMSG_ITEM_PICKUP);
outMsg.writeInt32(item->getId());
mPickUpTarget = NULL;
- } else {
+ }
+ else
+ {
setDestination(item->getX(), item->getY());
mPickUpTarget = item;
stopAttack();
@@ -339,27 +331,32 @@ void LocalPlayer::setTarget(Being *target)
{
if (mLastTarget != -1 || target == this)
return;
+
mLastTarget = tick_time;
- if ((target == NULL) || target == mTarget)
- {
+ if (target == mTarget)
target = NULL;
- mKeepAttacking = false;
- mTargetTime = -1;
- }
+
if (target)
{
mTargetTime = tick_time;
}
- if (mTarget && mTarget->getType() == Being::MONSTER)
+ else
{
- static_cast<Monster *>(mTarget)->showName(false);
+ mKeepAttacking = false;
+ mTargetTime = -1;
}
+
+ if (mTarget)
+ mTarget->untarget();
+
+ if (mTarget && mTarget->getType() == Being::MONSTER)
+ static_cast<Monster *>(mTarget)->showName(false);
+
mTarget = target;
+
if (target && target->getType() == Being::MONSTER)
- {
static_cast<Monster *>(target)->showName(true);
- }
}
void LocalPlayer::setDestination(Uint16 x, Uint16 y)
@@ -541,7 +538,8 @@ void LocalPlayer::attack(Being *target, bool keep)
std::string soundFile = mEquippedWeapon->getSound(EQUIP_EVENT_STRIKE);
if (soundFile != "") sound.playSfx(soundFile);
}
- else {
+ else
+ {
sound.playSfx("sfx/fist-swish.ogg");
}
@@ -551,9 +549,7 @@ void LocalPlayer::attack(Being *target, bool keep)
outMsg.writeInt8(0);
if (!keep)
- {
stopAttack();
- }
}
void LocalPlayer::stopAttack()
@@ -643,54 +639,19 @@ void LocalPlayer::loadTargetCursor(std::string filename, int width, int height,
currentImageSet = resman->getImageSet(filename, width, height);
Animation *anim = new Animation();
- for (unsigned int i = 0; i < currentImageSet->size(); ++i)
- {
- anim->addFrame(currentImageSet->get(i), 75, 0, 0);
- }
- currentCursor = new SimpleAnimation(anim);
- if (outRange)
- {
- mOutRangeImages[size] = currentImageSet;
- mTargetCursorOutRange[size] = currentCursor;
- }
- else
+ for (unsigned int i = 0; i < currentImageSet->size(); ++i)
{
- mInRangeImages[size] = currentImageSet;
- mTargetCursorInRange[size] = currentCursor;
+ anim->addFrame(currentImageSet->get(i), 75,
+ (16 - (currentImageSet->getWidth() / 2)),
+ (16 - (currentImageSet->getHeight() / 2)));
}
-}
-
-void LocalPlayer::drawTargetCursor(Graphics *graphics, int scrollX, int scrollY)
-{
-
- // Draw target marker if needed
- if (mTarget)
- {
- // Calculate target circle position
-
- // Find whether target is in range
- int rangeX = abs(mTarget->mX - mX);
- int rangeY = abs(mTarget->mY - mY);
- int attackRange = getAttackRange();
- // Get the correct target cursors graphic
- TargetCursorSize cursorSize = mTarget->getTargetCursorSize();
-
- if (rangeX > attackRange || rangeY > attackRange)
- {
- mTarget->mTargetCursor = mTargetCursorOutRange[cursorSize]->getCurrentImage();
- }
- else
- {
- mTarget->mTargetCursor = mTargetCursorInRange[cursorSize]->getCurrentImage();
- }
+ currentCursor = new SimpleAnimation(anim);
- // Draw the target cursor at the correct position
- int posX = mTarget->getPixelX() + 16 - mTarget->mTargetCursor->getWidth() / 2 - scrollX;
- int posY = mTarget->getPixelY() + 16 - mTarget->mTargetCursor->getHeight() / 2 - scrollY;
+ const int index = outRange ? 1 : 0;
- graphics->drawImage(mTarget->mTargetCursor, posX, posY);
- }
- return;
+ mTargetCursorImages[index][size] = currentImageSet;
+ mTargetCursor[index][size] = currentCursor;
}
+
diff --git a/src/localplayer.h b/src/localplayer.h
index d6d5ad2e..a4625f73 100644
--- a/src/localplayer.h
+++ b/src/localplayer.h
@@ -39,7 +39,6 @@ class Inventory;
class Item;
class Map;
class Network;
-class SimpleAnimation;
/**
* The local player character.
@@ -231,14 +230,6 @@ class LocalPlayer : public Player
float mLastAttackTime; /**< Used to synchronize the charge dialog */
- void drawTargetCursor(Graphics *graphics, int offsetX, int offsetY);
-
- /** Animated in range target cursor. */
- SimpleAnimation *mTargetCursorInRange[NUM_TC];
-
- /** Animated out of range target cursor. */
- SimpleAnimation *mTargetCursorOutRange[NUM_TC];
-
const std::auto_ptr<Equipment> mEquipment;
protected:
@@ -264,20 +255,20 @@ class LocalPlayer : public Player
Inventory *mInventory;
Inventory *mStorage;
+ // Load the target cursors into memory
+ void initTargetCursor();
+
/**
* Helper function for loading target cursors
*/
void loadTargetCursor(std::string filename, int width, int height,
bool outRange, Being::TargetCursorSize size);
- /** Images of in range target cursor. */
- ImageSet *mInRangeImages[NUM_TC];
-
- /** Images of out of range target cursor. */
- ImageSet *mOutRangeImages[NUM_TC];
+ /** Images of the target cursor. */
+ ImageSet *mTargetCursorImages[2][NUM_TC];
- // Load the target cursors into memory
- void initTargetCursor();
+ /** Animated target cursors. */
+ SimpleAnimation *mTargetCursor[2][NUM_TC];
};
extern LocalPlayer *player_node;
diff --git a/src/map.cpp b/src/map.cpp
index 716e9aee..16882540 100644
--- a/src/map.cpp
+++ b/src/map.cpp
@@ -25,9 +25,9 @@
#include "configuration.h"
#include "game.h"
#include "graphics.h"
-#include "localplayer.h"
#include "map.h"
#include "particle.h"
+#include "simpleanimation.h"
#include "sprite.h"
#include "tileset.h"
@@ -63,34 +63,35 @@ struct Location
};
TileAnimation::TileAnimation(Animation *ani):
- mAnimation(ani),
mLastImage(NULL)
{
+ mAnimation = new SimpleAnimation(ani);
}
TileAnimation::~TileAnimation()
{
- delete mLastImage;
+ delete mAnimation;
}
void TileAnimation::update()
{
+ if (!mAnimation)
+ return;
+
//update animation
- mAnimation.update(1);
+ mAnimation->update(1);
// exchange images
- Image *img = mAnimation.getCurrentImage();
+ Image *img = mAnimation->getCurrentImage();
if (img != mLastImage)
{
- for (std::list<std::pair<MapLayer*, int> >::iterator i = mAffected.begin();
- i != mAffected.end();
- i++)
+ for (std::list<std::pair<MapLayer*, int> >::iterator i =
+ mAffected.begin(); i != mAffected.end(); i++)
{
i->first->setTile(i->second, img);
}
mLastImage = img;
}
-
}
MapLayer::MapLayer(int x, int y, int width, int height, bool isFringeLayer):
@@ -140,9 +141,10 @@ void MapLayer::draw(Graphics *graphics,
{
// If drawing the fringe layer, make sure all sprites above this row of
// tiles have been drawn
- if (mIsFringeLayer) {
- player_node->drawTargetCursor(graphics, scrollX, scrollY);
- while (si != sprites.end() && (*si)->getPixelY() <= y * 32 - 32) {
+ if (mIsFringeLayer)
+ {
+ while (si != sprites.end() && (*si)->getPixelY() <= y * 32 - 32)
+ {
(*si)->draw(graphics, -scrollX, -scrollY);
si++;
}
@@ -151,7 +153,8 @@ void MapLayer::draw(Graphics *graphics,
for (int x = startX; x < endX; x++)
{
Image *img = getTile(x, y);
- if (img) {
+ if (img)
+ {
const int px = (x + mX) * 32 - scrollX;
const int py = (y + mY) * 32 - scrollY + 32 - img->getHeight();
graphics->drawImage(img, px, py);
diff --git a/src/map.h b/src/map.h
index 1423565f..9703c5b3 100644
--- a/src/map.h
+++ b/src/map.h
@@ -27,7 +27,6 @@
#include "position.h"
#include "properties.h"
-#include "simpleanimation.h"
class Animation;
class AmbientOverlay;
@@ -35,6 +34,7 @@ class Graphics;
class Image;
class MapLayer;
class Particle;
+class SimpleAnimation;
class Sprite;
class Tileset;
@@ -78,8 +78,8 @@ class TileAnimation
{ mAffected.push_back(std::make_pair(layer, index)); }
private:
std::list<std::pair<MapLayer*, int> > mAffected;
- SimpleAnimation mAnimation;
- Image* mLastImage;
+ SimpleAnimation *mAnimation;
+ Image *mLastImage;
};
/**
diff --git a/src/simpleanimation.cpp b/src/simpleanimation.cpp
index 9066ed7f..030619ce 100644
--- a/src/simpleanimation.cpp
+++ b/src/simpleanimation.cpp
@@ -19,6 +19,7 @@
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
+#include "graphics.h"
#include "log.h"
#include "simpleanimation.h"
@@ -112,17 +113,42 @@ SimpleAnimation::SimpleAnimation(xmlNodePtr animationNode):
mCurrentFrame = mAnimation->getFrame(0);
}
+bool SimpleAnimation::draw(Graphics* graphics, int posX, int posY) const
+{
+ if (!mCurrentFrame || !mCurrentFrame->image)
+ return false;
+
+ return graphics->drawImage(mCurrentFrame->image,
+ posX + mCurrentFrame->offsetX,
+ posY + mCurrentFrame->offsetY);
+}
+
+void SimpleAnimation::reset()
+{
+ mAnimationTime = 0;
+ mAnimationPhase = 0;
+}
+
void SimpleAnimation::update(unsigned int timePassed)
{
+ // Avoid freaking out at first frame or when tick_time overflows
+ if (timePassed < mLastTime || mLastTime == 0)
+ mLastTime = timePassed;
+
+ // If not enough time has passed yet, do nothing
+ if (timePassed <= mLastTime || !mAnimation)
+ return;
+
mAnimationTime += timePassed;
- while (mAnimationTime > mCurrentFrame->delay)
+
+ while (mAnimationTime > mCurrentFrame->delay && mCurrentFrame->delay > 0)
{
mAnimationTime -= mCurrentFrame->delay;
mAnimationPhase++;
+
if (mAnimationPhase >= mAnimation->getLength())
- {
mAnimationPhase = 0;
- }
+
mCurrentFrame = mAnimation->getFrame(mAnimationPhase);
}
}
diff --git a/src/simpleanimation.h b/src/simpleanimation.h
index 16ac2906..a639cf14 100644
--- a/src/simpleanimation.h
+++ b/src/simpleanimation.h
@@ -50,12 +50,22 @@ class SimpleAnimation
void update(unsigned int timePassed);
+ bool draw(Graphics* graphics, int posX, int posY) const;
+
+ /**
+ * Resets the animation.
+ */
+ void reset();
+
Image *getCurrentImage() const;
private:
/** The hosted animation. */
Animation *mAnimation;
+ /**< The last time update was called. */
+ unsigned int mLastTime;
+
/** Time in game ticks the current frame is shown. */
unsigned int mAnimationTime;