Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

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

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

cr: extended the collisionevent and added the interface to the WE

File size: 20.5 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 collisionEvent the event
372 */
373bool WorldEntity::registerCollision(WorldEntity* entityA, WorldEntity* entityB, BoundingVolume* bvA, BoundingVolume* bvB)
374{
375  // is there any handler listening?
376  if( !this->bReactive)
377    return false;
378
379  // create a collision event
380  CollisionEvent* c = CREngine::getInstance()->popCollisionEventObject();
381  assert(c != NULL); // if this should fail: we got not enough precached CollisionEvents: alter value in cr_defs.h
382  c->collide(entityA, entityB, bvA, bvB);
383
384  for( int i = 0; i < CREngine::CR_NUMBER; ++i)
385    if( this->collisionHandles[i] != NULL)
386      this->collisionHandles[i]->registerCollisionEvent(c);
387}
388
389
390/**
391 * registers a new collision event to this woeld entity
392 *  @param entity the entity that collides
393 *  @param plane it stands on
394 *  @param position it collides on the plane
395 */
396bool WorldEntity::registerCollision(WorldEntity* entity, Plane* plane, Vector position)
397{
398  // is there any handler listening?
399  if( !this->bReactive)
400    return false;
401
402  // create a collision event
403  CollisionEvent* c = CREngine::getInstance()->popCollisionEventObject();
404  assert(c != NULL); // if this should fail: we got not enough precached CollisionEvents: alter value in cr_defs.h
405  c->collide(entity, plane, position);
406
407  for( int i = 0; i < CREngine::CR_NUMBER; ++i)
408    if( this->collisionHandles[i] != NULL)
409      this->collisionHandles[i]->registerCollisionEvent(c);
410}
411
412
413/**
414 * @brief moves this entity to the List OM_List
415 * @param list the list to set this Entity to.
416 *
417 * this is the same as a call to State::getObjectManager()->toList(entity , list);
418 * directly, but with an easier interface.
419 *
420 * @todo inline this (peut etre)
421 */
422void WorldEntity::toList(OM_LIST list)
423{
424  State::getObjectManager()->toList(this, list);
425}
426
427
428
429/**
430 * sets the character attributes of a worldentity
431 * @param character attributes
432 *
433 * these attributes don't have to be set, only use them, if you need them
434*/
435//void WorldEntity::setCharacterAttributes(CharacterAttributes* charAttr)
436//{}
437
438
439/**
440 *  this function is called, when two entities collide
441 * @param entity: the world entity with whom it collides
442 *
443 * Implement behaviour like damage application or other miscellaneous collision stuff in this function
444 */
445void WorldEntity::collidesWith(WorldEntity* entity, const Vector& location)
446{
447  /**
448   * THIS IS A DEFAULT COLLISION-Effect.
449   * IF YOU WANT TO CREATE A SPECIFIC COLLISION ON EACH OBJECT
450   * USE::
451   * if (entity->isA(CL_WHAT_YOU_ARE_LOOKING_FOR)) { printf "dothings"; };
452   *
453   * You can always define a default Action.... don't be affraid just test it :)
454   */
455  //  PRINTF(3)("collision %s vs %s @ (%f,%f,%f)\n", this->getClassName(), entity->getClassName(), location.x, location.y, location.z);
456}
457
458
459/**
460 *  this is called immediately after the Entity has been constructed, initialized and then Spawned into the World
461 *
462 */
463void WorldEntity::postSpawn ()
464{}
465
466
467/**
468 *  this method is called by the world if the WorldEntity leaves the game
469 */
470void WorldEntity::leaveWorld ()
471{}
472
473
474/**
475 * resets the WorldEntity to its initial values. eg. used for multiplayer games: respawning
476 */
477void WorldEntity::reset()
478{}
479
480/**
481 *  this method is called every frame
482 * @param time: the time in seconds that has passed since the last tick
483 *
484 * Handle all stuff that should update with time inside this method (movement, animation, etc.)
485*/
486void WorldEntity::tick(float time)
487{}
488
489
490/**
491 *  the entity is drawn onto the screen with this function
492 *
493 * This is a central function of an entity: call it to let the entity painted to the screen.
494 * Just override this function with whatever you want to be drawn.
495*/
496void WorldEntity::draw() const
497{
498  //PRINTF(0)("(%s::%s)\n", this->getClassName(), this->getName());
499  //  assert(!unlikely(this->models.empty()));
500  {
501    glMatrixMode(GL_MODELVIEW);
502    glPushMatrix();
503
504    /* translate */
505    glTranslatef (this->getAbsCoor ().x,
506                  this->getAbsCoor ().y,
507                  this->getAbsCoor ().z);
508    Vector tmpRot = this->getAbsDir().getSpacialAxis();
509    glRotatef (this->getAbsDir().getSpacialAxisAngle(), tmpRot.x, tmpRot.y, tmpRot.z );
510
511
512    // This Draws the LOD's
513    float cameraDistance = State::getCamera()->distance(this);
514    if (cameraDistance > 30 && this->models.size() >= 3 && this->models[2] != NULL)
515    {
516      this->models[2]->draw();
517    }
518    else if (cameraDistance > 10 && this->models.size() >= 2 && this->models[1] != NULL)
519    {
520      this->models[1]->draw();
521    }
522    else if (this->models.size() >= 1 && this->models[0] != NULL)
523    {
524      this->models[0]->draw();
525    }
526    glPopMatrix();
527  }
528}
529
530/**
531 * @param health the Health to add.
532 * @returns the health left (this->healthMax - health+this->health)
533 */
534float WorldEntity::increaseHealth(float health)
535{
536  this->health += health;
537  if (this->health > this->healthMax)
538  {
539    float retHealth = this->healthMax - this->health;
540    this->health = this->healthMax;
541    this->updateHealthWidget();
542    return retHealth;
543  }
544  this->updateHealthWidget();
545  return 0.0;
546}
547
548/**
549 * @param health the Health to be removed
550 * @returns 0.0 or the rest, that was not substracted (bellow 0.0)
551 */
552float WorldEntity::decreaseHealth(float health)
553{
554  this->health -= health;
555
556  if (this->health < 0)
557  {
558    float retHealth = -this->health;
559    this->health = 0.0f;
560    this->updateHealthWidget();
561    return retHealth;
562  }
563  this->updateHealthWidget();
564  return 0.0;
565
566}
567
568/**
569 * @param maxHealth the maximal health that can be loaded onto the entity.
570 */
571void WorldEntity::setHealthMax(float healthMax)
572{
573  this->healthMax = healthMax;
574  if (this->health > this->healthMax)
575  {
576    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());
577    this->health = this->healthMax;
578  }
579  this->updateHealthWidget();
580}
581
582/**
583 * @brief creates the HealthWidget
584 *
585 * since not all entities need an HealthWidget, it is only created on request.
586 */
587void WorldEntity::createHealthWidget()
588{
589  if (this->healthWidget == NULL)
590  {
591    this->healthWidget = new OrxGui::GLGuiBar();
592    this->healthWidget->setSize2D(30,400);
593    this->healthWidget->setAbsCoor2D(10,100);
594
595    this->updateHealthWidget();
596  }
597  else
598    PRINTF(3)("Allready created the HealthWidget for %s::%s\n", this->getClassName(), this->getName());
599}
600
601void WorldEntity::increaseHealthMax(float increaseHealth)
602{
603  this->healthMax += increaseHealth;
604  this->updateHealthWidget();
605}
606
607
608OrxGui::GLGuiWidget* WorldEntity::getHealthWidget()
609{
610  this->createHealthWidget();
611  return this->healthWidget;
612}
613
614/**
615 * @param visibility shows or hides the health-bar
616 * (creates the widget if needed)
617 */
618void WorldEntity::setHealthWidgetVisibilit(bool visibility)
619{
620  if (visibility)
621  {
622    if (this->healthWidget != NULL)
623      this->healthWidget->show();
624    else
625    {
626      this->createHealthWidget();
627      this->updateHealthWidget();
628      this->healthWidget->show();
629    }
630  }
631  else if (this->healthWidget != NULL)
632    this->healthWidget->hide();
633}
634
635/**
636 * @brief updates the HealthWidget
637 */
638void WorldEntity::updateHealthWidget()
639{
640  if (this->healthWidget != NULL)
641  {
642    this->healthWidget->setMaximum(this->healthMax);
643    this->healthWidget->setValue(this->health);
644  }
645}
646
647
648/**
649 * DEBUG-DRAW OF THE BV-Tree.
650 * @param depth What depth to draw
651 * @param drawMode the mode to draw this entity under
652 */
653void WorldEntity::drawBVTree(int depth, int drawMode) const
654{
655  glMatrixMode(GL_MODELVIEW);
656  glPushMatrix();
657  /* translate */
658  glTranslatef (this->getAbsCoor ().x,
659                this->getAbsCoor ().y,
660                this->getAbsCoor ().z);
661  /* rotate */
662  Vector tmpRot = this->getAbsDir().getSpacialAxis();
663  glRotatef (this->getAbsDir().getSpacialAxisAngle(), tmpRot.x, tmpRot.y, tmpRot.z );
664
665
666  if (this->obbTree)
667    this->obbTree->drawBV(depth, drawMode);
668
669
670  glPopMatrix();
671}
672
673
674/**
675 * Debug the WorldEntity
676 */
677void WorldEntity::debugEntity() const
678{
679  PRINT(0)("WorldEntity %s::%s  (DEBUG)\n", this->getClassName(), this->getName());
680  this->debugNode();
681  PRINT(0)("List: %s ; ModelCount %d - ", ObjectManager::OMListToString(this->objectListNumber) , this->models.size());
682  for (unsigned int i = 0; i < this->models.size(); i++)
683  {
684    if (models[i] != NULL)
685      PRINT(0)(" : %d:%s", i, this->models[i]->getName());
686  }
687  PRINT(0)("\n");
688
689}
690
691
692
693
694/********************************************************************************************
695 NETWORK STUFF
696 ********************************************************************************************/
697
698
699/**
700 * Writes data from network containing information about the state
701 * @param data pointer to data
702 * @param length length of data
703 * @param sender hostID of sender
704 */
705int WorldEntity::writeState( const byte * data, int length, int sender )
706{
707  std::string modelFileName;
708  SYNCHELP_READ_BEGIN();
709
710  SYNCHELP_READ_FKT( PNode::writeState, NWT_WE_PN_WRITESTATE );
711
712  SYNCHELP_READ_STRING( modelFileName, NWT_WE_PN_MODELFILENAME );
713  SYNCHELP_READ_FLOAT( scaling, NWT_WE_PN_SCALING );
714  //check if modelFileName is relative to datadir or absolute
715
716
717  PRINTF(0)("================ LOADING MODEL %s, %f\n", modelFileName.c_str(), scaling);
718
719  if ( modelFileName != "" )
720  {
721    loadModel( modelFileName, scaling);
722    PRINTF(0)("modelfilename: %s\n", getModel( 0 )->getName());
723  }
724
725  /*SYNCHELP_READ_STRINGM( modelFileName );
726
727  if ( strcmp(modelFileName, "") )
728    if ( strstr(modelFileName, ResourceManager::getInstance()->getDataDir()) )
729    {
730      this->md2TextureFileName = new char[strlen(modelFileName)-strlen(ResourceManager::getInstance()->getDataDir())+1];
731      strcpy((char*)this->md2TextureFileName, modelFileName+strlen(ResourceManager::getInstance()->getDataDir()));
732    }
733    else
734    {
735      this->md2TextureFileName = modelFileName;
736    }
737  */
738
739  return SYNCHELP_READ_N;
740}
741
742
743/**
744 * data copied in data will bee sent to another host
745 * @param data pointer to data
746 * @param maxLength max length of data
747 * @return the number of bytes writen
748 */
749int WorldEntity::readState( byte * data, int maxLength )
750{
751  SYNCHELP_WRITE_BEGIN();
752
753  SYNCHELP_WRITE_FKT( PNode::readState, NWT_WE_PN_WRITESTATE );
754
755  if ( getModel(0) && getModel(0)->getName() != "" )
756  {
757    std::string name = getModel( 0 )->getName();
758
759    if (  name.find( ResourceManager::getInstance()->getDataDir() ) == 0 )
760    {
761      name.erase(ResourceManager::getInstance()->getDataDir().size());
762    }
763
764    SYNCHELP_WRITE_STRING( name, NWT_WE_PN_MODELFILENAME );
765  }
766  else
767  {
768    SYNCHELP_WRITE_STRING("", NWT_WE_PN_MODELFILENAME);
769  }
770
771  SYNCHELP_WRITE_FLOAT( scaling, NWT_WE_PN_SCALING );
772  /*if ( this->md2TextureFileName!=NULL && strcmp(this->md2TextureFileName, "") )
773  {
774    SYNCHELP_WRITE_STRING(this->md2TextureFileName);
775  }
776  else
777  {
778    SYNCHELP_WRITE_STRING("");
779  }*/
780
781  return SYNCHELP_WRITE_N;
782}
Note: See TracBrowser for help on using the repository browser.