/*
===========================================================================
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
===========================================================================
*/

#include "scriptthread.h"
#include "scriptmaster.h"
#include "scriptclass.h"
#include "scriptvariable.h"
#include "scriptexception.h"

#include "g_spawn.h"
#include "level.h"
#include "game.h"
#include "camera.h"
#include "dm_manager.h"
#include "hud.h"
#include "huddraw.h"
#include "object.h"
#include "weaputils.h"
#include "consoleevent.h"
#include "player.h"
#include "lightstyleclass.h"
#include "debuglines.h"

#include "scriptflags.h"

#include "slre.h"

#include "md5.h"

#ifdef WIN32
#    include <direct.h>
#else
#    include <sys/stat.h>
#    include <unistd.h>
#endif

#define scriptfiles sv_scriptfiles

Event EV_ScriptThread_GetCvar
(
    "getcvar",
    EV_DEFAULT,
    "s",
    "name",
    "getcvar",
    EV_RETURN
);
Event EV_ScriptThread_GetRandomFloat
(
    "randomfloat",
    EV_DEFAULT,
    "f",
    "max",
    "randomfloat",
    EV_RETURN
);
Event EV_ScriptThread_GetRandomInt
(
    "randomint",
    EV_DEFAULT,
    "i",
    "max",
    "randomint",
    EV_RETURN
);
Event EV_ScriptThread_GetAbs
(
    "abs",
    EV_DEFAULT,
    "f",
    "arg",
    "Absolute value of int or float",
    EV_RETURN
);
//////////
// Added in 2.30
Event EV_ScriptThread_GetCos
(
    "cos",
    EV_DEFAULT,
    "f",
    "arg",
    "Cosine of float (expects degrees not radians)",
    EV_RETURN
);
Event EV_ScriptThread_GetSin
(
    "sin",
    EV_DEFAULT,
    "f",
    "arg",
    "Sine of float (expects degrees not radians)",
    EV_RETURN
);
Event EV_ScriptThread_GetTan
(
    "tan",
    EV_DEFAULT,
    "f",
    "arg",
    "Tangent of float (expects degrees not radians)",
    EV_RETURN
);
Event EV_ScriptThread_GetATan
(
    "atan",
    EV_DEFAULT,
    "fF",
    "arg arg2",
    "ArcTangent of float (in degrees not radians)",
    EV_RETURN
);
Event EV_ScriptThread_GetSqrt
(
    "sqrt",
    EV_DEFAULT,
    "f",
    "arg",
    "Sqrt of float",
    EV_RETURN
);
//////////
Event EV_ScriptThread_GetSelf
(
    "self",
    EV_DEFAULT,
    NULL,
    NULL,
    "self",
    EV_GETTER
);
Event EV_ScriptThread_Goto
(
    "goto",
    EV_DEFAULT,
    "s",
    "label",
    "Goes to the specified label.",
    EV_NORMAL
);
Event EV_ScriptThread_Wait
(
    "wait",
    EV_DEFAULT,
    "f",
    "wait_time",
    "Wait for the specified amount of time.",
    EV_NORMAL
);
Event EV_ScriptThread_WaitFrame
(
    "waitframe",
    EV_DEFAULT,
    NULL,
    NULL,
    "Wait for one server frame.",
    EV_NORMAL
);
Event EV_ScriptThread_Pause
(
    "pause",
    EV_DEFAULT,
    NULL,
    NULL,
    "Pauses the thread.",
    EV_NORMAL
);
Event EV_ScriptThread_End
(
    "end",
    EV_DEFAULT,
    NULL,
    NULL,
    "Ends the script",
    EV_NORMAL
);
Event EV_ScriptThread_Print
(
    "print",
    EV_DEFAULT,
    "s",
    "string",
    "Prints a string.",
    EV_NORMAL
);
Event EV_ScriptThread_Println
(
    "println",
    EV_DEFAULT,
    "s",
    "string",
    "Prints a string. followed by a newline.",
    EV_NORMAL
);
Event EV_ScriptThread_DPrintln // Added in 2.0
(
    "dprintln",
    EV_DEFAULT,
    "s",
    "string",
    "Prints a debug string.followed by a newline.",
    EV_NORMAL
);
Event EV_ScriptThread_IPrintln
(
    "iprintln",
    EV_DEFAULT,
    "s",
    "string",
    "Prints a string. followed by a newline.",
    EV_NORMAL
);
Event EV_ScriptThread_IPrintlnBold
(
    "iprintlnbold",
    EV_DEFAULT,
    "s",
    "string",
    "Prints a string.followed by a newline in a bold/important way.",
    EV_NORMAL
);
Event EV_ScriptThread_IPrintln_NoLoc
(
    "iprintln_noloc",
    EV_DEFAULT,
    "s",
    "string",
    "Prints a string.followed by a newline with no localization conversion.",
    EV_NORMAL
);
Event EV_ScriptThread_IPrintlnBold_NoLoc
(
    "iprintlnbold_noloc",
    EV_DEFAULT,
    "s",
    "string",
    "Prints a string.followed by a newline in a bold/important way with no localization conversion.",
    EV_NORMAL
);
Event EV_ScriptThread_MPrint
(
    "mprint",
    EV_DEFAULT,
    "s",
    "string",
    "Prints a string.",
    EV_NORMAL
);
Event EV_ScriptThread_MPrintln
(
    "mprintln",
    EV_DEFAULT,
    "s",
    "string",
    "Prints a string. followed by a newline",
    EV_NORMAL
);
Event EV_ScriptThread_Assert
(
    "assert",
    EV_DEFAULT,
    "f",
    "value",
    "Assert if value is 0.",
    EV_NORMAL
);
Event EV_ScriptThread_CastInt
(
    "int",
    EV_DEFAULT,
    "i",
    "value",
    "Casts value to an int.",
    EV_RETURN
);
Event EV_ScriptThread_CastFloat
(
    "float",
    EV_DEFAULT,
    "i",
    "value",
    "Casts value to a float.",
    EV_RETURN
);
Event EV_ScriptThread_CastString
(
    "string",
    EV_DEFAULT,
    "i",
    "value",
    "Casts value to a string.",
    EV_RETURN
);
Event EV_ScriptThread_CastBoolean
(
    "bool",
    EV_DEFAULT,
    "i",
    "value",
    "Casts value to a bool.",
    EV_RETURN
);
Event EV_ScriptThread_CastEntity
(
    "entity",
    EV_DEFAULT,
    "i",
    "value",
    "Casts value to an entity.",
    EV_RETURN
);
Event EV_ScriptThread_Clear
(
    "clear",
    EV_DEFAULT,
    "s",
    "var_group",
    "Clears the specified var group."
);
Event EV_ScriptThread_Trigger
(
    "trigger",
    EV_DEFAULT,
    "s",
    "name",
    "Trigger the specified target or entity."
);
Event EV_ScriptThread_Spawn
(
    "spawn",
    EV_DEFAULT,
    "sSSSSSSSS",
    "entityname keyname1 value1 keyname2 value2 keyname3 value3 keyname4 value4",
    "Spawns an entity.",
    EV_NORMAL
);
Event EV_ScriptThread_SpawnReturn
(
    "spawn",
    EV_DEFAULT,
    "sSSSSSSSS",
    "entityname keyname1 value1 keyname2 value2 keyname3 value3 keyname4 value4",
    "Spawns an entity.",
    EV_RETURN
);
Event EV_ScriptThread_Map
(
    "map",
    EV_DEFAULT,
    "s",
    "map_name",
    "Starts the specified map."
);
Event EV_ScriptThread_SetCvar
(
    "setcvar",
    EV_DEFAULT,
    "ss",
    "cvar_name value",
    "Sets the value of the specified cvar."
);
Event EV_ScriptThread_DebugLine // Added in 2.0
(
    "debugline",
     EV_DEFAULT,
     "vvFFFF",
     "start end red green blue alpha",
     "Draws a debug line from start to end using the given color and alpha for g_numdebuglinedelays server frames"
);
Event EV_ScriptThread_CueCamera
(
    "cuecamera",
    EV_DEFAULT,
    "eF",
    "entity switchTime",
    "Cue the camera. If switchTime is specified, then the camera\n"
    "will switch over that length of time."
);
Event EV_ScriptThread_CuePlayer
(
    "cueplayer",
    EV_DEFAULT,
    "F",
    "switchTime",
    "Go back to the normal camera. If switchTime is specified,\n"
    "then the camera will switch over that length of time."
);
Event EV_ScriptThread_FreezePlayer
(
    "freezeplayer",
    EV_DEFAULT,
    NULL,
    NULL,
    "Freeze the player."
);
Event EV_ScriptThread_ReleasePlayer
(
    "releaseplayer",
    EV_DEFAULT,
    NULL,
    NULL,
    "Release the player."
);
Event EV_ScriptThread_KillEnt
(
    "killent",
    EV_CHEAT,
    "i",
    "ent_num",
    "Kill the specified entity."
);
Event EV_ScriptThread_IsAlive
(
    "IsAlive",
    EV_DEFAULT,
    "e",
    "ent",
    "Returns true if the specified entity exists and has health > 0.",
    EV_RETURN
);
Event EV_ScriptThread_KillClass
(
    "killclass",
    EV_CHEAT,
    "sI",
    "class_name except",
    "Kills everything in the specified class except for the specified entity (optional)."
);
Event EV_ScriptThread_GetEntByEntnum // Added in 2.30
(
    "getentbyentnum",
     EV_CHEAT,
     "i",
     "ent_num",
     "Gets the specified entity",
     EV_RETURN
);
Event EV_ScriptThread_RemoveEnt
(
    "removeent",
    EV_CHEAT,
    "i",
    "ent_num",
    "Removes the specified entity."
);
Event EV_ScriptThread_RemoveClass
(
    "removeclass",
    EV_CHEAT,
    "sI",
    "class_name except",
    "Removes everything in the specified class except for the specified entity (optional)."
);

// client/server flow control

Event EV_ScriptThread_ServerOnly
(
    "server",
    EV_DEFAULT,
    "SSSSSS",
    "arg1 arg2 arg3 arg4 arg5 arg6",
    "Server only command."
);
Event EV_ScriptThread_StuffCommand
(
    "stuffcmd",
    EV_DEFAULT,
    "SSSSSS",
    "arg1 arg2 arg3 arg4 arg5 arg6",
    "Server only command."
);
Event EV_ScriptThread_SetCinematic
(
    "cinematic",
    EV_DEFAULT,
    NULL,
    NULL,
    "Turns on cinematic."
);
Event EV_ScriptThread_SetNonCinematic
(
    "noncinematic",
    EV_DEFAULT,
    NULL,
    NULL,
    "Turns off cinematic."
);
Event EV_ScriptThread_AllAIOff
(
    "all_ai_off",
    EV_DEFAULT,
    NULL,
    NULL,
    "Turns all AI off."
);
Event EV_ScriptThread_AllAIOn
(
    "all_ai_on",
    EV_DEFAULT,
    NULL,
    NULL,
    "Turns all AI on."
);
// Precache specific
Event EV_ScriptThread_AliasCache
(
    "aliascache",
    EV_DEFAULT,
    "ssSSSSSS",
    "alias realPath arg1 arg2 arg3 arg4 arg5 arg6",
    "Create an alias to the specified path and cache the resource"
);
Event EV_ScriptThread_Precache_Cache
(
    "cache",
    EV_DEFAULT,
    "s",
    "resource_name",
    "Cache the specified resource."
);
// fades for movies
Event EV_ScriptThread_FadeIn
(
    "fadein",
    EV_DEFAULT,
    "fffffI",
    "time red green blue alpha mode",
    "Sets up fadein in values."
);
Event EV_ScriptThread_FadeOut
(
    "fadeout",
    EV_DEFAULT,
    "fffffI",
    "time red green blue alpha mode",
    "Sets up fadeout values."
);
Event EV_ScriptThread_FadeSound
(
    "fadesound",
    -1,
    "fFE",
    "time min_vol player",
    "Fades the sound out over the given time, optionally to one single player.\n"
    "min_vol being a 0-1 fraction is the minimum volume.",
    EV_NORMAL
);
Event EV_ScriptThread_CameraCommand
(
    "cam",
    EV_DEFAULT,
    "sSSSSSS",
    "command arg1 arg2 arg3 arg4 arg5 arg6",
    "Processes a camera command."
);

// music command
Event EV_ScriptThread_MusicEvent
(
    "music",
    EV_DEFAULT,
    "sS",
    "current fallback",
    "Sets the current and fallback (optional) music moods."
);
Event EV_ScriptThread_ForceMusicEvent
(
    "forcemusic",
    EV_DEFAULT,
    "sS",
    "current fallback",
    "Forces the current and fallback (optional) music moods."
);
Event EV_ScriptThread_MusicVolumeEvent
(
    "musicvolume",
    EV_DEFAULT,
    "ff",
    "volume fade_time",
    "Sets the volume and fade time of the music."
);
Event EV_ScriptThread_RestoreMusicVolumeEvent
(
    "restoremusicvolume",
    EV_DEFAULT,
    "f",
    "fade_time",
    "Restores the music volume to its previous value."
);
Event EV_ScriptThread_SoundtrackEvent
(
    "soundtrack",
    EV_DEFAULT,
    "s",
    "soundtrack_name",
    "Changes the soundtrack."
);
Event EV_ScriptThread_RestoreSoundtrackEvent
(
    "restoresoundtrack",
    EV_DEFAULT,
    NULL,
    NULL,
    "Restores the soundtrack to the previous one."
);
Event EV_ScriptThread_ClearFade
(
    "clearfade",
    EV_DEFAULT,
    NULL,
    NULL,
    "Clear the fade from the screen"
);
Event EV_ScriptThread_Letterbox
(
    "letterbox",
    EV_DEFAULT,
    "f",
    "time",
    "Puts the game in letterbox mode."
);
Event EV_ScriptThread_ClearLetterbox
(
    "clearletterbox",
    EV_DEFAULT,
    "f",
    "time",
    "Clears letterbox mode."
);
Event EV_ScriptThread_SetDialogScript
(
    "setdialogscript",
    EV_DEFAULT,
    "s",
    "dialog_script",
    "Set the script to be used when dialog:: is used"
);
Event EV_ScriptThread_SetLightStyle
(
    "setlightstyle",
    EV_DEFAULT,
    "is",
    "lightstyleindex lightstyledata",
    "Set up the lightstyle with lightstyleindex to the specified data"
);
Event EV_ScriptThread_CenterPrint
(
    "centerprint",
    EV_DEFAULT,
    "s",
    "stuffToPrint",
    "prints the included message in the middle of all player's screens"
);
Event EV_ScriptThread_LocationPrint
(
    "locprint",
    EV_DEFAULT,
    "iis",
    "xoffset yoffset stuffToPrint",
    "prints the included message in the specified location of all player's screens"
);
Event EV_ScriptThread_MissionFailed
(
    "missionfailed",
    EV_DEFAULT,
    NULL,
    NULL,
    "Makes the player fail their mission,"
    "level restarts."
);
Event EV_ScriptThread_Vector_Length
(
    "vector_length",
    EV_DEFAULT,
    "v",
    "vector",
    "Returns the length of the specified vector.",
    EV_RETURN
);
Event EV_ScriptThread_Vector_Normalize
(
    "vector_normalize",
    EV_DEFAULT,
    "v",
    "vector",
    "Returns the normalized vector of the specified vector.",
    EV_RETURN
);
Event EV_ScriptThread_Vector_Add
(
    "vector_add",
    EV_DEFAULT,
    "vv",
    "vector1 vector2",
    "Returns vector1 + vector2.",
    EV_RETURN
);
Event EV_ScriptThread_Vector_Subtract
(
    "vector_subtract",
    EV_DEFAULT,
    "vv",
    "vector1 vector2",
    "Returns vector1 - vector2.",
    EV_RETURN
);
Event EV_ScriptThread_Vector_Scale
(
    "vector_scale",
    EV_DEFAULT,
    "vf",
    "vector1 scale_factor",
    "Returns vector1 * scale_factor.",
    EV_RETURN
);
Event EV_ScriptThread_Vector_DotProduct
(
    "vector_dot",
    EV_DEFAULT,
    "vv",
    "vector1 vector2",
    "Returns vector1 * vector2.",
    EV_RETURN
);
Event EV_ScriptThread_Vector_CrossProduct
(
    "vector_cross",
    EV_DEFAULT,
    "vv",
    "vector1 vector2",
    "Returns vector1 x vector2.",
    EV_RETURN
);
Event EV_ScriptThread_Vector_ToAngles
(
    "vector_toangles",
    EV_DEFAULT,
    "v",
    "vector1",
    "Returns vector1 converted to angles.",
    EV_RETURN
);
Event EV_ScriptThread_Angles_ToForward
(
    "angles_toforward",
    EV_DEFAULT,
    "v",
    "angles",
    "Returns the forward vector of the specified angles",
    EV_RETURN
);
Event EV_ScriptThread_Angles_ToLeft
(
    "angles_toleft",
    EV_DEFAULT,
    "v",
    "angles",
    "Returns the left vector of the specified angles",
    EV_RETURN
);
Event EV_ScriptThread_Angles_ToUp
(
    "angles_toup",
    EV_DEFAULT,
    "v",
    "angles",
    "Returns the up vector of the specified angles",
    EV_RETURN
);
Event EV_ScriptThread_Angles_PointAt
(
    "angles_pointat",
    EV_DEFAULT,
    "eee",
    "parent_entity entity target_entity",
    "Returns the angles that points at the target_entity given the base orientation of the parent_entity and the "
    "position of the entity.",
    EV_RETURN
);
Event EV_ScriptThread_AIsCloserThanBToC
(
    "vector_closer",
    EV_DEFAULT,
    "vvv",
    "vec_a vec_b vec_c",
    "returns 1 if the first vector is closer than the second vector to the third vector.",
    EV_RETURN
);
Event EV_ScriptThread_PointsWithinDist
(
    "vector_within",
    EV_DEFAULT,
    "vvf",
    "position1 position2 distance",
    "returns 1 if the two points are <= distance apart, or 0 if they are greater than distance apart.",
    EV_RETURN
);
Event EV_ScriptThread_Popmenu
(
    "popmenu",
    EV_DEFAULT,
    "i",
    "index",
    "pop menu"
);
Event EV_ScriptThread_Showmenu
(
    "showmenu",
    EV_DEFAULT,
    "sI",
    "name bForce",
    "show menu,"
    "with option to force it on"
);
Event EV_ScriptThread_Hidemenu
(
    "hidemenu",
    EV_DEFAULT,
    "sI",
    "name bForce",
    "hide menu,"
    "with option to force it off"
);
Event EV_ScriptThread_PlayMovie // Added in 2.0
(
    "PlayMovie",
    EV_DEFAULT,
    "s",
    "name",
    "Play a Movie..."
);
Event EV_ScriptThread_Pushmenu
(
    "pushmenu",
    EV_DEFAULT,
    "s",
    "name",
    "push menu"
);
Event EV_ScriptThread_HideMouse
(
    "hidemouse",
    EV_DEFAULT,
    NULL,
    NULL,
    "hide mouse cursor"
);
Event EV_ScriptThread_CreateListener
(
    "CreateListener",
    EV_DEFAULT,
    NULL,
    NULL,
    "Creates a Listener instance.",
    EV_RETURN
);
Event EV_ScriptThread_Trace
(
    "trace",
    EV_DEFAULT,
    "vvIVV",
    "start end pass_entities mins maxs",
    "Performs a Trace Line from the start to the end, returns the end or the position it hit at.",
    EV_RETURN
);
Event EV_ScriptThread_SightTrace
(
    "sighttrace",
    EV_DEFAULT,
    "vvIVV",
    "start end pass_entities mins maxs",
    "Performs a trace line from the start to the end, returns 0 if something was hit and 1 otherwise.",
    EV_RETURN
);
Event EV_ScriptThread_Print3D
(
    "print3d",
    EV_DEFAULT,
    "vfs",
    "origin scale string",
    "prints a string in 3D space",
    EV_NORMAL
);
Event EV_ScriptThread_HudDraw_3d
(
    "huddraw_3d",
    EV_DEFAULT,
    "iviiE",
    "index vector_or_offset always_show depth entity",
    "Sets this huddraw element to be a 3D world icon and can specify if this icon is always shown on-screen even if "
    "the player isn't looking at.\n"
    "Uses xy pos from huddraw_rect.\n"
    "If entity is specified, the vector will be an offset relative to the entity.\n"
    "depth specify if the icon is shown through walls.",
    EV_NORMAL
);
Event EV_ScriptThread_HudDraw_Timer
(
    "huddraw_timer",
    EV_DEFAULT,
    "iff",
    "index duration fade_out_time",
    "Sets a timer to be displayed and fade within the given time in seconds. Clears the string value and the shader "
    "value of huddraw element.",
    EV_NORMAL
);
Event EV_ScriptThread_HudDraw_Shader
(
    "huddraw_shader",
    EV_DEFAULT,
    "is",
    "index shader",
    "Sets the shader to use for a particular huddraw element for given player",
    EV_NORMAL
);
Event EV_ScriptThread_HudDraw_Align
(
    "huddraw_align",
    EV_DEFAULT,
    "iss",
    "index h_align v_align",
    "Sets the alignment of a huddraw element for given player. Specified with 'left', 'center', or 'right'",
    EV_NORMAL
);
Event EV_ScriptThread_HudDraw_Rect
(
    "huddraw_rect",
    EV_DEFAULT,
    "iiiii",
    "index x y width height",
    "Specifies the position of the upper left corner and size of a huddraw element for given player",
    EV_NORMAL
);
Event EV_ScriptThread_HudDraw_VirtualSize
(
    "huddraw_virtualsize",
    EV_DEFAULT,
    "ii",
    "index virtual",
    "Sets if the huddraw element for given player should use virtual screen resolution for positioning and size",
    EV_NORMAL
);
Event EV_ScriptThread_HudDraw_Color
(
    "huddraw_color",
    EV_DEFAULT,
    "ifff",
    "index red green blue",
    "Sets the color for a huddraw element for given player",
    EV_NORMAL
);
Event EV_ScriptThread_HudDraw_Alpha
(
    "huddraw_alpha",
    EV_DEFAULT,
    "if",
    "index alpha",
    "Sets the alpha of a huddraw element for given player",
    EV_NORMAL
);
Event EV_ScriptThread_HudDraw_String
(
    "huddraw_string",
    EV_DEFAULT,
    "is",
    "index string",
    "Sets a string to be displayed for given player. Clears the shader value",
    EV_NORMAL
);
Event EV_ScriptThread_HudDraw_Font
(
    "huddraw_font",
    EV_DEFAULT,
    "is",
    "index fontname",
    "Sets the font to use for given player huddraw element",
    EV_NORMAL
);
Event EV_ScriptThread_Error
(
    "error",
    EV_DEFAULT,
    NULL,
    NULL,
    "Ends the script",
    EV_NORMAL
);
Event EV_ScriptThread_DebugInt3 // Added in 2.0
(
    "debug_int3",
    EV_DEFAULT,
    NULL,
    NULL,
    "Generate an asm int 3",
    EV_NORMAL
);
Event EV_ScriptThread_Timeout
(
    "timeout",
    EV_DEFAULT,
    "f",
    "time",
    "specifies script timeout time",
    EV_NORMAL
);
Event EV_ScriptThread_RadiusDamage
(
    "radiusdamage",
    EV_DEFAULT,
    "viiI",
    "origin damage radius constant_damage",
    "radius damage at origin"
);
Event EV_ScriptThread_LandmineDamage // Added in 2.30
(
    "landminedamage",
    EV_DEFAULT,
    "eff",
    "landmine damage radius",
    "radius damage at landmine"
);
Event EV_ScriptThread_BspTransition
(
    "bsptransition",
    EV_DEFAULT,
    "sB",
    "next_map skipFade", // Added in 2.30: skipFade
    "Transitions to the next BSP. Keeps player data, and game data."
);
Event EV_ScriptThread_LevelTransition
(
    "leveltransition",
    EV_DEFAULT,
    "s",
    "next_map",
    "Transitions to the next Level. Statistics to Map Loading,"
    "does not keep player data or game data."
);
Event EV_ScriptThread_MissionTransition
(
    "missiontransition",
    EV_DEFAULT,
    "s",
    "next_map",
    "Transitions to the next Mission. Statistics to Main Menu,"
    "Next Level should be unlocked."
);
Event EV_ScriptThread_Earthquake
(
    "earthquake",
    EV_DEFAULT,
    "ffff",
    "duration magnitude no_rampup no_rampdown",
    "Create an earthquake"
);
Event EV_ScriptThread_TeamWin
(
    "teamwin",
    EV_DEFAULT,
    "s",
    "axis_or_allies",
    "Sets that the 'axis' or the 'allies' have won the map."
);
Event EV_ScriptThread_StopTeamRespawn // Added in 2.30
(
    "stopteamrespawn",
     EV_DEFAULT,
     "s",
     "team",
     "Stops a team (anerican, german, both) from spawning in non-wave deathmatches"
);
Event EV_ScriptThread_GetBoundKey1
(
    "getboundkey1",
    EV_DEFAULT,
    "s",
    "keyname",
    "return a string describing the key",
    EV_RETURN
);
Event EV_ScriptThread_GetBoundKey2
(
    "getboundkey2",
    EV_DEFAULT,
    "s",
    "keyname",
    "return a string describing the key",
    EV_RETURN
);
Event EV_ScriptThread_LocConvertString
(
    "loc_convert_string",
    EV_DEFAULT,
    "s",
    "in",
    "returns a localized version of the string.",
    EV_RETURN
);
Event EV_ScriptThread_SetScoreboardToggle // Added in 2.30
(
    "setscoreboardtoggle",
    EV_DEFAULT,
    "si",
    "team value",
    "Sets the specified team's scoreboard toggle",
    EV_NORMAL
);
Event EV_ScriptThread_AddObjective
(
    "addobjective",
    EV_DEFAULT,
    "iisv",
    "objective_number status text location",
    "Adds/Changes an Objective"
);
Event EV_ScriptThread_SetCurrentObjective
(
    "setcurrentobjective",
    EV_DEFAULT,
    "i",
    "objective_number",
    "Sets the specified objective as the current objective"
);
Event EV_ScriptThread_SetObjectiveLocation
(
    "set_objective_pos",
    EV_DEFAULT,
    "v",
    "pos",
    "Sets the position in the world of the current objective"
);
Event EV_ScriptThread_ClearObjectiveLocation(
    "clear_objective_pos",
    EV_DEFAULT,
    NULL,
    NULL,
    "Clears the position of the current objective,"
    "for when you don't have one"
);
Event EV_ScriptThread_DrawHud
(
    "drawhud",
    EV_DEFAULT,
    "i",
    "value",
    "Specify if hud is to be drawn"
);
Event EV_ScriptThread_RegisterCommand
(
    "registercmd",
    EV_DEFAULT,
    "ss",
    "name label",
    "Registers a command.",
    EV_NORMAL
);

