#pragma once

// uncomment this if your mod uses the roll angle in fixangle
// #define KEEP_ROLL

IntrusiveList g_warpzones;
STATIC_INIT(g_warpzones)
{
	g_warpzones = IL_NEW();
}

float warpzone_warpzones_exist;
float warpzone_cameras_exist;

.float warpzone_isboxy;
.vector warpzone_transform; ///< Both a warpzone property, and set on entities found by WarpZone_SearchInRadius
.vector warpzone_shift;     ///< Both a warpzone property, and set on entities found by WarpZone_SearchInRadius
.vector warpzone_origin;
.vector warpzone_angles;
.vector warpzone_forward;
.vector warpzone_targetorigin;
.vector warpzone_targetangles;
.vector warpzone_targetforward;
.float warpzone_fadestart;
.float warpzone_fadeend;
void WarpZone_SetUp(entity e, vector my_org, vector my_ang, vector other_org, vector other_ang);
void WarpZone_Camera_SetUp(entity e, vector my_org, vector my_ang);

float WarpZoneLib_BoxTouchesBrush(vector mi, vector ma, entity e, entity ig);

entity WarpZone_Find(vector mi, vector ma);
void WarpZone_MakeAllSolid();
void WarpZone_MakeAllOther();

#define MOVE_NOTHING -1
entity WarpZone_trace_forent; ///< Temp, callback is allowed to change it
USING(WarpZone_trace_callback_t, void(vector start, vector hit, vector end)); // Called on every elementary trace
WarpZone_trace_callback_t te_csqc_trace_callback;
var void te_csqc_trace_callback_func(vector start, vector hit);
entity WarpZone_trace_transform; ///< Transform accumulator during a trace
entity WarpZone_trace_firstzone; ///< First warpzone hit by a trace (can differ from the requested zone in case of _ThroughZone, the trace is aborted then)
entity WarpZone_trace_lastzone; ///< First warpzone hit by a trace (can differ from the requested zone in case of _ThroughZone, the trace is aborted then)
vector WarpZone_tracetoss_velocity; ///< Ending velocity of a tracetoss (post-transform)
float WarpZone_tracetoss_time; ///< Duration of toss (approximate)

void WarpZone_TraceBox_ThroughZone(vector org, vector min, vector max, vector end, float nomonsters, entity forent, entity zone, WarpZone_trace_callback_t cb);
#define WarpZone_TraceLine_ThroughZone(org, end, nomonsters, forent, zone, cb) \
	WarpZone_TraceBox_ThroughZone(org, '0 0 0', '0 0 0', end, nomonsters, forent, zone, cb)
#define WarpZone_TraceBox(org, mi, ma, end, nomonsters, forent) \
	WarpZone_TraceBox_ThroughZone(org, mi, ma, end, nomonsters, forent, NULL, func_null)
#define WarpZone_TraceLine(org, end, nomonsters, forent) \
	WarpZone_TraceBox(org, '0 0 0', '0 0 0', end, nomonsters, forent)
#define WarpZone_TraceToss(e, forent) \
	WarpZone_TraceToss_ThroughZone(e, forent, NULL, func_null)
void WarpZone_TraceToss_ThroughZone(entity e, entity forent, entity zone, WarpZone_trace_callback_t cb);
void WarpZone_TrailParticles(entity own, float eff, vector org, vector end);
#ifdef CSQC
void WarpZone_TrailParticles_WithMultiplier(entity own, float eff, vector org, vector end, float f, float boxflags);
#endif

// .warpzone_transform and .warpzone_shift are also set on entities found by WarpZone_SearchInRadius
.vector WarpZone_findradius_dist;
.vector WarpZone_findradius_nearest;
// also set: warpzone parameters, so WarpZone_TransformOrigin can transform vectors from blast's to victim's system
//.vector WarpZone_findradius_findorigin;
//.float WarpZone_findradius_findradius;
USING(WarpZone_FindRadius_cond_callback_t, bool(entity e)); // called on every entity found by WarpZone_SearchInRadius
/// Modified findradius aware of warpzones, that only includes entities that meet the "cb" criteria.
/// Most useful if lineofsight, when searching with very specific criteria, or over a range likely to contain many entities.
entity WarpZone_SearchInRadius(vector org, float radius, float nomonsters, WarpZone_FindRadius_cond_callback_t cb);
noref entity WarpZone_SearchInRadius_otherent; ///< Free to use in the callback
/// Modified findradius aware of warpzones
#define WarpZone_FindRadius(org, radius) WarpZone_SearchInRadius(org, radius, MOVE_NOTHING, func_null)

