Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: orxonox.OLD/branches/cr/src/world_entities/world_entity.cc @ 8183

Last change on this file since 8183 was 8183, checked in by patrick, 18 years ago

cr: work flush

File size: 20.6 KB
Line 
1
2
3/*
4   orxonox - the future of 3D-vertical-scrollers
5
6   Copyright (C) 2004 orx
7
8   This program is free software; you can redistribute it and/or modify
9   it under the terms of the GNU General Public License as published by
10   the Free Software Foundation; either version 2, or (at your option)
11   any later version.
12
13   ### File Specific:
14   main-programmer: Patrick Boenzli
15   co-programmer: Christian Meyer
16*/
17#define DEBUG_SPECIAL_MODULE DEBUG_MODULE_WORLD_ENTITY
18
19#include "world_entity.h"
20#include "shell_command.h"
21
22#include "model.h"
23#include "md2Model.h"
24#include "util/loading/resource_manager.h"
25#include "util/loading/load_param.h"
26#include "vector.h"
27#include "obb_tree.h"
28
29#include "glgui_bar.h"
30
31#include "state.h"
32#include "camera.h"
33
34#include "collision_handle.h"
35#include "collision_event.h"
36
37#include <stdarg.h>
38
39
40using namespace std;
41
42SHELL_COMMAND(model, WorldEntity, loadModel)
43->describe("sets the Model of the WorldEntity")
44->defaultValues("models/ships/fighter.obj", 1.0f);
45
46SHELL_COMMAND(debugEntity, WorldEntity, debugWE);
47
48/**
49 *  Loads the WordEntity-specific Part of any derived Class
50 *
51 * @param root: Normally NULL, as the Derived Entities define a loadParams Function themeselves,
52 *              that can calls WorldEntities loadParams for itself.
53 */
54WorldEntity::WorldEntity()
55    : Synchronizeable()
56{
57  this->setClassID(CL_WORLD_ENTITY, "WorldEntity");
58
59  this->obbTree = NULL;
60  this->healthWidget = NULL;
61  this->healthMax = 1.0f;
62  this->health = 1.0f;
63  this->damage = 0.0f; // no damage dealt by a default entity
64  this->scaling = 1.0f;
65
66  /* OSOLETE */
67  this->bVisible = true;
68  this->bCollide = true;
69
70  this->objectListNumber = OM_INIT;
71  this->objectListIterator = NULL;
72
73  // reset all collision handles to NULL == unsubscribed state
74  for(int i = 0; i < CREngine::CR_NUMBER; ++i)
75    this->collisionHandles[i] = NULL;
76  this->bReactive = false;
77
78  // registering default reactions:
79  this->subscribeReaction(CREngine::CR_OBJECT_DAMAGE, CL_WORLD_ENTITY);
80  this->subscribeReaction(CREngine::CR_PHYSICS_GROUND, CL_TERRAIN);
81
82  this->toList(OM_NULL);
83}
84
85/**
86 *  standard destructor
87*/
88WorldEntity::~WorldEntity ()
89{
90  State::getObjectManager()->toList(this, OM_INIT);
91
92  // Delete the model (unregister it with the ResourceManager)
93  for (unsigned int i = 0; i < this->models.size(); i++)
94    this->setModel(NULL, i);
95
96  // Delete the obbTree
97  if( this->obbTree != NULL)
98    delete this->obbTree;
99
100  if (this->healthWidget != NULL)
101    delete this->healthWidget;
102
103  this->unsubscribeReaction();
104}
105
106/**
107 * loads the WorldEntity Specific Parameters.
108 * @param root: the XML-Element to load the Data From
109 */
110void WorldEntity::loadParams(const TiXmlElement* root)
111{
112  // Do the PNode loading stuff
113  PNode::loadParams(root);
114
115  LoadParam(root, "md2texture", this, WorldEntity, loadMD2Texture)
116  .describe("the fileName of the texture, that should be loaded onto this world-entity. (must be relative to the data-dir)")
117  .defaultValues("");
118
119  // Model Loading
120  LoadParam(root, "model", this, WorldEntity, loadModel)
121  .describe("the fileName of the model, that should be loaded onto this world-entity. (must be relative to the data-dir)")
122  .defaultValues("", 1.0f, 0);
123
124  LoadParam(root, "maxHealth", this, WorldEntity, setHealthMax)
125  .describe("The Maximum health that can be loaded onto this entity")
126  .defaultValues(1.0f);
127
128  LoadParam(root, "health", this, WorldEntity, setHealth)
129  .describe("The Health the WorldEntity has at this moment")
130  .defaultValues(1.0f);
131}
132
133
134/**
135 * loads a Model onto a WorldEntity
136 * @param fileName the name of the model to load
137 * @param scaling the Scaling of the model
138 *
139 * FIXME
140 * @todo: separate the obb tree generation from the model
141 */
142void WorldEntity::loadModel(const std::string& fileName, float scaling, unsigned int modelNumber, unsigned int obbTreeDepth)
143{
144  this->modelLODName = fileName;
145  this->scaling = scaling;
146  if (!fileName.empty())
147  {
148    // search for the special character # in the LoadParam
149    if (fileName.find('#') != std::string::npos)
150    {
151      PRINTF(4)("Found # in %s... searching for LOD's\n", fileName.c_str());
152      std::string lodFile = fileName;
153      unsigned int offset = lodFile.find('#');
154      for (unsigned int i = 0; i < 3; i++)
155      {
156        lodFile[offset] = 48+(int)i;
157        if (ResourceManager::isInDataDir(lodFile))
158          this->loadModel(lodFile, scaling, i);
159      }
160      return;
161    }
162    if (this->scaling <= 0.0)
163    {
164      PRINTF(1)("YOU GAVE ME A CRAPY SCALE resetting to 1.0\n");
165      this->scaling = 1.0;
166    }
167    if(fileName.find(".obj") != std::string::npos)
168    {
169      PRINTF(4)("fetching OBJ file: %s\n", fileName.c_str());
170      BaseObject* loadedModel = ResourceManager::getInstance()->load(fileName, OBJ, RP_CAMPAIGN, this->scaling);
171      if (loadedModel != NULL)
172        this->setModel(dynamic_cast<Model*>(loadedModel), modelNumber);
173      else
174        PRINTF(1)("OBJ-File %s not found.\n", fileName.c_str());
175
176      if( modelNumber == 0)
177        this->buildObbTree(obbTreeDepth);
178    }
179    else if(fileName.find(".md2") != std::string::npos)
180    {
181      PRINTF(4)("fetching MD2 file: %s\n", fileName.c_str());
182      Model* m = new MD2Model(fileName, this->md2TextureFileName, this->scaling);
183      //this->setModel((Model*)ResourceManager::getInstance()->load(fileName, MD2, RP_CAMPAIGN), 0);
184      this->setModel(m, 0);
185
186      if( m != NULL)
187        this->buildObbTree(obbTreeDepth);
188    }
189  }
190  else
191  {
192    this->setModel(NULL);
193  }
194}
195
196/**
197 * sets a specific Model for the Object.
198 * @param model The Model to set
199 * @param modelNumber the n'th model in the List to get.
200 */
201void WorldEntity::setModel(Model* model, unsigned int modelNumber)
202{
203  if (this->models.size() <= modelNumber)
204    this->models.resize(modelNumber+1, NULL);
205
206  if (this->models[modelNumber] != NULL)
207  {
208    Resource* resource = ResourceManager::getInstance()->locateResourceByPointer(dynamic_cast<BaseObject*>(this->models[modelNumber]));
209    if (resource != NULL)
210      ResourceManager::getInstance()->unload(resource, RP_LEVEL);
211    else
212    {
213      PRINTF(4)("Forcing model deletion\n");
214      delete this->models[modelNumber];
215    }
216  }
217
218  this->models[modelNumber] = model;
219
220
221  //   if (this->model != NULL)
222  //     this->buildObbTree(4);
223}
224
225
226/**
227 * builds the obb-tree
228 * @param depth the depth to calculate
229 */
230bool WorldEntity::buildObbTree(int depth)
231{
232  if (this->obbTree)
233    delete this->obbTree;
234
235  if (this->models[0] != NULL)
236  {
237    this->obbTree = new OBBTree(depth, models[0]->getModelInfo(), this);
238    return true;
239  }
240  else
241  {
242    PRINTF(1)("could not create obb-tree, because no model was loaded yet\n");
243    this->obbTree = NULL;
244    return false;
245  }
246}
247
248
249/**
250 * subscribes this world entity to a collision reaction
251 *  @param type the type of reaction to subscribe to
252 *  @param target1 a filter target (classID)
253 */
254void WorldEntity::subscribeReaction(CREngine::CRType type, long target1)
255{
256  this->subscribeReaction(type);
257
258  // add the target filter
259  this->collisionHandles[type]->addTarget(target1);
260}
261
262
263/**
264 * subscribes this world entity to a collision reaction
265 *  @param type the type of reaction to subscribe to
266 *  @param target1 a filter target (classID)
267 */
268void WorldEntity::subscribeReaction(CREngine::CRType type, long target1, long target2)
269{
270  this->subscribeReaction(type);
271
272  // add the target filter
273  this->collisionHandles[type]->addTarget(target1);
274  this->collisionHandles[type]->addTarget(target2);
275}
276
277
278/**
279 * subscribes this world entity to a collision reaction
280 *  @param type the type of reaction to subscribe to
281 *  @param target1 a filter target (classID)
282 */
283void WorldEntity::subscribeReaction(CREngine::CRType type, long target1, long target2, long target3)
284{
285  this->subscribeReaction(type);
286
287  // add the target filter
288  this->collisionHandles[type]->addTarget(target1);
289  this->collisionHandles[type]->addTarget(target2);
290  this->collisionHandles[type]->addTarget(target3);
291}
292
293
294/**
295 * subscribes this world entity to a collision reaction
296 *  @param type the type of reaction to subscribe to
297 *  @param target1 a filter target (classID)
298 */
299void WorldEntity::subscribeReaction(CREngine::CRType type, long target1, long target2, long target3, long target4)
300{
301  this->subscribeReaction(type);
302
303  // add the target filter
304  this->collisionHandles[type]->addTarget(target1);
305  this->collisionHandles[type]->addTarget(target2);
306  this->collisionHandles[type]->addTarget(target3);
307  this->collisionHandles[type]->addTarget(target4);
308}
309
310
311/**
312 * subscribes this world entity to a collision reaction
313 *  @param type the type of reaction to subscribe to
314 *  @param nrOfTargets number of target filters
315 *  @param ... the targets as classIDs
316 */
317void WorldEntity::subscribeReaction(CREngine::CRType type)
318{
319  if( this->collisionHandles[type] != NULL)  {
320    PRINTF(2)("Registering for a CollisionReaction already subscribed to! Skipping\n");
321    return;
322  }
323
324  this->collisionHandles[type] = CREngine::getInstance()->subscribeReaction(this, type);
325
326  // now there is at least one collision reaction subscribed
327  this->bReactive = true;
328}
329
330
331/**
332 * unsubscribes a specific reaction from the worldentity
333 *  @param type the reaction to unsubscribe
334 */
335void WorldEntity::unsubscribeReaction(CREngine::CRType type)
336{
337  if( this->collisionHandles[type] == NULL)
338    return;
339
340  CREngine::getInstance()->unsubscribeReaction(this->collisionHandles[type]);
341  this->collisionHandles[type] = NULL;
342
343  // check if there is still any handler registered
344  for(int i = 0; i < CREngine::CR_NUMBER; ++i)
345  {
346    if( this->collisionHandles[i] != NULL)
347    {
348      this->bReactive = true;
349      return;
350    }
351  }
352  this->bReactive = false;
353}
354
355
356/**
357 * unsubscribes all collision reactions
358 */
359void WorldEntity::unsubscribeReaction()
360{
361  for( int i = 0; i < CREngine::CR_NUMBER; i++)
362    this->unsubscribeReaction((CREngine::CRType)i);
363
364  // there are no reactions subscribed from now on
365  this->bReactive = false;
366}
367
368
369/**
370 * registers a new collision event to this world entity
371 *  @param entityA entity of the collision
372 *  @param entityB entity of the collision
373 *  @param bvA colliding bounding volume of entityA
374 *  @param bvB colliding bounding volume of entityA
375 */
376bool WorldEntity::registerCollision(WorldEntity* entityA, WorldEntity* entityB, BoundingVolume* bvA, BoundingVolume* bvB)
377{
378  // is there any handler listening?
379  if( !this->bReactive)
380    return false;
381
382  // get a collision event
383  CollisionEvent* c = CREngine::getInstance()->popCollisionEventObject();
384  assert(c != NULL); // if this should fail: we got not enough precached CollisionEvents: alter value in cr_defs.h
385  c->collide(entityA, entityB, bvA, bvB);
386
387  for( int i = 0; i < CREngine::CR_NUMBER; ++i)
388    if( this->collisionHandles[i] != NULL)
389      this->collisionHandles[i]->registerCollisionEvent(c);
390}
391
392
393/**
394 * registers a new collision event to this woeld entity
395 *  @param entity the entity that collides
396 *  @param plane it stands on
397 *  @param position it collides on the plane
398 */
399bool WorldEntity::registerCollision(WorldEntity* entity, Plane* plane, Vector position)
400{
401  // is there any handler listening?
402  if( !this->bReactive)
403    return false;
404
405  // get a collision event
406  CollisionEvent* c = CREngine::getInstance()->popCollisionEventObject();
407  assert(c != NULL); // if this should fail: we got not enough precached CollisionEvents: alter value in cr_defs.h
408  c->collide(entity, plane, position);
409
410  for( int i = 0; i < CREngine::CR_NUMBER; ++i)
411    if( this->collisionHandles[i] != NULL)
412      this->collisionHandles[i]->registerCollisionEvent(c);
413}
414
415
416/**
417 * @brief moves this entity to the List OM_List
418 * @param list the list to set this Entity to.
419 *
420 * this is the same as a call to State::getObjectManager()->toList(entity , list);
421 * directly, but with an easier interface.
422 *
423 * @todo inline this (peut etre)
424 */
425void WorldEntity::toList(OM_LIST list)
426{
427  State::getObjectManager()->toList(this, list);
428}
429
430
431
432/**
433 * sets the character attributes of a worldentity
434 * @param character attributes
435 *
436 * these attributes don't have to be set, only use them, if you need them
437*/
438//void WorldEntity::setCharacterAttributes(CharacterAttributes* charAttr)
439//{}
440
441
442/**
443 *  this function is called, when two entities collide
444 * @param entity: the world entity with whom it collides
445 *
446 * Implement behaviour like damage application or other miscellaneous collision stuff in this function
447 */
448void WorldEntity::collidesWith(WorldEntity* entity, const Vector& location)
449{
450  /**
451   * THIS IS A DEFAULT COLLISION-Effect.
452   * IF YOU WANT TO CREATE A SPECIFIC COLLISION ON EACH OBJECT
453   * USE::
454   * if (entity->isA(CL_WHAT_YOU_ARE_LOOKING_FOR)) { printf "dothings"; };
455   *
456   * You can always define a default Action.... don't be affraid just test it :)
457   */
458  //  PRINTF(3)("collision %s vs %s @ (%f,%f,%f)\n", this->getClassName(), entity->getClassName(), location.x, location.y, location.z);
459}
460
461
462/**
463 *  this is called immediately after the Entity has been constructed, initialized and then Spawned into the World
464 *
465 */
466void WorldEntity::postSpawn ()
467{}
468
469
470/**
471 *  this method is called by the world if the WorldEntity leaves the game
472 */
473void WorldEntity::leaveWorld ()
474{}
475
476
477/**
478 * resets the WorldEntity to its initial values. eg. used for multiplayer games: respawning
479 */
480void WorldEntity::reset()
481{}
482
483/**
484 *  this method is called every frame
485 * @param time: the time in seconds that has passed since the last tick
486 *
487 * Handle all stuff that should update with time inside this method (movement, animation, etc.)
488*/
489void WorldEntity::tick(float time)
490{}
491
492
493/**
494 *  the entity is drawn onto the screen with this function
495 *
496 * This is a central function of an entity: call it to let the entity painted to the screen.
497 * Just override this function with whatever you want to be drawn.
498*/
499void WorldEntity::draw() const
500{
501  //PRINTF(0)("(%s::%s)\n", this->getClassName(), this->getName());
502  //  assert(!unlikely(this->models.empty()));
503  {
504    glMatrixMode(GL_MODELVIEW);
505    glPushMatrix();
506
507    /* translate */
508    glTranslatef (this->getAbsCoor ().x,
509                  this->getAbsCoor ().y,
510                  this->getAbsCoor ().z);
511    Vector tmpRot = this->getAbsDir().getSpacialAxis();
512    glRotatef (this->getAbsDir().getSpacialAxisAngle(), tmpRot.x, tmpRot.y, tmpRot.z );
513
514
515    // This Draws the LOD's
516    float cameraDistance = State::getCamera()->distance(this);
517    if (cameraDistance > 30 && this->models.size() >= 3 && this->models[2] != NULL)
518    {
519      this->models[2]->draw();
520    }
521    else if (cameraDistance > 10 && this->models.size() >= 2 && this->models[1] != NULL)
522    {
523      this->models[1]->draw();
524    }
525    else if (this->models.size() >= 1 && this->models[0] != NULL)
526    {
527      this->models[0]->draw();
528    }
529    glPopMatrix();
530  }
531}
532
533/**
534 * @param health the Health to add.
535 * @returns the health left (this->healthMax - health+this->health)
536 */
537float WorldEntity::increaseHealth(float health)
538{
539  this->health += health;
540  if (this->health > this->healthMax)
541  {
542    float retHealth = this->healthMax - this->health;
543    this->health = this->healthMax;
544    this->updateHealthWidget();
545    return retHealth;
546  }
547  this->updateHealthWidget();
548  return 0.0;
549}
550
551/**
552 * @param health the Health to be removed
553 * @returns 0.0 or the rest, that was not substracted (bellow 0.0)
554 */
555float WorldEntity::decreaseHealth(float health)
556{
557  this->health -= health;
558
559  if (this->health < 0)
560  {
561    float retHealth = -this->health;
562    this->health = 0.0f;
563    this->updateHealthWidget();
564    return retHealth;
565  }
566  this->updateHealthWidget();
567  return 0.0;
568
569}
570
571/**
572 * @param maxHealth the maximal health that can be loaded onto the entity.
573 */
574void WorldEntity::setHealthMax(float healthMax)
575{
576  this->healthMax = healthMax;
577  if (this->health > this->healthMax)
578  {
579    PRINTF(3)("new maxHealth is bigger as the old health. Did you really intend to do this for (%s::%s)\n", this->getClassName(), this->getName());
580    this->health = this->healthMax;
581  }
582  this->updateHealthWidget();
583}
584
585/**
586 * @brief creates the HealthWidget
587 *
588 * since not all entities need an HealthWidget, it is only created on request.
589 */
590void WorldEntity::createHealthWidget()
591{
592  if (this->healthWidget == NULL)
593  {
594    this->healthWidget = new OrxGui::GLGuiBar();
595    this->healthWidget->setSize2D(30,400);
596    this->healthWidget->setAbsCoor2D(10,100);
597
598    this->updateHealthWidget();
599  }
600  else
601    PRINTF(3)("Allready created the HealthWidget for %s::%s\n", this->getClassName(), this->getName());
602}
603
604void WorldEntity::increaseHealthMax(float increaseHealth)
605{
606  this->healthMax += increaseHealth;
607  this->updateHealthWidget();
608}
609
610
611OrxGui::GLGuiWidget* WorldEntity::getHealthWidget()
612{
613  this->createHealthWidget();
614  return this->healthWidget;
615}
616
617/**
618 * @param visibility shows or hides the health-bar
619 * (creates the widget if needed)
620 */
621void WorldEntity::setHealthWidgetVisibilit(bool visibility)
622{
623  if (visibility)
624  {
625    if (this->healthWidget != NULL)
626      this->healthWidget->show();
627    else
628    {
629      this->createHealthWidget();
630      this->updateHealthWidget();
631      this->healthWidget->show();
632    }
633  }
634  else if (this->healthWidget != NULL)
635    this->healthWidget->hide();
636}
637
638/**
639 * @brief updates the HealthWidget
640 */
641void WorldEntity::updateHealthWidget()
642{
643  if (this->healthWidget != NULL)
644  {
645    this->healthWidget->setMaximum(this->healthMax);
646    this->healthWidget->setValue(this->health);
647  }
648}
649
650
651/**
652 * DEBUG-DRAW OF THE BV-Tree.
653 * @param depth What depth to draw
654 * @param drawMode the mode to draw this entity under
655 */
656void WorldEntity::drawBVTree(int depth, int drawMode) const
657{
658  glMatrixMode(GL_MODELVIEW);
659  glPushMatrix();
660  /* translate */
661  glTranslatef (this->getAbsCoor ().x,
662                this->getAbsCoor ().y,
663                this->getAbsCoor ().z);
664  /* rotate */
665  Vector tmpRot = this->getAbsDir().getSpacialAxis();
666  glRotatef (this->getAbsDir().getSpacialAxisAngle(), tmpRot.x, tmpRot.y, tmpRot.z );
667
668
669  if (this->obbTree)
670    this->obbTree->drawBV(depth, drawMode);
671
672
673  glPopMatrix();
674}
675
676
677/**
678 * Debug the WorldEntity
679 */
680void WorldEntity::debugEntity() const
681{
682  PRINT(0)("WorldEntity %s::%s  (DEBUG)\n", this->getClassName(), this->getName());
683  this->debugNode();
684  PRINT(0)("List: %s ; ModelCount %d - ", ObjectManager::OMListToString(this->objectListNumber) , this->models.size());
685  for (unsigned int i = 0; i < this->models.size(); i++)
686  {
687    if (models[i] != NULL)
688      PRINT(0)(" : %d:%s", i, this->models[i]->getName());
689  }
690  PRINT(0)("\n");
691
692}
693
694
695
696
697/********************************************************************************************
698 NETWORK STUFF
699 ********************************************************************************************/
700
701
702/**
703 * Writes data from network containing information about the state
704 * @param data pointer to data
705 * @param length length of data
706 * @param sender hostID of sender
707 */
708int WorldEntity::writeState( const byte * data, int length, int sender )
709{
710  std::string modelFileName;
711  SYNCHELP_READ_BEGIN();
712
713  SYNCHELP_READ_FKT( PNode::writeState, NWT_WE_PN_WRITESTATE );
714
715  SYNCHELP_READ_STRING( modelFileName, NWT_WE_PN_MODELFILENAME );
716  SYNCHELP_READ_FLOAT( scaling, NWT_WE_PN_SCALING );
717  //check if modelFileName is relative to datadir or absolute
718
719
720  PRINTF(0)("================ LOADING MODEL %s, %f\n", modelFileName.c_str(), scaling);
721
722  if ( modelFileName != "" )
723  {
724    loadModel( modelFileName, scaling);
725    PRINTF(0)("modelfilename: %s\n", getModel( 0 )->getName());
726  }
727
728  /*SYNCHELP_READ_STRINGM( modelFileName );
729
730  if ( strcmp(modelFileName, "") )
731    if ( strstr(modelFileName, ResourceManager::getInstance()->getDataDir()) )
732    {
733      this->md2TextureFileName = new char[strlen(modelFileName)-strlen(ResourceManager::getInstance()->getDataDir())+1];
734      strcpy((char*)this->md2TextureFileName, modelFileName+strlen(ResourceManager::getInstance()->getDataDir()));
735    }
736    else
737    {
738      this->md2TextureFileName = modelFileName;
739    }
740  */
741
742  return SYNCHELP_READ_N;
743}
744
745
746/**
747 * data copied in data will bee sent to another host
748 * @param data pointer to data
749 * @param maxLength max length of data
750 * @return the number of bytes writen
751 */
752int WorldEntity::readState( byte * data, int maxLength )
753{
754  SYNCHELP_WRITE_BEGIN();
755
756  SYNCHELP_WRITE_FKT( PNode::readState, NWT_WE_PN_WRITESTATE );
757
758  if ( getModel(0) && getModel(0)->getName() != "" )
759  {
760    std::string name = getModel( 0 )->getName();
761
762    if (  name.find( ResourceManager::getInstance()->getDataDir() ) == 0 )
763    {
764      name.erase(ResourceManager::getInstance()->getDataDir().size());
765    }
766
767    SYNCHELP_WRITE_STRING( name, NWT_WE_PN_MODELFILENAME );
768  }
769  else
770  {
771    SYNCHELP_WRITE_STRING("", NWT_WE_PN_MODELFILENAME);
772  }
773
774  SYNCHELP_WRITE_FLOAT( scaling, NWT_WE_PN_SCALING );
775  /*if ( this->md2TextureFileName!=NULL && strcmp(this->md2TextureFileName, "") )
776  {
777    SYNCHELP_WRITE_STRING(this->md2TextureFileName);
778  }
779  else
780  {
781    SYNCHELP_WRITE_STRING("");
782  }*/
783
784  return SYNCHELP_WRITE_N;
785}
Note: See TracBrowser for help on using the repository browser.