/* Alliance Offhand Grapple code. */
#include "g_local.h"
#include "bg_battlebots.h"

/*
Offhand grapple design.
Publius 15 Jan 2000
 
The onhand grapple (as listed and fired from the weapons list) is to be
essentially removed. The offhand grapple will be the only type of
grapple in the game.
 
Grapples may be introduced in the game by:
	- Placing them as items on the map. (using std hook entity)
	- Giving a hook to each player, using the item-giving ents present
	  on the map.
	- Using the global cvar g_globalGrapple (tri-state),
	  which may be used to enable or disable the grapple on all maps
	  in the rotation.
 
The grapple is NOT to be placed on the weapon list. There will be an
icon on the HUD indicating if the player has a grapple, or (even better)
the weapon model will be hacked to show a grapple in 3d.
 
In some form or another, players will bind to +hook. This will
eventually map to button 5 (32) in ucmd->buttons. This will probably
involve stuffcmd's.
 
Use cvars for little grappling tweaks (g_grappleHookSpeed,
g_grapplePullSpeed) that we may toy
with mid-game until we find a good combo.
 
*/

/*
 
  GRAPPLES ARE NOT MISSILES.
 
  Repeat this until you follow it blindly. There are some similarities
  between grapples and missiles in flight, but since grapples aren't
  weapons, and missile code assumes that the missile came from a weapon
  from some sort, we can't use it.
 
  Once the grapple hits something it acts fundamentally different from most
  everything else in the world, but most of that functionality has been
  coded in already. However it does require special physics for moving
  bmodels and such.
 
*/

// grappleUsed: returns true if the grapple is used at all on the map.
// used for precaching
/*
qboolean grappleUsed(void) {
    if (globalGrapple() == qtrue)
        return qtrue;

    // also check for any grapple existing on the map, including in a target_give

    //	return qfalse;
    return qtrue;
}
*/

// playerMayFireGrapple: returns qtrue if the player has an offhand
// grapple on hand. Depends on globalGrapple and/or the player's
// inventory.
qboolean playerMayFireGrapple(gentity_t *ent) {
    // if the player is dead..
    if (ent->health <= 0)
        return qfalse;

    // Joe - if hook is enabled it will always be in the inventory
    // not anymore, Myrkul wants instant grapple

    if (g_grappleGlobal.integer == 0)
        return qfalse;

    if (g_grappleGlobal.integer == 1)
        return qtrue;

    // return true if the player has a hook in inventory
    if (ent->client->ps.stats[STAT_GRAPPLE])
        return qtrue;

    return qfalse;
}

// Joe
//void BG_CalcMuzzlePoint (vec3_t origin, int viewheight, vec3_t forward, vec3_t muzzlePoint);
void CalcMuzzlePointOrigin ( gentity_t *ent, vec3_t origin, vec3_t forward, vec3_t right, vec3_t up, vec3_t muzzlePoint );
void Weapon_Offhand_GrapplingHook_Fire (gentity_t *ent) {

    //G_Printf("Weapon_Offhand_GrapplingHook_Fire  ()\n");
    // calculate muzzle and forward first
	vec3_t muzzle, forward, right, up;

    // Joe hook bug fix
    // couldn't fire grapple twice when attack button was held down
    //	if (!ent->client->offhandfireHeld && !ent->client->offhandhook) {
    if (!ent->client->offhandhook) {
        // end Joe hook bug fix


        // begin Joe grapple refire limit
        if (ent->client->hook_time > level.framenum)
			return;
        //else if(ent->client->hook_time < level.framenum && ent->client->offhandhook && ent->client->ps.pm_flags & ~PMF_OFFHAND_GRAPPLE_PULL){
		//	Weapon_Offhand_HookFree(ent->client->offhandhook);
		//	ent->client->hook_time = level.framenum + GRAPPLE_REFIRE;
		else{
            ent->client->hook_time = level.framenum + GRAPPLE_REFIRE;
		}
        // end Joe

        //if(ent->client){
        AngleVectors (ent->client->ps.viewangles, forward, right, up);
        CalcMuzzlePointOrigin ( ent, ent->client->oldOrigin, forward, right, up, muzzle );
	    Fire_Offhand_Grapple (ent, muzzle, forward);
        ent->client->offhandfireHeld = qtrue;
        trap_LinkEntity( ent );
    } else {
		if (ent->client->hook_time > level.framenum)
			return;
        else{
            ent->client->hook_time = level.framenum + GRAPPLE_REFIRE;
		}
        AngleVectors (ent->client->ps.viewangles, forward, right, up);
        CalcMuzzlePointOrigin ( ent->client->offhandhook , ent->client->oldOrigin, forward, right, up, muzzle );
        Fire_Offhand_Grapple (ent->client->offhandhook , muzzle, forward);
        ent->client->offhandfireHeld = qtrue;
        trap_LinkEntity( ent->client->offhandhook);
    }
}

