/**Klasa po ktorej dziedziszca wszystkie pociski latajace i wybuchajace takie jak rakiety,granaty
 * itp.
 */
public class Bullet extends ChromeActor
{
    /** Wylaczanie swiatla*/
    boolean bNoLight = true;
    
    LightObject cDynLight = null;
    
    float fExplosionLightLifeTime = 0.1f;
    /**zasieg swiatla w locie w centymetrach*/
    float fLightScaleFlight = 300f;
    /**Zasieg swiatla przy eksplozji w centymetrach, w OnCreate ustawiany na wartosc
     * fBlastRadius*/
    float fLightScaleExplosion = 2000;
    
    public Vector vLightColor = new Vector(141f/255f,
                                           93f/255f,
                                           35f/255f);
    
    // zmienna mowiaca czy pocisk ma byc zaataczowany o 
    // parenta ownera
    static boolean sm_bAttachToOwnerParent = false;
    
    /** kto jest wlascicielem tego pocisku */
    Pawn Owner = null;
    
    /**predkosc poczatkowa*/
    public float fStartSpeed = 1000.0f;

    /** Czas jaki zyje rakieta tzn. na ile czasu latania starcza paliwa */
    public float fTimeToLive = 25.0f;
    
    /**damage jaki jest brany przy trafieniu*/
    public float fDamage = 100.0f;

    /**Damage przekazywany do symulacji fizyki*/
    public float fPhysicsSimDamage = 100f;
    
    /**dameg jaki jest odbierany w promieniu, czym dalej tym mniej jest odbierane */
    public float fBlastDamage = 1000.0f;
    
    public float fDirectHitDamage = fBlastDamage;
    
    /**Damage przekazywany do symulacji fizyki*/
    public float fPhysicsSimBlastDamage = 1000f;
    
    /**Promien razenia bulleta*/
    public float fBlastRadius = 100.0f;
    
    /** Maksymalna wartosc zatrzesienia kamera przy wybuchu */
    float fMaxExplCameraShake = 200;
    
    /**Skalar predkosci odrzutu wubuchu bulletu*/
    public float fBlastSpeed = 10000000.0f;
    
    /**Bron do ktorej nalezy bullet*/
    public Weapon cWeaponOwner = null;
    
    /**W przypadku np. RocketCannon jest jeszcze dodatkowy mesh z ktorego strzelamy*/
    public ChromeActor cWeaponMesh = null;
    
    /**Czy rakieta jest zniszczona czy nie */
    public boolean bExploded =  false;
    
    /**Nazwa FX przy zderzeniu*/
    public String sFXHitName = "FXBlowRocket";
    
    /**Nazwa dzwiku odpalanego dla wybuchu bulleta*/
    public String sSoundExplodeName = "";
    
    /** pomocniczy wspolczynnik do obliczania sily uderzenia */
    float fBlastCoeff;

    /** obiekt, ktory zostal bezposrednio trafiony rakieta - dostaje on
     * maksymalne obrazenia, a pozniej jest pomijany przy obrazeniach
     * zaleznych od odleglosci
     */
    ControlObject m_DirectVictim = null;

    boolean bOwnerThrown = false;

    float fExplosionNoiseRange = 1000f;
    
    /**Czy bullet juz wystartowal */
    private boolean m_bStarted  = false; 
    boolean IsStarted(){ return m_bStarted; }
    
    int METHOD_ScheduledDelete = 0 ;

    // przesuniecie pktu damage'u przy wybuchu
    float fDamgeShiftPos = 0.0f;
    
    /**
     * Jezeli gra podlaczony jest do budynku,
     * weapon do gracza,
     * a bullet do weapona
     * to trzeba ustawiac bulletowi pozycje przez SetPositionWorld
     * Trzeba to jednak jeszcze sprawdzic...
     */
    boolean m_bSetPositionWorld = true;
    
    boolean m_bExecuteParticleSystem = true;
    
    Bullet()
    {
        Role = ERole._AUTHORITY;
    }

