/*
===========================================================================
Copyright (C) 2023 the OpenMoHAA team

This file is part of OpenMoHAA source code.

OpenMoHAA 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.

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

// scriptslave.cpp: Standard scripted objects.  Controlled by ScriptThread.  These objects
// are bmodel objects created in the editor and controlled by an external
// text based script.  Commands are interpretted on by one and executed
// upon a signal from the script master.  The base script object can
// perform several different relative and specific rotations and translations
// and can cause other parts of the script to be executed when touched, damaged,
// touched, or used.
//

#include "g_local.h"
#include "g_phys.h"
#include "class.h"
#include "mover.h"
#include "scriptmaster.h"
#include "scriptthread.h"
#include "scriptslave.h"
#include "scriptexception.h"
#include "sentient.h"
#include "weapon.h"
#include "gibs.h"
#include "explosion.h"
#include "game.h"
#include "debuglines.h"
#include "weaputils.h"
#include "parm.h"

/*****************************************************************************/
/*QUAKED script_object (0 0.5 1) ? NOT_SOLID

******************************************************************************/

Event EV_ScriptSlave_DoMove
(
    "move",
    EV_DEFAULT,
    NULL,
    NULL,
    "Move the script slave.",
    EV_NORMAL
);
Event EV_ScriptSlave_WaitMove
(
    "waitmove",
    EV_DEFAULT,
    NULL,
    NULL,
    "Move the script slave and wait until finished.",
    EV_NORMAL
);
Event EV_ScriptSlave_Angles
(
    "angles",
    EV_DEFAULT,
    "v",
    "angles",
    "Sets the angles.",
    EV_NORMAL
);
Event EV_ScriptSlave_Trigger
(
    "trigger",
    EV_DEFAULT,
    "e",
    "ent",
    "Trigger entities target.",
    EV_NORMAL
);
Event EV_ScriptSlave_Next
(
    "next",
    EV_DEFAULT,
    NULL,
    NULL,
    "Goto the next waypoint.",
    EV_NORMAL
);
Event EV_ScriptSlave_JumpTo
(
    "jumpto",
    EV_DEFAULT,
    "s",
    "vector_or_entity",
    "Jump to specified vector or entity.",
    EV_NORMAL
);
Event EV_ScriptSlave_MoveTo
(
    "moveto",
    EV_DEFAULT,
    "s",
    "vector_or_entity",
    "Move to the specified vector or entity.",
    EV_NORMAL
);
Event EV_ScriptSlave_Speed
(
    "speed",
    EV_DEFAULT,
    "f",
    "speed",
    "Sets the speed.",
    EV_NORMAL
);
Event EV_ScriptSlave_Time
(
    "time",
    EV_DEFAULT,
    "f",
    "travel_time",
    "Sets the travel time.",
    EV_NORMAL
);
Event EV_ScriptSlave_MoveUp
(
    "moveUp",
    EV_DEFAULT,
    "f",
    "dist",
    "Move the position up.",
    EV_NORMAL
);
Event EV_ScriptSlave_MoveDown
(
    "moveDown",
    EV_DEFAULT,
    "f",
    "dist",
    "Move the position down.",
    EV_NORMAL
);
Event EV_ScriptSlave_MoveNorth
(
    "moveNorth",
    EV_DEFAULT,
    "f",
    "dist",
    "Move the position north.",
    EV_NORMAL
);
Event EV_ScriptSlave_MoveSouth
(
    "moveSouth",
    EV_DEFAULT,
    "f",
    "dist",
    "Move the position south.",
    EV_NORMAL
);
Event EV_ScriptSlave_MoveEast
(
    "moveEast",
    EV_DEFAULT,
    "f",
    "dist",
    "Move the position east.",
    EV_NORMAL
);
Event EV_ScriptSlave_MoveWest
(
    "moveWest",
    EV_DEFAULT,
    "f",
    "dist",
    "Move the position west.",
    EV_NORMAL
);
Event EV_ScriptSlave_MoveForward
(
    "moveForward",
    EV_DEFAULT,
    "f",
    "dist",
    "Move the position forward.",
    EV_NORMAL
);
Event EV_ScriptSlave_MoveBackward
(
    "moveBackward",
    EV_DEFAULT,
    "f",
    "dist",
    "Move the position backward.",
    EV_NORMAL
);
Event EV_ScriptSlave_MoveLeft
(
    "moveLeft",
    EV_DEFAULT,
    "f",
    "dist",
    "Move the position left.",
    EV_NORMAL
);
Event EV_ScriptSlave_MoveRight
(
    "moveRight",
    EV_DEFAULT,
    "f",
    "dist",
    "Move the position right.",
    EV_NORMAL
);
Event EV_ScriptSlave_RotateXDownTo
(
    "rotateXdownto",
    EV_DEFAULT,
    "f",
    "angle",
    "Rotate the x down to angle.",
    EV_NORMAL
);
Event EV_ScriptSlave_RotateYDownTo
(
    "rotateYdownto",
    EV_DEFAULT,
    "f",
    "angle",
    "Rotate the y down to angle.",
    EV_NORMAL
);
Event EV_ScriptSlave_RotateZDownTo
(
    "rotateZdownto",
    EV_DEFAULT,
    "f",
    "angle",
    "Rotate the z down to angle.",
    EV_NORMAL
);
Event EV_ScriptSlave_RotateAxisDownTo
(
    "rotateaxisdownto",
    EV_DEFAULT,
    "if",
    "axis angle",
    "Rotate the specified axis down to angle.",
    EV_NORMAL
);
Event EV_ScriptSlave_RotateXUpTo
(
    "rotateXupto",
    EV_DEFAULT,
    "f",
    "angle",
    "Rotate the x up to angle.",
    EV_NORMAL
);
Event EV_ScriptSlave_RotateYUpTo
(
    "rotateYupto",
    EV_DEFAULT,
    "f",
    "angle",
    "Rotate the y up to angle.",
    EV_NORMAL
);
Event EV_ScriptSlave_RotateZUpTo
(
    "rotateZupto",
    EV_DEFAULT,
    "f",
    "angle",
    "Rotate the z up to angle.",
    EV_NORMAL
);
Event EV_ScriptSlave_RotateAxisUpTo
(
    "rotateaxisupto",
    EV_DEFAULT,
    "if",
    "axis angle",
    "Rotate the specified axis up to angle.",
    EV_NORMAL
);
Event EV_ScriptSlave_RotateXDown
(
    "rotateXdown",
    EV_DEFAULT,
    "f",
    "angle",
    "Rotate the x down by the specified amount.",
    EV_NORMAL
);
Event EV_ScriptSlave_RotateYDown
(
    "rotateYdown",
    EV_DEFAULT,
    "f",
    "angle",
    "Rotate the y down by the specified amount.",
    EV_NORMAL
);
Event EV_ScriptSlave_RotateZDown
(
    "rotateZdown",
    EV_DEFAULT,
    "f",
    "angle",
    "Rotate the z down by the specified amount.",
    EV_NORMAL
);
Event EV_ScriptSlave_RotateAxisDown
(
    "rotateaxisdown",
    EV_DEFAULT,
    "if",
    "axis angle",
    "Rotate the specified axis down by the specified amount.",
    EV_NORMAL
);
Event EV_ScriptSlave_RotateXUp
(
    "rotateXup",
    EV_DEFAULT,
    "f",
    "angle",
    "Rotate the x up by the specified amount.",
    EV_NORMAL
);
Event EV_ScriptSlave_RotateYUp
(
    "rotateYup",
    EV_DEFAULT,
    "f",
    "angle",
    "Rotate the y up by the specified amount.",
    EV_NORMAL
);
Event EV_ScriptSlave_RotateZUp
(
    "rotateZup",
    EV_DEFAULT,
    "f",
    "angle",
    "Rotate the z up by the specified amount.",
    EV_NORMAL
);
Event EV_ScriptSlave_RotateAxisUp
(
    "rotateaxisup",
    EV_DEFAULT,
    "if",
    "axis angle",
    "Rotate the specified axis up by the specified amount.",
    EV_NORMAL
);
Event EV_ScriptSlave_RotateX
(
    "rotateX",
    EV_DEFAULT,
    "f",
    "avelocity",
    "Rotate about the x axis at the specified angular velocity.",
    EV_NORMAL
);
Event EV_ScriptSlave_RotateY
(
    "rotateY",
    EV_DEFAULT,
    "f",
    "avelocity",
    "Rotate about the y axis at the specified angular velocity.",
    EV_NORMAL
);
Event EV_ScriptSlave_RotateZ
(
    "rotateZ",
    EV_DEFAULT,
    "f",
    "avelocity",
    "Rotate about the z axis at the specified angular velocity.",
    EV_NORMAL
);
Event EV_ScriptSlave_RotateAxis
(
    "rotateaxis",
    EV_DEFAULT,
    "if",
    "axis avelocity",
    "Rotate about the specified axis at the specified angular velocity.",
    EV_NORMAL
);
Event EV_ScriptSlave_RotateDownTo
(
    "rotatedownto",
    EV_DEFAULT,
    "v",
    "direction",
    "Rotate down to the specified direction.",
    EV_NORMAL
);
Event EV_ScriptSlave_RotateUpTo
(
    "rotateupto",
    EV_DEFAULT,
    "v",
    "direction",
    "Rotate up to the specified direction.",
    EV_NORMAL
);
Event EV_ScriptSlave_RotateTo
(
    "rotateto",
    EV_DEFAULT,
    "v",
    "direction",
    "Rotate to the specified direction.",
    EV_NORMAL
);
Event EV_ScriptSlave_SetDamage
(
    "setdamage",
    EV_DEFAULT,
    "i",
    "damage",
    "Set the damage.",
    EV_NORMAL
);
Event EV_ScriptSlave_SetMeansOfDeath
(
    "setmeansofdeath",
    EV_DEFAULT,
    "s",
    "means_of_death",
    "Set the damage means of death.",
    EV_NORMAL
);
Event EV_ScriptSlave_SetDamageSpawn
(
    "dmg",
    EV_DEFAULT,
    "i",
    "damage",
    "Set the damage.",
    EV_NORMAL
);
Event EV_ScriptSlave_FollowPath
(
    "followpath",
    EV_DEFAULT,
    "eSSSSSS",
    "path arg1 arg2 arg3 arg4 arg5 arg6",
    "Makes the script slave follow the specified path.  The allowable arguments are ignoreangles,\n"
    "ignorevelocity, normalangles, loop, and a number specifying the start time.",
    EV_NORMAL
);
Event EV_ScriptSlave_FollowPath_RelativeYaw
(
    "path_relativeyaw",
    EV_DEFAULT,
    "f",
    "relativeYaw",
    "Makes the script slave follow the specified path with a yaw offset,",
    EV_NORMAL
);
Event EV_ScriptSlave_EndPath
(
    "endpath",
    EV_DEFAULT,
    NULL,
    NULL,
    "Stop following the path",
    EV_NORMAL
);
Event EV_ScriptSlave_MoveDone
(
    "scriptslave_movedone",
    EV_DEFAULT,
    NULL,
    NULL,
    "Called when the script slave is doen moving",
    EV_NORMAL
);
Event EV_ScriptSlave_FollowingPath
(
    "scriptslave_followingpath",
    EV_DEFAULT,
    NULL,
    NULL,
    "Called every frame to actually follow the path",
    EV_NORMAL
);
Event EV_ScriptSlave_Explode
(
    "explode",
    EV_DEFAULT,
    "f",
    "damage",
    "Creates an explosion at the script slave's position",
    EV_NORMAL
);
Event EV_ScriptSlave_NotShootable
(
    "notshootable",
    EV_DEFAULT,
    NULL,
    NULL,
    "Makes the script slave not shootable",
    EV_NORMAL
);