void Weapon_Offhand_HookFree (gentity_t *ent) {

    ent->parent->s.otherEntityNum = 0;  // not working for some reason
    
	// local player gets entitystate from playerstate
    ent->parent->client->ps.stats[STAT_HOOK_ENTNUM] = 0;
    ent->parent->client->offhandhook = NULL;
    ent->parent->client->ps.pm_flags &= ~PMF_GRAPPLE_PULL;
    // remove hook from hookchain if necessary
    //INCIN --- this isnt doing anything right now --- removed
    //removeHookChain(ent);
	ent->parent->client->offhandfireHeld = qfalse;
    G_FreeEntity( ent );
}

void Weapon_Offhand_HookThink (gentity_t *ent) {
	
    VectorCopy( ent->r.currentOrigin, ent->parent->client->ps.grapplePoint);

}

// FIXME copied this from g_missle.c
// moved to a_defs.h - Joe
//#define	MISSILE_PRESTEP_TIME	50
//pmove_t			*pm;

static gentity_t *Fire_Offhand_Grapple (gentity_t *self, vec3_t start, vec3_t dir) {
    gentity_t       *offhandhook;

    //G_Printf("firing grapple\n");

    VectorNormalize (dir);

	if(!self->client->offhandhook){
		offhandhook = G_Spawn();
	}
	else
	{
		offhandhook = self->client->offhandhook;
	}
	    // Joe - hook damage
    // complaints with opening doors
    /*
        offhandhook->damage = HOOK_DAMAGE;
        offhandhook->methodOfDeath = MOD_GRAPPLE;  // was already in bg_public.h
    */
    // end Joe

    offhandhook->classname = "hook";
    offhandhook->nextthink = level.time + HOOK_TIME * 1000;//level.time + 10000;
    offhandhook->think = Weapon_Offhand_HookFree;
	//offhandhook->giType = IT_GRAPPLE;
    // grapple is in air
    // unfortunately removed for id cgame compatibility
    // (and put back in)
    //      hook->s.eType = ET_GRAPPLE_AIR;
    offhandhook->s.eType = ET_MISSILE;

    // HACK HACK HACK Use EF_MOVER_STOP to distinguish hooks from missiles.
    offhandhook->s.eFlags |= EF_MOVER_STOP;

    offhandhook->r.svFlags = SVF_USE_CURRENT_ORIGIN;
    offhandhook->s.weapon = WP_GRAPPLING_HOOK;
    offhandhook->r.ownerNum = self->s.number;
    offhandhook->methodOfDeath = MOD_GRAPPLE;
    offhandhook->clipmask = MASK_SHOT;
    offhandhook->parent = self;

	//unlagged - projectile nudge
	// we'll need this for nudging projectiles later
	offhandhook->s.otherEntityNum = self->s.number;
	//unlagged - projectile nudge

    offhandhook->s.pos.trType = TR_LINEAR;
    
    if ( BG_gamemode() != GM_BATTLE ) {
        offhandhook->takedamage = qtrue;
        offhandhook->health = 1;
        offhandhook->die = G_Offhand_GrappleDie;
        offhandhook->r.contents = CONTENTS_TRIGGER;
    }

    //INCIN - know this isnt right , this causes the jerky movement over net connections.
    //offhandhook->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME;
    if ( self->client ) {
        offhandhook->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME;
    } else {
        offhandhook->s.pos.trTime = self->client->pers.cmd.serverTime + 50;
    }
    //INCIN -- tested.. are nice but too far ahead of time client side
    //offhandhook->s.pos.trTime =  pmove->ps->commandTime;// - MISSILE_PRESTEP_TIME;
    //offhandhook->s.pos.trTime = (pm->cmd.serverTime - pm->ps->commandTime) +  level.time + MISSILE_PRESTEP_TIME;
    // move a bit on the very first frame
    // offhandhook->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME;
    //      VectorCopy(hook->s.angles, self->s.angles);

    //offhandhook->s.otherEntityNum = self->s.number; // use to match beam in client
    //        G_Printf(" G: offhandhook thinks owner is %d\n", hook->s.otherEntityNum);

    // begin Joe
    // link owner to hook so cgame can get it
    // make sure to zero it when not in use
    // hope this doesn't break anything, I'm pretty sure
    // otherEntityNum is only used for events and missiles
    // both victim of missile hit and color coding

    // not showing up in client entityState !
    self->s.otherEntityNum = offhandhook - g_entities;
    // trying this instead:
    self->client->ps.stats[STAT_HOOK_ENTNUM] = offhandhook - g_entities;
    //        G_Printf("G: hook num is %d\n",  offhandhook - g_entities);
    // end Joe

    VectorCopy( start , offhandhook->s.pos.trBase );
    //      VectorScale( dir, BATTLEBOTS_GRAPPLE_SPEED, hook->s.pos.trDelta );
    VectorScale( dir, g_grappleHookSpeed.integer, offhandhook->s.pos.trDelta);
    trap_SnapVector( offhandhook->s.pos.trDelta );                      // save net
    // bandwidth

    // Joe real
    //      G_Printf("GR: type %d time %d base %s delta %s\n",
    //               offhandhook->s.pos.trType,
    //               offhandhook->s.pos.trTime,
    //               vtos(offhandhook->s.pos.trBase),
    //               vtos(offhandhook->s.pos.trDelta));

    // begin Joe
    // give origin at level.time
    // but then hook won't appear on wall if I'm really close
    /*
        if(g_predictHook.integer)
            BG_EvaluateTrajectory(&offhandhook->s.pos, level.time,
                                  offhandhook->r.currentOrigin);
        else
    */
    // end Joe
    VectorCopy (start, offhandhook->r.currentOrigin);
    // Joe, try to make the hook start closer
    // ends up tracing behind me
    self->client->offhandhook = offhandhook; //self->client->hook is already = hook here
   //      Com_Printf("Fire_Offhand_Grapple()\n");

    return offhandhook;
}

