// SauerMod - KRSGAME - Kart Racing Simulator by Quinton Reeves
// This is the primary KRS game module.

#include "pch.h"
#include "cube.h"

#include "iengine.h"
#include "igame.h"

#include "krsgame.h"

struct krsclient : igameclient
{
	#include "krsrender.h"
	#include "krsents.h"

    krsentities		et;
    krsdummycom		cc;

    cament *camera;
    krsent *player1;
	
	IVAR(showdebug, 0, 1, 1);

	string mapname;

    krsclient() : et(*this), camera(new cament()), player1(new krsent()) { }
    ~krsclient() { }

    char *getclientmap() { return mapname; }
    icliententities *getents() { return &et; }
    iclientcom *getcom() { return &cc; }

    char *gameident() { return "krs"; }
	char *gamepakdir() { return "krs"; }

	char *defaultmap() { return "start"; }

	char *savedconfig() { return "config.cfg"; }
	char *defaultconfig() { return "defaults.cfg"; }
	char *autoexec() { return "autoexec.cfg"; }
	char *savedservers() { return "servers.cfg"; }

	float stairheight(physent *d) { return 4.1f; }
	float floorz(physent *d) { return 0.867f; }
	float slopez(physent *d) { return 0.5f; }
	float wallz(physent *d) { return 0.2f; }
	float jumpvel(physent *d) { return 100.f; }
	float gravity(physent *d) { return 150.f; }
	float stepspeed(physent *d) { return 1.0f; }

	float watergravscale(physent *d)
	{
		if (iskart(d))
		{
			return vec(d->vel.x, d->vel.y, 0).magnitude() >= gears[MAXGEARS/2] ? 1.f : 32.f;
		}
		return 1.f;
	}
	float waterdampen(physent *d)
	{
		if (iskart(d))
		{
			return vec(d->vel.x, d->vel.y, 0).magnitude() >= gears[MAXGEARS/2] ? 1.6f : 16.f;
		}
		return 1.f;
	}
	float waterfric(physent *d)
	{
		if (iskart(d))
		{
			return vec(d->vel.x, d->vel.y, 0).magnitude() >= gears[MAXGEARS/2] ? 1e8f : 32.f;
		}
		return 6.f;
	}
	
	float floorfric(physent *d)
	{
		if (iskart(d))
		{
			float mag = vec(d->vel.x, d->vel.y, 0).magnitude();
			if (mag)
			{
				return 10.f+(50.f*(mag/(gears[MAXGEARS-1]*1.3f)));
			}
			return 10.f;
		}
		return 6.f;
	}
	float airfric(physent *d)
	{
		return iskart(d) ? 5e2f : 30.f;
	}

	vec feetpos(physent *d)
	{
		if (d->type == ENT_PLAYER || d->type == ENT_AI)
			return vec(d->o).sub(vec(0, 0, d->eyeheight-1));
		
		return vec(d->o);
	}
	
	void updateroll(physent *d) {}