Event EV_ScriptSlave_OpenAreaPortal
(
    "openportal",
    EV_DEFAULT,
    NULL,
    NULL,
    "Open the area portal enclosed in this object",
    EV_NORMAL
);

Event EV_ScriptSlave_CloseAreaPortal
(
    "closeportal",
    EV_DEFAULT,
    NULL,
    NULL,
    "Close the area portal enclosed in this object",
    EV_NORMAL
);

Event EV_ScriptSlave_PhysicsOn
(
    "physics_on",
    EV_DEFAULT,
    "I",
    "no_collide_entity",
    "Turn physics on this script object on\n"
    "If no_collide_entity is set to 1 then the script slave will not collide with other entities",
    EV_NORMAL
);

Event EV_ScriptSlave_PhysicsOff
(
    "physics_off",
    EV_DEFAULT,
    NULL,
    NULL,
    "Turn physics off this script object on",
    EV_NORMAL
);

Event EV_ScriptSlave_PhysicsVelocity
(
    "physics_velocity",
    EV_DEFAULT,
    "v",
    "impulseVector",
    "Add a physical impulse to an object when it is being physically simulated",
    EV_NORMAL
);

Event EV_ScriptSlave_FlyPath
(
    "flypath",
    EV_DEFAULT,
    "efff",
    "array speed acceleration look_ahead",
    "Makes the script slave fly the specified path with speed and acceleration until reached_distance close to "
    "position",
    EV_NORMAL
);

Event EV_ScriptSlave_ModifyFlyPath
(
    "modifyflypath",
    EV_DEFAULT,
    "efff",
    "array speed acceleration look_ahead",
    "Makes the script slave fly the specified path with speed and acceleration until reached_distance close to "
    "position",
    EV_NORMAL
);

Event EV_ScriptSlave_NormalAngles
(
    "normalangles",
    EV_DEFAULT,
    "b",
    "bUseNormalAngles",
    "Sets the object to use normal angles when travelling on a spline path",
    EV_NORMAL
);