//
// Added in OPM
//
Event EV_ScriptThread_CancelWaiting
(
    "_cancelwaiting",
    EV_CODEONLY,
    NULL,
    NULL,
    "internal event"
);
Event EV_ScriptThread_CanSwitchTeams
(
    "canswitchteams",
    EV_DEFAULT,
    "bB",
    "allow_team_change allow_spectator",
    "Specify if players are allowed to switch teams and spectate. Override player's canswitchteams.",
    EV_NORMAL
);
Event EV_ScriptThread_RemoveArchivedClass
(
    "removearchivedclass",
    EV_DEFAULT,
    "sI",
    "class except_entity_number",
    "Removes all of the simple archived entities in the specified class.",
    EV_NORMAL
);
Event EV_ScriptThread_RestoreSound(
    "restoresound",
    -1,
    "fFE",
    "time max_vol player",
    "Fades the sound in over the given time, optionally to one single player.\n"
    "max_vol being a 0-1 fraction is the maximum volume.",
    EV_NORMAL
);
Event EV_ScriptThread_Earthquake2
(
    "earthquake2",
    EV_DEFAULT,
    "ffbbVF",
    "duration magnitude no_rampup no_rampdown location radius",
    "Create a smooth realistic earthquake. Requires sv_reborn to be set.",
    EV_NORMAL
);
Event EV_ScriptThread_GetPlayerNetname
(
    "netname",
    EV_DEFAULT,
    "e",
    "player",
    "Gets player's netname",
    EV_RETURN
);
Event EV_ScriptThread_GetPlayerIP
(
    "getip",
    EV_DEFAULT,
    "e",
    "player",
    "Gets player's ip",
    EV_RETURN
);
Event EV_ScriptThread_ServerStufftext
(
    "stuffsrv",
    EV_DEFAULT,
    "s",
    "string",
    "Sends command to server",
    EV_NORMAL
);
Event EV_ScriptThread_GetAreaEntities
(
    "getareaentities",
    EV_DEFAULT,
    "vvv",
    "origin mins maxs",
    "Get all entities around the origin",
    EV_RETURN
);
Event EV_ScriptThread_GetPlayerPing
(
    "getping",
    EV_DEFAULT,
    "e",
    "player",
    "Gets player's ping",
    EV_RETURN
);
Event EV_ScriptThread_GetPlayerClientNum
(
    "getclientnum",
    EV_DEFAULT,
    "e",
    "player",
    "Gets player's client number",
    EV_RETURN
);
Event EV_ScriptThread_iHudDraw3d
(
    "ihuddraw_3d",
    EV_DEFAULT,
    "eiviiE",
    "player index vector_or_offset always_show depth entity",
    "Sets this huddraw element to be a 3D world icon for individual player and can specify if this icon is always "
    "shown on-screen even if the player isn't looking at.\n"
    "Uses xy pos from huddraw_rect.\n"
    "If entity is specified, the vector will be an offset relative to the entity.\n"
    "depth specify if the icon is shown through walls.",
    EV_NORMAL
);
Event EV_ScriptThread_iHudDrawAlign
(
    "ihuddraw_align",
    EV_DEFAULT,
    "eiss",
    "player index h_align v_align",
    "Sets the alignment of a huddraw element for given player. Specified with 'left', 'center', or 'right'",
    EV_NORMAL
);
Event EV_ScriptThread_iHudDrawAlpha
(
    "ihuddraw_alpha",
    EV_DEFAULT,
    "eif",
    "player index alpha",
    "Sets the alpha of a huddraw element for given player",
    EV_NORMAL
);
Event EV_ScriptThread_iHudDrawColor
(
    "ihuddraw_color",
    EV_DEFAULT,
    "eifff",
    "player index red green blue",
    "Sets the color for a huddraw element for given player",
    EV_NORMAL
);
Event EV_ScriptThread_iHudDrawFont
(
    "ihuddraw_font",
    EV_DEFAULT,
    "eis",
    "player index fontname",
    "Sets the font to use for given player huddraw element",
    EV_NORMAL
);
Event EV_ScriptThread_iHudDrawRect
(
    "ihuddraw_rect",
    EV_DEFAULT,
    "eiiiii",
    "player index x y width height",
    "Specifies the position of the upper left corner and size of a huddraw element for given player",
    EV_NORMAL
);
Event EV_ScriptThread_iHudDrawShader
(
    "ihuddraw_shader",
    EV_DEFAULT,
    "eis",
    "player index shader",
    "Sets the shader to use for a particular huddraw element for given player",
    EV_NORMAL
);
Event EV_ScriptThread_iHudDrawString
(
    "ihuddraw_string",
    EV_DEFAULT,
    "eis",
    "player index string",
    "Sets a string to be displayed for given player. Clears the shader value",
    EV_NORMAL
);
Event EV_ScriptThread_iHudDrawTimer
(
    "ihuddraw_timer",
    EV_DEFAULT,
    "eiff",
    "player index duration fade_out_time",
    "Sets a timer to be displayed and fade within the given time in seconds for given player. Clears the string value "
    "and the shader value of huddraw element.",
    EV_NORMAL
);
Event EV_ScriptThread_iHudDrawVirtualSize
(
    "ihuddraw_virtualsize",
    EV_DEFAULT,
    "eii",
    "player index virtual",
    "Sets if the huddraw element for given player should use virtual screen resolution for positioning and size",
    EV_NORMAL
);
Event EV_ScriptThread_IsArray
(
    "isarray",
    EV_DEFAULT,
    "s",
    "object",
    "Checks whether this variable is an array",
    EV_RETURN
);
Event EV_ScriptThread_IsDefined
(
    "isdefined",
    EV_DEFAULT,
    "s",
    "object",
    "Checks whether this entity/variable is defined",
    EV_RETURN
);
Event EV_ScriptThread_IsOnGround
(
    "isonground",
    EV_DEFAULT,
    "e",
    "entity",
    "Returns 1 if the entity is on ground. 0 otherwise",
    EV_RETURN
);
Event EV_ScriptThread_IsOutOfBounds
(
    "isoutofbounds",
    EV_DEFAULT,
    "e",
    "entity",
    "Returns 1 if the entity is out of bounds. 0 otherwise",
    EV_RETURN
);
Event EV_ScriptThread_FileOpen(
    "fopen",
    EV_DEFAULT,
    "ss",
    "filename accesstype",
    "Opens file,"
    "returning it's handle",
    EV_RETURN
);
Event EV_ScriptThread_FileWrite
(
    "fwrite",
    EV_DEFAULT,
    "eii",
    "player index virtual",
    "Writes binary buffer to file",
    EV_NORMAL
);
Event EV_ScriptThread_FileRead
(
    "fread",
    EV_DEFAULT,
    "eii",
    "player index virtual",
    "Reads binary buffer from file",
    EV_NORMAL
);
Event EV_ScriptThread_FileClose
(
    "fclose",
    EV_DEFAULT,
    "i",
    "filehandle",
    "Closes file of given file handle",
    EV_NORMAL
);
Event EV_ScriptThread_FileClose2
(
    "fclose",
    EV_DEFAULT,
    "i",
    "filehandle",
    "Closes file of given file handle",
    EV_RETURN
);
Event EV_ScriptThread_FileEof
(
    "feof",
    EV_DEFAULT,
    "i",
    "filehandle",
    "Checks for end of file",
    EV_RETURN
);
Event EV_ScriptThread_FileSeek
(
    "fseek",
    EV_DEFAULT,
    "iii",
    "filehandle offset startpos",
    "Sets file carret at given position",
    EV_RETURN
);
Event EV_ScriptThread_FileTell
(
    "ftell",
    EV_DEFAULT,
    "i",
    "filehandle",
    "Gets current file carret position",
    EV_RETURN
);
Event EV_ScriptThread_FileRewind
(
    "frewind",
    EV_DEFAULT,
    "i",
    "filehandle",
    "Rewinds file carret to files beginning",
    EV_NORMAL
);
Event EV_ScriptThread_FilePutc
(
    "fputc",
    EV_DEFAULT,
    "ii",
    "filehandle character",
    "Writes single character to file",
    EV_RETURN
);
Event EV_ScriptThread_FilePuts
(
    "fputs",
    EV_DEFAULT,
    "is",
    "filehandle text",
    "Writes string line to file",
    EV_RETURN
);
Event EV_ScriptThread_FileGetc
(
    "fgetc",
    EV_DEFAULT,
    "i",
    "filehandle",
    "Reads single character from file",
    EV_RETURN
);
Event EV_ScriptThread_FileGets
(
    "fgets",
    EV_DEFAULT,
    "ii",
    "filehandle maxbuffsize",
    "Reads string line from file",
    EV_RETURN
);
Event EV_ScriptThread_FileError
(
    "ferror",
    EV_DEFAULT,
    "i",
    "filehandle",
    "Checks for last file i/o error",
    EV_RETURN
);
Event EV_ScriptThread_FileFlush
(
    "fflush",
    EV_DEFAULT,
    "i",
    "filehandle",
    "Flushes given stream. Writes all unsaved data from stream buffer to stream",
    EV_RETURN
);
Event EV_ScriptThread_FlagClear
(
    "flag_clear",
    EV_DEFAULT,
    "s",
    "name",
    "Clears and deletes a flag.",
    EV_NORMAL
);
Event EV_ScriptThread_FlagInit
(
    "flag_init",
    EV_DEFAULT,
    "s",
    "name",
    "Initializes a flag so it can be set. Flags MUST be initialized before they can be used for the first time.",
    EV_NORMAL
);
Event EV_ScriptThread_FlagSet
(
    "flag_set",
    EV_DEFAULT,
    "s",
    "name",
    "Sets a flag which alerts all flag_wait()'s and changes are reflected in flag() checks thereafter.",
    EV_NORMAL
);
Event EV_ScriptThread_FlagWait
(
    "flag_wait",
    EV_DEFAULT,
    "s",
    "name",
    "Pauses execution flow until a flag has been set.",
    EV_NORMAL
);
Event EV_ScriptThread_Lock
(
    "lock",
    EV_DEFAULT,
    "l",
    "lock",
    "Lock the thread."
);
Event EV_ScriptThread_UnLock
(
    "unlock",
    EV_DEFAULT,
    "l",
    "lock",
    "Unlock the thread."
);
Event EV_ScriptThread_GetArrayKeys
(
    "getarraykeys",
    EV_DEFAULT,
    "s",
    "array",
    "Retrieves a full list containing the name of arrays",
    EV_RETURN
);
Event EV_ScriptThread_GetArrayValues
(
    "getarrayvalues",
    EV_DEFAULT,
    "s",
    "array",
    "Retrieves the full list of an array that was set-up with name, such as local.array[ \"name\" ] and return their "
    "values",
    EV_RETURN
);
Event EV_ScriptThread_GetEntArray
(
    "getentarray",
    EV_DEFAULT,
    "ss",
    "name key",
    "Gets an array of entities that have the given key, name pair (case sensitive)",
    EV_RETURN
);
Event EV_ScriptThread_GetTime
(
    "gettime",
    EV_DEFAULT,
    NULL,
    NULL,
    "Gets current time",
    EV_RETURN
);
Event EV_ScriptThread_GetTimeZone
(
    "gettimezone",
    EV_DEFAULT,
    NULL,
    NULL,
    "Gets current time zone",
    EV_RETURN
);
Event EV_ScriptThread_PregMatch
(
    "preg_match",
    EV_DEFAULT,
    "ss",
    "pattern subject",
    "Searches subject for a match to the regular expression given in pattern.",
    EV_RETURN
);
Event EV_ScriptThread_GetDate
(
    "getdate",
    EV_DEFAULT,
    NULL,
    NULL,
    "Gets current date",
    EV_RETURN
);
Event EV_ScriptThread_RegisterEv
(
    "registerev",
    EV_DEFAULT,
    "ss",
    "eventname script",
    "Registers script callback handler for specified event",
    EV_RETURN
);
Event EV_ScriptThread_UnregisterEv
(
    "unregisterev",
    EV_DEFAULT,
    "s",
    "eventname",
    "Unregisters script callback handler for specified event",
    EV_RETURN
);
Event EV_ScriptThread_Conprintf
(
    "conprintf",
    EV_DEFAULT,
    "s",
    "text",
    "Prints to console",
    EV_NORMAL
);
Event EV_ScriptThread_TeamSwitchDelay
(
    "teamswitchdelay",
    EV_DEFAULT,
    "f",
    "delay",
    "Sets delay between switching teams. [DEPRECATED]",
    EV_NORMAL
);
Event EV_ScriptThread_CharToInt
(
    "chartoint",
    EV_DEFAULT,
    "s",
    "character",
    "Converts char to int",
    EV_RETURN
);
Event EV_ScriptThread_FileExists
(
    "fexists",
    EV_DEFAULT,
    "s",
    "filename",
    "Checks if file exists",
    EV_RETURN
);
Event EV_ScriptThread_FileReadAll
(
    "freadall",
    EV_DEFAULT,
    "i",
    "filehandle",
    "Reads whole file and returns it as string",
    EV_RETURN
);
Event EV_ScriptThread_FileSaveAll
(
    "fsaveall",
    EV_DEFAULT,
    "is",
    "filehandle text",
    "Saves whole text to file",
    EV_RETURN
);
Event EV_ScriptThread_FileRemove
(
    "fremove",
    EV_DEFAULT,
    "s",
    "filename",
    "Removes the file",
    EV_RETURN
);
Event EV_ScriptThread_FileRename
(
    "frename",
    EV_DEFAULT,
    "ss",
    "oldfilename newfilename",
    "Renames the file",
    EV_RETURN
);
Event EV_ScriptThread_FileCopy
(
    "fcopy",
    EV_DEFAULT,
    "ss",
    "filename copyfilename",
    "Copies the file",
    EV_RETURN
);
Event EV_ScriptThread_FileReadPak
(
    "freadpak",
    EV_DEFAULT,
    "s",
    "filename",
    "Reads file from pak file",
    EV_RETURN
);
Event EV_ScriptThread_FileList
(
    "flist",
    EV_DEFAULT,
    "ssi",
    "path extension wantSubs",
    "Returns the list of files with given extension",
    EV_RETURN
);
Event EV_ScriptThread_FileNewDirectory
(
    "fnewdir",
    EV_DEFAULT,
    "s",
    "path",
    "Creates a new directory",
    EV_RETURN
);
Event EV_ScriptThread_FileRemoveDirectory
(
    "fremovedir",
    EV_DEFAULT,
    "s",
    "path",
    "Removes a directory",
    EV_RETURN
);
Event EV_ScriptThread_GetACos
(
    "acos",
    EV_DEFAULT,
    "f",
    "x",
    "Compute arc cosine",
    EV_RETURN
);
Event EV_ScriptThread_GetASin
(
    "asin",
    EV_DEFAULT,
    "f",
    "x",
    "Compute arc sine",
    EV_RETURN
);
Event EV_ScriptThread_GetATan2
(
    "atan2",
    EV_DEFAULT,
    "ff",
    "x y",
    "Compute arc tangent with two parameters",
    EV_RETURN
);
Event EV_ScriptThread_GetCosH
(
    "cosh",
    EV_DEFAULT,
    "f",
    "x",
    "Compute hyperbolic cosine",
    EV_RETURN
);
Event EV_ScriptThread_GetSinH
(
    "sinh",
    EV_DEFAULT,
    "f",
    "x",
    "Compute hyperbolic sine",
    EV_RETURN
);
Event EV_ScriptThread_GetTanH
(
    "tanh",
    EV_DEFAULT,
    "f",
    "x",
    "Compute hyperbolic tangent",
    EV_RETURN
);
Event EV_ScriptThread_GetExp
(
    "exp",
    EV_DEFAULT,
    "f",
    "x",
    "Compute exponential function",
    EV_RETURN
);
Event EV_ScriptThread_GetFrexp
(
    "frexp",
    EV_DEFAULT,
    "f",
    "x",
    "Get significand and exponent",
    EV_RETURN
);
Event EV_ScriptThread_GetLdexp
(
    "ldexp",
    EV_DEFAULT,
    "f",
    "x",
    "Generate number from significand and exponent",
    EV_RETURN
);
Event EV_ScriptThread_GetLog
(
    "log",
    EV_DEFAULT,
    "f",
    "x",
    "Compute natural logarithm",
    EV_RETURN
);
Event EV_ScriptThread_GetLog10
(
    "log10",
    EV_DEFAULT,
    "f",
    "x",
    "Compute common logarithm",
    EV_RETURN
);
Event EV_ScriptThread_GetModf
(
    "modf",
    EV_DEFAULT,
    "f",
    "x",
    "Break into fractional and integral parts",
    EV_RETURN
);
Event EV_ScriptThread_GetPow
(
    "pow",
    EV_DEFAULT,
    "ff",
    "x y",
    "Raise to power",
    EV_RETURN
);
Event EV_ScriptThread_GetCeil
(
    "ceil",
    EV_DEFAULT,
    "f",
    "x",
    "Round up value",
    EV_RETURN
);
Event EV_ScriptThread_GetFloor
(
    "floor",
    EV_DEFAULT,
    "f",
    "x",
    "Round down value",
    EV_RETURN
);
Event EV_ScriptThread_GetFmod
(
    "fmod",
    EV_DEFAULT,
    "f",
    "x",
    "Compute remainder of division",
    EV_RETURN
);
Event EV_ScriptThread_strncpy
(
    "strncpy",
    EV_DEFAULT,
    "is",
    "bytes source",
    "Returns the copied string with the specified bytes",
    EV_RETURN
);
Event EV_ScriptThread_CreateHUD
(
    "newhud",
    EV_DEFAULT,
    "E",
    "player",
    "Create a new HUD element. Optionally for a particular client",
    EV_RETURN
);
Event EV_ScriptThread_TraceDetails(
    "traced",
    EV_DEFAULT,
    "vvIVVI",
    "start end pass_entities mins maxs mask",
    "Performs a Trace Line from the start to the end, returns the array with detailed results",
    EV_RETURN
);
Event EV_ScriptThread_TypeOf
(
    "typeof",
    EV_DEFAULT,
    "i",
    "variable",
    "Returns the type of variable",
    EV_RETURN
);
Event EV_ScriptThread_Md5String
(
    "md5string",
    EV_DEFAULT,
    "s",
    "text",
    "generates MD5 hash of given text",
    EV_RETURN
);
Event EV_ScriptThread_SetTimer
(
    "settimer",
    EV_DEFAULT,
    "is",
    "interval script",
    "Sets timer that will execute script after given interval.",
    EV_RETURN
);
Event EV_ScriptThread_TeamGetScore
(
    "team_getscore",
    EV_DEFAULT,
    "s",
    "team",
    "Get a team's current score.",
    EV_NORMAL
);
Event EV_ScriptThread_TeamSetScore
(
    "team_setscore",
    EV_DEFAULT,
    "siI",
    "team score bAdd",
    "Change/Add score to a team.",
    EV_NORMAL
);
Event EV_ScriptThread_VisionGetNaked
(
    "visiongetnaked",
    EV_DEFAULT,
    NULL,
    NULL,
    "Gets players' global current naked-eye vision.",
    EV_NORMAL
);
Event EV_ScriptThread_VisionSetNaked
(
    "visionsetnaked",
    EV_DEFAULT,
    "sF",
    "vision_name transition_time",
    "Sets players' naked-eye vision. Optionally give a transition time from the current vision. If vision_name is an "
    "empty string, it will be set to the current map's name.",
    EV_NORMAL
);