    public void OnCreate()
    {
        METHOD_ScheduledDelete = RegisterCallMethod( "ScheduledDelete" );
        
        super.OnCreate();

        // TEN OBIEKT SAVEuje SIE Z Weapon
        bSG = false;
        
        SetCollType(ECollFlags.OBJCOL_TYPE_PERFACE );
        SetCollAction(ECollFlags.OBJCOL_ACTION_STOP);
        SetCollHandler(ECollFlags.OBJCOL_HANDLER_FULLINFO);
        ClearFlag(EFlags._FXPARTICLE_COLLISION | EFlags._RECEIVE_SHADOWS | EFlags._CAST_SHADOWS);
        //Module mod = GetModule();
        //vLevelExtents = new Vector(mod.GetTerrainSize());
        //vPhysicsExtents = new Vector(mod.GetExtents());
        //LogD("vLevelExtents = " + vLevelExtents + "\n");

        fBlastCoeff = fBlastDamage / fBlastRadius;
        
        SetFlag(EFlags._PORTAL_INDEPENDENT);
        SetUnImportanceFactor(0);
        
        if(!bNoLight)
        {
            cDynLight = (LightObject)CreateObject("LightObject");
            ((Module)GetModule()).AddControlObject(cDynLight);
            cDynLight.SetScale(new Vector(fLightScaleFlight, fLightScaleFlight, fLightScaleFlight));
            cDynLight.SetColor(vLightColor.fX, vLightColor.fY, vLightColor.fZ);
            ((ControlObject)cDynLight).AttachAsChildToAndKeepWorldPos(this);
        }
    }
    
    public void OnDestroy()
    {
        if (cWeaponOwner != null)
        {
            if (cWeaponOwner.cBullet == this)
                cWeaponOwner.ClearBulletRef();
        }
        
        Owner = null;
        cWeaponOwner = null;
        cWeaponMesh = null;
        SetCorrectPosOnRender(false);
        UnregisterInInteractionMap();
        super.OnDestroy();
    }
    
    boolean m_bOwnerRegisteredInInteractMap = false;

    public void Init( Weapon weapon )
    {
        RegisterInInteractionMap();
        
        SetOwner( weapon );
        if (weapon == null)
            return;
        
        //CrashLog("Bullet.Init Weapon: " + weapon + " WeaponOwner: " + weapon.Owner + " \n");
        cWeaponOwner = weapon;
        Owner        = weapon.Owner;
        
        
        nWeaponNetId = -1;
        if (cWeaponOwner!=null)
        {
            cWeaponOwner.RegisterInInteractionMap();
            SetInteraction(cWeaponOwner, false);
            
            cWeaponOwner.cBullet = this;
            nWeaponNetId = cWeaponOwner.GetNetId();
        }
        
        nPawnNetId   = -1;
        if (Owner!=null)
        {
            nPawnNetId = Owner.GetNetId();
            
            //Owner.RegisterInInteractionMap();
            //SetInteraction(Owner, false);
        }
        //Uwaga: rejestrowanie do mapy interakcji
        //przeniesione do metody SetOwner wolanej 
        //w momencie wyrzutu
        m_bOwnerRegisteredInInteractMap = false;
        
        
        if (weapon.m_cWalker != null)
        {
            weapon.m_cWalker.RegisterInInteractionMap();
            SetInteraction(weapon.m_cWalker, false);
        }
    }

    public void Init( Weapon weapon, ChromeActor cWeaponMesh)
    {
        Init(weapon);
        if (cWeaponMesh != null)
        {
            this.cWeaponMesh = cWeaponMesh;
            cWeaponMesh.RegisterInInteractionMap();
            SetInteraction(cWeaponMesh, false);
        }
    }
    
    void SetOwner()
    {
        if ( cWeaponOwner != null )
        {
            //CrashLog("SetOwner was owner " + Owner + " nnetid " + nPawnNetId + "\n");
            if ((cWeaponOwner.Owner == null) && (Owner != null))
            {
                //drobny hak zwiazany z tym, ze w sieci SetOwner zawola sie dwa razy, raz
                //ze Start(...) przy fire potem ze Start() w trakcie lotu (korekcja kierunku)
                //za drugim razem granat jest juz usuniety z inventory gracza i jego owner jest
                //null
                
                //nie ustawiamy ownera na null
                
            }
            else
            {
                Owner = cWeaponOwner.Owner;
            }
            
            if ( Owner == null )
                CrashLog(this + " Bullet.Start Owner = null\n");
            
        }
        //else
        //    CrashLog(this + " Bullet.Start cWeaponOwner = null\n");
        
        if ( ! m_bOwnerRegisteredInInteractMap && 
             (Owner != null)  )
        {
            this.RegisterInInteractionMap();
            Owner.RegisterInInteractionMap();
            SetInteraction( Owner, false );
        }
    }
    