CLASS_DECLARATION(Mover, ScriptSlave, "script_object") {
    {&EV_Bind,                               &ScriptSlave::BindEvent            },
    {&EV_Unbind,                             &ScriptSlave::EventUnbind          },
    {&EV_ScriptSlave_DoMove,                 &ScriptSlave::DoMove               },
    {&EV_ScriptSlave_WaitMove,               &ScriptSlave::WaitMove             },
    {&EV_ScriptSlave_Angles,                 &ScriptSlave::SetAnglesEvent       },
    {&EV_SetAngle,                           &ScriptSlave::SetAngleEvent        },
    {&EV_Model,                              &ScriptSlave::SetModelEvent        },
    {&EV_ScriptSlave_Trigger,                &ScriptSlave::TriggerEvent         },
    {&EV_ScriptSlave_Next,                   &ScriptSlave::GotoNextWaypoint     },
    {&EV_ScriptSlave_JumpTo,                 &ScriptSlave::JumpTo               },
    {&EV_ScriptSlave_MoveTo,                 &ScriptSlave::MoveToEvent          },
    {&EV_ScriptSlave_Speed,                  &ScriptSlave::SetSpeed             },
    {&EV_ScriptSlave_Time,                   &ScriptSlave::SetTime              },
    {&EV_ScriptSlave_MoveUp,                 &ScriptSlave::MoveUp               },
    {&EV_ScriptSlave_MoveDown,               &ScriptSlave::MoveDown             },
    {&EV_ScriptSlave_MoveNorth,              &ScriptSlave::MoveNorth            },
    {&EV_ScriptSlave_MoveSouth,              &ScriptSlave::MoveSouth            },
    {&EV_ScriptSlave_MoveEast,               &ScriptSlave::MoveEast             },
    {&EV_ScriptSlave_MoveWest,               &ScriptSlave::MoveWest             },
    {&EV_ScriptSlave_MoveForward,            &ScriptSlave::MoveForward          },
    {&EV_ScriptSlave_MoveBackward,           &ScriptSlave::MoveBackward         },
    {&EV_ScriptSlave_MoveLeft,               &ScriptSlave::MoveLeft             },
    {&EV_ScriptSlave_MoveRight,              &ScriptSlave::MoveRight            },
    {&EV_ScriptSlave_RotateXDownTo,          &ScriptSlave::RotateXdownto        },
    {&EV_ScriptSlave_RotateYDownTo,          &ScriptSlave::RotateYdownto        },
    {&EV_ScriptSlave_RotateZDownTo,          &ScriptSlave::RotateZdownto        },
    {&EV_ScriptSlave_RotateXUpTo,            &ScriptSlave::RotateXupto          },
    {&EV_ScriptSlave_RotateYUpTo,            &ScriptSlave::RotateYupto          },
    {&EV_ScriptSlave_RotateZUpTo,            &ScriptSlave::RotateZupto          },
    {&EV_ScriptSlave_RotateXDown,            &ScriptSlave::RotateXdown          },
    {&EV_ScriptSlave_RotateYDown,            &ScriptSlave::RotateYdown          },
    {&EV_ScriptSlave_RotateZDown,            &ScriptSlave::RotateZdown          },
    {&EV_ScriptSlave_RotateXUp,              &ScriptSlave::RotateXup            },
    {&EV_ScriptSlave_RotateYUp,              &ScriptSlave::RotateYup            },
    {&EV_ScriptSlave_RotateZUp,              &ScriptSlave::RotateZup            },
    {&EV_ScriptSlave_RotateX,                &ScriptSlave::RotateX              },
    {&EV_ScriptSlave_RotateY,                &ScriptSlave::RotateY              },
    {&EV_ScriptSlave_RotateZ,                &ScriptSlave::RotateZ              },
    {&EV_ScriptSlave_RotateAxisDownTo,       &ScriptSlave::RotateAxisdownto     },
    {&EV_ScriptSlave_RotateAxisUpTo,         &ScriptSlave::RotateAxisupto       },
    {&EV_ScriptSlave_RotateAxisDown,         &ScriptSlave::RotateAxisdown       },
    {&EV_ScriptSlave_RotateAxisUp,           &ScriptSlave::RotateAxisup         },
    {&EV_ScriptSlave_RotateAxis,             &ScriptSlave::RotateZ              },
    {&EV_ScriptSlave_SetDamage,              &ScriptSlave::SetDamage            },
    {&EV_ScriptSlave_SetMeansOfDeath,        &ScriptSlave::SetMeansOfDeath      },
    {&EV_ScriptSlave_SetDamageSpawn,         &ScriptSlave::SetDamage            },
    {&EV_ScriptSlave_FollowPath,             &ScriptSlave::FollowPath           },
    {&EV_ScriptSlave_FollowPath_RelativeYaw, &ScriptSlave::FollowPathRelativeYaw},
    {&EV_ScriptSlave_EndPath,                &ScriptSlave::EndPath              },
    {&EV_ScriptSlave_FollowingPath,          &ScriptSlave::FollowingPath        },
    {&EV_Touch,                              &ScriptSlave::TouchFunc            },
    {&EV_Blocked,                            &ScriptSlave::BlockFunc            },
    {&EV_Activate,                           &ScriptSlave::TriggerFunc          },
    {&EV_Use,                                &ScriptSlave::UseFunc              },
    {&EV_ScriptSlave_MoveDone,               &ScriptSlave::MoveEnd              },
    {&EV_Damage,                             &ScriptSlave::DamageFunc           },
    {&EV_ScriptSlave_RotateDownTo,           &ScriptSlave::Rotatedownto         },
    {&EV_ScriptSlave_RotateUpTo,             &ScriptSlave::Rotateupto           },
    {&EV_ScriptSlave_RotateTo,               &ScriptSlave::Rotateto             },
    {&EV_ScriptSlave_Explode,                &ScriptSlave::Explode              },
    {&EV_ScriptSlave_NotShootable,           &ScriptSlave::NotShootable         },
    {&EV_ScriptSlave_OpenAreaPortal,         &ScriptSlave::OpenPortal           },
    {&EV_ScriptSlave_CloseAreaPortal,        &ScriptSlave::ClosePortal          },
    {&EV_ScriptSlave_PhysicsOn,              &ScriptSlave::PhysicsOn            },
    {&EV_ScriptSlave_PhysicsOff,             &ScriptSlave::PhysicsOff           },
    {&EV_ScriptSlave_PhysicsVelocity,        &ScriptSlave::PhysicsVelocity      },
    {&EV_ScriptSlave_FlyPath,                &ScriptSlave::EventFlyPath         },
    {&EV_ScriptSlave_ModifyFlyPath,          &ScriptSlave::EventModifyFlyPath   },
    {&EV_ScriptSlave_NormalAngles,           &ScriptSlave::EventNormalAngles    },
    {NULL,                                   NULL                               }
};

cvar_t *g_showflypath;

ScriptSlave::ScriptSlave()
{
    g_showflypath = gi.Cvar_Get("g_showflypath", "0", 0);

    AddWaitTill(STRING_TOUCH);
    AddWaitTill(STRING_BLOCK);
    AddWaitTill(STRING_TRIGGER);
    AddWaitTill(STRING_USE);
    AddWaitTill(STRING_DAMAGE);

    if (LoadingSavegame) {
        // Archive function will setup all necessary data
        return;
    }

    // this is a normal entity
    edict->s.eType = ET_GENERAL;

    splineTime = 0;

    ignoreangles   = false;
    ignorevelocity = false;
    moving         = false;

    speed           = 100;
    takedamage      = DAMAGE_YES;
    waypoint        = NULL;
    traveltime      = 0;
    commandswaiting = false;

    splinePath           = NULL;
    splineangles         = false;
    m_fFollowRelativeYaw = 0;

    attack_finished    = 0;
    dmg                = 2;
    dmg_means_of_death = MOD_CRUSH;

    setMoveType(MOVETYPE_PUSH);

    if (spawnflags & 2) {
        edict->s.renderfx = RF_ALWAYSDRAW;
    }

    setSolidType(SOLID_NOT);

    m_pCurPath       = NULL;
    m_iCurNode       = 0;
    m_fCurSpeed      = 0;
    m_fLookAhead     = 0;
    m_fIdealSpeed    = 0;
    m_fIdealAccel    = 0;
    m_fIdealDistance = 100;

    if (spawnflags & 1) {
        PostEvent(EV_BecomeNonSolid, EV_POSTSPAWN);
    }

    edict->s.eFlags |= EF_LINKANGLES;
}

ScriptSlave::~ScriptSlave()
{
    if (splinePath) {
        delete splinePath;
        splinePath = NULL;
    }
}

void ScriptSlave::CheckNewOrders(void)
{
    // make sure position and angles are current
    if (!commandswaiting) {
        commandswaiting = true;
        NewAngles       = localangles;
        NewPos          = localorigin;
    }
}

void ScriptSlave::NewMove(void)
{
    float dist;

    CheckNewOrders();

    commandswaiting = false;

    if (RegisterSize(0)) {
        Event ev = Event(EV_DelayThrow);
        ev.AddConstString(STRING_FAIL);
        BroadcastEvent(0, ev);
    }

    if (m_pCurPath || splinePath) {
        PostEvent(EV_ScriptSlave_FollowingPath, 0);
    } else {
        float t = traveltime;
        if (t == 0) {
            dist = Vector(NewPos - localorigin).length();
            t    = dist / speed;
        }
        LinearInterpolate(NewPos, NewAngles, t, EV_ScriptSlave_MoveDone);
    }
}

void ScriptSlave::BindEvent(Event *ev)
{
    Entity *ent;

    ent = ev->GetEntity(1);
    if (ent) {
        bind(ent);
    }

    // make sure position and angles are current
    NewAngles = localangles;
    NewPos    = localorigin;
}

void ScriptSlave::EventUnbind(Event *ev)
{
    unbind();

    // make sure position and angles are current
    NewAngles = localangles;
    NewPos    = localorigin;
}

void ScriptSlave::DoMove(Event *ev)
{
    NewMove();
}

void ScriptSlave::WaitMove(Event *ev)
{
    NewMove();
    Register(0, Director.CurrentScriptThread());
}

void ScriptSlave::MoveEnd(Event *ev)
{
    Unregister(0);
}

void ScriptSlave::SetAnglesEvent(Event *ev)
{
    CheckNewOrders();
    SetAngles(ev);
    NewAngles = localangles;
}

void ScriptSlave::SetAngleEvent(Event *ev)
{
    float angle;

    angle = ev->GetFloat(1);
    if (angle == -1) {
        ForwardDir = Vector(0, 0, 90);
    } else if (angle == -2) {
        ForwardDir = Vector(0, 0, -90);
    } else {
        ForwardDir = Vector(0, angle, 0);
    }
}

void ScriptSlave::SetModelEvent(Event *ev)
{
    str m;

    m = ev->GetString(1);

    edict->r.svFlags &= ~SVF_NOCLIENT;

    if (!m || strstr(m, ".tik")) {
        setSolidType(SOLID_BBOX);
        setModel(m);
        if (!edict->s.modelindex) {
            hideModel();
            setSolidType(SOLID_NOT);
        }
    } else if (strstr(m, ".spr")) {
        setModel(m);
        if (!edict->s.modelindex) {
            hideModel();
        }
        setSolidType(SOLID_NOT);
    } else {
        setModel(m);
        if (edict->s.modelindex) {
            setSolidType(SOLID_BSP);
        } else {
            hideModel();
            setSolidType(SOLID_NOT);
        }
    }

    if (!edict->s.modelindex) {
        hideModel();
        setSolidType(SOLID_NOT);
    }
}

void ScriptSlave::TriggerEvent(Event *ev)
{
    Entity *ent;
    Event  *e;

    ent = ev->GetEntity(1);
    if (ent) {
        target = ent->TargetName();

        e = new Event(EV_Trigger_ActivateTargets);
        //fixme
        //get "other"
        e->AddEntity(world);
        ProcessEvent(e);
    }
}