CLASS_DECLARATION(Listener, ScriptThread, NULL) {
    {&EV_ScriptThread_GetCvar,                 &ScriptThread::Getcvar                 },
    {&EV_ScriptThread_GetRandomFloat,          &ScriptThread::GetRandomFloat          },
    {&EV_ScriptThread_GetRandomInt,            &ScriptThread::GetRandomInt            },
    {&EV_ScriptThread_GetAbs,                  &ScriptThread::GetAbs                  },
    {&EV_ScriptThread_GetSelf,                 &ScriptThread::GetSelf                 },
    {&EV_ScriptThread_GetCos,                  &ScriptThread::EventCos                },
    {&EV_ScriptThread_GetSin,                  &ScriptThread::EventSin                },
    {&EV_ScriptThread_GetTan,                  &ScriptThread::EventTan                },
    {&EV_ScriptThread_GetATan,                 &ScriptThread::EventATan               },
    {&EV_ScriptThread_GetSqrt,                 &ScriptThread::EventSqrt               },
    {&EV_ScriptThread_Vector_Length,           &ScriptThread::Vector_Length           },
    {&EV_ScriptThread_Vector_Normalize,        &ScriptThread::Vector_Normalize        },
    {&EV_ScriptThread_Vector_Add,              &ScriptThread::Vector_Add              },
    {&EV_ScriptThread_Vector_Subtract,         &ScriptThread::Vector_Subtract         },
    {&EV_ScriptThread_Vector_Scale,            &ScriptThread::Vector_Scale            },
    {&EV_ScriptThread_Vector_DotProduct,       &ScriptThread::Vector_DotProduct       },
    {&EV_ScriptThread_Vector_CrossProduct,     &ScriptThread::Vector_CrossProduct     },
    {&EV_ScriptThread_Vector_ToAngles,         &ScriptThread::Vector_ToAngles         },
    {&EV_ScriptThread_AIsCloserThanBToC,       &ScriptThread::EventAIsCloserThanBToC  },
    {&EV_ScriptThread_PointsWithinDist,        &ScriptThread::EventPointsWithinDist   },
    {&EV_ScriptThread_Angles_ToForward,        &ScriptThread::Angles_ToForward        },
    {&EV_ScriptThread_Angles_ToLeft,           &ScriptThread::Angles_ToLeft           },
    {&EV_ScriptThread_Angles_ToUp,             &ScriptThread::Angles_ToUp             },
    {&EV_ScriptThread_Angles_PointAt,          &ScriptThread::Angles_PointAt          },
    {&EV_Listener_CreateThread,                &ScriptThread::CreateThread            },
    {&EV_Listener_CreateReturnThread,          &ScriptThread::CreateReturnThread      },
    {&EV_Listener_ExecuteScript,               &ScriptThread::ExecuteScript           },
    {&EV_Listener_ExecuteReturnScript,         &ScriptThread::ExecuteReturnScript     },
    {&EV_ScriptThread_Goto,                    &ScriptThread::EventGoto               },
    {&EV_Throw,                                &ScriptThread::EventThrow              },
    {&EV_DelayThrow,                           &ScriptThread::EventDelayThrow         },
    {&EV_ScriptThread_Wait,                    &ScriptThread::EventWait               },
    {&EV_ScriptThread_WaitFrame,               &ScriptThread::EventWaitFrame          },
    {&EV_ScriptThread_Pause,                   &ScriptThread::EventPause              },
    {&EV_ScriptThread_End,                     &ScriptThread::EventEnd                },
    {&EV_ScriptThread_Print,                   &ScriptThread::Print                   },
    {&EV_ScriptThread_Println,                 &ScriptThread::Println                 },
    {&EV_ScriptThread_DPrintln,                &ScriptThread::DPrintln                },
    {&EV_ScriptThread_IPrintln,                &ScriptThread::IPrintln                },
    {&EV_ScriptThread_IPrintlnBold,            &ScriptThread::IPrintlnBold            },
    {&EV_ScriptThread_IPrintln_NoLoc,          &ScriptThread::IPrintln_NoLoc          },
    {&EV_ScriptThread_IPrintlnBold_NoLoc,      &ScriptThread::IPrintlnBold_NoLoc      },
    {&EV_ScriptThread_MPrint,                  &ScriptThread::MPrint                  },
    {&EV_ScriptThread_MPrintln,                &ScriptThread::MPrintln                },
    {&EV_ScriptThread_Assert,                  &ScriptThread::Assert                  },
    {&EV_ScriptThread_CastInt,                 &ScriptThread::CastInt                 },
    {&EV_ScriptThread_CastFloat,               &ScriptThread::CastFloat               },
    {&EV_ScriptThread_CastString,              &ScriptThread::CastString              },
    {&EV_ScriptThread_CastBoolean,             &ScriptThread::CastBoolean             },
    {&EV_ScriptThread_CastEntity,              &ScriptThread::CastEntity              },
    {&EV_ScriptThread_Trigger,                 &ScriptThread::TriggerEvent            },
    {&EV_ScriptThread_ServerOnly,              &ScriptThread::ServerEvent             },
    {&EV_ScriptThread_StuffCommand,            &ScriptThread::StuffCommand            },
    {&EV_ScriptThread_Spawn,                   &ScriptThread::Spawn                   },
    {&EV_ScriptThread_SpawnReturn,             &ScriptThread::SpawnReturn             },
    {&EV_ScriptThread_Map,                     &ScriptThread::MapEvent                },
    {&EV_ScriptThread_AliasCache,              &ScriptThread::RegisterAliasAndCache   },
    {&EV_ScriptThread_Precache_Cache,          &ScriptThread::CacheResourceEvent      },
    {&EV_ScriptThread_SetCvar,                 &ScriptThread::SetCvarEvent            },
    {&EV_ScriptThread_DebugLine,               &ScriptThread::EventDebugLine          },
    {&EV_ScriptThread_CueCamera,               &ScriptThread::CueCamera               },
    {&EV_ScriptThread_CuePlayer,               &ScriptThread::CuePlayer               },
    {&EV_ScriptThread_FreezePlayer,            &ScriptThread::FreezePlayer            },
    {&EV_ScriptThread_ReleasePlayer,           &ScriptThread::ReleasePlayer           },
    {&EV_ScriptThread_FadeIn,                  &ScriptThread::FadeIn                  },
    {&EV_ScriptThread_FadeOut,                 &ScriptThread::FadeOut                 },
    {&EV_ScriptThread_FadeSound,               &ScriptThread::FadeSound               },
    {&EV_ScriptThread_ClearFade,               &ScriptThread::ClearFade               },
    {&EV_ScriptThread_Letterbox,               &ScriptThread::Letterbox               },
    {&EV_ScriptThread_ClearLetterbox,          &ScriptThread::ClearLetterbox          },
    {&EV_ScriptThread_MusicEvent,              &ScriptThread::MusicEvent              },
    {&EV_ScriptThread_ForceMusicEvent,         &ScriptThread::ForceMusicEvent         },
    {&EV_ScriptThread_MusicVolumeEvent,        &ScriptThread::MusicVolumeEvent        },
    {&EV_ScriptThread_RestoreMusicVolumeEvent, &ScriptThread::RestoreMusicVolumeEvent },
    {&EV_ScriptThread_SoundtrackEvent,         &ScriptThread::SoundtrackEvent         },
    {&EV_ScriptThread_RestoreSoundtrackEvent,  &ScriptThread::RestoreSoundtrackEvent  },
    {&EV_ScriptThread_CameraCommand,           &ScriptThread::CameraCommand           },
    {&EV_ScriptThread_SetCinematic,            &ScriptThread::SetCinematic            },
    {&EV_ScriptThread_SetNonCinematic,         &ScriptThread::SetNonCinematic         },
    {&EV_ScriptThread_AllAIOff,                &ScriptThread::SetAllAIOff             },
    {&EV_ScriptThread_AllAIOn,                 &ScriptThread::SetAllAIOn              },
    {&EV_ScriptThread_KillEnt,                 &ScriptThread::KillEnt                 },
    {&EV_ScriptThread_GetEntByEntnum,          &ScriptThread::GetEntByEntnum          },
    {&EV_ScriptThread_RemoveEnt,               &ScriptThread::RemoveEnt               },
    {&EV_ScriptThread_KillClass,               &ScriptThread::KillClass               },
    {&EV_ScriptThread_RemoveClass,             &ScriptThread::RemoveClass             },
    {&EV_ScriptThread_SetLightStyle,           &ScriptThread::SetLightStyle           },
    {&EV_ScriptThread_CenterPrint,             &ScriptThread::CenterPrint             },
    {&EV_ScriptThread_LocationPrint,           &ScriptThread::LocationPrint           },
    {&EV_ScriptThread_MissionFailed,           &ScriptThread::MissionFailed           },
    {&EV_ScriptThread_IsAlive,                 &ScriptThread::EventIsAlive            },
    {&EV_ScriptThread_Popmenu,                 &ScriptThread::EventPopmenu            },
    {&EV_ScriptThread_Showmenu,                &ScriptThread::EventShowmenu           },
    {&EV_ScriptThread_Hidemenu,                &ScriptThread::EventHidemenu           },
    {&EV_ScriptThread_PlayMovie,               &ScriptThread::EventPlayMovie          },
    {&EV_ScriptThread_Pushmenu,                &ScriptThread::EventPushmenu           },
    {&EV_ScriptThread_HideMouse,               &ScriptThread::EventHideMouse          },
    {&EV_ScriptThread_CreateListener,          &ScriptThread::EventCreateListener     },
    {&EV_ScriptThread_Trace,                   &ScriptThread::EventTrace              },
    {&EV_ScriptThread_SightTrace,              &ScriptThread::EventSightTrace         },
    {&EV_ScriptThread_Print3D,                 &ScriptThread::EventPrint3D            },
    {&EV_ScriptThread_HudDraw_Shader,          &ScriptThread::EventHudDrawShader      },
    {&EV_ScriptThread_HudDraw_Align,           &ScriptThread::EventHudDrawAlign       },
    {&EV_ScriptThread_HudDraw_Rect,            &ScriptThread::EventHudDrawRect        },
    {&EV_ScriptThread_HudDraw_VirtualSize,     &ScriptThread::EventHudDrawVirtualSize },
    {&EV_ScriptThread_HudDraw_Color,           &ScriptThread::EventHudDrawColor       },
    {&EV_ScriptThread_HudDraw_Alpha,           &ScriptThread::EventHudDrawAlpha       },
    {&EV_ScriptThread_HudDraw_String,          &ScriptThread::EventHudDrawString      },
    {&EV_ScriptThread_HudDraw_Font,            &ScriptThread::EventHudDrawFont        },
    {&EV_ScriptThread_Timeout,                 &ScriptThread::EventTimeout            },
    {&EV_ScriptThread_Error,                   &ScriptThread::EventError              },
    {&EV_ScriptThread_DebugInt3,               &ScriptThread::EventDebugInt3          },
    {&EV_ScriptThread_RadiusDamage,            &ScriptThread::EventRadiusDamage       },
    {&EV_ScriptThread_LandmineDamage,          &ScriptThread::EventLandmineDamage     },
    {&EV_ScriptThread_BspTransition,           &ScriptThread::EventBspTransition      },
    {&EV_ScriptThread_LevelTransition,         &ScriptThread::EventLevelTransition    },
    {&EV_ScriptThread_MissionTransition,       &ScriptThread::EventMissionTransition  },
    {&EV_ScriptThread_Earthquake,              &ScriptThread::EventEarthquake         },
    {&EV_ScriptThread_TeamWin,                 &ScriptThread::EventTeamWin            },
    {&EV_ScriptThread_StopTeamRespawn,         &ScriptThread::EventStopTeamRespawn    },
    {&EV_ScriptThread_GetBoundKey1,            &ScriptThread::EventGetBoundKey1       },
    {&EV_ScriptThread_GetBoundKey2,            &ScriptThread::EventGetBoundKey2       },
    {&EV_ScriptThread_LocConvertString,        &ScriptThread::EventLocConvertString   },
    {&EV_ScriptThread_SetScoreboardToggle,     &ScriptThread::EventSetScoreboardToggle},
    {&EV_ScriptThread_AddObjective,            &ScriptThread::EventAddObjective       },
    {&EV_ScriptThread_SetCurrentObjective,     &ScriptThread::EventSetCurrentObjective},
    {&EV_ScriptThread_SetObjectiveLocation,    &ScriptThread::SetObjectiveLocation    },
    {&EV_ScriptThread_ClearObjectiveLocation,  &ScriptThread::ClearObjectiveLocation  },
    {&EV_ScriptThread_DrawHud,                 &ScriptThread::EventDrawHud            },

    {&EV_ScriptThread_RegisterCommand,         &ScriptThread::EventRegisterCommand    },
    {&EV_ScriptThread_IsArray,                 &ScriptThread::EventIsArray            },
    {&EV_ScriptThread_IsDefined,               &ScriptThread::EventIsDefined          },
    {&EV_ScriptThread_GetACos,                 &ScriptThread::EventACos               },
    {&EV_ScriptThread_GetASin,                 &ScriptThread::EventASin               },
    {&EV_ScriptThread_GetATan2,                &ScriptThread::EventATan2              },
    {&EV_ScriptThread_GetCeil,                 &ScriptThread::EventCeil               },
    {&EV_ScriptThread_GetCosH,                 &ScriptThread::EventCosH               },
    {&EV_ScriptThread_GetExp,                  &ScriptThread::EventExp                },
    {&EV_ScriptThread_GetFloor,                &ScriptThread::EventFloor              },
    {&EV_ScriptThread_GetFmod,                 &ScriptThread::EventFmod               },
    {&EV_ScriptThread_GetFrexp,                &ScriptThread::EventFrexp              },
    {&EV_ScriptThread_GetLdexp,                &ScriptThread::EventLdexp              },
    {&EV_ScriptThread_GetLog,                  &ScriptThread::EventLog                },
    {&EV_ScriptThread_GetLog10,                &ScriptThread::EventLog10              },
    {&EV_ScriptThread_GetModf,                 &ScriptThread::EventModf               },
    {&EV_ScriptThread_GetPow,                  &ScriptThread::EventPow                },
    {&EV_ScriptThread_GetSinH,                 &ScriptThread::EventSinH               },
    {&EV_ScriptThread_GetTanH,                 &ScriptThread::EventTanH               },
    {&EV_ScriptThread_strncpy,                 &ScriptThread::StringBytesCopy         },
    {&EV_ScriptThread_Md5String,               &ScriptThread::Md5String               },
    {&EV_ScriptThread_TypeOf,                  &ScriptThread::TypeOfVariable          },
    {&EV_ScriptThread_RegisterEv,              &ScriptThread::RegisterEvent           },
    {&EV_ScriptThread_UnregisterEv,            &ScriptThread::UnregisterEvent         },
    {&EV_ScriptThread_CancelWaiting,           &ScriptThread::CancelWaiting           },
    {&EV_ScriptThread_GetTime,                 &ScriptThread::GetTime                 },
    {&EV_ScriptThread_GetTimeZone,             &ScriptThread::GetTimeZone             },
    {&EV_ScriptThread_PregMatch,               &ScriptThread::PregMatch               },
    {&EV_ScriptThread_FlagClear,               &ScriptThread::FlagClear               },
    {&EV_ScriptThread_FlagInit,                &ScriptThread::FlagInit                },
    {&EV_ScriptThread_FlagSet,                 &ScriptThread::FlagSet                 },
    {&EV_ScriptThread_FlagWait,                &ScriptThread::FlagWait                },
    {&EV_ScriptThread_HudDraw_3d,              &ScriptThread::EventHudDraw3d          },
    {&EV_ScriptThread_HudDraw_Timer,           &ScriptThread::EventHudDrawTimer       },
    {&EV_ScriptThread_CanSwitchTeams,          &ScriptThread::CanSwitchTeams          },
    {&EV_ScriptThread_CharToInt,               &ScriptThread::CharToInt               },
    {&EV_ScriptThread_Conprintf,               &ScriptThread::Conprintf               },
    {&EV_ScriptThread_CreateHUD,               &ScriptThread::CreateHUD               },
    {&EV_ScriptThread_Earthquake2,             &ScriptThread::Earthquake              },
    {&EV_ScriptThread_FadeSound,               &ScriptThread::FadeSound               },
    {&EV_ScriptThread_FileClose,               &ScriptThread::FileClose               },
    {&EV_ScriptThread_FileClose2,              &ScriptThread::FileClose               },
    {&EV_ScriptThread_FileCopy,                &ScriptThread::FileCopy                },
    {&EV_ScriptThread_FileEof,                 &ScriptThread::FileEof                 },
    {&EV_ScriptThread_FileError,               &ScriptThread::FileError               },
    {&EV_ScriptThread_FileExists,              &ScriptThread::FileExists              },
    {&EV_ScriptThread_FileFlush,               &ScriptThread::FileFlush               },
    {&EV_ScriptThread_FileGetc,                &ScriptThread::FileGetc                },
    {&EV_ScriptThread_FileGets,                &ScriptThread::FileGets                },
    {&EV_ScriptThread_FileList,                &ScriptThread::FileList                },
    {&EV_ScriptThread_FileNewDirectory,        &ScriptThread::FileNewDirectory        },
    {&EV_ScriptThread_FileOpen,                &ScriptThread::FileOpen                },
    {&EV_ScriptThread_FilePutc,                &ScriptThread::FilePutc                },
    {&EV_ScriptThread_FilePuts,                &ScriptThread::FilePuts                },
    {&EV_ScriptThread_FileRead,                &ScriptThread::FileRead                },
    {&EV_ScriptThread_FileReadAll,             &ScriptThread::FileReadAll             },
    {&EV_ScriptThread_FileRemove,              &ScriptThread::FileRemove              },
    {&EV_ScriptThread_FileRemoveDirectory,     &ScriptThread::FileRemoveDirectory     },
    {&EV_ScriptThread_FileRename,              &ScriptThread::FileRename              },
    {&EV_ScriptThread_FileRewind,              &ScriptThread::FileRewind              },
    {&EV_ScriptThread_FileSaveAll,             &ScriptThread::FileSaveAll             },
    {&EV_ScriptThread_FileSeek,                &ScriptThread::FileSeek                },
    {&EV_ScriptThread_FileTell,                &ScriptThread::FileTell                },
    {&EV_ScriptThread_FileWrite,               &ScriptThread::FileWrite               },
    {&EV_ScriptThread_GetAreaEntities,         &ScriptThread::GetAreaEntities         },
    {&EV_ScriptThread_GetArrayKeys,            &ScriptThread::GetArrayKeys            },
    {&EV_ScriptThread_GetArrayValues,          &ScriptThread::GetArrayValues          },
    {&EV_ScriptThread_GetDate,                 &ScriptThread::GetDate                 },
    {&EV_ScriptThread_GetEntArray,             &ScriptThread::GetEntArray             },
    {&EV_ScriptThread_GetPlayerClientNum,      &ScriptThread::GetPlayerClientNum      },
    {&EV_ScriptThread_GetPlayerIP,             &ScriptThread::GetPlayerIP             },
    {&EV_ScriptThread_GetPlayerNetname,        &ScriptThread::GetPlayerNetname        },
    {&EV_ScriptThread_GetPlayerPing,           &ScriptThread::GetPlayerPing           },
    {&EV_ScriptThread_iHudDraw3d,              &ScriptThread::EventIHudDraw3d         },
    {&EV_ScriptThread_iHudDrawAlign,           &ScriptThread::EventIHudDrawAlign      },
    {&EV_ScriptThread_iHudDrawAlpha,           &ScriptThread::EventIHudDrawAlpha      },
    {&EV_ScriptThread_iHudDrawColor,           &ScriptThread::EventIHudDrawColor      },
    {&EV_ScriptThread_iHudDrawFont,            &ScriptThread::EventIHudDrawFont       },
    {&EV_ScriptThread_iHudDrawRect,            &ScriptThread::EventIHudDrawRect       },
    {&EV_ScriptThread_iHudDrawShader,          &ScriptThread::EventIHudDrawShader     },
    {&EV_ScriptThread_iHudDrawString,          &ScriptThread::EventIHudDrawString     },
    {&EV_ScriptThread_iHudDrawTimer,           &ScriptThread::EventIHudDrawTimer      },
    {&EV_ScriptThread_iHudDrawVirtualSize,     &ScriptThread::EventIHudDrawVirtualSize},
    {&EV_ScriptThread_IsOnGround,              &ScriptThread::EventIsOnGround         },
    {&EV_ScriptThread_IsOutOfBounds,           &ScriptThread::EventIsOutOfBounds      },
    {&EV_ScriptThread_RestoreSound,            &ScriptThread::RestoreSound            },
    {&EV_ScriptThread_RemoveArchivedClass,     &ScriptThread::RemoveArchivedClass     },
    {&EV_ScriptThread_ServerStufftext,         &ScriptThread::ServerStufftext         },
    {&EV_ScriptThread_SetTimer,                &ScriptThread::SetTimer                },
    {&EV_ScriptThread_TeamGetScore,            &ScriptThread::TeamGetScore            },
    {&EV_ScriptThread_TeamSetScore,            &ScriptThread::TeamSetScore            },
    {&EV_ScriptThread_TraceDetails,            &ScriptThread::TraceDetails            },
    {&EV_ScriptThread_VisionGetNaked,          &ScriptThread::VisionGetNaked          },
    {&EV_ScriptThread_VisionSetNaked,          &ScriptThread::VisionSetNaked          },
    {NULL,                                     NULL                                   }
};

MEM_BlockAlloc<ScriptThread> ScriptThread_allocator;

void *ScriptThread::operator new(size_t size)
{
    return ScriptThread_allocator.Alloc();
}

void ScriptThread::operator delete(void *ptr)
{
    ScriptThread_allocator.Free(ptr);
}

ScriptThread::ScriptThread()
{
    m_ScriptVM = NULL;
}

ScriptThread::ScriptThread(ScriptClass *scriptClass, unsigned char *pCodePos)
{
    m_ScriptVM = new ScriptVM(scriptClass, pCodePos, this);

    m_ScriptVM->m_ThreadState = THREAD_RUNNING;

    if (g_scripttrace->integer && CanScriptTracePrint()) {
        gi.DPrintf2("+++Constructor THREAD: %p %p\n", this, scriptClass);
    }
}

ScriptThread::~ScriptThread()
{
    ScriptVM *vm;

    if (g_scripttrace->integer && CanScriptTracePrint()) {
        gi.DPrintf2("---Destructor THREAD: %p\n", this);
    }

    assert(m_ScriptVM);
    if (!m_ScriptVM) {
        // should never happen
        //throw ScriptException("Attempting to delete a dead thread.");
        gi.Error(ERR_DROP, "Attempting to delete a dead thread.");
    }

    vm         = m_ScriptVM;
    m_ScriptVM = NULL;
    if (vm->ThreadState() == THREAD_WAITING) {
        vm->m_ThreadState = THREAD_RUNNING;
        Director.RemoveTiming(this);
    } else if (vm->ThreadState() == THREAD_SUSPENDED) {
        vm->m_ThreadState = THREAD_RUNNING;
        CancelWaitingAll();
    }

    vm->NotifyDelete();
}

ScriptClass *ScriptThread::GetScriptClass(void)
{
    return m_ScriptVM->GetScriptClass();
}

str ScriptThread::FileName(void)
{
    return m_ScriptVM->Filename();
}

void ScriptThread::StartedWaitFor(void)
{
    Stop();
    StartWaiting();
    m_ScriptVM->Suspend();
}

void ScriptThread::StoppedWaitFor(const_str name, bool bDeleting)
{
    if (!m_ScriptVM) {
        return;
    }

    // The thread is deleted if the listener is deleting
    if (bDeleting) {
        delete this;
        return;
    }

    ///////
    // Openmohaa: allows waittill to timeout
    //
    CancelEventsOfType(EV_ScriptThread_CancelWaiting);
    ///////

    if (m_ScriptVM->m_ThreadState == THREAD_SUSPENDED) {
        if (name != 0) {
            if (m_ScriptVM->IsSuspended()) {
                Execute();
            } else {
                m_ScriptVM->Resume();
            }
        } else {
            StartTiming();
        }
    }
}

void ScriptThread::StoppedNotify(void)
{
    // This is invalid and we mustn't get here
    if (m_ScriptVM) {
        delete this;
    }
}

void ScriptThread::ScriptExecute(ScriptVariable *data, int dataSize, ScriptVariable& returnValue)
{
    m_ScriptVM->m_ReturnValue = returnValue;

    ScriptExecuteInternal(data, dataSize);
}