    void Acquire(Module mod, Pawn owner)
    {   
        if (GetModule() == null)
        {
            CrashLog("ERROR: bezpanski bullet " + this);
            return;
        }
		UnregisterInInteractionMap();
			
        // przelaczamy bullet na nowy level
        mod.AcquireObjectModule(this);
        METHOD_ScheduledDelete = RegisterCallMethod( "ScheduledDelete" );
		m_DirectVictim = null;	
                
		RegisterInInteractionMap();
    }
    
    void Start(float fForce, Vector vPos, Vector vDir, float fTime)
    {
        //juz leci, wiec nie bedziemy musieli kasowac razem z bronia
        SetStarted();
        // bullet leci, wiec musi sie zapisac do Save'a
        bSG = true;
        
        //CrashLog("[Bullet.Start] bSG=true\n");
        
        // od teraz bullet jest sobie niezalezny od broni
        
        SetOwner();
        
        if ( m_bSetPositionWorld )
            SetPositionWorld( vPos );
        else            
            SetPosition( vPos );
        SetDir( vDir );
        SetPhysicsActive( fTime );
        
        ActivateOnModule();
        
        SetState("Flying");
        
        AttachToOwnerParentIfNeeded();
        
        SetCorrectPosOnRender(true);
    }

    void Start( float fForce ,Vector vPos, Vector vDir )
    {
        Start(fForce ,vPos, vDir, GetTime() );
    }

    
    public void NotifyHit(ChromeActor victim, int nDamageType)
    {
        if (Owner != null)
            Owner.NotifyHit(victim, nDamageType);
    }

    /** Pocisk przechodzi w ten stan gdy zostaje wystrzelony*/
    
    public class Flying extends State 
    {
        float fSpeed = 0.0f;
        
        public void OnEnter()
        {
            CallMethodIn("Explode",fTimeToLive);
        }
        
        
        /**przyspieszamy do fMaxSpeed*/
        void SpeedUpdate()
        {
            //LogD("\n");
        }
        
        /** */
        /*        public void OnTakeDamage(float fDamage, GameObject Assassin, Vector vHitLocation, Vector vSpeed, int nDamageType)
        {
        if ( Role == ERole._AUTHORITY )
        Damage(fDamage);
        }
        */
        // poprawione OnTakeDamage //////////////////////////////////////////////////////////////
        public void OnTakeDamage(float fDamage, float fPhSimDamage, GameObject Assassin, Vector vHitLocation,
                                 Vector vSpeed, int nMeshElementorBoneID, int nDamageType)
        {
            if ( Role == ERole._AUTHORITY )
                Damage(fDamage, nDamageType);
        }
        
        //        /** Pocisk zniszczony wybucha */
        //INFO: Die nie jest handlerem i nie wola sie w odpowiednim stanie...
        //        public void Die()
        //        {
        //            Explode();
        //        }

        /**Gdy czegos dotkniemy odbieramy energie*/
        public void OnCollisionFullInfo(ControlObject Col,Vector vPoint,Vector vNormal)
        {
            // dodatkowe sprawdzenie bezposredniego trafienia samego siebie
            if(Col == Owner)
                return;
            
            if ( Col instanceof Actor )
            {
                //zanegowana normalna to wektor z kierunku ktorego dostajemy
                // obrazenia
                vNormal.Negate();
                
                //obrazenia zaleza od kata miedzy normalna a wektorem predkosci
                // (najwieksze obrazenia przy prostopadlym strzale)
                //fDamage *= GetForwardVector().Dot(vNormal);

                if ( Role == ERole._AUTHORITY )
                {
                    ((ControlObject)Col).TakeDamage(fDamage, fPhysicsSimDamage, Owner, 
                                                    vPoint, vNormal,
                                                    EBones._NON_BONE, EnumDamageType._BULLET);
                }
            }
        }
        
        //*********************************************************
        //					SaveGame
        //*********************************************************
        
        public void SGLoad(FileChunk cParentFC)
        {
            FileChunk.Log("[Bullet$Flying] : SGLoad\n");
            
            FileChunk cFC = GameObject.LoadChunk(cParentFC);   
            
            if (cFC.GetID() == ESGChunksChrome._CHK_BULLET_FLYING_STATE)
            {
                super.SGLoad(cFC);
                
                fSpeed = cFC.LoadFloat();				
            }
            
            cFC.delete();
        }
        
        public void SGSave(FileChunk cParentFC)
        {
            FileChunk.Log("[Bullet$Flying] : SGSave\n");
            
            FileChunk cFC = NewChunk(ESGChunksChrome._CHK_BULLET_FLYING_STATE, cParentFC);
            
            super.SGSave(cFC);
            
            cFC.SaveFloat(fSpeed);
            
            cFC.delete();
        }
        