/*
#if 0
void bareImpact(gentity_t *ent, trace_t *trace) {
    vec3_t v;

    VectorCopy(trace->endpos, v);
    VectorCopy( v, ent->s.pos.trBase );
    ent->s.pos.trType = TR_STATIONARY;
    ent->s.pos.trTime = 0;
    ent->s.pos.trDuration = 0;
    ent->think = Weapon_Offhand_HookThink;
    ent->nextthink = level.time + FRAMETIME;
    VectorCopy( ent->r.currentOrigin, ent->parent->client->ps.grapplePoint);
    trap_LinkEntity( ent );
}
#endif
*/

// begin Hugo
/*
================
G_GrappleDie
 
Hook is shootable
================
*/
void G_Offhand_GrappleDie( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) {
    //	if (inflictor == self) {
    //		return;
    //	}

    Weapon_Offhand_HookFree( self );

}
// end Hugo

/*
=============
G_Run_Offhand_Grapple - NOT USED
 
Runs the grapple in flight
=============

void G_Run_Offhand_Grapple(gentity_t *ent) {
    // blatantly ripped from G_RunMissile
    vec3_t		origin;
    trace_t		tr;

    // get current position
    BG_EvaluateTrajectory( &ent->s.pos, level.time, origin );

    // trace a line from the previous position to the current position,
    // ignoring interactions with the missile owner
    trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, origin,
                ent->r.ownerNum, ent->clipmask );

    VectorCopy( tr.endpos, ent->r.currentOrigin );

    // even with this commented out sometimes fraction is 0

    if ( tr.startsolid ) {
        tr.fraction = 0;
        //		G_Printf("settting tr.fraction to 0\n");
    }

    trap_LinkEntity( ent );

    // tr.fraction is a float
    if ( tr.fraction != 1 ) {
        // never explode or bounce on sky
        if ( (tr.surfaceFlags & SURF_NOIMPACT) && (! g_skyHook.integer ) ) {
            // If grapple, reset owner
            if (ent->parent && ent->parent->client->offhandhook == ent)
                ent->parent->client->offhandhook = NULL;
            G_FreeEntity( ent );
            return;
        }

        // try to prevent multiple OffHand_grappleImpact() calls
        // seems to depend on what direction client is facing
        if (tr.fraction != 0)
			//G_Printf("G_Run_Offhand_Grapple()->OffHand_grappleImpact()\n");
            OffHand_grappleImpact(ent, &g_entities[tr.entityNum], &tr);
        //		if ( ent->s.eType != ET_OFFHAND_GRAPPLE ) {
        //			return;		// exploded
        //		}
    }

    // check think function after bouncing



    G_RunThink( ent );
}
*/
/*
========================
G_RunGrappleHooked
 
Runs a grapple already hooked to a wall
========================
*/
/*
void G_RunGrappleHooked(gentity_t *ent) {
    //	gentity_t		*tent;
//	vec3_t	vec;
//	float	len = 0;
    //    G_Printf("G_RunGrappleHooked()\n");
	
    if (ent->enemy || ( ent->parent->enemy->s.eType == ET_MOVER)) {
        vec3_t v, oldorigin;
        VectorCopy(ent->r.currentOrigin, oldorigin);
        VectorCopy(ent->enemy->r.currentOrigin, v);
        SnapVectorTowards( v, oldorigin );	// save net bandwidth
        G_SetOrigin( ent, v );
     }

	VectorCopy( ent->r.currentOrigin, ent->parent->client->ps.grapplePoint);
	

}
*/