void ScriptThread::ScriptExecuteInternal(ScriptVariable *data, int dataSize)
{
    SafePtr<ScriptThread> currentThread;
    SafePtr<ScriptThread> previousThread;

    currentThread  = Director.CurrentThread();
    previousThread = this;

    Director.m_PreviousThread = Director.CurrentThread();
    Director.m_CurrentThread  = this;

    Stop();
    m_ScriptVM->Execute(data, dataSize);

    // restore the previous values
    Director.m_CurrentThread  = currentThread;
    Director.m_PreviousThread = previousThread;

    Director.ExecuteRunning();
}

void ScriptThread::Execute(Event& ev)
{
    Execute(&ev);
}

void ScriptThread::Execute(Event *ev)
{
    assert(m_ScriptVM);

    try {
        if (ev == NULL) {
            ScriptExecuteInternal();
        } else {
            ScriptVariable returnValue;

            returnValue.newPointer();

            ScriptExecute(ev->data, ev->dataSize, returnValue);

            ev->AddValue(returnValue);
        }
    } catch (ScriptException& exc) {
        if (exc.bAbort) {
            gi.Error(ERR_DROP, "%s\n", exc.string.c_str());
        } else {
            Com_Printf("^~^~^ Script Error: %s\n", exc.string.c_str());
        }
    }
}

void ScriptThread::Execute(ScriptVariable *data, int dataSize)
{
    ScriptExecuteInternal(data, dataSize);
}

void ScriptThread::Execute()
{
    if (Director.iPaused > 0) {
        StartTiming();
    } else {
        ScriptExecuteInternal(NULL, 0);
    }
}

ScriptThread *ScriptThread::CreateThreadInternal(const ScriptVariable& label)
{
    return GetScriptClass()->CreateThreadInternal(label);
}

ScriptThread *ScriptThread::CreateScriptInternal(const ScriptVariable& label)
{
    return GetScriptClass()->CreateScriptInternal(label);
}

void ScriptThread::StartTiming(void)
{
    Stop();

    m_ScriptVM->m_ThreadState = THREAD_WAITING;

    Director.AddTiming(this, level.inttime);
}

void ScriptThread::StartTiming(int time)
{
    Stop();

    m_ScriptVM->m_ThreadState = THREAD_WAITING;

    if (time < level.inttime) {
        time = level.inttime;
    }

    Director.AddTiming(this, time);
}

void ScriptThread::StartWaiting()
{
    m_ScriptVM->m_ThreadState = THREAD_SUSPENDED;
}

void ScriptThread::Stop(void)
{
    if (m_ScriptVM->ThreadState() == THREAD_WAITING) {
        m_ScriptVM->m_ThreadState = THREAD_RUNNING;
        Director.RemoveTiming(this);
    } else if (m_ScriptVM->ThreadState() == THREAD_SUSPENDED) {
        m_ScriptVM->m_ThreadState = THREAD_RUNNING;
        CancelWaitingAll();
    }
}

void ScriptThread::Wait(int time)
{
    StartTiming(level.inttime + time);
    m_ScriptVM->Suspend();
}

void ScriptThread::Pause()
{
    Stop();
    m_ScriptVM->Suspend();
}

void ScriptThread::Getcvar(Event *ev)
{
    str varName = ev->GetString(1);
    str s       = gi.Cvar_Get(varName.c_str(), "", 0)->string;

    if (strstr(s.c_str(), ".")) {
        for (size_t i = s.length() - 1; i >= 0; i--) {
            if (s[i] == '0') {
                s[i] = 0;
            } else {
                if (s[i] == '.') {
                    s[i] = 0;
                }
                break;
            }
        }
    }

    ev->AddString(s);
}

void ScriptThread::GetRandomFloat(Event *ev)
{
    ev->AddFloat(G_Random(ev->GetFloat(1)));
}

void ScriptThread::GetRandomInt(Event *ev)
{
    ev->AddInteger(G_Random(ev->GetInteger(1)));
}

void ScriptThread::GetAbs(Event *ev)
{
    ScriptVariable& val = ev->GetValue(1);
    if (val.GetType() == VARIABLE_INTEGER) {
        ev->AddInteger(ev->GetInteger(1));
    } else {
        if (val.GetType() != VARIABLE_FLOAT) {
            throw ScriptException("abs applied to bad type '%s'", val.GetTypeName());
        }
        ev->AddFloat(fabs(ev->GetFloat(1)));
    }
}

void ScriptThread::EventCos(Event *ev)
{
    float arg;
    float value;

    arg   = ev->GetFloat(1);
    value = cos(DEG2RAD(arg));
    if (isnan(value)) {
        ScriptError("cos(%f) = NaN", arg);
    }

    ev->AddFloat(value);
}

void ScriptThread::EventSin(Event *ev)
{
    float arg;
    float value;

    arg   = ev->GetFloat(1);
    value = sin(DEG2RAD(arg));
    if (isnan(value)) {
        ScriptError("sin(%f) = NaN", arg);
    }

    ev->AddFloat(value);
}

void ScriptThread::EventTan(Event *ev)
{
    float arg;
    float value;

    arg   = ev->GetFloat(1);
    value = tan(DEG2RAD(arg));
    if (isnan(value)) {
        ScriptError("tan(%f) = NaN", arg);
    }

    ev->AddFloat(value);
}

void ScriptThread::EventATan(Event *ev)
{
    float arg, arg2;
    float value;

    if (ev->NumArgs() > 1) {
        arg   = ev->GetFloat(1);
        arg2  = ev->GetFloat(2);
        value = RAD2DEG(atan2(arg, arg2));
        if (isnan(value)) {
            ScriptError("atan(%f, %f) = NaN", arg, arg2);
        }
    } else {
        arg   = ev->GetFloat(1);
        value = RAD2DEG(atan(arg));
        if (isnan(value)) {
            ScriptError("atan(%f) = NaN", arg);
        }
    }

    ev->AddFloat(value);
}

void ScriptThread::EventSqrt(Event *ev)
{
    float arg;
    float value;

    arg   = ev->GetFloat(1);
    value = sqrtf(DEG2RAD(arg));
    if (isnan(value)) {
        ScriptError("sqrt(%f) = NaN", arg);
    }

    ev->AddFloat(value);
}

void ScriptThread::GetSelf(Event *ev)
{
    ev->AddListener(m_ScriptVM->GetScriptClass()->GetSelf());
}

void ScriptThread::CreateReturnThread(Event *ev)
{
    m_ScriptVM->GetScriptClass()->CreateReturnThread(ev);
}

void ScriptThread::CreateThread(Event *ev)
{
    m_ScriptVM->GetScriptClass()->CreateThread(ev);
}

void ScriptThread::ExecuteScript(Event *ev)
{
    m_ScriptVM->GetScriptClass()->ExecuteScript(ev);
}

void ScriptThread::ExecuteReturnScript(Event *ev)
{
    m_ScriptVM->GetScriptClass()->ExecuteReturnScript(ev);
}

void ScriptThread::EventGoto(Event *ev)
{
    m_ScriptVM->EventGoto(ev);

    if (m_ScriptVM->IsSuspended()) {
        ScriptExecuteInternal();
    } else {
        Stop();
        m_ScriptVM->Resume();
    }
}

void ScriptThread::EventThrow(Event *ev)
{
    if (!m_ScriptVM->m_PrevCodePos) {
        return;
    }

    if (m_ScriptVM->EventThrow(ev)) {
        if (m_ScriptVM->IsSuspended()) {
            ScriptExecuteInternal();
        } else {
            Stop();
            m_ScriptVM->Resume();
        }
    } else {
        // we make sure this won't get deleted
        SafePtr<ScriptThread> This = this;

        Stop();

        if (!BroadcastEvent(0, *ev)) {
            m_ScriptVM->GetScriptClass()->EventThrow(ev);
        }

        if (This) {
            delete this;
        }
    }
}

// seems to work like EventThrow
void ScriptThread::EventDelayThrow(Event *ev)
{
    if (!m_ScriptVM->m_PrevCodePos) {
        return;
    }

    if (m_ScriptVM->EventThrow(ev)) {
        if (m_ScriptVM->IsSuspended()) {
            StartTiming();
        } else {
            Stop();
            m_ScriptVM->Resume();
        }
    } else {
        // we make sure this won't get deleted
        SafePtr<ScriptThread> This = this;

        Stop();

        if (!BroadcastEvent(0, *ev)) {
            m_ScriptVM->GetScriptClass()->EventDelayThrow(ev);
        }

        if (This) {
            delete this;
        }
    }
}

void ScriptThread::EventWait(Event *ev)
{
    float timeSeconds;

    // Fixed in OPM
    //  Clamp to make sure to not overflow when converting to integer
    timeSeconds = Q_clamp_float(ev->GetFloat(1), 0, 2000000);
    Wait((int)roundf(timeSeconds * 1000));
}

void ScriptThread::EventWaitFrame(Event *ev)
{
    Wait((int)roundf(level.frametime * 1000));
}

void ScriptThread::EventResume(Event *ev)
{
    // Empty
}

void ScriptThread::EventPause(Event *ev)
{
    Pause();
}

void ScriptThread::EventEnd(Event *ev)
{
    if (ev->NumArgs() > 0) {
        m_ScriptVM->End(ev->GetValue(1));
    } else {
        m_ScriptVM->End();
    }
}

void ScriptThread::Print(Event *ev)
{
    if (!developer->integer) {
        return;
    }

    for (int i = 1; i <= ev->NumArgs(); i++) {
        gi.DPrintf2(ev->GetString(i).c_str());
    }
}

void ScriptThread::Println(Event *ev)
{
    if (!developer->integer) {
        return;
    }

    Print(ev);
    gi.DPrintf2("\n");
}

void ScriptThread::DPrintln(Event *ev)
{
    gentity_t *ent;
    int        i;
    str        message;

    if (!developer->integer) {
        return;
    }

    for (i = 1; i <= ev->NumArgs(); i++) {
        message += ev->GetString(i);
    }

    for (i = 0, ent = g_entities; i < game.maxclients; i++, ent++) {
        if (!ent->inuse || !ent->client) {
            continue;
        }

        gi.SendServerCommand(i, "print \"" HUD_MESSAGE_YELLOW "%s\n\"", message.c_str());
    }
}

void ScriptThread::IPrintln(Event *ev)
{
    str sString;
    int num = ev->NumArgs();
    int i;

    for (i = 1; i <= ev->NumArgs(); i++) {
        sString += ev->GetString(i);
    }

    gi.SendServerCommand(-1, "print \"" HUD_MESSAGE_YELLOW "%s\n\"", gi.LV_ConvertString(sString.c_str()));
}

void ScriptThread::IPrintlnBold(Event *ev)
{
    str sString;
    int num = ev->NumArgs();
    int i;

    for (i = 1; i <= ev->NumArgs(); i++) {
        sString += ev->GetString(i);
    }

    gi.SendServerCommand(-1, "print \"" HUD_MESSAGE_WHITE "%s\n\"", gi.LV_ConvertString(sString.c_str()));
}

void ScriptThread::IPrintln_NoLoc(Event *ev)
{
    str sString;
    int num = ev->NumArgs();
    int i;

    for (i = 1; i <= ev->NumArgs(); i++) {
        sString += ev->GetString(i);
    }

    gi.SendServerCommand(-1, "print \"" HUD_MESSAGE_YELLOW "%s\n\"", sString.c_str());
}

void ScriptThread::IPrintlnBold_NoLoc(Event *ev)
{
    str sString;
    int num = ev->NumArgs();
    int i;

    for (i = 1; i <= ev->NumArgs(); i++) {
        sString += ev->GetString(i);
    }

    gi.SendServerCommand(-1, "print \"" HUD_MESSAGE_WHITE "%s\n\"", sString.c_str());
}

void ScriptThread::MPrint(Event *ev)
{
    SimpleEntity *m_Self = (SimpleEntity *)m_ScriptVM->GetScriptClass()->GetSelf();

    if (!m_Self || !m_Self->isSubclassOf(SimpleEntity)) {
        return;
    }

    for (int i = 1; i <= ev->NumArgs(); i++) {
        m_Self->MPrintf(ev->GetString(i));
    }
}

void ScriptThread::MPrintln(Event *ev)
{
    SimpleEntity *m_Self = (SimpleEntity *)m_ScriptVM->GetScriptClass()->GetSelf();

    if (!m_Self || !m_Self->isSubclassOf(SimpleEntity)) {
        return;
    }

    MPrint(ev);
    m_Self->MPrintf("\n");
}

void ScriptThread::Assert(Event *ev)
{
    assert(ev->GetFloat(1));
}

void ScriptThread::CastInt(Event *ev)
{
    ev->AddInteger(ev->GetInteger(1));
}

void ScriptThread::CastFloat(Event *ev)
{
    ev->AddFloat(ev->GetFloat(1));
}

void ScriptThread::CastString(Event *ev)
{
    ev->AddString(ev->GetString(1));
}

void ScriptThread::CastBoolean(Event *ev)
{
    ev->AddInteger(ev->GetBoolean(1));
}

void ScriptThread::CastEntity(Event *ev)
{
    ev->AddListener((Listener *)ev->GetEntity(1));
}

void ScriptThread::TriggerEvent(Event *ev)
{
    ScriptVariable var;
    Entity        *ent;

    var = ev->GetValue(1);
    var.CastConstArrayValue();

    for (int i = var.arraysize(); i > 0; i--) {
        ent = var[i]->entityValue();
        if (ent) {
            Event *event = new Event(EV_Activate);
            event->AddEntity(world);
            ent->ProcessEvent(event);
        }
    }
}

void ScriptThread::RegisterAliasAndCache(Event *ev)
{
    //assert(!"ScriptThread::RegisterAliasAndCache needs to be implemented like ClientGameCommandManager::AliasCache");
    gi.DPrintf("ScriptThread::RegisterAliasAndCache needs to be implemented like ClientGameCommandManager::AliasCache\n"
    );
}

void ScriptThread::CacheResourceEvent(Event *ev)
{
    CacheResource(ev->GetString(1));
}

void ScriptThread::MapEvent(Event *ev)
{
    if (level.mission_failed) {
        return;
    }

    G_BeginIntermission(ev->GetString(1), TRANS_BSP);
}

void ScriptThread::SetCvarEvent(Event *ev)
{
    gi.cvar_set(ev->GetString(1), ev->GetString(2));
}

void ScriptThread::EventDebugLine(Event *ev)
{
    Vector start, end;
    float  red, green, blue, alpha;

    red   = 1;
    green = 1;
    blue  = 1;
    alpha = 1;

    if (ev->NumArgs() == 5) {
        red   = ev->GetFloat(3);
        green = ev->GetFloat(4);
        blue  = ev->GetFloat(5);
    } else if (ev->NumArgs() == 6) {
        red   = ev->GetFloat(3);
        green = ev->GetFloat(4);
        blue  = ev->GetFloat(5);
        alpha = ev->GetFloat(6);
    } else if (ev->NumArgs() != 2) {
        ScriptError("debugline should have 2, 5, or 6 arguments");
    }

    if (ev->IsVectorAt(1)) {
        start = ev->GetVector(1);
    } else if (ev->GetEntity(1)) {
        start = ev->GetEntity(1)->centroid;
    } else {
        ScriptError("argument 1 to debugline should be an entity or vector");
    }

    if (ev->IsVectorAt(2)) {
        end = ev->GetVector(2);
    } else if (ev->GetEntity(2)) {
        end = ev->GetEntity(2)->centroid;
    } else {
        ScriptError("argument 2 to debugline should be an entity or vector");
    }

    G_DebugLine(start, end, red, green, blue, alpha);
}

void ScriptThread::CueCamera(Event *ev)
{
    float   switchTime;
    Entity *ent;

    if (ev->NumArgs() > 1) {
        switchTime = ev->GetFloat(2);
    } else {
        switchTime = 0;
    }

    ent = ev->GetEntity(1);
    if (ent) {
        SetCamera(ent, switchTime);
    } else {
        throw ScriptException("Camera named %s not found", ev->GetString(1).c_str());
    }
}

void ScriptThread::CuePlayer(Event *ev)
{
    float switchTime;

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

    SetCamera(NULL, switchTime);
}

void ScriptThread::FreezePlayer(Event *ev)
{
    level.playerfrozen = true;
}

void ScriptThread::ReleasePlayer(Event *ev)
{
    level.playerfrozen = false;
}

Listener *ScriptThread::SpawnInternal(Event *ev)
{
    SpawnArgs args;
    str       classname;
    Listener *l;

    if (ev->NumArgs() <= 0) {
        throw ScriptException("Usage: spawn entityname [keyname] [value]...");
    }

    classname = ev->GetString(1);

    if (getClassForID(classname) || getClass(classname)) {
        args.setArg("classname", classname);
    } else {
        if (!strstr(classname.c_str(), ".tik")) {
            classname.append(".tik");
        }

        args.setArg("model", classname);
    }

    for (int i = 2; i < ev->NumArgs(); i += 2) {
        args.setArg(ev->GetString(i), ev->GetString(i + 1));
    }

    if (!args.getClassDef()) {
        throw ScriptException("'%s' is not a valid entity name", classname.c_str());
    }

    const char *spawntarget = args.getArg("spawntarget");

    if (spawntarget) {
        SimpleEntity *target = G_FindTarget(NULL, spawntarget);

        if (!target) {
            throw ScriptException("Can't find targetname %s", spawntarget);
        }

        args.setArg("origin", va("%f %f %f", target->origin[0], target->origin[1], target->origin[2]));
        args.setArg("angle", va("%f", target->angles[1]));
    }

    level.spawnflags = 0;

    const char *s = args.getArg("spawnflags");
    if (s) {
        level.spawnflags = atoi(s);
    }

    level.m_bScriptSpawn = true;
    l                    = args.Spawn();
    level.m_bScriptSpawn = false;

    if (level.m_bRejectSpawn) {
        level.m_bRejectSpawn = false;
        throw ScriptException("Spawn command rejected for %s", classname.c_str());
    }

    return l;
}

void ScriptThread::Spawn(Event *ev)
{
    Listener *listener = SpawnInternal(ev);

    if (listener && checkInheritance(&Object::ClassInfo, listener->classinfo())) {
        throw ScriptException("You must specify an explicit classname for misc object tik models");
    }
}

void ScriptThread::SpawnReturn(Event *ev)
{
    Listener *listener = SpawnInternal(ev);

    ev->AddListener(listener);

    if (listener && checkInheritance(&Object::ClassInfo, listener->classinfo())) {
        throw ScriptException("You must specify an explicit classname for misc object tik models");
    }
}

void ScriptThread::Letterbox(Event *ev)
{
    level.m_letterbox_fraction   = 1.0f / 8.0f;
    level.m_letterbox_time       = ev->GetFloat(1);
    level.m_letterbox_time_start = ev->GetFloat(1);
    level.m_letterbox_dir        = letterbox_in;

    if (ev->NumArgs() > 1) {
        level.m_letterbox_fraction = ev->GetFloat(2);
    }
}

void ScriptThread::ClearLetterbox(Event *ev)
{
    level.m_letterbox_time = level.m_letterbox_time_start;
    level.m_letterbox_dir  = letterbox_out;
}

void ScriptThread::SetLightStyle(Event *ev)
{
    lightStyles.SetLightStyle(ev->GetInteger(1), ev->GetString(2));
}

void ScriptThread::FadeIn(Event *ev)
{
    level.m_fade_time_start = ev->GetFloat(1);
    level.m_fade_time       = ev->GetFloat(1);
    level.m_fade_color[0]   = ev->GetFloat(2);
    level.m_fade_color[1]   = ev->GetFloat(3);
    level.m_fade_color[2]   = ev->GetFloat(4);
    level.m_fade_alpha      = ev->GetFloat(5);
    level.m_fade_type       = fadein;
    level.m_fade_style      = alphablend;

    if (ev->NumArgs() > 5) {
        level.m_fade_style = (fadestyle_t)ev->GetInteger(6);
    }
}

void ScriptThread::ClearFade(Event *ev)
{
    level.m_fade_time = -1;
    level.m_fade_type = fadein;
}

void ScriptThread::FadeOut(Event *ev)
{
    level.m_fade_time_start = ev->GetFloat(1);
    level.m_fade_time       = ev->GetFloat(1);
    level.m_fade_color[0]   = ev->GetFloat(2);
    level.m_fade_color[1]   = ev->GetFloat(3);
    level.m_fade_color[2]   = ev->GetFloat(4);
    level.m_fade_alpha      = ev->GetFloat(5);
    level.m_fade_type       = fadeout;
    level.m_fade_style      = alphablend;

    if (ev->NumArgs() > 5) {
        level.m_fade_style = (fadestyle_t)ev->GetInteger(6);
    }
}

void ScriptThread::FadeSound(Event *ev)
{
    // params
    float   delaytime;
    float   min_vol;
    Player *player;

    // variables
    float time;

    // code

    delaytime = ev->GetFloat(1);

    min_vol = ev->GetFloat(2);

    if (ev->NumArgs() > 2) {
        player = (Player *)ev->GetEntity(3);
    } else {
        player = NULL;
    }

    time = delaytime * 1000.0f;

    gi.SendServerCommand(player != nullptr ? player->edict - g_entities : 0, "fadesound2 %0.2f %f", time, min_vol);
}

void ScriptThread::MusicEvent(Event *ev)
{
    const char *current;
    const char *fallback;

    current  = NULL;
    fallback = NULL;
    current  = ev->GetString(1);

    if (ev->NumArgs() > 1) {
        fallback = ev->GetString(2);
    }

    ChangeMusic(current, fallback, false);
}

void ScriptThread::MusicVolumeEvent(Event *ev)
{
    float volume;
    float fade_time;

    volume    = ev->GetFloat(1);
    fade_time = ev->GetFloat(2);

    ChangeMusicVolume(volume, fade_time);
}

void ScriptThread::RestoreMusicVolumeEvent(Event *ev)
{
    float fade_time;

    fade_time = ev->GetFloat(1);

    RestoreMusicVolume(fade_time);
}

void ScriptThread::ForceMusicEvent(Event *ev)
{
    const char *current;
    const char *fallback;

    current  = NULL;
    fallback = NULL;
    current  = ev->GetString(1);

    if (ev->NumArgs() > 1) {
        fallback = ev->GetString(2);
    }

    ChangeMusic(current, fallback, true);
}

void ScriptThread::SoundtrackEvent(Event *ev)
{
    ChangeSoundtrack(ev->GetString(1));
}

void ScriptThread::RestoreSoundtrackEvent(Event *ev)
{
    RestoreSoundtrack();
}

void ScriptThread::SetCinematic(Event *ev)
{
    G_StartCinematic();
}

void ScriptThread::SetNonCinematic(Event *ev)
{
    G_StopCinematic();
}

void ScriptThread::SetAllAIOff(Event *ev)
{
    level.ai_on = qfalse;
}

void ScriptThread::SetAllAIOn(Event *ev)
{
    level.ai_on = qtrue;
}

void ScriptThread::CenterPrint(Event *ev)
{
    int        j;
    gentity_t *other;

    for (j = 0; j < game.maxclients; j++) {
        other = &g_entities[j];
        if (other->inuse && other->client) {
            gi.centerprintf(other, ev->GetString(1));
        }
    }
}

void ScriptThread::LocationPrint(Event *ev)
{
    int        j;
    gentity_t *other;
    int        x, y;

    x = ev->GetInteger(1);
    y = ev->GetInteger(2);

    for (j = 0; j < game.maxclients; j++) {
        other = &g_entities[j];
        if (other->inuse && other->client) {
            gi.locationprintf(other, x, y, ev->GetString(3));
        }
    }
}

void ScriptThread::ServerEvent(Event *ev)
{
    Event *event = new Event(ev->GetString(1));

    for (int i = 2; i <= ev->NumArgs(); i++) {
        event->AddValue(ev->GetValue(i));
    }

    ProcessScriptEvent(event);
}