        public void SGUpdate()
        {
            FileChunk.Log("[Bullet$Flying] : SGUpdate\n");
        }
    }
    
    
    void ExplosionSpecialEffects()
    {
        if(!bNoLight)
        {
            cDynLight.DetachFromParentAndKeepWorldPos();
            
            cDynLight.SetScale(new Vector(1f/fLightScaleFlight,
                                          1f/fLightScaleFlight,
                                          1f/fLightScaleFlight)); 
            cDynLight.SetScale(fLightScaleExplosion, fLightScaleExplosion, fLightScaleExplosion);
            cDynLight.CallMethodIn("delete", fExplosionLightLifeTime);
        }
        
        Vector vPos = GetPosition();
    	ExecuteFX( sFXHitName, vPos );
        if (m_bExecuteParticleSystem)
            ExecuteParticleSystem( sFXHitName, vPos, Vector.Up );
        PlaySound( sSoundExplodeName, vPos );
        ControlObject[] aPlayers = GetObjectsInRadiusFromClass(fBlastRadius, "Player");
        if (aPlayers != null)
        {
            Player player;
            for (int i = 0; i < aPlayers.length; i++)
            {
                player = (Player)aPlayers[i];
                if (player != null &&
                    player.Camera != null)
                {
                    float dist2 = GetDistance2To(player);
                    float fShakeVal = fMaxExplCameraShake *
                                      (1 - Tools.InterpLinear(0, fBlastRadius * fBlastRadius, dist2));
                    if (fShakeVal > 1)
                        player.Camera.ShakeImpulse(fShakeVal, fShakeVal, fShakeVal, CameraPlayer._SHAKE_EXPLOSION);
                }
            }
        }
    }

    /** Opoznienie zadawania ran,
     * domyslnie wartosc ujemna, co zagwarantuje natychmiastowe wykonanie
     * MakeDamage (tak jak dotychczas)
     */
    float  m_fDamageDelay = -1.0f;
    Vector m_vDamagePos   = null;
    
    void MakeDamageInTime( float fTimeDelay, Vector vPos )
    {
        if ( fTimeDelay < 0.0f )
            MakeDamage( vPos );
        else
        {
            m_vDamagePos = vPos;
            CallMethodIn( "MakeDamage", fTimeDelay );
        }
    }
    
    void MakeDamage()
    {
        MakeDamage( m_vDamagePos );
    }
    
    Vector vShift = new Vector();
    void MakeDamage(Vector vPos)
    {
        MakeDirectHit(m_DirectVictim);
        MakeBlastDamage( fBlastDamage, fBlastDamage, vPos, 
                         fBlastRadius, true,
                         m_DirectVictim instanceof Actor ? (Actor)m_DirectVictim : null, 
                         EnumDamageType._BLAST);
    }
    
    /**Wybuchamy bullet i odbieramy wszystkim w promieniu odpowiednia ilosc energii*/
    void Explode()
    {
        UnregisterInInteractionMap();
        DisableCallMethod("Explode");
        if (!bExploded)
        {
            Vector vMyPos = GetPosition();

            //fx'y
            ExplosionSpecialEffects();
            
            // make noise alarmujace AI o wybuchu
            if ( Owner != null )
                Owner.MakeNoiseOnPosition(vMyPos, fExplosionNoiseRange, Pawn._NOISE_EXPLOSION);
            
            if ( Role == ERole._AUTHORITY )
            {
                // dodajemy przesuniecie damage'u
                /*
                vShift.Zero();
                vShift.fY = fDamgeShiftPos;
                Vector vPos = PointLocalToWorld(vShift);
                */
                Vector vPos = GetPosition();
                vPos.fY += fDamgeShiftPos;
                
                MakeDamageInTime( m_fDamageDelay, vPos );
                
                /*
                //zbieramy ofiary wybuchu
                GameObject []aVictim = GetObjectsInRadiusFromClass(fBlastRadius,"Actor");
                if ( aVictim != null )
                {
                for ( int index = 0 ; index < aVictim.length ; index++ )
                {
                Vector vVictimPos = ((ControlObject)aVictim[index]).GetPosition();
                float fDis = vVictimPos.Distance(vMyPos);

                if ( (m_DirectVictim != null) &&
                (m_DirectVictim == (ControlObject)aVictim[index]) )
                continue;
                
                if (fDis < fBlastRadius) //jestesmy w zasiegu razenia
                {
                Vector vFromCenter = new Vector(vVictimPos);
                vFromCenter.Sub(vMyPos);
                vFromCenter.Normalize();
                vFromCenter.Mul(fBlastSpeed);

                
                //damage jest interpolowany liniowo na caly fBlastRadius
                
                
                //interpretacja wyniku wybuchu granatu w jakims zasiegu zajmuje sie ten,
                // kto dostal 
                // TEMP: (vFromCenter nie jest potrzebne w tym wypadku)
                
                
                ((ControlObject)aVictim[index]).TakeDamage( fBlastDamage, Owner,
                GetPosition(), vFromCenter, -2);//FIXIT damagetype odpowiadajacy rakietom
                }
                }
                }
                */
            }
            
            DeactivateOnModuleAlways();
            bExploded = true;
            
            //TODO ustalic czas trwania rozblysku swiatla przy wybuchu, rozblysk trwa od wybuchu
            //do skasowania obiektu
            CallMethodIn("delete", 5f);
            //cOwner.CallMethodIn("RemoveBullet",0.1f);
        }
    }
    
