root/trunk/src/shared/vmap/VMapManager.cpp @ 7

Revision 2, 24.8 kB (checked in by yumileroy, 17 years ago)

[svn] * Proper SVN structure

Original author: Neo2003
Date: 2008-10-02 16:23:55-05:00

Line 
1/*
2 * Copyright (C) 2005-2008 MaNGOS <http://www.mangosproject.org/>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17 */
18
19#include "VMapManager.h"
20#include "VMapDefinitions.h"
21
22using namespace G3D;
23
24namespace VMAP
25{
26
27    //=========================================================
28
29    VMapManager::VMapManager()
30    {
31#ifdef _VMAP_LOG_DEBUG
32        iCommandLogger.setFileName("vmapcmd.log");
33        iCommandLogger.setResetFile();
34#endif
35    }
36
37    //=========================================================
38
39    VMapManager::~VMapManager(void)
40    {
41        Array<unsigned int > keyArray = iInstanceMapTrees.getKeys();
42        for(int i=0;i<keyArray.size(); ++i)
43        {
44            delete iInstanceMapTrees.get(keyArray[i]);
45            iInstanceMapTrees.remove(keyArray[i]);
46        }
47    }
48
49    //=========================================================
50
51    Vector3 VMapManager::convertPositionToInternalRep(float x, float y, float z) const
52    {
53        float pos[3];
54        pos[0] = y;
55        pos[1] = z;
56        pos[2] = x;
57        double full = 64.0*533.33333333;
58        double mid = full/2.0;
59        pos[0] = full- (pos[0] + mid);
60        pos[2] = full- (pos[2] + mid);
61
62        return(Vector3(pos));
63    }
64
65    //=========================================================
66
67    Vector3 VMapManager::convertPositionToMangosRep(float x, float y, float z) const
68    {
69        float pos[3];
70        pos[0] = z;
71        pos[1] = x;
72        pos[2] = y;
73        double full = 64.0*533.33333333;
74        double mid = full/2.0;
75        pos[0] = -((mid+pos[0])-full);
76        pos[1] = -((mid+pos[1])-full);
77
78        return(Vector3(pos));
79    }
80    //=========================================================
81
82    std::string VMapManager::getDirFileName(unsigned int pMapId, int x, int y) const
83    {
84        char name[FILENAMEBUFFER_SIZE];
85
86        sprintf(name, "%03u_%d_%d%s",pMapId, x, y, DIR_FILENAME_EXTENSION);
87        return(std::string(name));
88    }
89
90    //=========================================================
91    std::string VMapManager::getDirFileName(unsigned int pMapId) const
92    {
93        char name[FILENAMEBUFFER_SIZE];
94
95        sprintf(name, "%03d%s",pMapId, DIR_FILENAME_EXTENSION);
96        return(std::string(name));
97    }
98    //=========================================================
99    // remote last return or LF
100    void chomp(std::string& str)
101    {
102        while(str.length() >0)
103        {
104            char lc = str[str.length()-1];
105            if(lc == '\r' || lc == '\n')
106            {
107                str = str.substr(0,str.length()-1);
108            }
109            else
110            {
111                break;
112            }
113        }
114    }
115    //=========================================================
116
117    void chompAndTrim(std::string& str)
118    {
119        while(str.length() >0)
120        {
121            char lc = str[str.length()-1];
122            if(lc == '\r' || lc == '\n' || lc == ' ' || lc == '"' || lc == '\'')
123            {
124                str = str.substr(0,str.length()-1);
125            }
126            else
127            {
128                break;
129            }
130        }
131        while(str.length() >0)
132        {
133            char lc = str[0];
134            if(lc == ' ' || lc == '"' || lc == '\'')
135            {
136                str = str.substr(1,str.length()-1);
137            }
138            else
139            {
140                break;
141            }
142        }
143    }
144
145    //=========================================================
146    // result false, if no more id are found
147
148    bool getNextMapId(const std::string& pString, unsigned int& pStartPos, unsigned int& pId)
149    {
150        bool result = false;
151        unsigned int i;
152        for(i=pStartPos;i<pString.size(); ++i)
153        {
154            if(pString[i] == ',')
155            {
156                break;
157            }
158        }
159        if(i>pStartPos)
160        {
161            std::string idString = pString.substr(pStartPos, i-pStartPos);
162            pStartPos = i+1;
163            chompAndTrim(idString);
164            pId = atoi(idString.c_str());
165            result = true;
166        }
167        return(result);
168    }
169
170    //=========================================================
171    /**
172    Block maps from being used.
173    parameter: String of map ids. Delimiter = ","
174    e.g.: "0,1,590"
175    */
176
177    void VMapManager::preventMapsFromBeingUsed(const char* pMapIdString)
178    {
179        if(pMapIdString != NULL)
180        {
181            unsigned int pos =0;
182            unsigned int id;
183            std::string confString(pMapIdString);
184            chompAndTrim(confString);
185            while(getNextMapId(confString, pos, id))
186            {
187                iIgnoreMapIds.set(id, true);
188            }
189        }
190    }
191
192    //=========================================================
193
194    int VMapManager::loadMap(const char* pBasePath, unsigned int pMapId, int x, int y)
195    {
196        int result = VMAP_LOAD_RESULT_IGNORED;
197        if(isMapLoadingEnabled() && !iIgnoreMapIds.containsKey(pMapId))
198        {
199            bool loaded = _loadMap(pBasePath, pMapId, x, y, false);
200            if(!loaded)
201            {
202                // if we can't load the map it might be splitted into tiles. Try that one and store the result
203                loaded = _loadMap(pBasePath, pMapId, x, y, true);
204                if(loaded)
205                {
206                    iMapsSplitIntoTiles.set(pMapId, true);
207                }
208            }
209            if(loaded)
210            {
211                result = VMAP_LOAD_RESULT_OK;
212                // just for debugging
213#ifdef _VMAP_LOG_DEBUG
214                Command c = Command();
215                c.fillLoadTileCmd(x, y, pMapId);
216                iCommandLogger.appendCmd(c);
217#endif
218            }
219            else
220            {
221                result = VMAP_LOAD_RESULT_ERROR;
222            }
223        }
224        return result;
225    }
226
227    //=========================================================
228    // load one tile (internal use only)
229
230    bool VMapManager::_loadMap(const char* pBasePath, unsigned int pMapId, int x, int y, bool pForceTileLoad)
231    {
232        bool result = false;
233        std::string dirFileName;
234        if(pForceTileLoad || iMapsSplitIntoTiles.containsKey(pMapId))
235        {
236            dirFileName = getDirFileName(pMapId,x,y);
237        }
238        else
239        {
240            dirFileName = getDirFileName(pMapId);
241        }
242        MapTree* instanceTree;
243        if(!iInstanceMapTrees.containsKey(pMapId))
244        {
245            instanceTree = new MapTree(pBasePath);
246            iInstanceMapTrees.set(pMapId, instanceTree);
247        }
248        else
249            instanceTree = iInstanceMapTrees.get(pMapId);
250
251        unsigned int mapTileIdent = MAP_TILE_IDENT(x,y);
252        result = instanceTree->loadMap(dirFileName, mapTileIdent);
253        if(!result)                                         // remove on fail
254        {
255            if(instanceTree->size() == 0)
256            {
257                iInstanceMapTrees.remove(pMapId);
258                delete instanceTree;
259            }
260        }
261        return(result);
262    }
263
264    //=========================================================
265
266    bool VMapManager::_existsMap(const std::string& pBasePath, unsigned int pMapId, int x, int y, bool pForceTileLoad)
267    {
268        bool result = false;
269        std::string dirFileName;
270        if(pForceTileLoad || iMapsSplitIntoTiles.containsKey(pMapId))
271        {
272            dirFileName = getDirFileName(pMapId,x,y);
273        }
274        else
275        {
276            dirFileName = getDirFileName(pMapId);
277        }
278        size_t len = pBasePath.length() + dirFileName.length();
279        char *filenameBuffer = new char[len+1];
280        sprintf(filenameBuffer, "%s%s", pBasePath.c_str(), dirFileName.c_str());
281        FILE* df = fopen(filenameBuffer, "rb");
282        if(df)
283        {
284            char lineBuffer[FILENAMEBUFFER_SIZE];
285            if (fgets(lineBuffer, FILENAMEBUFFER_SIZE-1, df) != 0)
286            {
287                std::string name = std::string(lineBuffer);
288                chomp(name);
289                if(name.length() >1)
290                {
291                    sprintf(filenameBuffer, "%s%s", pBasePath.c_str(), name.c_str());
292                    FILE* df2 = fopen(filenameBuffer, "rb");
293                    if(df2)
294                    {
295                        char magic[8];
296                        fread(magic,1,8,df2);
297                        if(!strncmp(VMAP_MAGIC,magic,8))
298                            result = true;
299                        fclose(df2);
300                    }
301                }
302            }
303            fclose(df);
304        }
305        delete[] filenameBuffer;
306        return result;
307    }
308
309    //=========================================================
310
311    bool VMapManager::existsMap(const char* pBasePath, unsigned int pMapId, int x, int y)
312    {
313        std::string basePath = std::string(pBasePath);
314        if(basePath.length() > 0 && (basePath[basePath.length()-1] != '/' || basePath[basePath.length()-1] != '\\'))
315        {
316            basePath.append("/");
317        }
318        bool found = _existsMap(basePath, pMapId, x, y, false);
319        if(!found)
320        {
321            // if we can't load the map it might be splitted into tiles. Try that one and store the result
322            found = _existsMap(basePath, pMapId, x, y, true);
323            if(found)
324            {
325                iMapsSplitIntoTiles.set(pMapId, true);
326            }
327        }
328        return found;
329    }
330
331    //=========================================================
332
333    void VMapManager::unloadMap(unsigned int pMapId, int x, int y)
334    {
335        _unloadMap(pMapId, x, y);
336
337#ifdef _VMAP_LOG_DEBUG
338        Command c = Command();
339        c.fillUnloadTileCmd(pMapId, x,y);
340        iCommandLogger.appendCmd(c);
341#endif
342    }
343
344    //=========================================================
345
346    void VMapManager::_unloadMap(unsigned int  pMapId, int x, int y)
347    {
348        if(iInstanceMapTrees.containsKey(pMapId))
349        {
350            MapTree* instanceTree = iInstanceMapTrees.get(pMapId);
351            std::string dirFileName;
352            if(iMapsSplitIntoTiles.containsKey(pMapId))
353            {
354                dirFileName = getDirFileName(pMapId,x,y);
355            }
356            else
357            {
358                dirFileName = getDirFileName(pMapId);
359            }
360            unsigned int mapTileIdent = MAP_TILE_IDENT(x,y);
361            instanceTree->unloadMap(dirFileName, mapTileIdent);
362            if(instanceTree->size() == 0)
363            {
364                iInstanceMapTrees.remove(pMapId);
365                delete instanceTree;
366            }
367        }
368    }
369
370    //=========================================================
371
372    void VMapManager::unloadMap(unsigned int pMapId)
373    {
374        if(iInstanceMapTrees.containsKey(pMapId))
375        {
376            MapTree* instanceTree = iInstanceMapTrees.get(pMapId);
377            std::string dirFileName = getDirFileName(pMapId);
378            instanceTree->unloadMap(dirFileName, 0, true);
379            if(instanceTree->size() == 0)
380            {
381                iInstanceMapTrees.remove(pMapId);
382                delete instanceTree;
383            }
384#ifdef _VMAP_LOG_DEBUG
385            Command c = Command();
386            c.fillUnloadTileCmd(pMapId);
387            iCommandLogger.appendCmd(c);
388#endif
389        }
390    }
391    //==========================================================
392
393    bool VMapManager::isInLineOfSight(unsigned int pMapId, float x1, float y1, float z1, float x2, float y2, float z2)
394    {
395        bool result = true;
396        if(isLineOfSightCalcEnabled() && iInstanceMapTrees.containsKey(pMapId))
397        {
398            Vector3 pos1 = convertPositionToInternalRep(x1,y1,z1);
399            Vector3 pos2 = convertPositionToInternalRep(x2,y2,z2);
400            if(pos1 != pos2)
401            {
402                MapTree* mapTree = iInstanceMapTrees.get(pMapId);
403                result = mapTree->isInLineOfSight(pos1, pos2);
404#ifdef _VMAP_LOG_DEBUG
405                Command c = Command();
406                                                            // save the orig vectors
407                c.fillTestVisCmd(pMapId,Vector3(x1,y1,z1),Vector3(x2,y2,z2),result);
408                iCommandLogger.appendCmd(c);
409#endif
410            }
411        }
412        return(result);
413    }
414    //=========================================================
415    /**
416    get the hit position and return true if we hit something
417    otherwise the result pos will be the dest pos
418    */
419    bool VMapManager::getObjectHitPos(unsigned int pMapId, float x1, float y1, float z1, float x2, float y2, float z2, float& rx, float &ry, float& rz, float pModifyDist)
420    {
421        bool result = false;
422        rx=x2;
423        ry=y2;
424        rz=z2;
425        if(isLineOfSightCalcEnabled())
426        {
427            if(iInstanceMapTrees.containsKey(pMapId))
428            {
429                Vector3 pos1 = convertPositionToInternalRep(x1,y1,z1);
430                Vector3 pos2 = convertPositionToInternalRep(x2,y2,z2);
431                Vector3 resultPos;
432                MapTree* mapTree = iInstanceMapTrees.get(pMapId);
433                result = mapTree->getObjectHitPos(pos1, pos2, resultPos, pModifyDist);
434                resultPos = convertPositionToMangosRep(resultPos.x,resultPos.y,resultPos.z);
435                rx = resultPos.x;
436                ry = resultPos.y;
437                rz = resultPos.z;
438#ifdef _VMAP_LOG_DEBUG
439                Command c = Command();
440                c.fillTestObjectHitCmd(pMapId, pos1, pos2, resultPos, result);
441                iCommandLogger.appendCmd(c);
442#endif
443            }
444        }
445        return result;
446    }
447
448    //=========================================================
449    /**
450    get height or INVALID_HEIGHT if to hight was calculated
451    */
452
453    //int gGetHeightCounter = 0;
454    float VMapManager::getHeight(unsigned int pMapId, float x, float y, float z)
455    {
456        float height = VMAP_INVALID_HEIGHT_VALUE;           //no height
457        if(isHeightCalcEnabled() && iInstanceMapTrees.containsKey(pMapId))
458        {
459            Vector3 pos = convertPositionToInternalRep(x,y,z);
460            MapTree* mapTree = iInstanceMapTrees.get(pMapId);
461            height = mapTree->getHeight(pos);
462            if(!(height < inf()))
463            {
464                height = VMAP_INVALID_HEIGHT_VALUE;         //no height
465            }
466#ifdef _VMAP_LOG_DEBUG
467            Command c = Command();
468            c.fillTestHeightCmd(pMapId,Vector3(x,y,z),height);
469            iCommandLogger.appendCmd(c);
470#endif
471        }
472        return(height);
473    }
474
475    //=========================================================
476    /**
477    used for debugging
478    */
479    bool VMapManager::processCommand(char *pCommand)
480    {
481        bool result = false;
482        std::string cmd = std::string(pCommand);
483        if(cmd == "startlog")
484        {
485#ifdef _VMAP_LOG_DEBUG
486
487            iCommandLogger.enableWriting(true);
488#endif
489            result = true;
490        }
491        else if(cmd == "stoplog")
492        {
493#ifdef _VMAP_LOG_DEBUG
494            iCommandLogger.appendCmd(Command());            // Write stop command
495            iCommandLogger.enableWriting(false);
496#endif
497            result = true;
498        }
499        else if(cmd.find_first_of("pos ") == 0)
500        {
501            float x,y,z;
502            sscanf(pCommand, "pos %f,%f,%f",&x,&y,&z);
503#ifdef _VMAP_LOG_DEBUG
504            Command c = Command();
505            c.fillSetPosCmd(convertPositionToInternalRep(x,y,z));
506            iCommandLogger.appendCmd(c);
507            iCommandLogger.enableWriting(false);
508#endif
509            result = true;
510        }
511        return result;
512    }
513
514    //=========================================================
515    //=========================================================
516    //=========================================================
517
518    MapTree::MapTree(const char* pBaseDir)
519    {
520        iBasePath = std::string(pBaseDir);
521        if(iBasePath.length() > 0 && (iBasePath[iBasePath.length()-1] != '/' || iBasePath[iBasePath.length()-1] != '\\'))
522        {
523            iBasePath.append("/");
524        }
525        iTree = new AABSPTree<ModelContainer *>();
526    }
527
528    //=========================================================
529    MapTree::~MapTree()
530    {
531        Array<ModelContainer *> mcArray;
532        iTree->getMembers(mcArray);
533        int no = mcArray.size();
534        while(no >0)
535        {
536            --no;
537            delete mcArray[no];
538        }
539        delete iTree;
540    }
541    //=========================================================
542
543    // just for visual debugging with an external debug class
544    #ifdef _DEBUG_VMAPS
545    #ifndef gBoxArray
546    extern Vector3 p1,p2,p3,p4,p5,p6,p7;
547    extern Array<AABox>gBoxArray;
548    extern int gCount1, gCount2, gCount3, gCount4;
549    extern bool myfound;
550    #endif
551    #endif
552
553    //=========================================================
554    /**
555    return dist to hit or inf() if no hit
556    */
557
558    float MapTree::getIntersectionTime(const Ray& pRay, float pMaxDist, bool pStopAtFirstHit)
559    {
560        float firstDistance = inf();
561        IntersectionCallBack<ModelContainer> intersectionCallBack;
562        float t = pMaxDist;
563        iTree->intersectRay(pRay, intersectionCallBack, t, pStopAtFirstHit, false);
564#ifdef _DEBUG_VMAPS
565        {
566            if(t < pMaxDist)
567            {
568                myfound = true;
569                p4 = pRay.origin + pRay.direction*t;
570            }
571        }
572#endif
573        if(t > 0 && t < inf() && pMaxDist > t)
574        {
575            firstDistance = t;
576        }
577        return firstDistance;
578    }
579    //=========================================================
580
581    bool MapTree::isInLineOfSight(const Vector3& pos1, const Vector3& pos2)
582    {
583        bool result = true;
584        float maxDist = abs((pos2 - pos1).magnitude());
585                                                            // direction with length of 1
586        Ray ray = Ray::fromOriginAndDirection(pos1, (pos2 - pos1)/maxDist);
587        float resultDist = getIntersectionTime(ray, maxDist, true);
588        if(resultDist < maxDist)
589        {
590            result = false;
591        }
592        return result;
593    }
594    //=========================================================
595    /**
596    When moving from pos1 to pos2 check if we hit an object. Return true and the position if we hit one
597    Return the hit pos or the original dest pos
598    */
599
600    bool MapTree::getObjectHitPos(const Vector3& pPos1, const Vector3& pPos2, Vector3& pResultHitPos, float pModifyDist)
601    {
602        bool result;
603        float maxDist = abs((pPos2 - pPos1).magnitude());
604        Vector3 dir = (pPos2 - pPos1)/maxDist;              // direction with length of 1
605        Ray ray = Ray::fromOriginAndDirection(pPos1, dir);
606        float dist = getIntersectionTime(ray, maxDist, false);
607        if(dist < maxDist)
608        {
609            pResultHitPos = pPos1 + dir * dist;
610            if(pModifyDist < 0)
611            {
612                if(abs((pResultHitPos - pPos1).magnitude()) > -pModifyDist)
613                {
614                    pResultHitPos = pResultHitPos + dir*pModifyDist;
615                }
616                else
617                {
618                    pResultHitPos = pPos1;
619                }
620            }
621            else
622            {
623                pResultHitPos = pResultHitPos + dir*pModifyDist;
624            }
625            result = true;
626        }
627        else
628        {
629            pResultHitPos = pPos2;
630            result = false;
631        }
632        return result;
633    }
634
635    //=========================================================
636
637    float MapTree::getHeight(const Vector3& pPos)
638    {
639        float height = inf();
640        Vector3 dir = Vector3(0,-1,0);
641        Ray ray = Ray::fromOriginAndDirection(pPos, dir);   // direction with length of 1
642        float maxDist = VMapDefinitions::getMaxCanFallDistance();
643        float dist = getIntersectionTime(ray, maxDist, false);
644        if(dist < inf())
645        {
646            height = (pPos + dir * dist).y;
647        }
648        return(height);
649    }
650
651    //=========================================================
652
653    bool MapTree::PrepareTree()
654    {
655        iTree->balance();
656        return true;
657    }
658
659    bool MapTree::loadMap(const std::string& pDirFileName, unsigned int pMapTileIdent)
660    {
661        bool result = true;
662        size_t len = iBasePath.length() + pDirFileName.length();
663        char *filenameBuffer = new char[len+1];
664        if(!hasDirFile(pDirFileName))
665        {
666            FilesInDir filesInDir;
667            result = false;
668            sprintf(filenameBuffer, "%s%s", iBasePath.c_str(), pDirFileName.c_str());
669            FILE* df = fopen(filenameBuffer, "rb");
670            if(df)
671            {
672                char lineBuffer[FILENAMEBUFFER_SIZE];
673                result = true;
674                bool newModelLoaded = false;
675                while(result && (fgets(lineBuffer, FILENAMEBUFFER_SIZE-1, df) != 0))
676                {
677                    std::string name = std::string(lineBuffer);
678                    chomp(name);
679                    if(name.length() >1)
680                    {
681                        filesInDir.append(name);
682                        ManagedModelContainer *mc;
683                        if(!isAlreadyLoaded(name))
684                        {
685                            std::string fname = iBasePath;
686                            fname.append(name);
687                            mc = new ManagedModelContainer();
688                            result = mc->readFile(fname.c_str());
689                            if(result)
690                            {
691                                addModelContainer(name, mc);
692                                newModelLoaded = true;
693                            }
694                        }
695                        else
696                        {
697                            mc = getModelContainer(name);
698                        }
699                        mc->incRefCount();
700                    }
701                }
702                if(result && newModelLoaded)
703                {
704                    iTree->balance();
705                }
706                if(result && ferror(df) != 0)
707                {
708                    result = false;
709                }
710                fclose(df);
711                if(result)
712                {
713                    filesInDir.incRefCount();
714                    addDirFile(pDirFileName, filesInDir);
715                    setLoadedMapTile(pMapTileIdent);
716                }
717            }
718        }
719        else
720        {
721            // Already loaded, so just inc. the ref count if mapTileIdent is new
722            if(!containsLoadedMapTile(pMapTileIdent))
723            {
724                setLoadedMapTile(pMapTileIdent);
725                FilesInDir& filesInDir = getDirFiles(pDirFileName);
726                filesInDir.incRefCount();
727            }
728        }
729        delete [] filenameBuffer;
730        return (result);
731    }
732
733    //=========================================================
734
735    void MapTree::unloadMap(const std::string& dirFileName, unsigned int pMapTileIdent, bool pForce)
736    {
737        if(hasDirFile(dirFileName) && (pForce || containsLoadedMapTile(pMapTileIdent)))
738        {
739            if(containsLoadedMapTile(pMapTileIdent))
740                removeLoadedMapTile(pMapTileIdent);
741            FilesInDir& filesInDir = getDirFiles(dirFileName);
742            filesInDir.decRefCount();
743            if(filesInDir.getRefCount() <= 0)
744            {
745                Array<std::string> fileNames = filesInDir.getFiles();
746                bool treeChanged = false;
747                for(int i=0; i<fileNames.size(); ++i)
748                {
749                    std::string name = fileNames[i];
750                    ManagedModelContainer* mc = getModelContainer(name);
751                    mc->decRefCount();
752                    if(mc->getRefCount() <= 0)
753                    {
754                        iLoadedModelContainer.remove(name);
755                        iTree->remove(mc);
756                        delete mc;
757                        treeChanged = true;
758                    }
759                }
760                iLoadedDirFiles.remove(dirFileName);
761                if(treeChanged)
762                {
763                    iTree->balance();
764                }
765            }
766        }
767    }
768
769    //=========================================================
770    //=========================================================
771
772    void MapTree::addModelContainer(const std::string& pName, ManagedModelContainer *pMc)
773    {
774        iLoadedModelContainer.set(pName, pMc);
775        iTree->insert(pMc);
776    }
777    //=========================================================
778    //=========================================================
779    //=========================================================
780}
Note: See TracBrowser for help on using the browser.