void ScriptThread::StuffCommand(Event *ev)
{
    gi.SendConsoleCommand(va("%s\n", ev->GetString(1).c_str()));
}

void ScriptThread::KillEnt(Event *ev)
{
    int     num;
    Entity *ent;

    if (ev->NumArgs() != 1) {
        throw ScriptException("No args passed in");
    }

    num = ev->GetInteger(1);
    if ((num < 0) || (num >= globals.max_entities)) {
        throw ScriptException("Value out of range.  Possible values range from 0 to %d.\n", globals.max_entities);
    }

    ent = G_GetEntity(num);
    ent->Damage(world, world, ent->max_health + 25, vec_zero, vec_zero, vec_zero, 0, 0, 0);
}

void ScriptThread::GetEntByEntnum(Event *ev)
{
    int     entnum = -1;
    Entity *ent;

    entnum = ev->GetInteger(1);

    if (entnum < 0 || entnum > globals.max_entities) {
        throw ScriptException("Entity number %d out of scope!\n", entnum);
    }

    ent = G_GetEntity(entnum);
    ev->AddEntity(ent);
}

void ScriptThread::RemoveEnt(Event *ev)
{
    int     num;
    Entity *ent;

    if (ev->NumArgs() != 1) {
        throw ScriptException("No args passed in");
    }

    num = ev->GetInteger(1);
    if ((num < 0) || (num >= globals.max_entities)) {
        throw ScriptException("Value out of range.  Possible values range from 0 to %d.\n", globals.max_entities);
    }

    ent = G_GetEntity(num);
    ent->PostEvent(Event(EV_Remove), 0);
}

void ScriptThread::KillClass(Event *ev)
{
    int        except;
    str        classname;
    gentity_t *from;
    Entity    *ent;

    if (ev->NumArgs() < 1) {
        throw ScriptException("No args passed in");
    }

    classname = ev->GetString(1);

    except = 0;
    if (ev->NumArgs() == 2) {
        except = ev->GetInteger(1);
    }

    for (from = &g_entities[game.maxclients]; from < &g_entities[globals.num_entities]; from++) {
        if (!from->inuse) {
            continue;
        }

        assert(from->entity);

        ent = from->entity;

        if (ent->entnum == except) {
            continue;
        }

        if (ent->inheritsFrom(classname.c_str())) {
            ent->Damage(world, world, ent->max_health + 25, vec_zero, vec_zero, vec_zero, 0, 0, 0);
        }
    }
}

void ScriptThread::RemoveClass(Event *ev)
{
    int        except;
    str        classname;
    gentity_t *from;
    Entity    *ent;

    if (ev->NumArgs() < 1) {
        throw ScriptException("No args passed in");
    }

    classname = ev->GetString(1);

    except = 0;
    if (ev->NumArgs() == 2) {
        except = ev->GetInteger(1);
    }

    for (from = &g_entities[game.maxclients]; from < &g_entities[globals.num_entities]; from++) {
        if (!from->inuse) {
            continue;
        }

        assert(from->entity);

        ent = from->entity;

        if (ent->entnum == except) {
            continue;
        }

        if (ent->inheritsFrom(classname.c_str())) {
            ent->PostEvent(Event(EV_Remove), 0);
        }
    }
}

void ScriptThread::CameraCommand(Event *ev)
{
    Event *e;
    str    cmd;
    int    i;
    int    n;

    if (!ev->NumArgs()) {
        throw ScriptException("Usage: cam [command] [arg 1]...[arg n]");
    }

    cmd = ev->GetString(1);
    if (Event::Exists(cmd)) {
        e = new ConsoleEvent(cmd);

        n = ev->NumArgs();
        for (i = 2; i <= n; i++) {
            e->AddToken(ev->GetToken(i));
        }

        CameraMan.ProcessEvent(e);
    } else {
        throw ScriptException("Unknown camera command '%s'.\n", cmd.c_str());
    }
}

void ScriptThread::MissionFailed(Event *ev)
{
    bool bNoFade;

    if (level.intermissiontime) {
        return;
    }

    bNoFade = ev->NumArgs() && ev->GetInteger(1);

    G_BeginIntermission(level.current_map, TRANS_MISSION_FAILED, bNoFade);
}

void ScriptThread::Archive(Archiver& arc) {}

void ScriptThread::ArchiveInternal(Archiver& arc)
{
    Listener::Archive(arc);

    arc.ArchiveObjectPosition(this);
    m_ScriptVM->Archive(arc);
}

void ScriptThread::Vector_Add(Event *ev)
{
    ev->AddVector(ev->GetVector(1) + ev->GetVector(2));
}

void ScriptThread::Vector_Subtract(Event *ev)
{
    ev->AddVector(ev->GetVector(1) - ev->GetVector(2));
}

void ScriptThread::Vector_Length(Event *ev)
{
    ev->AddFloat(ev->GetVector(1).length());
}

void ScriptThread::Vector_Normalize(Event *ev)
{
    Vector vector = ev->GetVector(1);

    vector.normalize();

    ev->AddVector(vector);
}

void ScriptThread::Vector_Scale(Event *ev)
{
    Vector vector = ev->GetVector(1);

    vector *= ev->GetFloat(2);

    ev->AddVector(vector);
}

void ScriptThread::Vector_DotProduct(Event *ev)
{
    Vector vector1 = ev->GetVector(1), vector2 = ev->GetVector(2);

    ev->AddFloat(Vector::Dot(vector1, vector2));
}

void ScriptThread::Vector_CrossProduct(Event *ev)
{
    ev->AddVector(Vector::Cross(ev->GetVector(1), ev->GetVector(2)));
}

void ScriptThread::Vector_ToAngles(Event *ev)
{
    Vector v1;
    Vector v3;

    v1 = ev->GetVector(1);
    vectoangles(v1, v3);

    ev->AddVector(v3);
}

void ScriptThread::Angles_ToForward(Event *ev)
{
    Vector v1;
    Vector v3;

    v1 = ev->GetVector(1);

    AngleVectors(v1, v3, NULL, NULL);
    ev->AddVector(v3);
}

void ScriptThread::Angles_ToLeft(Event *ev)
{
    Vector v1;
    Vector v3;

    v1 = ev->GetVector(1);

    AngleVectors(v1, NULL, v3, NULL);
    ev->AddVector(v3);
}

void ScriptThread::Angles_ToUp(Event *ev)
{
    Vector v1;
    Vector v3;

    v1 = ev->GetVector(1);

    AngleVectors(v1, NULL, NULL, v3);
    ev->AddVector(v3);
}

void ScriptThread::Angles_PointAt(Event *ev)
{
    Entity *pParent, *pEnt, *pTarget;
    Vector  vDelta, vVec, vAngles;

    pParent = ev->GetEntity(1);
    pEnt    = ev->GetEntity(2);
    pTarget = ev->GetEntity(3);

    if (pParent) {
        vDelta  = pEnt->centroid - pTarget->centroid;
        vVec[0] = DotProduct(vDelta, pParent->orientation[0]);
        vVec[1] = DotProduct(vDelta, pParent->orientation[1]);
        vVec[2] = DotProduct(vDelta, pParent->orientation[2]);
    } else {
        vVec = pEnt->centroid - pTarget->centroid;
    }

    VectorNormalize(vVec);
    vectoangles(vVec, vAngles);

    ev->AddVector(vAngles);
}

void ScriptThread::EventIsAlive(Event *ev)
{
    Entity *ent;

    if (!ev->IsEntityAt(1)) {
        ev->AddInteger(false);
        return;
    }

    ent = ev->GetEntity(1);
    if (!ent) {
        ev->AddInteger(false);
        return;
    }

    ev->AddInteger(ent->health > 0);
}

void ScriptThread::EventPopmenu(Event *ev)
{
    gentity_t *ent;
    int        i;
    int        index;

    if (game.maxclients <= 0) {
        return;
    }

    index = ev->GetInteger(1);

    for (i = 0, ent = g_entities; i < game.maxclients; i++, ent++) {
        if (!ent->inuse || !ent->client) {
            continue;
        }

        gi.Popmenu(i, index);
    }
}

void Showmenu(const str& name, qboolean bForce)
{
    gentity_t *ent;
    int        i;

    if (game.maxclients <= 0) {
        return;
    }

    for (i = 0, ent = g_entities; i < game.maxclients; i++, ent++) {
        if (!ent->inuse || !ent->client) {
            continue;
        }

        gi.Showmenu(i, name.c_str(), bForce);
    }
}

void ScriptThread::EventShowmenu(Event *ev)
{
    str name = ev->GetString(1);

    if (ev->NumArgs() > 1) {
        Showmenu(name, ev->GetBoolean(2));
    } else {
        Showmenu(name, false);
    }

    Director.AddMenu(name);
}

void Hidemenu(const str& name, qboolean bForce)
{
    gentity_t *ent;
    int        i;

    if (game.maxclients <= 0) {
        return;
    }

    for (i = 0, ent = g_entities; i < game.maxclients; i++, ent++) {
        if (!ent->inuse || !ent->client) {
            continue;
        }

        gi.Hidemenu(i, name.c_str(), bForce);
    }
}

void ScriptThread::EventHidemenu(Event *ev)
{
    str name = ev->GetString(1);

    Director.RemoveMenu(name);

    if (ev->NumArgs() > 1) {
        Hidemenu(name, ev->GetBoolean(2));
    } else {
        Hidemenu(name, false);
    }
}

void ScriptThread::EventPlayMovie(Event *ev)
{
    gi.SendConsoleCommand(va("cinematic %s", ev->GetString(1).c_str()));
}

void ScriptThread::EventPushmenu(Event *ev)
{
    gentity_t *ent;
    int        i;
    str        name;

    if (game.maxclients <= 0) {
        return;
    }

    name = ev->GetString(1);

    for (i = 0, ent = g_entities; i < game.maxclients; i++, ent++) {
        if (!ent->inuse || !ent->client) {
            continue;
        }

        gi.Pushmenu(i, name.c_str());
    }
}

void ScriptThread::EventHideMouse(Event *ev)
{
    gentity_t *ent;
    int        i;

    for (i = 0, ent = g_entities; i < game.maxclients; i++, ent++) {
        if (!ent->inuse || !ent->client) {
            continue;
        }

        gi.HideMouseCursor(i);
    }
}

void ScriptThread::EventCreateListener(Event *ev)
{
    ev->AddListener(new Listener);
}

void ScriptThread::EventTrace(Event *ev)
{
    int     content_mask = MASK_LINE;
    Vector  start;
    Vector  mins;
    Vector  maxs;
    Vector  end;
    trace_t trace;

    mins = vec_zero;
    maxs = vec_zero;

    switch (ev->NumArgs()) {
    case 5:
        maxs = ev->GetVector(5);
    case 4:
        mins = ev->GetVector(4);
    case 3:
        if (ev->GetInteger(3)) {
            content_mask &= ~MASK_SCRIPT_SLAVE;
        }
    case 2:
        end = ev->GetVector(2);
    case 1:
        start = ev->GetVector(1);
        break;
    default:
        throw ScriptException("Wrong number of arguments for trace.");
    }

    // call trace
    trace = G_Trace(start, mins, maxs, end, NULL, content_mask, false, "ScriptThread::EventTrace");

    ev->AddVector(trace.endpos);
}

void ScriptThread::EventSightTrace(Event *ev)
{
    int    content_mask = MASK_SOLID;
    Vector start;
    Vector mins;
    Vector maxs;
    Vector end;

    mins = vec_zero;
    maxs = vec_zero;

    switch (ev->NumArgs()) {
    case 5:
        maxs = ev->GetVector(5);
    case 4:
        mins = ev->GetVector(4);
    case 3:
        if (ev->GetInteger(3)) {
            content_mask &= ~MASK_IGNORE_ENTS;
        }
    case 2:
        end = ev->GetVector(2);
    case 1:
        start = ev->GetVector(1);
        break;
    default:
        throw ScriptException("Wrong number of arguments for sighttrace.");
    }

    // call trace
    ev->AddInteger(G_SightTrace(
        start,
        mins,
        maxs,
        end,
        (gentity_t *)NULL,
        (gentity_t *)NULL,
        content_mask,
        false,
        "ScriptThread::EventSightTrace"
    ));
}

void ScriptThread::EventPrint3D(Event *ev)
{
    Vector origin;
    float  scale;
    str    string;

    origin = ev->GetVector(1);
    scale  = ev->GetFloat(2);
    string = ev->GetString(3);

    G_DebugString(origin, scale, 1.0f, 1.0f, 1.0f, string);
}

void ScriptThread::EventHudDrawShader(Event *ev)
{
    int index = -1;
    str shadername;

    index = ev->GetInteger(1);

    if (index < 0 && index > 255) {
        throw ScriptException("Wrong index for huddraw_shader!\n");
    }

    shadername = ev->GetString(2);

    HudDrawShader(index, shadername);
}

void ScriptThread::EventHudDrawAlign(Event *ev)
{
    int index        = -1;
    int h_alignement = -1;
    int v_alignement = -1;
    str h_align;
    str v_align;

    index = ev->GetInteger(1);

    if (index < 0 && index > 255) {
        throw ScriptException("Wrong index for huddraw_align!\n");
    }

    h_align = ev->GetString(2);

    if (!h_align) {
        throw ScriptException("h_align is NULL for huddraw_align!\n");
    }

    v_align = ev->GetString(3);

    if (!v_align) {
        throw ScriptException("v_align is NULL for huddraw_align!\n");
    }

    if (h_align == "left") {
        h_alignement = 0;
    } else if (h_align == "center") {
        h_alignement = 1;
    } else if (h_align == "right") {
        h_alignement = 2;
    } else {
        throw ScriptException("Wrong alignment h_align string for huddraw_align!\n");
    }

    if (v_align == "top") {
        v_alignement = 0;
    } else if (v_align == "center") {
        v_alignement = 1;
    } else if (v_align == "bottom") {
        v_alignement = 2;
    } else {
        throw ScriptException("Wrong alignment v_align string for huddraw_align!\n");
    }

    HudDrawAlign(index, h_alignement, v_alignement);
}

void ScriptThread::EventHudDrawRect(Event *ev)
{
    int index  = -1;
    int x      = 0;
    int y      = 0;
    int width  = 0;
    int height = 0;

    index = ev->GetInteger(1);

    if (index < 0 && index > 255) {
        throw ScriptException("Wrong index for huddraw_rect!\n");
    }

    x      = ev->GetInteger(2);
    y      = ev->GetInteger(3);
    width  = ev->GetInteger(4);
    height = ev->GetInteger(5);

    HudDrawRect(index, x, y, width, height);
}

void ScriptThread::EventHudDrawVirtualSize(Event *ev)
{
    int index = -1;
    int virt  = -1;

    index = ev->GetInteger(1);

    if (index < 0 && index > 255) {
        throw ScriptException("Wrong index for huddraw_virtualsize!\n");
    }

    virt = ev->GetInteger(2);

    if (virt != 0) {
        virt = 1;
    }

    HudDrawVirtualSize(index, virt);
}

void ScriptThread::EventHudDrawColor(Event *ev)
{
    int    numArgs = -1;
    int    index   = -1;
    Vector color;

    numArgs = ev->NumArgs();

    index = ev->GetInteger(1);

    if (index < 0 && index > 255) {
        throw ScriptException("Wrong index for ihuddraw_color!\n");
    }

    color[0] = ev->GetFloat(2); // red
    color[1] = ev->GetFloat(3); // green
    color[2] = ev->GetFloat(4); // blue

    Vector::Clamp(color, vec_zero, Vector(1, 1, 1));

    HudDrawColor(index, color);
}

void ScriptThread::EventHudDrawAlpha(Event *ev)
{
    int   index = -1;
    float alpha = 0.0f;

    index = ev->GetInteger(1);

    if (index < 0 && index > 255) {
        throw ScriptException("Wrong index for ihuddraw_alpha!\n");
    }

    alpha = ev->GetFloat(2);
    Q_clamp(alpha, 0, 1);

    HudDrawAlpha(index, alpha);
}

void ScriptThread::EventHudDrawString(Event *ev)
{
    int index = -1;
    str string;

    index = ev->GetInteger(1);

    if (index < 0 && index > 255) {
        throw ScriptException("Wrong index for ihuddraw_string!\n");
    }

    string = ev->GetString(2);

    HudDrawString(index, string);
}

void ScriptThread::EventHudDrawFont(Event *ev)
{
    int index = -1;
    str fontname;

    index = ev->GetInteger(1);

    if (index < 0 && index > 255) {
        throw ScriptException("Wrong index for ihuddraw_font!\n");
    }

    fontname = ev->GetString(2);

    HudDrawFont(index, fontname);
}

void ScriptThread::EventError(Event *ev)
{
    if (ev->NumArgs() > 1) {
        ScriptException::next_abort = 0;
    } else {
        ScriptException::next_abort = ev->GetInteger(2);
        if (ScriptException::next_abort < 0) {
            ScriptException::next_abort = 0;
        }
    }

    throw ScriptException(ev->GetString(1));
}

void ScriptThread::EventDebugInt3(Event *ev)
{
#ifdef _MSC_VER
    __debugbreak();
#endif
    // FIXME: break on other platforms?
}

void ScriptThread::EventTimeout(Event *ev)
{
    Director.maxTime = roundf(ev->GetFloat(1) * 1000.0f);
}

void ScriptThread::EventLandmineDamage(Event *ev)
{
    Entity *landmine;
    float   damage;
    float   radius;
    Entity *owner;

    landmine = ev->GetEntity(1);
    damage   = ev->GetFloat(2);
    radius   = ev->GetFloat(3);

    if (!landmine || !landmine->edict) {
        ScriptError("Landmine doesn't exist, yet explodes!.\n");
    }

    owner = G_GetEntity(landmine->edict->r.ownerNum);

    RadiusDamage(landmine->origin, landmine, owner, damage, NULL, MOD_LANDMINE, radius);
}

void ScriptThread::EventRadiusDamage(Event *ev)
{
    Vector origin;
    float  damage;
    float  radius;
    int    constant_damage;

    origin = ev->GetVector(1);
    damage = ev->GetFloat(2);
    radius = ev->GetFloat(3);

    if (ev->NumArgs() > 3) {
        constant_damage = ev->GetInteger(4);
    } else {
        constant_damage = 0;
    }

    RadiusDamage(origin, world, world, damage, NULL, MOD_EXPLOSION, radius, 0, constant_damage);
}

void ScriptThread::EventBspTransition(Event *ev)
{
    str  map      = ev->GetString(1);
    bool skipFade = false;

    if (ev->NumArgs() >= 2) {
        // Added in 2.30
        skipFade = ev->GetBoolean(2);
    }

    if (level.intermissiontime == 0.0f) {
        G_BeginIntermission(map, TRANS_BSP, skipFade);
    }
}

void ScriptThread::EventLevelTransition(Event *ev)
{
    str map = ev->GetString(1);

    if (level.intermissiontime == 0.0f) {
        G_BeginIntermission(map, TRANS_LEVEL);
    }
}

void ScriptThread::EventMissionTransition(Event *ev)
{
    str map = ev->GetString(1);

    if (level.intermissiontime == 0.0f) {
        G_BeginIntermission(map, TRANS_MISSION);
    }
}

void ScriptThread::EventStopTeamRespawn(Event *ev)
{
    const_str team = ev->GetConstString(1);
    switch (team) {
    case STRING_GERMAN:
        dmManager.StopTeamRespawn(CONTROLLER_AXIS);
        break;
    case STRING_AMERICAN:
        dmManager.StopTeamRespawn(CONTROLLER_ALLIES);
        break;
    case STRING_BOTH:
        dmManager.StopTeamRespawn(CONTROLLER_AXIS);
        dmManager.StopTeamRespawn(CONTROLLER_ALLIES);
        break;
    default:
        ScriptError("stopteamrespawn must belong to 'american' or 'german' or 'both'");
        break;
    }
}

void ScriptThread::EventTeamWin(Event *ev)
{
    const_str team;
    int       teamnum;

    if (g_gametype->integer < GT_OBJECTIVE) {
        throw ScriptException("'teamwin' only valid for objective-based DM games");
    }

    team = ev->GetConstString(1);
    if (team == STRING_ALLIES) {
        teamnum = TEAM_ALLIES;
    } else if (team == STRING_AXIS) {
        teamnum = TEAM_AXIS;
    } else if (team == STRING_DISGUISE_NONE) {
        teamnum = TEAM_NONE;
    } else {
        throw ScriptException("'teamwin' must be called with 'axis', 'allies' or 'none' as its argument");
    }

    dmManager.TeamWin(teamnum);
}

void ScriptThread::EventEarthquake(Event *ev)
{
    earthquake_t e;

    e.duration = (int)roundf(ev->GetFloat(1) * 1000.0f);
    if (e.duration <= 0) {
        return;
    }

    e.magnitude   = ev->GetFloat(2);
    e.no_rampup   = ev->GetBoolean(3);
    e.no_rampdown = ev->GetBoolean(4);

    e.starttime = level.inttime;
    e.endtime   = level.inttime + e.duration;

    e.m_Thread = this;

    level.AddEarthquake(&e);
}

void ScriptThread::EventAIsCloserThanBToC(Event *ev)
{
    Vector vA, vB, vC;

    vA = ev->GetVector(1);
    vB = ev->GetVector(2);
    vC = ev->GetVector(3);

    ev->AddInteger((vA - vC).lengthSquared() < (vB - vC).lengthSquared());
}

void ScriptThread::EventPointsWithinDist(Event *ev)
{
    Vector delta;
    Vector v1, v2;
    float  fDistance;

    v1        = ev->GetVector(1);
    v2        = ev->GetVector(2);
    fDistance = ev->GetFloat(3);
    delta     = v1 - v2;

    // check squared distance
    ev->AddInteger(delta.lengthSquared() < Square(fDistance));
}

bool ScriptThread::CanScriptTracePrint(void)
{
    return m_ScriptVM->CanScriptTracePrint();
}

void ScriptThread::EventGetBoundKey1(Event *ev)
{
    int         iKey1;
    int         iKey2;
    const char *pszKeyName;
    str         sCommand = ev->GetString(1);

    gi.Key_GetKeysForCommand(sCommand, &iKey1, &iKey2);
    if (iKey1) {
        pszKeyName = gi.Key_KeynumToBindString(iKey1);
    } else {
        pszKeyName = "";
    }

    ev->AddString(pszKeyName);
}

void ScriptThread::EventGetBoundKey2(Event *ev)
{
    int         iKey1;
    int         iKey2;
    const char *pszKeyName;
    str         sCommand = ev->GetString(1);

    gi.Key_GetKeysForCommand(sCommand, &iKey1, &iKey2);
    if (iKey2) {
        pszKeyName = gi.Key_KeynumToBindString(iKey2);
    } else {
        pszKeyName = "";
    }

    ev->AddString(pszKeyName);
}

void ScriptThread::EventLocConvertString(Event *ev)
{
    ev->AddString(gi.LV_ConvertString(ev->GetString(1)));
}

void ScriptThread::AddObjective(int index, int status, str text, Vector location)
{
    static int last_time;
    int        flags;
    char       szSend[2048];
    char      *sTmp;

    flags = OBJ_FLAG_NONE;
    sTmp  = gi.getConfigstring(CS_OBJECTIVES + index);

    switch (status) {
    case OBJ_STATUS_HIDDEN:
        flags = OBJ_FLAG_HIDDEN;
        break;
    case OBJ_STATUS_CURRENT:
        sTmp = Info_ValueForKey(sTmp, "flags");
        if (!(atoi(sTmp) & OBJ_FLAG_CURRENT)) {
            if (last_time != level.inttime) {
                gi.Printf("An objective has been added!\n");
                last_time = level.inttime;
            }
        }
        flags = OBJ_FLAG_CURRENT;
        break;
    case OBJ_STATUS_COMPLETED:
        if (last_time != level.inttime) {
            gi.Printf("An objective has been completed!\n");
            last_time = level.inttime;
        }
        if (g_gametype->integer == GT_SINGLE_PLAYER) {
            if (g_entities->entity->IsSubclassOfPlayer()) {
                ((Player *)g_entities->entity)->m_iObjectivesCompleted++;
            }
        }
        flags = OBJ_FLAG_COMPLETED;
        break;
    }

    szSend[0] = 0;

    Info_SetValueForKey(szSend, "flags", va("%i", flags));
    Info_SetValueForKey(szSend, "text", text.c_str());
    Info_SetValueForKey(szSend, "loc", va("%f %f %f", location[0], location[1], location[2]));

    gi.setConfigstring(CS_OBJECTIVES + index, szSend);
}