    public void MakeDirectHit(ControlObject victim)
    {
        if (victim == null)
            return;
        
        // zadajemy pelne obrazenia obiektowi, ktory zostal bezposrednie trafienie
        /*Vector vFromCenter = new Vector(victim.GetPosition());
        vFromCenter.Sub(GetPosition());
        vFromCenter.Normalize();
        vFromCenter.Mul(fBlastSpeed);
        */
        Vector vHitPos = null;
        if (((MeshObject)victim).IsBiped())
        {
            vHitPos = ((MeshObject)victim).GetBoneJointPos(EBones._SPINE1);
        }
        else
        {
            Vector vMin = new Vector(), vMax = new Vector();
            ((MeshObject)victim).GetMeshExtentsInWorld(vMin, vMax);
            vHitPos = victim.GetPosition();
            vHitPos.fY = (vMax.fY + vMin.fY)/2f;
        }
        
        Collision stColl = TraceObjectsExclude(GetPosition(), vHitPos, this);
        Vector vDir = vHitPos.Sub2(GetPosition()).Normalize2();
        
        m_DirectVictim.TakeDamage( fDirectHitDamage, fPhysicsSimBlastDamage, Owner,
                                   vHitPos, vDir,
                                   EBones._NON_BONE, EnumDamageType._BLAST);
    }
    
    void InitReplicate(float fForce , Vector vPos, Vector vDir, float fTime)
    {
        MsgSendClientThrow( fForce, vPos, vDir, fTime );
        //CallMethodEvery("SendClientUpdate",0.1f);
    }

    void SendClientUpdate()
    {
        MsgSendClientUpdate( GetPosition(), GetSpeed(), GetTime() );
    }
    
    
    void ActionStart( Weapon weapon, float fForce , Vector vPos, Vector vDir )
    {
        float fTime = GetTime();
        
        //LogR("ActionStart fForce:"+fForce+" vPos "+vPos+" vDir "+vDir+" fTime "+fTime+"\n");
        
        if ( bNetIsOwner )
        {
            bOwnerThrown = true;
            Init( weapon );
            Start( fForce , vPos, vDir, fTime );
            
            if ( Role == ERole._AUTHORITY )
            {
                // wyrzucam na serwerze, musze to zaczac replikowac na klientow
                InitReplicate( fForce , vPos, vDir, fTime );
            }
            else
            {
                // wyrzucam na kliencie, musze to zreplikowac jako event z czasem
                // na serwera
                MsgSendServerThrow( fForce, vPos, vDir, fTime );
            }
        }
        else if ( Role == ERole._AUTHORITY )
        {
            if ( ! bOwnerThrown )
            {
                //Init( weapon ); // na serwerze juz jest zainicjowany w OnCreate
                Start( fForce , vPos, vDir, fTime );
            }
        }
    }
    
    void BulletStarted()
    {
        
    }
    
    static final int MSG_CLIENT_OWNERS_ID      = 30; //doslanie identyfikatorow wlascicieli
    static final int MSG_SERVER_THROW          = 31; //poinformowanie serwera o parametrach wyrzutu
    static final int MSG_CLIENT_THROW          = 32; //poinformowanie klientow o parametrach wyrzutu
    static final int MSG_CLIENT_UPDATE         = 33; //poinformowanie klientow o aktualnych parametrach obiektu
    
    public void OnReplicateServer()
    {
        super.OnReplicateServer();
        
        if ( bNetInit )
        {
            // podczas inicjalizacji, zawsze musimy pochwalic
            // sie swoim aktualnym stanem
            MsgSendClientOwnersId();
        }
    }
    