float WarpZone_PlaneDist(entity wz, vector v);
float WarpZone_TargetPlaneDist(entity wz, vector v);
vector WarpZone_TransformOrigin(entity wz, vector v);
vector WarpZone_TransformVelocity(entity wz, vector v);
vector WarpZone_TransformAngles(entity wz, vector v);
vector WarpZone_TransformVAngles(entity wz, vector v);
vector WarpZone_UnTransformOrigin(entity wz, vector v);
vector WarpZone_UnTransformVelocity(entity wz, vector v);
vector WarpZone_UnTransformAngles(entity wz, vector v);
vector WarpZone_UnTransformVAngles(entity wz, vector v);

// reference systems (chained warpzone transforms)
void WarpZone_RefSys_Clear(entity me); // R := id
#define WarpZone_RefSys_Add(me, wz) /* me.R := wz me.R */ \
	WarpZone_RefSys_AddTransform(me, (wz).warpzone_transform, (wz).warpzone_shift)
#define WarpZone_RefSys_AddInverse(me, wz) /* me.R := wz^-1 me.R */ \
	WarpZone_RefSys_AddInverseTransform(me, (wz).warpzone_transform, (wz).warpzone_shift)
void WarpZone_RefSys_AddTransform(entity me, vector t, vector s); // me.R := [t s] me.R
void WarpZone_RefSys_AddInverseTransform(entity me, vector t, vector s); // me.R := [t s]^-1 me.R

// makes this reference system track ref's changes
// NOTE: this is ONLY sensible if WarpZone_RefSys_Add is no longer called on "me" while doing this
// To achieve this, make sure no touch events on warpzone are raised by this entity
// or set a movetype that causes no warpzoning (e.g. MOVETYPE_NONE, MOVETYPE_FOLLOW)
void WarpZone_RefSys_AddIncrementally(entity me, entity ref); // me.R := ref.R me.Rref^-1 me.R; me.Rref := ref.R
void WarpZone_RefSys_BeginAddingIncrementally(entity me, entity ref); // me.Rref := ref.R

vector WarpZone_RefSys_TransformOrigin(entity from, entity to, vector org); // return to.R from.R^-1 org
vector WarpZone_RefSys_TransformVelocity(entity from, entity to, vector vel); // return to.R from.R^-1 vel
vector WarpZone_RefSys_TransformAngles(entity from, entity to, vector ang); // return to.R from.R^-1 ang
vector WarpZone_RefSys_TransformVAngles(entity from, entity to, vector ang); // return to.R from.R^-1 ang
void WarpZone_RefSys_Copy(entity me, entity from); // to.R := from.R
entity WarpZone_RefSys_SpawnSameRefSys(entity me); // spawn().R = me.R

#ifndef BITCLR
# define BITCLR(a, b) ((a) & ~(b))
#endif
#ifndef BITSET
# define BITSET(a, b) ((a) | (b))
#endif
#ifndef BITXOR
# define BITXOR(a, b) (((a) ^ (b))
#endif
#ifndef BITCLR_ASSIGN
# define BITCLR_ASSIGN(a, b) ((a) &= ~(b))
#endif
#ifndef BITSET_ASSIGN
# define BITSET_ASSIGN(a, b) ((a) |= (b))
#endif
#ifndef BITXOR_ASSIGN
# define BITXOR_ASSIGN(a, b) ((a) ^= (b))
#endif
int WarpZoneLib_MoveOutOfSolid(entity e);
#define move_out_of_solid(e) WarpZoneLib_MoveOutOfSolid(e)

bool WarpZoneLib_ExactTrigger_Touch(entity this, entity toucher, bool touchfunc);

// WARNING: this kills the trace globals
#define EXACTTRIGGER_TOUCH(e, t) if (!WarpZoneLib_ExactTrigger_Touch((e), (t), true)) return /* intended for use in touch funcs */
#define EXACTTRIGGER_INIT  WarpZoneLib_ExactTrigger_Init(this, true)