void ScriptThread::SetCurrentObjective(int iObjective, int iTeam)
{
    gi.setConfigstring(CS_CURRENT_OBJECTIVE, va("%i", iObjective));

    if (iObjective == -1) {
        level.m_vObjectiveLocation       = vec_zero;
        level.m_vAlliedObjectiveLocation = vec_zero;
        level.m_vAxisObjectiveLocation   = vec_zero;
    } else {
        const char *s   = gi.getConfigstring(CS_OBJECTIVES + iObjective);
        const char *loc = Info_ValueForKey(s, "loc");
        Vector      v;

        sscanf(loc, "%f %f %f", &v[0], &v[1], &v[2]);

        //
        // Since 2.0, allow objective location per team
        //
        switch (iTeam) {
        case TEAM_NONE:
        default:
            level.m_vObjectiveLocation = v;
            break;
        case TEAM_ALLIES:
            level.m_vAlliedObjectiveLocation = v;
            break;
        case TEAM_AXIS:
            level.m_vAxisObjectiveLocation = v;
            break;
        }
    }
}

void ScriptThread::EventAddObjective(Event *ev)
{
    int    index;
    int    status;
    str    text;
    Vector location;

    index  = ev->GetInteger(1) - 1;
    status = ev->GetInteger(2);

    if (index >= MAX_OBJECTIVES) {
        throw ScriptException("Index Out Of Range");
    }

    if (status > 3) {
        throw ScriptException("Invalid Status");
    }

    if (ev->IsNilAt(3)) {
        text = Info_ValueForKey(gi.getConfigstring(index + CS_OBJECTIVES), "text");
    } else {
        text = ev->GetString(3);
    }

    if (ev->IsNilAt(4)) {
        sscanf(
            Info_ValueForKey(gi.getConfigstring(index + CS_OBJECTIVES), "loc"),
            "%f %f %f",
            &location.x,
            &location.y,
            &location.z
        );
    } else {
        location = ev->GetVector(4);
    }

    AddObjective(index, status, text, location);
}

void ScriptThread::EventSetScoreboardToggle(Event *ev)
{
    int       value;
    const_str team;

    if (ev->NumArgs() != 2) {
        ScriptError("wrong number of arguments: setscoreboardtoggle (axis|allies) (0|1)");
    }

    value = ev->GetInteger(2);
    team  = ev->GetConstString(1);
    switch (team) {
    case STRING_AXIS:
        gi.cvar_set("scoreboard_toggle2", va("%i", value));
        break;
    case STRING_ALLIES:
        gi.cvar_set("scoreboard_toggle1", va("%i", value));
        break;
    default:
        ScriptError("1st argument can only be \"axis\" or \"allies\"");
        break;
    }
}

void ScriptThread::EventSetCurrentObjective(Event *ev)
{
    int iObjective = ev->GetInteger(1);
    int iTeam      = 0;

    if (iObjective > MAX_OBJECTIVES) {
        ScriptError("Index Out Of Range");
    }

    if (ev->NumArgs() >= 2) {
        const_str teamStr = ev->GetConstString(2);
        if (teamStr == STRING_ALLIES) {
            iTeam = TEAM_ALLIES;
        } else if (teamStr == STRING_AXIS) {
            iTeam = TEAM_AXIS;
        } else {
            ScriptError("Option 2nd argument can only be \"allies\" or \"axis\"");
        }
    }

    SetCurrentObjective(iObjective - 1, iTeam);
}

void ScriptThread::SetObjectiveLocation(Vector vLocation)
{
    level.m_vObjectiveLocation = vLocation;
}

void ScriptThread::SetObjectiveLocation(Event *ev)
{
    SetObjectiveLocation(ev->GetVector(1));
}

void ScriptThread::ClearObjectiveLocation(void)
{
    level.m_vObjectiveLocation = vec_zero;
}

void ScriptThread::ClearObjectiveLocation(Event *ev)
{
    ClearObjectiveLocation();
}

void ScriptThread::EventDrawHud(Event *ev)
{
    int        i;
    gentity_t *ent;

    // TRIVIA: in mohaa, drawhud worked only for the first player
    for (i = 0, ent = g_entities; i < game.maxclients; i++, ent++) {
        if (!ent->inuse || !ent->entity || !ent->client) {
            continue;
        }

        if (ev->GetBoolean(1)) {
            ent->client->ps.pm_flags &= ~PMF_NO_HUD;
        } else {
            ent->client->ps.pm_flags |= PMF_NO_HUD;
        }
    }
}

void ScriptThread::CancelWaiting(Event *ev)
{
    CancelWaitingAll();
}

void ScriptThread::DelayExecute(Event& ev)
{
    DelayExecute(&ev);
}

void ScriptThread::DelayExecute(Event *ev)
{
    assert(m_ScriptVM);

    if (ev) {
        ScriptVariable returnValue;

        m_ScriptVM->SetFastData(ev->data, ev->dataSize);

        returnValue.newPointer();
        m_ScriptVM->m_ReturnValue = returnValue;
        ev->AddValue(returnValue);
    }

    Director.AddTiming(this, 0);
}

int ScriptThread::GetThreadState(void)
{
    return m_ScriptVM->ThreadState();
}

void ScriptThread::ServerStufftext(Event *ev)
{
    gi.SendConsoleCommand(ev->GetString(1));
}

void ScriptThread::RemoveArchivedClass(Event *ev)
{
    str classname;
    int except_entity_number = -1;

    classname = ev->GetString(1);

    if (ev->NumArgs() > 1) {
        except_entity_number = ev->GetInteger(1);
    }

    for (int i = level.m_SimpleArchivedEntities.NumObjects(); i > 0; i--) {
        SimpleArchivedEntity *m_SimpleArchivedEntity = level.m_SimpleArchivedEntities.ObjectAt(i);

        if (m_SimpleArchivedEntity == NULL) {
            continue;
        }

        // If found, remove the simple archived entity
        if (m_SimpleArchivedEntity->inheritsFrom(classname)) {
            m_SimpleArchivedEntity->PostEvent(EV_Remove, EV_REMOVE);
        }
    }

    L_ProcessPendingEvents();
}

void ScriptThread::SetTimer(Event *ev)
{
    int    interval = -1;
    void  *scr_var  = NULL;
    int    i        = 0;
    Event *event;

    if (ev->NumArgs() != 2) {
        throw ScriptException("Wrong arguments count for settimer!\n");
    }

    interval = ev->GetInteger(1);

    if (interval <= 0) {
        ev->AddInteger(1);
        return;
    }

    event = new Event(EV_Listener_ExecuteScript);
    event->AddValue(ev->GetValue(2));

    PostEvent(event, (float)interval / 1000.0f);
}

void ScriptThread::EventRegisterCommand(Event *ev)
{
    ScriptThreadLabel scriptLabel;

    scriptLabel.SetThread(ev->GetValue(2));

    m_scriptCmds.addKeyValue(ev->GetString(1)) = scriptLabel;
}

void ScriptThread::CreateHUD(Event *ev)
{
    int clientNum = -1;

    if (ev->NumArgs() > 0) {
        Player *player = (Player *)ev->GetEntity(1);
        if (!player || !player->IsSubclassOfPlayer()) {
            throw ScriptException("Invalid player entity!\n");
        }

        clientNum = player->client->ps.clientNum;
    }

    Hud *hud = new Hud(clientNum);

    ev->AddListener(hud);
}

void ScriptThread::Earthquake(Event *ev)
{
    float    duration    = ev->GetFloat(1);
    float    magnitude   = ev->GetFloat(2);
    qboolean no_rampup   = ev->GetBoolean(3);
    qboolean no_rampdown = ev->GetBoolean(4);

    if (ev->NumArgs() > 4) {
        Vector location = ev->GetVector(5);
        float  radius   = 1.0f;

        if (ev->NumArgs() > 5) {
            radius = ev->GetFloat(6);
        }

        gi.SendServerCommand(
            -1,
            "eq %f %f %d %d %f %f %f %f",
            duration,
            magnitude,
            no_rampup,
            no_rampdown,
            location[0],
            location[1],
            location[2],
            radius
        );
    } else {
        gi.SendServerCommand(-1, "eq %f %f %d %d", duration, magnitude, no_rampup, no_rampdown);
    }
}

void ScriptThread::EventClearObjectives(Event *ev) {}

void ScriptThread::SendObjective() {}

void ScriptThread::SendObjectives() {}

void ScriptThread::ClearObjectives() {}

void ScriptThread::WaitSkip(Event *ev) {}

void ScriptThread::ThreadSkip(Event *ev) {}

void ScriptThread::RestoreSound(Event *ev)
{
    // params
    float   delaytime;
    float   max_vol;
    Player *player;

    // variables
    float time;

    // code

    delaytime = ev->GetFloat(1);

    if (ev->NumArgs() > 1) {
        max_vol = ev->GetFloat(2);
    } else {
        max_vol = 1.0f;
    }

    if (ev->NumArgs() > 2) {
        player = (Player *)ev->GetEntity(3);
    } else {
        player = NULL;
    }

    time = delaytime * 1000.0f;

    gi.SendServerCommand(player != NULL ? player->edict - g_entities : 0, "restoresound %0.2f %f", time, max_vol);
}

void ScriptThread::EventHudDraw3d(Event *ev)
{
    int      index;
    float   *tmp;
    vec3_t   vector;
    int      ent_num;
    qboolean bAlwaysShow, bDepth;

    index = ev->GetInteger(1);

    if (index < 0 && index > 255) {
        throw ScriptException("Wrong index for huddraw_3d!\n");
    }

    tmp = ev->GetVector(2);
    memcpy(&vector, tmp, sizeof(vec3_t));

    ent_num     = ev->GetInteger(3);
    bAlwaysShow = ev->GetInteger(4);
    bDepth      = ev->GetInteger(5);

    HudDraw3d(index, vector, ent_num, bAlwaysShow, bDepth);
}

void ScriptThread::EventHudDrawTimer(Event *ev)
{
    int   index;
    float duration;
    float fade_out_time;

    index = ev->GetInteger(1);

    if (index < 0 && index > 255) {
        throw ScriptException("Wrong index for huddraw_timer!\n");
    }

    duration      = ev->GetFloat(2);
    fade_out_time = ev->GetFloat(3);

    HudDrawTimer(index, duration, fade_out_time);
}

void ScriptThread::CanSwitchTeams(Event *ev)
{
    qboolean bAllow = ev->GetBoolean(1);

    disable_team_change = !bAllow;

    if (ev->NumArgs() > 1) {
        qboolean bAllow2 = ev->GetBoolean(2);

        disable_team_spectate = !bAllow2;
    }
}

void ScriptThread::GetPlayerNetname(Event *ev)
{
    Entity *ent = NULL;

    ent = (Entity *)ev->GetEntity(1);

    if (ent == NULL) {
        ev->AddString("");

        return;
    } else if (ent->client == NULL) {
        ev->AddString("");
        return;
    }

    ev->AddString(ent->client->pers.netname);
}

void ScriptThread::GetPlayerIP(Event *ev)
{
    Entity *ent = NULL;
    char   *ip  = NULL;
    char    ip_buff[65];
    ent = (Entity *)ev->GetEntity(1);

    if (ent == NULL) {
        ev->AddString("NIL");

        return;
    } else if (ent->client == NULL) {
        ev->AddString("NIL");
        return;
    }

    ip = ent->client->pers.ip;

    Com_sprintf(ip_buff, sizeof(ip_buff), "%s:%i", ip, ent->client->pers.port);

    ev->AddString(ip_buff);
}

void ScriptThread::GetPlayerPing(Event *ev)
{
    Entity *ent  = NULL;
    int     ping = -1;

    ent = (Entity *)ev->GetEntity(1);

    if (ent == NULL) {
        throw ScriptException("Player entity is NULL for getping!\n");
    } else if (ent->client == NULL) {
        throw ScriptException("Entity is probably not of player type - getping\n");
    }

    ping = ent->client->ps.ping;

    ev->AddInteger(ping);
}

void ScriptThread::GetPlayerClientNum(Event *ev)
{
    Entity *ent    = NULL;
    int     cl_num = -1;

    ent = (Entity *)ev->GetEntity(1);

    if (ent == NULL) {
        throw ScriptException("Player entity is NULL for getclientnum!\n");
    } else if (ent->client == NULL) {
        throw ScriptException("Entity is probably not of player type - getclientnum\n");
    }

    cl_num = ent->client->ps.clientNum;

    ev->AddInteger(cl_num);
}

void ScriptThread::GetAreaEntities(Event *ev)
{
    Vector          origin;
    Vector          mins;
    Vector          maxs;
    Vector          o_min;
    Vector          o_max;
    int             touch[MAX_GENTITIES];
    int             count;
    int             j   = 0;
    ScriptVariable *ref = new ScriptVariable, *array = new ScriptVariable;

    origin = ev->GetVector(1);
    mins   = ev->GetVector(2);
    maxs   = ev->GetVector(3);

    o_min = origin + mins;
    o_max = origin + maxs;

    count = gi.AreaEntities(o_min, o_max, touch, MAX_GENTITIES);

    ref->setRefValue(array);

    for (int i = 0; i < count; i++) {
        Entity *entity = G_GetEntity(touch[i]);

        if (entity == NULL) {
            continue;
        }

        ScriptVariable *index = new ScriptVariable, *value = new ScriptVariable;

        index->setIntValue(j);
        value->setListenerValue(entity);

        ref->setArrayAt(*index, *value);

        j++;
    }

    ev->AddValue(*array);
}

void ScriptThread::GetEntArray(Event *ev)
{
    str name = ev->GetString(1);
    str key  = ev->GetString(2);

    ScriptVariable array;
    ScriptVariable index, value;
    gentity_t     *gentity = globals.gentities;

    Event   *event       = new Event(key, EV_GETTER);
    qboolean useEvent    = event != NULL;
    qboolean createEvent = false;

    if (!event->eventnum) {
        delete event;
        useEvent = false;
    }

    int x = 0;

    for (int i = 0; i < globals.num_entities; i++, gentity++) {
        ScriptVariable returnValue;

        if (!gentity->inuse || gentity->entity == NULL) {
            continue;
        }

        if (createEvent) {
            event       = new Event(key, EV_GETTER);
            createEvent = false;
        }

        Entity *entity = gentity->entity;

        if (useEvent) {
            createEvent = true;
        }

        if (!useEvent) {
            // Now look for variables if predefined keys are not specified

            ScriptVariableList *vars = entity->Vars();

            if (vars == NULL) {
                continue;
            }

            // Get the variable from the key
            ScriptVariable *variable = vars->GetVariable(key);

            if (variable == NULL) {
                continue;
            }

            // Check if it matches with the name
            if (variable->stringValue() == name) {
                continue;
            }
        } else {
            const char *value = NULL;

            returnValue = entity->ProcessEventReturn(event);

            if (!event->NumArgs()) {
                continue;
            }

            value = returnValue.stringValue();

            if (value == NULL) {
                continue;
            }

            if (strcmp(value, name) != 0) {
                continue;
            }
        }

        index.setIntValue(x);
        value.setListenerValue(entity);

        array.setArrayAt(index, value);

        x++;
    }

    ev->AddValue(array);
}

void ScriptThread::EventIHudDraw3d(Event *ev)
{
    Player  *player;
    int      index;
    float   *tmp;
    vec3_t   vector;
    int      ent_num;
    qboolean bAlwaysShow, bDepth;

    player = (Player *)ev->GetEntity(1);

    if (player == NULL) {
        throw ScriptException("Player entity is NULL for ihuddraw_3d!\n");
    }

    index = ev->GetInteger(2);

    if (index < 0 && index > 255) {
        throw ScriptException("Wrong index for ihuddraw_3d!\n");
    }

    tmp = ev->GetVector(3);
    memcpy(&vector, tmp, sizeof(vec3_t));

    ent_num     = ev->GetInteger(4);
    bAlwaysShow = ev->GetInteger(5);
    bDepth      = ev->GetInteger(6);

    iHudDraw3d(player->edict - g_entities, index, vector, ent_num, bAlwaysShow, bDepth);
}

void ScriptThread::EventIHudDrawTimer(Event *ev)
{
    Player *player;
    int     index;
    float   duration;
    float   fade_out_time;

    player = (Player *)ev->GetEntity(1);

    if (player == NULL) {
        throw ScriptException("Player entity is NULL for ihuddraw_timer!\n");
    }

    index = ev->GetInteger(2);

    if (index < 0 && index > 255) {
        throw ScriptException("Wrong index for ihuddraw_timer!\n");
    }

    duration      = ev->GetFloat(3);
    fade_out_time = ev->GetFloat(4);

    iHudDrawTimer(player->edict - g_entities, index, duration, fade_out_time);
}

void ScriptThread::EventIHudDrawShader(Event *ev)
{
    int     numArgs = -1;
    int     index   = -1;
    Entity *player  = NULL;
    str     shadername;

    numArgs = ev->NumArgs();

    if (numArgs != 3) {
        throw ScriptException("Wrong arguments count for ihuddraw_shader!\n");
    }

    player = (Entity *)ev->GetEntity(1);

    if (player == NULL) {
        throw ScriptException("Player entity is NULL for ihuddraw_shader!\n");
    }

    index = ev->GetInteger(2);

    if (index < 0 && index > 255) {
        throw ScriptException("Wrong index for ihuddraw_shader!\n");
    }

    shadername = ev->GetString(3);

    iHudDrawShader(player->edict - g_entities, index, shadername);
}

void ScriptThread::EventIHudDrawAlign(Event *ev)
{
    int     numArgs      = -1;
    int     index        = -1;
    int     h_alignement = -1;
    int     v_alignement = -1;
    Entity *player       = NULL;
    str     h_align;
    str     v_align;

    numArgs = ev->NumArgs();

    if (numArgs != 4) {
        throw ScriptException("Wrong arguments count for ihuddraw_align!\n");
    }

    player = (Entity *)ev->GetEntity(1);

    if (player == NULL) {
        throw ScriptException("Player entity is NULL for ihuddraw_align!\n");
    }

    index = ev->GetInteger(2);

    if (index < 0 && index > 255) {
        throw ScriptException("Wrong index for ihuddraw_align!\n");
    }

    h_align = ev->GetString(3);

    if (!h_align) {
        throw ScriptException("h_align is NULL for ihuddraw_align!\n");
    }

    v_align = ev->GetString(4);

    if (!v_align) {
        throw ScriptException("v_align is NULL for ihuddraw_align!\n");
    }

    if (h_align == "left") {
        h_alignement = 0;
    } else if (h_align == "center") {
        h_alignement = 1;
    } else if (h_align == "right") {
        h_alignement = 2;
    } else {
        throw ScriptException("Wrong alignment h_align string for ihuddraw_align!\n");
    }

    if (v_align == "top") {
        v_alignement = 0;
    } else if (v_align == "center") {
        v_alignement = 1;
    } else if (v_align == "bottom") {
        v_alignement = 2;
    } else {
        throw ScriptException("Wrong alignment v_align string for ihuddraw_align!\n");
    }

    iHudDrawAlign(player->edict - g_entities, index, h_alignement, v_alignement);
}

void ScriptThread::EventIHudDrawRect(Event *ev)
{
    int     numArgs = -1;
    int     index   = -1;
    Entity *player  = NULL;
    int     x       = 0;
    int     y       = 0;
    int     width   = 0;
    int     height  = 0;

    numArgs = ev->NumArgs();

    if (numArgs != 6) {
        throw ScriptException("Wrong arguments count for ihuddraw_rect!\n");
    }

    player = (Entity *)ev->GetEntity(1);

    if (player == NULL) {
        throw ScriptException("Player entity is NULL for ihuddraw_rect!\n");
    }

    index = ev->GetInteger(2);

    if (index < 0 && index > 255) {
        throw ScriptException("Wrong index for ihuddraw_rect!\n");
    }

    x      = ev->GetInteger(3);
    y      = ev->GetInteger(4);
    width  = ev->GetInteger(5);
    height = ev->GetInteger(6);

    iHudDrawRect(player->edict - g_entities, index, x, y, width, height);
}

void ScriptThread::EventIHudDrawVirtualSize(Event *ev)
{
    int     numArgs = -1;
    int     index   = -1;
    Entity *player  = NULL;
    int     virt    = -1;

    numArgs = ev->NumArgs();

    if (numArgs != 3) {
        throw ScriptException("Wrong arguments count for ihuddraw_virtualsize!\n");
    }

    player = (Entity *)ev->GetEntity(1);

    if (player == NULL) {
        throw ScriptException("Player entity is NULL for ihuddraw_virtualsize!\n");
    }

    index = ev->GetInteger(2);

    if (index < 0 && index > 255) {
        throw ScriptException("Wrong index for ihuddraw_virtualsize!\n");
    }

    virt = ev->GetInteger(3);

    if (virt != 0) {
        virt = 1;
    }

    iHudDrawVirtualSize(player->edict - g_entities, index, virt);
}

void ScriptThread::EventIHudDrawColor(Event *ev)
{
    int     numArgs  = -1;
    int     index    = -1;
    Entity *player   = NULL;
    float   color[3] = {0.0f, 0.0f, 0.0f};

    numArgs = ev->NumArgs();

    if (numArgs != 5) {
        throw ScriptException("Wrong arguments count for ihuddraw_color!\n");
    }

    player = (Entity *)ev->GetEntity(1);

    if (player == NULL) {
        throw ScriptException("Player entity is NULL for ihuddraw_color!\n");
    }

    index = ev->GetInteger(2);

    if (index < 0 && index > 255) {
        throw ScriptException("Wrong index for ihuddraw_color!\n");
    }

    color[0] = ev->GetFloat(3); // red
    color[1] = ev->GetFloat(4); // green
    color[2] = ev->GetFloat(5); // blue

    iHudDrawColor(player->edict - g_entities, index, color);
}

void ScriptThread::EventIHudDrawAlpha(Event *ev)
{
    int     numArgs = -1;
    int     index   = -1;
    Entity *player  = NULL;
    float   alpha   = 0.0f;

    numArgs = ev->NumArgs();

    if (numArgs != 3) {
        throw ScriptException("Wrong arguments count for ihuddraw_alpha!\n");
    }

    player = (Entity *)ev->GetEntity(1);

    if (player == NULL) {
        throw ScriptException("Player entity is NULL for ihuddraw_alpha!\n");
    }

    index = ev->GetInteger(2);

    if (index < 0 && index > 255) {
        throw ScriptException("Wrong index for ihuddraw_alpha!\n");
    }

    alpha = ev->GetFloat(3);

    iHudDrawAlpha(player->edict - g_entities, index, alpha);
}