    int nWeaponNetId = -1;
    int nPawnNetId   = -1;
    
    void MsgSendClientOwnersId()
    {
        LogD("\n");
        MessageNew( MSG_CLIENT_OWNERS_ID );
        
        MessagePackInt( nWeaponNetId );
        MessagePackInt( nPawnNetId );
        
        MessageSend();
    }
    
    // odbranie stanu obiektu
    void MsgRecvClientOwnersId()
    {
        nWeaponNetId   = MessageUnPackInt();
        nPawnNetId     = MessageUnPackInt();
        MessageEnd();
        
        CheckOwnerWeapon();
        CheckOwnerPawn();
    }
    
    void MsgSendServerThrow( float fForce, Vector vPos, Vector vDir, float fTime )
    {
        //LogR("MsgSendServerThrow fForce "+fForce+"\n");
        MessageNew( MSG_SERVER_THROW );
        
        MessagePackFloat( fForce );
        MessagePackVector( vPos );
        MessagePackVector( vDir );
        MessagePackFloat( fTime );
        
        MessageSend();
    }
    
    void MsgRecvServerThrow()
    {
        LogD("\n");
        float fForce = MessageUnPackFloat();
        Vector vPos  = MessageUnPackVector();
        Vector vDir  = MessageUnPackVector();
        float fTime  = MessageUnPackFloat();
        
        MessageEnd();
        
        //LogR("MsgRecvServerThrow fForce:"+fForce+" vPos "+vPos+" vDir "+vDir+" fTime "+fTime+"\n");
        
        bOwnerThrown = true;
        Start( fForce , vPos, vDir, fTime );
        BulletStarted();
        InitReplicate( fForce , vPos, vDir, fTime );
    }
    
    void MsgSendClientThrow( float fForce, Vector vPos, Vector vDir, float fTime )
    {
        LogD("\n");
        MessageNew( MSG_CLIENT_THROW );

        MessagePackInt( nPawnNetId );        
        MessagePackFloat( fForce );
        MessagePackVector( vPos );
        MessagePackVector( vDir );
        MessagePackFloat( fTime );
        
        MessageSend( EMsgSendMode.NOTOWNER );
    }
    
    void MsgRecvClientThrow()
    {
        LogD("\n");
        nPawnNetId    = MessageUnPackInt();
        float  fForce = MessageUnPackFloat();
        Vector vPos   = MessageUnPackVector();
        Vector vDir   = MessageUnPackVector();
        float  fTime  = MessageUnPackFloat();
        
        MessageEnd();
        
        //dodatkowo, sprawdzam pawna
        CheckOwnerPawn();
        DisableCallMethod( "CheckOwnerPawn" );
        
        LogD(" fForce:"+fForce+" vPos "+vPos+" vDir "+vDir+" fTime "+fTime+"\n");
        Start( fForce , vPos, vDir,fTime );
    }
    
    void MsgSendClientUpdate( Vector vPos, Vector vSpeed, float fTime )
    {
        LogD("\n");
        MessageNew( MSG_CLIENT_UPDATE );
        
        MessagePackVector( vPos );
        MessagePackVector( vSpeed );
        MessagePackFloat( fTime );
        
        MessageSend();
    }
    
    void MsgRecvClientUpdate()
    {
        LogD("\n");
        Vector vPos   = MessageUnPackVector();
        Vector vSpeed = MessageUnPackVector();
        float fTime   = MessageUnPackFloat();
        
        MessageEnd();
        
        if ( m_bSetPositionWorld )
            SetPositionWorld( vPos );
        else            
            SetPosition( vPos );
        SetSpeed( vSpeed );
        SetPhysicsActive( fTime );
    }
    
    public void OnMessage()
    {
        switch ( MessageGetId() )
        {
        case MSG_CLIENT_OWNERS_ID:
            LogD(" MSG_CLIENT_OWNERS_ID\n");
            MsgRecvClientOwnersId();
            break;
            
        case MSG_SERVER_THROW:
            LogD(" MSG_SERVER_THROW\n");
            MsgRecvServerThrow();
            break;
            
        case MSG_CLIENT_THROW:
            LogD(" MSG_CLIENT_THROW\n");
            MsgRecvClientThrow();
            break;
            
        case MSG_CLIENT_UPDATE:
            LogD(" MSG_CLIENT_UPDATE\n");
            MsgRecvClientUpdate();
            break;
            
        default:
            super.OnMessage();
        }
    }
    
    
    void CheckOwnerWeapon()
    {
        if ( nWeaponNetId != -1 )
        {
            Weapon weapon = GetWeaponByNetId( nWeaponNetId );
            if ( weapon == null )
            {
                CallMethodIn("CheckOwnerWeapon",0.1f);
                return;
            }
            
            cWeaponOwner = weapon;
            cWeaponOwner.SetBullet( this );
            LogD(" "+weapon+" found, attached to "+this+"\n");
        }
    }
    