/*	
	VectorSubtract(ent->parent->r.currentOrigin,ent->r.currentOrigin, vec);
		//ent->parent->client->ps.grapplePoint, vec);
	len = VectorNormalize (vec);
	Com_Printf("%f %f %f %f", len, vec[0], vec[1], vec[2]);
    if(len <= 10)
		;
	else
*/

// OffHand_grappleImpact: called when the grapple hits something
// moved from g_missile.c:G_MissileImpact
// called from G_Run_Offhand_Grapple()
// multiple times if facing a certain direction???
void Grapple_Drop_Flag(gentity_t *ent, gentity_t *other);
void OffHand_grappleImpact(gentity_t *ent, gentity_t *other, trace_t *trace) {
    //    gentity_t *nent;// *tent;
    vec3_t v;
    // Hugo
//    vec3_t vel, forward, right, up;
    //float vlen;

    //    nent = G_Spawn();
	Grapple_Drop_Flag(ent, other);

    if ( other->takedamage && other->client) {
        // Joe - for bleeding?
        //        nent->s.clientNum = ent->r.ownerNum;
        G_AddEvent( ent, EV_MISSILE_HIT, DirToByte( trace->plane.normal ) );
        ent->enemy = other;
        ent->s.pos.trType = TR_INTERPOLATE;
        VectorCopy(other->r.currentOrigin, v);
        ent->s.eType = ET_OFFHAND_GRAPPLE;
		
    } else {
        VectorCopy(trace->endpos, v);
		G_AddEvent( ent, EV_MISSILE_MISS, DirToByte( trace->plane.normal ) );
        ent->enemy = NULL;
		ent->delay = level.time + HOOK_TIME * 1000;
        ent->s.pos.trType = TR_STATIONARY;
	}

	SnapVectorTowards( v, ent->s.pos.trBase );      // save net bandwidth
	VectorCopy( v, ent->s.pos.trBase );
	VectorCopy( v, ent->r.currentOrigin );
	ent->parent->client->ps.pm_flags |= PMF_GRAPPLE_PULL;
	VectorCopy( ent->r.currentOrigin, ent->parent->client->ps.grapplePoint);

	//Incin -- doubling the work here -- removed...
	//was probably causing the jerky movement
    // Hugo = making pm_grapplemove here instead of doing a pmove
/*
	if((g_grappleBuddies.integer || g_grappleEnemies.integer) ||
		(g_grappleBuddies.integer && g_grappleEnemies.integer)) {
		AngleVectors (ent->parent->client->ps.viewangles , forward, right, up);
		VectorScale(forward, -16, v);
		VectorAdd(ent->parent->client->ps.grapplePoint, v, v);
		VectorSubtract(v, ent->parent->client->ps.origin, vel);
		//vlen = VectorLength(vel);
		VectorNormalize( vel );
		//if (vlen <= 100)
		//    VectorScale(vel, 10 * vlen, vel);
		//else {
		VectorScale(vel, getGrapplePullSpeed(), vel);
		//}
		VectorCopy(vel, ent->parent->client->ps.velocity);
		trap_SnapVector(ent->parent->client->ps.velocity);
		
		ent->s.pos.trTime = 0;
		ent->s.pos.trDuration = 0;
		ent->think = Weapon_Offhand_HookThink;
		ent->nextthink = level.time + FRAMETIME;
	}
    // Alliance: If the hit element is a mover, add to its hookchain
    // and set this grapple's ground elt
    // Joe hookchain?  elt?  wha?  sounds good

	if (other->s.eType == ET_MOVER) {
		//addHookChain(ent, other);
        ent->enemy = other;
    }
 */
    trap_LinkEntity( ent );

    // Joe - do this at the bottom after nent is taken care of
    // and maybe hook appears one more time?
    if ( other->takedamage && other->client ) {
        if (( ent->parent->client->ps.persistant[PERS_TEAM] == other->client->ps.persistant[PERS_TEAM] ) &&
                (!g_grappleBuddies.integer)) {
            Weapon_Offhand_HookFree(ent);
        } else if (!g_grappleEnemies.integer) {
            Weapon_Offhand_HookFree(ent);
        }
    }
/*
	// Joe - unhook after hitting a door, etc
    if ( other->s.eType == ET_MOVER  && other->s.pos.trType != TR_STATIONARY){
		Weapon_Offhand_HookFree(ent);
	}
 */
	// covers everything including corpses?
    //    if ( other->takedamage )
    //        Weapon_Offhand_HookFree(ent);
}



