Changeset 10455 for code/branches/weaponFS15/src/modules/weapons
- Timestamp:
- May 23, 2015, 7:33:37 PM (10 years ago)
- Location:
- code/branches/weaponFS15/src/modules/weapons
- Files:
-
- 8 edited
Legend:
- Unmodified
- Added
- Removed
-
code/branches/weaponFS15/src/modules/weapons/munitions/GravityBombMunition.cc
r10369 r10455 3 3 * 4 4 * Created on: Apr 16, 2015 5 * Author: meggiman5 * Author: Manuel Eggimann 6 6 */ 7 7 #include "GravityBombMunition.h" … … 17 17 this->maxMunitionPerMagazine_ = 1; 18 18 this->maxMagazines_ = 30; 19 this->magazines_ = 1 0;19 this->magazines_ = 15; 20 20 21 21 this->bUseSeparateMagazines_ = false; -
code/branches/weaponFS15/src/modules/weapons/munitions/GravityBombMunition.h
r10369 r10455 14 14 namespace orxonox 15 15 { 16 16 /** 17 * @class GravityBombMunition 18 * 19 * @brief This class is used to set the behaviour of various 20 * munition specific attributes of the GravityBomb like max count of munition per magazine. 21 * 22 * @author Manuel 23 * @date 23.05.2015 24 */ 17 25 class _WeaponsExport GravityBombMunition : public Munition 18 26 { -
code/branches/weaponFS15/src/modules/weapons/projectiles/GravityBomb.cc
r10436 r10455 3 3 * 4 4 * Created on: Mar 26, 2015 5 * Author: meggiman5 * Author: Manuel Eggimann 6 6 */ 7 7 #include "GravityBomb.h" … … 12 12 RegisterClass(GravityBomb); 13 13 14 const float GravityBomb::LIFETIME = 5; 14 const float GravityBomb::LIFETIME = 5; ///< The gravity bomb lifetime in seconds. 15 15 16 16 GravityBomb::GravityBomb(Context* context): … … 41 41 rocketModel->scale(3.0f); 42 42 this->attach(rocketModel); 43 43 //Add second model because the bomb consists of the bomb and attached rockets (2 separate models) 44 44 Model* bombModel = new Model(this->getContext()); 45 45 bombModel->setMeshSource("GravityBomb.mesh"); //Demo Model from SimpleRocket … … 47 47 this->attach(bombModel); 48 48 49 //Add particle effect to the flying rockets. 50 ParticleEmitter* fire = new ParticleEmitter(this->getContext()); 51 fire->setOrientation(this->getOrientation()); 52 fire->setSource("Orxonox/simplerocketfire"); 53 this->attach(fire); 54 55 //Add sound effect while the bomb is flying. 56 WorldSound* bombSound = new WorldSound(context); 57 bombSound->setSource("sounds/GravityBombFlight.ogg"); 58 bombSound->setLooping(true); 59 bombSound->setVolume(1.0); 60 this->attach(bombSound); 61 bombSound->play(); 49 62 } 50 63 } … … 57 70 if(timeToLife_ < 0) 58 71 { 59 orxout(debug_output) << "bomb has stoped moving" <<endl; 60 setVelocity(Vector3::ZERO); 61 setAcceleration(Vector3::ZERO); 72 //orxout(debug_output) << "bomb has stoped moving" <<endl; 73 setVelocity(Vector3::ZERO); //Stop the bomb. 62 74 isDetonated_ = true; 63 75 } 64 76 else 65 77 { 66 orxout(debug_output)<< "Time to live:" << timeToLife_ <<endl;67 destroyCheck(); 78 //orxout(debug_output)<< "Time to live:" << timeToLife_ <<endl; 79 destroyCheck(); //Every BasicProjectil has to call this method in each tick. 68 80 } 69 81 if(isDetonated_) detonate(); … … 73 85 bool GravityBomb::collidesAgainst(WorldEntity* otherObject, const btCollisionShape* cs, btManifoldPoint& contactPoint) 74 86 { 75 if(otherObject != getShooter()) 87 if(otherObject != getShooter()) //Ensure that the bomb cannot collide with its shooter. 76 88 { 77 89 orxout(debug_output) << "collides" << endl; … … 81 93 } 82 94 else{ 83 orxout(debug_output) << "collided with shooter. Has no effect..." << endl;95 //orxout(debug_output) << "collided with shooter. Has no effect..." << endl; 84 96 return false; 85 97 } … … 88 100 void GravityBomb::detonate() 89 101 { 102 //Create the GravityBombField and destroy the Projectil. 90 103 GravityBombField* field = new GravityBombField(this->getContext()); 91 104 field->setShooter(this->getShooter()); 92 105 field->setPosition(getPosition()); 93 orxout(debug_output) << "detonating. Creating GravityBombField." <<endl;94 orxout(debug_output) << "Field is at Position: " << getPosition() << endl;106 //orxout(debug_output) << "detonating. Creating GravityBombField." <<endl; 107 //orxout(debug_output) << "Field is at Position: " << getPosition() << endl; 95 108 this->destroy(); 96 109 } -
code/branches/weaponFS15/src/modules/weapons/projectiles/GravityBomb.h
r10435 r10455 1 /*2 * ORXONOX - the hottest 3D action shooter ever to exist3 * > www.orxonox.net <4 *5 *6 * License notice:7 *8 * This program is free software; you can redistribute it and/or9 * modify it under the terms of the GNU General Public License10 * as published by the Free Software Foundation; either version 211 * of the License, or (at your option) any later version.12 *13 * This program is distributed in the hope that it will be useful,14 * but WITHOUT ANY WARRANTY; without even the implied warranty of15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the16 * GNU General Public License for more details.17 *18 * You should have received a copy of the GNU General Public License19 * along with this program; if not, write to the Free Software20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.21 *22 * Author:23 * Gabriel Nadler24 * Co-authors:25 * simonmie26 *27 */28 29 30 1 /* 31 2 * GravityBomb.h … … 51 22 #include "../../../orxonox/worldentities/WorldEntity.h" 52 23 #include "GravityBombField.h" 24 #include "sound\WorldSound.h" 53 25 54 26 namespace orxonox 55 27 { 56 28 /** 29 * @class GravityBomb 30 * 31 * @brief This class implements how long the bomb flies before it places the GravityField at it's last possition. 32 * The field will be created either because the timelimit of the bomb expired or it hit something. After creation of the field, 33 * the projectile (this object) is destroyed. 34 * 35 * @author Manuel Eggimann 36 * @date 23.05.2015 37 */ 57 38 class _WeaponsExport GravityBomb : public BasicProjectile , public MovableEntity, public RadarViewable 58 39 { … … 69 50 bool isDetonated_; //Used to check whether the Bomb has to be destroyed during next tick. 70 51 float timeToLife_; //Time the bomb flies before it explodes. 71 52 WorldSound* bombSound_; 72 53 }; 73 54 } -
code/branches/weaponFS15/src/modules/weapons/projectiles/GravityBombField.cc
r10435 r10455 3 3 * 4 4 * Created on: Apr 2, 2015 5 * Author: meggiman5 * Author: Manuel Eggimann 6 6 */ 7 7 … … 12 12 RegisterClass(GravityBombField); 13 13 14 const float GravityBombField::FORCE_FIELD_LIFETIME = 20; 14 //Change these constants to alter the behaviour of the field. 15 16 const float GravityBombField::FORCE_FIELD_LIFETIME = 15; 15 17 const float GravityBombField::FORCE_SPHERE_START_RADIUS = 250; 16 18 const float GravityBombField::FORCE_SPHERE_START_STRENGTH = -500; 17 const float GravityBombField::PEAK_EXPLOSION_FORCE = 1e6;19 const float GravityBombField::PEAK_EXPLOSION_FORCE = 5e4; 18 20 const float GravityBombField::FORCE_FIELD_EXPLOSION_DAMMAGE = 100; 19 21 const float GravityBombField::EXPLOSION_DURATION = 1; 20 const float GravityBombField::EXPLOSION_RADIUS = 400;22 const float GravityBombField::EXPLOSION_RADIUS = 600; 21 23 const float GravityBombField::PEAK_ANGULAR_VELOCITY = 20; 24 const float GravityBombField::CENTRE_MODEL_END_SIZE = 1.5; 22 25 23 26 GravityBombField::GravityBombField(Context* context) : ForceField(context),RadarViewable(this, static_cast<WorldEntity*>(this)) 24 27 { 25 28 RegisterObject(GravityBombField); 29 //Initialize variable with their initial values. 26 30 lifetime_=FORCE_FIELD_LIFETIME; 27 31 forceStrength_ = FORCE_SPHERE_START_STRENGTH; 28 32 forceSphereRadius_ = FORCE_SPHERE_START_RADIUS; 33 modelScaling_ = 1; 29 34 fieldExploded_ = false; 30 35 … … 34 39 setCollisionResponse(false); 35 40 41 //Make the Field visible on Radar and minimap. 36 42 this->setRadarObjectColour(ColourValue(0.2, 0.2, 1.0,1)); // Blue 37 43 this->setRadarObjectShape(RadarViewable::Dot); 38 44 this->setRadarObjectScale(0.5f); 45 39 46 40 47 //Attach Model 41 48 Model* model = new Model(this->getContext()); 42 49 model->setMeshSource("GravityBomb.mesh"); //Demo Model from SimpleRocket 43 model->scale( 3.0f);50 model->scale(2.5f); 44 51 bombModel_ = new MovableEntity(context); 45 52 bombModel_->attach(model); 46 53 this->attach(bombModel_); 47 54 48 Backlight* centreLight = new Backlight(context); 49 centreLight->setColour(ColourValue(0.9,0.5,0.5,1)); 50 centreLight->setScale(1.0); 51 centreLight->setTrailMaterial("Trail/backlighttrail"); 52 centreLight->setMaterial("Examples/Flare"); 53 centreLight->setLifetime(20); 54 bombModel_->attach(centreLight); 55 //Add a Backlight to the centre. 56 centreLight_ = new Backlight(context); 57 centreLight_->setColour(ColourValue(0.2,0.9,0.2,1)); 58 centreLight_->setScale(0.3); 59 centreLight_->setTrailMaterial("Trail/backlighttrail"); 60 centreLight_->setMaterial("Examples/Flare"); 61 centreLight_->setLifetime(20); 62 bombModel_->attach(centreLight_); 55 63 64 //Let the Bomb Modell in the centre rotate in a random direction. 56 65 Vector3 randomRotation; 57 66 srand(time(NULL)); … … 64 73 //Add Collision Shape 65 74 SphereCollisionShape* collisionShape = new SphereCollisionShape(context); 66 collisionShape->setRadius( 3.0);75 collisionShape->setRadius(10.0); 67 76 this->attachCollisionShape(collisionShape); 68 77 78 //Add particle effect to visualize the force field. 69 79 this->particleSphere_ = new ParticleEmitter(this->getContext()); 70 80 this->attach(this->particleSphere_); 71 81 particleSphere_->setSource("Orxonox/GravityBombField"); 82 83 //Add a sound effect to the field. 84 WorldSound* fieldSound = new WorldSound(context); 85 fieldSound->setSource("sounds/GravityField.ogg"); 86 fieldSound->setLooping(true); 87 fieldSound->setVolume(1.0); 88 this->attach(fieldSound); 89 fieldSound->play(); 72 90 } 73 91 74 92 GravityBombField::~GravityBombField(){} 75 93 94 76 95 void GravityBombField::tick(float dt) 77 96 { … … 79 98 lifetime_-=dt; 80 99 81 if(lifetime_ > EXPLOSION_DURATION) 100 if(lifetime_ > EXPLOSION_DURATION)//If field is still alive, make it smaller and stronger. 82 101 { 102 modelScaling_ += ((CENTRE_MODEL_END_SIZE-1) / FORCE_FIELD_LIFETIME)*dt; 83 103 forceStrength_ *= (1+dt/10); 84 104 forceSphereRadius_ = FORCE_SPHERE_START_RADIUS*(1-((FORCE_FIELD_LIFETIME-lifetime_)/FORCE_FIELD_LIFETIME)*((FORCE_FIELD_LIFETIME-lifetime_)/FORCE_FIELD_LIFETIME)*((FORCE_FIELD_LIFETIME-lifetime_)/FORCE_FIELD_LIFETIME)); … … 86 106 else if(lifetime_ > 0) 87 107 { 88 if (!fieldExploded_) 108 if (!fieldExploded_) // Start the field explosion if it has not been started yet. 89 109 { 90 forceStrength_ = PEAK_EXPLOSION_FORCE;110 forceStrength_ = pow((EXPLOSION_DURATION + lifetime_),4)/EXPLOSION_DURATION * PEAK_EXPLOSION_FORCE; 91 111 fieldExploded_ = true; 92 112 113 //Add particle effect to visualize explosion 93 114 explosionCross_ = new ParticleEmitter(this->getContext()); 115 explosionCross_->setSource("Orxonox/FieldExplosion"); 116 explosionCross_->setOrientation(rand(), rand(), rand(), rand()); 117 explosionCross_->setScale(0.7); 94 118 this->attach(explosionCross_); 95 explosionCross_->setSource("Orxonox/FieldExplosion"); 119 120 //Add explosion sound effect. 121 explosionSound_ = new WorldSound(getContext()); 122 explosionSound_->setSource("sounds/GravityFieldExplosion.ogg"); 123 explosionSound_->setVolume(1.0); 124 explosionSound_->play(); 96 125 } 97 126 127 //Check if any pawn is inside the shockwave and hit it with dammage proportional to the distance between explosion centre and pawn. Make sure, the same pawn is damaged only once. 98 128 for (ObjectList<Pawn>::iterator it = ObjectList<Pawn>::begin(); it != ObjectList<Pawn>::end(); ++it) 99 129 { 100 130 Vector3 distanceVector = it->getWorldPosition()-this->getWorldPosition(); 101 orxout(debug_output) << "Found Pawn:" << it->getWorldPosition() << endl;131 //orxout(debug_output) << "Found Pawn:" << it->getWorldPosition() << endl; 102 132 if(distanceVector.length()< forceSphereRadius_) 103 133 { 104 orxout(debug_output) << "Force sphere radius is: " << forceSphereRadius_ << " Distance to Pawn is: " << distanceVector.length();134 //orxout(debug_output) << "Force sphere radius is: " << forceSphereRadius_ << " Distance to Pawn is: " << distanceVector.length(); 105 135 if (std::find(victimsAlreadyDamaged_.begin(),victimsAlreadyDamaged_.end(),*it) == victimsAlreadyDamaged_.end()) 106 136 { 107 orxout(debug_output) << "Found Pawn to damage: " << it->getWorldPosition() << endl;137 //orxout(debug_output) << "Found Pawn to damage: " << it->getWorldPosition() << endl; 108 138 float damage = FORCE_FIELD_EXPLOSION_DAMMAGE*(1-distanceVector.length()/EXPLOSION_RADIUS); 109 orxout(debug_output) << "Damage: " << damage << endl;139 //orxout(debug_output) << "Damage: " << damage << endl; 110 140 it->hit(shooter_, it->getWorldPosition(), NULL, damage, 0,0); 111 // it->removeHealth(damage);112 141 victimsAlreadyDamaged_.push_back(*it); 113 142 } … … 118 147 explosionCross_->setScale(forceSphereRadius_/FORCE_SPHERE_START_RADIUS); 119 148 } 120 else if (lifetime_ > - 4)149 else if (lifetime_ > -6) //The field has to exist for 6 more seconds for the particles of the particle effect to vanish smoothly. 121 150 { 151 //Make the bomb model invisible, let the strength of the field be zero and remove all particle emitters so the particle effect will slowly vanish. 122 152 bombModel_->setVisible(false); 123 153 this->setRadarVisibility(false); … … 127 157 explosionCross_->getParticleInterface()->removeAllEmitters(); 128 158 } 129 159 130 160 setDiameter(forceSphereRadius_*2); 131 161 setVelocity(forceStrength_); 132 particleSphere_->setScale(forceSphereRadius_/FORCE_SPHERE_START_RADIUS); 162 if(lifetime_>0) particleSphere_->setScale(forceSphereRadius_/FORCE_SPHERE_START_RADIUS); 163 bombModel_->setScale(modelScaling_); 133 164 134 165 if (lifetime_ <= -4) … … 141 172 void GravityBombField::destroy() 142 173 { 143 //Animation144 174 ForceField::destroy(); 145 175 } -
code/branches/weaponFS15/src/modules/weapons/projectiles/GravityBombField.h
r10435 r10455 20 20 #include <stdlib.h> 21 21 #include <time.h> 22 #include <math.h> 22 23 #include "graphics/Backlight.h" 24 #include "sound\WorldSound.h" 23 25 24 26 namespace orxonox { 27 28 /** 29 * @class GravityBombField 30 * 31 * @brief This class is used by GravityBomb to place the ForceField and Visual effect to the environment. 32 * The field has a maximum lifetime and gets smaller and stronger the more time passes. In the end, the field explodes and damages all pawns nearby. 33 * 34 * @author Manuel Eggimann 35 * @date 23.05.2015 36 */ 25 37 class GravityBombField: public ForceField, public RadarViewable { 26 38 public: … … 30 42 virtual void destroy(); 31 43 44 /** 45 * @fn void GravityBombField::setShooter(Pawn* shooter) 46 * 47 * @brief This function is used to determine save the pawn who created the field and is used inside the GravityBomb class. 48 * 49 * @author Manuel Eggimann 50 * @date 23.05.2015 51 * 52 * @param [in,out] the Pawn that created the field. 53 */ 32 54 void setShooter(Pawn* shooter) 33 55 { this->shooter_ = shooter; } … … 37 59 38 60 private: 39 static const float FORCE_FIELD_LIFETIME; 40 static const float FORCE_SPHERE_START_RADIUS; 41 static const float FORCE_SPHERE_START_STRENGTH; 42 static const float FORCE_FIELD_EXPLOSION_DAMMAGE; 43 static const float EXPLOSION_DURATION; 44 static const float EXPLOSION_RADIUS; 45 static const float PEAK_ANGULAR_VELOCITY; 46 static const float PEAK_EXPLOSION_FORCE; 61 //Set these constants inside GravityBombField.cc to alter the behaviour of the field. 62 63 static const float FORCE_FIELD_LIFETIME; ///< The lifetime of the ForceField in seconds. After lifetime seconds, has already exploded and the particle effects will start to vanish. 64 static const float FORCE_SPHERE_START_RADIUS; ///< The initial sphere radius of the Force Field. The forcefield gets smaller by time. 65 static const float FORCE_SPHERE_START_STRENGTH; ///< The initial Force the Field exerts on every object with non-zero mass. 66 static const float FORCE_FIELD_EXPLOSION_DAMMAGE; ///< The maximum dammage a pawn gets nearby an exploding field. The farer away from explosion center the smaller the dammage. 67 static const float EXPLOSION_DURATION; ///< Determines how fast the shockwave of the Explosion expands. It takes GravityBombField::EXPLOSION_DURATION seconds for the field to expand from 0 radius to GravityBombField::EXPLOSION_RADIUS. 68 static const float EXPLOSION_RADIUS; ///< How far does the shockwave reach. All pawns which outside of a sphere witch this radius rest unharmed by the explosion. 69 static const float PEAK_ANGULAR_VELOCITY; ///< The model of the bomb in the center of the Field does rotate faster and faster as time passes until it reaches PEAK_ANGULAR_VELOCITY at the fields end of life. 70 static const float PEAK_EXPLOSION_FORCE; ///< The peak force the explosion exerts on the pawns nearby. 71 static const float CENTRE_MODEL_END_SIZE; ///< Size of the 3d-model of the bomb in the fields centre. 47 72 48 73 float forceSphereRadius_; 49 74 float forceStrength_; 50 75 float lifetime_; 76 float modelScaling_; 51 77 Vector3 rotationVector_; 52 78 bool fieldExploded_; … … 56 82 MovableEntity * bombModel_; 57 83 Pawn* shooter_; 84 Backlight* centreLight_; 85 WorldSound* explosionSound_; 58 86 }; 59 87 -
code/branches/weaponFS15/src/modules/weapons/weaponmodes/GravityBombFire.cc
r10438 r10455 3 3 * 4 4 * Created on: Apr 16, 2015 5 * Author: meggiman5 * Author: Manuel Eggimann 6 6 */ 7 7 #include "GravityBombFire.h" … … 19 19 RegisterClass(GravityBombFire); 20 20 21 const float GravityBombFire::BOMB_VELOCITY = 400.0; 21 const float GravityBombFire::BOMB_VELOCITY = 400.0; ///< The velocity of the bomb after launch 22 22 23 23 GravityBombFire::GravityBombFire(Context* context) : WeaponMode(context) … … 25 25 RegisterObject(GravityBombFire); 26 26 27 this->reloadTime_ = 0.50f; 27 this->reloadTime_ = 0.50f; 28 28 this->bParallelReload_ = false; 29 this->damage_ = 0.0f; 30 this->speed_ = BOMB_VELOCITY; 29 this->damage_ = 20.0f; ///< The damage of the Bomb if it hits a pawn. 31 30 32 31 this->setMunitionName("GravityBombMunition"); 33 this->setDefaultSoundWithVolume("sounds/Rocket_launch.ogg",0.8); 32 this->setDefaultSoundWithVolume("sounds/Rocket_launch.ogg",0.8); ///< sets sound of the bomb as it is fired. 34 33 } 35 34 … … 39 38 { 40 39 GravityBomb* bomb = new GravityBomb(this->getContext()); 41 this->computeMuzzleParameters(this->getWeapon()->getWeaponPack()->getWeaponSystem()->getPawn()->getAimPosition()); 40 //Create a new Bomb in 3D-Space and set the right direction speed and orientation. 41 this->computeMuzzleParameters(this->getWeapon()->getWeaponPack()->getWeaponSystem()->getPawn()->getAimPosition()); 42 42 bomb->setOrientation(this->getMuzzleOrientation()); 43 43 bomb->setPosition(this->getMuzzlePosition()); 44 bomb->setVelocity(this->getMuzzleDirection() * (this-> speed_+this->getWeapon()->getWeaponPack()->getWeaponSystem()->getPawn()->getVelocity().length()));44 bomb->setVelocity(this->getMuzzleDirection() * (this->BOMB_VELOCITY+this->getWeapon()->getWeaponPack()->getWeaponSystem()->getPawn()->getVelocity().length())); 45 45 46 //Set the shooter of the bomb so it is possible to determine the pawn that killed another one and define damage to shield and healt the bomb does. 46 47 bomb->setShooter(this->getWeapon()->getWeaponPack()->getWeaponSystem()->getPawn()); 47 48 bomb->setDamage(this->getDamage()); -
code/branches/weaponFS15/src/modules/weapons/weaponmodes/GravityBombFire.h
r10435 r10455 1 /** 2 * ORXONOX - the hottest 3D action shooter ever to exist 3 * > www.orxonox.net < 4 * 5 * 6 * License notice: 7 * 8 * This program is free software; you can redistribute it and/or 9 * modify it under the terms of the GNU General Public License 10 * as published by the Free Software Foundation; either version 2 11 * of the License, or (at your option) any later version. 12 * 13 * This program is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 * GNU General Public License for more details. 17 * 18 * You should have received a copy of the GNU General Public License 19 * along with this program; if not, write to the Free Software 20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 21 * 22 * Author: 23 * Oliver Scheuss 24 * Co-authors: 25 * ... 26 * 27 */ 1 /* 2 * GravityBombFire.h 3 * 4 * Created on: Apr 16, 2015 5 * Author: Manuel Eggimann 6 */ 28 7 29 8 /** … … 42 21 43 22 /** 44 @brief 45 Fires the GravityBomb 46 @author 47 Manuel Eggimann 48 @ingroup WeaponsWeaponModes 23 *@brief 24 * Fires the GravityBomb. This class implements everything needed to fire the BasicProjectile GravityBomb. 25 * Everything that has to do with the bombs behaviour after launching it is implemented in GravityBomb and GravityBombField. 26 *@author 27 * Manuel Eggimann 28 *@ingroup WeaponsWeaponModes 49 29 */ 50 30 class _WeaponsExport GravityBombFire : public WeaponMode … … 57 37 58 38 private: 59 float speed_; //!< The initial speed of the bomb when it is launched. 60 static const float BOMB_VELOCITY; 39 static const float BOMB_VELOCITY; //!< The initial speed of the bomb when it is launched. 61 40 }; 62 41 }
Note: See TracChangeset
for help on using the changeset viewer.