Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/branches/data_cleanup/src/modules/pickup/PickupManager.cc @ 7536

Last change on this file since 7536 was 7533, checked in by dafrick, 14 years ago

Documenting in pickups module.
Cleaning up in PickupManager.
Removed some obsolete functions in HumanController and ControllableEntity, which were remenants of the old pickups module.
Fixed a bug.

  • Property svn:eol-style set to native
File size: 25.6 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 *      Damian 'Mozork' Frick
24 *   Co-authors:
25 *      ...
26 *
27*/
28
29/**
30    @file PickupManager.cc
31    @brief Implementation of the PickupManager class.
32*/
33
34#include "PickupManager.h"
35
36#include "util/Convert.h"
37#include "util/ScopedSingletonManager.h"
38#include "core/CoreIncludes.h"
39#include "core/LuaState.h"
40#include "core/GUIManager.h"
41#include "core/Identifier.h"
42#include "network/Host.h"
43#include "network/NetworkFunction.h"
44
45#include "interfaces/PickupCarrier.h"
46#include "infos/PlayerInfo.h"
47#include "worldentities/pawns/Pawn.h"
48
49#include "CollectiblePickup.h"
50#include "PickupRepresentation.h"
51
52#include "ToluaBindPickup.h"
53
54namespace orxonox
55{
56    // Register tolua_open function when loading the library
57    DeclareToluaInterface(Pickup);
58
59    ManageScopedSingleton(PickupManager, ScopeID::Root, false);
60
61    // Initialization of the name of the PickupInventory GUI.
62    /*static*/ const std::string PickupManager::guiName_s = "PickupInventory";
63
64    // Register static newtork functions that are used to communicate changes to pickups over the network, such that the PickupInventory can display the information about the pickups properly.
65    registerStaticNetworkFunction(PickupManager::pickupChangedUsedNetwork);
66    registerStaticNetworkFunction(PickupManager::pickupChangedPickedUpNetwork);
67    registerStaticNetworkFunction(PickupManager::dropPickupNetworked);
68    registerStaticNetworkFunction(PickupManager::usePickupNetworked);
69
70    /**
71    @brief
72        Constructor. Registers the PickupManager and creates the default PickupRepresentation.
73    */
74    PickupManager::PickupManager() : guiLoaded_(false), pickupHighestIndex_(0), defaultRepresentation_(NULL)
75    {
76        RegisterObject(PickupManager);
77
78        //TODO: Only create if isMaster().
79        this->defaultRepresentation_ = new PickupRepresentation();
80
81        COUT(3) << "PickupManager created." << std::endl;
82    }
83
84    /**
85    @brief
86        Destructor.
87        Destroys the default PickupRepresentation and does some cleanup.
88    */
89    PickupManager::~PickupManager()
90    {
91        // Destroying the default representation.
92        if(this->defaultRepresentation_ != NULL)
93            this->defaultRepresentation_->destroy();
94
95        this->representations_.clear();
96        this->representationsNetworked_.clear();
97
98        //TODO: Destroying properly?
99        //TODO: Shouldnt these list be empty, to avoid problems when switching levels?
100        // Destroying all the PickupInventoryContainers that are still there.
101        for(std::map<uint32_t, PickupInventoryContainer*>::iterator it = this->pickupInventoryContainers_.begin(); it != this->pickupInventoryContainers_.end(); it++)
102            delete it->second;
103        this->pickupInventoryContainers_.clear();
104
105        // Destroying all the WeakPointers that are still there.
106        for(std::map<uint32_t, WeakPtr<Pickupable>*>::iterator it = this->pickups_.begin(); it != this->pickups_.end(); it++)
107            delete it->second;
108        this->pickups_.clear();
109
110        this->indexes_.clear();
111
112        COUT(3) << "PickupManager destroyed." << std::endl;
113    }
114
115    /**
116    @brief
117        Registers a PickupRepresentation together with the PickupIdentifier of the Pickupable the PickupRepresentation represents, on the server (or in standalone mode).
118        For every type of Pickupable (uniquely identified by a PickupIdentifier) there can be one (and just one) PickupRepresentation registered.
119    @param identifier
120        The PickupIdentifier identifying the Pickupable.
121    @param representation
122        A pointer to the PickupRepresentation.
123    @return
124        Returns true if successful and false if not.
125    */
126    bool PickupManager::registerRepresentation(const PickupIdentifier* identifier, PickupRepresentation* representation)
127    {
128        assert(identifier);
129        assert(representation);
130
131        // If the list is not empty and Pickupable already has a Representation registered.
132        if(!this->representations_.empty() && this->representations_.find(identifier) != this->representations_.end())
133            return false;
134
135        this->representations_[identifier] = representation;
136
137        COUT(4) << "PickupRepresentation &" << representation << " registered with the PickupManager." << std::endl;
138        return true;
139    }
140
141    /**
142    @brief
143        Unegisters a PickupRepresentation together with the PickupIdentifier of the Pickupable the PickupRepresentation represents, on the server (or in standalone mode).
144    @param identifier
145        The PickupIdentifier identifying the Pickupable.
146    @param representation
147        A pointer to the PickupRepresentation.
148    @return
149        Returns true if successful and false if not.
150    */
151    bool PickupManager::unregisterRepresentation(const PickupIdentifier* identifier, PickupRepresentation* representation)
152    {
153        assert(identifier);
154        assert(representation);
155
156        std::map<const PickupIdentifier*, PickupRepresentation*, PickupIdentifierCompare>::iterator it = this->representations_.find(identifier);
157        if(it == this->representations_.end()) // If the Pickupable is not registered in the first place.
158            return false;
159
160        this->representations_.erase(it);
161
162        COUT(4) << "PickupRepresentation &" << representation << " unregistered with the PickupManager." << std::endl;
163        return true;
164    }
165
166    /**
167    @brief
168        Registers a PickupRepresentation on the host it was created.
169    @param representation
170        A pointer to the PickupRepresentation.
171    @return
172        Returns true if successful, false if not.
173    */
174    bool PickupManager::registerRepresentation(PickupRepresentation* representation)
175    {
176        assert(representation);
177
178        // If the list is not empty and PickupRepresentation is already registered.
179        if(!this->representationsNetworked_.empty() && this->representationsNetworked_.find(representation->getObjectID()) != this->representationsNetworked_.end())
180            return false;
181
182        this->representationsNetworked_[representation->getObjectID()] = representation;
183        return true;
184    }
185
186    /**
187    @brief
188        Unregisters a PickupRepresentation on the host it is being destroyed (which is the same host on which it was created).
189    @param representation
190        A pointer to the Pickuprepresentation.
191    @return
192        Returns true if successful, false if not.
193    */
194    bool PickupManager::unregisterRepresentation(PickupRepresentation* representation)
195    {
196        assert(representation);
197
198        std::map<uint32_t, PickupRepresentation*>::iterator it = this->representationsNetworked_.find(representation->getObjectID());
199        if(it == this->representationsNetworked_.end()) // If the Pickupable is not registered in the first place.
200            return false;
201
202        this->representationsNetworked_.erase(it);
203        return true;
204    }
205
206    /**
207    @brief
208        Get the PickupRepresentation representing the Pickupable with the input PickupIdentifier.
209    @param identifier
210        The PickupIdentifier.
211    @return
212        Returns a pointer to the PickupRepresentation.
213    */
214    //TODO: Why not return a const?
215    PickupRepresentation* PickupManager::getRepresentation(const PickupIdentifier* identifier)
216    {
217        std::map<const PickupIdentifier*, PickupRepresentation*, PickupIdentifierCompare>::iterator it = this->representations_.find(identifier);
218        if(it == this->representations_.end()) // If there is no PickupRepresentation associated with the input PickupIdentifier.
219        {
220            COUT(4) << "PickupManager::getRepresentation() returned default representation." << std::endl;
221            return this->defaultRepresentation_;
222        }
223
224        return it->second;
225    }
226
227    /**
228    @brief
229        Is called by the PickupListener to notify the PickupManager, that the input Pickupable has transited to the input used state.
230    @param pickup
231        The Pickupable whose used status changed.
232    @param used
233        The used status the Pickupable changed to.
234    */
235    void PickupManager::pickupChangedUsed(Pickupable* pickup, bool used)
236    {
237        assert(pickup);
238
239        if(!GameMode::isMaster()) // If this is neither standalone nor the server.
240            return;
241
242        CollectiblePickup* collectible = orxonox_cast<CollectiblePickup*>(pickup);
243        // If the Pickupable is part of a PickupCollection it isn't displayed in the PickupInventory, just the PickupCollection is.
244        if(collectible != NULL && collectible->isInCollection())
245            return;
246
247        // Getting clientId of the host this change of the pickup's used status concerns.
248        PickupCarrier* carrier = pickup->getCarrier();
249        while(carrier->getCarrierParent() != NULL)
250            carrier = carrier->getCarrierParent();
251        Pawn* pawn = orxonox_cast<Pawn*>(carrier);
252        if(pawn == NULL)
253            return;
254        PlayerInfo* info = pawn->getPlayer();
255        if(info == NULL)
256            return;
257        unsigned int clientId = info->getClientID();
258
259        // Get the number identifying the pickup.
260        std::map<Pickupable*, uint32_t>::iterator it = this->indexes_.find(pickup);
261        assert(it != this->indexes_.end());
262        uint32_t index = it->second;
263
264        // If we're either in standalone mode or this is the host whom the change of the pickup's status concerns.
265        if(GameMode::isStandalone() || Host::getPlayerID() == clientId)
266        {
267            PickupManager::pickupChangedUsedNetwork(index, used, pickup->isUsable(), pickup->isUnusable());
268        }
269        // If the concerned host is somewhere in the network, we call pickupChangedUsedNetwork() on its PickupManager.
270        else
271        {
272            callStaticNetworkFunction(PickupManager::pickupChangedUsedNetwork, clientId, index, used, pickup->isUsable(), pickup->isUnusable());
273        }
274    }
275
276    /**
277    @brief
278        Helper method to react to the change in the used status of a Pickupable.
279        Static method that is used by the server to inform the client it concerns about the status change.
280        The parameters that are given are used to update the information (i.e. the PickupInventoryContainer) the concerning PickupManager has about the Pickupable that changed.
281    @param pickup
282        A number identifying the Pickupable that changed its used status.
283    @param inUse
284        The used status the Pickupable changed to. (i.e. whether the Pickupable is in use or not).
285    @param usable
286        Whether the Pickupable's used status can be changed used in the PickupInventory.
287    @param unusable
288        Whether the Pickupable's used status can be changed to unused in the PickupInventory.
289    */
290    /*static*/ void PickupManager::pickupChangedUsedNetwork(uint32_t pickup, bool inUse, bool usable, bool unusable)
291    {
292        PickupManager& manager = PickupManager::getInstance(); // Get the PickupManager singleton on this host.
293        // If the input Pickupable (i.e its identifier) is not present in the list the PickupManager has.
294        if(manager.pickupInventoryContainers_.find(pickup) == manager.pickupInventoryContainers_.end())
295        {
296            COUT(1) << "Error: Pickupable &(" << pickup << ") was not registered with PickupManager for the PickupInventory, when it changed used." << std::endl;
297            return;
298        }
299
300        // Update the Pickupable's container with the information transferred.
301        manager.pickupInventoryContainers_[pickup]->inUse = inUse;
302        manager.pickupInventoryContainers_[pickup]->usable = usable;
303        manager.pickupInventoryContainers_[pickup]->unusable = unusable;
304
305        manager.updateGUI(); // Tell the PickupInventory that something has changed.
306    }
307
308    /**
309    @brief
310        Is called by the PickupListener to notify the PickupManager, that the input Pickupable has transited to the input pickedUp state.
311    @param pickup
312        The Pickupable whose pickedUp status changed.
313    @param pickedUp
314        The pickedUp status the Pickupable changed to.
315    */
316    void PickupManager::pickupChangedPickedUp(Pickupable* pickup, bool pickedUp)
317    {
318        assert(pickup);
319
320        if(!GameMode::isMaster()) // If this is neither standalone nor the server.
321            return;
322
323        CollectiblePickup* collectible = orxonox_cast<CollectiblePickup*>(pickup);
324        // If the Pickupable is part of a PickupCollection it isn't displayed in the PickupInventory, just the PickupCollection is.
325        if(collectible != NULL && collectible->isInCollection())
326            return;
327
328        // Getting clientId of the host this change of the pickup's pickedUp status concerns.
329        PickupCarrier* carrier = pickup->getCarrier();
330        while(carrier->getCarrierParent() != NULL)
331            carrier = carrier->getCarrierParent();
332        Pawn* pawn = orxonox_cast<Pawn*>(carrier);
333        if(pawn == NULL)
334            return;
335        PlayerInfo* info = pawn->getFormerPlayer();
336        if(info == NULL)
337            return;
338        unsigned int clientId = info->getClientID();
339
340        uint32_t index = 0;
341        if(pickedUp) // If the Pickupable has changed to picked up, it is added to the required lists.
342        {
343            index = this->getPickupIndex(); // Ge a new identifier (index) for the Pickupable.
344            // Add the Pickupable to the indexes_ and pickups_ lists.
345            this->indexes_[pickup] = index;
346            this->pickups_[index] = new WeakPtr<Pickupable>(pickup);
347        }
348        else // If it was dropped, it is removed from the required lists.
349        {
350            // Get the indentifier (index) that identifies the input Pickupable.
351            std::map<Pickupable*, uint32_t>::iterator it = this->indexes_.find(pickup);
352            index = it->second;
353
354            // Remove the Pickupable form the indexes_ and pickups_ list.
355            WeakPtr<Pickupable>* ptr = this->pickups_[index];
356            this->indexes_.erase(it);
357            this->pickups_.erase(index);
358            delete ptr;
359        }
360
361        // If we're either in standalone mode or this is the host whom the change of the pickup's status concerns.
362        //TODO: Needs to be added to server even if is was not picked up by it?
363        if(GameMode::isStandalone() || Host::getPlayerID() == clientId)
364        {
365            // If there is no PickupRepresentation registered the default representation is used.
366            if(this->representations_.find(pickup->getPickupIdentifier()) == this->representations_.end())
367                PickupManager::pickupChangedPickedUpNetwork(index, pickup->isUsable(), this->defaultRepresentation_->getObjectID(), pickedUp);
368            else
369                PickupManager::pickupChangedPickedUpNetwork(index, pickup->isUsable(), this->representations_[pickup->getPickupIdentifier()]->getObjectID(), pickedUp);
370        }
371        // If the concerned host is somewhere in the network, we call pickupChangedPickedUpNetwork() on its PickupManager.
372        else
373        {
374            // If there is no PickupRepresentation registered the default representation is used.
375            if(this->representations_.find(pickup->getPickupIdentifier()) == this->representations_.end())
376            {
377                callStaticNetworkFunction(PickupManager::pickupChangedPickedUpNetwork, clientId, index, pickup->isUsable(), this->defaultRepresentation_->getObjectID(), pickedUp);
378            }
379            else
380            {
381                callStaticNetworkFunction(PickupManager::pickupChangedPickedUpNetwork, clientId, index, pickup->isUsable(), this->representations_[pickup->getPickupIdentifier()]->getObjectID(), pickedUp);
382            }
383        }
384
385    }
386
387    /**
388    @brief
389        Helper method to react to the change in the pickedUp status of a Pickupable.
390        Static method that is used by the server to inform the client it concerns about the status change.
391        The parameters that are given are used to update the information (i.e. the PickupInventoryContainer) the concerning PickupManager has about the Pickupable that changed.
392    @param pickup
393        A number identifying the Pickupable that changed its pickedUp status.
394    @param unusable
395        Whether the Pickupable's used status can be changed to unused in the PickupInventory.
396    @param representationObjectId
397        The objectId identifying (over the network) the PickupRepresentation that represents this Pickupable.
398    @param pickedUp
399        The pickedUp status the Pickupable changed to.
400    */
401    /*static*/ void PickupManager::pickupChangedPickedUpNetwork(uint32_t pickup, bool usable, uint32_t representationObjectId, bool pickedUp)
402    {
403        PickupManager& manager = PickupManager::getInstance(); // Get the PickupManager singleton on this host.
404        // If the Pickupable has been picked up, we create a new PickupInventoryContainer for it.
405        if(pickedUp)
406        {
407            // Create a new PickupInventoryContainer for the Pickupable and set all the necessary information.
408            PickupInventoryContainer* container = new PickupInventoryContainer;
409            container->pickup = pickup;
410            container->inUse = false;
411            container->pickedUp = pickedUp;
412            container->usable = usable;
413            container->unusable = false;
414            container->representationObjectId = representationObjectId;
415            // Insert the container into the pickupInventoryContainers_ list.
416            manager.pickupInventoryContainers_.insert(std::pair<uint32_t, PickupInventoryContainer*>(pickup, container));
417
418            manager.updateGUI(); // Tell the PickupInventory that something has changed.
419        }
420        // If the Pickupable has been dropped, we remove it from the pickupInventoryContainers_ list.
421        else
422        {
423            std::map<uint32_t, PickupInventoryContainer*>::iterator it = manager.pickupInventoryContainers_.find(pickup);
424            if(it != manager.pickupInventoryContainers_.end())
425                delete it->second;
426            manager.pickupInventoryContainers_.erase(pickup);
427
428            manager.updateGUI(); // Tell the PickupInventory that something has changed.
429        }
430    }
431
432    /**
433    @brief
434        Get the PickupRepresentation of an input Pickupable.
435        This method spares us the hassle to export the PickupIdentifier class to lua.
436    @param pickup
437        The number identifying the Pickupable whose PickupRepresentation should be returned.
438    @return
439        Returns the PickupRepresentation of the input Pickupable or NULL if an error occurred.
440    */
441    orxonox::PickupRepresentation* PickupManager::getPickupRepresentation(uint32_t pickup)
442    {
443        // Clear and rebuild the representationsNetworked_ list.
444        //TODO: Better solution?
445        this->representationsNetworked_.clear();
446        for(ObjectList<PickupRepresentation>::iterator it = ObjectList<PickupRepresentation>::begin(); it != ObjectList<PickupRepresentation>::end(); ++it)
447            this->representationsNetworked_[it->getObjectID()] = *it;
448
449        // Get the container belonging to the input pickup, if not found return the default representation.
450        std::map<uint32_t, PickupInventoryContainer*>::iterator it = this->pickupInventoryContainers_.find(pickup);
451        if(it == this->pickupInventoryContainers_.end())
452            return this->defaultRepresentation_;
453
454        // Get the PickupRepresentation of the input pickup (through the objecId of the representation stored in the PickupInventoryContainer belonging to the pickup), if not found return the default representation.
455        std::map<uint32_t, PickupRepresentation*>::iterator it2 = this->representationsNetworked_.find(it->second->representationObjectId);
456        if(it2 == this->representationsNetworked_.end())
457            return this->defaultRepresentation_;
458
459        return it2->second;
460    }
461
462    /**
463    @brief
464        Get the number of pickups currently picked up by the player.
465        This method is used in lua to populate the PickupInventory. The intended usage is to call this method to reset the iterator of the list of PickupInventoryContainers and then use popPickup() to get the individual PickupInventoryContainers.
466    @return
467        Returns the number of the players picked up Pickupables.
468    */
469    int PickupManager::getNumPickups(void)
470    {
471        this->pickupsIterator_ = this->pickupInventoryContainers_.begin(); // Reset iterator.
472
473        return this->pickupInventoryContainers_.size();
474    }
475
476    /**
477    @brief
478        Drop the input Pickupable.
479        This method checks whether the input Pickupable still exists and drops it, if so.
480    @param pickup
481        The identifier of the Pickupable to be dropped.
482    */
483    void PickupManager::dropPickup(uint32_t pickup)
484    {
485        // If we're either server or standalone and the list of pickups is not empty, we find and drop the input pickup.
486        if(GameMode::isMaster() && !this->pickups_.empty())
487        {
488            Pickupable* pickupable = this->pickups_.find(pickup)->second->get();
489            if(pickupable != NULL)
490                pickupable->drop();
491        }
492        // If we're neither server nor standalone we drop the pickup by calling dropPickupNetworked() of the PickupManager on the server.
493        else
494        {
495            callStaticNetworkFunction(PickupManager::dropPickupNetworked, 0, pickup);
496        }
497    }
498
499    /**
500    @brief
501        Helper method to drop the input pickup on the server.
502        Static method that is used by clients to instruct the server to drop the input pickup.
503    @param pickup
504        The identifier of the Pickupable to be dropped.
505    */
506    /*static*/ void PickupManager::dropPickupNetworked(uint32_t pickup)
507    {
508        if(GameMode::isServer()) // Obviously we only want to do this on the server.
509        {
510            PickupManager& manager = PickupManager::getInstance();
511            //TODO: Just call dropPickup() on manager?
512            if(manager.pickups_.empty())
513                return;
514            Pickupable* pickupable = manager.pickups_.find(pickup)->second->get();
515            if(pickupable != NULL)
516                pickupable->drop();
517        }
518    }
519
520    /**
521    @brief
522        Use (or unuse) the input Pickupable.
523        This method checks whether the input Pickupable still exists and uses (or unuses) it, if so,
524    @param pickup
525        The identifier of the Pickupable to be used (or unused).
526    @param use
527        If true the input Pickupable is used, if false it is unused.
528    */
529    void PickupManager::usePickup(uint32_t pickup, bool use)
530    {
531        // If we're either server or standalone and the list of pickups is not empty, we find and change the used status of the input pickup.
532        if(GameMode::isMaster() && !this->pickups_.empty())
533        {
534            Pickupable* pickupable = this->pickups_.find(pickup)->second->get();
535            if(pickupable != NULL)
536                pickupable->setUsed(use);
537        }
538        // If we're neither server nor standalone we change the used status of the pickup by calling usePickupNetworked() of the PickupManager on the server.
539        else
540        {
541            callStaticNetworkFunction(PickupManager::usePickupNetworked, 0, pickup, use);
542        }
543    }
544
545    /**
546    @brief
547        Helper method to use (or unuse) the input Pickupable on the server.
548        Static method that is used by clients to instruct the server to use (or unuse) the input pickup.
549    @param pickup
550        The identifier of the Pickupable to be used (or unused).
551    @param use
552        If true the input Pickupable is used, if false it is unused.
553    */
554    /*static*/ void PickupManager::usePickupNetworked(uint32_t pickup, bool use)
555    {
556        if(GameMode::isServer())
557        {
558            PickupManager& manager = PickupManager::getInstance();
559            //TODO: Just call usePickup() on manager?
560            if(manager.pickups_.empty())
561                return;
562            Pickupable* pickupable = manager.pickups_.find(pickup)->second->get();
563            if(pickupable != NULL)
564                pickupable->setUsed(use);
565        }
566    }
567
568    /**
569    @brief
570        Updates the PickupInventory GUI.
571        Also loads the PickupInventory GUI if is hasn't been done already.
572    */
573    inline void PickupManager::updateGUI(void)
574    {
575        // We only need to update (and load) the GUI if this host shows graphics.
576        if(GameMode::showsGraphics())
577        {
578            if(!this->guiLoaded_) // If the GUI hasn't been loaded, yet, we load it.
579            {
580                GUIManager::getInstance().loadGUI(PickupManager::guiName_s);
581                this->guiLoaded_ = true;
582            }
583
584            // Update the GUI.
585            GUIManager::getInstance().getLuaState()->doString(PickupManager::guiName_s + ".update()");
586        }
587    }
588
589    /**
590    @brief
591        Get a new index for a Pickupable.
592        This will work as long as the number of Pickupables that are picked up is sufficiently small and as long as they don't exist forever.
593    @return
594        Returns the new index.
595    */
596    uint32_t PickupManager::getPickupIndex(void)
597    {
598        if(this->pickupHighestIndex_ == uint32_t(~0x0)-1) // If we've reached the highest possible number, we wrap around.
599            this->pickupHighestIndex_ = 0;
600        return this->pickupHighestIndex_++;
601    }
602
603}
Note: See TracBrowser for help on using the repository browser.