void ScriptThread::EventIHudDrawString(Event *ev)
{
    int     numArgs = -1;
    int     index   = -1;
    Entity *player  = NULL;
    str     string;

    numArgs = ev->NumArgs();

    if (numArgs != 3) {
        throw ScriptException("Wrong arguments count for ihuddraw_string!\n");
    }

    player = (Entity *)ev->GetEntity(1);

    if (player == NULL) {
        throw ScriptException("Player entity is NULL for ihuddraw_string!\n");
    }

    index = ev->GetInteger(2);

    if (index < 0 && index > 255) {
        throw ScriptException("Wrong index for ihuddraw_string!\n");
    }

    string = ev->GetString(3);

    iHudDrawString(player->edict - g_entities, index, string);
}

void ScriptThread::EventIHudDrawFont(Event *ev)
{
    int     index;
    Entity *player = NULL;
    str     fontname;

    if (ev->NumArgs() != 3) {
        throw ScriptException("Wrong arguments count for ihuddraw_font!\n");
    }

    player = (Entity *)ev->GetEntity(1);

    if (player == NULL) {
        throw ScriptException("Player entity is NULL for ihuddraw_font!\n");
    }

    index = ev->GetInteger(2);

    if (index < 0 && index > 255) {
        throw ScriptException("Wrong index for ihuddraw_font!\n");
    }

    fontname = ev->GetString(3);

    iHudDrawFont(player->edict - g_entities, index, fontname);
}

void ScriptThread::EventIsOnGround(Event *ev)
{
    Entity *entity = ev->GetEntity(1);

    ev->AddInteger(entity->groundentity != NULL);
}

void ScriptThread::EventIsOutOfBounds(Event *ev)
{
    Entity *entity  = ev->GetEntity(1);
    int     areanum = gi.AreaForPoint(entity->origin);

    if (areanum == -1) {
        ev->AddInteger(1);
    } else {
        ev->AddInteger(0);
    }
}

void ScriptThread::TeamGetScore(Event *ev)
{
    str      teamname = ev->GetString(1);
    DM_Team *team     = dmManager.GetTeam(teamname);

    if (!team) {
        throw ScriptException("Invalid team \"%s\"", teamname.c_str());
    }

    if (team) {
        ev->AddInteger(team->m_teamwins);
    }
}

void ScriptThread::TeamSetScore(Event *ev)
{
    str      teamname;
    DM_Team *team;
    int      score;
    qboolean bAdd = false;

    teamname = ev->GetString(1);

    team = dmManager.GetTeam(teamname);
    if (!team) {
        throw ScriptException("Invalid team \"%s\"", teamname.c_str());
    }

    score = ev->GetInteger(2);

    if (ev->NumArgs() > 2) {
        bAdd = ev->GetInteger(3);
    }

    if (bAdd) {
        team->m_iKills += score;

        if (g_gametype->integer < GT_TEAM_ROUNDS) {
            team->m_teamwins += score;
        }
    } else {
        team->m_iKills = score;

        if (g_gametype->integer < GT_TEAM_ROUNDS) {
            team->m_teamwins = score;
        }
    }
}

void ScriptThread::CharToInt(Event *ev)
{
    str c = ev->GetString(1);

    ev->AddInteger(c[0]);
}

void ScriptThread::Conprintf(Event *ev)
{
    gi.Printf("%s", ev->GetString(1).c_str());
}

void ScriptThread::FileOpen(Event *ev)
{
    int   numArgs = -1;
    str   filename;
    str   accesstype;
    FILE *f       = NULL;
    char  buf[16] = {0};

    numArgs = ev->NumArgs();

    if (numArgs != 2) {
        throw ScriptException("Wrong arguments count for fopen!\n");
    }

    if (scriptfiles->integer == 32) {
        throw ScriptException(
            "Maximum count (32) of opened files is reached. Close at least one of them, to open new file - fopen!\n"
        );
    }

    filename = ev->GetString(1);

    accesstype = ev->GetString(2);

    f = fopen(filename, accesstype);

    if (!f) {
        ev->AddNil();
        return;
    }

    ev->AddListener(new OSFile(f));

    Com_sprintf(buf, sizeof(buf), "%i", scriptfiles->integer + 1);
    gi.cvar_set("sv_scriptfiles", buf);
}

void ScriptThread::FileWrite(Event *ev) {}

void ScriptThread::FileRead(Event *ev) {}

void ScriptThread::FileClose(Event *ev)
{
    OSFile *osFile  = NULL;
    int     numArgs = 0;
    FILE   *f       = NULL;
    char    buf[16] = {0};

    numArgs = ev->NumArgs();

    if (numArgs != 1) {
        throw ScriptException("Wrong arguments count for fclose!\n");
    }

    osFile = (OSFile *)ev->GetListener(1);
    if (!osFile || !osFile->isSubclassOf(OSFile)) {
        throw ScriptException("Not a file!\n");
    }

    f = (FILE *)osFile->getOSFile();

    if (f == NULL) {
        throw ScriptException("File handle is NULL for fclose!\n");
    }

    // delete the file object
    delete osFile;

    Com_sprintf(buf, sizeof(buf), "%i", scriptfiles->integer - 1);
    gi.cvar_set("sv_scriptfiles", buf);
}

void ScriptThread::FileEof(Event *ev)
{
    OSFile *osFile  = NULL;
    int     numArgs = 0;
    int     ret     = 0;
    FILE   *f       = NULL;

    numArgs = ev->NumArgs();

    if (numArgs != 1) {
        throw ScriptException("Wrong arguments count for feof!\n");
    }

    osFile = (OSFile *)ev->GetListener(1);
    if (!osFile || !osFile->isSubclassOf(OSFile)) {
        throw ScriptException("Not a file!\n");
    }

    f = (FILE *)osFile->getOSFile();

    ret = feof(f);

    ev->AddInteger(ret);
}

void ScriptThread::FileSeek(Event *ev)
{
    OSFile  *osFile  = NULL;
    int      numArgs = 0;
    int      pos     = 0;
    long int offset  = 0;
    int      ret     = 0;
    FILE    *f       = NULL;

    numArgs = ev->NumArgs();

    if (numArgs != 3) {
        throw ScriptException("Wrong arguments count for fseek!\n");
    }

    osFile = (OSFile *)ev->GetListener(1);
    if (!osFile || !osFile->isSubclassOf(OSFile)) {
        throw ScriptException("Not a file!\n");
    }

    f = (FILE *)osFile->getOSFile();

    offset = ev->GetInteger(2);

    if (offset < 0) {
        throw ScriptException("Wrong file offset! Should be starting from 0. - fseek\n");
    }

    pos = ev->GetInteger(3);

    if (pos != 0 && pos != 1 && pos != 2) {
        throw ScriptException("Wrong file offset start! Should be between 0 - 2! - fseek\n");
    }

    ret = fseek(f, offset, pos);

    ev->AddInteger(ret);
}

void ScriptThread::FileTell(Event *ev)
{
    OSFile  *osFile  = NULL;
    int      numArgs = 0;
    long int ret     = 0;
    FILE    *f       = NULL;

    numArgs = ev->NumArgs();

    if (numArgs != 1) {
        throw ScriptException("Wrong arguments count for ftell!\n");
    }

    osFile = (OSFile *)ev->GetListener(1);
    if (!osFile || !osFile->isSubclassOf(OSFile)) {
        throw ScriptException("Not a file!\n");
    }

    f = (FILE *)osFile->getOSFile();

    ret = ftell(f);

    ev->AddInteger(ret);
}

void ScriptThread::FileRewind(Event *ev)
{
    OSFile  *osFile  = NULL;
    int      numArgs = 0;
    long int ret     = 0;
    FILE    *f       = NULL;

    numArgs = ev->NumArgs();

    if (numArgs != 1) {
        throw ScriptException("Wrong arguments count for frewind!\n");
    }

    osFile = (OSFile *)ev->GetListener(1);
    if (!osFile || !osFile->isSubclassOf(OSFile)) {
        throw ScriptException("Not a file!\n");
    }

    f = (FILE *)osFile->getOSFile();

    rewind(f);
}

void ScriptThread::FilePutc(Event *ev)
{
    OSFile *osFile  = NULL;
    int     numArgs = 0;
    int     ret     = 0;
    FILE   *f       = NULL;
    int     c       = 0;

    numArgs = ev->NumArgs();

    if (numArgs != 2) {
        throw ScriptException("Wrong arguments count for fputc!\n");
    }

    osFile = (OSFile *)ev->GetListener(1);
    if (!osFile || !osFile->isSubclassOf(OSFile)) {
        throw ScriptException("Not a file!\n");
    }

    f = (FILE *)osFile->getOSFile();

    c = ev->GetInteger(2);

    ret = fputc((char)c, f);

    ev->AddInteger(ret);
}

void ScriptThread::FilePuts(Event *ev)
{
    OSFile *osFile  = NULL;
    int     numArgs = 0;
    int     ret     = 0;
    FILE   *f       = NULL;
    str     c;

    numArgs = ev->NumArgs();

    if (numArgs != 2) {
        throw ScriptException("Wrong arguments count for fputs!\n");
    }

    osFile = (OSFile *)ev->GetListener(1);
    if (!osFile || !osFile->isSubclassOf(OSFile)) {
        throw ScriptException("Not a file!\n");
    }

    f = (FILE *)osFile->getOSFile();

    c = ev->GetString(2);
    //gi.Printf("Putting line into a file\n");
    ret = fputs(c, f);
    //gi.Printf("Ret val: %i\n", ret);
    ev->AddInteger(ret);
}

void ScriptThread::FileGetc(Event *ev)
{
    OSFile *osFile  = NULL;
    int     numArgs = 0;
    int     ret     = 0;
    FILE   *f       = NULL;

    numArgs = ev->NumArgs();

    if (numArgs != 1) {
        throw ScriptException("Wrong arguments count for fgetc!\n");
    }

    osFile = (OSFile *)ev->GetListener(1);
    if (!osFile || !osFile->isSubclassOf(OSFile)) {
        throw ScriptException("Not a file!\n");
    }

    f = (FILE *)osFile->getOSFile();

    ret = fgetc(f);

    ev->AddInteger(ret);
}

void ScriptThread::FileGets(Event *ev)
{
    OSFile *osFile   = NULL;
    int     numArgs  = 0;
    int     maxCount = 0;
    FILE   *f        = NULL;
    char   *c        = NULL;
    char   *buff     = NULL;

    numArgs = ev->NumArgs();

    if (numArgs != 2) {
        throw ScriptException("Wrong arguments count for fgets!\n");
    }

    osFile = (OSFile *)ev->GetListener(1);
    if (!osFile || !osFile->isSubclassOf(OSFile)) {
        throw ScriptException("Not a file!\n");
    }

    f = (FILE *)osFile->getOSFile();

    maxCount = ev->GetInteger(2);

    if (maxCount <= 0) {
        throw ScriptException("Maximum buffer size should be higher than 0! - fgets\n");
    }

    buff = (char *)gi.Malloc(maxCount + 1);

    if (buff == NULL) {
        throw ScriptException(
            "Failed to allocate memory during fputs scriptCommand text buffer initialization! Try setting maximum "
            "buffer length lower.\n"
        );
        ev->AddInteger(-1);
    }

    memset(buff, 0, maxCount + 1);

    c = fgets(buff, maxCount, f);

    if (c == NULL) {
        ev->AddString("");
    } else {
        ev->AddString(c);
    }

    gi.Free(buff);
}

void ScriptThread::FileError(Event *ev)
{
    OSFile *osFile  = NULL;
    int     numArgs = 0;
    int     ret     = 0;
    FILE   *f       = NULL;

    numArgs = ev->NumArgs();

    if (numArgs != 1) {
        throw ScriptException("Wrong arguments count for ferror!\n");
    }

    osFile = (OSFile *)ev->GetListener(1);
    if (!osFile || !osFile->isSubclassOf(OSFile)) {
        throw ScriptException("Not a file!\n");
    }

    f = (FILE *)osFile->getOSFile();

    ret = ferror(f);

    ev->AddInteger(ret);
}

void ScriptThread::FileFlush(Event *ev)
{
    OSFile *osFile  = NULL;
    int     numArgs = 0;
    int     ret     = 0;
    FILE   *f       = NULL;

    numArgs = ev->NumArgs();

    if (numArgs != 1) {
        throw ScriptException("Wrong arguments count for fflush!\n");
    }

    osFile = (OSFile *)ev->GetListener(1);
    if (!osFile || !osFile->isSubclassOf(OSFile)) {
        throw ScriptException("Not a file!\n");
    }

    f = (FILE *)osFile->getOSFile();

    ret = fflush(f);

    ev->AddInteger(ret);
}

void ScriptThread::FileExists(Event *ev)
{
    int   numArgs = 0;
    FILE *f       = 0;
    str   filename;

    numArgs = ev->NumArgs();

    if (numArgs != 1) {
        throw ScriptException("Wrong arguments count for fexists!\n");
    }

    filename = ev->GetString(1);

    if (filename == NULL) {
        throw ScriptException("Empty file name passed to fexists!\n");
    }

    f = fopen(filename, "r");
    if (f) {
        fclose(f);
        ev->AddInteger(1);
    } else {
        ev->AddInteger(0);
    }
}

void ScriptThread::FileReadAll(Event *ev)
{
    OSFile *osFile     = NULL;
    int     numArgs    = 0;
    FILE   *f          = NULL;
    char   *ret        = NULL;
    long    currentPos = 0;
    size_t  size       = 0;
    size_t  sizeRead   = 0;

    numArgs = ev->NumArgs();

    if (numArgs != 1) {
        throw ScriptException("Wrong arguments count for freadall!\n");
    }

    osFile = (OSFile *)ev->GetListener(1);
    if (!osFile || !osFile->isSubclassOf(OSFile)) {
        throw ScriptException("Not a file!\n");
    }

    f = (FILE *)osFile->getOSFile();

    currentPos = ftell(f);
    fseek(f, 0, SEEK_END);
    size = ftell(f);
    fseek(f, currentPos, SEEK_SET);

    ret = (char *)gi.Malloc(sizeof(char) * size + 1);

    if (ret == NULL) {
        ev->AddInteger(-1);
        throw ScriptException("Error while allocating memory buffer for file content - freadall!\n");
    }

    sizeRead      = fread(ret, 1, size, f);
    ret[sizeRead] = '\0';

    ev->AddString(ret);

    gi.Free(ret);
}

void ScriptThread::FileSaveAll(Event *ev)
{
    OSFile *osFile    = NULL;
    int     numArgs   = 0;
    FILE   *f         = NULL;
    size_t  sizeWrite = 0;
    str     text;

    numArgs = ev->NumArgs();

    if (numArgs != 2) {
        throw ScriptException("Wrong arguments count for fsaveall!\n");
    }

    osFile = (OSFile *)ev->GetListener(1);
    if (!osFile || !osFile->isSubclassOf(OSFile)) {
        throw ScriptException("Not a file!\n");
    }

    f = (FILE *)osFile->getOSFile();

    text = ev->GetString(2);

    if (text == NULL) {
        ev->AddInteger(-1);
        throw ScriptException("Text to be written is NULL - fsaveall!\n");
    }

    sizeWrite = fwrite(text, 1, strlen(text), f);

    ev->AddInteger((int)sizeWrite);
}

void ScriptThread::FileRemove(Event *ev)
{
    int numArgs = 0;
    int ret     = 0;
    str filename;

    numArgs = ev->NumArgs();

    if (numArgs != 1) {
        throw ScriptException("Wrong arguments count for fremove!\n");
    }

    filename = ev->GetString(1);

    if (filename == NULL) {
        throw ScriptException("Empty file name passed to fremove!\n");
    }

    ret = remove(filename);

    ev->AddInteger(ret);
}

void ScriptThread::FileRename(Event *ev)
{
    int numArgs = 0;
    int ret     = 0;
    str oldfilename, newfilename;

    numArgs = ev->NumArgs();

    if (numArgs != 2) {
        throw ScriptException("Wrong arguments count for frename!\n");
    }

    oldfilename = ev->GetString(1);
    newfilename = ev->GetString(2);

    if (!oldfilename) {
        throw ScriptException("Empty old file name passed to frename!\n");
    }

    if (!newfilename) {
        throw ScriptException("Empty new file name passed to frename!\n");
    }

    ret = rename(oldfilename, newfilename);

    ev->AddInteger(ret);
}

void ScriptThread::FileCopy(Event *ev)
{
    size_t       n       = 0;
    int          numArgs = 0;
    unsigned int ret     = 0;
    str          filename, copyfilename;
    FILE        *f = NULL, *fCopy = NULL;
    char         buffer[4096];

    numArgs = ev->NumArgs();

    if (numArgs != 2) {
        throw ScriptException("Wrong arguments count for fcopy!\n");
    }

    filename     = ev->GetString(1);
    copyfilename = ev->GetString(2);

    if (!filename) {
        throw ScriptException("Empty file name passed to fcopy!\n");
    }

    if (copyfilename) {
        throw ScriptException("Empty copy file name passed to fcopy!\n");
    }

    f = fopen(filename, "rb");

    if (f == NULL) {
        ev->AddInteger(-1);
        throw ScriptException("Could not open \"%s\" for copying - fcopy!\n", filename.c_str());
    }

    fCopy = fopen(copyfilename, "wb");

    if (fCopy == NULL) {
        fclose(f);
        ev->AddInteger(-2);
        throw ScriptException("Could not open \"%s\" for copying - fcopy!\n", copyfilename.c_str());
    }

    while ((n = fread(buffer, sizeof(char), sizeof(buffer), f)) > 0) {
        if (fwrite(buffer, sizeof(char), n, fCopy) != n) {
            fclose(f);
            fflush(fCopy);
            fclose(fCopy);
            ev->AddInteger(-3);
            throw ScriptException("There was an error while copying files - fcopy!\n");
        }
    }

    fclose(f);
    fflush(fCopy);
    fclose(fCopy);

    ev->AddInteger(0);
}

void ScriptThread::FileReadPak(Event *ev)
{
    str   filename;
    char *content = NULL;
    int   numArgs = 0;
    int   ret     = 0;

    numArgs = ev->NumArgs();

    if (numArgs != 1) {
        throw ScriptException("Wrong arguments count for freadpak!\n");
    }

    filename = ev->GetString(1);

    if (filename == NULL) {
        throw ScriptException("Filename is NULL - freadpak!\n");
    }

    ret = gi.FS_ReadFile(filename, (void **)&content, qtrue);

    if (content == NULL) {
        ev->AddInteger(-1);
        throw ScriptException("Error while reading pak file content - freadpak!\n");
    }

    ev->AddString(content);
}

void ScriptThread::FileList(Event *ev)
{
    int             i = 0, numArgs = 0;
    const char     *path = NULL;
    str             extension;
    int             wantSubs = 0;
    int             numFiles = 0;
    char          **list     = NULL;
    ScriptVariable *ref      = new ScriptVariable;
    ScriptVariable *array    = new ScriptVariable;

    numArgs = ev->NumArgs();

    if (numArgs != 3) {
        throw ScriptException("Wrong arguments count for flist!\n");
    }

    path      = ev->GetString(1);
    extension = ev->GetString(2);
    wantSubs  = ev->GetInteger(3);

    list = gi.FS_ListFiles(path, extension, wantSubs, &numFiles);

    if (numFiles == 0) {
        gi.FS_FreeFileList(list);
        return;
    }

    ref->setRefValue(array);

    for (i = 0; i < numFiles; i++) {
        ScriptVariable *indexes = new ScriptVariable;
        ScriptVariable *values  = new ScriptVariable;

        indexes->setIntValue(i);
        values->setStringValue(list[i]);

        ref->setArrayAt(*indexes, *values);
    }

    gi.FS_FreeFileList(list);

    ev->AddValue(*array);

    return;
}

void ScriptThread::FileNewDirectory(Event *ev)
{
    str path;
    int numArgs = 0;
    int ret     = 0;

    numArgs = ev->NumArgs();

    if (numArgs != 1) {
        throw ScriptException("Wrong arguments count for fnewdir!\n");
    }

    path = ev->GetString(1);

    if (path == NULL) {
        throw ScriptException("Path is NULL - fnewdir!\n");
    }

#ifdef WIN32
    ret = _mkdir(path);
#else
    ret = mkdir(path, 0777);
#endif

    ev->AddInteger(ret);
    return;
}

void ScriptThread::FileRemoveDirectory(Event *ev)
{
    str path;
    int numArgs = 0;
    int ret     = 0;

    numArgs = ev->NumArgs();

    if (numArgs != 1) {
        throw ScriptException("Wrong arguments count for fremovedir!\n");
    }

    path = ev->GetString(1);

    if (path == NULL) {
        throw ScriptException("Path is NULL - fremovedir!\n");
    }

#ifdef WIN32
    ret = _rmdir(path);
#else
    ret = rmdir(path);
#endif

    ev->AddInteger(ret);
    return;
}

void ScriptThread::GetArrayKeys(Event *ev)
{
    Entity         *ent = NULL;
    ScriptVariable  array;
    ScriptVariable *value;
    int             i = 0;
    int             arraysize;

    /* Retrieve the array */
    array = ev->GetValue(1);

    /* Cast the array */
    array.CastConstArrayValue();
    arraysize = array.arraysize();

    if (arraysize < 1) {
        return;
    }

    ScriptVariable *ref = new ScriptVariable, *newArray = new ScriptVariable;

    ref->setRefValue(newArray);

    for (int i = 1; i <= arraysize; i++) {
        value = array[i];

        /* Get the array's name */
        //str name = value->getName();

        gi.Printf("name = %s\n", value->GetTypeName());

        ScriptVariable *newIndex = new ScriptVariable, *newValue = new ScriptVariable;

        newIndex->setIntValue(i);
        newValue->setStringValue("NIL");

        //name.removeRef();

        ref->setArrayAt(*newIndex, *newValue);
    }

    ev->AddValue(*newArray);
}

void ScriptThread::GetArrayValues(Event *ev)
{
    Entity         *ent = NULL;
    ScriptVariable  array;
    ScriptVariable *value;
    int             i = 0;
    int             arraysize;

    /* Retrieve the array */
    array = ev->GetValue(1);

    if (array.GetType() == VARIABLE_NONE) {
        return;
    }

    /* Cast the array */
    array.CastConstArrayValue();
    arraysize = array.arraysize();

    if (arraysize < 1) {
        return;
    }

    ScriptVariable *ref = new ScriptVariable, *newArray = new ScriptVariable;

    ref->setRefValue(newArray);

    for (int i = 1; i <= arraysize; i++) {
        value = array[i];

        ScriptVariable *newIndex = new ScriptVariable;

        newIndex->setIntValue(i - 1);

        ref->setArrayAt(*newIndex, *value);
    }

    ev->AddValue(*newArray);
}

void ScriptThread::GetDate(Event *ev)
{
    char       buff[1024];
    time_t     rawtime;
    struct tm *timeinfo;

    time(&rawtime);
    timeinfo = localtime(&rawtime);

    strftime(buff, 64, "%d.%m.%Y %r", timeinfo);

    ev->AddString(buff);
}

void ScriptThread::GetTimeZone(Event *ev)
{
    int gmttime;
    int local;

    time_t     rawtime;
    struct tm *timeinfo, *ptm;

    int timediff;
    int tmp;

    tmp = ev->GetInteger(1);

    time(&rawtime);
    timeinfo = localtime(&rawtime);

    local = timeinfo->tm_hour;

    ptm = gmtime(&rawtime);

    gmttime = ptm->tm_hour;

    timediff = local - gmttime;

    ev->AddInteger(timediff);
}

// IMPORTANT NOTE:
// SLRE is buggy, consider switch to Boost.Regex or .xpressive