	void updateplayer(krsent *d)
	{
		int move = d->move, strafe = d->strafe;
		vec o(d->o);
		
		if (d->type == ENT_PLAYER)
		{
			if (iskart(d))
			{
				//if (d->inwater && d->floor.z >= floorz(d)) d->move = 0;
				d->strafe = 0;
			}
			else
			{
				d->roll = 0.f;
			}

			moveplayer(d, 20, true);
			
			if (iskart(d))
			{
				d->move = move;
				d->strafe = strafe;
				
				loopi(WL_MAX)
				{
					d->wheels[0][i] = vec(d->xradius*cosf(2*M_PI*i/4.0f), d->yradius*sinf(2*M_PI*i/4.0f), 0);
					d->wheels[0][i].rotate_around_z((d->yaw+90)*RAD);
					d->wheels[0][i].add(d->o);

					vec down(0, 0, -1);
					if(raycubepos(d->wheels[0][i], down, d->wheels[1][i], 0, RAY_CLIPMAT|RAY_SKIPFIRST) == -1)
						d->wheels[1][i] = down.mul(10).add(d->o);
	
					vec unitv;
					float dist = d->wheels[1][i].dist(d->wheels[0][i], unitv);
					unitv.div(dist);
	
					float shorten = 0.0f;
					if(dist > 1024) shorten = 1024;
	
					float barrier = raycube(d->wheels[0][i], unitv, dist, RAY_CLIPMAT|RAY_POLY);
	
					if(barrier < dist && (!shorten || barrier < shorten))
						shorten = barrier;
	
					if(shorten)
					{
						d->wheels[1][i] = unitv;
						d->wheels[1][i].mul(shorten);
						d->wheels[1][i].add(d->wheels[0][i]);
					}
				}

				if (!d->timeinair && (!d->inwater || d->floor.z < floorz(d)))
				{
					vec q;
	
					q = d->wheels[1][WL_FRONT];
					q.sub(d->wheels[1][WL_REAR]);
					d->targpitch = asin(q.z/q.magnitude())/RAD;
	
					q = d->wheels[1][WL_RIGHT];
					q.sub(d->wheels[1][WL_LEFT]);
					d->targroll = asin(q.z/q.magnitude())/RAD;
				}
				else
				{
					d->targpitch = 0.f;
					d->targroll = 0.f;
				}
				
				float rotspeed = curtime/(d->timeinair ? 100.f : (d->inwater ? 50.f : 5.f));
				if (d->targpitch > d->pitch)
				{
					d->pitch += rotspeed;
					if (d->pitch > d->targpitch) d->pitch = d->targpitch;
				}
				else if (d->targpitch < d->pitch)
				{
					d->pitch -= rotspeed;
					if (d->pitch < d->targpitch) d->pitch = d->targpitch;
				}

				if (d->targroll > d->roll)
				{
					d->roll += rotspeed;
					if (d->roll > d->targroll) d->roll = d->targroll;
				}
				else if (d->targroll < d->roll)
				{
					d->roll -= rotspeed;
					if (d->roll < d->targroll) d->roll = d->targroll;
				}
		
				float mag = vec(d->vel.x, d->vel.y, 0).magnitude();
	
				if (mag)
				{
					int numgears = MAXGEARS;
					
					if (!d->timeinair)
					{
						if (move < 0) numgears = 2;
						
						loopi(numgears)
						{
							if (mag < gears[i] || i == numgears-1)
							{
								d->maxspeed = gears[i];
								break;
							}
						}
					}
					else
					{
						d->maxspeed = 10;
					}
					
					if (mag >= 10.f)
					{
						float yawamt = (strafe*(move >= 0 ? -1.f : 1.f))*(curtime/250.f)*(mag/10.f);
						#define incyaw(p) { p->yaw += yawamt; fixcamerarange(p, false); }
						incyaw(d);
						incyaw(camera1);
					}
				}
				else d->maxspeed = gears[0];
			}
			else
			{
				d->roll = 0;
				d->maxspeed = 100;
			}
		}
	}

    void updateworld(vec &pos, int ct, int lm)
    {
		if(!curtime) return;
		
		physicsframe();
		updateplayer(player1);
    }
	
    void initclient() {}
    void physicstrigger(physent *d, bool local, int floorlevel, int waterlevel) {}
    void edittrigger(const selinfo &sel, int op, int arg1 = 0, int arg2 = 0, int arg3 = 0) {}
	void suicide(physent *d) {}
	void newmap(int size) {}
	
    int numdynents() { return 1; }
    dynent *iterdynents(int i)
	{
		if (!i) { return player1; } i--;
		return NULL;
	}

    void resetgamestate() {}
	void startmap(const char *name)
	{
		//player1->o = vec(getworldsize()*0.5f, getworldsize()*0.5f, getworldsize());
		player1->setbounds();
		findplayerspawn(player1);
		recomputecamera();
		camera1->yaw = player1->yaw;
	}

    bool canjump() { return !menuactive() && !player1->inwater; }
    void doattack(bool on)
    {
    	if (player1->state == CS_DEAD)
    	{
    		player1->state = CS_ALIVE;
    		findplayerspawn(player1);
    	}

    }

    void writegamedata(vector<char> &extras) {}
    void readgamedata(vector<char> &extras) {}

	void loadworld(const char *name)
	{
		const char *worldname = *name ? name : "untitled";
		s_strcpy(mapname, worldname);
	}

	void saveworld(const char *name) {}
};

REGISTERGAME(krsgame, "krs", new krsclient(), new krsdummyserver());
