lightning.cpp
Engine/source/T3D/fx/lightning.cpp
Classes:
class
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> <a href="/coding/file/editortool_8cpp/#editortool_8cpp_1a4cb041169a32ea3d4cacadbb955e06b4">render</a> the bolt. The strike may be followed by <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</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 href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">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 href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</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 href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> <a href="/coding/class/classlightningstrikeevent/">LightningStrikeEvent</a> which strikes <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</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 href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</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 href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</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 href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</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 href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</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 href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</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 href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</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 href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</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> <a href="/coding/file/editortool_8cpp/#editortool_8cpp_1a4cb041169a32ea3d4cacadbb955e06b4">render</a> the bolt. The strike may be followed by <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</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 href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">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 href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</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 href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> <a href="/coding/class/classlightningstrikeevent/">LightningStrikeEvent</a> which strikes <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</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 href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</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 href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</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 href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</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 href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</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 href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</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 href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</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 href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</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 for (S32 i = 0; i < MaxThunders; i++) 245 thunderSounds[i] = NULL; 246 247 for (S32 i = 0; i < MaxTextures; i++) 248 { 249 strikeTextureNames[i] = NULL; 250 strikeTextures[i] = NULL; 251 } 252 numThunders = 0; 253 mNumStrikeTextures = 0; 254} 255 256LightningData::~LightningData() 257{ 258 259} 260 261//-------------------------------------------------------------------------- 262void LightningData::initPersistFields() 263{ 264 addField( "strikeSound", TYPEID< SFXTrack >(), Offset(strikeSound, LightningData), 265 "Sound profile to play when a lightning strike occurs." ); 266 addField( "thunderSounds", TYPEID< SFXTrack >(), Offset(thunderSounds, LightningData), MaxThunders, 267 "@brief List of thunder sound effects to play.\n\n" 268 "A random one of these sounds will be played shortly after each strike " 269 "occurs." ); 270 addField( "strikeTextures", TypeString, Offset(strikeTextureNames, LightningData), MaxTextures, 271 "List of textures to use to render lightning strikes." ); 272 273 Parent::initPersistFields(); 274} 275 276 277//-------------------------------------------------------------------------- 278bool LightningData::onAdd() 279{ 280 if(!Parent::onAdd()) 281 return false; 282 283 return true; 284} 285 286 287bool LightningData::preload(bool server, String &errorStr) 288{ 289 if (Parent::preload(server, errorStr) == false) 290 return false; 291 292 //dQsort(thunderSounds, MaxThunders, sizeof(SFXTrack*), cmpSounds); 293 294 for (S32 i = 0; i < MaxThunders; i++) { 295 if (thunderSounds[i]!= NULL) numThunders++; 296 } 297 298 if (server == false) 299 { 300 String sfxErrorStr; 301 for (U32 i = 0; i < MaxThunders; i++) { 302 if( !sfxResolve( &thunderSounds[ i ], sfxErrorStr ) ) 303 Con::errorf(ConsoleLogEntry::General, "LightningData::preload: Invalid packet: %s", sfxErrorStr.c_str()); 304 } 305 306 if( !sfxResolve( &strikeSound, sfxErrorStr ) ) 307 Con::errorf(ConsoleLogEntry::General, "LightningData::preload: Invalid packet: %s", sfxErrorStr.c_str()); 308 309 mNumStrikeTextures = 0; 310 for (U32 i = 0; i < MaxTextures; i++) 311 { 312 if (strikeTextureNames[i][0]) 313 { 314 strikeTextures[i] = GFXTexHandle(strikeTextureNames[i], &GFXStaticTextureProfile, avar("%s() - strikeTextures[%d] (line %d)", __FUNCTION__, i, __LINE__)); 315 mNumStrikeTextures++; 316 } 317 } 318 } 319 320 321 return true; 322} 323 324 325//-------------------------------------------------------------------------- 326void LightningData::packData(BitStream* stream) 327{ 328 Parent::packData(stream); 329 330 U32 i; 331 for (i = 0; i < MaxThunders; i++) 332 { 333 if (stream->writeFlag(thunderSounds[i])) 334 sfxWrite(stream, thunderSounds[i]); 335 } 336 337 stream->writeInt(mNumStrikeTextures, 4); 338 339 for (i = 0; i < MaxTextures; i++) 340 stream->writeString(strikeTextureNames[i]); 341 342 sfxWrite( stream, strikeSound ); 343} 344 345void LightningData::unpackData(BitStream* stream) 346{ 347 Parent::unpackData(stream); 348 349 U32 i; 350 for (i = 0; i < MaxThunders; i++) 351 { 352 if (stream->readFlag()) 353 sfxRead(stream, &thunderSounds[i]); 354 else 355 thunderSounds[i] = NULL; 356 } 357 358 mNumStrikeTextures = stream->readInt(4); 359 360 for (i = 0; i < MaxTextures; i++) 361 strikeTextureNames[i] = stream->readSTString(); 362 363 sfxRead( stream, &strikeSound ); 364} 365 366 367//-------------------------------------------------------------------------- 368//-------------------------------------- 369// 370Lightning::Lightning() 371{ 372 mNetFlags.set(Ghostable</a>|<a href="/coding/class/classnetobject/#classnetobject_1ab7f7b94af4c238c69f5673a98199a01caa501b2b8ffe184a0a58342a1b9790258">ScopeAlways); 373 mTypeMask |= StaticObjectType</a>|<a href="/coding/file/objecttypes_8h/#objecttypes_8h_1ac18d4a11e9446825e48fd474d7529084a43891480c40ab2b66f5d17713401a340">EnvironmentObjectType; 374 375 mDataBlock = NULL; 376 mLastThink = 0; 377 378 mStrikeListHead = NULL; 379 mThunderListHead = NULL; 380 381 strikesPerMinute = 12; 382 strikeWidth = 2.5; 383 chanceToHitTarget = 0.5f; 384 strikeRadius = 20.0f; 385 boltStartRadius = 20.0f; 386 color.set( 1.0f, 1.0f, 1.0f, 1.0f ); 387 fadeColor.set( 0.1f, 0.1f, 1.0f, 1.0f ); 388 useFog = true; 389 390 setScale( VectorF( 512.0f, 512.0f, 300.0f ) ); 391} 392 393Lightning::~Lightning() 394{ 395 while( mThunderListHead ) 396 { 397 Thunder* nextThunder = mThunderListHead->next; 398 delete mThunderListHead; 399 mThunderListHead = nextThunder; 400 } 401 402 while( mStrikeListHead ) 403 { 404 Strike* nextStrike = mStrikeListHead->next; 405 delete mStrikeListHead; 406 mStrikeListHead = nextStrike; 407 } 408} 409 410//-------------------------------------------------------------------------- 411void Lightning::initPersistFields() 412{ 413 addGroup( "Strikes" ); 414 addField( "strikesPerMinute", TypeS32, Offset(strikesPerMinute, Lightning), 415 "@brief Number of lightning strikes to perform per minute.\n\n" 416 "Automatically invokes strikeRandomPoint() at regular intervals." ); 417 addField( "strikeWidth", TypeF32, Offset(strikeWidth, Lightning), 418 "Width of a lightning bolt." ); 419 addField( "strikeRadius", TypeF32, Offset(strikeRadius, Lightning), 420 "@brief Horizontal size (XY plane) of the search box used to find and " 421 "damage Player or Vehicle objects within range of the strike.\n\n" 422 "Only the object at highest altitude with a clear line of sight to the " 423 "bolt will be hit." ); 424 endGroup( "Strikes" ); 425 426 addGroup( "Colors" ); 427 addField( "color", TypeColorF, Offset(color, Lightning), 428 "Color to blend the strike texture with." ); 429 addField( "fadeColor", TypeColorF, Offset(fadeColor, Lightning), 430 "@brief Color to blend the strike texture with when the bolt is fading away.\n\n" 431 "Bolts fade away automatically shortly after the strike occurs." ); 432 endGroup( "Colors" ); 433 434 addGroup( "Bolts" ); 435 addField( "chanceToHitTarget", TypeF32, Offset(chanceToHitTarget, Lightning), 436 "Percentage chance (0-1) that a given lightning bolt will hit something." ); 437 addField( "boltStartRadius", TypeF32, Offset(boltStartRadius, Lightning), 438 "@brief Radial distance from the center of the Lightning object for the " 439 "start point of the bolt.\n\n" 440 "The actual start point will be a random point within this radius." ); 441 addField( "useFog", TypeBool, Offset(useFog, Lightning), 442 "Controls whether lightning bolts are affected by fog when they are rendered." ); 443 endGroup( "Bolts" ); 444 445 Parent::initPersistFields(); 446} 447 448//-------------------------------------------------------------------------- 449bool Lightning::onAdd() 450{ 451 if(!Parent::onAdd()) 452 return false; 453 454 mObjBox.minExtents.set( -0.5f, -0.5f, -0.5f ); 455 mObjBox.maxExtents.set( 0.5f, 0.5f, 0.5f ); 456 457 resetWorldBox(); 458 addToScene(); 459 460 return true; 461} 462 463 464void Lightning::onRemove() 465{ 466 removeFromScene(); 467 468 Parent::onRemove(); 469} 470 471 472bool Lightning::onNewDataBlock( GameBaseData *dptr, bool reload ) 473{ 474 mDataBlock = dynamic_cast<LightningData*>( dptr ); 475 if ( !mDataBlock || !Parent::onNewDataBlock( dptr, reload ) ) 476 return false; 477 478 scriptOnNewDataBlock(); 479 return true; 480} 481 482 483//-------------------------------------------------------------------------- 484void Lightning::prepRenderImage( SceneRenderState* state ) 485{ 486 ObjectRenderInst *ri = state->getRenderPass()->allocInst<ObjectRenderInst>(); 487 ri->renderDelegate.bind(this, &Lightning::renderObject); 488 // The Lightning isn't technically foliage but our debug 489 // effect seems to render best as a Foliage type (translucent, 490 // renders itself, no sorting) 491 ri->type = RenderPassManager::RIT_Foliage; 492 state->getRenderPass()->addInst( ri ); 493} 494 495 496void Lightning::renderObject(ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance* overrideMat) 497{ 498 if (overrideMat) 499 return; 500 501 if (mLightningSB.isNull()) 502 { 503 GFXStateBlockDesc desc; 504 desc.setBlend( true, GFXBlendSrcAlpha, GFXBlendOne); 505 desc.setCullMode(GFXCullNone); 506 desc.zWriteEnable = false; 507 desc.vertexColorEnable = true; 508 509 if (mDataBlock->mNumStrikeTextures != 0) 510 { 511 desc.samplersDefined = true; 512 desc.samplers[0].magFilter = GFXTextureFilterLinear; 513 desc.samplers[0].minFilter = GFXTextureFilterLinear; 514 desc.samplers[0].addressModeU = GFXAddressWrap; 515 desc.samplers[0].addressModeV = GFXAddressWrap; 516 } 517 518 mLightningSB = GFX->createStateBlock(desc); 519 520 } 521 522 GFX->setStateBlock(mLightningSB); 523 524 525 Strike* walk = mStrikeListHead; 526 while (walk != NULL) 527 { 528 if (mDataBlock->mNumStrikeTextures > 1) 529 { 530 GFX->setTexture(0, mDataBlock->strikeTextures[sgLightningRand.randI(0, mDataBlock->mNumStrikeTextures - 1)]); 531 } 532 else if (mDataBlock->mNumStrikeTextures > 0) 533 { 534 GFX->setTexture(0, mDataBlock->strikeTextures[0]); 535 } 536 537 for( U32 i=0; i<MAX_LIGHTNING; i++ ) 538 { 539 if( walk->bolt[i].isFading ) 540 { 541 F32 alpha = 1.0f - walk->bolt[i].percentFade; 542 if( alpha < 0.0f ) alpha = 0.0f; 543 PrimBuild::color4f( fadeColor.red, fadeColor.green, fadeColor.blue, alpha ); 544 } 545 else 546 { 547 PrimBuild::color4f( color.red, color.green, color.blue, color.alpha ); 548 } 549 walk->bolt[i].render( state->getCameraPosition() ); 550 } 551 552 walk = walk->next; 553 } 554 555 //GFX->setZWriteEnable(true); 556 //GFX->setAlphaTestEnable(false); 557 //GFX->setAlphaBlendEnable(false); 558} 559 560void Lightning::scheduleThunder(Strike* newStrike) 561{ 562 AssertFatal(isClientObject(), "Lightning::scheduleThunder: server objects should not enter this version of the function"); 563 564 // If no thunder sounds, don't schedule anything! 565 if (mDataBlock->numThunders == 0) 566 return; 567 568 GameConnection* connection = GameConnection::getConnectionToServer(); 569 if (connection) { 570 MatrixF cameraMatrix; 571 572 if (connection->getControlCameraTransform(0, &cameraMatrix)) { 573 Point3F worldPos; 574 cameraMatrix.getColumn(3, &worldPos); 575 576 worldPos.x -= newStrike->xVal; 577 worldPos.y -= newStrike->yVal; 578 worldPos.z = 0.0f; 579 580 F32 dist = worldPos.len(); 581 F32 t = dist / 330.0f; 582 583 // Ok, we need to schedule a random strike sound t secs in the future... 584 // 585 if (t <= 0.03f) { 586 // If it's really close, just play it... 587 U32 thunder = sgLightningRand.randI(0, mDataBlock->numThunders - 1); 588 SFX->playOnce(mDataBlock->thunderSounds[thunder]); 589 } else { 590 Thunder* pThunder = new Thunder; 591 pThunder->tRemaining = t; 592 pThunder->next = mThunderListHead; 593 mThunderListHead = pThunder; 594 } 595 } 596 } 597} 598 599 600//-------------------------------------------------------------------------- 601void Lightning::processTick(const Move* move) 602{ 603 Parent::processTick(move); 604 605 if (isServerObject() && !isHidden()) { 606 S32 msBetweenStrikes = (S32)(60.0 / strikesPerMinute * 1000.0); 607 608 mLastThink += TickMs; 609 if( mLastThink > msBetweenStrikes ) 610 { 611 strikeRandomPoint(); 612 mLastThink -= msBetweenStrikes; 613 } 614 } 615} 616 617void Lightning::interpolateTick(F32 dt) 618{ 619 Parent::interpolateTick(dt); 620} 621 622void Lightning::advanceTime(F32 dt) 623{ 624 Parent::advanceTime(dt); 625 626 Strike** pWalker = &mStrikeListHead; 627 while (*pWalker != NULL) { 628 Strike* pStrike = *pWalker; 629 630 for( U32 i=0; i<MAX_LIGHTNING; i++ ) 631 { 632 pStrike->bolt[i].update( dt ); 633 } 634 635 pStrike->currentAge += dt; 636 if (pStrike->currentAge > pStrike->deathAge) { 637 *pWalker = pStrike->next; 638 delete pStrike; 639 } else { 640 pWalker = &((*pWalker)->next); 641 } 642 } 643 644 Thunder** pThunderWalker = &mThunderListHead; 645 while (*pThunderWalker != NULL) { 646 Thunder* pThunder = *pThunderWalker; 647 648 pThunder->tRemaining -= dt; 649 if (pThunder->tRemaining <= 0.0f) { 650 *pThunderWalker = pThunder->next; 651 delete pThunder; 652 653 // Play the sound... 654 U32 thunder = sgLightningRand.randI(0, mDataBlock->numThunders - 1); 655 SFX->playOnce(mDataBlock->thunderSounds[thunder]); 656 } else { 657 pThunderWalker = &((*pThunderWalker)->next); 658 } 659 } 660} 661 662 663//-------------------------------------------------------------------------- 664void Lightning::processEvent(LightningStrikeEvent* pEvent) 665{ 666 AssertFatal(pEvent->mStart.x >= 0.0f && pEvent->mStart.x <= 1.0f, "Out of bounds coord!"); 667 668 Strike* pStrike = new Strike; 669 670 Point3F strikePoint; 671 strikePoint.zero(); 672 673 if( pEvent->mTarget ) 674 { 675 Point3F objectCenter; 676 pEvent->mTarget->getObjBox().getCenter( &objectCenter ); 677 objectCenter.convolve( pEvent->mTarget->getScale() ); 678 pEvent->mTarget->getTransform().mulP( objectCenter ); 679 680 strikePoint = objectCenter; 681 } 682 else 683 { 684 strikePoint.x = pEvent->mStart.x; 685 strikePoint.y = pEvent->mStart.y; 686 strikePoint *= mObjScale; 687 strikePoint += getPosition(); 688 strikePoint += Point3F( -mObjScale.x * 0.5f, -mObjScale.y * 0.5f, 0.0f ); 689 690 RayInfo rayInfo; 691 Point3F start = strikePoint; 692 start.z = mObjScale.z * 0.5f + getPosition().z; 693 strikePoint.z += -mObjScale.z * 0.5f; 694 bool rayHit = gClientContainer.castRay( start, strikePoint, 695 (STATIC_COLLISION_TYPEMASK | WaterObjectType), 696 &rayInfo); 697 if( rayHit ) 698 { 699 strikePoint.z = rayInfo.point.z; 700 } 701 else 702 { 703 strikePoint.z = pStrike->bolt[0].findHeight( strikePoint, getSceneManager() ); 704 } 705 } 706 707 pStrike->xVal = strikePoint.x; 708 pStrike->yVal = strikePoint.y; 709 710 pStrike->deathAge = 1.6f; 711 pStrike->currentAge = 0.0f; 712 pStrike->next = mStrikeListHead; 713 714 for( U32 i=0; i<MAX_LIGHTNING; i++ ) 715 { 716 F32 randStart = boltStartRadius; 717 F32 height = mObjScale.z * 0.5f + getPosition().z; 718 pStrike->bolt[i].startPoint.set( pStrike->xVal + gRandGen.randF( -randStart, randStart ), pStrike->yVal + gRandGen.randF( -randStart, randStart ), height ); 719 pStrike->bolt[i].endPoint = strikePoint; 720 pStrike->bolt[i].width = strikeWidth; 721 pStrike->bolt[i].numMajorNodes = 10; 722 pStrike->bolt[i].maxMajorAngle = 30.0f; 723 pStrike->bolt[i].numMinorNodes = 4; 724 pStrike->bolt[i].maxMinorAngle = 15.0f; 725 pStrike->bolt[i].generate(); 726 pStrike->bolt[i].startSplits(); 727 pStrike->bolt[i].lifetime = 1.0f; 728 pStrike->bolt[i].fadeTime = 0.2f; 729 pStrike->bolt[i].renderTime = gRandGen.randF(0.0f, 0.25f); 730 } 731 732 mStrikeListHead = pStrike; 733 734 scheduleThunder(pStrike); 735 736 MatrixF trans(true); 737 trans.setPosition( strikePoint ); 738 739 if (mDataBlock->strikeSound) 740 { 741 SFX->playOnce(mDataBlock->strikeSound, &trans ); 742 } 743 744} 745 746void Lightning::warningFlashes() 747{ 748 AssertFatal(isServerObject(), "Error, client objects may not initiate lightning!"); 749 750 Point3F strikePoint( gRandGen.randF( 0.0f, 1.0f ), gRandGen.randF( 0.0f, 1.0f ), 0.0f ); 751 752 SimGroup* pClientGroup = Sim::getClientGroup(); 753 for (SimGroup::iterator itr = pClientGroup->begin(); itr != pClientGroup->end(); itr++) { 754 NetConnection* nc = static_cast<NetConnection*>(*itr); 755 if (nc != NULL) 756 { 757 LightningStrikeEvent* pEvent = new LightningStrikeEvent; 758 pEvent->mLightning = this; 759 760 pEvent->mStart.x = strikePoint.x; 761 pEvent->mStart.y = strikePoint.y; 762 763 nc->postNetEvent(pEvent); 764 } 765 } 766} 767 768void Lightning::strikeRandomPoint() 769{ 770 AssertFatal(isServerObject(), "Error, client objects may not initiate lightning!"); 771 772 773 Point3F strikePoint( gRandGen.randF( 0.0f, 1.0f ), gRandGen.randF( 0.0f, 1.0f ), 0.0f ); 774 775 // check if an object is within target range 776 Point3F worldPosStrikePoint = strikePoint; 777 778 worldPosStrikePoint *= mObjScale; 779 worldPosStrikePoint += getPosition(); 780 worldPosStrikePoint += Point3F( -mObjScale.x * 0.5f, -mObjScale.y * 0.5f, 0.0f ); 781 782 Box3F queryBox; 783 F32 boxWidth = strikeRadius * 2.0f; 784 785 queryBox.minExtents.set( -boxWidth * 0.5f, -boxWidth * 0.5f, -mObjScale.z * 0.5f ); 786 queryBox.maxExtents.set( boxWidth * 0.5f, boxWidth * 0.5f, mObjScale.z * 0.5f ); 787 queryBox.minExtents += worldPosStrikePoint; 788 queryBox.maxExtents += worldPosStrikePoint; 789 790 SimpleQueryList sql; 791 getContainer()->findObjects(queryBox, DAMAGEABLE_TYPEMASK, 792 SimpleQueryList::insertionCallback, &sql); 793 794 SceneObject *highestObj = NULL; 795 F32 highestPnt = 0.0f; 796 797 for( U32 i = 0; i < sql.mList.size(); i++ ) 798 { 799 Point3F objectCenter; 800 sql.mList[i]->getObjBox().getCenter(&objectCenter); 801 objectCenter.convolve(sql.mList[i]->getScale()); 802 sql.mList[i]->getTransform().mulP(objectCenter); 803 804 // check if object can be struck 805 806 RayInfo rayInfo; 807 Point3F start = objectCenter; 808 start.z = mObjScale.z * 0.5f + getPosition().z; 809 Point3F end = objectCenter; 810 end.z = -mObjScale.z * 0.5f + getPosition().z; 811 bool rayHit = gServerContainer.castRay( start, end, 812 (0xFFFFFFFF), 813 &rayInfo); 814 815 if( rayHit && rayInfo.object == sql.mList[i] ) 816 { 817 if( !highestObj ) 818 { 819 highestObj = sql.mList[i]; 820 highestPnt = objectCenter.z; 821 continue; 822 } 823 824 if( objectCenter.z > highestPnt ) 825 { 826 highestObj = sql.mList[i]; 827 highestPnt = objectCenter.z; 828 } 829 } 830 831 832 } 833 834 // hah haaaaa, we have a target! 835 SceneObject *targetObj = NULL; 836 if( highestObj ) 837 { 838 F32 chance = gRandGen.randF(); 839 if( chance <= chanceToHitTarget ) 840 { 841 Point3F objectCenter; 842 highestObj->getObjBox().getCenter(&objectCenter); 843 objectCenter.convolve(highestObj->getScale()); 844 highestObj->getTransform().mulP(objectCenter); 845 846 bool playerInWarmup = false; 847 Player *playerObj = dynamic_cast< Player * >(highestObj); 848 if( playerObj ) 849 { 850 if( !playerObj->getControllingClient() ) 851 { 852 playerInWarmup = true; 853 } 854 } 855 856 if( !playerInWarmup ) 857 { 858 applyDamage_callback( objectCenter, VectorF( 0.0f, 0.0f, 1.0f ), highestObj ); 859 targetObj = highestObj; 860 } 861 } 862 } 863 864 SimGroup* pClientGroup = Sim::getClientGroup(); 865 for (SimGroup::iterator itr = pClientGroup->begin(); itr != pClientGroup->end(); itr++) 866 { 867 NetConnection* nc = static_cast<NetConnection*>(*itr); 868 869 LightningStrikeEvent* pEvent = new LightningStrikeEvent; 870 pEvent->mLightning = this; 871 872 pEvent->mStart.x = strikePoint.x; 873 pEvent->mStart.y = strikePoint.y; 874 pEvent->mTarget = targetObj; 875 876 nc->postNetEvent(pEvent); 877 } 878 879 880} 881 882//-------------------------------------------------------------------------- 883void Lightning::strikeObject(ShapeBase* targetObj) 884{ 885 AssertFatal(isServerObject(), "Error, client objects may not initiate lightning!"); 886 887 Point3F strikePoint = targetObj->getPosition(); 888 Point3F objectCenter; 889 890 Box3F wb = getWorldBox(); 891 if (!wb.isContained(strikePoint)) 892 return; 893 894 Point3F targetRel = strikePoint - getPosition(); 895 Point3F length(wb.len_x() / 2.0f, wb.len_y() / 2.0f, wb.len_z() / 2.0f); 896 897 Point3F strikePos = targetRel / length; 898 899 bool playerInWarmup = false; 900 Player *playerObj = dynamic_cast< Player * >(targetObj); 901 if (playerObj) 902 { 903 if (!playerObj->getControllingClient()) 904 { 905 playerInWarmup = true; 906 } 907 } 908 909 if (!playerInWarmup) 910 { 911 applyDamage_callback(targetObj->getWorldSphere().center, VectorF(0.0, 0.0, 1.0), targetObj); 912 } 913 914 SimGroup* pClientGroup = Sim::getClientGroup(); 915 for (SimGroup::iterator itr = pClientGroup->begin(); itr != pClientGroup->end(); itr++) { 916 NetConnection* nc = static_cast<NetConnection*>(*itr); 917 if (nc != NULL) 918 { 919 LightningStrikeEvent* pEvent = new LightningStrikeEvent; 920 pEvent->mLightning = this; 921 922 pEvent->mStart.x = strikePoint.x; 923 pEvent->mStart.y = strikePoint.y; 924 pEvent->mTarget = targetObj; 925 926 nc->postNetEvent(pEvent); 927 } 928 } 929} 930 931//-------------------------------------------------------------------------- 932U32 Lightning::packUpdate(NetConnection* con, U32 mask, BitStream* stream) 933{ 934 U32 retMask = Parent::packUpdate(con, mask, stream); 935 936 // Only write data if this is the initial packet or we've been inspected. 937 if (stream->writeFlag(mask & (InitialUpdateMask | ExtendedInfoMask))) 938 { 939 // Initial update 940 mathWrite(*stream, getPosition()); 941 mathWrite(*stream, mObjScale); 942 943 stream->write(strikeWidth); 944 stream->write(chanceToHitTarget); 945 stream->write(strikeRadius); 946 stream->write(boltStartRadius); 947 stream->write(color.red); 948 stream->write(color.green); 949 stream->write(color.blue); 950 stream->write(color.alpha); 951 stream->write(fadeColor.red); 952 stream->write(fadeColor.green); 953 stream->write(fadeColor.blue); 954 stream->write(useFog); 955 stream->write(strikesPerMinute); 956 } 957 958 return retMask; 959} 960 961//-------------------------------------------------------------------------- 962void Lightning::unpackUpdate(NetConnection* con, BitStream* stream) 963{ 964 Parent::unpackUpdate(con, stream); 965 966 if (stream->readFlag()) 967 { 968 // Initial update 969 Point3F pos; 970 mathRead(*stream, &pos); 971 setPosition( pos ); 972 973 mathRead(*stream, &mObjScale); 974 975 stream->read(&strikeWidth); 976 stream->read(&chanceToHitTarget); 977 stream->read(&strikeRadius); 978 stream->read(&boltStartRadius); 979 stream->read(&color.red); 980 stream->read(&color.green); 981 stream->read(&color.blue); 982 stream->read(&color.alpha); 983 stream->read(&fadeColor.red); 984 stream->read(&fadeColor.green); 985 stream->read(&fadeColor.blue); 986 stream->read(&useFog); 987 stream->read(&strikesPerMinute); 988 } 989} 990 991//-------------------------------------------------------------------------- 992 993DefineEngineMethod(Lightning, warningFlashes, void, (),, 994 "@brief Creates a LightningStrikeEvent that triggers harmless lightning " 995 "bolts on all clients.\n" 996 "No objects will be damaged by these bolts.\n" 997 "@tsexample\n" 998 "// Generate a harmless lightning strike effect on all clients\n" 999 "%lightning.warningFlashes();\n" 1000 "@endtsexample" ) 1001{ 1002 if (object->isServerObject()) 1003 object->warningFlashes(); 1004} 1005 1006DefineEngineMethod(Lightning, strikeRandomPoint, void, (),, 1007 "Creates a LightningStrikeEvent which attempts to strike and damage a random " 1008 "object in range of the Lightning object.\n" 1009 "@tsexample\n" 1010 "// Generate a damaging lightning strike effect on all clients\n" 1011 "%lightning.strikeRandomPoint();\n" 1012 "@endtsexample" ) 1013{ 1014 if (object->isServerObject()) 1015 object->strikeRandomPoint(); 1016} 1017 1018DefineEngineMethod(Lightning, strikeObject, void, (ShapeBase* pSB), (nullAsType<ShapeBase*>()), 1019 "Creates a LightningStrikeEvent which strikes a specific object.\n" 1020 "@note This method is currently unimplemented.\n" ) 1021{ 1022 object->strikeObject(pSB); 1023} 1024 1025//************************************************************************** 1026// Lightning Bolt 1027//************************************************************************** 1028LightningBolt::LightningBolt() 1029{ 1030 width = 0.1f; 1031 startPoint.zero(); 1032 endPoint.zero(); 1033 chanceOfSplit = 0.0f; 1034 isFading = false; 1035 elapsedTime = 0.0f; 1036 lifetime = 1.0f; 1037 startRender = false; 1038 endPoint.zero(); 1039 width = 1; 1040 numMajorNodes = 10; 1041 maxMajorAngle = 30.0f; 1042 numMinorNodes = 4; 1043 maxMinorAngle = 15.0f; 1044 fadeTime = 0.2f; 1045 renderTime = 0.125; 1046 dMemset(&mMajorNodes, 0, sizeof(mMajorNodes)); 1047 percentFade = 0.0f; 1048} 1049 1050//-------------------------------------------------------------------------- 1051// Destructor 1052//-------------------------------------------------------------------------- 1053LightningBolt::~LightningBolt() 1054{ 1055 splitList.clear(); 1056} 1057 1058//-------------------------------------------------------------------------- 1059// Generate nodes 1060//-------------------------------------------------------------------------- 1061void LightningBolt::NodeManager::generateNodes() 1062{ 1063 F32 overallDist = VectorF( endPoint - startPoint ).magnitudeSafe(); 1064 F32 minDistBetweenNodes = overallDist / (numNodes-1); 1065 F32 maxDistBetweenNodes = minDistBetweenNodes / mCos( maxAngle * M_PI_F / 180.0f ); 1066 1067 VectorF mainLineDir = endPoint - startPoint; 1068 mainLineDir.normalizeSafe(); 1069 1070 for( U32 i=0; i<numNodes; i++ ) 1071 { 1072 Node node; 1073 1074 if( i == 0 ) 1075 { 1076 node.point = startPoint; 1077 node.dirToMainLine = mainLineDir; 1078 nodeList[i] = node; 1079 continue; 1080 } 1081 if( i == numNodes - 1 ) 1082 { 1083 node.point = endPoint; 1084 nodeList[i] = node; 1085 break; 1086 } 1087 1088 Node lastNode = nodeList[i-1]; 1089 1090 F32 segmentLength = gRandGen.randF( minDistBetweenNodes, maxDistBetweenNodes ); 1091 VectorF segmentDir = MathUtils::randomDir( lastNode.dirToMainLine, 0, maxAngle ); 1092 node.point = lastNode.point + segmentDir * segmentLength; 1093 1094 node.dirToMainLine = endPoint - node.point; 1095 node.dirToMainLine.normalizeSafe(); 1096 nodeList[i] = node; 1097 } 1098} 1099 1100 1101//-------------------------------------------------------------------------- 1102// Render bolt 1103//-------------------------------------------------------------------------- 1104void LightningBolt::render( const Point3F &camPos ) 1105{ 1106 if (!startRender) 1107 return; 1108 1109 if (!isFading) 1110 generateMinorNodes(); 1111 1112 U32 maxVerts = 0; 1113 for (U32 i = 0; i < mMinorNodes.size(); i++) 1114 maxVerts += mMinorNodes[i].numNodes * 2; 1115 1116 PrimBuild::begin(GFXTriangleStrip, maxVerts); 1117 1118 for (U32 i = 0; i < mMinorNodes.size(); i++) 1119 { 1120 if (i+1 == mMinorNodes.size()) 1121 renderSegment(mMinorNodes[i], camPos, true); 1122 else 1123 renderSegment(mMinorNodes[i], camPos, false); 1124 } 1125 1126 PrimBuild::end(); 1127 1128 for(LightingBoltList::Iterator i = splitList.begin(); i != splitList.end(); ++i) 1129 { 1130 if( isFading ) 1131 { 1132 i->isFading = true; 1133 } 1134 i->render( camPos ); 1135 } 1136 1137} 1138 1139//-------------------------------------------------------------------------- 1140// Render segment 1141//-------------------------------------------------------------------------- 1142void LightningBolt::renderSegment( NodeManager &segment, const Point3F &camPos, bool renderLastPoint ) 1143{ 1144 1145 for (U32 i = 0; i < segment.numNodes; i++) 1146 { 1147 Point3F curPoint = segment.nodeList[i].point; 1148 1149 Point3F nextPoint; 1150 Point3F segDir; 1151 1152 if( i == (segment.numNodes-1) ) 1153 { 1154 if( renderLastPoint ) 1155 { 1156 segDir = curPoint - segment.nodeList[i-1].point; 1157 } 1158 else 1159 { 1160 continue; 1161 } 1162 } 1163 else 1164 { 1165 nextPoint = segment.nodeList[i+1].point; 1166 segDir = nextPoint - curPoint; 1167 } 1168 segDir.normalizeSafe(); 1169 1170 1171 Point3F dirFromCam = curPoint - camPos; 1172 Point3F crossVec; 1173 mCross(dirFromCam, segDir, &crossVec); 1174 crossVec.normalize(); 1175 crossVec *= width * 0.5f; 1176 1177 F32 u = i % 2; 1178 1179 PrimBuild::texCoord2f( u, 1.0 ); 1180 PrimBuild::vertex3fv( curPoint - crossVec ); 1181 1182 PrimBuild::texCoord2f( u, 0.0 ); 1183 PrimBuild::vertex3fv( curPoint + crossVec ); 1184 } 1185 1186} 1187 1188//---------------------------------------------------------------------------- 1189// Find height 1190//---------------------------------------------------------------------------- 1191F32 LightningBolt::findHeight( Point3F &point, SceneManager *sceneManager ) 1192{ 1193 const Vector< SceneObject*> terrains = sceneManager->getContainer()->getTerrains(); 1194 for( Vector< SceneObject* >::const_iterator iter = terrains.begin(); iter != terrains.end(); ++ iter ) 1195 { 1196 TerrainBlock* terrain = dynamic_cast< TerrainBlock* >( *iter ); 1197 if( !terrain ) 1198 continue; 1199 1200 Point3F terrPt = point; 1201 terrain->getWorldTransform().mulP(terrPt); 1202 1203 F32 h; 1204 if( terrain->getHeight( Point2F( terrPt.x, terrPt.y ), &h ) ) 1205 return h; 1206 } 1207 1208 return 0.f; 1209} 1210 1211 1212//---------------------------------------------------------------------------- 1213// Generate lightning bolt 1214//---------------------------------------------------------------------------- 1215void LightningBolt::generate() 1216{ 1217 mMajorNodes.startPoint = startPoint; 1218 mMajorNodes.endPoint = endPoint; 1219 mMajorNodes.numNodes = numMajorNodes; 1220 mMajorNodes.maxAngle = maxMajorAngle; 1221 1222 mMajorNodes.generateNodes(); 1223 1224 generateMinorNodes(); 1225 1226} 1227 1228//---------------------------------------------------------------------------- 1229// Generate Minor Nodes 1230//---------------------------------------------------------------------------- 1231void LightningBolt::generateMinorNodes() 1232{ 1233 mMinorNodes.clear(); 1234 1235 for( S32 i=0; i<mMajorNodes.numNodes - 1; i++ ) 1236 { 1237 NodeManager segment; 1238 segment.startPoint = mMajorNodes.nodeList[i].point; 1239 segment.endPoint = mMajorNodes.nodeList[i+1].point; 1240 segment.numNodes = numMinorNodes; 1241 segment.maxAngle = maxMinorAngle; 1242 segment.generateNodes(); 1243 1244 mMinorNodes.increment(1); 1245 mMinorNodes[i] = segment; 1246 } 1247} 1248 1249//---------------------------------------------------------------------------- 1250// Recursive algo to create bolts that split off from main bolt 1251//---------------------------------------------------------------------------- 1252void LightningBolt::createSplit( const Point3F &startingPoint, const Point3F &endingPoint, U32 depth, F32 splitWidth ) 1253{ 1254 if( depth == 0 ) 1255 return; 1256 1257 F32 chanceToEnd = gRandGen.randF(); 1258 if( chanceToEnd > 0.70f ) 1259 return; 1260 1261 if(splitWidth < 0.75f ) 1262 splitWidth = 0.75f; 1263 1264 VectorF diff = endingPoint - startingPoint; 1265 F32 length = diff.len(); 1266 diff.normalizeSafe(); 1267 1268 LightningBolt newBolt; 1269 newBolt.startPoint = startingPoint; 1270 newBolt.endPoint = endingPoint; 1271 newBolt.width = splitWidth; 1272 newBolt.numMajorNodes = 3; 1273 newBolt.maxMajorAngle = 30.0f; 1274 newBolt.numMinorNodes = 3; 1275 newBolt.maxMinorAngle = 10.0f; 1276 newBolt.startRender = true; 1277 newBolt.generate(); 1278 1279 splitList.pushBack( newBolt ); 1280 1281 VectorF newDir1 = MathUtils::randomDir( diff, 10.0f, 45.0f ); 1282 Point3F newEndPoint1 = endingPoint + newDir1 * gRandGen.randF( 0.5f, 1.5f ) * length; 1283 1284 VectorF newDir2 = MathUtils::randomDir( diff, 10.0f, 45.0f ); 1285 Point3F newEndPoint2 = endingPoint + newDir2 * gRandGen.randF( 0.5f, 1.5f ) * length; 1286 1287 createSplit(endingPoint, newEndPoint1, depth - 1, splitWidth * 0.30f ); 1288 createSplit(endingPoint, newEndPoint2, depth - 1, splitWidth * 0.30f ); 1289 1290} 1291 1292//---------------------------------------------------------------------------- 1293// Start split - kick off the recursive 'createSplit' procedure 1294//---------------------------------------------------------------------------- 1295void LightningBolt::startSplits() 1296{ 1297 1298 for( U32 i=0; i<mMajorNodes.numNodes-1; i++ ) 1299 { 1300 if( gRandGen.randF() > 0.3f ) 1301 continue; 1302 1303 Node node = mMajorNodes.nodeList[i]; 1304 Node node2 = mMajorNodes.nodeList[i+1]; 1305 1306 VectorF segDir = node2.point - node.point; 1307 F32 length = segDir.len(); 1308 segDir.normalizeSafe(); 1309 1310 VectorF newDir = MathUtils::randomDir( segDir, 20.0f, 40.0f ); 1311 Point3F newEndPoint = node.point + newDir * gRandGen.randF( 0.5f, 1.5f ) * length; 1312 1313 1314 createSplit( node.point, newEndPoint, 4, width * 0.30f ); 1315 } 1316 1317 1318} 1319 1320//---------------------------------------------------------------------------- 1321// Update 1322//---------------------------------------------------------------------------- 1323void LightningBolt::update( F32 dt ) 1324{ 1325 elapsedTime += dt; 1326 1327 F32 percentDone = elapsedTime / lifetime; 1328 1329 if( elapsedTime > fadeTime ) 1330 { 1331 isFading = true; 1332 percentFade = percentDone + (fadeTime/<a href="/coding/class/structlightningbolt/#structlightningbolt_1af344b46100a4d07c2260a37e25e98bbe">lifetime</a>); 1333 } 1334 1335 if( elapsedTime > renderTime && !startRender ) 1336 { 1337 startRender = true; 1338 isFading = false; 1339 elapsedTime = 0.0f; 1340 } 1341} 1342