    void CheckOwnerPawn()
    {
        if ( nPawnNetId != -1 )
        {
            Pawn pawn = GetPawnByNetId( nPawnNetId );
            if ( pawn == null )
            {
                CallMethodIn("CheckOwnerPawn",0.1f);
                return;
            }
            
            Owner  = pawn;
            LogD(" "+pawn+" found, attached to "+this+"\n");
        }
    }
    
    void AttachToOwnerParentIfNeeded()
    {
        if (sm_bAttachToOwnerParent             &&
            Owner             != null        &&
            Owner.GetParent() != null)
        {
            AttachAsChildToAndKeepWorldPos( Owner.GetParent() );
            CallMethodEvery("CheckExtents", 0.5f);
        }
    }
    
    void CheckExtents()
    {
        boolean bInside = true;
        if ( !bInside )
        {
            Vector vSpeed = GetAbsoluteSpeed();
            DetachFromParentAndKeepWorldPos();
            SetSpeed( vSpeed );
            DisableCallMethod("CheckExtents");
        }
    }
    
    //*********************************************************
    //					SaveGame
    //*********************************************************
    
    /** Metoda wczytujaca zapisane dane w Save gracza
     */	
    public void SGLoadChunk(FileChunk cParentFC)
    {	
        FileChunk.Log("[Bullet.SGLoadChunk]\n");
        
        FileChunk cFC = GameObject.LoadChunk(cParentFC);   
        
        if (cFC.GetID() == ESGChunksChrome._CHK_BULLET)
        {
            super.SGLoadChunk(cFC);
            
            // Czas jaki zyje rakieta tzn. na ile czasu latania starcza paliwa
            fTimeToLive = cFC.LoadFloat();

            // Skalar predkosci odrzutu wubuchu bulletu
            fBlastSpeed = cFC.LoadFloat();
            
            // Czy rakieta jest zniszczona czy nie
            bExploded = cFC.LoadBool();
            
            // obiekt, ktory zostal bezposrednio trafiony rakieta
            m_DirectVictim = (ControlObject)cFC.LoadObjectRef();
            
            bOwnerThrown = cFC.LoadBool();
            m_bStarted = cFC.LoadBool();
            fExplosionNoiseRange = cFC.LoadFloat();			
            
            boolean bOwnerSaved = cFC.LoadBool();
            
            // gdy bullet odczytuje sie niezleznie to musi wpisac sobie do kogo nalezy
            if(bOwnerSaved)
            {
                LogR("BULLET bOwnerSaved = true " + this);
                // jest zapisany owner, wiec obiekt fruwa
                bSG = true;
                
                // kto jest wlascicielem tego pocisku
                Owner = (Pawn)cFC.LoadObjectRef();
                
                //TODO co tu wstawic!?
                cWeaponOwner = null;
            }
            
            m_bExecuteParticleSystem = cFC.LoadBool();
            
        }
        
        cFC.delete();
    }
    
    /** Metoda zapisujaca dane do Save gracza
     */	
    public void SGSaveChunk(FileChunk cFCParent)
    {
        FileChunk.Log("[Bullet.SGSaveChunk]\n");
        
        FileChunk cFC = NewChunk(ESGChunksChrome._CHK_BULLET, cFCParent);
        
        super.SGSaveChunk(cFC);

        // Czas jaki zyje rakieta tzn. na ile czasu latania starcza paliwa
        cFC.SaveFloat(fTimeToLive);
        
        // Skalar predkosci odrzutu wubuchu bulletu
        cFC.SaveFloat(fBlastSpeed);
        
        // Czy rakieta jest zniszczona czy nie
        cFC.SaveBool(bExploded);
        
        // obiekt, ktory zostal bezposrednio trafiony rakieta
        cFC.SaveObjectRef(m_DirectVictim);
        
        cFC.SaveBool(bOwnerThrown);
        cFC.SaveBool(m_bStarted);
        cFC.SaveFloat(fExplosionNoiseRange);
        
        // zapisujemy czy obiekt ma wpisanego wlasciciela
        cFC.SaveBool(bSG);
        
        // gdy bullet zapisuje sie niezleznie to musi wpisac sobie do kogo nalezy
        if(bSG)
        {
            // kto jest wlascicielem tego pocisku, czyli Pawn
            cFC.SaveObjectRef(Owner);
        }
        
        cFC.SaveBool(m_bExecuteParticleSystem);
        
        cFC.delete(); 
    }		