void ScriptSlave::GotoNextWaypoint(Event *ev)
{
    CheckNewOrders();

    if (!waypoint) {
        ScriptError("%s is currently not at a waypoint", TargetName().c_str());
        return;
    }

    waypoint = (Waypoint *)G_FindTarget(NULL, waypoint->Target());
    if (!waypoint) {
        ScriptError("%s could not find waypoint %s", TargetName().c_str(), waypoint->Target().c_str());
        return;
    } else {
        NewPos = waypoint->origin;
    }
}

void ScriptSlave::JumpTo(Event *ev)
{
    Entity *part;

    //
    // see if it is a vector
    //
    if (ev->IsVectorAt(1)) {
        NewPos = ev->GetVector(1);
        if (bindmaster) {
            localorigin = bindmaster->getLocalVector(NewPos - bindmaster->origin);
        } else {
            localorigin = NewPos;
        }

        for (part = this; part; part = part->teamchain) {
            part->setOrigin();
            part->origin.copyTo(part->edict->s.origin2);
            part->edict->s.renderfx |= RF_FRAMELERP;
        }
    } else {
        waypoint = ev->GetWaypoint(1);
        if (waypoint) {
            NewPos = waypoint->origin;
            if (bindmaster) {
                localorigin = bindmaster->getLocalVector(NewPos - bindmaster->origin);
            } else {
                localorigin = NewPos;
            }

            for (part = this; part; part = part->teamchain) {
                part->setOrigin();
                part->origin.copyTo(part->edict->s.origin2);
                part->edict->s.renderfx |= RF_FRAMELERP;
            }
        }
    }
}

void ScriptSlave::MoveToEvent(Event *ev)
{
    CheckNewOrders();

    //
    // see if it is a vector
    //
    if (ev->IsVectorAt(1)) {
        NewPos = ev->GetVector(1);
    } else {
        SimpleEntity* ent;

        ent = ev->GetSimpleEntity(1);
        if (ent) {
            //
            // see if it is a waypoint
            //
            if (ent->IsSubclassOfWaypoint()) {
                waypoint = static_cast<Waypoint*>(ent);
            }
            // use the entity's origin
            NewPos = ent->origin;
        }
    }
}

void ScriptSlave::SetSpeed(Event *ev)
{
    speed      = ev->GetFloat(1);
    traveltime = 0;
}

void ScriptSlave::SetTime(Event *ev)
{
    traveltime = ev->GetFloat(1);
}

// Relative move commands

void ScriptSlave::MoveUp(Event *ev)
{
    CheckNewOrders();
    NewPos[2] += ev->GetFloat(1);
}

void ScriptSlave::MoveDown(Event *ev)
{
    CheckNewOrders();
    NewPos[2] -= ev->GetFloat(1);
}

void ScriptSlave::MoveNorth(Event *ev)
{
    CheckNewOrders();
    NewPos[1] += ev->GetFloat(1);
}

void ScriptSlave::MoveSouth(Event *ev)
{
    CheckNewOrders();
    NewPos[1] -= ev->GetFloat(1);
}

void ScriptSlave::MoveEast(Event *ev)
{
    CheckNewOrders();
    NewPos[0] += ev->GetFloat(1);
}

void ScriptSlave::MoveWest(Event *ev)
{
    CheckNewOrders();
    NewPos[0] -= ev->GetFloat(1);
}

void ScriptSlave::MoveForward(Event *ev)
{
    Vector v;
    Vector t;

    CheckNewOrders();

    t = NewAngles + ForwardDir;
    t.AngleVectorsLeft(&v, NULL, NULL);

    NewPos += v * ev->GetFloat(1);
}

void ScriptSlave::MoveBackward(Event *ev)
{
    Vector v;
    Vector t;

    CheckNewOrders();

    t = NewAngles + ForwardDir;
    t.AngleVectorsLeft(&v, NULL, NULL);

    NewPos -= v * ev->GetFloat(1);
}

void ScriptSlave::MoveLeft(Event *ev)
{
    Vector v;
    Vector t;

    CheckNewOrders();

    t = NewAngles + ForwardDir;
    t.AngleVectorsLeft(NULL, &v, NULL);

    NewPos += v * ev->GetFloat(1);
}

void ScriptSlave::MoveRight(Event *ev)
{
    Vector t;
    Vector v;

    CheckNewOrders();

    t = NewAngles + ForwardDir;
    t.AngleVectorsLeft(NULL, &v, NULL);

    NewPos -= v * ev->GetFloat(1);
}

// exact rotate commands

void ScriptSlave::RotateXdownto(Event *ev)
{
    CheckNewOrders();

    NewAngles[0] = ev->GetFloat(1);
    if (NewAngles[0] > localangles[0]) {
        NewAngles[0] -= 360;
    }
}

void ScriptSlave::RotateYdownto(Event *ev)
{
    CheckNewOrders();

    NewAngles[1] = ev->GetFloat(1);
    if (NewAngles[1] > localangles[1]) {
        NewAngles[1] -= 360;
    }
}

void ScriptSlave::RotateZdownto(Event *ev)
{
    CheckNewOrders();

    NewAngles[2] = ev->GetFloat(1);
    if (NewAngles[2] > localangles[2]) {
        NewAngles[2] -= 360;
    }
}

void ScriptSlave::RotateAxisdownto(Event *ev)
{
    int axis;
    CheckNewOrders();

    axis            = ev->GetInteger(1);
    NewAngles[axis] = ev->GetFloat(2);
    if (NewAngles[axis] > localangles[axis]) {
        NewAngles[axis] -= 360;
    }
}

void ScriptSlave::RotateXupto(Event *ev)
{
    CheckNewOrders();

    NewAngles[0] = ev->GetFloat(1);
    if (NewAngles[0] < localangles[0]) {
        NewAngles[0] += 360;
    }
}

void ScriptSlave::RotateYupto(Event *ev)
{
    CheckNewOrders();

    NewAngles[1] = ev->GetFloat(1);
    if (NewAngles[1] < localangles[1]) {
        NewAngles[1] += 360;
    }
}

void ScriptSlave::RotateZupto(Event *ev)
{
    CheckNewOrders();

    NewAngles[2] = ev->GetFloat(1);
    if (NewAngles[2] < localangles[2]) {
        NewAngles[2] += 360;
    }
}

void ScriptSlave::RotateAxisupto(Event *ev)
{
    int axis;
    CheckNewOrders();

    axis            = ev->GetInteger(1);
    NewAngles[axis] = ev->GetFloat(2);
    if (NewAngles[axis] < localangles[axis]) {
        NewAngles[axis] += 360;
    }
}

// full vector rotation

void ScriptSlave::Rotatedownto(Event *ev)
{
    Vector ang;

    CheckNewOrders();

    ang = ev->GetVector(1);

    NewAngles[0] = ang[0];
    if (NewAngles[0] > localangles[0]) {
        NewAngles[0] -= 360;
    }
    NewAngles[1] = ang[1];
    if (NewAngles[1] > localangles[1]) {
        NewAngles[1] -= 360;
    }
    NewAngles[2] = ang[2];
    if (NewAngles[2] > localangles[2]) {
        NewAngles[2] -= 360;
    }
}

void ScriptSlave::Rotateupto(Event *ev)
{
    Vector ang;

    CheckNewOrders();

    ang = ev->GetVector(1);

    NewAngles[0] = ang[0];
    if (NewAngles[0] < localangles[0]) {
        NewAngles[0] += 360;
    }
    NewAngles[1] = ang[1];
    if (NewAngles[1] < localangles[1]) {
        NewAngles[1] += 360;
    }
    NewAngles[2] = ang[2];
    if (NewAngles[2] < localangles[2]) {
        NewAngles[2] += 360;
    }
}

void ScriptSlave::Rotateto(Event *ev)
{
    Vector ang;

    CheckNewOrders();

    ang = ev->GetVector(1);

    NewAngles = ang;
}

// Relative rotate commands

void ScriptSlave::RotateXdown(Event *ev)
{
    CheckNewOrders();
    NewAngles[0] = localangles[0] - ev->GetFloat(1);
}

void ScriptSlave::RotateYdown(Event *ev)
{
    CheckNewOrders();
    NewAngles[1] = localangles[1] - ev->GetFloat(1);
}

void ScriptSlave::RotateZdown(Event *ev)
{
    CheckNewOrders();
    NewAngles[2] = localangles[2] - ev->GetFloat(1);
}

