Public Functions
ConsoleDocClass(Item , "@brief Base <a href="/coding/class/classitem/">Item</a> class. Uses the <a href="/coding/class/structitemdata/">ItemData</a> datablock <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> common <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">properties.\n\n</a>" "Items represent an object in the world)
ConsoleDocClass(ItemData , "@brief Stores properties <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> an individual <a href="/coding/class/classitem/">Item</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">type.\n\n</a>" "Items represent an object in the world, usually one that the player will interact with. " "One example is a health kit on the group that is automatically picked up when the player " "comes into contact with <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">it.\n\n</a>" "<a href="/coding/class/structitemdata/">ItemData</a> provides the common properties <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> a set of Items. These properties include a " "DTS or DAE model used <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> render the <a href="/coding/class/classitem/">Item</a> in the world, its physical properties <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> when the " "<a href="/coding/class/classitem/">Item</a> interacts with the world, and any lights that emit " "from the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Item.\n\n</a>" " @<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">tsexample\n</a>" "datablock <a href="/coding/class/structitemdata/">ItemData</a>(HealthKitSmall)\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "{\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " category=\"Health\";\n" " className = \"HealthPatch\";\n" " shapeFile = \"art/shapes/items/kit/healthkit.dts\";\n" " gravityMod = \"1.0\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " mass = 2;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " friction = 1;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " elasticity = 0.3;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " density = 2;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " drag = 0.5;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " maxVelocity = \"10.0\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " emap = true;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " sticky = false;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " dynamicType = \"0\"\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>;" " lightOnlyStatic = false;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " lightType = \"NoLight\";\n" " lightColor = \"1.0 1.0 1.0 1.0\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " lightTime = 1000;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " lightRadius = 10.0;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " simpleServerCollision = true;" " // Dynamic properties used by the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">scripts\n\n</a>" " pickupName = \"a small health kit\";\n" " repairAmount = 50;\<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>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">gameObjects\n</a>" )
DefineEngineMethod(Item , getLastStickyNormal , const char * , () )
DefineEngineMethod(Item , getLastStickyPos , const char * , () )
DefineEngineMethod(Item , isAtRest , bool , () , "@brief Is the object at rest (ie, no longer moving)?\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n\n</a>" "@return True <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a2594a51175f310ed96ad6cd7d6514878">if</a> the object is at rest)
DefineEngineMethod(Item , isRotating , bool , () , "@brief Is the object still rotating?\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n\n</a>" "@return True <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a2594a51175f310ed96ad6cd7d6514878">if</a> the object is still rotating)
DefineEngineMethod(Item , isStatic , bool , () , "@brief Is the object static (ie, non-movable)?\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n\n</a>" "@return True <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a2594a51175f310ed96ad6cd7d6514878">if</a> the object is static)
DefineEngineMethod(Item , setCollisionTimeout , bool , (S32 ignoreColObj) , (NULL) , "@brief Temporarily disable collisions against a specific <a href="/coding/class/classshapebase/">ShapeBase</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">object.\n\n</a>" "This is useful <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> prevent a player from immediately picking up an <a href="/coding/class/classitem/">Item</a> they have " "just thrown. Only one object may be on the timeout list at a time. The timeout is " "defined as 15 <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">ticks.\n\n</a>" "@param objectID <a href="/coding/class/classshapebase/">ShapeBase</a> object ID <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> disable collisions <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">against.\n</a>" "@return Returns true <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a2594a51175f310ed96ad6cd7d6514878">if</a> the <a href="/coding/class/classshapebase/">ShapeBase</a> object requested could be found)
IMPLEMENT_CALLBACK(Item , onEnterLiquid , void , (const char *objID, F32 waterCoverage, const char *liquidType) , (objID, waterCoverage, liquidType) , "Informs an <a href="/coding/class/classitem/">Item</a> object that it has entered liquid, along with information about the liquid <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">type.\n</a>" " @param objID <a href="/coding/file/gizmo_8h/#gizmo_8h_1a10fcd3bee2ea25191e31795e36bdeba1a5df911aaca43421a25e32c3002befbc4">Object</a> ID <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> this <a href="/coding/class/classitem/">Item</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">object.\n</a>" " @param waterCoverage How much coverage of water this <a href="/coding/class/classitem/">Item</a> object <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">has.\n</a>" " @param liquidType The type of liquid that this <a href="/coding/class/classitem/">Item</a> object has <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">entered.\n</a>" " @note Server side <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">only.\n</a>" " @see Item, <a href="/coding/class/structitemdata/">ItemData</a> , <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">WaterObject\n</a>" )
IMPLEMENT_CALLBACK(Item , onLeaveLiquid , void , (const char *objID, const char *liquidType) , (objID, liquidType) , "Informs an <a href="/coding/class/classitem/">Item</a> object that it has left a liquid, along with information about the liquid <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">type.\n</a>" " @param objID <a href="/coding/file/gizmo_8h/#gizmo_8h_1a10fcd3bee2ea25191e31795e36bdeba1a5df911aaca43421a25e32c3002befbc4">Object</a> ID <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> this <a href="/coding/class/classitem/">Item</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">object.\n</a>" " @param liquidType The type of liquid that this <a href="/coding/class/classitem/">Item</a> object has <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">left.\n</a>" " @note Server side <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">only.\n</a>" " @see Item, <a href="/coding/class/structitemdata/">ItemData</a> , <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">WaterObject\n</a>" )
IMPLEMENT_CALLBACK(Item , onStickyCollision , void , (const char *objID) , (objID) , "@brief Informs the <a href="/coding/class/classitem/">Item</a> object that it is now sticking <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> another <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">object.\n\n</a>" "This callback is only called <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a2594a51175f310ed96ad6cd7d6514878">if</a> the <a href="/coding/class/structitemdata/#structitemdata_1a351749d1f39ac3c5342334188c44c8c3">ItemData::sticky</a> property <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> this <a href="/coding/class/classitem/">Item</a> is <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">true.\n</a>" "@param objID <a href="/coding/file/gizmo_8h/#gizmo_8h_1a10fcd3bee2ea25191e31795e36bdeba1a5df911aaca43421a25e32c3002befbc4">Object</a> ID this <a href="/coding/class/classitem/">Item</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">object.\n</a>" "@note Server side <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">only.\n</a>" "@see Item, <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">ItemData\n</a>" )
IMPLEMENT_CO_DATABLOCK_V1(ItemData )
IMPLEMENT_CO_NETOBJECT_V1(Item )
ImplementEnumType(ItemLightType , "@brief The type of light the <a href="/coding/class/classitem/">Item</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">has\n\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">gameObjects\n\n</a>" )
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/item.h"
26
27#include "core/stream/bitStream.h"
28#include "math/mMath.h"
29#include "console/console.h"
30#include "console/consoleTypes.h"
31#include "sim/netConnection.h"
32#include "collision/boxConvex.h"
33#include "collision/earlyOutPolyList.h"
34#include "collision/extrudedPolyList.h"
35#include "math/mPolyhedron.h"
36#include "math/mathIO.h"
37#include "lighting/lightInfo.h"
38#include "lighting/lightManager.h"
39#include "T3D/physics/physicsPlugin.h"
40#include "T3D/physics/physicsBody.h"
41#include "T3D/physics/physicsCollision.h"
42#include "ts/tsShapeInstance.h"
43#include "console/engineAPI.h"
44
45
46const F32 sRotationSpeed = 6.0f; // Secs/Rotation
47const F32 sAtRestVelocity = 0.15f; // Min speed after collision
48const S32 sCollisionTimeout = 15; // Timout value in ticks
49
50// Client prediction
51static F32 sMinWarpTicks = 0.5 ; // Fraction of tick at which instant warp occures
52static S32 sMaxWarpTicks = 3; // Max warp duration in ticks
53
54F32 Item::mGravity = -20.0f;
55
56const U32 sClientCollisionMask = (TerrainObjectType |
57 StaticShapeObjectType |
58 VehicleObjectType |
59 PlayerObjectType);
60
61const U32 sServerCollisionMask = (sClientCollisionMask);
62
63const S32 Item::csmAtRestTimer = 64;
64
65//----------------------------------------------------------------------------
66
67IMPLEMENT_CO_DATABLOCK_V1(ItemData);
68
69ConsoleDocClass( ItemData,
70 "@brief Stores properties for an individual Item type.\n\n"
71
72 "Items represent an object in the world, usually one that the player will interact with. "
73 "One example is a health kit on the group that is automatically picked up when the player "
74 "comes into contact with it.\n\n"
75
76 "ItemData provides the common properties for a set of Items. These properties include a "
77 "DTS or DAE model used to render the Item in the world, its physical properties for when the "
78 "Item interacts with the world (such as being tossed by the player), and any lights that emit "
79 "from the Item.\n\n"
80
81 "@tsexample\n"
82 "datablock ItemData(HealthKitSmall)\n"
83 "{\n"
84 " category =\"Health\";\n"
85 " className = \"HealthPatch\";\n"
86 " shapeFile = \"art/shapes/items/kit/healthkit.dts\";\n"
87 " gravityMod = \"1.0\";\n"
88 " mass = 2;\n"
89 " friction = 1;\n"
90 " elasticity = 0.3;\n"
91 " density = 2;\n"
92 " drag = 0.5;\n"
93 " maxVelocity = \"10.0\";\n"
94 " emap = true;\n"
95 " sticky = false;\n"
96 " dynamicType = \"0\"\n;"
97 " lightOnlyStatic = false;\n"
98 " lightType = \"NoLight\";\n"
99 " lightColor = \"1.0 1.0 1.0 1.0\";\n"
100 " lightTime = 1000;\n"
101 " lightRadius = 10.0;\n"
102 " simpleServerCollision = true;"
103 " // Dynamic properties used by the scripts\n\n"
104 " pickupName = \"a small health kit\";\n"
105 " repairAmount = 50;\n"
106 "};\n"
107 "@endtsexample\n"
108
109 "@ingroup gameObjects\n"
110);
111
112
113ItemData::ItemData()
114{
115 shadowEnable = true;
116
117
118 friction = 0;
119 elasticity = 0;
120
121 sticky = false;
122 gravityMod = 1.0;
123 maxVelocity = 25.0f;
124
125 density = 2;
126 drag = 0.5;
127
128 lightOnlyStatic = false;
129 lightType = Item::NoLight;
130 lightColor.set(1.f,1.f,1.f,1.f);
131 lightTime = 1000;
132 lightRadius = 10.f;
133
134 simpleServerCollision = true;
135}
136
137ImplementEnumType( ItemLightType,
138 "@brief The type of light the Item has\n\n"
139 "@ingroup gameObjects\n\n")
140 { Item::NoLight, "NoLight", "The item has no light attached.\n" },
141 { Item::ConstantLight, "ConstantLight", "The item has a constantly emitting light attached.\n" },
142 { Item::PulsingLight, "PulsingLight", "The item has a pulsing light attached.\n" }
143EndImplementEnumType;
144
145void ItemData::initPersistFields()
146{
147 addField("friction", TypeF32, Offset(friction, ItemData), "A floating-point value specifying how much velocity is lost to impact and sliding friction.");
148 addField("elasticity", TypeF32, Offset(elasticity, ItemData), "A floating-point value specifying how 'bouncy' this ItemData is.");
149 addField("sticky", TypeBool, Offset(sticky, ItemData),
150 "@brief If true, ItemData will 'stick' to any surface it collides with.\n\n"
151 "When an item does stick to a surface, the Item::onStickyCollision() callback is called. The Item has methods to retrieve "
152 "the world position and normal the Item is stuck to.\n"
153 "@note Valid objects to stick to must be of StaticShapeObjectType.\n");
154 addField("gravityMod", TypeF32, Offset(gravityMod, ItemData), "Floating point value to multiply the existing gravity with, just for this ItemData.");
155 addField("maxVelocity", TypeF32, Offset(maxVelocity, ItemData), "Maximum velocity that this ItemData is able to move.");
156
157 addField("lightType", TYPEID< Item::LightType >(), Offset(lightType, ItemData), "Type of light to apply to this ItemData. Options are NoLight, ConstantLight, PulsingLight. Default is NoLight." );
158 addField("lightColor", TypeColorF, Offset(lightColor, ItemData),
159 "@brief Color value to make this light. Example: \"1.0,1.0,1.0\"\n\n"
160 "@see lightType\n");
161 addField("lightTime", TypeS32, Offset(lightTime, ItemData),
162 "@brief Time value for the light of this ItemData, used to control the pulse speed of the PulsingLight LightType.\n\n"
163 "@see lightType\n");
164 addField("lightRadius", TypeF32, Offset(lightRadius, ItemData),
165 "@brief Distance from the center point of this ItemData for the light to affect\n\n"
166 "@see lightType\n");
167 addField("lightOnlyStatic", TypeBool, Offset(lightOnlyStatic, ItemData),
168 "@brief If true, this ItemData will only cast a light if the Item for this ItemData has a static value of true.\n\n"
169 "@see lightType\n");
170
171 addField("simpleServerCollision", TypeBool, Offset(simpleServerCollision, ItemData),
172 "@brief Determines if only simple server-side collision will be used (for pick ups).\n\n"
173 "If set to true then only simple, server-side collision detection will be used. This is often the case "
174 "if the item is used for a pick up object, such as ammo. If set to false then a full collision volume "
175 "will be used as defined by the shape. The default is true.\n"
176 "@note Only applies when using a physics library.\n"
177 "@see TurretShape and ProximityMine for examples that should set this to false to allow them to be "
178 "shot by projectiles.\n");
179
180 Parent::initPersistFields();
181}
182
183void ItemData::packData(BitStream* stream)
184{
185 Parent::packData(stream);
186 stream->writeFloat(friction, 10);
187 stream->writeFloat(elasticity, 10);
188 stream->writeFlag(sticky);
189 if(stream->writeFlag(gravityMod != 1.0))
190 stream->writeFloat(gravityMod, 10);
191 if(stream->writeFlag(maxVelocity != -1))
192 stream->write(maxVelocity);
193
194 if(stream->writeFlag(lightType != Item::NoLight))
195 {
196 AssertFatal(Item::NumLightTypes < (1 << 2), "ItemData: light type needs more bits");
197 stream->writeInt(lightType, 2);
198 stream->writeFloat(lightColor.red, 7);
199 stream->writeFloat(lightColor.green, 7);
200 stream->writeFloat(lightColor.blue, 7);
201 stream->writeFloat(lightColor.alpha, 7);
202 stream->write(lightTime);
203 stream->write(lightRadius);
204 stream->writeFlag(lightOnlyStatic);
205 }
206
207 stream->writeFlag(simpleServerCollision);
208}
209
210void ItemData::unpackData(BitStream* stream)
211{
212 Parent::unpackData(stream);
213 friction = stream->readFloat(10);
214 elasticity = stream->readFloat(10);
215 sticky = stream->readFlag();
216 if(stream->readFlag())
217 gravityMod = stream->readFloat(10);
218 else
219 gravityMod = 1.0;
220
221 if(stream->readFlag())
222 stream->read(&maxVelocity);
223 else
224 maxVelocity = -1;
225
226 if(stream->readFlag())
227 {
228 lightType = stream->readInt(2);
229 lightColor.red = stream->readFloat(7);
230 lightColor.green = stream->readFloat(7);
231 lightColor.blue = stream->readFloat(7);
232 lightColor.alpha = stream->readFloat(7);
233 stream->read(&lightTime);
234 stream->read(&lightRadius);
235 lightOnlyStatic = stream->readFlag();
236 }
237 else
238 lightType = Item::NoLight;
239
240 simpleServerCollision = stream->readFlag();
241}
242
243
244//----------------------------------------------------------------------------
245
246IMPLEMENT_CO_NETOBJECT_V1(Item);
247
248ConsoleDocClass( Item,
249 "@brief Base Item class. Uses the ItemData datablock for common properties.\n\n"
250
251 "Items represent an object in the world, usually one that the player will interact with. "
252 "One example is a health kit on the group that is automatically picked up when the player "
253 "comes into contact with it.\n\n"
254
255 "@tsexample\n"
256 "// This is the \"health patch\" dropped by a dying player.\n"
257 "datablock ItemData(HealthKitPatch)\n"
258 "{\n"
259 " // Mission editor category, this datablock will show up in the\n"
260 " // specified category under the \"shapes\" root category.\n"
261 " category = \"Health\";\n\n"
262 " className = \"HealthPatch\";\n\n"
263 " // Basic Item properties\n"
264 " shapeFile = \"art/shapes/items/patch/healthpatch.dts\";\n"
265 " mass = 2;\n"
266 " friction = 1;\n"
267 " elasticity = 0.3;\n"
268 " emap = true;\n\n"
269 " // Dynamic properties used by the scripts\n"
270 " pickupName = \"a health patch\";\n"
271 " repairAmount = 50;\n"
272 "};\n\n"
273
274 "%obj = new Item()\n"
275 "{\n"
276 " dataBlock = HealthKitSmall;\n"
277 " parentGroup = EWCreatorWindow.objectGroup;\n"
278 " static = true;\n"
279 " rotate = true;\n"
280 "};\n"
281 "@endtsexample\n\n"
282
283 "@see ItemData\n"
284
285 "@ingroup gameObjects\n"
286);
287
288IMPLEMENT_CALLBACK( Item, onStickyCollision, void, ( const char* objID ),( objID ),
289 "@brief Informs the Item object that it is now sticking to another object.\n\n"
290 "This callback is only called if the ItemData::sticky property for this Item is true.\n"
291 "@param objID Object ID this Item object.\n"
292 "@note Server side only.\n"
293 "@see Item, ItemData\n"
294);
295
296IMPLEMENT_CALLBACK( Item, onEnterLiquid, void, ( const char* objID, F32 waterCoverage, const char* liquidType ),( objID, waterCoverage, liquidType ),
297 "Informs an Item object that it has entered liquid, along with information about the liquid type.\n"
298 "@param objID Object ID for this Item object.\n"
299 "@param waterCoverage How much coverage of water this Item object has.\n"
300 "@param liquidType The type of liquid that this Item object has entered.\n"
301 "@note Server side only.\n"
302 "@see Item, ItemData, WaterObject\n"
303);
304
305IMPLEMENT_CALLBACK( Item, onLeaveLiquid, void, ( const char* objID, const char* liquidType ),( objID, liquidType ),
306 "Informs an Item object that it has left a liquid, along with information about the liquid type.\n"
307 "@param objID Object ID for this Item object.\n"
308 "@param liquidType The type of liquid that this Item object has left.\n"
309 "@note Server side only.\n"
310 "@see Item, ItemData, WaterObject\n"
311);
312
313
314Item::Item()
315{
316 mTypeMask |= ItemObjectType | DynamicShapeObjectType;
317 mDataBlock = 0;
318 mStatic = false;
319 mRotate = false;
320 mVelocity = VectorF(0,0,0);
321 mAtRest = true;
322 mAtRestCounter = 0;
323 mInLiquid = false;
324 delta.warpTicks = 0;
325 delta.dt = 1;
326 mCollisionObject = 0;
327 mCollisionTimeout = 0;
328 mPhysicsRep = NULL;
329
330 mConvex.init(this);
331 mWorkingQueryBox.minExtents.set(-1e9, -1e9, -1e9);
332 mWorkingQueryBox.maxExtents.set(-1e9, -1e9, -1e9);
333
334 mLight = NULL;
335
336 mSubclassItemHandlesScene = false;
337}
338
339Item::~Item()
340{
341 SAFE_DELETE(mLight);
342}
343
344
345//----------------------------------------------------------------------------
346
347bool Item::onAdd()
348{
349 if (!Parent::onAdd() || !mDataBlock)
350 return false;
351
352 if (mStatic)
353 mAtRest = true;
354 mObjToWorld.getColumn(3,&delta.pos);
355
356 // Setup the box for our convex object...
357 mObjBox.getCenter(&mConvex.mCenter);
358 mConvex.mSize.x = mObjBox.len_x() / 2.0;
359 mConvex.mSize.y = mObjBox.len_y() / 2.0;
360 mConvex.mSize.z = mObjBox.len_z() / 2.0;
361 mWorkingQueryBox.minExtents.set(-1e9, -1e9, -1e9);
362 mWorkingQueryBox.maxExtents.set(-1e9, -1e9, -1e9);
363
364 if( !isHidden() && !mSubclassItemHandlesScene )
365 addToScene();
366
367 if (isServerObject())
368 {
369 if (!mSubclassItemHandlesScene)
370 scriptOnAdd();
371 }
372 else if (mDataBlock->lightType != NoLight)
373 {
374 mDropTime = Sim::getCurrentTime();
375 }
376
377 _updatePhysics();
378
379 return true;
380}
381
382void Item::_updatePhysics()
383{
384 SAFE_DELETE( mPhysicsRep );
385
386 if ( !PHYSICSMGR )
387 return;
388
389 if (mDataBlock->simpleServerCollision)
390 {
391 // We only need the trigger on the server.
392 if ( isServerObject() )
393 {
394 PhysicsCollision *colShape = PHYSICSMGR->createCollision();
395 colShape->addBox( mObjBox.getExtents() * 0.5f, MatrixF::Identity );
396
397 PhysicsWorld *world = PHYSICSMGR->getWorld( isServerObject() ? "server" : "client" );
398 mPhysicsRep = PHYSICSMGR->createBody();
399 mPhysicsRep->init( colShape, 0, PhysicsBody::BF_TRIGGER | PhysicsBody::BF_KINEMATIC, this, world );
400 mPhysicsRep->setTransform( getTransform() );
401 }
402 }
403 else
404 {
405 if ( !mShapeInstance )
406 return;
407
408 PhysicsCollision* colShape = mShapeInstance->getShape()->buildColShape( false, getScale() );
409
410 if ( colShape )
411 {
412 PhysicsWorld *world = PHYSICSMGR->getWorld( isServerObject() ? "server" : "client" );
413 mPhysicsRep = PHYSICSMGR->createBody();
414 mPhysicsRep->init( colShape, 0, PhysicsBody::BF_KINEMATIC, this, world );
415 mPhysicsRep->setTransform( getTransform() );
416 }
417 }
418}
419
420bool Item::onNewDataBlock( GameBaseData *dptr, bool reload )
421{
422 mDataBlock = dynamic_cast<ItemData*>(dptr);
423 if (!mDataBlock || !Parent::onNewDataBlock(dptr,reload))
424 return false;
425
426 if (!mSubclassItemHandlesScene)
427 scriptOnNewDataBlock();
428
429 if ( isProperlyAdded() )
430 _updatePhysics();
431
432 return true;
433}
434
435void Item::onRemove()
436{
437 mWorkingQueryBox.minExtents.set(-1e9, -1e9, -1e9);
438 mWorkingQueryBox.maxExtents.set(-1e9, -1e9, -1e9);
439
440 SAFE_DELETE( mPhysicsRep );
441
442 if (!mSubclassItemHandlesScene)
443 {
444 scriptOnRemove();
445 removeFromScene();
446 }
447
448 Parent::onRemove();
449}
450
451void Item::onDeleteNotify( SimObject *obj )
452{
453 if ( obj == mCollisionObject )
454 {
455 mCollisionObject = NULL;
456 mCollisionTimeout = 0;
457 }
458
459 Parent::onDeleteNotify( obj );
460}
461
462// Lighting: -----------------------------------------------------------------
463
464void Item::registerLights(LightManager * lightManager, bool lightingScene)
465{
466 if(lightingScene)
467 return;
468
469 if(mDataBlock->lightOnlyStatic && !mStatic)
470 return;
471
472 F32 intensity;
473 switch(mDataBlock->lightType)
474 {
475 case ConstantLight:
476 intensity = mFadeVal;
477 break;
478
479 case PulsingLight:
480 {
481 S32 delta = Sim::getCurrentTime() - mDropTime;
482 intensity = 0.5f + 0.5f * mSin(M_PI_F * F32(delta) / F32(mDataBlock->lightTime));
483 intensity = 0.15f + intensity * 0.85f;
484 intensity *= mFadeVal; // fade out light on flags
485 break;
486 }
487
488 default:
489 return;
490 }
491
492 // Create a light if needed
493 if (!mLight)
494 {
495 mLight = lightManager->createLightInfo();
496 }
497 mLight->setColor( mDataBlock->lightColor * intensity );
498 mLight->setType( LightInfo::Point );
499 mLight->setRange( mDataBlock->lightRadius );
500 mLight->setPosition( getBoxCenter() );
501
502 lightManager->registerGlobalLight( mLight, this );
503}
504
505
506//----------------------------------------------------------------------------
507
508Point3F Item::getVelocity() const
509{
510 return mVelocity;
511}
512
513void Item::setVelocity(const VectorF& vel)
514{
515 mVelocity = vel;
516
517 // Clamp against the maximum velocity.
518 if ( mDataBlock->maxVelocity > 0 )
519 {
520 F32 len = mVelocity.magnitudeSafe();
521 if ( len > mDataBlock->maxVelocity )
522 {
523 Point3F excess = mVelocity * ( 1.0f - (mDataBlock->maxVelocity / len ) );
524 mVelocity -= excess;
525 }
526 }
527
528 setMaskBits(PositionMask);
529 mAtRest = false;
530 mAtRestCounter = 0;
531}
532
533void Item::applyImpulse(const Point3F&,const VectorF& vec)
534{
535 // Items ignore angular velocity
536 VectorF vel;
537 vel.x = vec.x / mDataBlock->mass;
538 vel.y = vec.y / mDataBlock->mass;
539 vel.z = vec.z / mDataBlock->mass;
540 setVelocity(vel);
541}
542
543void Item::setCollisionTimeout(ShapeBase* obj)
544{
545 if (mCollisionObject)
546 clearNotify(mCollisionObject);
547 deleteNotify(obj);
548 mCollisionObject = obj;
549 mCollisionTimeout = sCollisionTimeout;
550 setMaskBits(ThrowSrcMask);
551}
552
553
554//----------------------------------------------------------------------------
555
556void Item::processTick(const Move* move)
557{
558 Parent::processTick(move);
559
560 if ( isMounted() )
561 return;
562
563 //
564 if (mCollisionObject && !--mCollisionTimeout)
565 mCollisionObject = 0;
566
567 // Warp to catch up to server
568 if (delta.warpTicks > 0)
569 {
570 delta.warpTicks--;
571
572 // Set new pos.
573 MatrixF mat = mObjToWorld;
574 mat.getColumn(3,&delta.pos);
575 delta.pos += delta.warpOffset;
576 mat.setColumn(3,delta.pos);
577 Parent::setTransform(mat);
578
579 // Backstepping
580 delta.posVec.x = -delta.warpOffset.x;
581 delta.posVec.y = -delta.warpOffset.y;
582 delta.posVec.z = -delta.warpOffset.z;
583 }
584 else
585 {
586 if (isServerObject() && mAtRest && (mStatic == false && mDataBlock->sticky == false))
587 {
588 if (++mAtRestCounter > csmAtRestTimer)
589 {
590 mAtRest = false;
591 mAtRestCounter = 0;
592 setMaskBits(PositionMask);
593 }
594 }
595
596 if (!mStatic && !mAtRest && isHidden() == false)
597 {
598 updateVelocity(TickSec);
599 updateWorkingCollisionSet(isGhost() ? sClientCollisionMask : sServerCollisionMask, TickSec);
600 updatePos(isGhost() ? sClientCollisionMask : sServerCollisionMask, TickSec);
601 }
602 else
603 {
604 // Need to clear out last updatePos or warp interpolation
605 delta.posVec.set(0,0,0);
606 }
607 }
608}
609
610void Item::interpolateTick(F32 dt)
611{
612 Parent::interpolateTick(dt);
613 if ( isMounted() )
614 return;
615
616 // Client side interpolation
617 Point3F pos = delta.pos + delta.posVec * dt;
618 MatrixF mat = mRenderObjToWorld;
619 mat.setColumn(3,pos);
620 setRenderTransform(mat);
621 delta.dt = dt;
622}
623
624
625//----------------------------------------------------------------------------
626
627void Item::setTransform(const MatrixF& mat)
628{
629 Point3F pos;
630 mat.getColumn(3,&pos);
631 MatrixF tmat;
632 if (!mRotate) {
633 // Forces all rotation to be around the z axis
634 VectorF vec;
635 mat.getColumn(1,&vec);
636 tmat.set(EulerF(0,0,-mAtan2(-vec.x,vec.y)));
637 }
638 else
639 tmat.identity();
640 tmat.setColumn(3,pos);
641 Parent::setTransform(tmat);
642 if (!mStatic)
643 {
644 mAtRest = false;
645 mAtRestCounter = 0;
646 }
647
648 if ( mPhysicsRep )
649 mPhysicsRep->setTransform( getTransform() );
650
651 setMaskBits(RotationMask | PositionMask | NoWarpMask);
652}
653
654
655//----------------------------------------------------------------------------
656void Item::updateWorkingCollisionSet(const U32 mask, const F32 dt)
657{
658 // It is assumed that we will never accelerate more than 10 m/s for gravity...
659 //
660 Point3F scaledVelocity = mVelocity * dt;
661 F32 len = scaledVelocity.len();
662 F32 newLen = len + (10 * dt);
663
664 // Check to see if it is actually necessary to construct the new working list,
665 // or if we can use the cached version from the last query. We use the x
666 // component of the min member of the mWorkingQueryBox, which is lame, but
667 // it works ok.
668 bool updateSet = false;
669
670 Box3F convexBox = mConvex.getBoundingBox(getTransform(), getScale());
671 F32 l = (newLen * 1.1) + 0.1; // from Convex::updateWorkingList
672 convexBox.minExtents -= Point3F(l, l, l);
673 convexBox.maxExtents += Point3F(l, l, l);
674
675 // Check containment
676 {
677 if (mWorkingQueryBox.minExtents.x != -1e9)
678 {
679 if (mWorkingQueryBox.isContained(convexBox) == false)
680 {
681 // Needed region is outside the cached region. Update it.
682 updateSet = true;
683 }
684 else
685 {
686 // We can leave it alone, we're still inside the cached region
687 }
688 }
689 else
690 {
691 // Must update
692 updateSet = true;
693 }
694 }
695
696 // Actually perform the query, if necessary
697 if (updateSet == true)
698 {
699 mWorkingQueryBox = convexBox;
700 mWorkingQueryBox.minExtents -= Point3F(2 * l, 2 * l, 2 * l);
701 mWorkingQueryBox.maxExtents += Point3F(2 * l, 2 * l, 2 * l);
702
703 disableCollision();
704 if (mCollisionObject)
705 mCollisionObject->disableCollision();
706
707 mConvex.updateWorkingList(mWorkingQueryBox, mask);
708
709 if (mCollisionObject)
710 mCollisionObject->enableCollision();
711 enableCollision();
712 }
713}
714
715void Item::updateVelocity(const F32 dt)
716{
717 // Acceleration due to gravity
718 mVelocity.z += (mGravity * mDataBlock->gravityMod) * dt;
719 F32 len;
720 if (mDataBlock->maxVelocity > 0 && (len = mVelocity.len()) > (mDataBlock->maxVelocity * 1.05)) {
721 Point3F excess = mVelocity * (1.0 - (mDataBlock->maxVelocity / len ));
722 excess *= 0.1f;
723 mVelocity -= excess;
724 }
725
726 // Container buoyancy & drag
727 mVelocity.z -= mBuoyancy * (mGravity * mDataBlock->gravityMod * mGravityMod) * dt;
728 mVelocity -= mVelocity * mDrag * dt;
729}
730
731
732void Item::updatePos(const U32 /*mask*/, const F32 dt)
733{
734 // Try and move
735 Point3F pos;
736 mObjToWorld.getColumn(3,&pos);
737 delta.posVec = pos;
738
739 bool contact = false;
740 bool nonStatic = false;
741 bool stickyNotify = false;
742 CollisionList collisionList;
743 F32 time = dt;
744
745 static Polyhedron sBoxPolyhedron;
746 static ExtrudedPolyList sExtrudedPolyList;
747 static EarlyOutPolyList sEarlyOutPolyList;
748 MatrixF collisionMatrix(true);
749 Point3F end = pos + mVelocity * time;
750 U32 mask = isServerObject() ? sServerCollisionMask : sClientCollisionMask;
751
752 // Part of our speed problem here is that we don't track contact surfaces, like we do
753 // with the player. In order to handle the most common and performance impacting
754 // instance of this problem, we'll use a ray cast to detect any contact surfaces below
755 // us. This won't be perfect, but it only needs to catch a few of these to make a
756 // big difference. We'll cast from the top center of the bounding box at the tick's
757 // beginning to the bottom center of the box at the end.
758 Point3F startCast((mObjBox.minExtents.x + mObjBox.maxExtents.x) * 0.5,
759 (mObjBox.minExtents.y + mObjBox.maxExtents.y) * 0.5,
760 mObjBox.maxExtents.z);
761 Point3F endCast((mObjBox.minExtents.x + mObjBox.maxExtents.x) * 0.5,
762 (mObjBox.minExtents.y + mObjBox.maxExtents.y) * 0.5,
763 mObjBox.minExtents.z);
764 collisionMatrix.setColumn(3, pos);
765 collisionMatrix.mulP(startCast);
766 collisionMatrix.setColumn(3, end);
767 collisionMatrix.mulP(endCast);
768 RayInfo rinfo;
769 bool doToughCollision = true;
770 disableCollision();
771 if (mCollisionObject)
772 mCollisionObject->disableCollision();
773 if (getContainer()->castRay(startCast, endCast, mask, &rinfo))
774 {
775 F32 bd = -mDot(mVelocity, rinfo.normal);
776
777 if (bd >= 0.0)
778 {
779 // Contact!
780 if (mDataBlock->sticky && rinfo.object->getTypeMask() & (STATIC_COLLISION_TYPEMASK)) {
781 mVelocity.set(0, 0, 0);
782 mAtRest = true;
783 mAtRestCounter = 0;
784 stickyNotify = true;
785 mStickyCollisionPos = rinfo.point;
786 mStickyCollisionNormal = rinfo.normal;
787 doToughCollision = false;;
788 } else {
789 // Subtract out velocity into surface and friction
790 VectorF fv = mVelocity + rinfo.normal * bd;
791 F32 fvl = fv.len();
792 if (fvl) {
793 F32 ff = bd * mDataBlock->friction;
794 if (ff < fvl) {
795 fv *= ff / fvl;
796 fvl = ff;
797 }
798 }
799 bd *= 1 + mDataBlock->elasticity;
800 VectorF dv = rinfo.normal * (bd + 0.002);
801 mVelocity += dv;
802 mVelocity -= fv;
803
804 // Keep track of what we hit
805 contact = true;
806 U32 typeMask = rinfo.object->getTypeMask();
807 if (!(typeMask & StaticObjectType))
808 nonStatic = true;
809 if (isServerObject() && (typeMask & ShapeBaseObjectType)) {
810 ShapeBase* col = static_cast<ShapeBase*>(rinfo.object);
811 queueCollision(col,mVelocity - col->getVelocity());
812 }
813 }
814 }
815 }
816 enableCollision();
817 if (mCollisionObject)
818 mCollisionObject->enableCollision();
819
820 if (doToughCollision)
821 {
822 U32 count;
823 for (count = 0; count < 3; count++)
824 {
825 // Build list from convex states here...
826 end = pos + mVelocity * time;
827
828
829 collisionMatrix.setColumn(3, end);
830 Box3F wBox = getObjBox();
831 collisionMatrix.mul(wBox);
832 Box3F testBox = wBox;
833 Point3F oldMin = testBox.minExtents;
834 Point3F oldMax = testBox.maxExtents;
835 testBox.minExtents.setMin(oldMin + (mVelocity * time));
836 testBox.maxExtents.setMin(oldMax + (mVelocity * time));
837
838 sEarlyOutPolyList.clear();
839 sEarlyOutPolyList.mNormal.set(0,0,0);
840 sEarlyOutPolyList.mPlaneList.setSize(6);
841 sEarlyOutPolyList.mPlaneList[0].set(wBox.minExtents,VectorF(-1,0,0));
842 sEarlyOutPolyList.mPlaneList[1].set(wBox.maxExtents,VectorF(0,1,0));
843 sEarlyOutPolyList.mPlaneList[2].set(wBox.maxExtents,VectorF(1,0,0));
844 sEarlyOutPolyList.mPlaneList[3].set(wBox.minExtents,VectorF(0,-1,0));
845 sEarlyOutPolyList.mPlaneList[4].set(wBox.minExtents,VectorF(0,0,-1));
846 sEarlyOutPolyList.mPlaneList[5].set(wBox.maxExtents,VectorF(0,0,1));
847
848 CollisionWorkingList& eorList = mConvex.getWorkingList();
849 CollisionWorkingList* eopList = eorList.wLink.mNext;
850 while (eopList != &eorList) {
851 if ((eopList->mConvex->getObject()->getTypeMask() & mask) != 0)
852 {
853 Box3F convexBox = eopList->mConvex->getBoundingBox();
854 if (testBox.isOverlapped(convexBox))
855 {
856 eopList->mConvex->getPolyList(&sEarlyOutPolyList);
857 if (sEarlyOutPolyList.isEmpty() == false)
858 break;
859 }
860 }
861 eopList = eopList->wLink.mNext;
862 }
863 if (sEarlyOutPolyList.isEmpty())
864 {
865 pos = end;
866 break;
867 }
868
869 collisionMatrix.setColumn(3, pos);
870 sBoxPolyhedron.buildBox(collisionMatrix, mObjBox, true);
871
872 // Build extruded polyList...
873 VectorF vector = end - pos;
874 sExtrudedPolyList.extrude(sBoxPolyhedron, vector);
875 sExtrudedPolyList.setVelocity(mVelocity);
876 sExtrudedPolyList.setCollisionList(&collisionList);
877
878 CollisionWorkingList& rList = mConvex.getWorkingList();
879 CollisionWorkingList* pList = rList.wLink.mNext;
880 while (pList != &rList) {
881 if ((pList->mConvex->getObject()->getTypeMask() & mask) != 0)
882 {
883 Box3F convexBox = pList->mConvex->getBoundingBox();
884 if (testBox.isOverlapped(convexBox))
885 {
886 pList->mConvex->getPolyList(&sExtrudedPolyList);
887 }
888 }
889 pList = pList->wLink.mNext;
890 }
891
892 if (collisionList.getTime() < 1.0)
893 {
894 // Set to collision point
895 F32 dt = time * collisionList.getTime();
896 pos += mVelocity * dt;
897 time -= dt;
898
899 // Pick the most resistant surface
900 F32 bd = 0;
901 const Collision* collision = 0;
902 for (S32 c = 0; c < collisionList.getCount(); c++) {
903 const Collision &cp = collisionList[c];
904 F32 dot = -mDot(mVelocity,cp.normal);
905 if (dot > bd) {
906 bd = dot;
907 collision = &cp;
908 }
909 }
910
911 if (collision && mDataBlock->sticky && collision->object->getTypeMask() & (STATIC_COLLISION_TYPEMASK)) {
912 mVelocity.set(0, 0, 0);
913 mAtRest = true;
914 mAtRestCounter = 0;
915 stickyNotify = true;
916 mStickyCollisionPos = collision->point;
917 mStickyCollisionNormal = collision->normal;
918 break;
919 } else {
920 // Subtract out velocity into surface and friction
921 if (collision) {
922 VectorF fv = mVelocity + collision->normal * bd;
923 F32 fvl = fv.len();
924 if (fvl) {
925 F32 ff = bd * mDataBlock->friction;
926 if (ff < fvl) {
927 fv *= ff / fvl;
928 fvl = ff;
929 }
930 }
931 bd *= 1 + mDataBlock->elasticity;
932 VectorF dv = collision->normal * (bd + 0.002);
933 mVelocity += dv;
934 mVelocity -= fv;
935
936 // Keep track of what we hit
937 contact = true;
938 U32 typeMask = collision->object->getTypeMask();
939 if (!(typeMask & StaticObjectType))
940 nonStatic = true;
941 if (isServerObject() && (typeMask & ShapeBaseObjectType)) {
942 ShapeBase* col = static_cast<ShapeBase*>(collision->object);
943 queueCollision(col,mVelocity - col->getVelocity());
944 }
945 }
946 }
947 }
948 else
949 {
950 pos = end;
951 break;
952 }
953 }
954 if (count == 3)
955 {
956 // Couldn't move...
957 mVelocity.set(0, 0, 0);
958 }
959 }
960
961 // If on the client, calculate delta for backstepping
962 if (isGhost()) {
963 delta.pos = pos;
964 delta.posVec -= pos;
965 delta.dt = 1;
966 }
967
968 // Update transform
969 MatrixF mat = mObjToWorld;
970 mat.setColumn(3,pos);
971 Parent::setTransform(mat);
972 enableCollision();
973 if (mCollisionObject)
974 mCollisionObject->enableCollision();
975 updateContainer();
976
977 if ( mPhysicsRep )
978 mPhysicsRep->setTransform( mat );
979
980 //
981 if (contact) {
982 // Check for rest condition
983 if (!nonStatic && mVelocity.len() < sAtRestVelocity) {
984 mVelocity.x = mVelocity.y = mVelocity.z = 0;
985 mAtRest = true;
986 mAtRestCounter = 0;
987 }
988
989 // Only update the client if we hit a non-static shape or
990 // if this is our final rest pos.
991 if (nonStatic || mAtRest)
992 setMaskBits(PositionMask);
993 }
994
995 // Collision callbacks. These need to be processed whether we hit
996 // anything or not.
997 if (!isGhost())
998 {
999 SimObjectPtr<Item> safePtr(this);
1000 if (stickyNotify)
1001 {
1002 notifyCollision();
1003 if(bool(safePtr))
1004 onStickyCollision_callback( getIdString() );
1005 }
1006 else
1007 notifyCollision();
1008
1009 // water
1010 if(bool(safePtr))
1011 {
1012 if(!mInLiquid && mWaterCoverage != 0.0f)
1013 {
1014 onEnterLiquid_callback( getIdString(), mWaterCoverage, mLiquidType.c_str() );
1015 mInLiquid = true;
1016 }
1017 else if(mInLiquid && mWaterCoverage == 0.0f)
1018 {
1019 onLeaveLiquid_callback(getIdString(), mLiquidType.c_str());
1020 mInLiquid = false;
1021 }
1022 }
1023 }
1024}
1025
1026
1027//----------------------------------------------------------------------------
1028
1029static MatrixF IMat(1);
1030
1031bool Item::buildPolyList(PolyListContext context, AbstractPolyList* polyList, const Box3F&, const SphereF&)
1032{
1033 if ( context == PLC_Decal )
1034 return false;
1035
1036 // Collision with the item is always against the item's object
1037 // space bounding box axis aligned in world space.
1038 Point3F pos;
1039 mObjToWorld.getColumn(3,&pos);
1040 IMat.setColumn(3,pos);
1041 polyList->setTransform(&IMat, mObjScale);
1042 polyList->setObject(this);
1043 polyList->addBox(mObjBox);
1044 return true;
1045}
1046
1047
1048//----------------------------------------------------------------------------
1049
1050U32 Item::packUpdate(NetConnection *connection, U32 mask, BitStream *stream)
1051{
1052 U32 retMask = Parent::packUpdate(connection,mask,stream);
1053
1054 if (stream->writeFlag(mask & InitialUpdateMask)) {
1055 stream->writeFlag(mRotate);
1056 stream->writeFlag(mStatic);
1057 if (stream->writeFlag(getScale() != Point3F(1, 1, 1)))
1058 mathWrite(*stream, getScale());
1059 }
1060
1061 if (mask & ThrowSrcMask && mCollisionObject) {
1062 S32 gIndex = connection->getGhostIndex(mCollisionObject);
1063 if (stream->writeFlag(gIndex != -1))
1064 stream->writeInt(gIndex,NetConnection::GhostIdBitSize);
1065 }
1066 else
1067 stream->writeFlag(false);
1068
1069 if (stream->writeFlag(mask & RotationMask && !mRotate)) {
1070 // Assumes rotation is about the Z axis
1071 AngAxisF aa(mObjToWorld);
1072 stream->writeFlag(aa.axis.z < 0);
1073 stream->write(aa.angle);
1074 }
1075
1076 if (stream->writeFlag(mask & PositionMask)) {
1077 Point3F pos;
1078 mObjToWorld.getColumn(3,&pos);
1079 mathWrite(*stream, pos);
1080 if (!stream->writeFlag(mAtRest)) {
1081 mathWrite(*stream, mVelocity);
1082 }
1083 stream->writeFlag(!(mask & NoWarpMask));
1084 }
1085 return retMask;
1086}
1087
1088void Item::unpackUpdate(NetConnection *connection, BitStream *stream)
1089{
1090 Parent::unpackUpdate(connection,stream);
1091
1092 // InitialUpdateMask
1093 if (stream->readFlag()) {
1094 mRotate = stream->readFlag();
1095 mStatic = stream->readFlag();
1096 if (stream->readFlag())
1097 mathRead(*stream, &mObjScale);
1098 else
1099 mObjScale.set(1, 1, 1);
1100 }
1101
1102 // ThrowSrcMask && mCollisionObject
1103 if (stream->readFlag()) {
1104 S32 gIndex = stream->readInt(NetConnection::GhostIdBitSize);
1105 setCollisionTimeout(static_cast<ShapeBase*>(connection->resolveGhost(gIndex)));
1106 }
1107
1108 MatrixF mat = mObjToWorld;
1109
1110 // RotationMask && !mRotate
1111 if (stream->readFlag()) {
1112 // Assumes rotation is about the Z axis
1113 AngAxisF aa;
1114 aa.axis.set(0.0f, 0.0f, stream->readFlag() ? -1.0f : 1.0f);
1115 stream->read(&aa.angle);
1116 aa.setMatrix(&mat);
1117 Point3F pos;
1118 mObjToWorld.getColumn(3,&pos);
1119 mat.setColumn(3,pos);
1120 }
1121
1122 // PositionMask
1123 if (stream->readFlag()) {
1124 Point3F pos;
1125 mathRead(*stream, &pos);
1126 F32 speed = mVelocity.len();
1127 if ((mAtRest = stream->readFlag()) == true)
1128 mVelocity.set(0.0f, 0.0f, 0.0f);
1129 else
1130 mathRead(*stream, &mVelocity);
1131
1132 if (stream->readFlag() && isProperlyAdded()) {
1133 // Determin number of ticks to warp based on the average
1134 // of the client and server velocities.
1135 delta.warpOffset = pos - delta.pos;
1136 F32 as = (speed + mVelocity.len()) * 0.5f * TickSec;
1137 F32 dt = (as > 0.00001f) ? delta.warpOffset.len() / as: sMaxWarpTicks;
1138 delta.warpTicks = (S32)((dt > sMinWarpTicks)? getMax(mFloor(dt + 0.5f), 1.0f): 0.0f);
1139
1140 if (delta.warpTicks)
1141 {
1142 // Setup the warp to start on the next tick, only the
1143 // object's position is warped.
1144 if (delta.warpTicks > sMaxWarpTicks)
1145 delta.warpTicks = sMaxWarpTicks;
1146 delta.warpOffset /= (F32)delta.warpTicks;
1147 }
1148 else {
1149 // Going to skip the warp, server and client are real close.
1150 // Adjust the frame interpolation to move smoothly to the
1151 // new position within the current tick.
1152 Point3F cp = delta.pos + delta.posVec * delta.dt;
1153 VectorF vec = delta.pos - cp;
1154 F32 vl = vec.len();
1155 if (vl) {
1156 F32 s = delta.posVec.len() / vl;
1157 delta.posVec = (cp - pos) * s;
1158 }
1159 delta.pos = pos;
1160 mat.setColumn(3,pos);
1161 }
1162 }
1163 else {
1164 // Set the item to the server position
1165 delta.warpTicks = 0;
1166 delta.posVec.set(0,0,0);
1167 delta.pos = pos;
1168 delta.dt = 0;
1169 mat.setColumn(3,pos);
1170 }
1171 }
1172 Parent::setTransform(mat);
1173}
1174
1175DefineEngineMethod( Item, isStatic, bool, (),,
1176 "@brief Is the object static (ie, non-movable)?\n\n"
1177 "@return True if the object is static, false if it is not.\n"
1178 "@tsexample\n"
1179 "// Query the item on if it is or is not static.\n"
1180 "%isStatic = %itemData.isStatic();\n\n"
1181 "@endtsexample\n\n"
1182 "@see static\n"
1183 )
1184{
1185 return object->isStatic();
1186}
1187
1188DefineEngineMethod( Item, isAtRest, bool, (),,
1189 "@brief Is the object at rest (ie, no longer moving)?\n\n"
1190 "@return True if the object is at rest, false if it is not.\n"
1191 "@tsexample\n"
1192 "// Query the item on if it is or is not at rest.\n"
1193 "%isAtRest = %item.isAtRest();\n\n"
1194 "@endtsexample\n\n"
1195 )
1196{
1197 return object->isAtRest();
1198}
1199
1200DefineEngineMethod( Item, isRotating, bool, (),,
1201 "@brief Is the object still rotating?\n\n"
1202 "@return True if the object is still rotating, false if it is not.\n"
1203 "@tsexample\n"
1204 "// Query the item on if it is or is not rotating.\n"
1205 "%isRotating = %itemData.isRotating();\n\n"
1206 "@endtsexample\n\n"
1207 "@see rotate\n"
1208 )
1209{
1210 return object->isRotating();
1211}
1212
1213DefineEngineMethod( Item, setCollisionTimeout, bool, (S32 ignoreColObj),(NULL),
1214 "@brief Temporarily disable collisions against a specific ShapeBase object.\n\n"
1215
1216 "This is useful to prevent a player from immediately picking up an Item they have "
1217 "just thrown. Only one object may be on the timeout list at a time. The timeout is "
1218 "defined as 15 ticks.\n\n"
1219
1220 "@param objectID ShapeBase object ID to disable collisions against.\n"
1221 "@return Returns true if the ShapeBase object requested could be found, false if it could not.\n"
1222
1223 "@tsexample\n"
1224 "// Set the ShapeBase Object ID to disable collisions against\n"
1225 "%ignoreColObj = %player.getID();\n\n"
1226 "// Inform this Item object to ignore collisions temproarily against the %ignoreColObj.\n"
1227 "%item.setCollisionTimeout(%ignoreColObj);\n\n"
1228 "@endtsexample\n\n"
1229 )
1230{
1231 ShapeBase* source = NULL;
1232 if (Sim::findObject(ignoreColObj,source)) {
1233 object->setCollisionTimeout(source);
1234 return true;
1235 }
1236 return false;
1237}
1238
1239
1240DefineEngineMethod( Item, getLastStickyPos, const char*, (),,
1241 "@brief Get the position on the surface on which this Item is stuck.\n\n"
1242 "@return Returns The XYZ position of where this Item is stuck.\n"
1243 "@tsexample\n"
1244 "// Acquire the position where this Item is currently stuck\n"
1245 "%stuckPosition = %item.getLastStickPos();\n\n"
1246 "@endtsexample\n\n"
1247 "@note Server side only.\n"
1248 )
1249{
1250 static const U32 bufSize = 256;
1251 char* ret = Con::getReturnBuffer(bufSize);
1252 if (object->isServerObject())
1253 dSprintf(ret, bufSize, "%g %g %g",
1254 object->mStickyCollisionPos.x,
1255 object->mStickyCollisionPos.y,
1256 object->mStickyCollisionPos.z);
1257 else
1258 dStrcpy(ret, "0 0 0");
1259
1260 return ret;
1261}
1262
1263DefineEngineMethod( Item, getLastStickyNormal, const char *, (),,
1264 "@brief Get the normal of the surface on which the object is stuck.\n\n"
1265 "@return Returns The XYZ normal from where this Item is stuck.\n"
1266 "@tsexample\n"
1267 "// Acquire the position where this Item is currently stuck\n"
1268 "%stuckPosition = %item.getLastStickPos();\n\n"
1269 "@endtsexample\n\n"
1270 "@note Server side only.\n"
1271 )
1272{
1273 static const U32 bufSize = 256;
1274 char* ret = Con::getReturnBuffer(bufSize);
1275 if (object->isServerObject())
1276 dSprintf(ret, bufSize, "%g %g %g",
1277 object->mStickyCollisionNormal.x,
1278 object->mStickyCollisionNormal.y,
1279 object->mStickyCollisionNormal.z);
1280 else
1281 dStrcpy(ret, "0 0 0");
1282
1283 return ret;
1284}
1285
1286//----------------------------------------------------------------------------
1287
1288bool Item::_setStatic(void *object, const char *index, const char *data)
1289{
1290 Item *i = static_cast<Item*>(object);
1291 i->mAtRest = dAtob(data);
1292 i->setMaskBits(InitialUpdateMask | PositionMask);
1293 return true;
1294}
1295
1296bool Item::_setRotate(void *object, const char *index, const char *data)
1297{
1298 Item *i = static_cast<Item*>(object);
1299 i->setMaskBits(InitialUpdateMask | RotationMask);
1300 return true;
1301}
1302
1303void Item::initPersistFields()
1304{
1305 addGroup("Misc");
1306 addProtectedField("static", TypeBool, Offset(mStatic, Item), &_setStatic, &defaultProtectedGetFn, "If true, the object is not moving in the world.\n");
1307 addProtectedField("rotate", TypeBool, Offset(mRotate, Item), &_setRotate, &defaultProtectedGetFn, "If true, the object will automatically rotate around its Z axis.\n");
1308 endGroup("Misc");
1309
1310 Parent::initPersistFields();
1311}
1312
1313void Item::consoleInit()
1314{
1315 Con::addVariable("Item::minWarpTicks",TypeF32,&sMinWarpTicks,
1316 "@brief Fraction of tick at which instant warp occures on the client.\n\n"
1317 "@ingroup GameObjects");
1318 Con::addVariable("Item::maxWarpTicks",TypeS32,&sMaxWarpTicks,
1319 "@brief When a warp needs to occur due to the client being too far off from the server, this is the "
1320 "maximum number of ticks we'll allow the client to warp to catch up.\n\n"
1321 "@ingroup GameObjects");
1322}
1323
1324//----------------------------------------------------------------------------
1325
1326void Item::prepRenderImage( SceneRenderState* state )
1327{
1328 // Items do NOT render if destroyed
1329 if (getDamageState() == Destroyed)
1330 return;
1331
1332 Parent::prepRenderImage( state );
1333}
1334
1335void Item::buildConvex(const Box3F& box, Convex* convex)
1336{
1337 if (mShapeInstance == NULL)
1338 return;
1339
1340 // These should really come out of a pool
1341 mConvexList->collectGarbage();
1342
1343 if (box.isOverlapped(getWorldBox()) == false)
1344 return;
1345
1346 // Just return a box convex for the entire shape...
1347 Convex* cc = 0;
1348 CollisionWorkingList& wl = convex->getWorkingList();
1349 for (CollisionWorkingList* itr = wl.wLink.mNext; itr != &wl; itr = itr->wLink.mNext) {
1350 if (itr->mConvex->getType() == BoxConvexType &&
1351 itr->mConvex->getObject() == this) {
1352 cc = itr->mConvex;
1353 break;
1354 }
1355 }
1356 if (cc)
1357 return;
1358
1359 // Create a new convex.
1360 BoxConvex* cp = new BoxConvex;
1361 mConvexList->registerObject(cp);
1362 convex->addToWorkingList(cp);
1363 cp->init(this);
1364
1365 mObjBox.getCenter(&cp->mCenter);
1366 cp->mSize.x = mObjBox.len_x() / 2.0f;
1367 cp->mSize.y = mObjBox.len_y() / 2.0f;
1368 cp->mSize.z = mObjBox.len_z() / 2.0f;
1369}
1370
1371void Item::advanceTime(F32 dt)
1372{
1373 Parent::advanceTime(dt);
1374 if ( isMounted() )
1375 return;
1376
1377 if( mRotate )
1378 {
1379 F32 r = (dt / sRotationSpeed) * M_2PI;
1380 Point3F pos = mRenderObjToWorld.getPosition();
1381 MatrixF rotMatrix;
1382 if( mRotate )
1383 {
1384 rotMatrix.set( EulerF( 0.0, 0.0, r ) );
1385 }
1386 else
1387 {
1388 rotMatrix.set( EulerF( r * 0.5, 0.0, r ) );
1389 }
1390 MatrixF mat = mRenderObjToWorld;
1391 mat.setPosition( pos );
1392 mat.mul( rotMatrix );
1393 setRenderTransform(mat);
1394 }
1395
1396}
1397