/*
	server/weapons/tesla.qc

	Core logic for the Wunderwaffe special weapon.

	Copyright (C) 2021-2023 NZ:P Team

	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License
	as published by the Free Software Foundation; either version 2
	of the License, or (at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

	See the GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to:

		Free Software Foundation, Inc.
		59 Temple Place - Suite 330
		Boston, MA  02111-1307, USA


*/


// 
// Think function for tesla spark sprite spawned at zombie torso
// 
void() tesla_spark_think =
{
    float prev_frame = self.frame;
    while(self.frame == prev_frame) {
        self.frame = rint(random()*3);
    }
	if(self.ltime < time) {
		remove(self);
	}
	self.nextthink = time + 0.05;
}

// 
// Tesla spark sprite spawned at zombie torso
// 
void(vector pos) tesla_spark = {
	local entity lightning = spawn();
	setmodel(lightning, "models/sprites/lightning.spr");
	setorigin(lightning, pos);
	lightning.think = tesla_spark_think;
	lightning.nextthink = time + 0.05;
	lightning.ltime = time + 1;
}


// 
// 
// 
void(entity hit_ent, entity arc_parent, entity arc_owner, float arc_num, float do_arc) tesla_damage = {
    // set lib models to null
	// (if we remove them RelinkZombies will cry)
	if (hit_ent.head)
		setmodel(hit_ent.head, "");
	if (hit_ent.larm)
		setmodel(hit_ent.larm, "");
	if (hit_ent.rarm)
		setmodel(hit_ent.rarm, "");

    hit_ent.velocity.x = 0;
    hit_ent.velocity.y = 0;

	// window hop fix
	if (self.state == 1 || self.hop_step != 0) {
		self.state = self.hop_step = 0;
	}

    // // change classname so it can't be re-targted
	// hit_ent.classname = "wunder";

    if(arc_owner != world) {
        arc_owner.tesla_n_kills += 1;
        
        // 50 points for waffe kills
        if(arc_owner.classname == "player") {
            arc_owner.kills += 1;
            addmoney(arc_owner, 50, true);
        }
    }

    if(do_arc == true) {
        // float wait_time = lerp(0.2,0.6, random()) * arc_num;
        // The first zombie does not wait, every arc thereafter does
        float wait_time = arc_num == 1 ? 0 : lerp(0.2,0.6, random()) * arc_num;
        hit_ent.tesla_next_arc_time = time + wait_time;
        // print("Entity ", hit_ent.classname, " set to arc after ",ftos(wait_time)," (t=", ftos(hit_ent.tesla_next_arc_time), ")\n");
    }
    else {
        // print("Entity ", hit_ent.classname, " NOT set to arc at.\n");
        hit_ent.tesla_next_arc_time = -1;
    }
    hit_ent.tesla_arc_parent = arc_parent;
    hit_ent.tesla_arc_owner = arc_owner;
    hit_ent.tesla_arc_num = arc_num;
	// play our wunder-ful anim
    entity oself = self;
    self = hit_ent;
    self.th_diewunder();
    self = oself;
}


vector(entity ent) tesla_arc_ofs_for_ent = {
    if(ent.classname == "ai_zombie") {
        if(ent.crawling == true) {
            return '0 0 -18';
        }
        else {
            return '0 0 20';
        }
    }
    else if(ent.classname == "ai_dog") {
        return '0 0 0';
    }
    
    return '0 0 0';
}


// entity cur_ent, entity prev_ent, entity player
void() tesla_arc = {
    if(self.tesla_next_arc_time < 0) {
        return;
    }
    if(self.tesla_next_arc_time > time) {
        return;
    }
    // print("\"",self.classname,"\" arcing ( arcs:",ftos(self.tesla_arc_num)," kills: ",ftos(self.tesla_arc_owner.tesla_n_kills),") for arctime: ");
    // print(ftos(self.tesla_next_arc_time),"\n");
    self.tesla_next_arc_time = -1;
    if(self.tesla_arc_owner.tesla_n_kills >= 10) {
        // print("\tAt max kills. Stopping.\n");
        return;
    }

    self.iszomb = 0;

    // If we can't arc any further, tell parent to keep trying
    if(self.tesla_arc_num >= 5) {
        // We already waited, do it immediately
        self.tesla_arc_parent.tesla_next_arc_time = 0;
        // print("\tAt max arc-counts, keep arcing from parent.\n");
        return;
    }

    // Maximum arcing distance for this zombie
    float max_dist = 300 - (20 * self.tesla_arc_num);
    // print("\tZombie arc to max dist: ", ftos(max_dist), "\n");

	entity best_ent = world;
    if(max_dist > 0) {
        // If arcing, find the closest zombie to current zombie:
        entity ent = findfloat(world, iszomb, 1);
        float dist;
        float best_dist = max_dist;
        while(ent != world) {
            if(ent != self && ent.aistatus == "1") {
                dist = vlen(self.origin - ent.origin);
                if(dist < best_dist) {
                    // Check traceline to make sure is visible
                    vector trace_start = self.origin + tesla_arc_ofs_for_ent(self);
                    vector trace_end = ent.origin + tesla_arc_ofs_for_ent(self);
                    traceline(trace_start, trace_end, 1, world); // MOVE_NOMONSTERS=1
                    if(trace_fraction >= 1.0) {
                        best_dist = dist;
                        best_ent = ent;
                    }
                }
            }
            ent = findfloat(ent, iszomb, 1);
        }
    }

    // print("\tZombie arcing to entity: \"", best_ent.classname, "\" dist: ", ftos(best_dist), "\n");


    // We found a zombie to arc to, continue arc from there
    if(best_ent != world) {
        tesla_damage(best_ent, self, self.tesla_arc_owner, self.tesla_arc_num + 1, true); 

        vector source_pos = self.origin + tesla_arc_ofs_for_ent(self);
        vector target_pos = best_ent.origin + tesla_arc_ofs_for_ent(best_ent);

#ifdef FTE

        te_lightning1(self, source_pos, target_pos);

#else

        WriteByte(MSG_BROADCAST, SVC_TEMPENTITY);
        WriteByte(MSG_BROADCAST, TE_LIGHTNING1);
        WriteEntity(MSG_BROADCAST, self);
        WriteCoord(MSG_BROADCAST, source_pos.x);
        WriteCoord(MSG_BROADCAST, source_pos.y);
        WriteCoord(MSG_BROADCAST, source_pos.z);
        WriteCoord(MSG_BROADCAST, target_pos.x);
        WriteCoord(MSG_BROADCAST, target_pos.y);
        WriteCoord(MSG_BROADCAST, target_pos.z);

#endif // FTE

    }
    // No ent to arc to, tell arc parent to resume its search
    // else if(self.tesla_arc_parent != world) {
    //     // Don't wait, start immediately
    //     self.tesla_arc_parent.tesla_next_arc_time = 0;
    // }
    // Have self immediately arc again next frame
    self.tesla_next_arc_time = 0;
}