void ScriptSlave::RotateAxisdown(Event *ev)
{
    int axis;
    CheckNewOrders();

    axis            = ev->GetInteger(1);
    NewAngles[axis] = localangles[axis] - ev->GetFloat(2);
}

void ScriptSlave::RotateXup(Event *ev)
{
    CheckNewOrders();
    NewAngles[0] = localangles[0] + ev->GetFloat(1);
}

void ScriptSlave::RotateYup(Event *ev)
{
    CheckNewOrders();
    NewAngles[1] = localangles[1] + ev->GetFloat(1);
}

void ScriptSlave::RotateZup(Event *ev)
{
    CheckNewOrders();
    NewAngles[2] = localangles[2] + ev->GetFloat(1);
}

void ScriptSlave::RotateAxisup(Event *ev)
{
    int axis;
    CheckNewOrders();

    axis            = ev->GetInteger(1);
    NewAngles[axis] = localangles[axis] + ev->GetFloat(2);
}

void ScriptSlave::RotateX(Event *ev)
{
    avelocity[0] = ev->GetFloat(1);
}

void ScriptSlave::RotateY(Event *ev)
{
    avelocity[1] = ev->GetFloat(1);
}

void ScriptSlave::RotateZ(Event *ev)
{
    avelocity[2] = ev->GetFloat(1);
}

void ScriptSlave::RotateAxis(Event *ev)
{
    int axis;

    axis            = ev->GetInteger(1);
    avelocity[axis] = ev->GetFloat(2);
}

void ScriptSlave::TouchFunc(Event* ev)
{
    parm.other = ev->GetEntity(1);
    parm.owner = parm.other;
    Unregister(STRING_TOUCH);
}

void ScriptSlave::BlockFunc(Event* ev)
{
    Entity* other;

    other = ev->GetEntity(1);
    if (level.time >= attack_finished) {
        attack_finished = level.time + 0.5;
        if (dmg) {
            other->Damage(this, this, dmg, origin, vec_zero, vec_zero, 0, 0, dmg_means_of_death);
        }
    }

    parm.other = ev->GetEntity(1);
    parm.owner = parm.other;
    Unregister(STRING_BLOCK);
}

void ScriptSlave::TriggerFunc(Event* ev)
{
    parm.other = ev->GetEntity(1);
    parm.owner = parm.other;
    Unregister(STRING_TRIGGER);
}

void ScriptSlave::UseFunc(Event* ev)
{
    parm.other = ev->GetEntity(1);
    parm.owner = parm.other;
    Unregister(STRING_USE);
}

void ScriptSlave::DamageFunc(Event *ev)
{
    Unregister(STRING_DAMAGE);
}

void ScriptSlave::SetDamage(Event *ev)
{
    dmg = ev->GetInteger(1);
}

void ScriptSlave::SetMeansOfDeath(Event *ev)
{
    dmg_means_of_death = MOD_string_to_int(ev->GetString(1));
}

void ScriptSlave::CreatePath(SplinePath *path, splinetype_t type)
{
    SplinePath *node;

    if (!splinePath) {
        splinePath = new BSpline;
    }

    splinePath->Clear();
    splinePath->SetType(type);

    node = path;
    while (node != NULL) {
        splinePath->AppendControlPoint(node->origin, node->angles, node->speed);
        node = node->GetNext();

        if (node == path) {
            break;
        }
    }
}

void ScriptSlave::FollowPath(Event *ev)
{
    int               i, argnum;
    SimpleEntity     *ent;
    str               token;
    SplinePath       *path;
    qboolean          clamp;
    float             starttime;

    ent             = ev->GetSimpleEntity(1);
    argnum          = 2;
    starttime       = -2;
    clamp           = true;
    ignoreangles    = false;
    ignorevelocity  = false;
    splineangles    = true;
    for (i = argnum; i <= ev->NumArgs(); i++) {
        token = ev->GetString(i);
        if (!Q_stricmp(token, "ignoreangles")) {
            ignoreangles = true;
        } else if (!Q_stricmp(token, "ignorevelocity")) {
            ignorevelocity = true;
        } else if (!Q_stricmp(token, "normalangles")) {
            splineangles = false;
        } else if (!Q_stricmp(token, "loop")) {
            clamp = false;
        } else if (IsNumeric(token)) {
            starttime = atof(token);
            // Fixed in OPM
            // HACKHACK
            //
            // There is a bug in mohaa where the token's raw pointer is directly used
            // but then the token can be freed before comparison, which cause
            // starttime to be always 0.
            // OPM is correct here but for compatibility purpose the bug have to be reproduced.
            // Otherwise, for example the final bomber plane in t3l2 will not fly.
            if (!ev->IsStringAt(i)) {
                starttime = 0;
            }
        } else {
            ScriptError("Unknown followpath command %s.", token.c_str());
        }
    }

    if (ent && ent->IsSubclassOfSplinePath()) {
        path = (SplinePath *)ent;
        if (clamp) {
            CreatePath(path, SPLINE_CLAMP);
        } else {
            CreatePath(path, SPLINE_LOOP);
        }
        splineTime = starttime;
        CancelEventsOfType(EV_ScriptSlave_FollowingPath);
        if (!ignoreangles) {
            avelocity = vec_zero;
        }
        if (!ignorevelocity) {
            velocity = vec_zero;
        }
    }
}

void ScriptSlave::FollowPathRelativeYaw(Event *ev)
{
    m_fFollowRelativeYaw = ev->GetFloat(1);
}

void ScriptSlave::EndPath(Event *ev)
{
    if (!splinePath) {
        return;
    }

    delete splinePath;
    splinePath = NULL;
    if (!ignorevelocity) {
        velocity = vec_zero;
    }
    if (!ignoreangles) {
        avelocity = vec_zero;
    }
}

