/*
===========================================================================
Copyright (C) 1997-2001 Id Software, Inc.
Copyright (C) 2000-2002 Mr. Hyde and Mad Dog

This file is part of Lazarus Quake 2 Mod source code.

Lazarus Quake 2 Mod source code 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.

Lazarus Quake 2 Mod source code 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 Lazarus Quake 2 Mod source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/

#include "g_local.h"

void box_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf);

// g_crane.c
//
// Utility functions for manipulating overhead crane
//

#define STEPSIZE 8
#define MAX_PICKUP_DISTANCE 64
#define BUFFER 1 // Bonk buffer
#define CABLE_SEGMENT 32.0
#define SPOT_SEGMENT 32.0
#define CARGO_BUFFER 0.125

qboolean box_movestep (edict_t *ent, vec3_t move, qboolean relink);
void Crane_Move_Begin (edict_t *);

void Moving_Speaker_Think(edict_t *speaker)
{
	qboolean moved;
	vec3_t  offset;
	edict_t *owner;

	owner = speaker->owner;
	if(!owner)
	{
		G_FreeEdict(speaker);
		return;
	}
	if(!owner->inuse)
	{
		G_FreeEdict(speaker);
		return;
	}
	if(speaker->spawnflags & 15)
	{
		if((speaker->spawnflags & 8) && (
			(!speaker->owner->groundentity) ||      // not on the ground
			(!speaker->owner->activator)    ||      // not "activated" by anything
			(!speaker->owner->activator->client)  ) // not activated by client
			) {
			moved = false;
		}
		else
		{
			moved = false;
			VectorSubtract(speaker->s.origin,owner->s.origin,offset);
			if((speaker->spawnflags & 1) && (fabs(offset[0] - speaker->offset[0]) > 0.125) )
				moved = true;
			if((speaker->spawnflags & 2) && (fabs(offset[1] - speaker->offset[1]) > 0.125) )
				moved = true;
			if((speaker->spawnflags & 4) && (fabs(offset[2] - speaker->offset[2]) > 0.125) )
				moved = true;
		}
		if(moved) {
			speaker->s.sound = speaker->owner->noise_index;
	#ifdef LOOP_SOUND_ATTENUATION
			speaker->s.attenuation = speaker->attenuation;
	#endif
		}
		else
			speaker->s.sound = 0;
	}
	else {
		speaker->s.sound = speaker->owner->noise_index;
#ifdef LOOP_SOUND_ATTENUATION
		speaker->s.attenuation = speaker->attenuation;
#endif
	}

	VectorAdd(owner->s.origin,speaker->offset,speaker->s.origin);
	speaker->nextthink = level.time + FRAMETIME;
	gi.linkentity(speaker);
}

edict_t *CrateOnTop (edict_t *from, edict_t *ent)
{
	float maxdist;

	if (!from)
		from = g_edicts;
	else
		from++;

	for ( ; from < &g_edicts[globals.num_edicts] ; from++)
	{
		if (from == ent)
			continue;
		if (!from->inuse)
			continue;
		if (from->movetype != MOVETYPE_PUSHABLE)
			continue;
		if (from->absmin[0] >=  ent->absmax[0]) continue;
		if (from->absmax[0] <=  ent->absmin[0]) continue;
		if (from->absmin[1] >=  ent->absmax[1]) continue;
		if (from->absmax[1] <=  ent->absmin[1]) continue;
		maxdist = VectorLength(ent->velocity)*FRAMETIME + 2.0;
		if (fabs(from->absmin[2] - ent->absmax[2]) > maxdist) continue;
		return from;
	}

	return NULL;
}

void Cargo_Stop (edict_t *ent)
{
	vec3_t v;

	VectorClear (ent->velocity);
	// Sanity check: force cargo to correct elevation
	ent->s.origin[2] += ent->crane_hook->absmin[2] - CARGO_BUFFER - ent->absmax[2];
	ent->think = NULL;
	ent->nextthink = 0;
	ent->gravity   = 0.0;
	VectorAdd(ent->absmax,ent->absmin,v);
	VectorScale(v,0.5,v);
	v[2] = ent->absmax[2];
	gi.positioned_sound (v, ent, CHAN_VOICE, gi.soundindex("tank/thud.wav"), 1, 1, 0);
	gi.linkentity(ent);
	ent->crane_control->busy = false;
}

void cargo_blocked (edict_t *cargo, edict_t *obstacle )
{
	vec3_t	origin;

	VectorAdd(obstacle->s.origin,obstacle->origin_offset,origin);
	cargo->gravity  = 1.0;
	cargo->movetype = MOVETYPE_PUSHABLE;
	cargo->velocity[2] = 0;
	box_movestep (cargo, vec3_origin, true);
	cargo->crane_control->busy = false;
	cargo->crane_hook->crane_cargo = NULL;
	cargo->blocked   = NULL;
	cargo->touch     = box_touch;
	cargo->nextthink = 0;
	gi.linkentity(cargo);
}

void Cargo_Float_Up (edict_t *cargo)
{
	cargo->velocity[2] += sv_gravity->value * FRAMETIME;
	cargo->velocity[0] = cargo->velocity[1] = 0;
	if(cargo->absmax[2] + cargo->velocity[2]*FRAMETIME >=
		cargo->crane_hook->absmin[2]-CARGO_BUFFER)
	{
		cargo->attracted = false;
		cargo->think = Cargo_Stop;
	}
	cargo->nextthink = level.time + FRAMETIME;
	gi.linkentity(cargo);
}

void SetCableLength(edict_t *cable)
{
	int frame;
	float length;

	length = cable->s.origin[2] - cable->crane_hook->absmax[2];
	frame = (int)(length/CABLE_SEGMENT);
	if((frame+1)*CABLE_SEGMENT < length) frame++;
	frame = max(0,min(frame,19));
	cable->s.frame = frame;
}

void SetSpotlightLength(edict_t *hook)
{
	trace_t tr;
	vec3_t start, end;

	start[0] = (hook->absmin[0] + hook->absmax[0])/2;
	start[1] = (hook->absmin[1] + hook->absmax[1])/2;
	start[2] = hook->absmin[2] + 1;
	end[0] = start[0];
	end[1] = start[1];
	end[2] = start[2] - WORLD_SIZE;	// was 8192
	tr = gi.trace(start,NULL,NULL,end,hook,MASK_SOLID);
	hook->crane_light->s.origin[2] = tr.endpos[2] + 1;
}

void Cable_Think(edict_t *cable)
{
	SetCableLength(cable);
	cable->nextthink = level.time + FRAMETIME;
	gi.linkentity(cable);
}

void crane_light_off(edict_t *light)
{
	light->svflags |= SVF_NOCLIENT;
}

void Crane_Move_Done (edict_t *ent)
{
	if(!Q_stricmp(ent->classname,"crane_hook"))
	{
		edict_t *cable;
		edict_t *light;
		// Sanity checks - force hook to correct relative location from hoist...
		ent->s.origin[0] = ent->crane_hoist->s.origin[0] + ent->offset[0];
		ent->s.origin[1] = ent->crane_hoist->s.origin[1] + ent->offset[1];
		// ... and force cargo to correct elevation ...
		if(ent->crane_cargo)
		{
			ent->crane_cargo->s.origin[2] +=
				ent->absmin[2] - CARGO_BUFFER - ent->crane_cargo->absmax[2];
			gi.linkentity(ent->crane_cargo);
		}
		// ... and finally, stop cable and move to correct position
		cable = ent->crane_cable;
		VectorClear(cable->velocity);
		cable->s.origin[0] = ent->s.origin[0] + cable->offset[0];
		cable->s.origin[1] = ent->s.origin[1] + cable->offset[1];
		SetCableLength(cable);
		gi.linkentity(cable);
		light = ent->crane_light;
		if(light)
		{
			VectorClear(light->velocity);
			light->think = crane_light_off;
			light->nextthink = level.time + 1.0;
			gi.linkentity(light);
		}
	}
// Lazarus: ACK! If crate is being carried, it's NOT a MOVETYPE_PUSHABLE!!!!
//	        if(ent->movetype == MOVETYPE_PUSHABLE)
	if(!Q_stricmp(ent->classname,"func_pushable"))
	{
		edict_t *e;

		ent->s.origin[2] += ent->crane_hook->absmin[2] - CARGO_BUFFER - ent->absmax[2];
		// Check to see if any OTHER crates are stacked on this one. If so,
		// adjust their position and velocity as well.
		e = NULL;
		while ((e = CrateOnTop(e, ent)) != NULL)
		{
			VectorClear(e->velocity);
			e->s.origin[2] += ent->crane_hook->absmin[2] - e->absmin[2];
			gi.linkentity(e);
		}
	}
	VectorClear (ent->velocity);
	ent->busy      = false;
	ent->think     = NULL;
	ent->nextthink = 0;
	gi.linkentity(ent);
}

void Crane_Stop(edict_t *control)
{
	if(control->crane_beam->crane_onboard_control)
		Crane_Move_Done(control->crane_beam->crane_onboard_control);
	Crane_Move_Done(control->crane_beam);
	Crane_Move_Done(control->crane_hoist);
	Crane_Move_Done(control->crane_hook);
	if(control->crane_hook->crane_cargo) Crane_Move_Done(control->crane_hook->crane_cargo);
}

qboolean Crane_Hook_Bonk(edict_t *hook, int axis, int dir, vec3_t bonk)
{
	float  fraction, cargo_fraction;
	int    i1,i2;
	edict_t *cargo;
	vec3_t cargo_origin, cargo_bonk, origin, end, forward, start;
	vec3_t mins, maxs;
	trace_t tr;

	VectorClear(end);
	VectorClear(start);
	VectorClear(forward);
	forward[axis] = (float)dir;
	switch (axis)
	{
	case 0:
		// X
		i1 = 1;
		i2 = 2;
		break;
	case 1:
		// Y
		i1 = 0;
		i2 = 2;
		break;
	default:
		// Z
		i1 = 0;
		i2 = 1;
		break;
	}
	cargo = hook->crane_cargo;
	VectorAdd(hook->s.origin,hook->origin_offset,origin);
	VectorCopy(origin,start);
	if(dir > 0)
		start[axis] = origin[axis] + hook->size[axis]/2;
	else
		start[axis] = origin[axis] - hook->size[axis]/2;
	fraction = 1.0;
	mins[axis] =  0;
	mins[i1]   = -hook->size[i1]/2;
	mins[i2]   = -hook->size[i2]/2;
	maxs[axis] =  0;
	maxs[i1]   =  hook->size[i1]/2;
	maxs[i2]   =  hook->size[i2]/2;
	VectorMA(start, WORLD_SIZE, forward, end);	// was 8192
	tr = gi.trace(start,mins,maxs,end,cargo,MASK_PLAYERSOLID);
	if(tr.fraction < fraction && tr.ent != hook->crane_beam &&
		tr.ent != hook->crane_hoist && tr.ent != cargo )
	{
		VectorCopy(tr.endpos,bonk);
		bonk[axis] -= dir*BUFFER;
		fraction = tr.fraction;
	} else {
		VectorCopy(end,bonk);
	}

	if(cargo)
	{
		VectorAdd(cargo->s.origin,cargo->origin_offset,cargo_origin);
		VectorCopy(cargo_origin,start);
		if(dir > 0)
			start[axis] = cargo_origin[axis] + cargo->size[axis]/2;
		else
			start[axis] = cargo_origin[axis] - cargo->size[axis]/2;
		cargo_fraction = 1.0;
		mins[axis] =  0;
		mins[i1]   = -cargo->size[i1]/2+1;
		mins[i2]   = -cargo->size[i2]/2+1;
		maxs[axis] =  0;
		maxs[i1]   =  cargo->size[i1]/2-1;
		maxs[i2]   =  cargo->size[i2]/2-1;
		VectorMA(start, WORLD_SIZE, forward, end);	// was 8192
		tr=gi.trace(start, mins, maxs, end, cargo, MASK_PLAYERSOLID );
		if(tr.fraction < cargo_fraction && tr.ent != hook->crane_beam &&
			tr.ent != hook->crane_hoist && tr.ent != hook )
		{
			VectorCopy(tr.endpos,cargo_bonk);
			cargo_bonk[axis] -= dir*BUFFER;
			cargo_fraction = tr.fraction;
		} else {
			VectorCopy(end,cargo_bonk);
		}
		if(cargo_fraction < 1)
		{
			fraction = cargo_fraction;
			if(dir > 0)
			{
				cargo_bonk[axis] += hook->absmax[axis] - cargo->absmax[axis];
				bonk[axis] = min(bonk[axis],cargo_bonk[axis]);
			}
			else
			{
				cargo_bonk[axis] += hook->absmin[axis] - cargo->absmin[axis];
				bonk[axis] = max(bonk[axis],cargo_bonk[axis]);
			}
		}
	}

	if(fraction < 1)
		return true;
	else
		return false;
}

void Crane_blocked (edict_t *self, edict_t *other)
{
	if ( (other->classname) && (other->movetype == MOVETYPE_PUSHABLE))
	{
		// treat func_pushable like a world brush - attempt to stop
		// crane
		// This *shouldn't* be necessary, but I'm a pessimist
		Crane_Stop(self->crane_control);
		return;

	}

	if (self->crane_control->crane_hook == other)
		return;

	if (!(other->svflags & SVF_MONSTER) && (!other->client) )
	{
		// give it a chance to go away on it's own terms (like gibs)
		T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH);
		// if it's still there, nuke it
		if (other)
		{
			// Lazarus: Some of our ents don't have origin near the model
			vec3_t save;
			VectorCopy(other->s.origin,save);
			VectorMA (other->absmin, 0.5, other->size, other->s.origin);
			BecomeExplosion1 (other);
		}
		return;
	}

	if (level.time < self->touch_debounce_time)
		return;

	if (!self->dmg)
		return;
	self->touch_debounce_time = level.time + 0.5;
	T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
}

void Crane_Move_Final (edict_t *ent)
{
	float bonk_distance;

	if(ent->crane_control->activator->client->use)
	{
		// Use key is still pressed
		bonk_distance = ent->crane_control->crane_increment *
			(ent->crane_bonk - ent->absmin[ent->crane_dir]);
		if(ent->moveinfo.remaining_distance > 0)
			bonk_distance -= ent->moveinfo.remaining_distance;
		bonk_distance = min(bonk_distance,STEPSIZE);
		if(bonk_distance > 0)
		{
			ent->moveinfo.remaining_distance += bonk_distance;
			Crane_Move_Begin(ent);
			return;
		}
	}
	
	if (ent->moveinfo.remaining_distance == 0)
	{
		Crane_Move_Done (ent);
		return;
	}

	VectorScale (ent->moveinfo.dir, ent->moveinfo.remaining_distance / FRAMETIME, ent->velocity);
	if(!Q_stricmp(ent->classname,"crane_hook"))
	{
		VectorCopy(ent->velocity,ent->crane_cable->velocity);
		ent->crane_cable->velocity[2] = 0;
		gi.linkentity(ent);
		if(ent->crane_light != NULL)
		{
			VectorCopy(ent->velocity,ent->crane_light->velocity);
			ent->crane_light->velocity[2] = 0;
			gi.linkentity(ent->crane_light);
		}
	}
	ent->think     = Crane_Move_Done;
	ent->nextthink = level.time + FRAMETIME;
	gi.linkentity(ent);
}

void Crane_Move_Begin (edict_t *ent)
{
	float	frames;

	if ((ent->moveinfo.speed * FRAMETIME) >= ent->moveinfo.remaining_distance)
	{
		Crane_Move_Final (ent);
		return;
	}
	VectorScale (ent->moveinfo.dir, ent->moveinfo.speed, ent->velocity);
	frames = floor((ent->moveinfo.remaining_distance / ent->moveinfo.speed) / FRAMETIME);
	ent->moveinfo.remaining_distance -= frames * ent->moveinfo.speed * FRAMETIME;
	if(!Q_stricmp(ent->classname,"crane_hook"))
	{
		if((ent->crane_light) && (ent->crane_cargo==NULL))
		{
			SetSpotlightLength(ent);
			ent->crane_light->svflags &= ~SVF_NOCLIENT;
		}
		VectorCopy(ent->velocity,ent->crane_cable->velocity);
		ent->crane_cable->velocity[2] = 0;
		gi.linkentity(ent->crane_cable);
		if(ent->crane_light != NULL)
		{
			VectorCopy(ent->velocity,ent->crane_light->velocity);
			ent->crane_light->velocity[2] = 0;
			gi.linkentity(ent->crane_light);
		}
	}
	ent->nextthink = level.time + (frames * FRAMETIME);
	ent->think     = Crane_Move_Final;
	ent->blocked   = Crane_blocked;
	gi.linkentity(ent);
}

void G_FindCraneParts()
{
	vec3_t  dist;
	edict_t *cable;
	edict_t *control;
	edict_t *beam;
	edict_t *hoist;
	edict_t *hook;
	edict_t *light;
	edict_t *p1, *p2;

	edict_t	*e;
	int		direction;
	int		i;

	for (i=1, e=g_edicts+i ; i < globals.num_edicts ; i++,e++)
	{
		if (!e->inuse)
			continue;
		if (!e->classname)
			continue;
		if (Q_stricmp(e->classname,"crane_control"))
			continue;

		control = e;
		beam = G_Find(NULL,FOFS(targetname),control->target);
		if(!beam)
		{
			gi.dprintf("Crane_control with no target\n");
			G_FreeEdict(control);
			return;
		}
		// get path_corner locations to determine movement direction
		p1 = G_Find(NULL,FOFS(targetname),beam->pathtarget);
		if(!p1->target)
		{
			gi.dprintf("Only 1 path_corner in pathtarget sequence for crane_beam\n"
				"(2 are required)\n");
			G_FreeEdict(control);
			G_FreeEdict(beam);
			G_FreeEdict(p1);
			return;
		}
		p2 = G_Find(NULL,FOFS(targetname),p1->target);
		VectorSubtract(p1->s.origin,p2->s.origin,dist);
		if(fabs(dist[0]) > fabs(dist[1]))
		{
			VectorSet(beam->movedir, 1.0,0.0,0.0);
			direction = 0;
			if(p1->s.origin[0] < p2->s.origin[0])
			{
				VectorCopy(p1->s.origin,beam->pos1);
				VectorCopy(p2->s.origin,beam->pos2);
			}
			else
			{
				VectorCopy(p2->s.origin,beam->pos1);
				VectorCopy(p1->s.origin,beam->pos2);
			}
		}
		else
		{
			VectorSet(beam->movedir, 0.0,1.0,0.0);
			direction = 1;
			if(p1->s.origin[1] < p2->s.origin[1])
			{
				VectorCopy(p1->s.origin,beam->pos1);
				VectorCopy(p2->s.origin,beam->pos2);
			}
			else
			{
				VectorCopy(p2->s.origin,beam->pos1);
				VectorCopy(p1->s.origin,beam->pos2);
			}
		}
		hoist = G_Find(NULL,FOFS(targetname),beam->target);
		if(!hoist)
		{
			gi.dprintf("Crane_beam with no target\n");
			G_FreeEdict(control);
			G_FreeEdict(beam);
			return;
		}
		// get path_corner locations to determine movement direction
		p1 = G_Find(NULL,FOFS(targetname),hoist->pathtarget);
		if(!p1->target)
		{
			gi.dprintf("Only 1 path_corner in pathtarget sequence for crane_hoist\n"
				"(2 are required)\n");
			G_FreeEdict(control);
			G_FreeEdict(beam);
			G_FreeEdict(hoist);
			G_FreeEdict(p1);
			return;
		}
		p2 = G_Find(NULL,FOFS(targetname),p1->target);
		VectorSubtract(p1->s.origin,p2->s.origin,dist);
		if(fabs(dist[0]) > fabs(dist[1]))
		{
			VectorSet(hoist->movedir, 1.0,0.0,0.0);
			if(p1->s.origin[0] < p2->s.origin[0])
			{
				VectorCopy(p1->s.origin,hoist->pos1);
				VectorCopy(p2->s.origin,hoist->pos2);
			}
			else
			{
				VectorCopy(p2->s.origin,hoist->pos1);
				VectorCopy(p1->s.origin,hoist->pos2);
			}
		}
		else
		{
			VectorSet(hoist->movedir, 0.0,1.0,0.0);
			if(p1->s.origin[1] < p2->s.origin[1])
			{
				VectorCopy(p1->s.origin,hoist->pos1);
				VectorCopy(p2->s.origin,hoist->pos2);
			}
			else
			{
				VectorCopy(p2->s.origin,hoist->pos1);
				VectorCopy(p1->s.origin,hoist->pos2);
			}
		}

		// correct spawnflags for beam and hoist speakers
		if(beam->speaker)
		{
			if(direction)
				beam->speaker->spawnflags = 2;
			else
				beam->speaker->spawnflags = 1;
		}
		if(hoist->speaker)
		{
			if(direction)
				hoist->speaker->spawnflags = 1;
			else
				hoist->speaker->spawnflags = 2;
		}

		hook = G_Find(NULL,FOFS(targetname),hoist->target);
		if(!hook)
		{
			gi.dprintf("Crane hoist with no target\n");
			G_FreeEdict(control);
			G_FreeEdict(beam);
			G_FreeEdict(hoist);
			return;
		}
		// Turn on hook ambient sound if control is on
		// We use a trick here... since hook by definition cannot
		// be moving unless control is on, then with control off
		// we set hook speaker spawnflag to require hook to be
		// moving to play
		if(hook->speaker)
			hook->speaker->spawnflags = 1 - (control->spawnflags & 1);
			
		// Get offset from hook origin to hoist origin, so we can
		// correct timing problems
		VectorSubtract(hook->s.origin,hoist->s.origin,hook->offset);

		// get path_corner locations to determine movement direction
		p1 = G_Find(NULL,FOFS(targetname),hook->pathtarget);
		if(!p1->target)
		{
			gi.dprintf("Only 1 path_corner in pathtarget sequence for crane_hook\n"
				"(2 are required)\n");
			G_FreeEdict(control);
			G_FreeEdict(beam);
			G_FreeEdict(hoist);
			G_FreeEdict(hook);
			G_FreeEdict(p1);
			return;
		}
		p2 = G_Find(NULL,FOFS(targetname),p1->target);
		VectorSet(hook->movedir,0.0,0.0,1.0);
		if(p1->s.origin[2] < p2->s.origin[2])
		{
			VectorCopy(p1->s.origin,hook->pos1);
			VectorCopy(p2->s.origin,hook->pos2);
		}
		else
		{
			VectorCopy(p2->s.origin,hook->pos1);
			VectorCopy(p1->s.origin,hook->pos2);
		}

		control->crane_control = control;
		control->crane_beam    = beam;
		control->crane_hoist   = hoist;
		control->crane_hook    = hook;
		if(!beam->crane_control) beam->crane_control = control;
		if(control->team)
		{
			beam->crane_control = control;
			if(beam->flags & FL_TEAMSLAVE)
				beam->crane_onboard_control = beam->teammaster;
			else
				beam->crane_onboard_control = beam->teamchain;
		}
		else
			beam->crane_onboard_control = NULL;
		beam->crane_hoist    = hoist;
		beam->crane_hook     = hook;
		hoist->crane_control = beam->crane_control;
		hoist->crane_beam    = beam;
		hoist->crane_hook    = hook;
		hook->crane_control  = beam->crane_control;
		hook->crane_beam     = beam;
		hook->crane_hoist    = hoist;
		if(control->spawnflags & 4)
		{
			beam->dmg  = 0;
			hoist->dmg = 0;
			hook->dmg  = 0;
		}

		if(hook->crane_cable == NULL)
		{
			int   frame;
			float length;

			cable = G_Spawn();
			cable->classname = "crane_cable";
			VectorAdd(hook->absmin,hook->absmax,cable->s.origin);
			VectorScale(cable->s.origin,0.5,cable->s.origin);
			VectorAdd(cable->s.origin,hook->move_origin,cable->s.origin);
			VectorSubtract(cable->s.origin,hook->s.origin,cable->offset);
			cable->s.origin[2] = hoist->absmax[2] - 2;
			cable->model = "models/cable/tris.md2";
			gi.setmodel(cable,cable->model);
			cable->s.skinnum = 0;
#ifdef KMQUAKE2_ENGINE_MOD
			cable->s.renderfx |= RF_NOSHADOW;	// Knightmare- no shadow for crane cable
#endif
			length = hoist->absmax[2]-1 - hook->absmax[2];
			frame = (int)(length/CABLE_SEGMENT);
			if((frame+1)*CABLE_SEGMENT < length) frame++;
			frame = max(0,min(frame,19));
			cable->s.frame = frame;
			cable->solid = SOLID_NOT;
			cable->movetype = MOVETYPE_STOP;
			VectorSet(cable->mins,-2,-2,length);
			VectorSet(cable->maxs, 2, 2,0);
			gi.linkentity(cable);
			beam->crane_cable    = cable;
			hoist->crane_cable   = cable;
			hook->crane_cable    = cable;
			cable->crane_control = control;
			cable->crane_beam    = beam;
			cable->crane_hoist   = hoist;
			cable->crane_hook    = hook;
		}
		control->crane_cable = hook->crane_cable;

		if((hook->spawnflags & 1) && (hook->crane_light == NULL))
		{
			light = G_Spawn();
			light->s.origin[0] = (hook->absmin[0] + hook->absmax[0])/2;
			light->s.origin[1] = (hook->absmin[1] + hook->absmax[1])/2;
			light->s.origin[2] = hook->absmin[2] + 8;
			VectorSet(light->mins,-32,-32,-512);
			VectorSet(light->maxs, 32, 32,   0);
			light->solid      = SOLID_NOT;
			light->movetype   = MOVETYPE_NOCLIP;
			light->s.effects  = EF_SPHERETRANS;
			light->s.modelindex = gi.modelindex("sprites/point.sp2");
			light->s.effects = EF_HYPERBLASTER;
			light->svflags       = SVF_NOCLIENT;
			VectorSubtract(light->s.origin,hook->s.origin,light->offset);
			gi.linkentity(light);
			beam->crane_light    = light;
			hook->crane_light    = light;
			cable->crane_light   = light;
		}
		control->crane_light = hook->crane_light;

		// If control is NOT onboard, move beam speaker (if any) to end of
		// beam closest to control
		if(!beam->crane_onboard_control && beam->speaker) {
			if(beam->movedir[0] > 0) {
				if(control->absmin[1]+control->absmax[1] < beam->absmin[1]+beam->absmax[1])
					beam->speaker->s.origin[1] = beam->absmin[1];
				else
					beam->speaker->s.origin[1] = beam->absmax[1];
			} else {
				if(control->absmin[0]+control->absmax[0] < beam->absmin[0]+beam->absmax[0])
					beam->speaker->s.origin[0] = beam->absmin[0];
				else
					beam->speaker->s.origin[0] = beam->absmax[0];
			}
			beam->speaker->s.origin[2] = control->s.origin[2] + 32;
			VectorSubtract(beam->speaker->s.origin,beam->s.origin,beam->speaker->offset);
		}
	}
}

void Crane_AdjustSpeed(edict_t *ent)
{
	float frames;

	// Adjust speed so that travel time is an integral multiple
	// of FRAMETIME
	if(ent->moveinfo.remaining_distance > 0)
	{
		ent->moveinfo.speed = ent->speed;
		frames = floor((ent->moveinfo.remaining_distance / ent->moveinfo.speed) / FRAMETIME);
		if(frames < 1) frames = 1;
		ent->moveinfo.speed = ent->moveinfo.remaining_distance/(frames*FRAMETIME);
	}
}

void crane_control_action(edict_t *control, edict_t *activator, vec3_t point)
{
	float  Z;
	int    dir;
	int    row, column;
	int    content;
	edict_t *beam, *cable, *cargo, *hoist, *hook;
	trace_t tr;
	vec3_t  center, v;
	vec3_t  end, forward, start, pt;
	vec3_t  bonk, mins, maxs;

	if(!(control->spawnflags & 1))
	{
		if(control->message)
			safe_centerprintf(activator,"%s\n",control->message);
		else
			safe_centerprintf(activator,"No power\n");
		return;
	}

	if(control->busy) return;

	// First make sure player (activator) is on the panel side of the
	// control panel
	// Also get center point of panel side
	switch (control->style)
	{
	case 0:
		if(activator->s.origin[0] > control->absmax[0]) return;
		center[0] = control->absmin[0];
		center[1] = (control->absmin[1] + control->absmax[1])/2;
		center[2] = (control->absmin[2] + control->absmax[2])/2;
		break;
	case 1:
		if(activator->s.origin[1] > control->absmax[1]) return;
		center[0] = (control->absmin[0] + control->absmax[0])/2;
		center[1] = control->absmin[1];
		center[2] = (control->absmin[2] + control->absmax[2])/2;
		break;
	case 2:
		if(activator->s.origin[0] < control->absmin[0]) return;
		center[0] = control->absmax[0];
		center[1] = (control->absmin[1] + control->absmax[1])/2;
		center[2] = (control->absmin[2] + control->absmax[2])/2;
		break;
	case 3:
		if(activator->s.origin[1] < control->absmin[1]) return;
		center[0] = (control->absmin[0] + control->absmax[0])/2;
		center[1] = control->absmax[1];
		center[2] = (control->absmin[2] + control->absmax[2])/2;
		break;
	}
	// now check distance from player to panel
	VectorSubtract(activator->s.origin,center,v);
	if(VectorLength(v) > 64) return;

	beam  = control->crane_beam;
	cable = control->crane_cable;
	hoist = control->crane_hoist;
	hook  = control->crane_hook;
	cargo = hook->crane_cargo;
	if(cargo) cargo->gravity = 0.0;    // reset after making it float up,
									   // otherwise things get jammed up
	control->activator = activator;

	// if any part of crane is currently moving, do nothing.
	if(VectorLength(control->velocity) > 0.) return;
	if(VectorLength(beam->velocity)    > 0.) return;
	if(VectorLength(hoist->velocity)   > 0.) return;
	if(VectorLength(hook->velocity)    > 0.) return;

	// now find which row and column of buttons corresponds to "point"
	row = (2*(point[2] - control->absmin[2]))/(control->absmax[2]-control->absmin[2]);
	if(row < 0) row = 0;
	if(row > 1) row = 1;
	switch (control->style)
	{
	case 1:
		column = (4*(point[0]-control->absmin[0]))/(control->absmax[0]-control->absmin[0]);
		break;
	case 2:
		column = (4*(point[1]-control->absmin[1]))/(control->absmax[1]-control->absmin[1]);
		break;
	case 3:
		column = (4*(point[0]-control->absmax[0]))/(control->absmin[0]-control->absmax[0]);
		break;
	default:
		column = (4*(point[1]-control->absmax[1]))/(control->absmin[1]-control->absmax[1]);
		break;
	}
	if(column < 0) column = 0;
	if(column > 3) column = 3;

	// adjust for controller facing beam movement direction
	if( beam->movedir[0] > 0 && (control->style == 0 || control->style == 2)) {
		if(column == 0 || column == 1) {
			column = 1-column;
			row = 1-row;
		}
	}
	if( beam->movedir[1] > 0 && (control->style == 1 || control->style == 3)) {
		if(column == 0 || column == 1) {
			column = 1-column;
			row = 1-row;
		}
	}

	switch(column)
	{
	case 0:
		//==================
		// move hoist
		//==================
		if(row)
		{
			// hoist away
			if(control->style == 0 || control->style == 1)
				control->crane_increment = 1;
			else
				control->crane_increment = -1;
		}
		else
		{
			// hoist toward
			if(control->style == 0 || control->style == 1)
				control->crane_increment = -1;
			else
				control->crane_increment = 1;
		}
		if(hoist->movedir[0] > 0)
		{
			// hoist travels in X
			dir = 0;
			if(control->crane_increment > 0)
			{
				if(Crane_Hook_Bonk(hook,0,1,bonk))
				{
					bonk[0] += hoist->absmax[0] - hook->absmax[0];
					hoist->crane_bonk = min(bonk[0],hoist->pos2[0]);
				}
				else
					hoist->crane_bonk = hoist->pos2[0];
				hoist->crane_bonk += hoist->absmin[0] - hoist->absmax[0];
			}
			else
			{
				if(Crane_Hook_Bonk(hook,0,-1,bonk))
				{
					bonk[0] += hoist->absmin[0] - hook->absmin[0];
					hoist->crane_bonk = max(bonk[0],hoist->pos1[0]);
				}
				else
					hoist->crane_bonk = hoist->pos1[0];
			}
		}
		else
		{
			// travels in Y
			dir = 1;
			if(control->crane_increment > 0)
			{
				if(Crane_Hook_Bonk(hook,1,1,bonk))
				{
					bonk[1] += hoist->absmax[1] - hook->absmax[1];
					hoist->crane_bonk = min(bonk[1],hoist->pos2[1]);
				}
				else
					hoist->crane_bonk = hoist->pos2[1];
				hoist->crane_bonk += hoist->absmin[1] - hoist->absmax[1];

			}
			else
			{
				if(Crane_Hook_Bonk(hook,1,-1,bonk))
				{
					bonk[1] += hoist->absmin[1] - hook->absmin[1];
					hoist->crane_bonk = max(bonk[1],hoist->pos1[1]);
				}
				else
					hoist->crane_bonk = hoist->pos1[1];
			}
		}
		hoist->crane_dir = dir;
		hoist->moveinfo.remaining_distance = control->crane_increment *
			(hoist->crane_bonk - hoist->absmin[dir]);
		if(hoist->moveinfo.remaining_distance <= 0) return;

		hoist->moveinfo.remaining_distance = min(hoist->moveinfo.remaining_distance,STEPSIZE);
		Crane_AdjustSpeed(hoist);
		VectorSet(hoist->moveinfo.dir,
			hoist->movedir[0]*control->crane_increment,
			hoist->movedir[1]*control->crane_increment,
			0);
		hoist->crane_control = control;

		hook->crane_dir  = dir;
		hook->crane_bonk = hoist->crane_bonk + hook->absmin[dir] -
			hoist->absmin[dir];
		hook->crane_control = control;
		memcpy(&hook->moveinfo,&hoist->moveinfo,sizeof(moveinfo_t));

		cable->crane_dir  = dir;
		cable->crane_bonk = hoist->crane_bonk + cable->absmin[dir] -
			hoist->absmin[dir];
		cable->crane_control = control;
		memcpy(&cable->moveinfo,&hoist->moveinfo,sizeof(moveinfo_t));

		if(cargo)
		{
			cargo->movetype   = MOVETYPE_PUSH;
			cargo->crane_dir  = dir;
			cargo->crane_bonk = hoist->crane_bonk + cargo->absmin[dir] -
				hoist->absmin[dir];
			cargo->crane_control = control;
			memcpy(&cargo->moveinfo,&hoist->moveinfo,sizeof(moveinfo_t));
		}
		Crane_Move_Begin(hoist);
		Crane_Move_Begin(hook);
		if(cargo) Crane_Move_Begin(cargo);
		break;
	case 1:
		//==================
		// move beam
		//==================
		// first re-parent associated speaker, if any
		if(beam->speaker && control == beam->crane_onboard_control)
		{
			beam->speaker->owner = control;
			VectorAdd(control->absmin,control->absmax,beam->speaker->s.origin);
			VectorScale(beam->speaker->s.origin,0.5,beam->speaker->s.origin);
			VectorSubtract(beam->speaker->s.origin,control->s.origin,beam->speaker->offset);
			control->noise_index = beam->noise_index;
		}
		if(row)
		{
			// left arrow
			if(control->style == 0 || control->style == 3)
				control->crane_increment =  1;
			else
				control->crane_increment = -1;
		}
		else
		{
			// right arrow
			if(control->style == 0 || control->style == 3)
				control->crane_increment = -1;
			else
				control->crane_increment =  1;
		}
		if(beam->movedir[0] > 0)
		{
			// travels in X
			dir = 0;
			if(control->crane_increment > 0)
			{
				if(Crane_Hook_Bonk(hook,0,1,bonk))
				{
					bonk[0] += beam->absmax[0] - hook->absmax[0];
					beam->crane_bonk = min(bonk[0],beam->pos2[0]);
				}
				else
					beam->crane_bonk = beam->pos2[0];
				beam->crane_bonk += beam->absmin[0] - beam->absmax[0];
			}
			else
			{
				if(Crane_Hook_Bonk(hook,0,-1,bonk))
				{
					bonk[0] += beam->absmin[0] - hook->absmin[0];
					beam->crane_bonk = max(bonk[0],beam->pos1[0]);
				}
				else
					beam->crane_bonk = beam->pos1[0];
			}
		}
		else
		{
			// travels in Y
			dir = 1;
			if(control->crane_increment > 0)
			{
				if(Crane_Hook_Bonk(hook,1,1,bonk))
				{
					bonk[1] += beam->absmax[1] - hook->absmax[1];
					beam->crane_bonk = min(bonk[1],beam->pos2[1]);
				}
				else
					beam->crane_bonk = beam->pos2[1];
				beam->crane_bonk += beam->absmin[1] - beam->absmax[1];
			}
			else
			{
				if(Crane_Hook_Bonk(hook,1,-1,bonk))
				{
					bonk[1] += beam->absmin[1] - hook->absmin[1];
					beam->crane_bonk = max(bonk[1],beam->pos1[1]);
				}
				else
					beam->crane_bonk = beam->pos1[1];
			}
		}
		beam->crane_dir = dir;
		beam->moveinfo.remaining_distance = control->crane_increment *
			(beam->crane_bonk - beam->absmin[dir]);
//		gi.dprintf("remaining distance = %g\n",beam->moveinfo.remaining_distance);
		if(beam->moveinfo.remaining_distance <= 0) return;
		beam->moveinfo.remaining_distance = min(beam->moveinfo.remaining_distance,STEPSIZE);

		Crane_AdjustSpeed(beam);

		VectorSet(beam->moveinfo.dir,
			beam->movedir[0]*control->crane_increment,
			beam->movedir[1]*control->crane_increment,
			0);
		beam->crane_control = control;

		hoist->crane_dir  = dir;
		hoist->crane_bonk = beam->crane_bonk + hoist->absmin[dir] - beam->absmin[dir];
		hoist->crane_control = control;
		memcpy(&hoist->moveinfo,&beam->moveinfo,sizeof(moveinfo_t));

		hook->crane_dir   = dir;
		hook->crane_bonk  = beam->crane_bonk + hook->absmin[dir] - beam->absmin[dir];
		hook->crane_control = control;
		memcpy(&hook->moveinfo,&beam->moveinfo,sizeof(moveinfo_t));

		cable->crane_dir  = dir;
		cable->crane_bonk = beam->crane_bonk + cable->absmin[dir] -
			beam->absmin[dir];
		cable->crane_control = control;
		memcpy(&cable->moveinfo,&beam->moveinfo,sizeof(moveinfo_t));
		
		if(beam->crane_onboard_control)
		{
			beam->crane_onboard_control->crane_dir  = dir;
			beam->crane_onboard_control->crane_bonk = beam->crane_bonk +
				beam->crane_onboard_control->absmin[dir] -
				beam->absmin[dir];
			beam->crane_onboard_control->crane_control = control;
			memcpy(&beam->crane_onboard_control->moveinfo,&beam->moveinfo,sizeof(moveinfo_t));
		}

		if(cargo)
		{
			cargo->movetype   = MOVETYPE_PUSH;
			cargo->crane_dir  = dir;
			cargo->crane_bonk = beam->crane_bonk + cargo->absmin[dir] - beam->absmin[dir];
			cargo->crane_control = control;
			memcpy(&cargo->moveinfo,&beam->moveinfo,sizeof(moveinfo_t));
		}
		Crane_Move_Begin(beam);
		Crane_Move_Begin(hoist);
		Crane_Move_Begin(hook);
		if(beam->crane_onboard_control)
			Crane_Move_Begin(beam->crane_onboard_control);
		if(cargo) Crane_Move_Begin(cargo);
		break;
	case 2:
		//==================
		// hook up/down
		//==================
		hook->crane_dir = dir = 2;
		if(row)
		{
			// hook up
			control->crane_increment = 1;
			if(Crane_Hook_Bonk(hook,2,1,bonk))
				hook->crane_bonk = min(bonk[2],hook->pos2[2]);
			else
				hook->crane_bonk = hook->pos2[2];
			hook->crane_bonk += hook->absmin[2] - hook->absmax[2];
		}
		else
		{
			// hook down
			if(cargo)
			{
				pt[0] = (cargo->absmin[0] + cargo->absmax[0])/2;
				pt[1] = (cargo->absmin[1] + cargo->absmax[1])/2;
				pt[2] = cargo->absmin[2] - 0.125;
				content = gi.pointcontents(pt);
				if(content & MASK_SOLID)
				{
					BeepBeep(activator);
					return;
				}
			}
			control->crane_increment = -1;
			if(Crane_Hook_Bonk(hook,2,-1,bonk))
				hook->crane_bonk = max(bonk[2],hook->pos1[2]);
			else
				hook->crane_bonk = hook->pos1[2];
		}
		hook->moveinfo.remaining_distance = control->crane_increment *
			(hook->crane_bonk - hook->absmin[hook->crane_dir]);
		if(hook->moveinfo.remaining_distance <= 0)
		{
			BeepBeep(activator);
			return;
		}
		hook->moveinfo.remaining_distance = min(hook->moveinfo.remaining_distance,STEPSIZE);
		Crane_AdjustSpeed(hook);
		VectorSet(hook->moveinfo.dir,0.,0.,(float)(control->crane_increment));
		hook->crane_control = control;
		if(cargo)
		{
			cargo->movetype   = MOVETYPE_PUSH;
			cargo->crane_dir  = dir;
			cargo->crane_bonk = hook->crane_bonk + cargo->absmin[dir] - hook->absmin[dir];
			cargo->crane_control = control;
			VectorSubtract(cargo->s.origin,hook->s.origin,cargo->offset);
			memcpy(&cargo->moveinfo,&hook->moveinfo,sizeof(moveinfo_t));
		}
		cable->think = Cable_Think;
		cable->nextthink = level.time + FRAMETIME;

		Crane_Move_Begin(hook);
		if(cargo) Crane_Move_Begin(cargo);
		break;
	case 3:
		//==================
		// hook/unhook
		//==================
		if(row)
		{
			// pickup cargo

			if(hook->crane_cargo)
			{
				// already carrying something
				BeepBeep(activator);
				return;
			}
			VectorAdd(hook->absmin,hook->absmax,start);
			VectorScale(start,0.5,start);
			VectorSet(forward,0.,0.,-1.);
			VectorMA(start, WORLD_SIZE, forward, end);	// was 8192
			VectorSubtract(hook->absmin,start,mins);
			VectorSubtract(hook->absmax,start,maxs);
			// 06/03/00 change: Use 1/3 the bounding box to force a better hit
			VectorScale(mins,0.3333,mins);
			VectorScale(maxs,0.3333,maxs);
			// end 06/03/00 change
			tr=gi.trace(start, mins, maxs, end, hook, MASK_SOLID);
			if((tr.fraction < 1) && (tr.ent) && (tr.ent->classname) &&
				(tr.ent->movetype == MOVETYPE_PUSHABLE) )
			{
				Z = hook->absmin[2] - tr.ent->absmax[2];
				if(Z > MAX_PICKUP_DISTANCE)
				{
					safe_centerprintf(activator,"Too far\n");
					return;
				}
				if(CrateOnTop(NULL,tr.ent))
				{
					BeepBeep(activator);
					gi.dprintf("Too heavy\n");
					return;
				}
				// run a trace from top of cargo up... if first entity hit is NOT
				// the hook, we can't get there from here.
				if( Z > 0 )
				{
					trace_t tr2;

					VectorMA(tr.ent->mins,0.5,tr.ent->size,start);
					start[2] = tr.ent->maxs[2];
					VectorCopy(tr.ent->size,mins);
					VectorScale(mins,-0.5,mins);
					VectorCopy(tr.ent->size,maxs);
					VectorScale(maxs,0.5,maxs);
					mins[2] = maxs[2] = 0;
					mins[0] += 1; mins[1] += 1; maxs[0] -= 1; maxs[1] -= 1;
					VectorCopy(start,end);
					end[2] += Z + 1;
					tr2=gi.trace(start, mins, maxs, end, hook, MASK_SOLID);
					if((tr2.fraction < 1) && tr2.ent && (tr2.ent != hook))
					{
						safe_centerprintf(activator,"Blocked!\n");
						return;
					}
				}
				Z -= CARGO_BUFFER;   // leave a buffer between hook and cargo
				hook->crane_cargo    = cargo = tr.ent;
				cargo->groundentity  = NULL;
				cargo->crane_control = control;
				cargo->crane_hook    = hook;
				cargo->movetype      = MOVETYPE_PUSH;
				cargo->touch         = NULL;
				// Make cargo float up to the hook
				if(Z > 0)
				{
					control->busy      = true;
					cargo->attracted   = true;
					cargo->gravity     = 0.0;
					cargo->velocity[2] = 0.0;
					cargo->think       = Cargo_Float_Up;
					cargo->blocked     = cargo_blocked;
					cargo->goal_frame  = level.framenum;
					cargo->nextthink   = level.time + FRAMETIME;
					gi.linkentity(cargo);
				}
				else
				{
					gi.positioned_sound (start, cargo, CHAN_VOICE,
						gi.soundindex("tank/thud.wav"), 1, 1, 0);
				}
			}
			else
				BeepBeep(activator);
		}
		else
		{
			// drop cargo

			if(hook->crane_cargo)
			{
				hook->crane_cargo->gravity    = 1.0;
				hook->crane_cargo->movetype   = MOVETYPE_PUSHABLE;
				hook->crane_cargo->touch      = box_touch;
				hook->crane_cargo->crane_control = NULL;
				gi.linkentity(hook->crane_cargo);
				box_movestep (hook->crane_cargo, vec3_origin, true);
				hook->crane_cargo = NULL;
			}
			else
				BeepBeep(activator);
		}
	}
}

void Use_Crane_Control (edict_t *ent, edict_t *other, edict_t *activator)
{ 
	ent->spawnflags ^= 1;
	if(ent->crane_hook->speaker)
		ent->crane_hook->speaker->spawnflags = 1 - (ent->spawnflags & 1);
}

void SP_crane_control (edict_t *self)
{
	if (!self->target)
	{
		gi.dprintf ("crane_control with no target at %s\n", vtos(self->s.origin));
		G_FreeEdict (self);
		return;
	}
	self->classname = "crane_control";
	self->solid     = SOLID_BSP;
	self->movetype  = MOVETYPE_PUSH;
	gi.setmodel (self, self->model);
	self->use       = Use_Crane_Control;
	gi.linkentity (self);
}

void SP_crane_hook (edict_t *self)
{
	vec3_t origin;
	edict_t *speaker;

	gi.setmodel (self, self->model);
	VectorAdd(self->absmin,self->absmax,origin);
	VectorScale(origin,0.5,origin);

	if (!self->targetname)
	{
		gi.dprintf ("crane_hook with no targetname at %s\n", vtos(origin));
		G_FreeEdict (self);
		return;
	}
	self->classname = "crane_hook";
	self->solid     = SOLID_BSP;
	self->movetype  = MOVETYPE_PUSH;
	if(st.noise)
		self->noise_index = gi.soundindex(st.noise);
	else
		self->noise_index = 0;

#ifdef LOOP_SOUND_ATTENUATION
	if (self->attenuation <= 0)
		self->attenuation = ATTN_IDLE;
#endif

	if(!self->speed) self->speed = 160;
	self->moveinfo.speed = self->speed;
	gi.linkentity (self);

	if(self->noise_index && !VectorLength(self->s.origin) )
	{
		speaker = G_Spawn();
		speaker->classname   = "moving_speaker";
		speaker->s.sound     = 0;
		speaker->volume      = 1;
		speaker->attenuation = self->attenuation; // was 1
		speaker->owner       = self;
		speaker->think       = Moving_Speaker_Think;
		speaker->nextthink   = level.time + 2*FRAMETIME;
		speaker->spawnflags  = 0;       // plays constantly
		self->speaker        = speaker;
		VectorAdd(self->absmin,self->absmax,speaker->s.origin);
		VectorScale(speaker->s.origin,0.5,speaker->s.origin);
		VectorSubtract(speaker->s.origin,self->s.origin,speaker->offset);
	}

}


void SP_crane_hoist (edict_t *self)
{
	vec3_t origin;
	edict_t *speaker;

	gi.setmodel (self, self->model);
	VectorAdd(self->absmin,self->absmax,origin);
	VectorScale(origin,0.5,origin);

	if (!self->targetname)
	{
		gi.dprintf ("crane_hoist with no targetname at %s\n", vtos(origin));
		G_FreeEdict (self);
		return;
	}
	if (!self->target)
	{
		gi.dprintf ("crane_hoist with no target at %s\n", vtos(origin));
		G_FreeEdict (self);
		return;
	}
	self->classname = "crane_hoist";
	self->solid     = SOLID_BSP;
	self->movetype  = MOVETYPE_PUSH;
	if(!self->speed) self->speed = 160;
	self->moveinfo.speed = self->speed;
	if(st.noise)
		self->noise_index = gi.soundindex(st.noise);
	else
		self->noise_index = 0;

#ifdef LOOP_SOUND_ATTENUATION
	if (self->attenuation <= 0)
		self->attenuation = ATTN_IDLE;
#endif

	gi.linkentity (self);

	if(self->noise_index && !VectorLength(self->s.origin) )
	{
		speaker = G_Spawn();
		speaker->classname   = "moving_speaker";
		speaker->s.sound     = 0;
		speaker->volume      = 1;
		speaker->attenuation = self->attenuation; // was 1
		speaker->owner       = self;
		speaker->think       = Moving_Speaker_Think;
		speaker->nextthink   = level.time + 2*FRAMETIME;
		speaker->spawnflags  = 7;       // owner must be moving to play
		self->speaker        = speaker;
		VectorAdd(self->absmin,self->absmax,speaker->s.origin);
		VectorScale(speaker->s.origin,0.5,speaker->s.origin);
		VectorSubtract(speaker->s.origin,self->s.origin,speaker->offset);
	}
}


void SP_crane_beam (edict_t *self)
{
	vec3_t  origin;
	edict_t *speaker;

	gi.setmodel (self, self->model);
	VectorAdd(self->absmin,self->absmax,origin);
	VectorScale(origin,0.5,origin);

	if (!self->targetname)
	{
		gi.dprintf ("crane_beam with no targetname at %s\n", vtos(origin));
		G_FreeEdict (self);
		return;
	}
	if (!self->target)
	{
		gi.dprintf ("crane_beam with no target at %s\n", vtos(origin));
		G_FreeEdict (self);
		return;
	}
	if (!self->pathtarget)
	{
		gi.dprintf ("crane_beam with no pathtarget at %s\n", vtos(origin));
		G_FreeEdict (self);
		return;
	}
	self->classname = "crane_beam";
	self->solid     = SOLID_BSP;
	self->movetype  = MOVETYPE_PUSH;

	if(!self->speed) self->speed = 160;
	self->moveinfo.speed = self->speed;
	if(st.noise)
		self->noise_index = gi.soundindex(st.noise);
	else
		self->noise_index = 0;

#ifdef LOOP_SOUND_ATTENUATION
	if (self->attenuation <= 0)
		self->attenuation = ATTN_IDLE;
#endif

	gi.linkentity (self);

	if(self->noise_index && !VectorLength(self->s.origin) )
	{
		speaker = G_Spawn();
		speaker->classname   = "moving_speaker";
		speaker->s.sound     = 0;
		speaker->volume      = 1;
		speaker->attenuation = self->attenuation; // was 1
		speaker->think       = Moving_Speaker_Think;
		speaker->nextthink   = level.time + 2*FRAMETIME;
		speaker->spawnflags  = 7;       // owner must be moving to play
		speaker->owner       = self;    // this will be changed later when we know
		                                // controls are spawned
		self->speaker        = speaker;
		VectorAdd(self->absmin,self->absmax,speaker->s.origin);
		VectorScale(speaker->s.origin,0.5,speaker->s.origin);
		VectorSubtract(speaker->s.origin,self->s.origin,speaker->offset);
	}
}

//===================================================================================

//QUAKED crane_reset - special purpose trigger_relay that calls the associated crane
// to the beam extent closest to the crane_reset. Typically crane_reset is targeted
// by a func_button

void crane_reset_go (edict_t *temp)
{
	edict_t *control;
	control = temp->owner;
	Crane_Move_Begin(control->crane_beam);
	Crane_Move_Begin(control->crane_hoist);
	Crane_Move_Begin(control->crane_hook);
	if(control->crane_beam->crane_onboard_control)
		Crane_Move_Begin(control->crane_beam->crane_onboard_control);
	if(control->crane_hook->crane_cargo) Crane_Move_Begin(control->crane_hook->crane_cargo);
	G_FreeEdict(temp);
}

void crane_reset_use (edict_t *self, edict_t *other, edict_t *activator)
{
	float   d1, d2;
	int     dir;
	edict_t *delay;
	edict_t *crane;
	edict_t *control, *beam, *cable, *cargo, *hoist, *hook;
	vec3_t  bonk, v1, v2;
	

	crane = G_Find (NULL, FOFS(targetname), self->target);
	if(!crane)
	{
		gi.dprintf("Cannot find target of crane_reset at %s\n",vtos(self->s.origin));
		return;
	}
	control = crane->crane_control;
	control->activator = activator;

	if(!(control->spawnflags & 1))
	{
		if(control->message)
			safe_centerprintf(activator,"%s\n",control->message);
		else
			safe_centerprintf(activator,"No power\n");
		return;
	}
	
	beam    = control->crane_beam;
	hoist   = control->crane_hoist;
	hook    = control->crane_hook;
	cable   = control->crane_cable;
	cargo   = hook->crane_cargo;
	VectorSubtract(beam->pos1,self->s.origin,v1);
	VectorSubtract(beam->pos2,self->s.origin,v2);
	d1 = VectorLength(v1);
	d2 = VectorLength(v2);

	if(d2 < d1)
		control->crane_increment = 1;
	else
		control->crane_increment = -1;

	if(beam->movedir[0] > 0)
	{
		// travels in X
		dir = 0;
		if(control->crane_increment > 0)
		{
			if(Crane_Hook_Bonk(hook,0,1,bonk))
			{
				bonk[0] += beam->absmax[0] - hook->absmax[0];
				beam->crane_bonk = min(bonk[0],beam->pos2[0]);
			}
			else
				beam->crane_bonk = beam->pos2[0];
			beam->crane_bonk += beam->absmin[0] - beam->absmax[0];
		}
		else
		{
			if(Crane_Hook_Bonk(hook,0,-1,bonk))
			{
				bonk[0] += beam->absmin[0] - hook->absmin[0];
				beam->crane_bonk = max(bonk[0],beam->pos1[0]);
			}
			else
				beam->crane_bonk = beam->pos1[0];
		}
	}
	else
	{
		// travels in Y
		dir = 1;
		if(control->crane_increment > 0)
		{
			if(Crane_Hook_Bonk(hook,1,1,bonk))
			{
				bonk[1] += beam->absmax[1] - hook->absmax[1];
				beam->crane_bonk = min(bonk[1],beam->pos2[1]);
			}
			else
				beam->crane_bonk = beam->pos2[1];
			beam->crane_bonk += beam->absmin[1] - beam->absmax[1];
		}
		else
		{
			if(Crane_Hook_Bonk(hook,1,-1,bonk))
			{
				bonk[1] += beam->absmin[1] - hook->absmin[1];
				beam->crane_bonk = max(bonk[1],beam->pos1[1]);
			}
			else
				beam->crane_bonk = beam->pos1[1];
		}
	}

	if(beam->speaker && beam->crane_onboard_control)
	{
		beam->speaker->owner = beam->crane_onboard_control;
		VectorAdd(beam->crane_onboard_control->absmin,
			      beam->crane_onboard_control->absmax,
				  beam->speaker->s.origin);
		VectorScale(beam->speaker->s.origin,0.5,beam->speaker->s.origin);
		VectorSubtract(beam->speaker->s.origin,
			           beam->crane_onboard_control->s.origin,beam->speaker->offset);
		beam->speaker->owner->noise_index = beam->noise_index;
	}

	beam->crane_dir = dir;
	beam->moveinfo.remaining_distance = control->crane_increment *
		(beam->crane_bonk - beam->absmin[dir]);
	if(beam->moveinfo.remaining_distance <= 0) return;

	Crane_AdjustSpeed(beam);

	VectorSet(beam->moveinfo.dir,
		beam->movedir[0]*control->crane_increment,
		beam->movedir[1]*control->crane_increment,
		0);
	beam->crane_control = control;

	hoist->crane_dir  = dir;
	hoist->crane_bonk = beam->crane_bonk + hoist->absmin[dir] - beam->absmin[dir];
	hoist->crane_control = control;
	memcpy(&hoist->moveinfo,&beam->moveinfo,sizeof(moveinfo_t));

	hook->crane_dir   = dir;
	hook->crane_bonk  = beam->crane_bonk + hook->absmin[dir] - beam->absmin[dir];
	hook->crane_control = control;
	memcpy(&hook->moveinfo,&beam->moveinfo,sizeof(moveinfo_t));

	cable->crane_dir  = dir;
	cable->crane_bonk = beam->crane_bonk + cable->absmin[dir] - beam->absmin[dir];
	cable->crane_control = control;
	memcpy(&cable->moveinfo,&beam->moveinfo,sizeof(moveinfo_t));
		
	if(beam->crane_onboard_control)
	{
		beam->crane_onboard_control->crane_dir  = dir;
		beam->crane_onboard_control->crane_bonk = beam->crane_bonk +
			beam->crane_onboard_control->absmin[dir] -
			beam->absmin[dir];
		beam->crane_onboard_control->crane_control = control;
		memcpy(&beam->crane_onboard_control->moveinfo,&beam->moveinfo,sizeof(moveinfo_t));
	}

	if(cargo)
	{
		cargo->crane_dir  = dir;
		cargo->crane_bonk = beam->crane_bonk + cargo->absmin[dir] - beam->absmin[dir];
		cargo->crane_control = control;
		memcpy(&cargo->moveinfo,&beam->moveinfo,sizeof(moveinfo_t));
	}
	delay = G_Spawn();
	delay->owner     = control;
	delay->think     = crane_reset_go;
	delay->nextthink = level.time + FRAMETIME;
	gi.linkentity(delay);

	self->count--;
	if(!self->count) {
		self->think = G_FreeEdict;
		self->nextthink = level.time + 1;
	}
}

void SP_crane_reset (edict_t *self)
{
	if (!self->targetname)
	{
		gi.dprintf ("crane_reset with no targetname at %s\n", vtos(self->s.origin));
		G_FreeEdict (self);
		return;
	}
	if (!self->target)
	{
		gi.dprintf ("crane_reset with no target at %s\n", vtos(self->s.origin));
		G_FreeEdict (self);
		return;
	}
	self->use = crane_reset_use;
}