    public void SGUpdate()
    {
        
        super.SGUpdate();
        if (!IsStarted() && 
            cWeaponOwner != null && 
            cWeaponOwner.cBullet == this)
            bSG = false;
    }
    
    void DumpState()
    {
        CrashLog("Object <"+GetName()+":"+GetUniqueID()+"> class <"+getClass().getName()+">\n");
        CrashLog("Physics active="+GetPhysicsActive()+"\n");
        CrashLog("Pos="+GetPositionVectorVolatile()+"\n");
        CrashLog("Speed="+GetSpeed()+"\n");
    }
    
    void RemoveForMovie()
    {
        UnregisterInInteractionMap();
        DisableCallMethods();
        DetachFromParentAndKeepWorldPos();
        DeactivateOnModuleAlways();
        bExploded = true;
        //TODO ustalic czas trwania rozblysku swiatla przy wybuchu, rozblysk trwa od wybuchu
        //do skasowania obiektu
        CallMethodIn("delete", 5f);
        //cOwner.CallMethodIn("RemoveBullet",0.1f);
    }
    
    void SetStarted()
    {
        DetachFromParentAndKeepWorldPos();
        ((ChromeModule)GetModule()).BulletStarted(this);
        m_bStarted  = true; 
        DisableCallMethod( METHOD_ScheduledDelete );
    }

    
    /**
     * Przy kasowaniu broni nalezy tez skasowac
     * ewentualne granaty i rakiety ktore sie w niej stworzyly
     * Oczywiscie takie kasowanie nastapi tylko wtedy, gdy
     * bullet jeszcze nie "wylecial"
     **/
    void ScheduleDeleteOnWeaponDelete()
    {
        if ( ! m_bStarted )
        {
            //Daje to z opoznieniem, bowiem na klientach
            //w momencie kasowania broni moze jeszcze nie byc
            //wiadomo ze granat wylecial
            CallMethodIn( METHOD_ScheduledDelete, 6.0f );
        }
    }
    
    void ScheduledDelete()
    {
        delete();
    }
    
    
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////
    void SetCorrectPosOnRender( boolean bVal )
    {
        if (GetModule() instanceof ModuleNet)
        {
            ModuleNet moduleNet = ((ModuleNet)GetModule());
            if (bVal)
                moduleNet.AddCorrectOnRenderObject(this);
            else
                moduleNet.RemoveCorrectOnRenderObject(this);
        }
        super.SetCorrectPosOnRender(bVal);
    }
    
    void OnRender()
    {
        if (!m_bCorrectPosOnRender)
            return;

        //pobieramy oryginalne pozycje
        GetPosition( m_vActuallyPosition );

        //ustawiamy je jako potencjalne do renderu
        m_vRenderPosition.Set( m_vActuallyPosition );    
        //dosymulowujemy fizyke modyfikujac parametry
        AdditionalSimulateOnRender(m_vRenderPosition);
        //ustawiamy w nowej pozycji
        SetPositionWorld(m_vRenderPosition);
        //i zapamietujemy ten fakt
        m_bOnRenderPosition = true;

        SetRenderTime(GetTime());
    }
    
    void OnPostRender()
    {
        if (m_bOnRenderPosition)
        {
            // to przypracamy faktyczna pozycje
            SetPositionWorld(m_vActuallyPosition);
            m_bOnRenderPosition = false;
        }
    }
    
    void AdditionalSimulateOnRender(Vector vPos)
    {
        float fDeltaTime = GetTime() - GetPhysicsLastUpdateTime();
        if ( fDeltaTime != 0.0f && Math.abs( fDeltaTime ) < 1.0f )
        {
            GetSpeed( sm_vSpeed );
            sm_vSpeed.Mul( fDeltaTime );
            MoveByEllipsoidPure( sm_vSpeed );
            vPos.Add( sm_vSpeed );
        }
    } 
    
    void SetOwner( Weapon cWeapon )
    {
        if (cWeapon != null)
        {
            ChangeNetOwner( cWeapon );
            cWeaponOwner = cWeapon;
            Owner        = cWeapon.Owner;
        }
    }

}