void ScriptSlave::FollowingPath(Event *ev)
{
    Vector pos;
    Vector orient;
    float  speed_multiplier;

    if (m_pCurPath && m_pCurPath->m_iPoints) {
        Vector vAngles;
        Vector vDeltaAngles;
        Vector vDelta;
        float *vTmp;
        Vector vPrev;
        Vector vCur;
        Vector vTotal;
        float  fCoef;
        Vector vWishPosition;
        Vector vWishAngles;
        Vector vNextWishAngles;
        Vector primal_angles;
        Vector n_angles;

        if (g_showflypath && g_showflypath->integer) {
            for (int i = 0; i < m_pCurPath->m_iPoints; i++) {
                vTmp = m_pCurPath->GetByNode(i, NULL);
                VectorCopy((vTmp + 1), vCur);

                G_DebugBBox(vCur, Vector(-32, -32, -32), Vector(32, 32, 32), 0, 1, 1, 1);

                for (int ii = 0; ii <= 8; ii++) {
                    vTmp = m_pCurPath->GetByNode(i + ii / 10.0, NULL);
                    VectorCopy((vTmp + 1), vPrev);
                    vTmp = m_pCurPath->GetByNode(i + ii / 10.0 + m_fLookAhead, NULL);
                    VectorCopy((vTmp + 1), vCur);

                    G_DebugLine(vPrev, vCur, 0, 1, 0, 1);
                }
            }
        }

        if (m_iCurNode > 0) {
            // Get previous node
            vTmp  = m_pCurPath->GetByNode(m_iCurNode - 1, NULL);
            vPrev = (vTmp + 1);
            // Get current node
            vTmp = m_pCurPath->GetByNode(m_iCurNode, NULL);
            vCur = (vTmp + 1);

            vDelta = vCur - vPrev;
            m_vIdealDir = vDelta;
            VectorNormalize(m_vIdealDir);
            angles.AngleVectorsLeft(&vWishAngles);

            fCoef = ProjectLineOnPlane(vWishAngles, DotProduct(origin, vWishAngles), vPrev, vCur, NULL);
            
            vTmp = m_pCurPath->GetByNode(m_iCurNode - (1.0 - fCoef), NULL);
            vTmp = m_pCurPath->Get(vTmp[0] + m_fLookAhead, NULL);
            vWishPosition = (vTmp + 1);

            if (fCoef > 1.0f) {
                m_iCurNode++;

                if (m_iCurNode >= m_pCurPath->m_iPoints) {
                    velocity  = vec_zero;
                    avelocity = vec_zero;

                    delete m_pCurPath;
                    m_pCurPath = NULL;
                    m_iCurNode = 0;
                    moving     = false;

                    ProcessEvent(EV_ScriptSlave_MoveDone);
                    return;
                }
            }
        } else {
            vTmp          = m_pCurPath->GetByNode(m_iCurNode, NULL);
            vWishPosition = (vTmp + 1);
            vDelta        = vWishPosition - origin;

            if (vDelta.length() <= 32.0f) {
                m_iCurNode++;

                if (m_iCurNode >= m_pCurPath->m_iPoints) {
                    velocity  = vec_zero;
                    avelocity = vec_zero;

                    delete m_pCurPath;
                    m_pCurPath = NULL;
                    m_iCurNode = 0;
                    moving     = false;
                    ProcessEvent(EV_ScriptSlave_MoveDone);
                    return;
                }
            }
        }

        vDelta = vWishPosition - origin;

        if (vDelta.length() > 0.0f) {
            vDelta.normalize();
            VectorToAngles(vDelta, vNextWishAngles);

            //
            // Added in 2.0
            //  Relative yaw
            vNextWishAngles[1] += m_fFollowRelativeYaw;
            if (m_fFollowRelativeYaw == 180) {
                vNextWishAngles[0] *= -1;
            }
        } else {
            vNextWishAngles = angles;
            AngleVectorsLeft(angles, vDelta, NULL, NULL);
        }

        vAngles = angles;

        for (int i = 0; i < 3; i++) {
            float change, error;

            n_angles[i] = vNextWishAngles[i] - angles[i];

            if (n_angles[i] > 180) {
                n_angles[i] -= 360;
            } else if (n_angles[i] < -180) {
                n_angles[i] += 360;
            }

            change = level.frametime * 360.0f;
            error  = 0.33f * n_angles[i];

            if (error < -change) {
                error = -change;
            } else if (error > change) {
                error = change;
            }

            primal_angles[i] = angles[i] + error;
        }

        setAngles(primal_angles);

        vDeltaAngles = (angles - vAngles) * level.frametime;

        if (vDeltaAngles[0] > 180.0f || vDeltaAngles[0] <= -180.0f) {
            vDeltaAngles[0] = 0.0f;
        }

        if (vDeltaAngles[1] > 180.0f || vDeltaAngles[1] <= -180.0f) {
            vDeltaAngles[1] = 0.0f;
        }

        if (vDeltaAngles[2] > 180.0f || vDeltaAngles[2] <= -180.0f) {
            vDeltaAngles[2] = 0.0f;
        }

        if (vDeltaAngles[0] > -1.0f || vDeltaAngles[0] < 1.0f) {
            vDeltaAngles[0] = 0.0f;
        }

        if (vDeltaAngles[1] > -1.0f || vDeltaAngles[1] < 1.0f) {
            vDeltaAngles[1] = 0.0f;
        }

        if (vDeltaAngles[2] > -1.0f || vDeltaAngles[2] < 1.0f) {
            vDeltaAngles[2] = 0.0f;
        }

        avelocity = vDeltaAngles;
        velocity  = vDelta * m_fCurSpeed;

        if (m_fIdealSpeed > m_fCurSpeed) {
            m_fCurSpeed += m_fIdealAccel * level.frametime;

            if (m_fCurSpeed > m_fIdealSpeed) {
                m_fCurSpeed = m_fIdealSpeed;
            }
        } else if (m_fIdealSpeed < m_fCurSpeed) {
            m_fCurSpeed -= m_fIdealAccel * level.frametime;

            if (m_fCurSpeed < m_fIdealSpeed) {
                m_fCurSpeed = m_fIdealSpeed;
            }
        }
    } else {
        if (!splinePath) {
            return;
        }

        if ((splinePath->GetType() == SPLINE_CLAMP) && (splineTime > (splinePath->EndPoint() - 2))) {
            delete splinePath;
            splinePath = NULL;
            if (!ignorevelocity) {
                velocity = vec_zero;
            }
            if (!ignoreangles) {
                avelocity = vec_zero;
            }
            moving = false;
            ProcessEvent(EV_ScriptSlave_MoveDone);
            return;
        }

        speed_multiplier = splinePath->Eval(splineTime, pos, orient);

        splineTime += level.frametime * speed_multiplier;

        velocity = (pos - origin) * (1 / level.frametime);
        if (!ignoreangles) {
            if (splineangles) {
                avelocity = (orient - angles) * (1 / level.frametime);
            } else {
                float len;

                len = velocity.length();
                if (len > 0.05) {
                    Vector ang;
                    Vector dir;
                    float  aroll;

                    aroll           = avelocity[ROLL];
                    dir             = velocity * (1 / len);
                    ang             = dir.toAngles();
                    avelocity       = (ang - angles) * (1 / level.frametime);
                    avelocity[ROLL] = aroll;
                } else {
                    avelocity = vec_zero;
                }
            }
        }
    }

    PostEvent(EV_ScriptSlave_FollowingPath, level.frametime);
}

void ScriptSlave::Explode(Event *ev)
{
    float damage;

    if (ev->NumArgs()) {
        damage = ev->GetFloat(1);
    } else {
        damage = 120.0f;
    }

    CreateExplosion(origin, damage, this, this, this);
}

void ScriptSlave::NotShootable(Event *ev)
{
    setContents(0);
}

void ScriptSlave::OpenPortal(Event *ev)
{
    gi.AdjustAreaPortalState(this->edict, true);
}

void ScriptSlave::ClosePortal(Event *ev)
{
    gi.AdjustAreaPortalState(this->edict, false);
}

void ScriptSlave::PhysicsOn(Event *ev)
{
    commandswaiting = false;
    setMoveType(MOVETYPE_BOUNCE);
    setSolidType(SOLID_BBOX);
    velocity        = Vector(0, 0, 1);
    edict->clipmask = MASK_SOLID | CONTENTS_BODY;
    if (ev->NumArgs() == 1 && ev->GetInteger(1)) {
        edict->clipmask &= ~MASK_SCRIPT_SLAVE;
    }
}

void ScriptSlave::PhysicsOff(Event *ev)
{
    Event *event;

    commandswaiting = false;
    setMoveType(MOVETYPE_PUSH);
    edict->clipmask = 0;
    // become solid again
    event = new Event(EV_Model);
    event->AddString(model);
    PostEvent(event, 0);
}

void ScriptSlave::PhysicsVelocity(Event *ev)
{
    velocity += ev->GetVector(1);
}

void ScriptSlave::EventFlyPath(Event *ev)
{
    SimpleEntity *path;

    m_fIdealDistance = 100.0f;
    m_fLookAhead     = 256.0f;
    m_fIdealAccel    = 35.0f;
    m_fIdealSpeed    = 250.0f;

    if (ev->NumArgs() != 1 && ev->NumArgs() != 2 && ev->NumArgs() != 3 && ev->NumArgs() != 4) {
        ScriptError("wrong number of arguments");
    }

    if (ev->NumArgs() > 1) {
        m_fIdealSpeed = ev->GetFloat(2);
    }

    if (ev->NumArgs() > 2) {
        m_fIdealAccel = ev->GetFloat(3);
    }

    if (ev->NumArgs() > 3) {
        m_fLookAhead = ev->GetFloat(4);
    }

    path = ev->GetSimpleEntity(1);

    if (!path) {
        ScriptError("ScriptSlave Given FlyPath Command with NULL path.");
    }

    if (m_pCurPath) {
        delete m_pCurPath;
    }

    m_pCurPath = new cSpline<4, 512>;

    SetupPath(m_pCurPath, path);
    m_iCurNode = 0;
    CancelEventsOfType(EV_ScriptSlave_FollowingPath);
}

void ScriptSlave::EventModifyFlyPath(Event *ev)
{
    m_fIdealDistance = 100.0f;

    if (ev->NumArgs() != 1 && ev->NumArgs() != 2 && ev->NumArgs() != 3) {
        ScriptError("wrong number of arguments");
    }

    if (ev->NumArgs() > 0) {
        m_fIdealSpeed = ev->GetFloat(1);
    }

    if (ev->NumArgs() > 1) {
        m_fIdealAccel = ev->GetFloat(2);
    }

    if (ev->NumArgs() > 2) {
        m_fLookAhead = ev->GetFloat(3);
    }
}

void ScriptSlave::EventNormalAngles(Event *ev)
{
    splineangles = !ev->GetBoolean(1);
    ignoreangles = false;
}

