lightning.cpp

Engine/source/T3D/fx/lightning.cpp

More...

Classes:

Public Variables

Public Functions

ConsoleDocClass(Lightning , "@brief An emitter <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> lightning <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">bolts.\n\n</a>" "<a href="/coding/class/classlightning/">Lightning</a> strike events are created on the server and transmitted <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> all " "clients <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> render the bolt. The strike may be followed by a random thunder " "sound. <a href="/coding/class/classplayer/">Player</a> or <a href="/coding/class/classvehicle/">Vehicle</a> objects within the <a href="/coding/class/classlightning/">Lightning</a> strike range can be " "hit and damaged by <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">bolts.\n</a>" "@see <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">LightningData\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">FX\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Atmosphere\n</a>" )
ConsoleDocClass(LightningData , "@brief Common data <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> a <a href="/coding/class/classlightning/">Lightning</a> emitter <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">object.\n</a>" "@see <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Lightning\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">FX\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Atmosphere\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Datablocks\n</a>" )
ConsoleDocClass(LightningStrikeEvent , "@brief Network event that triggers a lightning strike on the client when it " "is <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">received.\n\n</a>" "This event is sent <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> all clients when the  warningFlashes, " "strikeRandomPoint() or strikeObject() methods are invoked on the <a href="/coding/class/classlightning/">Lightning</a> " "object on the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">server.\n</a>" " @see Lightning, <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">LightningData\n</a>" " @ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">FX\n</a>" " @ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Atmosphere\n</a>" )
DefineEngineMethod(Lightning , strikeObject , void , (ShapeBase *pSB) , (nullAsType< ShapeBase * >()) , "Creates a <a href="/coding/class/classlightningstrikeevent/">LightningStrikeEvent</a> which strikes a specific <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">object.\n</a>" "@note This method is currently <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">unimplemented.\n</a>" )
DefineEngineMethod(Lightning , strikeRandomPoint , void , () , "Creates a <a href="/coding/class/classlightningstrikeevent/">LightningStrikeEvent</a> which attempts <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> strike and damage a random " "object in range of the <a href="/coding/class/classlightning/">Lightning</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">object.\n</a>" "@<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">tsexample\n</a>" "// Generate a damaging lightning strike effect on all <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">clients\n</a>" "%lightning.strikeRandomPoint();\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "@endtsexample" )
DefineEngineMethod(Lightning , warningFlashes , void , () , "@brief Creates a <a href="/coding/class/classlightningstrikeevent/">LightningStrikeEvent</a> that triggers harmless lightning " "bolts on all <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">clients.\n</a>" "No objects will be damaged by these <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">bolts.\n</a>" "@<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">tsexample\n</a>" "// Generate a harmless lightning strike effect on all <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">clients\n</a>" "%lightning.warningFlashes();\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "@endtsexample" )
IMPLEMENT_CALLBACK(Lightning , applyDamage , void , (const Point3F &hitPosition, const Point3F &hitNormal, SceneObject *hitObject) , (hitPosition, hitNormal, hitObject) , "Informs an object that it was hit by a lightning bolt and needs <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> take <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">damage.\n</a>" "@param hitPosition <a href="/coding/file/gizmo_8h/#gizmo_8h_1a10fcd3bee2ea25191e31795e36bdeba1a81f4537631c9ab219ec74de554483adc">World</a> position hit by the lightning <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">bolt.\n</a>" "@param hitNormal Surface normal at @a <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">hitPosition.\n</a>" "@param hitObject <a href="/coding/class/classplayer/">Player</a> or <a href="/coding/class/classvehicle/">Vehicle</a> object that was <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">hit.\n</a>" "@<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">tsexample\n</a>" "function Lightning::applyDamage( %this, %hitPosition, %hitNormal, %hitObject )\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "{\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   // apply damage <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">player\n</a>" "   %hitObject.applyDamage( 25 );\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "}\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "@<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">endtsexample\n</a>" )

Detailed Description

Public Variables

MRandomLCG sgLightningRand 

Public Functions

cmpSounds(const void * p1, const void * p2)

ConsoleDocClass(Lightning , "@brief An emitter <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> lightning <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">bolts.\n\n</a>" "<a href="/coding/class/classlightning/">Lightning</a> strike events are created on the server and transmitted <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> all " "clients <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> render the bolt. The strike may be followed by a random thunder " "sound. <a href="/coding/class/classplayer/">Player</a> or <a href="/coding/class/classvehicle/">Vehicle</a> objects within the <a href="/coding/class/classlightning/">Lightning</a> strike range can be " "hit and damaged by <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">bolts.\n</a>" "@see <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">LightningData\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">FX\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Atmosphere\n</a>" )

ConsoleDocClass(LightningData , "@brief Common data <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> a <a href="/coding/class/classlightning/">Lightning</a> emitter <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">object.\n</a>" "@see <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Lightning\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">FX\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Atmosphere\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Datablocks\n</a>" )

ConsoleDocClass(LightningStrikeEvent , "@brief Network event that triggers a lightning strike on the client when it " "is <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">received.\n\n</a>" "This event is sent <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> all clients when the  warningFlashes, " "strikeRandomPoint() or strikeObject() methods are invoked on the <a href="/coding/class/classlightning/">Lightning</a> " "object on the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">server.\n</a>" " @see Lightning, <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">LightningData\n</a>" " @ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">FX\n</a>" " @ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Atmosphere\n</a>" )

DefineEngineMethod(Lightning , strikeObject , void , (ShapeBase *pSB) , (nullAsType< ShapeBase * >()) , "Creates a <a href="/coding/class/classlightningstrikeevent/">LightningStrikeEvent</a> which strikes a specific <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">object.\n</a>" "@note This method is currently <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">unimplemented.\n</a>" )

DefineEngineMethod(Lightning , strikeRandomPoint , void , () , "Creates a <a href="/coding/class/classlightningstrikeevent/">LightningStrikeEvent</a> which attempts <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> strike and damage a random " "object in range of the <a href="/coding/class/classlightning/">Lightning</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">object.\n</a>" "@<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">tsexample\n</a>" "// Generate a damaging lightning strike effect on all <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">clients\n</a>" "%lightning.strikeRandomPoint();\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "@endtsexample" )

DefineEngineMethod(Lightning , warningFlashes , void , () , "@brief Creates a <a href="/coding/class/classlightningstrikeevent/">LightningStrikeEvent</a> that triggers harmless lightning " "bolts on all <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">clients.\n</a>" "No objects will be damaged by these <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">bolts.\n</a>" "@<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">tsexample\n</a>" "// Generate a harmless lightning strike effect on all <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">clients\n</a>" "%lightning.warningFlashes();\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "@endtsexample" )

IMPLEMENT_CALLBACK(Lightning , applyDamage , void , (const Point3F &hitPosition, const Point3F &hitNormal, SceneObject *hitObject) , (hitPosition, hitNormal, hitObject) , "Informs an object that it was hit by a lightning bolt and needs <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> take <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">damage.\n</a>" "@param hitPosition <a href="/coding/file/gizmo_8h/#gizmo_8h_1a10fcd3bee2ea25191e31795e36bdeba1a81f4537631c9ab219ec74de554483adc">World</a> position hit by the lightning <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">bolt.\n</a>" "@param hitNormal Surface normal at @a <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">hitPosition.\n</a>" "@param hitObject <a href="/coding/class/classplayer/">Player</a> or <a href="/coding/class/classvehicle/">Vehicle</a> object that was <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">hit.\n</a>" "@<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">tsexample\n</a>" "function Lightning::applyDamage( %this, %hitPosition, %hitNormal, %hitObject )\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "{\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "   // apply damage <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">player\n</a>" "   %hitObject.applyDamage( 25 );\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "}\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "@<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">endtsexample\n</a>" )

IMPLEMENT_CO_CLIENTEVENT_V1(LightningStrikeEvent )

IMPLEMENT_CO_DATABLOCK_V1(LightningData )

IMPLEMENT_CO_NETOBJECT_V1(Lightning )

   1
   2//-----------------------------------------------------------------------------
   3// Copyright (c) 2012 GarageGames, LLC
   4//
   5// Permission is hereby granted, free of charge, to any person obtaining a copy
   6// of this software and associated documentation files (the "Software"), to
   7// deal in the Software without restriction, including without limitation the
   8// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
   9// sell copies of the Software, and to permit persons to whom the Software is
  10// furnished to do so, subject to the following conditions:
  11//
  12// The above copyright notice and this permission notice shall be included in
  13// all copies or substantial portions of the Software.
  14//
  15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  20// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  21// IN THE SOFTWARE.
  22//-----------------------------------------------------------------------------
  23
  24#include "platform/platform.h"
  25#include "T3D/fx/lightning.h"
  26
  27#include "scene/sceneRenderState.h"
  28#include "console/consoleTypes.h"
  29#include "math/mathIO.h"
  30#include "core/stream/bitStream.h"
  31#include "T3D/gameBase/gameConnection.h"
  32#include "T3D/shapeBase.h"
  33#include "math/mRandom.h"
  34#include "math/mathUtils.h"
  35#include "terrain/terrData.h"
  36#include "scene/sceneManager.h"
  37#include "T3D/player.h"
  38#include "T3D/camera.h"
  39#include "sfx/sfxSystem.h"
  40#include "sfx/sfxTrack.h"
  41#include "sfx/sfxTypes.h"
  42#include "gfx/primBuilder.h"
  43#include "console/engineAPI.h"
  44
  45
  46IMPLEMENT_CO_DATABLOCK_V1(LightningData);
  47IMPLEMENT_CO_NETOBJECT_V1(Lightning);
  48
  49ConsoleDocClass( LightningData,
  50   "@brief Common data for a Lightning emitter object.\n"
  51   "@see Lightning\n"
  52   "@ingroup FX\n"
  53   "@ingroup Atmosphere\n"
  54   "@ingroup Datablocks\n"
  55);
  56
  57ConsoleDocClass( Lightning,
  58   "@brief An emitter for lightning bolts.\n\n"
  59
  60   "Lightning strike events are created on the server and transmitted to all "
  61   "clients to render the bolt. The strike may be followed by a random thunder "
  62   "sound. Player or Vehicle objects within the Lightning strike range can be "
  63   "hit and damaged by bolts.\n"
  64
  65   "@see LightningData\n"
  66   "@ingroup FX\n"
  67   "@ingroup Atmosphere\n"
  68);
  69
  70IMPLEMENT_CALLBACK( Lightning, applyDamage, void, ( const Point3F& hitPosition, const Point3F& hitNormal, SceneObject* hitObject ),
  71   ( hitPosition, hitNormal, hitObject ),
  72   "Informs an object that it was hit by a lightning bolt and needs to take damage.\n"
  73   "@param hitPosition World position hit by the lightning bolt.\n"
  74   "@param hitNormal Surface normal at @a hitPosition.\n"
  75   "@param hitObject Player or Vehicle object that was hit.\n"
  76   "@tsexample\n"
  77   "function Lightning::applyDamage( %this, %hitPosition, %hitNormal, %hitObject )\n"
  78   "{\n"
  79   "   // apply damage to the player\n"
  80   "   %hitObject.applyDamage( 25 );\n"
  81   "}\n"
  82   "@endtsexample\n"
  83);
  84
  85
  86MRandomLCG sgLightningRand;
  87
  88
  89S32 QSORT_CALLBACK cmpSounds(const void* p1, const void* p2)
  90{
  91   U32 i1 = *((const S32*)p1);
  92   U32 i2 = *((const S32*)p2);
  93
  94   if (i1 < i2) {
  95      return 1;
  96   } else if (i1 > i2) {
  97      return -1;
  98   } else {
  99      return 0;
 100   }
 101}
 102
 103//--------------------------------------------------------------------------
 104//--------------------------------------
 105//
 106class LightningStrikeEvent : public NetEvent
 107{
 108  public:
 109   typedef NetEvent Parent;
 110   enum EventType {
 111      WarningFlash   = 0,
 112      Strike         = 1,
 113      TargetedStrike = 2,
 114
 115      TypeMin        = WarningFlash,
 116      TypeMax        = TargetedStrike
 117   };
 118   enum Constants {
 119      PositionalBits = 10
 120   };
 121
 122   Point2F                   mStart;
 123   SimObjectPtr<SceneObject> mTarget;
 124
 125   Lightning*                mLightning;
 126
 127   // Set by unpack...
 128  public:
 129   S32                       mClientId;
 130
 131  public:
 132   LightningStrikeEvent();
 133   ~LightningStrikeEvent();
 134
 135   void pack(NetConnection*, BitStream*);
 136   void write(NetConnection*, BitStream*){}
 137   void unpack(NetConnection*, BitStream*);
 138   void process(NetConnection*);
 139
 140   DECLARE_CONOBJECT(LightningStrikeEvent);
 141};
 142
 143IMPLEMENT_CO_CLIENTEVENT_V1(LightningStrikeEvent);
 144
 145ConsoleDocClass( LightningStrikeEvent,
 146   "@brief Network event that triggers a lightning strike on the client when it "
 147   "is received.\n\n"
 148   "This event is sent to all clients when the warningFlashes(), "
 149   "strikeRandomPoint() or strikeObject() methods are invoked on the Lightning "
 150   "object on the server.\n"
 151   "@see Lightning, LightningData\n"
 152   "@ingroup FX\n"
 153   "@ingroup Atmosphere\n"
 154);
 155
 156LightningStrikeEvent::LightningStrikeEvent()
 157{
 158   mLightning = NULL;
 159   mTarget = NULL;
 160   mClientId = 0;
 161}
 162
 163LightningStrikeEvent::~LightningStrikeEvent()
 164{
 165}
 166
 167void LightningStrikeEvent::pack(NetConnection* con, BitStream* stream)
 168{
 169   if(!mLightning)
 170   {
 171      stream->writeFlag(false);
 172      return;
 173   }
 174   S32 id = con->getGhostIndex(mLightning);
 175   if(id == -1)
 176   {
 177      stream->writeFlag(false);
 178      return;
 179   }
 180   stream->writeFlag(true);
 181   stream->writeRangedU32(U32(id), 0, NetConnection::MaxGhostCount);
 182   stream->writeFloat(mStart.x, PositionalBits);
 183   stream->writeFloat(mStart.y, PositionalBits);
 184
 185   if( mTarget )
 186   {
 187      S32 ghostIndex = con->getGhostIndex(mTarget);
 188      if (ghostIndex == -1)
 189         stream->writeFlag(false);
 190      else
 191      {
 192         stream->writeFlag(true);
 193         stream->writeRangedU32(U32(ghostIndex), 0, NetConnection::MaxGhostCount);
 194      }
 195   }
 196   else
 197      stream->writeFlag( false );
 198}
 199
 200void LightningStrikeEvent::unpack(NetConnection* con, BitStream* stream)
 201{
 202   if(!stream->readFlag())
 203      return;
 204   mClientId = stream->readRangedU32(0, NetConnection::MaxGhostCount);
 205   mLightning = NULL;
 206   NetObject* pObject = con->resolveGhost(mClientId);
 207   if (pObject)
 208      mLightning = dynamic_cast<Lightning*>(pObject);
 209
 210   mStart.x = stream->readFloat(PositionalBits);
 211   mStart.y = stream->readFloat(PositionalBits);
 212
 213   if( stream->readFlag() )
 214   {
 215      // target id
 216      S32 mTargetID    = stream->readRangedU32(0, NetConnection::MaxGhostCount);
 217
 218      NetObject* tObject = con->resolveGhost(mTargetID);
 219      if(tObject != NULL )
 220      {
 221         mTarget = dynamic_cast<SceneObject*>(tObject);
 222      }
 223      if( bool(mTarget) == false )
 224      {
 225         Con::errorf(ConsoleLogEntry::General, "LightningStrikeEvent::unpack: could not resolve target ghost properly");
 226      }
 227   }
 228}
 229
 230void LightningStrikeEvent::process(NetConnection*)
 231{
 232   if (mLightning)
 233      mLightning->processEvent(this);
 234}
 235
 236
 237//--------------------------------------------------------------------------
 238//--------------------------------------
 239//
 240LightningData::LightningData()
 241{
 242   strikeSound = NULL;
 243
 244   dMemset( strikeTextureNames, 0, sizeof( strikeTextureNames ) );
 245   dMemset( strikeTextures, 0, sizeof( strikeTextures ) );
 246   dMemset( thunderSounds, 0, sizeof( thunderSounds ) );
 247   mNumStrikeTextures = 0;
 248}
 249
 250LightningData::~LightningData()
 251{
 252
 253}
 254
 255//--------------------------------------------------------------------------
 256void LightningData::initPersistFields()
 257{
 258   addField( "strikeSound", TYPEID< SFXTrack >(), Offset(strikeSound, LightningData),
 259      "Sound profile to play when a lightning strike occurs." );
 260   addField( "thunderSounds", TYPEID< SFXTrack >(), Offset(thunderSounds, LightningData), MaxThunders,
 261      "@brief List of thunder sound effects to play.\n\n"
 262      "A random one of these sounds will be played shortly after each strike "
 263      "occurs." );
 264   addField( "strikeTextures", TypeString, Offset(strikeTextureNames, LightningData), MaxTextures,
 265      "List of textures to use to render lightning strikes." );
 266
 267   Parent::initPersistFields();
 268}
 269
 270
 271//--------------------------------------------------------------------------
 272bool LightningData::onAdd()
 273{
 274   if(!Parent::onAdd())
 275      return false;
 276
 277   return true;
 278}
 279
 280
 281bool LightningData::preload(bool server, String &errorStr)
 282{
 283   if (Parent::preload(server, errorStr) == false)
 284      return false;
 285
 286   dQsort(thunderSounds, MaxThunders, sizeof(SFXTrack*), cmpSounds);
 287   for (numThunders = 0; numThunders < MaxThunders && thunderSounds[numThunders] != NULL; numThunders++) {
 288      //
 289   }
 290
 291   if (server == false) 
 292   {
 293      String sfxErrorStr;
 294      for (U32 i = 0; i < MaxThunders; i++) {
 295         if( !sfxResolve( &thunderSounds[ i ], sfxErrorStr ) )
 296            Con::errorf(ConsoleLogEntry::General, "LightningData::preload: Invalid packet: %s", sfxErrorStr.c_str());
 297      }
 298
 299      if( !sfxResolve( &strikeSound, sfxErrorStr ) )
 300         Con::errorf(ConsoleLogEntry::General, "LightningData::preload: Invalid packet: %s", sfxErrorStr.c_str());
 301
 302      mNumStrikeTextures = 0;
 303      for (U32 i = 0; i < MaxTextures; i++) 
 304      {
 305         if (strikeTextureNames[i][0])
 306         {
 307            strikeTextures[i] = GFXTexHandle(strikeTextureNames[i], &GFXDefaultStaticDiffuseProfile, avar("%s() - strikeTextures[%d] (line %d)", __FUNCTION__, i, __LINE__));
 308            mNumStrikeTextures++;
 309         }
 310      }
 311   }
 312
 313
 314   return true;
 315}
 316
 317
 318//--------------------------------------------------------------------------
 319void LightningData::packData(BitStream* stream)
 320{
 321   Parent::packData(stream);
 322
 323   U32 i;
 324   for (i = 0; i < MaxThunders; i++)
 325      sfxWrite( stream, thunderSounds[ i ] );
 326
 327   stream->writeInt(mNumStrikeTextures, 4);
 328
 329   for (i = 0; i < MaxTextures; i++) 
 330   {
 331      stream->writeString(strikeTextureNames[i]);
 332   }
 333
 334   sfxWrite( stream, strikeSound );
 335}
 336
 337void LightningData::unpackData(BitStream* stream)
 338{
 339   Parent::unpackData(stream);
 340
 341   U32 i;
 342   for (i = 0; i < MaxThunders; i++)
 343      sfxRead( stream, &thunderSounds[ i ] );
 344
 345   mNumStrikeTextures = stream->readInt(4);
 346
 347   for (i = 0; i < MaxTextures; i++) 
 348   {
 349      strikeTextureNames[i] = stream->readSTString();
 350   }
 351
 352   sfxRead( stream, &strikeSound );
 353}
 354
 355
 356//--------------------------------------------------------------------------
 357//--------------------------------------
 358//
 359Lightning::Lightning()
 360{
 361   mNetFlags.set(Ghostable</a>|<a href="/coding/class/classnetobject/#classnetobject_1ab7f7b94af4c238c69f5673a98199a01caa501b2b8ffe184a0a58342a1b9790258">ScopeAlways);
 362   mTypeMask |= StaticObjectType</a>|<a href="/coding/file/objecttypes_8h/#objecttypes_8h_1ac18d4a11e9446825e48fd474d7529084a43891480c40ab2b66f5d17713401a340">EnvironmentObjectType;
 363
 364   mLastThink = 0;
 365
 366   mStrikeListHead  = NULL;
 367   mThunderListHead = NULL;
 368
 369   strikesPerMinute = 12;
 370   strikeWidth = 2.5;
 371   chanceToHitTarget = 0.5f;
 372   strikeRadius = 20.0f;
 373   boltStartRadius = 20.0f;
 374   color.set( 1.0f, 1.0f, 1.0f, 1.0f );
 375   fadeColor.set( 0.1f, 0.1f, 1.0f, 1.0f );
 376   useFog = true;
 377
 378   setScale( VectorF( 512.0f, 512.0f, 300.0f ) );
 379}
 380
 381Lightning::~Lightning()
 382{
 383   while( mThunderListHead )
 384   {
 385      Thunder* nextThunder = mThunderListHead->next;
 386      delete mThunderListHead;
 387      mThunderListHead = nextThunder;
 388   }
 389
 390   while( mStrikeListHead )
 391   {
 392      Strike* nextStrike = mStrikeListHead->next;
 393      delete mStrikeListHead;
 394      mStrikeListHead = nextStrike;
 395   }
 396}
 397
 398//--------------------------------------------------------------------------
 399void Lightning::initPersistFields()
 400{
 401   addGroup( "Strikes" );
 402   addField( "strikesPerMinute", TypeS32, Offset(strikesPerMinute, Lightning),
 403      "@brief Number of lightning strikes to perform per minute.\n\n"
 404      "Automatically invokes strikeRandomPoint() at regular intervals." );
 405   addField( "strikeWidth", TypeF32, Offset(strikeWidth, Lightning),
 406      "Width of a lightning bolt." );
 407   addField( "strikeRadius", TypeF32, Offset(strikeRadius, Lightning),
 408      "@brief Horizontal size (XY plane) of the search box used to find and "
 409      "damage Player or Vehicle objects within range of the strike.\n\n"
 410      "Only the object at highest altitude with a clear line of sight to the "
 411      "bolt will be hit." );
 412   endGroup( "Strikes" );
 413
 414   addGroup( "Colors" );
 415   addField( "color", TypeColorF, Offset(color, Lightning),
 416      "Color to blend the strike texture with." );
 417   addField( "fadeColor", TypeColorF, Offset(fadeColor, Lightning),
 418      "@brief Color to blend the strike texture with when the bolt is fading away.\n\n"
 419      "Bolts fade away automatically shortly after the strike occurs." );
 420   endGroup( "Colors" );
 421
 422   addGroup( "Bolts" );
 423   addField( "chanceToHitTarget", TypeF32, Offset(chanceToHitTarget, Lightning),
 424      "Percentage chance (0-1) that a given lightning bolt will hit something." );
 425   addField( "boltStartRadius", TypeF32, Offset(boltStartRadius, Lightning),
 426      "@brief Radial distance from the center of the Lightning object for the "
 427      "start point of the bolt.\n\n"
 428      "The actual start point will be a random point within this radius." );
 429   addField( "useFog", TypeBool, Offset(useFog, Lightning),
 430      "Controls whether lightning bolts are affected by fog when they are rendered." );
 431   endGroup( "Bolts" );
 432
 433   Parent::initPersistFields();
 434}
 435
 436//--------------------------------------------------------------------------
 437bool Lightning::onAdd()
 438{
 439   if(!Parent::onAdd())
 440      return false;
 441
 442   mObjBox.minExtents.set( -0.5f, -0.5f, -0.5f );
 443   mObjBox.maxExtents.set(  0.5f,  0.5f,  0.5f );
 444
 445   resetWorldBox();
 446   addToScene();
 447
 448   return true;
 449}
 450
 451
 452void Lightning::onRemove()
 453{
 454   removeFromScene();
 455
 456   Parent::onRemove();
 457}
 458
 459
 460bool Lightning::onNewDataBlock( GameBaseData *dptr, bool reload )
 461{
 462   mDataBlock = dynamic_cast<LightningData*>( dptr );
 463   if ( !mDataBlock || !Parent::onNewDataBlock( dptr, reload ) )
 464      return false;
 465
 466   scriptOnNewDataBlock();
 467   return true;
 468}
 469
 470
 471//--------------------------------------------------------------------------
 472void Lightning::prepRenderImage( SceneRenderState* state )
 473{
 474   ObjectRenderInst *ri = state->getRenderPass()->allocInst<ObjectRenderInst>();
 475   ri->renderDelegate.bind(this, &Lightning::renderObject);
 476   // The Lightning isn't technically foliage but our debug
 477   // effect seems to render best as a Foliage type (translucent,
 478   // renders itself, no sorting)
 479   ri->type = RenderPassManager::RIT_Foliage;
 480   state->getRenderPass()->addInst( ri );
 481}
 482
 483
 484void Lightning::renderObject(ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance* overrideMat)
 485{
 486   if (overrideMat)
 487      return;
 488
 489   if (mLightningSB.isNull())
 490   {
 491      GFXStateBlockDesc desc;
 492      desc.setBlend( true, GFXBlendSrcAlpha, GFXBlendOne);
 493      desc.setCullMode(GFXCullNone);
 494      desc.zWriteEnable = false;
 495      desc.vertexColorEnable = true;
 496
 497      if (mDataBlock->mNumStrikeTextures != 0)
 498      {
 499         desc.samplersDefined = true;
 500         desc.samplers[0].magFilter = GFXTextureFilterLinear;
 501         desc.samplers[0].minFilter = GFXTextureFilterLinear;
 502         desc.samplers[0].addressModeU = GFXAddressWrap;
 503         desc.samplers[0].addressModeV = GFXAddressWrap;
 504      }
 505
 506      mLightningSB = GFX->createStateBlock(desc);
 507
 508   }
 509
 510   GFX->setStateBlock(mLightningSB);
 511
 512
 513   Strike* walk = mStrikeListHead;
 514   while (walk != NULL)
 515   {
 516      if (mDataBlock->mNumStrikeTextures > 1)
 517      {
 518         GFX->setTexture(0, mDataBlock->strikeTextures[sgLightningRand.randI(0, mDataBlock->mNumStrikeTextures - 1)]);
 519      }
 520      else if (mDataBlock->mNumStrikeTextures > 0)
 521      {
 522         GFX->setTexture(0, mDataBlock->strikeTextures[0]);
 523      }
 524
 525      for( U32 i=0; i<MAX_LIGHTNING; i++ )
 526      {
 527         if( walk->bolt[i].isFading )
 528         {
 529            F32 alpha = 1.0f - walk->bolt[i].percentFade;
 530            if( alpha < 0.0f ) alpha = 0.0f;
 531            PrimBuild::color4f( fadeColor.red, fadeColor.green, fadeColor.blue, alpha );
 532         }
 533         else
 534         {
 535            PrimBuild::color4f( color.red, color.green, color.blue, color.alpha );
 536         }
 537         walk->bolt[i].render( state->getCameraPosition() );
 538      }
 539
 540      walk = walk->next;
 541   }
 542
 543   //GFX->setZWriteEnable(true);
 544   //GFX->setAlphaTestEnable(false);
 545   //GFX->setAlphaBlendEnable(false);
 546}
 547
 548void Lightning::scheduleThunder(Strike* newStrike)
 549{
 550   AssertFatal(isClientObject(), "Lightning::scheduleThunder: server objects should not enter this version of the function");
 551
 552   // If no thunder sounds, don't schedule anything!
 553   if (mDataBlock->numThunders == 0)
 554      return;
 555
 556   GameConnection* connection = GameConnection::getConnectionToServer();
 557   if (connection) {
 558      MatrixF cameraMatrix;
 559
 560      if (connection->getControlCameraTransform(0, &cameraMatrix)) {
 561         Point3F worldPos;
 562         cameraMatrix.getColumn(3, &worldPos);
 563
 564         worldPos.x -= newStrike->xVal;
 565         worldPos.y -= newStrike->yVal;
 566         worldPos.z  = 0.0f;
 567
 568         F32 dist = worldPos.len();
 569         F32 t    = dist / 330.0f;
 570
 571         // Ok, we need to schedule a random strike sound t secs in the future...
 572         //
 573         if (t <= 0.03f) {
 574            // If it's really close, just play it...
 575            U32 thunder = sgLightningRand.randI(0, mDataBlock->numThunders - 1);
 576            SFX->playOnce(mDataBlock->thunderSounds[thunder]);
 577         } else {
 578            Thunder* pThunder = new Thunder;
 579            pThunder->tRemaining = t;
 580            pThunder->next       = mThunderListHead;
 581            mThunderListHead     = pThunder;
 582         }
 583      }
 584   }
 585}
 586
 587
 588//--------------------------------------------------------------------------
 589void Lightning::processTick(const Move* move)
 590{
 591   Parent::processTick(move);
 592
 593   if (isServerObject() && !isHidden()) {
 594      S32 msBetweenStrikes = (S32)(60.0 / strikesPerMinute * 1000.0);
 595
 596      mLastThink += TickMs;
 597      if( mLastThink > msBetweenStrikes )
 598      {
 599         strikeRandomPoint();
 600         mLastThink -= msBetweenStrikes;
 601      }
 602   }
 603}
 604
 605void Lightning::interpolateTick(F32 dt)
 606{
 607   Parent::interpolateTick(dt);
 608}
 609
 610void Lightning::advanceTime(F32 dt)
 611{
 612   Parent::advanceTime(dt);
 613
 614   Strike** pWalker = &mStrikeListHead;
 615   while (*pWalker != NULL) {
 616      Strike* pStrike = *pWalker;
 617
 618      for( U32 i=0; i<MAX_LIGHTNING; i++ )
 619      {
 620         pStrike->bolt[i].update( dt );
 621      }
 622
 623      pStrike->currentAge += dt;
 624      if (pStrike->currentAge > pStrike->deathAge) {
 625         *pWalker = pStrike->next;
 626         delete pStrike;
 627      } else {
 628         pWalker = &((*pWalker)->next);
 629      }
 630   }
 631
 632   Thunder** pThunderWalker = &mThunderListHead;
 633   while (*pThunderWalker != NULL) {
 634      Thunder* pThunder = *pThunderWalker;
 635
 636      pThunder->tRemaining -= dt;
 637      if (pThunder->tRemaining <= 0.0f) {
 638         *pThunderWalker = pThunder->next;
 639         delete pThunder;
 640
 641         // Play the sound...
 642         U32 thunder = sgLightningRand.randI(0, mDataBlock->numThunders - 1);
 643         SFX->playOnce(mDataBlock->thunderSounds[thunder]);
 644      } else {
 645         pThunderWalker = &((*pThunderWalker)->next);
 646      }
 647   }
 648}
 649
 650
 651//--------------------------------------------------------------------------
 652void Lightning::processEvent(LightningStrikeEvent* pEvent)
 653{
 654      AssertFatal(pEvent->mStart.x >= 0.0f && pEvent->mStart.x <= 1.0f, "Out of bounds coord!");
 655
 656      Strike* pStrike = new Strike;
 657
 658      Point3F strikePoint;
 659      strikePoint.zero();
 660
 661      if( pEvent->mTarget )
 662      {
 663         Point3F objectCenter;
 664         pEvent->mTarget->getObjBox().getCenter( &objectCenter );
 665         objectCenter.convolve( pEvent->mTarget->getScale() );
 666         pEvent->mTarget->getTransform().mulP( objectCenter );
 667
 668         strikePoint = objectCenter;
 669      }
 670      else
 671      {
 672         strikePoint.x = pEvent->mStart.x;
 673         strikePoint.y = pEvent->mStart.y;
 674         strikePoint *= mObjScale;
 675         strikePoint += getPosition();
 676         strikePoint += Point3F( -mObjScale.x * 0.5f, -mObjScale.y * 0.5f, 0.0f );
 677
 678         RayInfo rayInfo;
 679         Point3F start = strikePoint;
 680         start.z = mObjScale.z * 0.5f + getPosition().z;
 681         strikePoint.z += -mObjScale.z * 0.5f;
 682         bool rayHit = gClientContainer.castRay( start, strikePoint,
 683                                      (STATIC_COLLISION_TYPEMASK | WaterObjectType),
 684                                      &rayInfo);
 685         if( rayHit )
 686         {
 687            strikePoint.z = rayInfo.point.z;
 688         }
 689         else
 690         {
 691            strikePoint.z = pStrike->bolt[0].findHeight( strikePoint, getSceneManager() );
 692         }
 693      }
 694
 695      pStrike->xVal       = strikePoint.x;
 696      pStrike->yVal       = strikePoint.y;
 697
 698      pStrike->deathAge   = 1.6f;
 699      pStrike->currentAge = 0.0f;
 700      pStrike->next       = mStrikeListHead;
 701
 702      for( U32 i=0; i<MAX_LIGHTNING; i++ )
 703      {
 704         F32 randStart = boltStartRadius;
 705         F32 height = mObjScale.z * 0.5f + getPosition().z;
 706         pStrike->bolt[i].startPoint.set( pStrike->xVal + gRandGen.randF( -randStart, randStart ), pStrike->yVal + gRandGen.randF( -randStart, randStart ), height );
 707         pStrike->bolt[i].endPoint = strikePoint;
 708         pStrike->bolt[i].width = strikeWidth;
 709         pStrike->bolt[i].numMajorNodes = 10;
 710         pStrike->bolt[i].maxMajorAngle = 30.0f;
 711         pStrike->bolt[i].numMinorNodes = 4;
 712         pStrike->bolt[i].maxMinorAngle = 15.0f;
 713         pStrike->bolt[i].generate();
 714         pStrike->bolt[i].startSplits();
 715         pStrike->bolt[i].lifetime = 1.0f;
 716         pStrike->bolt[i].fadeTime = 0.2f;
 717         pStrike->bolt[i].renderTime = gRandGen.randF(0.0f, 0.25f);
 718      }
 719
 720      mStrikeListHead     = pStrike;
 721
 722      scheduleThunder(pStrike);
 723
 724      MatrixF trans(true);
 725      trans.setPosition( strikePoint );
 726
 727      if (mDataBlock->strikeSound)
 728      {
 729         SFX->playOnce(mDataBlock->strikeSound, &trans );
 730      }
 731
 732}
 733
 734void Lightning::warningFlashes()
 735{
 736   AssertFatal(isServerObject(), "Error, client objects may not initiate lightning!");
 737
 738   Point3F strikePoint( gRandGen.randF( 0.0f, 1.0f ), gRandGen.randF( 0.0f, 1.0f ), 0.0f );
 739
 740   SimGroup* pClientGroup = Sim::getClientGroup();
 741   for (SimGroup::iterator itr = pClientGroup->begin(); itr != pClientGroup->end(); itr++) {
 742      NetConnection* nc = static_cast<NetConnection*>(*itr);
 743      if (nc != NULL)
 744      {
 745         LightningStrikeEvent* pEvent = new LightningStrikeEvent;
 746         pEvent->mLightning = this;
 747       
 748       pEvent->mStart.x = strikePoint.x;
 749       pEvent->mStart.y = strikePoint.y;
 750
 751         nc->postNetEvent(pEvent);
 752      }
 753   }
 754}
 755
 756void Lightning::strikeRandomPoint()
 757{
 758   AssertFatal(isServerObject(), "Error, client objects may not initiate lightning!");
 759
 760
 761   Point3F strikePoint( gRandGen.randF( 0.0f, 1.0f ), gRandGen.randF( 0.0f, 1.0f ), 0.0f );
 762
 763   // check if an object is within target range
 764   Point3F worldPosStrikePoint = strikePoint;
 765
 766   worldPosStrikePoint *= mObjScale;
 767   worldPosStrikePoint += getPosition();
 768   worldPosStrikePoint += Point3F( -mObjScale.x * 0.5f, -mObjScale.y * 0.5f, 0.0f );
 769
 770   Box3F queryBox;
 771   F32 boxWidth = strikeRadius * 2.0f;
 772
 773   queryBox.minExtents.set( -boxWidth * 0.5f, -boxWidth * 0.5f, -mObjScale.z * 0.5f );
 774   queryBox.maxExtents.set(  boxWidth * 0.5f,  boxWidth * 0.5f,  mObjScale.z * 0.5f );
 775   queryBox.minExtents += worldPosStrikePoint;
 776   queryBox.maxExtents += worldPosStrikePoint;
 777
 778   SimpleQueryList sql;
 779   getContainer()->findObjects(queryBox, DAMAGEABLE_TYPEMASK,
 780                               SimpleQueryList::insertionCallback, &sql);
 781
 782   SceneObject *highestObj = NULL;
 783   F32 highestPnt = 0.0f;
 784
 785   for( U32 i = 0; i < sql.mList.size(); i++ )
 786   {
 787      Point3F objectCenter;
 788      sql.mList[i]->getObjBox().getCenter(&objectCenter);
 789      objectCenter.convolve(sql.mList[i]->getScale());
 790      sql.mList[i]->getTransform().mulP(objectCenter);
 791
 792      // check if object can be struck
 793
 794      RayInfo rayInfo;
 795      Point3F start = objectCenter;
 796      start.z = mObjScale.z * 0.5f + getPosition().z;
 797      Point3F end = objectCenter;
 798      end.z = -mObjScale.z * 0.5f + getPosition().z;
 799      bool rayHit = gServerContainer.castRay( start, end,
 800                                   (0xFFFFFFFF),
 801                                   &rayInfo);
 802
 803      if( rayHit && rayInfo.object == sql.mList[i] )
 804      {
 805         if( !highestObj )
 806         {
 807            highestObj = sql.mList[i];
 808            highestPnt = objectCenter.z;
 809            continue;
 810         }
 811
 812         if( objectCenter.z > highestPnt )
 813         {
 814            highestObj = sql.mList[i];
 815            highestPnt = objectCenter.z;
 816         }
 817      }
 818
 819
 820   }
 821
 822   // hah haaaaa, we have a target!
 823   SceneObject *targetObj = NULL;
 824   if( highestObj )
 825   {
 826      F32 chance = gRandGen.randF();
 827      if( chance <= chanceToHitTarget )
 828      {
 829         Point3F objectCenter;
 830         highestObj->getObjBox().getCenter(&objectCenter);
 831         objectCenter.convolve(highestObj->getScale());
 832         highestObj->getTransform().mulP(objectCenter);
 833
 834         bool playerInWarmup = false;
 835         Player *playerObj = dynamic_cast< Player * >(highestObj);
 836         if( playerObj )
 837         {
 838            if( !playerObj->getControllingClient() )
 839            {
 840               playerInWarmup = true;
 841            }
 842         }
 843
 844         if( !playerInWarmup )
 845         {
 846            applyDamage_callback( objectCenter, VectorF( 0.0f, 0.0f, 1.0f ), highestObj );
 847            targetObj = highestObj;
 848         }
 849      }
 850   }
 851
 852   SimGroup* pClientGroup = Sim::getClientGroup();
 853   for (SimGroup::iterator itr = pClientGroup->begin(); itr != pClientGroup->end(); itr++)
 854   {
 855      NetConnection* nc = static_cast<NetConnection*>(*itr);
 856
 857      LightningStrikeEvent* pEvent = new LightningStrikeEvent;
 858      pEvent->mLightning = this;
 859
 860      pEvent->mStart.x = strikePoint.x;
 861      pEvent->mStart.y = strikePoint.y;
 862      pEvent->mTarget = targetObj;
 863
 864      nc->postNetEvent(pEvent);
 865   }
 866
 867
 868}
 869
 870//--------------------------------------------------------------------------
 871void Lightning::strikeObject(ShapeBase* targetObj)
 872{
 873   AssertFatal(isServerObject(), "Error, client objects may not initiate lightning!");
 874
 875   Point3F strikePoint = targetObj->getPosition();
 876   Point3F objectCenter;
 877
 878   Box3F wb = getWorldBox();
 879   if (!wb.isContained(strikePoint))
 880        return;
 881
 882   Point3F targetRel = strikePoint - getPosition();
 883   Point3F length(wb.len_x() / 2.0f, wb.len_y() / 2.0f, wb.len_z() / 2.0f);
 884
 885   Point3F strikePos = targetRel / length;
 886
 887   bool playerInWarmup = false;
 888   Player *playerObj = dynamic_cast< Player * >(targetObj);
 889   if (playerObj)
 890   {
 891       if (!playerObj->getControllingClient())
 892       {
 893           playerInWarmup = true;
 894       }
 895   }
 896
 897   if (!playerInWarmup)
 898   {
 899       applyDamage_callback(targetObj->getWorldSphere().center, VectorF(0.0, 0.0, 1.0), targetObj);
 900   }
 901 
 902   SimGroup* pClientGroup = Sim::getClientGroup();
 903   for (SimGroup::iterator itr = pClientGroup->begin(); itr != pClientGroup->end(); itr++) {
 904      NetConnection* nc = static_cast<NetConnection*>(*itr);
 905      if (nc != NULL)
 906      {
 907         LightningStrikeEvent* pEvent = new LightningStrikeEvent;
 908         pEvent->mLightning = this;
 909       
 910         pEvent->mStart.x = strikePoint.x;
 911         pEvent->mStart.y = strikePoint.y;
 912         pEvent->mTarget = targetObj;
 913
 914         nc->postNetEvent(pEvent);
 915      }
 916   }
 917}
 918
 919//--------------------------------------------------------------------------
 920U32 Lightning::packUpdate(NetConnection* con, U32 mask, BitStream* stream)
 921{
 922   U32 retMask = Parent::packUpdate(con, mask, stream);
 923
 924   // Only write data if this is the initial packet or we've been inspected.
 925   if (stream->writeFlag(mask & (InitialUpdateMask | ExtendedInfoMask)))
 926   {
 927      // Initial update
 928      mathWrite(*stream, getPosition());
 929      mathWrite(*stream, mObjScale);
 930
 931      stream->write(strikeWidth);
 932      stream->write(chanceToHitTarget);
 933      stream->write(strikeRadius);
 934      stream->write(boltStartRadius);
 935      stream->write(color.red);
 936      stream->write(color.green);
 937      stream->write(color.blue);
 938      stream->write(color.alpha);
 939      stream->write(fadeColor.red);
 940      stream->write(fadeColor.green);
 941      stream->write(fadeColor.blue);
 942      stream->write(useFog);
 943      stream->write(strikesPerMinute);
 944   }
 945
 946   return retMask;
 947}
 948
 949//--------------------------------------------------------------------------
 950void Lightning::unpackUpdate(NetConnection* con, BitStream* stream)
 951{
 952   Parent::unpackUpdate(con, stream);
 953
 954   if (stream->readFlag())
 955   {
 956      // Initial update
 957      Point3F pos;
 958      mathRead(*stream, &pos);
 959      setPosition( pos );
 960
 961      mathRead(*stream, &mObjScale);
 962
 963      stream->read(&strikeWidth);
 964      stream->read(&chanceToHitTarget);
 965      stream->read(&strikeRadius);
 966      stream->read(&boltStartRadius);
 967      stream->read(&color.red);
 968      stream->read(&color.green);
 969      stream->read(&color.blue);
 970      stream->read(&color.alpha);
 971      stream->read(&fadeColor.red);
 972      stream->read(&fadeColor.green);
 973      stream->read(&fadeColor.blue);
 974      stream->read(&useFog);
 975      stream->read(&strikesPerMinute);
 976   }
 977}
 978
 979//--------------------------------------------------------------------------
 980
 981DefineEngineMethod(Lightning, warningFlashes, void, (),,
 982   "@brief Creates a LightningStrikeEvent that triggers harmless lightning "
 983   "bolts on all clients.\n"
 984   "No objects will be damaged by these bolts.\n"
 985   "@tsexample\n"
 986   "// Generate a harmless lightning strike effect on all clients\n"
 987   "%lightning.warningFlashes();\n"
 988   "@endtsexample" )
 989{
 990   if (object->isServerObject()) 
 991      object->warningFlashes();
 992}
 993
 994DefineEngineMethod(Lightning, strikeRandomPoint, void, (),,
 995   "Creates a LightningStrikeEvent which attempts to strike and damage a random "
 996   "object in range of the Lightning object.\n"
 997   "@tsexample\n"
 998   "// Generate a damaging lightning strike effect on all clients\n"
 999   "%lightning.strikeRandomPoint();\n"
1000   "@endtsexample" )
1001{
1002   if (object->isServerObject()) 
1003      object->strikeRandomPoint();
1004}
1005
1006DefineEngineMethod(Lightning, strikeObject, void, (ShapeBase* pSB), (nullAsType<ShapeBase*>()),
1007   "Creates a LightningStrikeEvent which strikes a specific object.\n"
1008   "@note This method is currently unimplemented.\n" )
1009{
1010      object->strikeObject(pSB);
1011}
1012
1013//**************************************************************************
1014// Lightning Bolt
1015//**************************************************************************
1016LightningBolt::LightningBolt()
1017{
1018   width = 0.1f;
1019   startPoint.zero();
1020   endPoint.zero();
1021   chanceOfSplit = 0.0f;
1022   isFading = false;
1023   elapsedTime = 0.0f;
1024   lifetime = 1.0f;
1025   startRender = false;
1026}
1027
1028//--------------------------------------------------------------------------
1029// Destructor
1030//--------------------------------------------------------------------------
1031LightningBolt::~LightningBolt()
1032{
1033   splitList.clear();
1034}
1035
1036//--------------------------------------------------------------------------
1037// Generate nodes
1038//--------------------------------------------------------------------------
1039void LightningBolt::NodeManager::generateNodes()
1040{
1041   F32 overallDist = VectorF( endPoint - startPoint ).magnitudeSafe();
1042   F32 minDistBetweenNodes = overallDist / (numNodes-1);
1043   F32 maxDistBetweenNodes = minDistBetweenNodes / mCos( maxAngle * M_PI_F / 180.0f );
1044
1045   VectorF mainLineDir = endPoint - startPoint;
1046   mainLineDir.normalizeSafe();
1047
1048   for( U32 i=0; i<numNodes; i++ )
1049   {
1050      Node node;
1051
1052      if( i == 0 )
1053      {
1054         node.point = startPoint;
1055         node.dirToMainLine = mainLineDir;
1056         nodeList[i] = node;
1057         continue;
1058      }
1059      if( i == numNodes - 1 )
1060      {
1061         node.point = endPoint;
1062         nodeList[i] = node;
1063         break;
1064      }
1065
1066      Node lastNode = nodeList[i-1];
1067
1068      F32 segmentLength = gRandGen.randF( minDistBetweenNodes, maxDistBetweenNodes );
1069      VectorF segmentDir = MathUtils::randomDir( lastNode.dirToMainLine, 0, maxAngle );
1070      node.point = lastNode.point + segmentDir * segmentLength;
1071
1072      node.dirToMainLine = endPoint - node.point;
1073      node.dirToMainLine.normalizeSafe();
1074      nodeList[i] = node;
1075   }
1076}
1077
1078
1079//--------------------------------------------------------------------------
1080// Render bolt
1081//--------------------------------------------------------------------------
1082void LightningBolt::render( const Point3F &camPos )
1083{
1084   if (!startRender)
1085      return;
1086
1087   if (!isFading)
1088      generateMinorNodes();
1089
1090   U32 maxVerts = 0;
1091   for (U32 i = 0; i < mMinorNodes.size(); i++)
1092      maxVerts += mMinorNodes[i].numNodes * 2;
1093
1094   PrimBuild::begin(GFXTriangleStrip, maxVerts);
1095
1096   for (U32 i = 0; i < mMinorNodes.size(); i++)
1097   {
1098      if (i+1 == mMinorNodes.size())
1099         renderSegment(mMinorNodes[i], camPos, true);
1100      else
1101         renderSegment(mMinorNodes[i], camPos, false);
1102   }
1103
1104   PrimBuild::end();
1105
1106   for(LightingBoltList::Iterator i = splitList.begin(); i != splitList.end(); ++i)
1107   {
1108      if( isFading )
1109      {
1110         i->isFading = true;
1111      }
1112      i->render( camPos );
1113   }
1114
1115}
1116
1117//--------------------------------------------------------------------------
1118// Render segment
1119//--------------------------------------------------------------------------
1120void LightningBolt::renderSegment( NodeManager &segment, const Point3F &camPos, bool renderLastPoint )
1121{
1122
1123   for (U32 i = 0; i < segment.numNodes; i++)
1124   {
1125      Point3F  curPoint = segment.nodeList[i].point;
1126
1127      Point3F  nextPoint;
1128      Point3F  segDir;
1129
1130      if( i == (segment.numNodes-1) )
1131      {
1132         if( renderLastPoint )
1133         {
1134            segDir = curPoint - segment.nodeList[i-1].point;
1135         }
1136         else
1137         {
1138            continue;
1139         }
1140      }
1141      else
1142      {
1143         nextPoint = segment.nodeList[i+1].point;
1144         segDir = nextPoint - curPoint;
1145      }
1146      segDir.normalizeSafe();
1147
1148
1149      Point3F dirFromCam = curPoint - camPos;
1150      Point3F crossVec;
1151      mCross(dirFromCam, segDir, &crossVec);
1152      crossVec.normalize();
1153      crossVec *= width * 0.5f;
1154
1155      F32 u = i % 2;
1156
1157      PrimBuild::texCoord2f( u, 1.0 );
1158      PrimBuild::vertex3fv( curPoint - crossVec );
1159
1160      PrimBuild::texCoord2f( u, 0.0 );
1161      PrimBuild::vertex3fv( curPoint + crossVec );
1162   }
1163
1164}
1165
1166//----------------------------------------------------------------------------
1167// Find height
1168//----------------------------------------------------------------------------
1169F32 LightningBolt::findHeight( Point3F &point, SceneManager *sceneManager )
1170{
1171   const Vector< SceneObject*> terrains = sceneManager->getContainer()->getTerrains();
1172   for( Vector< SceneObject* >::const_iterator iter = terrains.begin(); iter != terrains.end(); ++ iter )
1173   {
1174      TerrainBlock* terrain = dynamic_cast< TerrainBlock* >( *iter );
1175      if( !terrain )
1176         continue;
1177
1178      Point3F terrPt = point;
1179      terrain->getWorldTransform().mulP(terrPt);
1180
1181      F32 h;
1182      if( terrain->getHeight( Point2F( terrPt.x, terrPt.y ), &h ) )
1183         return h;
1184   }
1185
1186   return 0.f;
1187}
1188
1189
1190//----------------------------------------------------------------------------
1191// Generate lightning bolt
1192//----------------------------------------------------------------------------
1193void LightningBolt::generate()
1194{
1195   mMajorNodes.startPoint   = startPoint;
1196   mMajorNodes.endPoint     = endPoint;
1197   mMajorNodes.numNodes     = numMajorNodes;
1198   mMajorNodes.maxAngle     = maxMajorAngle;
1199
1200   mMajorNodes.generateNodes();
1201
1202   generateMinorNodes();
1203
1204}
1205
1206//----------------------------------------------------------------------------
1207// Generate Minor Nodes
1208//----------------------------------------------------------------------------
1209void LightningBolt::generateMinorNodes()
1210{
1211   mMinorNodes.clear();
1212
1213   for( S32 i=0; i<mMajorNodes.numNodes - 1; i++ )
1214   {
1215      NodeManager segment;
1216      segment.startPoint = mMajorNodes.nodeList[i].point;
1217      segment.endPoint = mMajorNodes.nodeList[i+1].point;
1218      segment.numNodes = numMinorNodes;
1219      segment.maxAngle = maxMinorAngle;
1220      segment.generateNodes();
1221
1222      mMinorNodes.increment(1);
1223      mMinorNodes[i] = segment;
1224   }
1225}
1226
1227//----------------------------------------------------------------------------
1228// Recursive algo to create bolts that split off from main bolt
1229//----------------------------------------------------------------------------
1230void LightningBolt::createSplit( const Point3F &startingPoint, const Point3F &endingPoint, U32 depth, F32 splitWidth )
1231{
1232   if( depth == 0 )
1233      return;
1234     
1235   F32 chanceToEnd = gRandGen.randF();
1236   if( chanceToEnd > 0.70f )
1237      return;
1238
1239   if(splitWidth < 0.75f )
1240      splitWidth = 0.75f;
1241
1242   VectorF diff = endingPoint - startingPoint;
1243   F32 length = diff.len();
1244   diff.normalizeSafe();
1245
1246   LightningBolt newBolt;
1247   newBolt.startPoint = startingPoint;
1248   newBolt.endPoint = endingPoint;
1249   newBolt.width = splitWidth;
1250   newBolt.numMajorNodes = 3;
1251   newBolt.maxMajorAngle = 30.0f;
1252   newBolt.numMinorNodes = 3;
1253   newBolt.maxMinorAngle = 10.0f;
1254   newBolt.startRender = true;
1255   newBolt.generate();
1256
1257   splitList.pushBack( newBolt );
1258
1259   VectorF newDir1 = MathUtils::randomDir( diff, 10.0f, 45.0f );
1260   Point3F newEndPoint1 = endingPoint + newDir1 * gRandGen.randF( 0.5f, 1.5f ) * length;
1261
1262   VectorF newDir2 = MathUtils::randomDir( diff, 10.0f, 45.0f );
1263   Point3F newEndPoint2 = endingPoint + newDir2 * gRandGen.randF( 0.5f, 1.5f ) * length;
1264
1265   createSplit(endingPoint, newEndPoint1, depth - 1, splitWidth * 0.30f );
1266   createSplit(endingPoint, newEndPoint2, depth - 1, splitWidth * 0.30f );
1267
1268}
1269
1270//----------------------------------------------------------------------------
1271// Start split - kick off the recursive 'createSplit' procedure
1272//----------------------------------------------------------------------------
1273void LightningBolt::startSplits()
1274{
1275
1276   for( U32 i=0; i<mMajorNodes.numNodes-1; i++ )
1277   {
1278      if( gRandGen.randF() > 0.3f )
1279        continue;
1280
1281      Node node = mMajorNodes.nodeList[i];
1282      Node node2 = mMajorNodes.nodeList[i+1];
1283
1284      VectorF segDir = node2.point - node.point;
1285      F32 length = segDir.len();
1286      segDir.normalizeSafe();
1287
1288      VectorF newDir = MathUtils::randomDir( segDir, 20.0f, 40.0f );
1289      Point3F newEndPoint = node.point + newDir * gRandGen.randF( 0.5f, 1.5f ) * length;
1290
1291
1292      createSplit( node.point, newEndPoint, 4, width * 0.30f );
1293   }
1294
1295
1296}
1297
1298//----------------------------------------------------------------------------
1299// Update
1300//----------------------------------------------------------------------------
1301void LightningBolt::update( F32 dt )
1302{
1303   elapsedTime += dt;
1304
1305   F32 percentDone = elapsedTime / lifetime;
1306
1307   if( elapsedTime > fadeTime )
1308   {
1309      isFading = true;
1310      percentFade = percentDone + (fadeTime/<a href="/coding/class/structlightningbolt/#structlightningbolt_1af344b46100a4d07c2260a37e25e98bbe">lifetime</a>);
1311   }
1312
1313   if( elapsedTime > renderTime && !startRender )
1314   {
1315      startRender = true;
1316      isFading = false;
1317      elapsedTime = 0.0f;
1318   }
1319}
1320