Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/branches/fps/src/orxonox/controllers/NewHumanController.cc @ 7374

Last change on this file since 7374 was 6883, checked in by freicy, 15 years ago

by cyrill

  • Property svn:eol-style set to native
File size: 22.8 KB
Line 
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 *      Michael Wirth
24 *   Co-authors:
25 *      ...
26 *
27 */
28
29#include "NewHumanController.h"
30
31#include <cmath>
32#include <OgreRay.h>
33#include <OgreSceneQuery.h>
34#include <OgreCamera.h>
35#include <OgreSceneManager.h>
36#include <bullet/BulletCollision/NarrowPhaseCollision/btManifoldPoint.h>
37
38#include "core/CoreIncludes.h"
39#include "core/ConsoleCommand.h"
40#include "worldentities/ControllableEntity.h"
41#include "worldentities/pawns/Pawn.h"
42#include "infos/PlayerInfo.h"
43#include "overlays/OrxonoxOverlay.h"
44#include "graphics/Camera.h"
45#include "sound/SoundManager.h"
46#include "tools/BulletConversions.h"
47#include "Scene.h"
48
49namespace orxonox
50{
51    SetConsoleCommand(NewHumanController, changeMode, false).keybindMode(KeybindMode::OnPress);
52    SetConsoleCommand(NewHumanController, accelerate, false).keybindMode(KeybindMode::OnPress);
53    SetConsoleCommand(NewHumanController, decelerate, false).keybindMode(KeybindMode::OnPress);
54    SetConsoleCommand(NewHumanController, unfire,      true).keybindMode(KeybindMode::OnRelease);
55
56    CreateUnloadableFactory(NewHumanController);
57
58    NewHumanController* NewHumanController::localController_s = 0;
59
60    NewHumanController::NewHumanController(BaseObject* creator)
61        : HumanController(creator)
62        , crossHairOverlay_(NULL)
63        , centerOverlay_(NULL)
64        , damageOverlayTop_(NULL)
65        , damageOverlayRight_(NULL)
66        , damageOverlayBottom_(NULL)
67        , damageOverlayLeft_(NULL)
68        , damageOverlayTT_(0)
69        , arrowsOverlay1_(NULL)
70        , arrowsOverlay2_(NULL)
71        , arrowsOverlay3_(NULL)
72        , arrowsOverlay4_(NULL)
73    {
74        RegisterObject(NewHumanController);
75
76        overlaySize_ = 0.08f;
77        arrowsSize_ = 0.4f;
78
79        damageOverlayTime_ = 0.6f;
80
81        controlMode_ = 0;
82        acceleration_ = 0;
83        accelerating_ = false;
84        firemode_ = -1;
85
86        showArrows_ = true;
87        showOverlays_ = false;
88        showDamageOverlay_ = true;
89
90        //currentPitch_ = 1;
91        //currentYaw_ = 1;
92
93        if (GameMode::showsGraphics())
94        {
95            crossHairOverlay_ = new OrxonoxOverlay(this);
96            crossHairOverlay_->setBackgroundMaterial("Orxonox/Crosshair3");
97            crossHairOverlay_->setSize(Vector2(overlaySize_, overlaySize_));
98            crossHairOverlay_->hide();
99            //crossHairOverlay_->setAspectCorrection(true); not working
100
101            centerOverlay_ = new OrxonoxOverlay(this);
102            centerOverlay_->setBackgroundMaterial("Orxonox/CenterOverlay");
103            centerOverlay_->setSize(Vector2(overlaySize_ * 2.5f, overlaySize_ * 2.5f));
104            centerOverlay_->setPosition(Vector2(0.5f - overlaySize_*2.5f/2.0f, 0.5f - overlaySize_*2.5f/2.0f));
105            centerOverlay_->hide();
106
107            if (showDamageOverlay_)
108            {
109                damageOverlayTop_ = new OrxonoxOverlay(this);
110                damageOverlayTop_->setBackgroundMaterial("Orxonox/DamageOverlayTop");
111                damageOverlayTop_->setSize(Vector2(overlaySize_ * 2.5f, overlaySize_ * 2.5f));
112                damageOverlayTop_->setPosition(Vector2(0.5f - overlaySize_*2.5f/2.0f, 0.5f - overlaySize_*2.5f/2.0f));
113                damageOverlayTop_->hide();
114
115                damageOverlayRight_ = new OrxonoxOverlay(this);
116                damageOverlayRight_->setBackgroundMaterial("Orxonox/DamageOverlayRight");
117                damageOverlayRight_->setSize(Vector2(overlaySize_ * 2.5f, overlaySize_ * 2.5f));
118                damageOverlayRight_->setPosition(Vector2(0.5f - overlaySize_*2.5f/2.0f, 0.5f - overlaySize_*2.5f/2.0f));
119                damageOverlayRight_->hide();
120
121                damageOverlayBottom_ = new OrxonoxOverlay(this);
122                damageOverlayBottom_->setBackgroundMaterial("Orxonox/DamageOverlayBottom");
123                damageOverlayBottom_->setSize(Vector2(overlaySize_ * 2.5f, overlaySize_ * 2.5f));
124                damageOverlayBottom_->setPosition(Vector2(0.5f - overlaySize_*2.5f/2.0f, 0.5f - overlaySize_*2.5f/2.0f));
125                damageOverlayBottom_->hide();
126
127                damageOverlayLeft_ = new OrxonoxOverlay(this);
128                damageOverlayLeft_->setBackgroundMaterial("Orxonox/DamageOverlayLeft");
129                damageOverlayLeft_->setSize(Vector2(overlaySize_ * 2.5f, overlaySize_ * 2.5f));
130                damageOverlayLeft_->setPosition(Vector2(0.5f - overlaySize_*2.5f/2.0f, 0.5f - overlaySize_*2.5f/2.0f));
131                damageOverlayLeft_->hide();
132            }
133
134            if (showArrows_)
135            {
136                arrowsOverlay1_ = new OrxonoxOverlay(this);
137                arrowsOverlay1_->setBackgroundMaterial("Orxonox/DirectionArrows1");
138                arrowsOverlay1_->setSize(Vector2(0.02727f, 0.36f * arrowsSize_));
139                arrowsOverlay1_->setPickPoint(Vector2(0.5f, 0.5f));
140                arrowsOverlay1_->setPosition(Vector2(0.5f, 0.5f));
141                arrowsOverlay1_->hide();
142
143                arrowsOverlay2_ = new OrxonoxOverlay(this);
144                arrowsOverlay2_->setBackgroundMaterial("Orxonox/DirectionArrows2");
145                arrowsOverlay2_->setSize(Vector2(0.02727f, 0.59f * arrowsSize_));
146                arrowsOverlay2_->setPickPoint(Vector2(0.5f, 0.5f));
147                arrowsOverlay2_->setPosition(Vector2(0.5f, 0.5f));
148                arrowsOverlay2_->hide();
149
150                arrowsOverlay3_ = new OrxonoxOverlay(this);
151                arrowsOverlay3_->setBackgroundMaterial("Orxonox/DirectionArrows3");
152                arrowsOverlay3_->setSize(Vector2(0.02727f, 0.77f * arrowsSize_));
153                arrowsOverlay3_->setPickPoint(Vector2(0.5f, 0.5f));
154                arrowsOverlay3_->setPosition(Vector2(0.5f, 0.5f));
155                arrowsOverlay3_->hide();
156
157                arrowsOverlay4_ = new OrxonoxOverlay(this);
158                arrowsOverlay4_->setBackgroundMaterial("Orxonox/DirectionArrows4");
159                arrowsOverlay4_->setSize(Vector2(0.02727f, arrowsSize_));
160                arrowsOverlay4_->setPickPoint(Vector2(0.5f, 0.5f));
161                arrowsOverlay4_->setPosition(Vector2(0.5f, 0.5f));
162                arrowsOverlay4_->hide();
163            }
164        }
165
166        // HACK: Define which objects are targetable when considering the creator of an orxonox::Model
167        this->targetMask_.exclude(ClassByString("BaseObject"));
168        this->targetMask_.include(ClassByString("WorldEntity"));
169        this->targetMask_.exclude(ClassByString("Projectile"));
170
171        NewHumanController::localController_s = this;
172
173        controlPaused_ = false;
174
175        //HumanController::localController_s->getControllableEntity()->getCamera()->setDrag(true);
176    }
177
178    NewHumanController::~NewHumanController()
179    {
180        if (this->isInitialized())
181        {
182            if (this->crossHairOverlay_)
183                this->crossHairOverlay_->destroy();
184            if (this->centerOverlay_)
185                this->centerOverlay_->destroy();
186
187            if (showArrows_)
188            {
189                if (this->arrowsOverlay1_)
190                    this->arrowsOverlay1_->destroy();
191                if (this->arrowsOverlay2_)
192                    this->arrowsOverlay2_->destroy();
193                if (this->arrowsOverlay3_)
194                    this->arrowsOverlay3_->destroy();
195                if (this->arrowsOverlay4_)
196                    this->arrowsOverlay4_->destroy();
197            }
198        }
199    }
200
201    void NewHumanController::tick(float dt)
202    {
203        if (GameMode::showsGraphics())
204        {
205
206            if (this->controllableEntity_ && !this->controllableEntity_->isInMouseLook())
207            {
208                this->updateTarget();
209
210                if (!controlPaused_ )
211                {
212                    if (this->getControllableEntity() && (this->getControllableEntity()->isExactlyA(ClassByString("SpaceShip")) || this->getControllableEntity()->isExactlyA(ClassByString("Rocket"))))
213                        {this->showOverlays();}
214                       
215                    if (this->getControllableEntity() &&  this->getControllableEntity()->isExactlyA(ClassByString("FpsPlayer")))\
216                        {this->showOverlays();
217                        this->hideArrows();}
218
219                    this->crossHairOverlay_->setPosition(Vector2(static_cast<float>(this->currentYaw_)/2*-1+.5f-overlaySize_/2, static_cast<float>(this->currentPitch_)/2*-1+.5f-overlaySize_/2));
220
221                    if (this->controlMode_ == 0 || (this->controlMode_ == 1 && this->firemode_ == 1))
222                    {
223                        if (this->showOverlays_ && this->showArrows_)
224                            alignArrows();
225                    }
226                    else
227                        hideArrows();
228
229                    if (this->showDamageOverlay_ && (this->damageOverlayTT_ > 0 || this->damageOverlayTR_ > 0 || this->damageOverlayTB_ > 0 || this->damageOverlayTL_ > 0))
230                    {
231                        this->damageOverlayTT_ -= dt;
232                        this->damageOverlayTR_ -= dt;
233                        this->damageOverlayTB_ -= dt;
234                        this->damageOverlayTL_ -= dt;
235                        if (this->damageOverlayTT_ <= 0)
236                            this->damageOverlayTop_->hide();
237                        if (this->damageOverlayTR_ <= 0)
238                            this->damageOverlayRight_->hide();
239                        if (this->damageOverlayTB_ <= 0)
240                            this->damageOverlayBottom_->hide();
241                        if (this->damageOverlayTL_ <= 0)
242                            this->damageOverlayLeft_->hide();
243                    }
244                }
245            }
246            else
247                this->hideOverlays();
248
249            if (this->acceleration_ > 0)
250            {
251                if (this->accelerating_)
252                    HumanController::moveFrontBack(Vector2(1, 0));
253                else
254                    HumanController::moveFrontBack(Vector2(this->acceleration_, 0));
255                this->accelerating_ = false;
256                //HumanController::moveFrontBack(Vector2(clamp(this->acceleration_ + this->currentAcceleration_, 0.0f, 1.0f), 0));
257            }
258        }
259
260        // Reset pitch and yaw rates
261        // TODO: Reactivate this to fix the game pad problem with 0 input
262        //this->currentPitch_ = 0;
263        //this->currentYaw_ = 0;
264
265        HumanController::tick(dt);
266    }
267
268    void NewHumanController::doFire(unsigned int firemode)
269    {
270        if (!this->controllableEntity_)
271            return;
272
273        this->firemode_ = firemode;
274
275        if (firemode == 1 && this->controlMode_ == 1)
276        {
277            //unlocked steering, steer on right mouse click
278            HumanController::yaw(Vector2(this->currentYaw_, 0));
279            HumanController::pitch(Vector2(this->currentPitch_, 0));
280        }
281        else
282            HumanController::localController_s->getControllableEntity()->fire(firemode);
283
284    }
285
286    void NewHumanController::hit(Pawn* originator, btManifoldPoint& contactpoint, float damage)
287    {
288        if (this->showDamageOverlay_ && !this->controlPaused_ && this->controllableEntity_ && !this->controllableEntity_->isInMouseLook())
289        {
290            Vector3 posA;
291            if (originator)
292                posA = originator->getWorldPosition();
293            else
294                posA = multi_cast<Vector3>(contactpoint.getPositionWorldOnA());
295            //Vector3 posB = multi_cast<Vector3>(contactpoint.getPositionWorldOnB());
296            //posA and posB are almost identical
297
298            Vector3 relativeHit = this->getControllableEntity()->getWorldOrientation().UnitInverse() * (this->getControllableEntity()->getWorldPosition() - posA);
299
300            //back is z positive
301            //x is left positive
302            //y is down positive
303            relativeHit.normalise();
304
305            float threshold = 0.3f;
306            if (relativeHit.x > threshold) // Left
307            {
308                this->damageOverlayLeft_->show();
309                this->damageOverlayTL_ = this->damageOverlayTime_;
310                //this->damageOverlayLeft_->setBackgroundAlpha(0.3);
311            }
312            if (relativeHit.x < -threshold) //Right
313            {
314                this->damageOverlayRight_->show();
315                this->damageOverlayTR_ = this->damageOverlayTime_;
316                //this->damageOverlayRight_->setBackgroundAlpha(0.3);
317            }
318            if (relativeHit.y > threshold) //Top
319            {
320                this->damageOverlayBottom_->show();
321                this->damageOverlayTB_ = this->damageOverlayTime_;
322                //this->damageOverlayTop_->setBackgroundAlpha(0.3);
323            }
324            if (relativeHit.y < -threshold) //Bottom
325            {
326                this->damageOverlayTop_->show();
327                this->damageOverlayTT_ = this->damageOverlayTime_;
328                //this->damageOverlayBottom_->setBackgroundAlpha(0.3);
329            }
330        }
331    }
332
333    void NewHumanController::unfire()
334    {
335        if (NewHumanController::localController_s)
336            NewHumanController::localController_s->doUnfire();
337    }
338
339    void NewHumanController::doUnfire()
340    {
341        this->firemode_ = -1;
342        hideArrows();
343    }
344
345    void NewHumanController::updateTarget()
346    {
347        Ogre::RaySceneQuery * rsq = HumanController::localController_s->getControllableEntity()->getScene()->getSceneManager()->createRayQuery(Ogre::Ray());
348
349        Ogre::Ray mouseRay = HumanController::localController_s->getControllableEntity()->getCamera()->getOgreCamera()->getCameraToViewportRay(static_cast<float>(this->currentYaw_)/2*-1+.5f, static_cast<float>(this->currentPitch_)/2*-1+.5f);
350
351        rsq->setRay(mouseRay);
352        rsq->setSortByDistance(true);
353
354        /*
355        Distance of objects:
356        ignore everything under 200 maybe even take 1000 as min distance to shoot at
357
358        shots are regularly traced and are entities!!!!!!!!! this is the biggest problem
359        they vanish only after a distance of 10'000
360        */
361
362
363        Ogre::RaySceneQueryResult& result = rsq->execute();
364        Pawn* pawn = orxonox_cast<Pawn*>(this->getControllableEntity());
365
366        Ogre::RaySceneQueryResult::iterator itr;
367        for (itr = result.begin(); itr != result.end(); ++itr)
368        {
369            if (itr->movable->isInScene() && itr->movable->getMovableType() == "Entity" && itr->distance > 500)
370            {
371                // Try to cast the user pointer
372                WorldEntity* wePtr = dynamic_cast<WorldEntity*>(Ogre::any_cast<OrxonoxClass*>(itr->movable->getUserAny()));
373                if (wePtr)
374                {
375                    // go through all parents of object and look whether they are sightable or not
376                    bool isSightable = false;
377                    WorldEntity* parent = wePtr->getParent();
378                    while (parent)
379                    {
380                        if (this->targetMask_.isExcluded(parent->getIdentifier()))
381                        {
382                            parent = parent->getParent();
383                            continue;
384                        }
385                        else
386                        {
387                            isSightable = true;
388                            break;
389                        }
390                    }
391                    if (!isSightable)
392                        continue;
393                }
394
395                if (this->getControllableEntity() && this->getControllableEntity()->getTarget() != wePtr)
396                    this->getControllableEntity()->setTarget(wePtr);
397
398                if (pawn)
399                    pawn->setAimPosition( mouseRay.getOrigin() + mouseRay.getDirection() * itr->distance );
400
401                //itr->movable->getParentSceneNode()->showBoundingBox(true);
402                //return mouseRay.getOrigin() + mouseRay.getDirection() * itr->distance; //or itr->movable->getParentSceneNode()->_getDerivedPosition()
403                return;
404            }
405        }
406
407        if (pawn)
408            pawn->setAimPosition( mouseRay.getOrigin() + mouseRay.getDirection() * 1200 );
409
410        if( this->getControllableEntity() && this->getControllableEntity()->getTarget() != 0 )
411            this->getControllableEntity()->setTarget( 0 );
412
413        //return this->controllableEntity_->getWorldPosition() + (this->controllableEntity_->getWorldOrientation() * Vector3::NEGATIVE_UNIT_Z * 2000);
414        //return this->controllableEntity_->getWorldPosition() + (this->controllableEntity_->getCamera()->getOgreCamera()->getOrientation() * Vector3::NEGATIVE_UNIT_Z);
415    }
416
417    void NewHumanController::frontback(const Vector2& value)
418    {
419        this->accelerating_ = true;
420
421        //if (this->acceleration_ == 0)
422        HumanController::frontback(value);
423    }
424
425    void NewHumanController::yaw(const Vector2& value)
426    {
427        //SUPER(NewHumanController, yaw, value);
428        if (this->controlMode_ == 0 || (this->controllableEntity_ && this->controllableEntity_->isInMouseLook()))
429            HumanController::yaw(value);
430
431        if (this->getControllableEntity() && !this->getControllableEntity()->isExactlyA(ClassByString("FpsPlayer")))
432            this->currentYaw_ = value.x;
433    }
434
435    void NewHumanController::pitch(const Vector2& value)
436    {
437        //SUPER(NewHumanController, pitch, value);
438        if (this->controlMode_ == 0 || (this->controllableEntity_ && this->controllableEntity_->isInMouseLook()))
439            HumanController::pitch(value);
440
441        if (this->getControllableEntity() && !this->getControllableEntity()->isExactlyA(ClassByString("FpsPlayer")))
442            this->currentPitch_ = value.x;
443    }
444
445    void NewHumanController::changeMode()
446    {
447        if (NewHumanController::localController_s)
448        {
449            if (NewHumanController::localController_s->controlMode_ == 0)
450            {
451                NewHumanController::localController_s->controlMode_ = 1;
452                NewHumanController::localController_s->hideArrows();
453            }
454            else
455                NewHumanController::localController_s->controlMode_ = 0;
456        }
457    }
458
459    void NewHumanController::changedControllableEntity()
460    {
461        this->controlMode_ = 0;
462        this->currentYaw_ = 0;
463        this->currentPitch_ = 0;
464        if (this->getControllableEntity() && (this->getControllableEntity()->isExactlyA(ClassByString("SpaceShip")) || this->getControllableEntity()->isExactlyA(ClassByString("Rocket"))))
465        {
466           
467            this->showOverlays_ = true;
468            if (!this->controlPaused_)
469            {
470                this->showOverlays();
471                this->alignArrows();
472            }
473        }
474        else
475        {
476            this->showOverlays_ = false;
477            this->hideOverlays();
478        }
479    }
480
481    void NewHumanController::accelerate()
482    {
483        if (NewHumanController::localController_s)
484            NewHumanController::localController_s->acceleration_ = clamp(NewHumanController::localController_s->acceleration_ + 0.2f, 0.00f, 1.0f);
485    }
486
487    void NewHumanController::decelerate()
488    {
489        if (NewHumanController::localController_s)
490            NewHumanController::localController_s->acceleration_ = clamp(NewHumanController::localController_s->acceleration_ - 0.1f, 0.0f, 1.0f);
491    }
492
493    void NewHumanController::doResumeControl()
494    {
495        this->controlPaused_ = false;
496        if (this->showOverlays_)
497            this->showOverlays();
498    }
499
500    void NewHumanController::doPauseControl()
501    {
502        this->controlPaused_ = true;
503        this->hideOverlays();
504    }
505
506    void NewHumanController::alignArrows()
507    {
508        if (showArrows_)
509        {
510            hideArrows();
511
512            float distance = sqrt(pow(static_cast<float>(this->currentYaw_)/2*-1,2) + pow(static_cast<float>(this->currentPitch_)/2*-1,2));
513
514            if (distance > 0.04f && distance <= 0.59f * arrowsSize_ / 2.0f )
515            {
516                this->arrowsOverlay1_->setRotation(Degree(-90 + -1.0f * atan2(static_cast<float>(this->currentPitch_)/2*-1, static_cast<float>(this->currentYaw_)/2*-1) / (2.0f * Ogre::Math::PI) * 360.0f));
517                this->arrowsOverlay1_->show();
518            }
519            else if (distance > 0.59f * arrowsSize_ / 2.0f && distance <= 0.77f * arrowsSize_ / 2.0f )
520            {
521                this->arrowsOverlay2_->setRotation(Degree(-90 + -1.0f * atan2(static_cast<float>(this->currentPitch_)/2*-1, static_cast<float>(this->currentYaw_)/2*-1) / (2.0f * Ogre::Math::PI) * 360.0f));
522                this->arrowsOverlay2_->show();
523            }
524            else if (distance > 0.77f * arrowsSize_ / 2.0f && distance <= arrowsSize_ / 2.0f)
525            {
526                this->arrowsOverlay3_->setRotation(Degree(-90 + -1.0f * atan2(static_cast<float>(this->currentPitch_)/2*-1, static_cast<float>(this->currentYaw_)/2*-1) / (2.0f * Ogre::Math::PI) * 360.0f));
527                this->arrowsOverlay3_->show();
528            }
529            else if (distance > arrowsSize_ / 2.0f)
530            {
531                this->arrowsOverlay4_->setRotation(Degree(-90 + -1.0f * atan2(static_cast<float>(this->currentPitch_)/2*-1, static_cast<float>(this->currentYaw_)/2*-1) / (2.0f * Ogre::Math::PI) * 360.0f));
532                this->arrowsOverlay4_->show();
533            }
534        }
535    }
536
537    void NewHumanController::showOverlays()
538    {
539        if (!GameMode::showsGraphics())
540            return;
541        this->crossHairOverlay_->show();
542        this->centerOverlay_->show();
543
544        if (showArrows_)
545        {
546            this->arrowsOverlay1_->show();
547            this->arrowsOverlay2_->show();
548            this->arrowsOverlay3_->show();
549            this->arrowsOverlay4_->show();
550        }
551    }
552
553    void NewHumanController::hideOverlays()
554    {
555        if (!GameMode::showsGraphics())
556            return;
557        this->crossHairOverlay_->hide();
558        this->centerOverlay_->hide();
559
560        if (showDamageOverlay_)
561        {
562            this->damageOverlayTop_->hide();
563            this->damageOverlayRight_->hide();
564            this->damageOverlayBottom_->hide();
565            this->damageOverlayLeft_->hide();
566        }
567
568        this->hideArrows();
569    }
570
571    void NewHumanController::hideArrows()
572    {
573        if(!GameMode::showsGraphics())
574            return;
575        if (showArrows_)
576        {
577            this->arrowsOverlay1_->hide();
578            this->arrowsOverlay2_->hide();
579            this->arrowsOverlay3_->hide();
580            this->arrowsOverlay4_->hide();
581        }
582    }
583}
Note: See TracBrowser for help on using the repository browser.