void ScriptSlave::SetupPath(cSpline<4, 512> *pPath, SimpleEntity *se)
{
    str name;
    int                     iObjNum = 0;
    Vector                  vLastOrigin;
    SimpleEntity           *ent;
    int                     i = 1;
    static cSpline<4, 512> *pTmpPath = new cSpline<4, 512>;

    if (!pPath) {
        return;
    }

    pPath->Reset();
    pTmpPath->Reset();

    vLastOrigin = se->origin;

    name = se->Target();

    if (name.length()) {
        for(ent = se; ent; ent = ent->Next()) {
            Vector vDelta;
            vec4_t vTmp;

            vDelta = vLastOrigin - ent->origin;

            if (vDelta.length() == 0 && i > 1) {
                Com_Printf("^~^~^Warning: ScriptSlave Flying with a Path that contains 2 equal points\n");
            } else {
                vTmp[0] = iObjNum++;
                vTmp[1] = ent->origin[0];
                vTmp[2] = ent->origin[1];
                vTmp[3] = ent->origin[2];
                pTmpPath->Add(vTmp, 0);
                vLastOrigin = ent->origin;
            }

            if (ent == se && i > 1) {
                break;
            }

            i++;
        }
    }

    if (pTmpPath->m_iPoints > 2) {
        float* vTmp;
        float fCurLength = 0;
        vec3_t vLastOrigin;
        vec3_t origin;
        vec3_t vDelta;

        pPath->Reset();

        vTmp = pTmpPath->GetByNode(0, NULL);
        VectorCopy((vTmp + 1), vLastOrigin);
        VectorCopy((vTmp + 1), origin);

        for (i = 0; i < pTmpPath->m_iPoints; i++) {
            vec4_t vTmp2;

            vTmp = pTmpPath->GetByNode(i, NULL);

            VectorCopy((vTmp + 1), vLastOrigin);
            VectorSubtract(vLastOrigin, origin, vDelta);

            fCurLength += VectorLength(vDelta);
            vTmp2[0] = fCurLength;
            vTmp2[1] = vLastOrigin[0];
            vTmp2[2] = vLastOrigin[1];
            vTmp2[3] = vLastOrigin[2];

            pPath->Add(vTmp2, 0);
            VectorCopy(vLastOrigin, origin);
        }
    }
}

/*****************************************************************************/
/*QUAKED script_model (0 0.5 1) (0 0 0) (0 0 0) NOT_SOLID

******************************************************************************/

Event EV_ScriptModel_SetAnim
(
    "anim",
    EV_DEFAULT,
    "s",
    "anim_name",
    "Sets the script model's animation",
    EV_NORMAL
);
Event EV_ScriptModel_AnimDone
(
    "animdone",
    EV_ZERO,
    NULL,
    NULL,
    "Script model animation has finished.",
    EV_NORMAL
);
Event EV_ScriptModel_MoveAnim
(
    "moveanim",
    EV_DEFAULT,
    "s",
    "animName",
    "Makes the script model play an animation and move with the deltas contained in the animation",
    EV_NORMAL
);
Event EV_ScriptModel_MovingAnim
(
    "moving_from_anim",
    EV_DEFAULT,
    NULL,
    NULL,
    "The script model is moving based on an animation",
    EV_NORMAL
);

CLASS_DECLARATION(ScriptSlave, ScriptModel, "script_model") {
    {&EV_SetAngle,               &ScriptModel::SetAngleEvent      },
    {&EV_ScriptModel_SetAnim,    &ScriptModel::SetAnimEvent       },
    {&EV_Model,                  &ScriptModel::SetModelEvent      },
    {&EV_ScriptModel_AnimDone,   &ScriptModel::AnimDoneEvent      },
    {&EV_ScriptModel_MoveAnim,   &ScriptModel::MoveAnimEvent      },
    {&EV_ScriptModel_MovingAnim, &ScriptModel::MovingFromAnimEvent},
    {NULL,                       NULL                             },
};

ScriptModel::ScriptModel()
{
    // this is a tiki model
    edict->s.eType = ET_MODELANIM;

    AddWaitTill(STRING_ANIMDONE);
}

void ScriptModel::SetModelEvent(Event *ev)
{
    ScriptSlave::SetModelEvent(ev);

    if (edict->tiki && !mins.length() && !maxs.length()) {
        gi.TIKI_CalculateBounds(edict->tiki, edict->s.scale, mins, maxs);
    }
}

void ScriptModel::SetAnimEvent(Event *ev)
{
    str animname;

    animname = ev->GetString(1);
    if (animname.length() && edict->tiki) {
        int animnum;

        animnum = gi.Anim_NumForName(edict->tiki, animname);
        if (animnum >= 0) {
            NewAnim(animnum, EV_ScriptModel_AnimDone);
            RestartAnimSlot(0);
        }
    }
}

void ScriptModel::AnimDoneEvent(Event *ev)
{
    CancelEventsOfType(EV_ScriptModel_MovingAnim);
    Unregister(STRING_ANIMDONE);
}

void ScriptModel::MoveAnimEvent(Event *ev)
{
    str animName;
    int animNum;

    animName = ev->GetString(1);

    if (!animName.length()) {
        return;
    }

    animNum = gi.Anim_NumForName(edict->tiki, animName.c_str());
    if (animNum < 0) {
        ScriptError("ScriptModel could not find animation %s.", animName.c_str());
    }

    NewAnim(animNum, EV_ScriptModel_AnimDone);
    RestartAnimSlot(0);
    PostEvent(EV_ScriptModel_MovingAnim, 0);
}

void ScriptModel::MovingFromAnimEvent(Event *ev)
{
    Vector newOrigin;
    Vector newAngles;

    // calculate velocity
    newOrigin = origin + frame_delta;
    velocity  = (newOrigin - origin) / level.frametime;

    // calculate angular velocity
    newAngles = angles + Vector(0, angular_delta, 0);
    avelocity = (newAngles - angles) / level.frametime;

    PostEvent(EV_ScriptModel_MovingAnim, level.frametime);
}

void ScriptModel::SetAngleEvent(Event *ev)
{
    float angle;

    angle = ev->GetFloat(1);
    if (angle == -1) {
        ForwardDir  = Vector(0, 0, 90);
        localangles = Vector(-90, 0, 0);
    } else if (angle == -2) {
        ForwardDir  = Vector(0, 0, -90);
        localangles = Vector(90, 0, 0);
    } else {
        ForwardDir  = Vector(0, angle, 0);
        localangles = Vector(0, angle, 0);
    }

    setAngles(localangles);
}

void ScriptModel::GibEvent(Event *ev)
{
    int   num, power;
    float scale;

    setSolidType(SOLID_NOT);
    hideModel();

    if (!com_blood->integer) {
        PostEvent(EV_Remove, 0);
        return;
    }

    num   = ev->GetInteger(1);
    power = ev->GetInteger(2);
    scale = ev->GetFloat(3);

    power = -power;

    if (ev->NumArgs() > 3) {
        CreateGibs(this, power, scale, num, ev->GetString(4));
    } else {
        CreateGibs(this, power, scale, num);
    }

    PostEvent(EV_Remove, 0);
}

/*****************************************************************************/
/*QUAKED script_model_realdamage (0 0.5 1) (0 0 0) (0 0 0) NOT_SOLID ALWAYS_DRAW

******************************************************************************/
/*****************************************************************************/

CLASS_DECLARATION(ScriptModel, ScriptModelRealDamage, "script_model_realdamage") {
    {&EV_Damage, &ScriptModelRealDamage::EventDamage},
    {NULL,       NULL                               }
};

ScriptModelRealDamage::ScriptModelRealDamage()
{
    RemoveWaitTill(STRING_DAMAGE);
}

void ScriptModelRealDamage::EventDamage(Event *ev)
{
    Entity::DamageEvent(ev);
}

/*****************************************************************************/
/*QUAKED script_origin (1.0 0 0) (-8 -8 -8) (8 8 8)

Used as an alternate origin for objects.  Bind the object to the script_origin
in order to simulate changing that object's origin.
******************************************************************************/

// Added in 2.30
Event EV_ScriptOrigin_SetAngle
(
    "angle",
    EV_DEFAULT,
    "f",
    "newAngle",
    "set the angles of the entity using just one value.\n"
    "Sets the yaw of the entity or an up and down\n"
    "direction if newAngle is [0-359] or -1 or -2"
);

// Added in 2.30
Event EV_ScriptOrigin_GetAngle
(
    "angle",
    EV_DEFAULT,
    NULL,
    NULL,
    "get the angles of the entity using just one value.\n"
    "Gets the yaw of the entity or an up and down\n"
    "direction if newAngle is [0-359] or -1 or -2",
    EV_GETTER
);

CLASS_DECLARATION(ScriptSlave, ScriptOrigin, "script_origin") {
    {&EV_Model, &ScriptOrigin::SetModelEvent},

    //
    // Added in 2.30
    //
    {&EV_ScriptOrigin_SetAngle, &ScriptOrigin::SetAngleEvent},
    {&EV_ScriptOrigin_GetAngle, &ScriptOrigin::GetAngleEvent},
    {NULL,                      NULL                        }
};

