LTBOOL CBaseParticleSystemFX::CreateObject(ILTClient *pClientDE)
{
    if (!CSpecialFX::CreateObject(pClientDE)) return LTFALSE;

    LTVector vPos = m_vPos;
    LTRotation rRot;
	rRot.Init();

	// Use server object position if a position wasn't specified...

	if (m_hServerObject)
	{
        LTVector vZero(0, 0, 0), vServObjPos;
        if (vPos.Equals(vZero))
        {
			pClientDE->GetObjectPos(m_hServerObject, &vServObjPos);
			vPos = vServObjPos;
		}
		else
		{
            m_basecs.bClientControlsPos = LTTRUE;
		}

		// Calculate our offset from the server object...

		m_vPosOffset = vPos - vServObjPos;
	}

	// Use the specified rotation if applicable

	if (!m_rRot.IsIdentity())
	{
		rRot = m_rRot;
	}


	ObjectCreateStruct createStruct;
	INIT_OBJECTCREATESTRUCT(createStruct);

	createStruct.m_ObjectType = OT_PARTICLESYSTEM;
	createStruct.m_Flags = FLAG_VISIBLE | FLAG_UPDATEUNSEEN | FLAG_FOGDISABLE;
	createStruct.m_Pos = vPos;
	createStruct.m_Rotation = rRot;

	m_hObject = m_pClientDE->CreateObject(&createStruct);

	// Setup the ParticleSystem...

	return SetupSystem();
}
LTBOOL CProjectile::UpdateDoVectorValues(SURFACE & surf, LTFLOAT fThickness,
                                        LTVector vImpactPos, LTVector & vFrom, LTVector & vTo)
{
	// See if we've traveled the distance...

    LTVector vDistTraveled = vImpactPos - m_vFirePos;
    LTFLOAT fDist = m_fRange - vDistTraveled.Mag();
    if (fDist < 1.0f) return LTTRUE;

	// Just dampen based on the bute file values, don't worry about the
	// surface thinkness...

	m_fInstDamage *= surf.fBulletDamageDampen;
	fDist *= surf.fBulletRangeDampen;

	int nPerturb = surf.nMaxShootThroughPerturb;

	if (nPerturb)
	{
        LTRotation rRot;
        g_pLTServer->AlignRotation(&rRot, &m_vDir, LTNULL);

        LTVector vU, vR, vF;
        g_pLTServer->GetRotationVectors(&rRot, &vU, &vR, &vF);

        LTFLOAT fRPerturb = ((LTFLOAT)GetRandom(-nPerturb, nPerturb))/1000.0f;
        LTFLOAT fUPerturb = ((LTFLOAT)GetRandom(-nPerturb, nPerturb))/1000.0f;

		m_vDir += (vR * fRPerturb);
		m_vDir += (vU * fUPerturb);

		m_vDir.Norm();
	}

	// Make sure we move the from position...

	if (vFrom.Equals(vImpactPos, 1.0f))
	{
		vFrom += m_vDir;
	}
	else
	{
		vFrom = vImpactPos;
	}

	vTo = vFrom + (m_vDir * fDist);

    return LTFALSE;
}
LTBOOL CProjectile::HandleVectorImpact(IntersectInfo & iInfo, LTVector & vFrom,
                                     LTVector & vTo)
{
	// Get the surface type...

	SurfaceType eSurfType = GetSurfaceType(iInfo);


	// See if we hit an invisible surface...

	if (eSurfType == ST_INVISIBLE)
	{
		if (!CalcInvisibleImpact(iInfo, eSurfType))
		{
			SURFACE* pSurf = g_pSurfaceMgr->GetSurface(eSurfType);
            if (!pSurf) return LTTRUE;

			return UpdateDoVectorValues(*pSurf, 0, iInfo.m_Point, vFrom, vTo);
		}
	}


	// See if we hit an object that should be damaged...

    LTBOOL bHitWorld = IsMainWorld(iInfo.m_hObject);

	if (!bHitWorld && eSurfType != ST_LIQUID)
	{
		ImpactDamageObject(m_hFiredFrom, iInfo.m_hObject);
	}


	// If the fire position is the initial fire position, use the flash
	// position when building the impact special fx...

    LTVector vFirePos = (vFrom.Equals(m_vFirePos) ? m_vFlashPos : vFrom);

	AddImpact(iInfo.m_hObject, vFirePos, iInfo.m_Point, iInfo.m_Plane.m_Normal, eSurfType);


	// See if we can shoot through the surface...

	SURFACE* pSurf = g_pSurfaceMgr->GetSurface(eSurfType);
    if (!pSurf) return LTTRUE;  // Done.

	if (pSurf->bCanShootThrough)
	{
		int nMaxThickness = pSurf->nMaxShootThroughThickness;
		if (nMaxThickness == 0)
		{
			// Special case of always being able to shoot through surface...

			// Calculate new values for next DoVector iteration...

			return UpdateDoVectorValues(*pSurf, 0, iInfo.m_Point, vFrom, vTo);
		}

		// Test if object/wall intersected is thin enough to be shot
		// through...

		// Test object case first...

		if (!bHitWorld && iInfo.m_hObject)
		{
			// Test to see if we can shoot through the object...

            LTVector vDims;
            g_pLTServer->GetObjectDims(iInfo.m_hObject, &vDims);

			if (vDims.x*2.0f >= nMaxThickness &&  vDims.y*2.0f >= nMaxThickness &&
				vDims.z*2.0f >= nMaxThickness)
			{
				// Can't shoot through this object...
                return LTTRUE;
			}
		}

		// Determine if we shot through the wall/object...

		IntersectInfo iTestInfo;
		IntersectQuery qTestInfo;

        qTestInfo.m_From = iInfo.m_Point + (m_vDir * (LTFLOAT)(nMaxThickness + 1));
		qTestInfo.m_To   = iInfo.m_Point - m_vDir;

		qTestInfo.m_Flags = INTERSECT_OBJECTS | IGNORE_NONSOLID | INTERSECT_HPOLY;

		qTestInfo.m_FilterFn  = DoVectorFilterFn;
		qTestInfo.m_pUserData = m_hFiredFrom;

        if (g_pLTServer->IntersectSegment(&qTestInfo, &iTestInfo))
		{
			// Calculate new values for next DoVector iteration...

            LTVector vThickness = iTestInfo.m_Point - iInfo.m_Point;
			return UpdateDoVectorValues(*pSurf, vThickness.Mag(), iTestInfo.m_Point, vFrom, vTo);
		}
	}

    return LTTRUE;
}
void Breakable::UpdateFalling()
{
    LTVector vOldPos;
    g_pLTServer->GetObjectPos(m_hObject, &vOldPos);

    LTVector vPos = vOldPos;
    vPos += (m_vVel * g_pLTServer->GetFrameTime());

    LTBOOL bDone = LTFALSE;

	if (vPos.y < m_vFinalPos.y)
	{
		vPos.y = m_vFinalPos.y;
        bDone = LTTRUE;
	}

    g_pLTServer->TeleportObject(m_hObject, &vPos);

	if (vPos.Equals(m_vFinalPos, 10.0f))
	{
        bDone = LTTRUE;
	}

	if (bDone)
	{
        m_bFalling = LTFALSE;

        uint32 dwFlags = g_pLTServer->GetObjectFlags(m_hObject);
		dwFlags |= FLAG_SOLID;
        g_pLTServer->SetObjectFlags(m_hObject, dwFlags);

        g_pLTServer->SetNextUpdate(m_hObject, 0.0f);


		// Create impact fx...

		CreateImpactFX();


		if (m_bDestroyOnImpact)
		{
			Destroy();
		}
		else
		{
			// Play the impact sound...

			if (m_hstrImpactSound)
			{
                LTVector vPos;
                g_pLTServer->GetObjectPos(m_hObject, &vPos);

                char* pSnd = g_pLTServer->GetStringData(m_hstrImpactSound);

				if (pSnd)
				{
					g_pServerSoundMgr->PlaySoundFromPos(vPos, pSnd, m_fImpactSoundRadius,
						SOUNDPRIORITY_MISC_MEDIUM);
				}
			}
		}
	}
}