// add hook *hook to *mover's hookchain and sets *hook's hookmover.

/*
INCIN --- This is dead code
void addHookChain(gentity_t *hook, gentity_t *mover) {
    if (mover->hookchain == NULL) {
        mover->hookchain = hook;
        hook->hookchain = NULL;
    } else {
        // another hook has already latched on.
        // the odds of this are very low but it's still very much
        // worth implementing
        hook->hookchain = mover->hookchain;
        mover->hookchain = hook;
    }
 
    hook->hookmover = mover;
}
*/
/*
//INCIN -- This is Dead code
// remove *hook from its mover's hookchain. Assumes addHookChain
// was called earlier on hook.
void removeHookChain(gentity_t *hook) {
 
    gentity_t *mover, *curHook;
 
    //	trap_SendServerCommand( -1, "print \"removeHookChain()\n\"");
 
    mover = hook->hookmover;
 
    if (mover == NULL)
        return;	// no mover
 
    if (mover->hookchain == hook) {
        // 1st hook in chain
        mover->hookchain = hook->hookchain;
    } else {
        for (curHook=mover->hookchain; curHook != NULL;
                curHook=curHook->hookchain) {
 
            if (curHook->hookchain == hook)
                curHook->hookchain = hook->hookchain;
        }
 
        if (curHook == NULL) {
            G_Printf("removeHookChain: hook not in mover's hookchain\n");
        }
    }
 
    hook->hookchain = NULL;
    hook->hookmover = NULL;
}
*/

/*
//INCIN -- THIS is dead Code
// moves all the hooks connected to this mover.
void moveMoverHooks(gentity_t *mover, vec3_t move, vec3_t amove) {
    gentity_t *hook;
 
    for (hook=mover->hookchain; hook != NULL; hook = hook->hookchain) {
        //		G_Printf("hookmove: %f %f %f hookamove: %f %f %f\n",
        //			move[0], move[1], move[2], amove[0], amove[2], amove[3]);
        trap_UnlinkEntity(hook);
 
        VectorAdd(hook->s.pos.trBase, move, hook->s.pos.trBase);
 
        // Joe, dll crashed here
        if ( ! hook->parent) {
            G_Printf("hook->parent is null in moveMoverHooks()\n");
            return;
        } else
            // end Joe
            VectorCopy( hook->s.pos.trBase, hook->parent->client->ps.grapplePoint);
 
        //		VectorAdd(hook->r.currentOrigin, move, hook->r.currentOrigin);
        //		VectorCopy(hook->r.currentOrigin, hook->parent->client->ps.grapplePoint);
 
        //		VectorAdd(hook->r.currentAngles, amove, hook->r.currentAngles);
        trap_LinkEntity(hook);
    }
}
*/