// Fire lightning out of the gun
void() W_FireTesla =
{
    vector source;
    entity tempe = spawn();

    makevectors(self.v_angle);
	source = self.origin + self.view_ofs;
    vector barrel_ofs = GetWeaponFlash_Offset(self.weapon);
	source += v_right * (barrel_ofs.x/1000);
    source += v_up * (barrel_ofs.y/1000);
    source += v_forward * (barrel_ofs.z/1000);

#ifdef FTE
	self.dimension_hit = HITBOX_DIM_LIMBS | HITBOX_DIM_ZOMBIES;
#endif // FTE

	FireTrace(1, 0, 0, 0);

#ifdef FTE
	self.dimension_hit = HITBOX_DIM_ZOMBIES;
#endif // FTE

    // Initial Lightning is Red when Pack-A-Punched
    if (IsPapWeapon(self.weapon)) {

#ifdef FTE

        te_lightning2(world, source, trace_endpos);

#else

        WriteByte(MSG_BROADCAST, SVC_TEMPENTITY);
        WriteByte(MSG_BROADCAST, TE_LIGHTNING2);
        WriteEntity(MSG_BROADCAST, tempe);
        WriteCoord(MSG_BROADCAST, source_x);
        WriteCoord(MSG_BROADCAST, source_y);
        WriteCoord(MSG_BROADCAST, source_z);
        WriteCoord(MSG_BROADCAST, trace_endpos_x);
        WriteCoord(MSG_BROADCAST, trace_endpos_y);
        WriteCoord(MSG_BROADCAST, trace_endpos_z);

#endif // FTE

    } else {

#ifdef FTE

        te_lightning1(world, source, trace_endpos);

#else

        WriteByte(MSG_BROADCAST, SVC_TEMPENTITY);
        WriteByte(MSG_BROADCAST, TE_LIGHTNING1);
        WriteEntity(MSG_BROADCAST, tempe);
        WriteCoord(MSG_BROADCAST, source_x);
        WriteCoord(MSG_BROADCAST, source_y);
        WriteCoord(MSG_BROADCAST, source_z);
        WriteCoord(MSG_BROADCAST, trace_endpos_x);
        WriteCoord(MSG_BROADCAST, trace_endpos_y);
        WriteCoord(MSG_BROADCAST, trace_endpos_z);

#endif // FTE

    }

    remove(tempe);

	self.tesla_n_kills = 0;
    
    entity hit_ent = trace_ent;
	if(hit_ent.classname == "ai_zombie_head" || hit_ent.classname == "ai_zombie_larm" || hit_ent.classname == "ai_zombie_rarm")
		hit_ent = hit_ent.owner;

    // print("W_FireTesla hit ent: ", hit_ent.classname, "\n");

    if(trace_ent != world && trace_ent.takedamage) {
        tesla_damage(hit_ent, world, self, 1, true); 
    }
    // Not a direct-hit, do splash damage:
    else {
        tesla_spark(trace_endpos);

        // TODO - Search for proximity to players and damage players
        // TODO - Electric overlay player HUD


        // ------- Find zombie closest to splash damage pos ---------
        // Damage any players within splash damage radius along the way
        float inner_radius = 30;
        float splash_radius = 110;
        entity ent = findradius(trace_endpos, splash_radius);
        float dist;
        entity best_ent = world;
        float best_dist = splash_radius;
        while(ent != world) {
            if(ent == self) {
                // ----------------------------------------
                // Player logic:
                // ----------------------------------------
                // - Same damage with or without jug
                // - Deal 75 damage at `inner_radius` qu
                // - Deal 0 damage at `splash_radius` qu
                // - Linearly interpolate damage in-between
                dist = vlen(ent.origin - trace_endpos);
                // lerp_frac: 1 at inner_radius, 0 at splash_radius
                float lerp_frac = (dist - splash_radius) / (inner_radius - splash_radius); 
                float damage = lerp(0, 75, lerp_frac);
                // print("Giving player ",ftos(damage), " damage.\n");

                if (other.perks & P_JUG)
    				DamageHandler(ent, self, damage, S_ZAPPER);
	    		else
		    		DamageHandler(ent, self, damage, S_ZAPPER);
                // ----------------------------------------
            }

            if(ent.aistatus == "1") {
                dist = vlen(ent.origin - trace_endpos);
                if(dist < best_dist) {
                    best_dist = dist;
                    best_ent = ent;
                }
            }
            ent = ent.chain;
        }
        if(best_ent != world) {
            tesla_damage(best_ent, world, self, 1, true); 
        }
    }
}