1 /* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */
2
3
4 #include <set>
5 #include <string>
6 #include <vector>
7 #include <set>
8 #include <map>
9 #include <cctype>
10
11 #include "LuaWeaponDefs.h"
12
13 #include "LuaInclude.h"
14
15 #include "LuaDefs.h"
16 #include "LuaHandle.h"
17 #include "LuaUtils.h"
18 #include "Game/TraceRay.h"
19 #include "Rendering/Models/IModelParser.h"
20 #include "Sim/Misc/CategoryHandler.h"
21 #include "Sim/Misc/DamageArrayHandler.h"
22 #include "Sim/Projectiles/Projectile.h"
23 #include "Sim/Weapons/Weapon.h"
24 #include "Sim/Weapons/WeaponDefHandler.h"
25 #include "System/FileSystem/SimpleParser.h"
26 #include "System/Log/ILog.h"
27 #include "System/Util.h"
28 #include "Sim/Misc/GlobalSynced.h"
29
30
31 static ParamMap paramMap;
32
33 static bool InitParamMap();
34
35 // iteration routines
36 static int Next(lua_State* L);
37 static int Pairs(lua_State* L);
38
39 // meta-table calls
40 static int WeaponDefIndex(lua_State* L);
41 static int WeaponDefNewIndex(lua_State* L);
42 static int WeaponDefMetatable(lua_State* L);
43
44 // special access functions
45 static int NoFeatureCollide(lua_State* L, const void* data);
46 static int NoFriendlyCollide(lua_State* L, const void* data);
47 static int NoNeutralCollide(lua_State* L, const void* data);
48
49 static int VisualsTable(lua_State* L, const void* data);
50 static int DamagesArray(lua_State* L, const void* data);
51 static int CustomParamsTable(lua_State* L, const void* data);
52 static int GuiSoundSetTable(lua_State* L, const void* data);
53 //static int CategorySetFromBits(lua_State* L, const void* data);
54
55
56 /******************************************************************************/
57 /******************************************************************************/
58
PushEntries(lua_State * L)59 bool LuaWeaponDefs::PushEntries(lua_State* L)
60 {
61 if (paramMap.empty()) {
62 InitParamMap();
63 }
64
65 const map<string, int>& weaponMap = weaponDefHandler->weaponID;
66 map<string, int>::const_iterator wit;
67 for (wit = weaponMap.begin(); wit != weaponMap.end(); ++wit) {
68 const WeaponDef* wd = &weaponDefHandler->weaponDefs[wit->second];
69 if (wd == NULL) {
70 continue;
71 }
72 lua_pushnumber(L, wd->id);
73 lua_newtable(L); { // the proxy table
74
75 lua_newtable(L); { // the metatable
76
77 HSTR_PUSH(L, "__index");
78 lua_pushlightuserdata(L, (void*)wd);
79 lua_pushcclosure(L, WeaponDefIndex, 1);
80 lua_rawset(L, -3); // closure
81
82 HSTR_PUSH(L, "__newindex");
83 lua_pushlightuserdata(L, (void*)wd);
84 lua_pushcclosure(L, WeaponDefNewIndex, 1);
85 lua_rawset(L, -3);
86
87 HSTR_PUSH(L, "__metatable");
88 lua_pushlightuserdata(L, (void*)wd);
89 lua_pushcclosure(L, WeaponDefMetatable, 1);
90 lua_rawset(L, -3);
91 }
92
93 lua_setmetatable(L, -2);
94 }
95
96 HSTR_PUSH(L, "pairs");
97 lua_pushcfunction(L, Pairs);
98 lua_rawset(L, -3);
99
100 HSTR_PUSH(L, "next");
101 lua_pushcfunction(L, Next);
102 lua_rawset(L, -3);
103
104 lua_rawset(L, -3); // proxy table into WeaponDefs
105 }
106
107 return true;
108 }
109
110
111 /******************************************************************************/
112
WeaponDefIndex(lua_State * L)113 static int WeaponDefIndex(lua_State* L)
114 {
115 // not a default value
116 if (!lua_isstring(L, 2)) {
117 lua_rawget(L, 1);
118 return 1;
119 }
120
121 const char* name = lua_tostring(L, 2);
122 ParamMap::const_iterator it = paramMap.find(name);
123
124 // not a default value
125 if (it == paramMap.end()) {
126 lua_rawget(L, 1);
127 return 1;
128 }
129
130 const void* userData = lua_touserdata(L, lua_upvalueindex(1));
131 const WeaponDef* wd = static_cast<const WeaponDef*>(userData);
132 const DataElement& elem = it->second;
133 const void* p = ((const char*)wd) + elem.offset;
134 switch (elem.type) {
135 case READONLY_TYPE: {
136 lua_rawget(L, 1);
137 return 1;
138 }
139 case INT_TYPE: {
140 lua_pushnumber(L, *reinterpret_cast<const int*>(p));
141 return 1;
142 }
143 case BOOL_TYPE: {
144 lua_pushboolean(L, *reinterpret_cast<const bool*>(p));
145 return 1;
146 }
147 case FLOAT_TYPE: {
148 lua_pushnumber(L, *reinterpret_cast<const float*>(p));
149 return 1;
150 }
151 case STRING_TYPE: {
152 lua_pushsstring(L, *reinterpret_cast<const std::string*>(p));
153 return 1;
154 }
155 case FUNCTION_TYPE: {
156 return elem.func(L, p);
157 }
158 case ERROR_TYPE: {
159 LOG_L(L_ERROR, "[%s] ERROR_TYPE for key \"%s\" in WeaponDefs __index", __FUNCTION__, name);
160 lua_pushnil(L);
161 return 1;
162 }
163 }
164
165 return 0;
166 }
167
168
WeaponDefNewIndex(lua_State * L)169 static int WeaponDefNewIndex(lua_State* L)
170 {
171 // not a default value, set it
172 if (!lua_isstring(L, 2)) {
173 lua_rawset(L, 1);
174 return 0;
175 }
176
177 const char* name = lua_tostring(L, 2);
178 ParamMap::const_iterator it = paramMap.find(name);
179
180 // not a default value, set it
181 if (it == paramMap.end()) {
182 lua_rawset(L, 1);
183 return 0;
184 }
185
186 const void* userData = lua_touserdata(L, lua_upvalueindex(1));
187 const WeaponDef* wd = static_cast<const WeaponDef*>(userData);
188
189 // write-protected
190 if (!gs->editDefsEnabled) {
191 luaL_error(L, "Attempt to write WeaponDefs[%d].%s", wd->id, name);
192 return 0;
193 }
194
195 // Definition editing
196 const DataElement& elem = it->second;
197 const void* p = ((const char*)wd) + elem.offset;
198
199 switch (elem.type) {
200 case FUNCTION_TYPE:
201 case READONLY_TYPE: {
202 luaL_error(L, "Can not write to %s", name);
203 return 0;
204 }
205 case INT_TYPE: {
206 *((int*)p) = lua_toint(L, -1);
207 return 0;
208 }
209 case BOOL_TYPE: {
210 *((bool*)p) = lua_toboolean(L, -1);
211 return 0;
212 }
213 case FLOAT_TYPE: {
214 *((float*)p) = lua_tofloat(L, -1);
215 return 0;
216 }
217 case STRING_TYPE: {
218 *((string*)p) = lua_tostring(L, -1);
219 return 0;
220 }
221 case ERROR_TYPE: {
222 LOG_L(L_ERROR, "[%s] ERROR_TYPE for key \"%s\" in WeaponDefs __newindex", __FUNCTION__, name);
223 lua_pushnil(L);
224 return 1;
225 }
226 }
227
228 return 0;
229 }
230
231
WeaponDefMetatable(lua_State * L)232 static int WeaponDefMetatable(lua_State* L)
233 {
234 //const void* userData = lua_touserdata(L, lua_upvalueindex(1));
235 //const WeaponDef* wd = (const WeaponDef*)userData;
236 return 0;
237 }
238
239
240 /******************************************************************************/
241
Next(lua_State * L)242 static int Next(lua_State* L)
243 {
244 return LuaUtils::Next(paramMap, L);
245 }
246
247
Pairs(lua_State * L)248 static int Pairs(lua_State* L)
249 {
250 luaL_checktype(L, 1, LUA_TTABLE);
251 lua_pushcfunction(L, Next); // iterator
252 lua_pushvalue(L, 1); // state (table)
253 lua_pushnil(L); // initial value
254 return 3;
255 }
256
257
258 /******************************************************************************/
259 /******************************************************************************/
260
DamagesArray(lua_State * L,const void * data)261 static int DamagesArray(lua_State* L, const void* data)
262 {
263 const DamageArray& d = *static_cast<const DamageArray*>(data);
264 lua_newtable(L);
265 HSTR_PUSH_NUMBER(L, "impulseFactor", d.impulseFactor);
266 HSTR_PUSH_NUMBER(L, "impulseBoost", d.impulseBoost);
267 HSTR_PUSH_NUMBER(L, "craterMult", d.craterMult);
268 HSTR_PUSH_NUMBER(L, "craterBoost", d.craterBoost);
269 HSTR_PUSH_NUMBER(L, "paralyzeDamageTime", d.paralyzeDamageTime);
270
271 // damage values
272 const int typeCount = damageArrayHandler->GetNumTypes();
273 for (int i = 0; i < typeCount; i++) {
274 lua_pushnumber(L, i);
275 lua_pushnumber(L, d[i]);
276 lua_rawset(L, -3);
277 }
278
279 return 1;
280 }
281
282
VisualsTable(lua_State * L,const void * data)283 static int VisualsTable(lua_State* L, const void* data)
284 {
285 const struct WeaponDef::Visuals& v = *static_cast<const struct WeaponDef::Visuals*>(data);
286 lua_newtable(L);
287 HSTR_PUSH_STRING(L, "modelName", modelParser->FindModelPath(v.modelName));
288 HSTR_PUSH_NUMBER(L, "colorR", v.color.x);
289 HSTR_PUSH_NUMBER(L, "colorG", v.color.y);
290 HSTR_PUSH_NUMBER(L, "colorB", v.color.z);
291 HSTR_PUSH_NUMBER(L, "color2R", v.color2.x);
292 HSTR_PUSH_NUMBER(L, "color2G", v.color2.y);
293 HSTR_PUSH_NUMBER(L, "color2B", v.color2.z);
294 HSTR_PUSH_BOOL (L, "smokeTrail", v.smokeTrail);
295 HSTR_PUSH_NUMBER(L, "tileLength", v.tilelength);
296 HSTR_PUSH_NUMBER(L, "scrollSpeed", v.scrollspeed);
297 HSTR_PUSH_NUMBER(L, "pulseSpeed", v.pulseSpeed);
298 HSTR_PUSH_NUMBER(L, "laserFlareSize", v.laserflaresize);
299 HSTR_PUSH_NUMBER(L, "thickness", v.thickness);
300 HSTR_PUSH_NUMBER(L, "coreThickness", v.corethickness);
301 HSTR_PUSH_NUMBER(L, "beamDecay", v.beamdecay);
302 HSTR_PUSH_NUMBER(L, "stages", v.stages);
303 HSTR_PUSH_NUMBER(L, "sizeDecay", v.sizeDecay);
304 HSTR_PUSH_NUMBER(L, "alphaDecay", v.alphaDecay);
305 HSTR_PUSH_NUMBER(L, "separation", v.separation);
306 HSTR_PUSH_BOOL (L, "noGap", v.noGap);
307 HSTR_PUSH_BOOL (L, "alwaysVisible", v.alwaysVisible);
308 HSTR_PUSH_BOOL (L, "beamWeapon", false); // DEPRECATED
309
310 return 1;
311 // CColorMap *colorMap;
312 // AtlasedTexture *texture1;
313 // AtlasedTexture *texture2;
314 // AtlasedTexture *texture3;
315 // AtlasedTexture *texture4;
316 }
317
318
319
NoEnemyCollide(lua_State * L,const void * data)320 static int NoEnemyCollide(lua_State* L, const void* data)
321 {
322 const int bits = *((const int*) data);
323 lua_pushboolean(L, (bits & Collision::NOENEMIES));
324 return 1;
325 }
326
NoFriendlyCollide(lua_State * L,const void * data)327 static int NoFriendlyCollide(lua_State* L, const void* data)
328 {
329 const int bits = *((const int*) data);
330 lua_pushboolean(L, (bits & Collision::NOFRIENDLIES));
331 return 1;
332 }
333
NoFeatureCollide(lua_State * L,const void * data)334 static int NoFeatureCollide(lua_State* L, const void* data)
335 {
336 const int bits = *((const int*) data);
337 lua_pushboolean(L, (bits & Collision::NOFEATURES));
338 return 1;
339 }
340
NoNeutralCollide(lua_State * L,const void * data)341 static int NoNeutralCollide(lua_State* L, const void* data)
342 {
343 const int bits = *((const int*) data);
344 lua_pushboolean(L, (bits & Collision::NONEUTRALS));
345 return 1;
346 }
347
NoGroundCollide(lua_State * L,const void * data)348 static int NoGroundCollide(lua_State* L, const void* data)
349 {
350 const int bits = *((const int*) data);
351 lua_pushboolean(L, (bits & Collision::NOGROUND));
352 return 1;
353 }
354
355
356
BuildCategorySet(lua_State * L,const vector<string> & cats)357 static inline int BuildCategorySet(lua_State* L, const vector<string>& cats)
358 {
359 lua_newtable(L);
360 const int count = (int)cats.size();
361 for (int i = 0; i < count; i++) {
362 lua_pushsstring(L, cats[i]);
363 lua_pushboolean(L, true);
364 lua_rawset(L, -3);
365 }
366 return 1;
367 }
368
369
370 /*
371 static int CategorySetFromBits(lua_State* L, const void* data)
372 {
373 const int bits = *((const int*)data);
374 const vector<string> &cats =
375 CCategoryHandler::Instance()->GetCategoryNames(bits);
376 return BuildCategorySet(L, cats);
377 }
378 */
379
380 /*static int CategorySetFromString(lua_State* L, const void* data)
381 {
382 const string& str = *((const string*)data);
383 const string lower = StringToLower(str);
384 const vector<string> &cats = CSimpleParser::Tokenize(lower, 0);
385 return BuildCategorySet(L, cats);
386 }*/
387
388
CustomParamsTable(lua_State * L,const void * data)389 static int CustomParamsTable(lua_State* L, const void* data)
390 {
391 const map<string, string>& params = *((const map<string, string>*)data);
392 lua_newtable(L);
393 map<string, string>::const_iterator it;
394 for (it = params.begin(); it != params.end(); ++it) {
395 lua_pushsstring(L, it->first);
396 lua_pushsstring(L, it->second);
397 lua_rawset(L, -3);
398 }
399 return 1;
400 }
401
402
GuiSoundSetTable(lua_State * L,const void * data)403 static int GuiSoundSetTable(lua_State* L, const void* data)
404 {
405 const GuiSoundSet& soundSet = *static_cast<const GuiSoundSet*>(data);
406 const int soundCount = (int)soundSet.sounds.size();
407 lua_newtable(L);
408 for (int i = 0; i < soundCount; i++) {
409 lua_pushnumber(L, i + 1);
410 lua_newtable(L);
411 const GuiSoundSet::Data& sound = soundSet.sounds[i];
412 HSTR_PUSH_STRING(L, "name", sound.name);
413 HSTR_PUSH_NUMBER(L, "volume", sound.volume);
414 if (!CLuaHandle::GetHandleSynced(L)) {
415 HSTR_PUSH_NUMBER(L, "id", sound.id);
416 }
417 lua_rawset(L, -3);
418 }
419 return 1;
420 }
421
422
423
424 /******************************************************************************/
425 /******************************************************************************/
426
InitParamMap()427 static bool InitParamMap()
428 {
429 paramMap["next"] = DataElement(READONLY_TYPE);
430 paramMap["pairs"] = DataElement(READONLY_TYPE);
431
432 // dummy WeaponDef for offset generation
433 const WeaponDef wd;
434 const char* start = ADDRESS(wd);
435
436 ADD_FUNCTION("damages", wd.damages, DamagesArray);
437 ADD_FUNCTION("visuals", wd.visuals, VisualsTable);
438
439 ADD_FUNCTION("hitSound", wd.hitSound, GuiSoundSetTable);
440 ADD_FUNCTION("fireSound", wd.fireSound, GuiSoundSetTable);
441
442 ADD_FUNCTION("customParams", wd.customParams, CustomParamsTable);
443 ADD_FUNCTION("noEnemyCollide", wd.collisionFlags, NoEnemyCollide);
444 ADD_FUNCTION("noFriendlyCollide", wd.collisionFlags, NoFriendlyCollide);
445 ADD_FUNCTION("noFeatureCollide", wd.collisionFlags, NoFeatureCollide);
446 ADD_FUNCTION("noNeutralCollide", wd.collisionFlags, NoNeutralCollide);
447 ADD_FUNCTION("noGroundCollide", wd.collisionFlags, NoGroundCollide);
448
449 ADD_DEPRECATED_LUADEF_KEY("areaOfEffect");
450 ADD_DEPRECATED_LUADEF_KEY("maxVelocity");
451 ADD_DEPRECATED_LUADEF_KEY("onlyTargetCategories");
452 ADD_DEPRECATED_LUADEF_KEY("restTime");
453
454 ADD_INT("id", wd.id);
455
456 ADD_INT("tdfId", wd.tdfId);
457
458 ADD_STRING("name", wd.name);
459 ADD_STRING("description", wd.description);
460
461 // FIXME: why is this expgen-tag exposed but not the other two?
462 ADD_STRING("cegTag", wd.visuals.ptrailExpGenTag);
463
464 ADD_STRING("type", wd.type);
465
466 ADD_FLOAT("range", wd.range);
467 ADD_FLOAT("heightMod", wd.heightmod);
468 ADD_FLOAT("accuracy", wd.accuracy);
469 ADD_FLOAT("sprayAngle", wd.sprayAngle);
470 ADD_FLOAT("movingAccuracy", wd.movingAccuracy);
471 ADD_FLOAT("targetMoveError", wd.targetMoveError);
472 ADD_FLOAT("leadLimit", wd.leadLimit);
473 ADD_FLOAT("leadBonus", wd.leadBonus);
474 ADD_FLOAT("predictBoost", wd.predictBoost);
475 ADD_INT("highTrajectory", wd.highTrajectory);
476
477 ADD_BOOL("noSelfDamage", wd.noSelfDamage);
478 ADD_BOOL("impactOnly", wd.impactOnly);
479
480 ADD_FLOAT("craterAreaOfEffect", wd.craterAreaOfEffect);
481 ADD_FLOAT("damageAreaOfEffect", wd.damageAreaOfEffect);
482 ADD_FLOAT("edgeEffectiveness", wd.edgeEffectiveness);
483 ADD_FLOAT("fireStarter", wd.fireStarter);
484 ADD_FLOAT("size", wd.size);
485 ADD_FLOAT("sizeGrowth", wd.sizeGrowth);
486 ADD_FLOAT("collisionSize", wd.collisionSize);
487
488 ADD_INT("salvoSize", wd.salvosize);
489 ADD_INT("projectiles", wd.projectilespershot);
490 ADD_FLOAT("salvoDelay", wd.salvodelay);
491 ADD_FLOAT("reload", wd.reload);
492 ADD_FLOAT("beamtime", wd.beamtime);
493 ADD_BOOL("beamburst", wd.beamburst);
494
495 ADD_BOOL("waterbounce", wd.waterBounce);
496 ADD_BOOL("groundbounce", wd.groundBounce);
497 ADD_FLOAT("groundslip", wd.bounceSlip);
498 ADD_FLOAT("bouncerebound", wd.bounceRebound);
499 ADD_INT("numbounce", wd.numBounce);
500
501 ADD_FLOAT("maxAngle", wd.maxAngle);
502
503 ADD_FLOAT("uptime", wd.uptime);
504
505 ADD_FLOAT("metalCost", wd.metalcost);
506 ADD_FLOAT("energyCost", wd.energycost);
507
508 ADD_BOOL("turret", wd.turret);
509 ADD_BOOL("onlyForward", wd.onlyForward);
510 ADD_BOOL("waterWeapon", wd.waterweapon);
511 ADD_BOOL("tracks", wd.tracks);
512 ADD_BOOL("paralyzer", wd.paralyzer);
513
514 ADD_BOOL("noAutoTarget", wd.noAutoTarget);
515 ADD_BOOL("manualFire", wd.manualfire);
516 ADD_INT("targetable", wd.targetable);
517 ADD_BOOL("stockpile", wd.stockpile);
518 ADD_INT("interceptor", wd.interceptor);
519 ADD_BOOL("interceptSolo", wd.interceptSolo);
520 ADD_FLOAT("coverageRange", wd.coverageRange);
521
522 ADD_FLOAT("stockpileTime", wd.stockpileTime);
523
524 ADD_FLOAT("intensity", wd.intensity);
525 ADD_FLOAT("duration", wd.duration);
526 ADD_INT("beamTTL", wd.beamLaserTTL);
527
528 ADD_BOOL("soundTrigger", wd.soundTrigger);
529
530 ADD_BOOL("selfExplode", wd.selfExplode);
531 ADD_BOOL("gravityAffected", wd.gravityAffected);
532 ADD_FLOAT("myGravity", wd.myGravity);
533 ADD_BOOL("noExplode", wd.noExplode);
534 ADD_FLOAT("startvelocity", wd.startvelocity);
535 ADD_FLOAT("weaponAcceleration", wd.weaponacceleration);
536 ADD_FLOAT("turnRate", wd.turnrate);
537
538 ADD_FLOAT("projectilespeed", wd.projectilespeed);
539 ADD_FLOAT("explosionSpeed", wd.explosionSpeed);
540
541 ADD_FLOAT("wobble", wd.wobble);
542 ADD_FLOAT("dance", wd.dance);
543
544 ADD_FLOAT("trajectoryHeight", wd.trajectoryHeight);
545
546 ADD_BOOL("largeBeamLaser", wd.largeBeamLaser);
547 ADD_BOOL("laserHardStop", wd.laserHardStop);
548
549 ADD_BOOL("isShield", wd.isShield);
550 ADD_BOOL("shieldRepulser", wd.shieldRepulser);
551 ADD_BOOL("smartShield", wd.smartShield);
552 ADD_BOOL("exteriorShield", wd.exteriorShield);
553 ADD_BOOL("visibleShield", wd.visibleShield);
554 ADD_BOOL("visibleShieldRepulse", wd.visibleShieldRepulse);
555 ADD_INT( "visibleShieldHitFrames", wd.visibleShieldHitFrames);
556 ADD_FLOAT("shieldEnergyUse", wd.shieldEnergyUse);
557 ADD_FLOAT("shieldRadius", wd.shieldRadius);
558 ADD_FLOAT("shieldForce", wd.shieldForce);
559 ADD_FLOAT("shieldMaxSpeed", wd.shieldMaxSpeed);
560 ADD_FLOAT("shieldPower", wd.shieldPower);
561 ADD_FLOAT("shieldPowerRegen", wd.shieldPowerRegen);
562 ADD_FLOAT("shieldPowerRegenEnergy", wd.shieldPowerRegenEnergy);
563 ADD_INT( "shieldRechargeDelay", wd.shieldRechargeDelay);
564 ADD_FLOAT("shieldGoodColorR", wd.shieldGoodColor.x);
565 ADD_FLOAT("shieldGoodColorG", wd.shieldGoodColor.y);
566 ADD_FLOAT("shieldGoodColorB", wd.shieldGoodColor.z);
567 ADD_FLOAT("shieldBadColorR", wd.shieldBadColor.x);
568 ADD_FLOAT("shieldBadColorG", wd.shieldBadColor.y);
569 ADD_FLOAT("shieldBadColorB", wd.shieldBadColor.z);
570 ADD_FLOAT("shieldAlpha", wd.shieldAlpha);
571
572 ADD_INT("shieldInterceptType", wd.shieldInterceptType);
573 ADD_INT("interceptedByShieldType", wd.interceptedByShieldType);
574
575 ADD_BOOL("avoidFriendly", wd.avoidFriendly);
576 ADD_BOOL("avoidFeature", wd.avoidFeature);
577 ADD_BOOL("avoidNeutral", wd.avoidNeutral);
578
579 ADD_FLOAT("targetBorder", wd.targetBorder);
580 ADD_FLOAT("cylinderTargeting", wd.cylinderTargeting);
581 ADD_FLOAT("cylinderTargetting", wd.cylinderTargeting); // FIXME deprecated misspelling
582 ADD_FLOAT("minIntensity", wd.minIntensity);
583 ADD_FLOAT("heightBoostFactor", wd.heightBoostFactor);
584 ADD_FLOAT("proximityPriority", wd.proximityPriority);
585
586 ADD_BOOL("sweepFire", wd.sweepFire);
587
588 ADD_BOOL("canAttackGround", wd.canAttackGround);
589
590 return true;
591 }
592
593
594 /******************************************************************************/
595 /******************************************************************************/
596