void ScriptThread::PregMatch(Event *ev)
{
    slre_cap       sl_cap[32];
    int            i, j;
    size_t         iMaxLength;
    size_t         iLength;
    size_t         iFoundLength = 0;
    str            pattern, subject;
    ScriptVariable index, value, subindex, subvalue;
    ScriptVariable array, subarray;

    memset(sl_cap, 0, sizeof(sl_cap));

    pattern = ev->GetString(1);
    subject = ev->GetString(2);

    iMaxLength = strlen(subject);
    iLength    = 0;
    i          = 0;

    while (
        iLength < iMaxLength
        && (iFoundLength = slre_match(
                pattern, subject.c_str() + iLength, iMaxLength - iLength, sl_cap, sizeof(sl_cap) / sizeof(sl_cap[0]), 0
            )) > 0
    ) {
        subarray.Clear();

        for (j = 0; sl_cap[j].ptr != NULL; j++) {
            char *buffer;

            buffer                = (char *)gi.Malloc(sl_cap[j].len + 1);
            buffer[sl_cap[j].len] = 0;
            strncpy(buffer, sl_cap[j].ptr, sl_cap[j].len);

            subindex.setIntValue(j);
            subvalue.setStringValue(buffer);
            subarray.setArrayAtRef(subindex, subvalue);

            gi.Free(buffer);

            iLength += sl_cap[j].ptr - subject.c_str();
        }

        index.setIntValue(i);
        array.setArrayAtRef(index, subarray);

        i++;
    }

    ev->AddValue(array);
}

void ScriptThread::EventIsArray(Event *ev)
{
    ScriptVariable *value = &ev->GetValue(1);

    if (value == NULL) {
        return ev->AddInteger(0);
    }

    ev->AddInteger(
        value->type == VARIABLE_ARRAY || value->type == VARIABLE_CONSTARRAY || value->type == VARIABLE_SAFECONTAINER
    );
}

void ScriptThread::EventIsDefined(Event *ev)
{
    ev->AddInteger(!ev->IsNilAt(1));
}

void ScriptThread::FlagClear(Event *ev)
{
    str   name;
    Flag *flag;

    name = ev->GetString(1);

    flag = flags.FindFlag(name);

    if (flag == NULL) {
        throw ScriptException("Invalid flag '%s'\n", name.c_str());
    }

    delete flag;
}

void ScriptThread::FlagInit(Event *ev)
{
    str   name;
    Flag *flag;

    name = ev->GetString(1);

    flag = flags.FindFlag(name);

    if (flag != NULL) {
        flag->Reset();
        return;
    }

    flag            = new Flag;
    flag->bSignaled = false;
    Q_strncpyz(flag->flagName, name, sizeof(flag->flagName));
}

void ScriptThread::FlagSet(Event *ev)
{
    str   name;
    Flag *flag;

    name = ev->GetString(1);

    flag = flags.FindFlag(name);

    if (flag == NULL) {
        throw ScriptException("Invalid flag '%s'.\n", name.c_str());
    }

    flag->Set();
}

void ScriptThread::FlagWait(Event *ev)
{
    str   name;
    Flag *flag;

    name = ev->GetString(1);

    flag = flags.FindFlag(name);

    if (flag == NULL) {
        throw ScriptException("Invalid flag '%s'.\n", name.c_str());
    }

    flag->Wait(this);
}

void ScriptThread::EventATan2(Event *ev)
{
    int    numArgs = 0;
    double x = 0.0f, y = 0.0f, res = 0.0f;

    numArgs = ev->NumArgs();

    if (numArgs != 2) {
        throw ScriptException("Wrong arguments count for atan2!\n");
    }

    y = ev->GetFloat(1);
    x = ev->GetFloat(2);

    res = atan2(y, x);

    ev->AddFloat((float)res);
}

void ScriptThread::EventCosH(Event *ev)
{
    int    numArgs = 0;
    double x = 0.0f, res = 0.0f;

    numArgs = ev->NumArgs();

    if (numArgs != 1) {
        throw ScriptException("Wrong arguments count for cosh!\n");
    }

    x   = ev->GetFloat(1);
    res = cosh(x);

    ev->AddFloat((float)res);
}

void ScriptThread::EventSinH(Event *ev)
{
    int    numArgs = 0;
    double x = 0.0f, res = 0.0f;

    numArgs = ev->NumArgs();

    if (numArgs != 1) {
        throw ScriptException("Wrong arguments count for sinh!\n");
    }

    x   = ev->GetFloat(1);
    res = sinh(x);

    ev->AddFloat((float)res);
}

void ScriptThread::EventTanH(Event *ev)
{
    int    numArgs = 0;
    double x = 0.0f, res = 0.0f;

    numArgs = ev->NumArgs();

    if (numArgs != 1) {
        throw ScriptException("Wrong arguments count for tanh!\n");
    }

    x   = ev->GetFloat(1);
    res = tanh(x);

    ev->AddFloat((float)res);
}

void ScriptThread::EventExp(Event *ev)
{
    int    numArgs = 0;
    double x = 0.0f, res = 0.0f;

    numArgs = ev->NumArgs();

    if (numArgs != 1) {
        throw ScriptException("Wrong arguments count for exp!\n");
    }

    x   = ev->GetFloat(1);
    res = exp(x);

    ev->AddFloat((float)res);
}

void ScriptThread::EventFrexp(Event *ev)
{
    int             numArgs = 0;
    double          x = 0.0f, res = 0.0f;
    int             exp              = 0;
    ScriptVariable *ref              = new ScriptVariable;
    ScriptVariable *array            = new ScriptVariable;
    ScriptVariable *SignificandIndex = new ScriptVariable;
    ScriptVariable *ExponentIndex    = new ScriptVariable;
    ScriptVariable *SignificandVal   = new ScriptVariable;
    ScriptVariable *ExponentVal      = new ScriptVariable;

    numArgs = ev->NumArgs();

    if (numArgs != 1) {
        throw ScriptException("Wrong arguments count for frexp!\n");
    }

    x   = ev->GetFloat(1);
    res = frexp(x, &exp);

    ref->setRefValue(array);

    SignificandIndex->setStringValue("significand");
    ExponentIndex->setStringValue("exponent");

    SignificandVal->setFloatValue((float)res);
    ExponentVal->setIntValue(exp);

    ref->setArrayAt(*SignificandIndex, *SignificandVal);
    ref->setArrayAt(*ExponentIndex, *ExponentVal);

    ev->AddValue(*array);
}

void ScriptThread::EventLdexp(Event *ev)
{
    int    numArgs = 0;
    double x = 0.0f, res = 0.0f;
    int    exp = 0;

    numArgs = ev->NumArgs();

    if (numArgs != 2) {
        throw ScriptException("Wrong arguments count for ldexp!\n");
    }

    x   = ev->GetFloat(1);
    exp = ev->GetInteger(2);

    res = ldexp(x, exp);

    ev->AddFloat((float)res);
}

void ScriptThread::EventLog(Event *ev)
{
    int    numArgs = 0;
    double x = 0.0f, res = 0.0f;

    numArgs = ev->NumArgs();

    if (numArgs != 1) {
        throw ScriptException("Wrong arguments count for log!\n");
    }

    x   = ev->GetFloat(1);
    res = log(x);

    ev->AddFloat((float)res);
}

void ScriptThread::EventLog10(Event *ev)
{
    int    numArgs = 0;
    double x = 0.0f, res = 0.0f;

    numArgs = ev->NumArgs();

    if (numArgs != 1) {
        throw ScriptException("Wrong arguments count for log10!\n");
    }

    x   = ev->GetFloat(1);
    res = log10(x);

    ev->AddFloat((float)res);
}

void ScriptThread::EventModf(Event *ev)
{
    int    numArgs = 0;
    double x = 0.0f, res = 0.0f;
    double intpart = 0;
    //char varIntpartIndex[16] = { 0 }, varFractionalIndex[16] = { 0 }, varIntpartVal[16] = { 0 }, varFractionalVal[16] = { 0 }, varArray[16] = { 0 }, varRef[16] = { 0 };
    ScriptVariable *array           = new ScriptVariable;
    ScriptVariable *ref             = new ScriptVariable;
    ScriptVariable *IntpartIndex    = new ScriptVariable;
    ScriptVariable *FractionalIndex = new ScriptVariable;
    ScriptVariable *FractionalVal   = new ScriptVariable;
    ScriptVariable *IntpartVal      = new ScriptVariable;

    numArgs = ev->NumArgs();

    if (numArgs != 1) {
        throw ScriptException("Wrong arguments count for modf!\n");
    }

    x   = ev->GetFloat(1);
    res = modf(x, &intpart);

    ref->setRefValue(array);

    IntpartIndex->setStringValue("intpart");
    FractionalIndex->setStringValue("fractional");
    FractionalVal->setFloatValue((float)res);
    IntpartVal->setFloatValue((float)intpart);

    ref->setArrayAt(*IntpartIndex, *IntpartVal);
    ref->setArrayAt(*FractionalIndex, *FractionalVal);

    ev->AddValue(*array);
}

void ScriptThread::EventPow(Event *ev)
{
    int    numArgs = 0;
    double base = 0.0f, res = 0.0f;
    int    exponent = 0;

    numArgs = ev->NumArgs();

    if (numArgs != 2) {
        throw ScriptException("Wrong arguments count for pow!\n");
    }

    base     = ev->GetFloat(1);
    exponent = ev->GetInteger(2);
    res      = pow(base, exponent);

    ev->AddFloat((float)res);
}

void ScriptThread::EventACos(Event *ev)
{
    int    numArgs = 0;
    double x = 0.0f, res = 0.0f;

    numArgs = ev->NumArgs();

    if (numArgs != 1) {
        throw ScriptException("Wrong arguments count for acos!\n");
    }

    x   = ev->GetFloat(1);
    res = acos(x);

    ev->AddFloat((float)res);
}

void ScriptThread::EventASin(Event *ev)
{
    int    numArgs = 0;
    double x = 0.0f, res = 0.0f;

    numArgs = ev->NumArgs();

    if (numArgs != 1) {
        throw ScriptException("Wrong arguments count for asin!\n");
    }

    x   = ev->GetFloat(1);
    res = asin(x);

    ev->AddFloat((float)res);
}

void ScriptThread::EventCeil(Event *ev)
{
    int    numArgs = 0;
    double x = 0.0f, res = 0.0f;

    numArgs = ev->NumArgs();

    if (numArgs != 1) {
        throw ScriptException("Wrong arguments count for ceil!\n");
    }

    x   = ev->GetFloat(1);
    res = ceil(x);

    ev->AddFloat((float)res);
}

void ScriptThread::EventFloor(Event *ev)
{
    int    numArgs = 0;
    double x = 0.0f, res = 0.0f;

    numArgs = ev->NumArgs();

    if (numArgs != 1) {
        throw ScriptException("Wrong arguments count for floor!\n");
    }

    x   = ev->GetFloat(1);
    res = floor(x);

    ev->AddFloat((float)res);
}

void ScriptThread::EventFmod(Event *ev)
{
    int    numArgs   = 0;
    double numerator = 0.0f, denominator = 0.0f, res = 0.0f;

    numArgs = ev->NumArgs();

    if (numArgs != 2) {
        throw ScriptException("Wrong arguments count for fmod!\n");
    }

    numerator   = ev->GetFloat(1);
    denominator = ev->GetFloat(2);
    res         = fmod(numerator, denominator);

    ev->AddFloat((float)res);
}

int checkMD5(const char *filepath, char *md5Hash, size_t destSize)
{
    md5_state_t state;
    md5_byte_t  digest[16];
    int         di;

    FILE  *f         = NULL;
    char  *buff      = NULL;
    size_t filesize  = 0;
    size_t bytesread = 0;

    f = fopen(filepath, "rb");

    if (f == NULL) {
        return -1;
    }

    fseek(f, 0, SEEK_END);
    filesize = ftell(f);
    rewind(f);

    //gi.Printf("Size: %i\n", filesize);

    buff = (char *)gi.Malloc(filesize + 1);

    if (buff == NULL) {
        fclose(f);
        Com_Printf("error0\n");
        return -2;
    }

    buff[filesize] = '\0';

    bytesread = fread(buff, 1, filesize, f);

    if (bytesread < filesize) {
        gi.Free(buff);
        fclose(f);
        Com_Printf("error1: %zi\n", bytesread);
        return -3;
    }

    fclose(f);

    md5_init(&state);
    md5_append(&state, (const md5_byte_t *)buff, filesize);
    md5_finish(&state, digest);

    for (di = 0; di < 16; ++di) {
        Com_sprintf(md5Hash + di * 2, destSize - di * 2, "%02x", digest[di]);
    }

    gi.Free(buff);

    return 0;
}

int checkMD5String(const char *string, char *md5Hash, size_t destSize)
{
    md5_state_t state;
    md5_byte_t  digest[16];
    int         di;

    char  *buff      = NULL;
    size_t stringlen = 0;

    stringlen = strlen(string);

    buff = (char *)gi.Malloc(stringlen + 1);

    if (buff == NULL) {
        return -1;
    }

    buff[stringlen] = '\0';
    memcpy(buff, string, stringlen);

    md5_init(&state);
    md5_append(&state, (const md5_byte_t *)buff, stringlen);
    md5_finish(&state, digest);

    for (di = 0; di < 16; ++di) {
        Com_sprintf(md5Hash + di * 2, destSize - di * 2, "%02x", digest[di]);
    }

    gi.Free(buff);

    return 0;
}

void ScriptThread::Md5File(Event *ev)
{
    char hash[64];
    str  filename;
    int  ret = 0;

    if (ev->NumArgs() != 1) {
        throw ScriptException("Wrong arguments count for md5file!\n");
    }

    filename = ev->GetString(1);

    ret = checkMD5(filename, hash, sizeof(hash));
    if (ret != 0) {
        ev->AddInteger(-1);
        throw ScriptException("Error while generating MD5 checksum for file - md5file!\n");
    }

    ev->AddString(hash);
}

void ScriptThread::StringBytesCopy(Event *ev)
{
    int   bytes  = ev->GetInteger(1);
    str   source = ev->GetString(2);
    char *buffer;

    buffer = (char *)gi.Malloc(bytes + 1);

    strncpy(buffer, source, bytes);
    buffer[bytes] = 0;

    ev->AddString(buffer);

    gi.Free(buffer);
}

void ScriptThread::Md5String(Event *ev)
{
    char hash[64];
    str  text;
    int  ret = 0;

    if (ev->NumArgs() != 1) {
        throw ScriptException("Wrong arguments count for md5string!\n");
    }

    text = ev->GetString(1);

    ret = checkMD5String(text, hash, sizeof(hash));
    if (ret != 0) {
        ev->AddInteger(-1);
        throw ScriptException("Error while generating MD5 checksum for strin!\n");
    }

    ev->AddString(hash);
}

scriptedEvType_t EventNameToType(const char *eventname, char *fullname)
{
    scriptedEvType_t evType;

    const char *eventname_full;

    if (strcmp(eventname, "connected") == 0) {
        eventname_full = "ConnectedEvent";
        evType         = SE_CONNECTED;
    } else if (strcmp(eventname, "disconnected") == 0) {
        eventname_full = "DisconnectedEvent";
        evType         = SE_DISCONNECTED;
    } else if (strcmp(eventname, "spawn") == 0) {
        eventname_full = "SpawnEvent";
        evType         = SE_SPAWN;
    } else if (strcmp(eventname, "damage") == 0) {
        eventname_full = "DamageEvent";
        evType         = SE_DAMAGE;
    } else if (strcmp(eventname, "kill") == 0) {
        eventname_full = "KillEvent";
        evType         = SE_KILL;
    } else if (strcmp(eventname, "keypress") == 0) {
        eventname_full = "KeypressEvent";
        evType         = SE_KEYPRESS;
    } else if (strcmp(eventname, "intermission") == 0) {
        eventname_full = "IntermissionEvent";
        evType         = SE_INTERMISSION;
    } else if (strcmp(eventname, "servercommand") == 0) {
        eventname_full = "ServerCommandEvent";
        evType         = SE_SERVERCOMMAND;
    } else if (strcmp(eventname, "changeteam") == 0) {
        eventname_full = "ChangeTeamEvent";
        evType         = SE_CHANGETEAM;
    } else {
        return SE_DEFAULT;
    }

    if (fullname != NULL) {
        strcpy(fullname, eventname_full);
    }

    return evType;
}

void ScriptThread::RegisterEvent(Event *ev)
{
    str              eventname;
    char             eventname_full[64];
    scriptedEvType_t evType;

    eventname = ev->GetString(1);

    evType = EventNameToType(eventname, eventname_full);

    if (evType == SE_DEFAULT) {
        ev->AddInteger(0);
        throw ScriptException("Wrong event type name for registerev!\n");
    }

    if (scriptedEvents[evType].IsRegistered()) {
        ev->AddInteger(1);
        throw ScriptException("Scripted event '%s' is already registered\n", eventname.c_str());
    }

    scriptedEvents[evType].label.SetThread(ev->GetValue(2));

    if (evType == SE_KEYPRESS) {
        gi.cvar_set("sv_keypressevents", "1");
    } else if (evType == SE_SERVERCOMMAND) {
        gi.cvar_set("sv_servercmdevents", "1");
    }

    ev->AddInteger(0);
}

void ScriptThread::UnregisterEvent(Event *ev)
{
    str              eventname;
    int              numArgs = 0;
    scriptedEvType_t evType;

    eventname = ev->GetString(1);

    evType = EventNameToType(eventname, NULL);

    if (evType == scriptedEvType_t(~0u)) {
        ev->AddInteger(0);
        throw ScriptException("Wrong event type name for unregisterev!\n");
    }

    numArgs = ev->NumArgs();

    if (numArgs != 1) {
        throw ScriptException("Wrong arguments count for unregisterev!\n");
    }

    eventname = ev->GetString(1);

    if (!scriptedEvents[evType].IsRegistered()) {
        ev->AddInteger(1);
        return;
    }

    scriptedEvents[evType].label.Set("");

    if (evType == SE_KEYPRESS) {
        gi.cvar_set("sv_keypressevents", "0");
    } else if (evType == SE_SERVERCOMMAND) {
        gi.cvar_set("sv_servercmdevents", "0");
    }

    ev->AddInteger(0);
}

void ScriptThread::TypeOfVariable(Event *ev)
{
    int             numArgs = 0;
    char           *type    = NULL;
    ScriptVariable *variable;

    numArgs = ev->NumArgs();

    if (numArgs != 1) {
        throw ScriptException("Wrong arguments count for typeof!\n");
    }

    variable = (ScriptVariable *)&ev->GetValue(1);
    type     = (char *)variable->GetTypeName();

    ev->AddString(type);
}

void ScriptThread::VisionGetNaked(Event *ev)
{
    ev->AddString(vision_current);
}

void ScriptThread::VisionSetNaked(Event *ev)
{
    str     vision = ev->GetString(1);
    float   fade_time;
    cvar_t *mapname = gi.Cvar_Get("mapname", "", 0);

    if (ev->NumArgs() > 1) {
        fade_time = ev->GetFloat(2);
    } else {
        fade_time = 0.0f;
    }

    if (!vision.length()) {
        vision = mapname->string;
    }

    // We won't malicously overflow client commands :)
    if (vision.length() >= MAX_STRING_TOKENS) {
        throw ScriptException("vision_name exceeds the maximum vision name limit (256) !\n");
    }

    vision_current = vision;

    gi.SendServerCommand(-1, "vsn %s %f", vision.c_str(), fade_time);
}

void ScriptThread::GetTime(Event *ev)
{
    int  timearray[3], gmttime;
    char buff[1024];

    time_t     rawtime;
    struct tm *timeinfo, *ptm;

    int timediff;

    time(&rawtime);
    timeinfo = localtime(&rawtime);

    timearray[0] = timeinfo->tm_hour;
    timearray[1] = timeinfo->tm_min;
    timearray[2] = timeinfo->tm_sec;

    ptm = gmtime(&rawtime);

    gmttime = ptm->tm_hour;

    timediff = timearray[0] - gmttime;

    Com_sprintf(buff, sizeof(buff), "%02i:%02i:%02i", (int)timearray[0], (int)timearray[1], (int)timearray[2]);

    ev->AddString(buff);
}

void ScriptThread::TraceDetails(Event *ev)
{
    int     numArgs     = 0;
    int     pass_entity = 0;
    int     mask        = 0x2000B01;
    trace_t trace;
    Vector  vecStart, vecEnd, vecMins, vecMaxs;
    Entity *entity;
    //todo : remove all these vars and add one for index and one for value

    ScriptVariable array;
    ScriptVariable allSolidIndex, allSolidValue;
    ScriptVariable startSolidIndex, startSolidValue;
    ScriptVariable fractionIndex, fractionValue;
    ScriptVariable endPosIndex, endPosValue;
    ScriptVariable surfaceFlagsIndex, surfaceFlagsValue;
    ScriptVariable shaderNumIndex, shaderNumValue;
    ScriptVariable contentsIndex, contentsValue;
    ScriptVariable entityNumIndex, entityNumValue;
    ScriptVariable locationIndex, locationValue;
    ScriptVariable entityIndex, entityValue;

    numArgs = ev->NumArgs();

    if (numArgs < 2 || numArgs > 6) {
        throw ScriptException("Wrong arguments count for traced!\n");
    }

    vecStart = ev->GetVector(1);
    vecEnd   = ev->GetVector(2);

    if (numArgs >= 3) {
        pass_entity = ev->GetInteger(3);
    }

    if (numArgs >= 4) {
        vecMins = ev->GetVector(4);
    }

    if (numArgs >= 5) {
        vecMaxs = ev->GetVector(5);
    }

    if (numArgs == 6) {
        mask = ev->GetInteger(6);
    }

    gi.trace(&trace, vecStart, vecMins, vecMaxs, vecEnd, pass_entity, mask, 0, 0);

    allSolidIndex.setStringValue("allSolid");
    startSolidIndex.setStringValue("startSolid");
    fractionIndex.setStringValue("fraction");
    endPosIndex.setStringValue("endpos");
    surfaceFlagsIndex.setStringValue("surfaceFlags");
    shaderNumIndex.setStringValue("shaderNum");
    contentsIndex.setStringValue("contents");
    entityNumIndex.setStringValue("entityNum");
    locationIndex.setStringValue("location");
    entityIndex.setStringValue("entity");

    allSolidValue.setIntValue(trace.allsolid);
    startSolidValue.setIntValue(trace.startsolid);
    fractionValue.setFloatValue(trace.fraction);
    endPosValue.setVectorValue(trace.endpos);
    surfaceFlagsValue.setIntValue(trace.surfaceFlags);
    shaderNumValue.setIntValue(trace.shaderNum);
    contentsValue.setIntValue(trace.contents);
    entityNumValue.setIntValue(trace.entityNum);
    locationValue.setIntValue(trace.location);

    entity = G_GetEntity(trace.entityNum);

    // Have to use G_GetEntity instead otherwise it won't work
    if (entity != NULL) {
        entityValue.setListenerValue(entity);
    }

    array.setArrayAtRef(allSolidIndex, allSolidValue);
    array.setArrayAtRef(startSolidIndex, startSolidValue);
    array.setArrayAtRef(fractionIndex, fractionValue);
    array.setArrayAtRef(endPosIndex, endPosValue);
    array.setArrayAtRef(surfaceFlagsIndex, surfaceFlagsValue);
    array.setArrayAtRef(shaderNumIndex, shaderNumValue);
    array.setArrayAtRef(contentsIndex, contentsValue);
    array.setArrayAtRef(entityNumIndex, entityNumValue);
    array.setArrayAtRef(locationIndex, locationValue);
    array.setArrayAtRef(entityIndex, entityValue);

    ev->AddValue(array);
}

CLASS_DECLARATION(Listener, OSFile, NULL) {
    {NULL, NULL}
};

OSFile::OSFile()
    : file(NULL)
{}

OSFile::OSFile(void *inFile)
    : file(inFile)
{}

OSFile::~OSFile()
{
    if (file) {
        fclose((FILE *)file);
    }
}

void *OSFile::getOSFile() const
{
    return file;
}