ScriptOrigin::ScriptOrigin()
{
    setContents(0);
    setSolidType(SOLID_NOT);
}

void ScriptOrigin::SetAngleEvent(Event* ev)
{
    float angle;

    if (g_target_game < target_game_e::TG_MOHTT) {
        // Restore the old behavior on previous versions (below 2.30).
        // For example, higgins rangers in m3l1a would not face forward
        return ScriptSlave::SetAngleEvent(ev);
    }

    angle = ev->GetFloat(1);
    if (angle == -1) {
        ForwardDir = Vector(0, 0, 90);
        localangles = Vector(-90, 0, 0);
    } else if (angle == -2) {
        ForwardDir = Vector(0, 0, -90);
        localangles = Vector(90, 0, 0);
    } else {
        ForwardDir = Vector(0, angle, 0);
        localangles = Vector(0, angle, 0);
    }

    setAngles(localangles);
}

void ScriptOrigin::GetAngleEvent(Event* ev)
{
    Vector forward;

    angles.AngleVectorsLeft(&forward);
    ev->AddFloat(G_GetAngle(forward));
}

/*****************************************************************************/
/*QUAKED script_skyorigin (1.0 0 0) ?

Used to specify the origin of a portal sky
******************************************************************************/

CLASS_DECLARATION(ScriptSlave, ScriptSkyOrigin, "script_skyorigin") {
    {NULL, NULL}
};

ScriptSkyOrigin::ScriptSkyOrigin()
{
    edict->s.renderfx |= RF_SKYORIGIN;
    edict->r.svFlags &= ~SVF_NOCLIENT;
    setContents(0);
    setSolidType(SOLID_NOT);
}

Event EV_ScriptSimpleStrafingGunfire_On
(
    "on",
    EV_DEFAULT,
    NULL,
    NULL,
    "Turn the gunfire on.",
    EV_NORMAL
);
Event EV_ScriptSimpleStrafingGunfire_Off
(
    "off",
    EV_DEFAULT,
    NULL,
    NULL,
    "Turn the gunfire off.",
    EV_NORMAL
);
Event EV_ScriptSimpleStrafingGunfire_Fire
(
    "fire",
    EV_DEFAULT,
    NULL,
    NULL,
    "Fire.",
    EV_NORMAL
);
Event EV_ScriptSimpleStrafingGunfire_TracerFreq
(
    "tracerfreq",
    EV_DEFAULT,
    "f",
    NULL,
    "Set the frequency of the tracers",
    EV_NORMAL
);
Event EV_ScriptSimpleStrafingGunfire_ProjectileModel
(
    "projectile",
    EV_DEFAULT,
    "s",
    NULL,
    "Set the projectile model",
    EV_NORMAL
);

CLASS_DECLARATION(ScriptSlave, ScriptSimpleStrafingGunfire, "script_simplestrafinggunfire") {
    {&EV_ScriptSimpleStrafingGunfire_On,              &ScriptSimpleStrafingGunfire::GunOn             },
    {&EV_ScriptSimpleStrafingGunfire_Off,             &ScriptSimpleStrafingGunfire::GunOff            },
    {&EV_ScriptSimpleStrafingGunfire_Fire,            &ScriptSimpleStrafingGunfire::GunFire           },
    {&EV_Weapon_FireDelay,                            &ScriptSimpleStrafingGunfire::SetFireDelay      },
    {&EV_Weapon_SetBulletRange,                       &ScriptSimpleStrafingGunfire::SetRange          },
    {&EV_Weapon_SetBulletSpread,                      &ScriptSimpleStrafingGunfire::SetSpread         },
    {&EV_Weapon_SetBulletDamage,                      &ScriptSimpleStrafingGunfire::SetDamage         },
    {&EV_Weapon_SetBulletKnockback,                   &ScriptSimpleStrafingGunfire::SetKnockback      },
    {&EV_Weapon_SetBulletThroughWood,                 &ScriptSimpleStrafingGunfire::SetThroughWood    },
    {&EV_Weapon_SetBulletThroughMetal,                &ScriptSimpleStrafingGunfire::SetThroughMetal   },
    {&EV_Weapon_SetBulletCount,                       &ScriptSimpleStrafingGunfire::SetBulletCount    },
    {&EV_ScriptSimpleStrafingGunfire_TracerFreq,      &ScriptSimpleStrafingGunfire::SetTracerFreq     },
    {&EV_ScriptSimpleStrafingGunfire_ProjectileModel, &ScriptSimpleStrafingGunfire::SetProjectileModel},
    {NULL,                                            NULL                                            }
};

ScriptSimpleStrafingGunfire::ScriptSimpleStrafingGunfire()
{
    isOn            = false;
    fireDelay       = 0.05f;
    range           = 4000;
    spread          = Vector(100, 100, 0);
    damage          = 100;
    knockback       = 0;
    throughWood     = 0;
    throughMetal    = 0;
    bulletCount     = 1;
    tracerCount     = 0;
    tracerFrequency = 0;
    projectileModel = "models/projectiles/stukaround.tik";
}

void ScriptSimpleStrafingGunfire::GunOn(Event *ev)
{
    isOn = true;

    CancelEventsOfType(&EV_ScriptSimpleStrafingGunfire_Fire);
    PostEvent(EV_ScriptSimpleStrafingGunfire_Fire, 0.05f);
}

void ScriptSimpleStrafingGunfire::GunOff(Event *ev)
{
    isOn = false;

    CancelEventsOfType(&EV_ScriptSimpleStrafingGunfire_Fire);
}

void ScriptSimpleStrafingGunfire::GunFire(Event *ev)
{
    Vector dir, right, up;
    Vector horzAngles;

    AngleVectors(angles, NULL, NULL, up);
    dir = -1 * up;

    VectorToAngles(dir, horzAngles);
    AngleVectors(horzAngles, NULL, right, up);

    dir = dir * range + right * grandom() * spread.x;
    dir += up * grandom() * spread.y;
    dir.normalize();

    ProjectileAttack(origin, dir, this, projectileModel, 1, 0, NULL);
    // continue firing
    PostEvent(EV_ScriptSimpleStrafingGunfire_Fire, fireDelay);
}

void ScriptSimpleStrafingGunfire::Archive(Archiver& arc)
{
    ScriptSlave::Archive(arc);

    arc.ArchiveBoolean(&isOn);
    arc.ArchiveFloat(&fireDelay);
    arc.ArchiveFloat(&range);
    arc.ArchiveVector(&spread);
    arc.ArchiveFloat(&damage);
    arc.ArchiveFloat(&knockback);
    arc.ArchiveFloat(&throughWood);
    arc.ArchiveFloat(&throughMetal);
    arc.ArchiveInteger(&bulletCount);
    arc.ArchiveInteger(&tracerCount);
    arc.ArchiveInteger(&tracerFrequency);
    arc.ArchiveString(&projectileModel);
}

Event EV_ScriptAimedStrafingGunfire_AimTarget
(
    "aimtarget",
    EV_DEFAULT,
    "e",
    NULL,
    "Set the aim target.",
    EV_NORMAL
);

CLASS_DECLARATION(ScriptSimpleStrafingGunfire, ScriptAimedStrafingGunfire, "script_aimedstrafinggunfire") {
    {&EV_ScriptAimedStrafingGunfire_AimTarget, &ScriptAimedStrafingGunfire::SetAimTarget},
    {&EV_ScriptSimpleStrafingGunfire_Fire,     &ScriptAimedStrafingGunfire::GunFire     },
    {NULL,                                 NULL                                         }
};

ScriptAimedStrafingGunfire::ScriptAimedStrafingGunfire()
{
    aimTarget = NULL;
}

void ScriptAimedStrafingGunfire::GunFire(Event *ev)
{
    if (!aimTarget) {
        ScriptSimpleStrafingGunfire::GunFire(ev);
        return;
    }

    Vector dir, right, up;
    Vector horzAngles;

    dir = aimTarget->origin - origin;
    dir.normalize();

    VectorToAngles(dir, horzAngles);
    AngleVectors(horzAngles, NULL, right, up);

    dir = dir * range + right * grandom() * spread.x;
    dir += up * grandom() * spread.y;
    dir.normalize();

    ProjectileAttack(origin, dir, this, projectileModel, 1, 0, NULL);
    // continue firing
    PostEvent(EV_ScriptSimpleStrafingGunfire_Fire, fireDelay);
}

void ScriptAimedStrafingGunfire::Archive(Archiver& arc)
{
    ScriptSimpleStrafingGunfire::Archive(arc);

    arc.ArchiveObjectPointer((Class **)&aimTarget);
}
