diff --git a/Source/STA/Boundingbox.inc b/Source/STA/Boundingbox.inc new file mode 100644 index 0000000..db2ba08 --- /dev/null +++ b/Source/STA/Boundingbox.inc @@ -0,0 +1,172 @@ +#if defined BOUNDINGBOX_INCLUDED +#endinput +#endif +#define BOUNDINGBOX_INCLUDED + +#include "Offsets.inc" + +/* + 3---7 + /| /| + / | / | + 2---6 | + | 1|--5 + | / | / + |/ |/ + 0---4 + + From Source 2007 +*/ +public void STA_PointsFromBox(float[3] mins, float[3] maxs, float[8][3] points) +{ + points[0][0] = mins[0]; + points[0][1] = mins[1]; + points[0][2] = mins[2]; + + points[1][0] = mins[0]; + points[1][1] = mins[1]; + points[1][2] = maxs[2]; + + points[2][0] = mins[0]; + points[2][1] = maxs[1]; + points[2][2] = mins[2]; + + points[3][0] = mins[0]; + points[3][1] = maxs[1]; + points[3][2] = maxs[2]; + + points[4][0] = maxs[0]; + points[4][1] = mins[1]; + points[4][2] = mins[2]; + + points[5][0] = maxs[0]; + points[5][1] = mins[1]; + points[5][2] = maxs[2]; + + points[6][0] = maxs[0]; + points[6][1] = maxs[1]; + points[6][2] = mins[2]; + + points[7][0] = maxs[0]; + points[7][1] = maxs[1]; + points[7][2] = maxs[2]; +} + +public bool STA_BoxIntersects(float[3] min1, float[3] max1, float[3] min2, float[3] max2) +{ + return + (min1[0] <= max2[0] && max1[0] >= min2[0]) && + (min1[1] <= max2[1] && max1[1] >= min2[1]) && + (min1[2] <= max2[2] && max1[2] >= min2[2]); +} + +public void STA_GetPlayerBoundingBox(int client, float[3] min, float[3] max) +{ + GetEntDataVector(client, Offset_VecMin, min); + GetEntDataVector(client, Offset_VecMax, max); +} + +public void STA_GetBoxOrigin(float[3] min, float[3] max, float[3] output) +{ + float center[3]; + MakeVectorFromPoints(min, max, center); + + center[0] /= 2.0; + center[1] /= 2.0; + center[2] /= 2.0; + + AddVectors(min, center, output); +} + +public void STA_GetBoxSize(float[3] min, float[3] max, float[3] center, float[3] outputmin, float[3] outputmax) +{ + for (int i = 0; i < 3; ++i) + { + outputmin[i] = min[i] - center[i]; + + if (outputmin[i] > 0.0) + { + outputmin[i] *= -1.0; + } + + outputmax[i] = FloatAbs(max[i] - center[i]); + } +} + +public void STA_DisplayBox(int client, int sprite, float[8][3] points, float lifetime, int color[4]) +{ + TE_SetupBeamPoints(points[0], points[1], sprite, 0, 0, 0, lifetime, 2.0, 2.0, 10, 0.0, color, 0); + TE_SendToClient(client); + + TE_SetupBeamPoints(points[1], points[5], sprite, 0, 0, 0, lifetime, 2.0, 2.0, 10, 0.0, color, 0); + TE_SendToClient(client); + + TE_SetupBeamPoints(points[5], points[4], sprite, 0, 0, 0, lifetime, 2.0, 2.0, 10, 0.0, color, 0); + TE_SendToClient(client); + + TE_SetupBeamPoints(points[4], points[0], sprite, 0, 0, 0, lifetime, 2.0, 2.0, 10, 0.0, color, 0); + TE_SendToClient(client); + + TE_SetupBeamPoints(points[2], points[3], sprite, 0, 0, 0, lifetime, 2.0, 2.0, 10, 0.0, color, 0); + TE_SendToClient(client); + + TE_SetupBeamPoints(points[3], points[7], sprite, 0, 0, 0, lifetime, 2.0, 2.0, 10, 0.0, color, 0); + TE_SendToClient(client); + + TE_SetupBeamPoints(points[7], points[6], sprite, 0, 0, 0, lifetime, 2.0, 2.0, 10, 0.0, color, 0); + TE_SendToClient(client); + + TE_SetupBeamPoints(points[6], points[2], sprite, 0, 0, 0, lifetime, 2.0, 2.0, 10, 0.0, color, 0); + TE_SendToClient(client); + + TE_SetupBeamPoints(points[0], points[2], sprite, 0, 0, 0, lifetime, 2.0, 2.0, 10, 0.0, color, 0); + TE_SendToClient(client); + + TE_SetupBeamPoints(points[1], points[3], sprite, 0, 0, 0, lifetime, 2.0, 2.0, 10, 0.0, color, 0); + TE_SendToClient(client); + + TE_SetupBeamPoints(points[5], points[7], sprite, 0, 0, 0, lifetime, 2.0, 2.0, 10, 0.0, color, 0); + TE_SendToClient(client); + + TE_SetupBeamPoints(points[4], points[6], sprite, 0, 0, 0, lifetime, 2.0, 2.0, 10, 0.0, color, 0); + TE_SendToClient(client); +} + +public void STA_DisplayBoxAll(int sprite, float[8][3] points, float lifetime, int color[4]) +{ + TE_SetupBeamPoints(points[0], points[1], sprite, 0, 0, 0, lifetime, 2.0, 2.0, 10, 0.0, color, 0); + TE_SendToAll(); + + TE_SetupBeamPoints(points[1], points[5], sprite, 0, 0, 0, lifetime, 2.0, 2.0, 10, 0.0, color, 0); + TE_SendToAll(); + + TE_SetupBeamPoints(points[5], points[4], sprite, 0, 0, 0, lifetime, 2.0, 2.0, 10, 0.0, color, 0); + TE_SendToAll(); + + TE_SetupBeamPoints(points[4], points[0], sprite, 0, 0, 0, lifetime, 2.0, 2.0, 10, 0.0, color, 0); + TE_SendToAll(); + + TE_SetupBeamPoints(points[2], points[3], sprite, 0, 0, 0, lifetime, 2.0, 2.0, 10, 0.0, color, 0); + TE_SendToAll(); + + TE_SetupBeamPoints(points[3], points[7], sprite, 0, 0, 0, lifetime, 2.0, 2.0, 10, 0.0, color, 0); + TE_SendToAll(); + + TE_SetupBeamPoints(points[7], points[6], sprite, 0, 0, 0, lifetime, 2.0, 2.0, 10, 0.0, color, 0); + TE_SendToAll(); + + TE_SetupBeamPoints(points[6], points[2], sprite, 0, 0, 0, lifetime, 2.0, 2.0, 10, 0.0, color, 0); + TE_SendToAll(); + + TE_SetupBeamPoints(points[0], points[2], sprite, 0, 0, 0, lifetime, 2.0, 2.0, 10, 0.0, color, 0); + TE_SendToAll(); + + TE_SetupBeamPoints(points[1], points[3], sprite, 0, 0, 0, lifetime, 2.0, 2.0, 10, 0.0, color, 0); + TE_SendToAll(); + + TE_SetupBeamPoints(points[5], points[7], sprite, 0, 0, 0, lifetime, 2.0, 2.0, 10, 0.0, color, 0); + TE_SendToAll(); + + TE_SetupBeamPoints(points[4], points[6], sprite, 0, 0, 0, lifetime, 2.0, 2.0, 10, 0.0, color, 0); + TE_SendToAll(); +} diff --git a/Source/STA/Checkpoints.inc b/Source/STA/Checkpoints.inc new file mode 100644 index 0000000..6df7626 --- /dev/null +++ b/Source/STA/Checkpoints.inc @@ -0,0 +1,931 @@ +#if defined CHECKPOINTS_INCLUDED +#endinput +#endif +#define CHECKPOINTS_INCLUDED + +#include "Boundingbox.inc" +#include "Permissions.inc" +#include "STAPlayer.inc" + +#include + +/* + A start checkpoint will reset the time to 0 +*/ +enum +{ + CP_ZoneTypeStart, + CP_ZoneTypeNormal, + CP_ZoneTypeEnd, +}; + +/* + Possible states when CreatingCheckpoint is true +*/ +enum +{ + CP_BuildState_Start, + CP_BuildState_End, +}; + +enum +{ + CP_ZoneType, + + CP_MinX, + CP_MinY, + CP_MinZ, + + CP_MaxX, + CP_MaxY, + CP_MaxZ, + + CP_ArrayLength, +}; + +enum +{ + CPMenu_CreateStart, + CPMenu_CreateNormal, + CPMenu_CreateEnd, + + CPMenu_Proceed, + + CPMenu_LoadFromFile, + CPMenu_SaveToFile, +}; + +#define CP_ZoneTypeStartColor {255, 0, 255, 255} +#define CP_ZoneTypeNormalColor {155, 55, 155, 255} +#define CP_ZoneTypeEndColor {255, 0, 255, 255} + +public void CP_GetZoneColor(int zonetype, int outcolor[4]) +{ + switch (zonetype) + { + case CP_ZoneTypeStart: + { + outcolor = CP_ZoneTypeStartColor; + } + + case CP_ZoneTypeNormal: + { + outcolor = CP_ZoneTypeNormalColor; + } + + case CP_ZoneTypeEnd: + { + outcolor = CP_ZoneTypeEndColor; + } + } +} + +ArrayList STA_Checkpoints; +int STA_LaserSprite; + +public bool CP_ZoneEditTraceFilter(int entity, int contentsmask, any data) +{ + if (entity == data || entity < MAXPLAYERS) + { + return false; + } + + return true; +} + +public void CP_GetPlayerForwardVector(int client, float[3] pos, float[3] forwarddir, float[3] endpos) +{ + float angle[3]; + + GetClientEyePosition(client, pos); + GetClientEyeAngles(client, angle); + + GetAngleVectors(angle, forwarddir, NULL_VECTOR, NULL_VECTOR); + NormalizeVector(forwarddir, forwarddir); + ScaleVector(forwarddir, 1024.0); + AddVectors(pos, forwarddir, endpos); +} + +public void CP_ResetPlayer(int client) +{ + Player_SetZoneEditingStage(client, CP_BuildState_Start); + Player_SetIsCreatingCheckpoint(client, false); +} + +public int MenuHandler_Checkpoints(Menu menu, MenuAction action, int param1, int param2) +{ + int client = param1; + + if (action == MenuAction_Select) + { + char info[3]; + bool found = GetMenuItem(menu, param2, info, sizeof(info)); + + if (!found) + { + return; + } + + int itemid = StringToInt(info); + + switch (itemid) + { + case CPMenu_LoadFromFile: + { + CP_LoadZonesFromFile(client); + } + + case CPMenu_SaveToFile: + { + CP_SaveZonesToFile(client); + } + + /* + Just entered editing mode, no previous state + */ + case CPMenu_CreateStart, CPMenu_CreateNormal, CPMenu_CreateEnd: + { + Player_SetIsCreatingCheckpoint(client, true); + + int zonetype = itemid; + + switch (zonetype) + { + case CPMenu_CreateStart: + { + STA_PrintMessageToClient(client, "Creating zone: Start"); + } + + case CPMenu_CreateNormal: + { + STA_PrintMessageToClient(client, "Creating zone: Checkpoint"); + } + + case CPMenu_CreateEnd: + { + STA_PrintMessageToClient(client, "Creating zone: End"); + } + } + + Player_SetZoneEditCreationType(client, zonetype); + Player_SetZoneEditingStage(client, CP_BuildState_Start); + + STA_OpenZoneMenu(client); + } + + case CPMenu_Proceed: + { + int stage = Player_GetZoneEditingStage(client); + + switch (stage) + { + /* + Start position has been set + */ + case CP_BuildState_Start: + { + Player_SetZoneEditingStage(client, CP_BuildState_End); + } + + /* + End position has been set + */ + case CP_BuildState_End: + { + float min[3]; + float max[3]; + + Player_GetZoneEditBoxMinMax(client, min, max); + + any cpinfo[CP_ArrayLength]; + + cpinfo[0] = Player_GetZoneEditCreationType(client); + + CopyVector3ToArray(min, cpinfo, CP_MinX); + CopyVector3ToArray(max, cpinfo, CP_MaxX); + + int zoneid = GetArraySize(STA_Checkpoints); + + PushArrayArray(STA_Checkpoints, cpinfo); + + CP_CreateTriggerForCheckpoint(zoneid); + + CP_ResetPlayer(client); + } + } + + STA_OpenZoneMenu(client); + } + } + } + + else if (action == MenuAction_Cancel) + { + CP_ResetPlayer(client); + } + + else if (action == MenuAction_End) + { + delete menu; + } +} + +public void STA_OpenZoneMenu(int client) +{ + //Player_PrintEditInfo(client); + + bool onteam = IsPlayingOnTeam(client); + + if (!onteam) + { + STA_PrintMessageToClient(client, "You msut be in a team to manage checkpoints"); + return; + } + + Menu menu = CreateMenu(MenuHandler_Checkpoints); + SetMenuTitle(menu, "STA Checkpoints"); + + if (!Player_GetIsCreatingCheckpoint(client)) + { + Menu_AddEnumEntry(menu, CPMenu_CreateStart, "Create start"); + Menu_AddEnumEntry(menu, CPMenu_CreateNormal, "Create checkpoint"); + Menu_AddEnumEntry(menu, CPMenu_CreateEnd, "Create end"); + + Menu_AddEnumEntry(menu, CPMenu_LoadFromFile, "Load checkpoints (override)"); + + if (GetArraySize(STA_Checkpoints) > 0) + { + Menu_AddEnumEntry(menu, CPMenu_SaveToFile, "Save checkpoints"); + } + } + + else + { + int stage = Player_GetZoneEditingStage(client); + + switch (stage) + { + case CP_BuildState_Start: + { + Menu_AddEnumEntry(menu, CPMenu_Proceed, "Set point 1"); + } + + case CP_BuildState_End: + { + Menu_AddEnumEntry(menu, CPMenu_Proceed, "Set point 2"); + } + } + } + + DisplayMenu(menu, client, MENU_TIME_FOREVER); +} + +public Action STA_ZoneEdit(int client, int args) +{ + if (!HandlePlayerPermission(client)) + { + return Plugin_Handled; + } + + STA_OpenZoneMenu(client); + return Plugin_Handled; +} + +public Action STA_ZoneGridSize(int client, int args) +{ + if (args == 2) + { + char newgridbuf[3]; + GetCmdArg(1, newgridbuf, sizeof(newgridbuf)); + + int newgridsize; + + newgridsize = StringToInt(newgridbuf); + + Player_SetZoneGridSize(client, newgridsize); + } + + return Plugin_Handled; +} + +/* + Triggers are required to have a model +*/ +#define CP_Model "models\\props\\cs_italy\\bananna.mdl" + +#define CP_LaserSprite "materials\\sprites\\spotlight.vmt" + +public void CP_MapStartInit() +{ + STA_LaserSprite = PrecacheModel(CP_LaserSprite); + PrecacheModel(CP_Model); + + CreateTimer(1.0, RenewZoneBoxes, _, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); + + if (STA_Checkpoints != null) + { + ClearArray(STA_Checkpoints); + delete STA_Checkpoints; + } + + STA_Checkpoints = CreateArray(CP_ArrayLength); +} + +public void CP_OnMapEnd() +{ + +} + +public void CP_Init() +{ + RegConsoleCmd("sm_zone_edit", STA_ZoneEdit); + RegConsoleCmd("sm_zone_gridsize", STA_ZoneGridSize); +} + +public void CP_UpdateZoneEditBox(int client) +{ + float min[3]; + float max[3]; + float points[8][3]; + + Player_GetZoneEditBoxMinMax(client, min, max); + STA_PointsFromBox(min, max, points); + + int color[4]; + int zonetype = Player_GetZoneEditCreationType(client); + CP_GetZoneColor(zonetype, color); + + STA_DisplayBox(client, STA_LaserSprite, points, 0.1, color); +} + +public Action RenewZoneBoxes(Handle timer, any client) +{ + int arraysize = GetArraySize(STA_Checkpoints); + + for (int i = 0; i < arraysize; ++i) + { + int zonetype; + float min[3]; + float max[3]; + + float points[8][3]; + + any cpinfo[CP_ArrayLength]; + GetArrayArray(STA_Checkpoints, i, cpinfo); + + zonetype = cpinfo[0]; + + GetArrayVector3(cpinfo, CP_MinX, min); + GetArrayVector3(cpinfo, CP_MaxX, max); + + STA_PointsFromBox(min, max, points); + + int color[4]; + CP_GetZoneColor(zonetype, color); + + STA_DisplayBoxAll(STA_LaserSprite, points, 1.0, color); + } + + return Plugin_Continue; +} + +public bool CP_IsNormalFlat(float[3] tracenormal) +{ + for (int i = 0; i < 3; ++i) + { + if (tracenormal[i] != -1.0 || tracenormal[i] != 0.0 || tracenormal[i] != 1.0) + { + return false; + } + } + + return true; +} + +public void CP_Update(int client) +{ + int stage = Player_GetZoneEditingStage(client); + + switch (stage) + { + case CP_BuildState_Start, CP_BuildState_End: + { + float pos[3]; + GetClientAbsOrigin(client, pos); + + SnapVectorTo(pos, Player_GetZoneGridSize(client)); + + if (stage == CP_BuildState_Start) + { + Player_SetZoneEditStartPos(client, pos); + } + + else if (stage == CP_BuildState_End) + { + Player_SetZoneEditEndPos(client, pos); + CP_UpdateZoneEditBox(client); + } + } + } +} + +public int MenuHandler_ZoneSelect(Menu menu, MenuAction action, int param1, int param2) +{ + int client = param1; + + if (action == MenuAction_Select) + { + /* + "info" is the filename including extension + */ + char info[512]; + bool found = GetMenuItem(menu, param2, info, sizeof(info)); + + if (!found) + { + return; + } + + char mapbuf[MAX_NAME_LENGTH]; + GetCurrentMap(mapbuf, sizeof(mapbuf)); + + char filepath[512]; + FormatEx(filepath, sizeof(filepath), "%s%s\\%s", STA_ZonePath, mapbuf, info); + + File file = OpenFile(filepath, "rb", true); + + if (file == null) + { + STA_PrintMessageToClient(client, "\"%s\" could not be opened", info); + return; + } + + CP_ResetPlayer(client); + + int arraysize; + ReadFileCell(file, arraysize, 4); + + ClearArray(STA_Checkpoints); + + any cpinfo[CP_ArrayLength]; + + for (int i = 0; i < arraysize; ++i) + { + for (int j = 0; j < CP_ArrayLength; ++j) + { + ReadFileCell(file, cpinfo[j], 4); + } + + PushArrayArray(STA_Checkpoints, cpinfo); + } + + delete file; + + for (int i = 0; i < arraysize; ++i) + { + CP_CreateTriggerForCheckpoint(i); + } + + STA_PrintMessageToClient(client, "Loaded zones \"%s\"", info); + + STA_OpenZoneMenu(client); + } + + else if (action == MenuAction_Cancel) + { + + } + + else if (action == MenuAction_End) + { + delete menu; + } +} + +public void CP_LoadZonesFromFile(int client) +{ + ClearArray(STA_Checkpoints); + + char mapbuf[MAX_NAME_LENGTH]; + GetCurrentMap(mapbuf, sizeof(mapbuf)); + + char mapzonebuf[512]; + FormatEx(mapzonebuf, sizeof(mapzonebuf), "%s%s", STA_ZonePath, mapbuf); + + if (!DirExists(mapzonebuf)) + { + STA_PrintMessageToClient(client, "No zones available for \"%s\"", mapbuf); + return; + } + + DirectoryListing dirlist = OpenDirectory(mapzonebuf, true); + + if (dirlist == null) + { + STA_PrintMessageToClient(client, "Could not open STA directory path"); + return; + } + + bool done = false; + FileType curtype; + char curname[512]; + int index = 0; + + Menu selectmenu = CreateMenu(MenuHandler_ZoneSelect); + SetMenuTitle(selectmenu, "Zone File Select"); + + do + { + done = ReadDirEntry(dirlist, curname, sizeof(curname), curtype); + + if (!done) + { + /* + Only the "." and ".." in here + */ + if (index == 2) + { + STA_PrintMessageToClient(client, "No zones available"); + return; + } + + break; + } + + ++index; + + /* + Skip the "." and ".." + */ + if (index > 2 && curtype == FileType_File) + { + //PrintToServer("%s", curname); + AddMenuItem(selectmenu, curname, curname); + } + } + while (done); + + delete dirlist; + + DisplayMenu(selectmenu, client, MENU_TIME_FOREVER); +} + +public void CP_SaveZonesToFile(int client) +{ + char mapbuf[MAX_NAME_LENGTH]; + GetCurrentMap(mapbuf, sizeof(mapbuf)); + + char playernamebuf[MAX_NAME_LENGTH]; + GetClientName(client, playernamebuf, sizeof(playernamebuf)); + + char newdirbuf[512]; + FormatEx(newdirbuf, sizeof(newdirbuf), "%s%s", STA_ZonePath, mapbuf); + + CreateDirectory(newdirbuf, 0, true, "GAME"); + + int steamid = GetSteamAccountID(client); + + char timebuf[128]; + FormatTime(timebuf, sizeof(timebuf), "%Y %m %d, %H %M %S"); + + char namebuf[256]; + FormatEx(namebuf, sizeof(namebuf), "[%d] %s (%s)", steamid, playernamebuf, timebuf); + + char filename[512]; + FormatEx(filename, sizeof(filename), "%s\\%s.STA", newdirbuf, namebuf); + + File file = OpenFile(filename, "wb", true); + + if (file == null) + { + STA_PrintMessageToClient(client, "Could not save zones"); + return; + } + + int arraysize = GetArraySize(STA_Checkpoints); + WriteFileCell(file, arraysize, 4); + + any cpinfo[CP_ArrayLength]; + + for (int i = 0; i < arraysize; ++i) + { + GetArrayArray(STA_Checkpoints, i, cpinfo); + + for (int j = 0; j < CP_ArrayLength; ++j) + { + WriteFileCell(file, cpinfo[j], 4); + } + } + + delete file; + + STA_PrintMessageToClient(client, "Saved as \"%s\"", namebuf); +} + +public bool CP_CanPassHookTest(int client) +{ + if (!IsPlayerAlive(client)) + { + //PrintToServer("alive"); + return false; + } + + if (!IsFakeClient(client)) + { + if (Player_GetIsCreatingCheckpoint(client)) + { + //PrintToServer("creating checkpoint"); + return false; + } + + if (Player_GetIsRewinding(client)) + { + //PrintToServer("is rewinding"); + return false; + } + + if (!Player_GetIsSegmenting(client)) + { + //PrintToServer("is not segmenting"); + return false; + } + } + + //PrintToServer("ok"); + return true; +} + +public bool CP_CanPassHookSayTest(int client) +{ + return true; +} + +public void CP_StartTouch(int entity, int client) +{ + if (!CP_CanPassHookTest(client)) + { + return; + } + + bool isfake = false; + int fakeid = 0; + + if (IsFakeClient(client)) + { + isfake = true; + fakeid = client; + + client = Bot_GetLinkedPlayerIndex(client); + } + + int zonetype; + int zoneid; + int unused2; + int unused3; + Offsets_GetEntityRenderColor(entity, zonetype, zoneid, unused2, unused3); + + bool timing = Player_GetIsTimingRun(client); + + if (timing) + { + if (zonetype == CP_ZoneTypeNormal || zonetype == CP_ZoneTypeEnd) + { + char message[256]; + + /* + float xyvel; + float zvel; + + if (isfake) + { + ClientVelocityLength(fakeid, xyvel, zvel); + } + + else + { + ClientVelocityLength(client, xyvel, zvel); + } + + FormatEx(message, sizeof(message), STA_ZoneSpeedEnterFormat, xyvel, zvel); + + if (isfake) + { + char fakenamebuf[MAX_NAME_LENGTH]; + GetClientName(fakeid, fakenamebuf, sizeof(fakenamebuf)); + + CPrintToChat(client, "%s{lightblue}* %s * %s", STA_MessagePrefix, fakenamebuf, message); + } + + else + { + CPrintToChat(client, "%s%s", STA_MessagePrefix, message); + } + */ + + float tickinterval = GetTickInterval(); + int curframe = Player_GetRewindFrame(client) - Player_GetStartTimeReplayTick(client); + + float curtime = curframe * tickinterval; + + char curtimebuf[64]; + FormatTimeSpan(curtimebuf, sizeof(curtimebuf), curtime); + + if (zonetype == CP_ZoneTypeNormal) + { + FormatEx(message, sizeof(message), STA_CheckpointFormat, zoneid, curtimebuf); + + if (isfake) + { + STA_PrintMessageToClientSpectators(fakeid, message); + } + + else + { + STA_PrintMessageToClient(client, message); + } + } + + else + { + char namebuf[MAX_NAME_LENGTH]; + GetClientName(fakeid, namebuf, sizeof(namebuf)); + + FormatEx(message, sizeof(message), STA_EndFinishFormat, namebuf, curtimebuf); + + STA_PrintMessageToAllClients(message); + + Player_SetIsTimingRun(client, false); + } + } + + else if (zonetype == CP_ZoneTypeStart) + { + Player_SetIsTimingRun(client, false); + } + } + + //PrintToServer("enter to type %d", zonetype); +} + +public void CP_EndTouch(int entity, int client) +{ + if (!CP_CanPassHookTest(client)) + { + return; + } + + bool isfake = false; + int fakeid = 0; + + if (IsFakeClient(client)) + { + isfake = true; + fakeid = client; + + client = Bot_GetLinkedPlayerIndex(client); + } + + int zonetype; + int zoneid; + int unused2; + int unused3; + Offsets_GetEntityRenderColor(entity, zonetype, zoneid, unused2, unused3); + + bool timing = Player_GetIsTimingRun(client); + + if (zonetype == CP_ZoneTypeStart) + { + if (!timing) + { + Player_SetStartTimeReplayTick(client, Player_GetRewindFrame(client)); + Player_SetIsTimingRun(client, true); + } + } + + /* + float xyvel; + float zvel; + + if (isfake) + { + ClientVelocityLength(fakeid, xyvel, zvel); + } + + else + { + ClientVelocityLength(client, xyvel, zvel); + } + + char message[256]; + FormatEx(message, sizeof(message), STA_ZoneSpeedLeaveFormat, xyvel, zvel); + + if (isfake) + { + char fakenamebuf[MAX_NAME_LENGTH]; + GetClientName(fakeid, fakenamebuf, sizeof(fakenamebuf)); + + CPrintToChat(client, "%s{lightblue}* %s * %s", STA_MessagePrefix, fakenamebuf, message); + } + + else + { + CPrintToChat(client, "%s%s", STA_MessagePrefix, message); + } + */ + //PrintToServer("leave from type %d", zonetype); +} + +public void CP_CreateTriggerForCheckpoint(int checkpoint) +{ + int newent = CreateEntityByName("trigger_multiple"); + + if (!IsValidEntity(newent)) + { + return; + } + + any cpinfo[CP_ArrayLength]; + GetArrayArray(STA_Checkpoints, checkpoint, cpinfo); + + float min[3]; + float max[3]; + //Player_GetZoneEditBoxMinMax(client, min, max); + GetArrayVector3(cpinfo, CP_MinX, min); + GetArrayVector3(cpinfo, CP_MaxX, max); + + float origin[3]; + STA_GetBoxOrigin(min, max, origin); + + float localmin[3]; + float localmax[3]; + STA_GetBoxSize(min, max, origin, localmin, localmax); + + char zonename[MAX_TARGET_LENGTH]; + FormatEx(zonename, sizeof(zonename), "sta_zone_%d", checkpoint); + + /* + Flag 1 = Only clients + */ + DispatchKeyValue(newent, "spawnflags", "1"); + DispatchKeyValue(newent, "StartDisabled", "0"); + DispatchKeyValue(newent, "targetname", zonename); + DispatchKeyValue(newent, "wait", "0"); + + SetEntityModel(newent, CP_Model); + + int zonetype = cpinfo[0]; + + /* + We store our trigger data in render color to easily get its userdata in touch/leave hooks + */ + SetEntityRenderColor(newent, zonetype, checkpoint); + + DispatchSpawn(newent); + ActivateEntity(newent); + + /* + All these settings can only be changed after the entity is spawned + */ + DispatchKeyValueVector(newent, "origin", origin); + DispatchKeyValueVector(newent, "mins", localmin); + DispatchKeyValueVector(newent, "maxs", localmax); + + /* + Flag 2 = SOLID_BBOX + */ + DispatchKeyValue(newent, "solid", "2"); + + /* + Flag 32 = EF_NODRAW + */ + SetEntData(newent, Offsets_Effects, GetEntData(newent, Offsets_Effects) | 32); + + SDKHookEx(newent, SDKHook_StartTouch, CP_StartTouch); + SDKHookEx(newent, SDKHook_EndTouch, CP_EndTouch); +} + +public void STA_GetCheckpointBoundingBox(int checkpoint, float[3] min, float[3] max) +{ + min[0] = GetArrayCell(STA_Checkpoints, checkpoint, 0); + min[1] = GetArrayCell(STA_Checkpoints, checkpoint, 1); + min[2] = GetArrayCell(STA_Checkpoints, checkpoint, 2); + + max[0] = GetArrayCell(STA_Checkpoints, checkpoint, 3); + max[1] = GetArrayCell(STA_Checkpoints, checkpoint, 4); + max[2] = GetArrayCell(STA_Checkpoints, checkpoint, 5); +} + +public void STA_DrawCheckpoint(int checkpoint) +{ + float points[8][3]; + STA_PointsFromCheckpoint(checkpoint, points); +} + +public void STA_PointsFromCheckpoint(int checkpoint, float[8][3] points) +{ + float mins[3]; + float maxs[3]; + STA_GetCheckpointBoundingBox(checkpoint, mins, maxs); + + STA_PointsFromBox(mins, maxs, points); +} diff --git a/Source/STA/CollisionGroups.inc b/Source/STA/CollisionGroups.inc new file mode 100644 index 0000000..d7f1618 --- /dev/null +++ b/Source/STA/CollisionGroups.inc @@ -0,0 +1,14 @@ +#if defined COLGROUPS_INCLUDED +#endinput +#endif +#define COLGROUPS_INCLUDED + +enum +{ + /* + This group is used to keep players from colliding with each other + */ + COLLISION_GROUP_DEBRIS_TRIGGER = 2, + + COLLISION_GROUP_PLAYER = 5, +}; diff --git a/Source/STA/Formats.inc b/Source/STA/Formats.inc new file mode 100644 index 0000000..c0fa959 --- /dev/null +++ b/Source/STA/Formats.inc @@ -0,0 +1,16 @@ +#if defined FORMATS_INCLUDED +#endinput +#endif +#define FORMATS_INCLUDED + +#include "Include\morecolors.inc" + +#define STA_MessagePrefix "{vintage}[{dodgerblue}STA{vintage}]{ghostwhite} - " +#define STA_ReplayPath "Source Tool Assist\\Replays\\" +#define STA_ZonePath "Source Tool Assist\\Zones\\" + +#define STA_CheckpointFormat "{ghostwhite}Checkpoint [{unique}%d{ghostwhite}] {lightblue}%s" +#define STA_EndFinishFormat "{orchid}%s {ghostwhite}finished in {unique}%s" + +#define STA_ZoneSpeedLeaveFormat "{ghostwhite}Zone leave velocity XY: {lightblue}%0.2f{ghostwhite} - Z: {lightblue}%0.2f" +#define STA_ZoneSpeedEnterFormat "{ghostwhite}Zone enter velocity XY: {lightblue}%0.2f{ghostwhite} - Z: {lightblue}%0.2f" diff --git a/Source/STA/Menus.inc b/Source/STA/Menus.inc new file mode 100644 index 0000000..2accb97 --- /dev/null +++ b/Source/STA/Menus.inc @@ -0,0 +1,45 @@ +#if defined MENUS_INCLUDED +#endinput +#endif +#define MENUS_INCLUDED + +/* + Main plugin menu entries +*/ +enum +{ + /* + Menu commands before or while a replay is being created + */ + SEG_Start, + SEG_Resume, + SEG_Pause, + SEG_GoBack, + SEG_Play, + SEG_Stop, + SEG_LoadFromFile, + + /* + Menu commands after a replay is created + */ + MOV_Resume, + MOV_NewFrom, + MOV_Stop, + MOV_Pause, + MOV_ContinueFrom, + MOV_SaveToFile, + + ALL_RewindSpeed, + ALL_JumpToStart, + ALL_JumpToEnd, +}; + +/* + Converts the enum value "data" into a string and sets it as the info value to a menu item. +*/ +public void Menu_AddEnumEntry(Handle menu, any data, char[] displaystr) +{ + char buf[64]; + IntToString(data, buf, sizeof(buf)); + AddMenuItem(menu, buf, displaystr); +} diff --git a/Source/STA/Offsets.inc b/Source/STA/Offsets.inc new file mode 100644 index 0000000..96bd92b --- /dev/null +++ b/Source/STA/Offsets.inc @@ -0,0 +1,59 @@ +#if defined OFFSETS_INCLUDED +#endinput +#endif +#define OFFSETS_INCLUDED + +/* + Cached offsets +*/ +public int Offset_CollisionGroup; +public int Offset_VecMin; +public int Offset_VecMax; +public int Offset_ObserverTarget; +public int Offset_ObserverMode; +public int Offset_SolidType; +public int Offsets_Effects; +public int Offsets_RenderColor; + +void Offsets_ThrowIfNotFound(int offset, char[] name) +{ + if (offset == -1) + { + PrintToServer("################# %s not found", name); + } +} + +public void Offsets_Init() +{ + Offset_CollisionGroup = FindSendPropOffs("CBaseEntity", "m_CollisionGroup"); + Offsets_ThrowIfNotFound(Offset_CollisionGroup, "m_CollisionGroup"); + + Offset_VecMin = FindSendPropOffs("CBaseEntity", "m_vecMins"); + Offsets_ThrowIfNotFound(Offset_VecMin, "m_vecMins"); + + Offset_VecMax = FindSendPropOffs("CBaseEntity", "m_vecMaxs"); + Offsets_ThrowIfNotFound(Offset_VecMax, "m_vecMaxs"); + + Offset_ObserverTarget = FindSendPropOffs("CBasePlayer", "m_hObserverTarget"); + Offsets_ThrowIfNotFound(Offset_ObserverTarget, "m_hObserverTarget"); + + Offset_ObserverMode = FindSendPropOffs("CBasePlayer", "m_iObserverMode"); + Offsets_ThrowIfNotFound(Offset_ObserverMode, "m_iObserverMode"); + + Offset_SolidType = FindSendPropOffs("CBaseEntity", "m_nSolidType"); + Offsets_ThrowIfNotFound(Offset_SolidType, "m_nSolidType"); + + Offsets_Effects = FindSendPropOffs("CBaseEntity", "m_fEffects"); + Offsets_ThrowIfNotFound(Offsets_Effects, "m_fEffects"); + + Offsets_RenderColor = FindSendPropOffs("CBaseEntity", "m_clrRender"); + Offsets_ThrowIfNotFound(Offsets_RenderColor, "m_clrRender"); +} + +public void Offsets_GetEntityRenderColor(int entity, int& red, int& green, int& blue, int& alpha) +{ + red = GetEntData(entity, Offsets_RenderColor, 1); + green = GetEntData(entity, Offsets_RenderColor + 1, 1); + blue = GetEntData(entity, Offsets_RenderColor + 2, 1); + alpha = GetEntData(entity, Offsets_RenderColor + 3, 1); +} diff --git a/Source/STA/Permissions.inc b/Source/STA/Permissions.inc new file mode 100644 index 0000000..ed3dedf --- /dev/null +++ b/Source/STA/Permissions.inc @@ -0,0 +1,40 @@ +#if defined PERMISSIONS_INCLUDED +#endinput +#endif +#define PERMISSIONS_INCLUDED + +#include "Formats.inc" + +public bool PlayerHasPermissionToUse(int client) +{ + AdminId adminid = GetUserAdmin(client); + return GetAdminFlag(adminid, Admin_Generic); +} + +public bool PlayerHasPermisionToManage(int client) +{ + AdminId adminid = GetUserAdmin(client); + return GetAdminFlag(adminid, Admin_Root); +} + +public bool HandlePlayerPermission(int client) +{ + if (!PlayerHasPermissionToUse(client)) + { + STA_PrintMessageToClient(client, "You do not have permission to use STA"); + return false; + } + + return true; +} + +public bool HandleManagePermission(int client) +{ + if (!PlayerHasPermisionToManage(client)) + { + STA_PrintMessageToClient(client, "You do not have permission to manage STA"); + return false; + } + + return true; +} diff --git a/Source/STA/ReplayFrame.inc b/Source/STA/ReplayFrame.inc new file mode 100644 index 0000000..dde0afc --- /dev/null +++ b/Source/STA/ReplayFrame.inc @@ -0,0 +1,33 @@ +#if defined REPLAYFRAME_INCLUDED +#endinput +#endif +#define REPLAYFRAME_INCLUDED + +/* + Number of entries in a frame array + + 0: Buttons + 1: Pos X + 2: Pos Y + 3: Pos Z + 4: Angle X + 5: Angle Y + 6: Vel X + 7: Vel Y + 8: Vel Z +*/ + +enum +{ + FRAME_Buttons, + FRAME_PosX, + FRAME_PosY, + FRAME_PosZ, + FRAME_AngX, + FRAME_AngY, + FRAME_VelX, + FRAME_VelY, + FRAME_VelZ, + + FRAME_Length, +}; diff --git a/Source/STA/STAPlayer.inc b/Source/STA/STAPlayer.inc new file mode 100644 index 0000000..5bf9038 --- /dev/null +++ b/Source/STA/STAPlayer.inc @@ -0,0 +1,569 @@ +#if defined STAPLAYER_INCLUDED +#endinput +#endif +#define STAPLAYER_INCLUDED + +#include "Formats.inc" +#include "Vector.inc" +#include "ReplayFrame.inc" + +/* + ============================================================== +*/ + +/* + When a player gets moved to spectator, this saves their previous team +*/ +int PreferredTeam[MAXPLAYERS + 1]; + +public int Player_GetPreferredTeam(int client) +{ + return PreferredTeam[client]; +} + +public void Player_SetPreferredTeam(int client, int value) +{ + PreferredTeam[client] = value; +} + +/* + ============================================================== +*/ + +bool IsSegmenting[MAXPLAYERS + 1]; + +public bool Player_GetIsSegmenting(int client) +{ + return IsSegmenting[client]; +} + +public void Player_SetIsSegmenting(int client, bool value) +{ + IsSegmenting[client] = value; +} + +/* + ============================================================== +*/ + +int LastPausedTick[MAXPLAYERS + 1]; + +public int Player_GetLastPausedTick(int client) +{ + return LastPausedTick[client]; +} + +public void Player_SetLastPausedTick(int client, int value) +{ + LastPausedTick[client] = value; +} + +/* + ============================================================== +*/ + +bool IsRewinding[MAXPLAYERS + 1]; + +public bool Player_GetIsRewinding(int client) +{ + return IsRewinding[client]; +} + +public void Player_SetIsRewinding(int client, bool value) +{ + IsRewinding[client] = value; +} + +/* + ============================================================== +*/ + +bool HasRun[MAXPLAYERS + 1]; + +public bool Player_GetHasRun(int client) +{ + return HasRun[client]; +} + +public void Player_SetHasRun(int client, bool value) +{ + HasRun[client] = value; +} + +/* + ============================================================== +*/ + +bool IsPlayingReplay[MAXPLAYERS + 1]; + +public bool Player_GetIsPlayingReplay(int client) +{ + return IsPlayingReplay[client]; +} + +public void Player_SetPlayingReplay(int client, bool value) +{ + IsPlayingReplay[client] = value; +} + +/* + ============================================================== +*/ + +int RewindSpeed[MAXPLAYERS + 1] = {1, ...}; + +public void Player_SetRewindSpeed(int client, int value) +{ + RewindSpeed[client] = value; +} + +public int Player_GetRewindSpeed(int client) +{ + return RewindSpeed[client]; +} + +/* + ============================================================== +*/ + +ArrayList RecordFramesList[MAXPLAYERS + 1]; + +public void Player_CreateFrameArray(int client) +{ + RecordFramesList[client] = CreateArray(FRAME_Length); +} + +public Handle Player_GetRecordFramesList(int client) +{ + return RecordFramesList[client]; +} + +public void Player_PushFrame(int client, any[] frameinfo) +{ + PushArrayArray(RecordFramesList[client], frameinfo); +} + +public void Player_GetFrame(int client, int index, any[] frameinfo) +{ + GetArrayArray(RecordFramesList[client], index, frameinfo); +} + +public int Player_GetRecordedFramesCount(int client) +{ + return GetArraySize(RecordFramesList[client]); +} + +public void Player_ResizeRecordFrameList(int client, int newsize) +{ + ResizeArray(RecordFramesList[client], newsize); +} + +public void Player_DeleteRecordFrames(int client) +{ + if (RecordFramesList[client]!= null) + { + delete RecordFramesList[client]; + } +} + +/* + ============================================================== +*/ + +int CurrentRewindFrame[MAXPLAYERS + 1]; + +public int Player_GetRewindFrame(int client) +{ + return CurrentRewindFrame[client]; +} + +public void Player_SetRewindFrame(int client, int value) +{ + CurrentRewindFrame[client] = value; +} + +public void Player_IncrementRewindFrame(int client) +{ + ++CurrentRewindFrame[client]; +} + +public void Player_DecrementRewindFrame(int client) +{ + --CurrentRewindFrame[client]; +} + +/* + ============================================================== +*/ + +int PlayerLinkedBotIndex[MAXPLAYERS + 1]; + +public int Player_GetLinkedBotIndex(int client) +{ + return PlayerLinkedBotIndex[client]; +} + +public void Player_SetLinkedBotIndex(int client, int value) +{ + PlayerLinkedBotIndex[client] = value; +} + +/* + ============================================================== +*/ + +int BotLinkedPlayerIndex[MAXPLAYERS + 1]; + +public int Bot_GetLinkedPlayerIndex(int client) +{ + return BotLinkedPlayerIndex[client]; +} + +public void Bot_SetLinkedPlayerIndex(int client, int value) +{ + BotLinkedPlayerIndex[client] = value; +} + +/* + ============================================================== +*/ + +/* + Threshold value to which zone editing snaps to +*/ +int ZoneGridSize[MAXPLAYERS + 1] = {8, ...}; + +public int Player_GetZoneGridSize(int client) +{ + return ZoneGridSize[client]; +} + +public void Player_SetZoneGridSize(int client, int value) +{ + ZoneGridSize[client] = value; +} + +/* + ============================================================== +*/ + +bool IsCreatingCheckpoint[MAXPLAYERS + 1]; + +public bool Player_GetIsCreatingCheckpoint(int client) +{ + return IsCreatingCheckpoint[client]; +} + +public void Player_SetIsCreatingCheckpoint(int client, bool value) +{ + IsCreatingCheckpoint[client] = value; +} + +/* + ============================================================== +*/ + +/* + See CP_BuildState in Checkpoints.inc for possible values + + CreatingCheckpoint gets set to false after the height stage is complete +*/ +int ZoneEditingStage[MAXPLAYERS + 1]; + +public int Player_GetZoneEditingStage(int client) +{ + return ZoneEditingStage[client]; +} + +public void Player_SetZoneEditingStage(int client, int value) +{ + ZoneEditingStage[client] = value; +} + +/* + ============================================================== +*/ + +float ZoneEditStartPos[MAXPLAYERS + 1][3]; + +public void Player_GetZoneEditStartPos(int client, float[3] pos) +{ + pos = ZoneEditStartPos[client]; +} + +public void Player_SetZoneEditStartPos(int client, float[3] value) +{ + ZoneEditStartPos[client] = value; +} + +/* + ============================================================== +*/ + +float ZoneEditEndPos[MAXPLAYERS + 1][3]; + +public void Player_GetZoneEditEndPos(int client, float[3] pos) +{ + pos = ZoneEditEndPos[client]; +} + +public void Player_SetZoneEditEndPos(int client, float[3] value) +{ + ZoneEditEndPos[client] = value; +} + +/* + ============================================================== +*/ + +public void Player_GetZoneEditBoxMinMax(int client, float[3] min, float[3] max) +{ + float startpos[3]; + float endpos[3]; + Player_GetZoneEditStartPos(client, startpos); + Player_GetZoneEditEndPos(client, endpos); + + MinVector(startpos, endpos, min); + MaxVector(startpos, endpos, max); +} + +/* + ============================================================== +*/ + +/* + What type of zone is being created if IsCreatingCheckpoint is true +*/ +int ZoneEditCreationType[MAXPLAYERS + 1]; + +public int Player_GetZoneEditCreationType(int client) +{ + return ZoneEditCreationType[client]; +} + +public void Player_SetZoneEditCreationType(int client, int value) +{ + ZoneEditCreationType[client] = value; +} + +/* + ============================================================== +*/ + +bool IsTimingRun[MAXPLAYERS + 1]; + +public bool Player_GetIsTimingRun(int client) +{ + return IsTimingRun[client]; +} + +public void Player_SetIsTimingRun(int client, bool value) +{ + IsTimingRun[client] = value; +} + +/* + ============================================================== +*/ + +int StartTimeReplayTick[MAXPLAYERS + 1]; + +public int Player_GetStartTimeReplayTick(int client) +{ + return StartTimeReplayTick[client]; +} + +public void Player_SetStartTimeReplayTick(int client, int value) +{ + StartTimeReplayTick[client] = value; +} + +/* + ============================================================== +*/ + +bool HasFastForwardKeyDown[MAXPLAYERS + 1]; + +public bool Player_GetHasFastForwardKeyDown(int client) +{ + return HasFastForwardKeyDown[client]; +} + +public void Player_SetHasFastForwardKeyDown(int client, bool value) +{ + HasFastForwardKeyDown[client] = value; +} + +/* + ============================================================== +*/ + +bool HasRewindKeyDown[MAXPLAYERS + 1]; + +public bool Player_GetHasRewindKeyDown(int client) +{ + return HasRewindKeyDown[client]; +} + +public void Player_SetHasRewindKeyDown(int client, bool value) +{ + HasRewindKeyDown[client] = value; +} + +/* + ============================================================== +*/ + +public int Player_ClampRecordFrame(int client, int frame) +{ + int count = Player_GetRecordedFramesCount(client); + + if (frame < 0) + { + frame = 0; + } + + else if (frame >= count) + { + frame = count - 1; + } + + return frame; +} + +public void Player_PrintInfo(int client) +{ + PrintToChat(client, "============= Player_PrintInfo"); + PrintToChat(client, "IsSegmenting = %d", IsSegmenting[client]); + PrintToChat(client, "IsRewinding = %d", IsRewinding[client]); + PrintToChat(client, "HasRun = %d", HasRun[client]); + PrintToChat(client, "PlayingReplay = %d", IsPlayingReplay[client]); + PrintToChat(client, "CurrentRewindFrame = %d", CurrentRewindFrame[client]); + PrintToChat(client, "PlayerLinkedBotIndex = %d", PlayerLinkedBotIndex[client]); +} + +public void Player_PrintEditInfo(int client) +{ + PrintToChat(client, "============= Player_PrintEditInfo"); + PrintToChat(client, "CreatingCheckpoint = %d", IsCreatingCheckpoint[client]); + PrintToChat(client, "ZoneGridSize = %d", ZoneGridSize[client]); + PrintToChat(client, "ZoneEditingStage = %d", ZoneEditingStage[client]); +} + +/* + ============================================================== +*/ + +public const int SPECMODE_NONE = 0; +public const int SPECMODE_FIRSTPERSON = 4; +public const int SPECMODE_THIRDPERSON = 5; +public const int SPECMODE_FREELOOK = 6; + +public bool Player_IsSpectating(int client) +{ + return IsClientInGame(client) && GetClientTeam(client) == CS_TEAM_SPECTATOR; +} + +public int Player_GetSpectateTarget(int client) +{ + if (!Player_IsSpectating(client)) + { + return -1; + } + + return GetEntDataEnt2(client, Offset_ObserverTarget); +} + +public int Player_GetSpectateMode(int client) +{ + if (!Player_IsSpectating(client)) + { + return -1; + } + + return GetEntData(client, Offset_ObserverMode); +} + +public int Player_GetSpectateList(int client, int[] outlist) +{ + int ret = 0; + + for (int i = 1; i < MaxClients + 1; ++i) + { + if (Player_IsSpectating(i)) + { + int mode = Player_GetSpectateMode(i); + + /* + There is an intermediate state between thirdperson and freemode + but no need to support that I guess + */ + if (mode == SPECMODE_THIRDPERSON || mode == SPECMODE_FIRSTPERSON) + { + int target = Player_GetSpectateTarget(i); + + if (target == client) + { + outlist[ret] = i; + ++ret; + } + } + } + } + + return ret; +} + +/* + Adds the prefix and then adds the desired format +*/ +public void STA_PrintMessageToAllClients(const char[] format, any ...) +{ + char message[255]; + + FormatEx(message, sizeof(message), "%s", STA_MessagePrefix); + StrCat(message, sizeof(message), format); + + VFormat(message, sizeof(message), message, 2); + + CPrintToChatAll(message); +} + +/* + Adds the prefix and then adds the desired format +*/ +public void STA_PrintMessageToClient(int client, const char[] format, any ...) +{ + char message[255]; + + FormatEx(message, sizeof(message), "%s", STA_MessagePrefix); + StrCat(message, sizeof(message), format); + + VFormat(message, sizeof(message), message, 3); + + CPrintToChat(client, message); +} + +/* + Adds the prefix and then adds the desired format +*/ +public void STA_PrintMessageToClientSpectators(int client, const char[] format, any ...) +{ + char message[255]; + + char clientname[MAX_NAME_LENGTH]; + GetClientName(client, clientname, sizeof(clientname)); + + FormatEx(message, sizeof(message), "%s {lightblue}* %s * {ghostwhite}- ", STA_MessagePrefix, clientname); + StrCat(message, sizeof(message), format); + + VFormat(message, sizeof(message), message, 3); + + int specs[MAXPLAYERS + 1]; + int count = Player_GetSpectateList(client, specs); + + for (int i = 0; i < count; ++i) + { + CPrintToChat(specs[i], message); + } +} diff --git a/Source/STA/Time.inc b/Source/STA/Time.inc new file mode 100644 index 0000000..d96fd23 --- /dev/null +++ b/Source/STA/Time.inc @@ -0,0 +1,15 @@ +#if defined TIME_INCLUDED +#endinput +#endif +#define TIME_INCLUDED + +public void FormatTimeSpan(char[] buffer, int buffersize, float timeinseconds) +{ + int mstime = RoundFloat(timeinseconds * 1000.0); + + int minutes = (mstime / 1000) / 60; + int seconds = (mstime / 1000) % 60; + int milli = mstime % 1000; + + FormatEx(buffer, buffersize, "%02d:%02d.%03d", minutes, seconds, milli); +} diff --git a/Source/STA/Vector.inc b/Source/STA/Vector.inc new file mode 100644 index 0000000..07717f2 --- /dev/null +++ b/Source/STA/Vector.inc @@ -0,0 +1,119 @@ +#if defined VECTOR_INCLUDED +#endinput +#endif +#define VECTOR_INCLUDED + +public void GetArrayVector3(any[] array, int index, float[3] outvec) +{ + outvec[0] = array[index]; + outvec[1] = array[index + 1]; + outvec[2] = array[index + 2]; +} + +public void GetArrayVector2(any[] array, int index, float[2] outvec) +{ + outvec[0] = array[index]; + outvec[1] = array[index + 1]; +} + +public void CopyVector3ToArray(float[3] vector, any[] array, int index) +{ + array[index] = vector[0]; + array[index + 1] = vector[1]; + array[index + 2] = vector[2]; +} + +public void CopyVector2ToArray(float[2] vector, any[] array, int index) +{ + array[index] = vector[0]; + array[index + 1] = vector[1]; +} + +public void CopyVector2ToVector3(float[2] vec2, float[3] vec3) +{ + vec3[0] = vec2[0]; + vec3[1] = vec2[1]; + vec3[2] = 0.0; +} + +public void CopyVector3ToVector2(float[3] vec3, float[2] vec2) +{ + vec2[0] = vec3[0]; + vec2[1] = vec3[1]; +} + +public void CopyVector3ToVector3(float[3] source, float[3] dest) +{ + dest[0] = source[0]; + dest[1] = source[1]; + dest[2] = source[2]; +} + +public void ZeroVector3(float[3] vec3) +{ + vec3[0] = 0.0; + vec3[1] = 0.0; + vec3[2] = 0.0; +} + +public float SnapValueTo(float pos, int gridsize) +{ + int roundpos = RoundToNearest(pos); + + int closest = roundpos - roundpos % gridsize; + int offset = roundpos - closest; + + if (offset > (gridsize / 2)) + { + closest += gridsize; + } + + else if (offset < (-gridsize / 2)) + { + closest -= gridsize; + } + + return float(closest); +} + +public void SnapVectorTo(float[3] pos, int gridsize) +{ + pos[0] = (SnapValueTo(pos[0], gridsize)); + pos[1] = (SnapValueTo(pos[1], gridsize)); + pos[2] = (SnapValueTo(pos[2], gridsize)); +} + +public void MinVector(float[3] vec1, float[3] vec2, float[3] output) +{ + for (int i = 0; i < 3; ++i) + { + output[i] = vec1[i] < vec2[i] ? vec1[i] : vec2[i]; + } +} + +public void MaxVector(float[3] vec1, float[3] vec2, float[3] output) +{ + for (int i = 0; i < 3; ++i) + { + output[i] = vec1[i] > vec2[i] ? vec1[i] : vec2[i]; + } +} + +public void VectorLength(float[3] vec, float& xyvalue, float& zvalue) +{ + float veccopy[3]; + CopyVector3ToVector3(vec, veccopy); + + zvalue = veccopy[2]; + veccopy[2] = 0.0; + + xyvalue = GetVectorLength(veccopy); +} + +public void ClientVelocityLength(int client, float& xyvalue, float& zvalue) +{ + float velocity[3]; + GetEntPropVector(client, Prop_Data, "m_vecAbsVelocity", velocity); + + VectorLength(velocity, xyvalue, zvalue); +} diff --git a/Source/surf_segmentplay.sp b/Source/surf_segmentplay.sp new file mode 100644 index 0000000..4795161 --- /dev/null +++ b/Source/surf_segmentplay.sp @@ -0,0 +1,1235 @@ +#pragma semicolon 1 + +#include +#include +#include +#include +#include +#include +#include + +/* + https://forums.alliedmods.net/showthread.php?t=185016 +*/ +#include "Include\morecolors.inc" + +#include "STA\Time.inc" +#include "STA\Boundingbox.inc" +#include "STA\CollisionGroups.inc" +#include "STA\Menus.inc" +#include "STA\Permissions.inc" +#include "STA\Offsets.inc" +#include "STA\Formats.inc" +#include "STA\Vector.inc" + +#include "STA\STAPlayer.inc" +#include "STA\Checkpoints.inc" +#include "STA\ReplayFrame.inc" + +public Plugin myinfo = +{ + name = "Surf Segment Replay", + author = "CRASH FORT", + description = "", + version = "1", + url = "https://google.se" +}; + +#define BOT_Count 1 +int BotIDs[BOT_Count]; + +public void ResetPlayerReplaySegment(int client) +{ + if (!IsClientInGame(client)) + { + return; + } + + Player_SetIsSegmenting(client, false); + Player_SetIsRewinding(client, false); + Player_SetHasRun(client, false); + Player_SetPlayingReplay(client, false); + + /* + Don't change the observer move type if they are in spec, + it will make them bounce all over the place + */ + if (IsPlayingOnTeam(client)) + { + SetEntityMoveType(client, MOVETYPE_WALK); + } + + Player_SetRewindFrame(client, 0); + + Player_DeleteRecordFrames(client); + + if (Player_GetLinkedBotIndex(client) != 0) + { + RemoveBotFromPlayer(client); + } +} + +/* + Could do something clever here in case there are multiple players +*/ +public int GetFreeBotID() +{ + return 0; +} + +public void CreateBotForPlayer(int client) +{ + int index = BotIDs[GetFreeBotID()]; + + if (index == 0) + { + return; + } + + Player_SetLinkedBotIndex(client, index); + Bot_SetLinkedPlayerIndex(index, client); + + bool onteam = IsPlayingOnTeam(client); + + /* + Bots should join the same team as their player if they are on a team + */ + if (onteam) + { + ChangeClientTeam(index, GetClientTeam(client)); + } + + else + { + ChangeClientTeam(index, CS_TEAM_T); + } + + CS_RespawnPlayer(index); + + SetEntityRenderMode(index, RENDER_TRANSADD); + SetEntityRenderColor(index, 255, 255, 255, 100); + + /* + Having a bot in noclip and zero gravity ensures it's smooth + */ + SetEntityMoveType(index, MOVETYPE_NOCLIP); + SetEntityGravity(index, 0.0); +} + +public void RemoveBotFromPlayer(int client) +{ + int fakeid = Player_GetLinkedBotIndex(client); + ChangeClientTeam(fakeid, CS_TEAM_SPECTATOR); +} + +public int MenuHandler_ReplaySelect(Menu menu, MenuAction action, int param1, int param2) +{ + int client = param1; + + if (action == MenuAction_Select) + { + /* + "info" is the filename including extension + */ + char info[512]; + bool found = GetMenuItem(menu, param2, info, sizeof(info)); + + if (!found) + { + return; + } + + char mapbuf[MAX_NAME_LENGTH]; + GetCurrentMap(mapbuf, sizeof(mapbuf)); + + char filepath[512]; + FormatEx(filepath, sizeof(filepath), "%s%s\\%s", STA_ReplayPath, mapbuf, info); + + File file = OpenFile(filepath, "rb", true); + + if (file == null) + { + STA_PrintMessageToClient(client, "\"%s\" could not be opened", info); + return; + } + + ResetPlayerReplaySegment(client); + + int framecount; + ReadFileCell(file, framecount, 4); + + //PrintToChat(client, "%d frames", framecount); + + Player_CreateFrameArray(client); + + any frameinfo[FRAME_Length]; + + for (int i = 0; i < framecount; ++i) + { + for (int j = 0; j < FRAME_Length; ++j) + { + ReadFileCell(file, frameinfo[j], 4); + } + + Player_PushFrame(client, frameinfo); + } + + delete file; + + STA_PrintMessageToClient(client, "Loaded replay \"%s\"", info); + + Player_SetHasRun(client, true); + + STA_OpenSegmentReplayMenu(client); + } + + else if (action == MenuAction_Cancel) + { + + } + + else if (action == MenuAction_End) + { + delete menu; + } +} + +public int MenuHandler_SegmentReplay(Menu menu, MenuAction action, int param1, int param2) +{ + int client = param1; + + if (action == MenuAction_Select) + { + char info[3]; + bool found = GetMenuItem(menu, param2, info, sizeof(info)); + + if (!found) + { + return; + } + + int itemid = StringToInt(info); + + switch (itemid) + { + case SEG_Start: + { + //PrintToChat(client, "%s", SEG_Start); + + STA_PrintMessageToClient(client, "Started recording replay"); + + Player_SetIsSegmenting(client, true); + Player_SetIsRewinding(client, false); + Player_SetHasRun(client, false); + Player_SetPlayingReplay(client, false); + + Player_CreateFrameArray(client); + Player_SetRewindFrame(client, 0); + + STA_OpenSegmentReplayMenu(client); + } + + case SEG_LoadFromFile: + { + //PrintToChat(client, "%s", SEG_LoadFromFile); + + char mapbuf[MAX_NAME_LENGTH]; + GetCurrentMap(mapbuf, sizeof(mapbuf)); + + char mapreplaybuf[512]; + FormatEx(mapreplaybuf, sizeof(mapreplaybuf), "%s%s", STA_ReplayPath, mapbuf); + + if (!DirExists(mapreplaybuf)) + { + STA_PrintMessageToClient(client, "No replays available for \"%s\"", mapbuf); + return; + } + + DirectoryListing dirlist = OpenDirectory(mapreplaybuf, true); + + if (dirlist == null) + { + STA_PrintMessageToClient(client, "Could not open STA directory path"); + return; + } + + bool done = false; + FileType curtype; + char curname[512]; + int index = 0; + + Menu selectmenu = CreateMenu(MenuHandler_ReplaySelect); + SetMenuTitle(selectmenu, "Replay File Select"); + + do + { + done = ReadDirEntry(dirlist, curname, sizeof(curname), curtype); + + if (!done) + { + /* + Only the "." and ".." in here + */ + if (index == 2) + { + STA_PrintMessageToClient(client, "No replays available"); + return; + } + + break; + } + + ++index; + + /* + Skip the "." and ".." + */ + if (index > 2 && curtype == FileType_File) + { + //PrintToServer("%s", curname); + AddMenuItem(selectmenu, curname, curname); + } + } + while (done); + + delete dirlist; + + DisplayMenu(selectmenu, client, MENU_TIME_FOREVER); + } + + case MOV_SaveToFile: + { + //PrintToChat(client, "%s", MOV_SaveToFile); + + char mapbuf[MAX_NAME_LENGTH]; + GetCurrentMap(mapbuf, sizeof(mapbuf)); + + char playernamebuf[MAX_NAME_LENGTH]; + GetClientName(client, playernamebuf, sizeof(playernamebuf)); + + char newdirbuf[512]; + FormatEx(newdirbuf, sizeof(newdirbuf), "%s%s", STA_ReplayPath, mapbuf); + + CreateDirectory(newdirbuf, 0, true, "GAME"); + + int steamid = GetSteamAccountID(client); + + char timebuf[128]; + FormatTime(timebuf, sizeof(timebuf), "%Y %m %d, %H %M %S"); + + char namebuf[256]; + FormatEx(namebuf, sizeof(namebuf), "[%d] %s (%s)", steamid, playernamebuf, timebuf); + + char filename[512]; + FormatEx(filename, sizeof(filename), "%s\\%s.STA", newdirbuf, namebuf); + + File file = OpenFile(filename, "wb", true); + + if (file == null) + { + STA_PrintMessageToClient(client, "Could not save replay"); + return; + } + + int framecount = Player_GetRecordedFramesCount(client); + WriteFileCell(file, framecount, 4); + + any frameinfo[FRAME_Length]; + + for (int i = 0; i < framecount; ++i) + { + Player_GetFrame(client, i, frameinfo); + + for (int j = 0; j < FRAME_Length; ++j) + { + WriteFileCell(file, frameinfo[j], 4); + } + } + + delete file; + + STA_PrintMessageToClient(client, "Saved as \"%s\"", namebuf); + + STA_OpenSegmentReplayMenu(client); + } + + case SEG_Resume: + { + //PrintToChat(client, "%s", SEG_Resume); + + Player_SetIsRewinding(client, false); + SetEntityMoveType(client, MOVETYPE_WALK); + + Player_SetLastPausedTick(client, Player_GetRewindFrame(client)); + + Player_ResizeRecordFrameList(client, Player_GetRewindFrame(client)); + + STA_OpenSegmentReplayMenu(client); + } + + case SEG_Pause: + { + //PrintToChat(client, "%s", SEG_Pause); + + Player_SetIsRewinding(client, true); + SetEntityMoveType(client, MOVETYPE_NONE); + + //Player_SetRewindFrame(client, Player_GetRecordedFramesCount(client) - 1); + + STA_OpenSegmentReplayMenu(client); + } + + case SEG_GoBack: + { + //PrintToChat(client, "%s", SEG_GoBack); + + int newframe = Player_GetLastPausedTick(client); + + newframe = Player_ClampRecordFrame(client, newframe); + + Player_SetRewindFrame(client, newframe); + + Player_SetIsRewinding(client, true); + SetEntityMoveType(client, MOVETYPE_NONE); + + STA_OpenSegmentReplayMenu(client); + } + + case SEG_Play: + { + //PrintToChat(client, "%s", SEG_Play); + + Player_SetPlayingReplay(client, true); + Player_SetRewindFrame(client, 0); + + CreateBotForPlayer(client); + + bool onteam = IsPlayingOnTeam(client); + + if (!onteam) + { + Player_SetPreferredTeam(client, CS_TEAM_T); + } + + else + { + Player_SetPreferredTeam(client, GetClientTeam(client)); + + ChangeClientTeam(client, CS_TEAM_SPECTATOR); + SetEntDataEnt2(client, Offset_ObserverTarget, Player_GetLinkedBotIndex(client), true); + SetEntData(client, Offset_ObserverMode, 3, 4, true); + } + + STA_OpenSegmentReplayMenu(client); + } + + case SEG_Stop: + { + //PrintToChat(client, "%s", SEG_Stop); + + Player_SetHasRun(client, true); + Player_SetIsSegmenting(client, false); + Player_SetIsRewinding(client, false); + Player_SetPlayingReplay(client, false); + + SetEntityMoveType(client, MOVETYPE_WALK); + + STA_OpenSegmentReplayMenu(client); + } + + case MOV_Resume: + { + //PrintToChat(client, "%s", MOV_Resume); + + Player_SetIsRewinding(client, false); + + SetEntityMoveType(client, MOVETYPE_WALK); + + STA_OpenSegmentReplayMenu(client); + } + + case MOV_NewFrom: + { + //PrintToChat(client, "%s", MOV_NewFrom); + + /* + This reuses the active frame's data as the start for the new run + */ + + any frameinfo[FRAME_Length]; + + int frame = Player_GetRewindFrame(client); + Player_GetFrame(client, frame, frameinfo); + + ResetPlayerReplaySegment(client); + + Player_SetIsSegmenting(client, true); + Player_SetIsRewinding(client, true); + Player_SetHasRun(client, true); + Player_SetPlayingReplay(client, true); + + Player_CreateFrameArray(client); + Player_SetRewindFrame(client, 0); + + Player_PushFrame(client, frameinfo); + + /* + Forcing a teamchange like this does open the "select character" menu but does not + kill the player upon choosing + */ + ChangeClientTeam(client, Player_GetPreferredTeam(client)); + + SetEntityMoveType(client, MOVETYPE_NONE); + + STA_OpenSegmentReplayMenu(client); + } + + case MOV_Stop: + { + //PrintToChat(client, "%s", MOV_Stop); + + Player_SetHasRun(client, true); + Player_SetIsSegmenting(client, false); + Player_SetIsRewinding(client, false); + Player_SetPlayingReplay(client, false); + + RemoveBotFromPlayer(client); + + STA_OpenSegmentReplayMenu(client); + } + + case MOV_Pause: + { + //PrintToChat(client, "%s", MOV_Pause); + + Player_SetIsRewinding(client, true); + + STA_OpenSegmentReplayMenu(client); + } + + case MOV_ContinueFrom: + { + //PrintToChat(client, "%s", MOV_ContinueFrom); + + //PrintToChat(client, "0: %d %d", RecordFramesList[client].Length, CurrentRewindFrame[client]); + + int endframe = Player_GetRewindFrame(client) + 1; + int framecount = Player_GetRecordedFramesCount(client) - 1; + + if (endframe > framecount) + { + endframe = framecount; + } + + /* + Truncate anything past this point if we are not at the end + */ + Player_ResizeRecordFrameList(client, endframe); + + Player_SetIsSegmenting(client, true); + Player_SetIsRewinding(client, true); + Player_SetHasRun(client, false); + Player_SetPlayingReplay(client, false); + + Player_SetRewindFrame(client, endframe - 1); + + //PrintToChat(client, "1: %d %d", RecordFramesList[client].Length, CurrentRewindFrame[client]); + + ChangeClientTeam(client, Player_GetPreferredTeam(client)); + SetEntityMoveType(client, MOVETYPE_NONE); + + RemoveBotFromPlayer(client); + + STA_OpenSegmentReplayMenu(client); + } + + case ALL_RewindSpeed: + { + //PrintToChat(client, "%s", ALL_RewindSpeed); + + int curspeed = Player_GetRewindSpeed(client); + + curspeed *= 2; + + if (curspeed > 32) + { + curspeed = 1; + } + + Player_SetRewindSpeed(client, curspeed); + + STA_OpenSegmentReplayMenu(client); + } + + case ALL_JumpToStart: + { + //PrintToChat(client, "%s", ALL_JumpToStart); + + Player_SetRewindFrame(client, 0); + + STA_OpenSegmentReplayMenu(client); + } + + case ALL_JumpToEnd: + { + //PrintToChat(client, "%s", ALL_JumpToEnd); + + Player_SetRewindFrame(client, Player_GetRecordedFramesCount(client) - 1); + + STA_OpenSegmentReplayMenu(client); + } + } + } + + else if (action == MenuAction_Cancel) + { + ResetPlayerReplaySegment(client); + } + + else if (action == MenuAction_End) + { + delete menu; + } +} + +public bool IsPlayingOnTeam(int client) +{ + int team = GetClientTeam(client); + + return team != CS_TEAM_SPECTATOR && team != CS_TEAM_NONE && IsPlayerAlive(client); +} + +public void STA_OpenSegmentReplayMenu(int client) +{ + bool onteam = IsPlayingOnTeam(client); + + Menu menu = CreateMenu(MenuHandler_SegmentReplay); + SetMenuTitle(menu, "Segment Replay Menu"); + + //Player_PrintInfo(client); + + if (!Player_GetIsSegmenting(client)) + { + if (!Player_GetHasRun(client)) + { + if (onteam) + { + Menu_AddEnumEntry(menu, SEG_Start, "Start replay"); + } + + else + { + STA_PrintMessageToClient(client, "You must be in a team to start recording"); + } + + Menu_AddEnumEntry(menu, SEG_LoadFromFile, "Load replay"); + } + + else + { + if (Player_GetIsPlayingReplay(client)) + { + if (Player_GetIsRewinding(client)) + { + Menu_AddEnumEntry(menu, MOV_Resume, "Resume"); + + char speedstr[64]; + FormatEx(speedstr, sizeof(speedstr), "Speed: x%d", Player_GetRewindSpeed(client)); + Menu_AddEnumEntry(menu, ALL_RewindSpeed, speedstr); + + Menu_AddEnumEntry(menu, ALL_JumpToStart, "Jump to start"); + Menu_AddEnumEntry(menu, ALL_JumpToEnd, "Jump to end"); + + Menu_AddEnumEntry(menu, MOV_ContinueFrom, "Continue from here"); + Menu_AddEnumEntry(menu, MOV_NewFrom, "Start a new replay from here"); + } + + else + { + Menu_AddEnumEntry(menu, MOV_Pause, "Pause"); + //AddMenuItem(menu, MOV_Stop, "Remove bot & stop"); + } + + Menu_AddEnumEntry(menu, MOV_SaveToFile, "Save replay"); + } + + else + { + Menu_AddEnumEntry(menu, SEG_Play, "Create bot & play run"); + } + } + } + + else + { + if (Player_GetIsRewinding(client)) + { + Menu_AddEnumEntry(menu, SEG_Resume, "Resume"); + + char speedstr[64]; + FormatEx(speedstr, sizeof(speedstr), "Speed: x%d", Player_GetRewindSpeed(client)); + Menu_AddEnumEntry(menu, ALL_RewindSpeed, speedstr); + + Menu_AddEnumEntry(menu, ALL_JumpToStart, "Jump to start"); + Menu_AddEnumEntry(menu, ALL_JumpToEnd, "Jump to end"); + + Menu_AddEnumEntry(menu, SEG_Stop, "Stop & save"); + } + + else + { + Menu_AddEnumEntry(menu, SEG_Pause, "Pause"); + Menu_AddEnumEntry(menu, SEG_GoBack, "Go back to previous pause"); + } + } + + DisplayMenu(menu, client, MENU_TIME_FOREVER); +} + +public Action STA_ManageReplays(int client, int args) +{ + if (!HandlePlayerPermission(client)) + { + return Plugin_Handled; + } + + STA_OpenSegmentReplayMenu(client); + return Plugin_Handled; +} + +public Action CS_OnTerminateRound(float& delay, CSRoundEndReason& reason) +{ + /* + Don't allow the round to end + */ + return Plugin_Handled; +} + +public Action STA_RespawnPlayer(int client, int args) +{ + if (GetClientTeam(client) == CS_TEAM_SPECTATOR) + { + return Plugin_Handled; + } + + if (!IsPlayerAlive(client)) + { + CS_RespawnPlayer(client); + } + + return Plugin_Handled; +} + +/* + Step forward a single tick +*/ +public Action STA_StepForward(int client, int args) +{ + if (!HandlePlayerPermission(client)) + { + return Plugin_Handled; + } + + if (!Player_GetIsRewinding(client)) + { + return Plugin_Handled; + } + + int oldfactor = Player_GetRewindSpeed(client); + + Player_SetRewindSpeed(client, 1); + Player_SetHasFastForwardKeyDown(client, true); + + HandleReplayRewind(client); + + Player_SetRewindSpeed(client, oldfactor); + Player_SetHasFastForwardKeyDown(client, false); + + return Plugin_Handled; +} + +/* + Step back a single tick +*/ +public Action STA_StepBack(int client, int args) +{ + if (!HandlePlayerPermission(client)) + { + return Plugin_Handled; + } + + if (!Player_GetIsRewinding(client)) + { + return Plugin_Handled; + } + + int oldfactor = Player_GetRewindSpeed(client); + + Player_SetRewindSpeed(client, 1); + Player_SetHasRewindKeyDown(client, true); + + HandleReplayRewind(client); + + Player_SetRewindSpeed(client, oldfactor); + Player_SetHasRewindKeyDown(client, false); + + return Plugin_Handled; +} + +/* + ============================================================== +*/ + +public Action STA_RewindDown(int client, int args) +{ + if (!HandlePlayerPermission(client)) + { + return Plugin_Handled; + } + + if (!Player_GetIsRewinding(client)) + { + return Plugin_Handled; + } + + Player_SetHasRewindKeyDown(client, true); + + return Plugin_Handled; +} + +public Action STA_RewindUp(int client, int args) +{ + if (!HandlePlayerPermission(client)) + { + return Plugin_Handled; + } + + if (!Player_GetIsRewinding(client)) + { + return Plugin_Handled; + } + + Player_SetHasRewindKeyDown(client, false); + + return Plugin_Handled; +} + +/* + ============================================================== +*/ + +public Action STA_FastForwardDown(int client, int args) +{ + if (!HandlePlayerPermission(client)) + { + return Plugin_Handled; + } + + if (!Player_GetIsRewinding(client)) + { + return Plugin_Handled; + } + + Player_SetHasFastForwardKeyDown(client, true); + + return Plugin_Handled; +} + +public Action STA_FastForwardUp(int client, int args) +{ + if (!HandlePlayerPermission(client)) + { + return Plugin_Handled; + } + + if (!Player_GetIsRewinding(client)) + { + return Plugin_Handled; + } + + Player_SetHasFastForwardKeyDown(client, false); + + return Plugin_Handled; +} + +/* + ============================================================== +*/ + +public Action SetPlayerMoveTypeNone(Handle timer, any client) +{ + SetEntityMoveType(client, MOVETYPE_NONE); +} + +public Action OnPlayerSpawn(Event event, const char[] name, bool dontbroadcast) +{ + int userid = GetEventInt(event, "userid"); + int client = GetClientOfUserId(userid); + + /* + Disables player collision + */ + SetEntData(client, Offset_CollisionGroup, COLLISION_GROUP_DEBRIS_TRIGGER, 4, true); + + /* + A little delay is needed before a players movetype can be changed + NONE is used to remove the jittering when rewinding, it probably removes the client prediction + */ + if (Player_GetIsRewinding(client)) + { + CreateTimer(0.1, SetPlayerMoveTypeNone, client); + } +} + +public void OnPluginStart() +{ + RegConsoleCmd("sm_segmentreplay", STA_ManageReplays); + RegConsoleCmd("sm_respawn", STA_RespawnPlayer); + + RegConsoleCmd("sm_stepforward", STA_StepForward); + RegConsoleCmd("sm_stepback", STA_StepBack); + + RegConsoleCmd("+sm_rewind", STA_RewindDown); + RegConsoleCmd("-sm_rewind", STA_RewindUp); + RegConsoleCmd("+sm_fastforward", STA_FastForwardDown); + RegConsoleCmd("-sm_fastforward", STA_FastForwardUp); + + HookEvent("player_spawn", OnPlayerSpawn, EventHookMode_Post); + + Offsets_Init(); + CP_Init(); +} + +public Action GetBotIDs(Handle timer) +{ + int index = 0; + + for (int i = 1; i < MaxClients; i++) + { + if (IsClientInGame(i) && IsFakeClient(i) && !IsClientSourceTV(i)) + { + //PrintToServer("%d at index %d", i, index); + + ChangeClientTeam(i, CS_TEAM_SPECTATOR); + + CS_SetClientClanTag(i, "[STA]"); + + char namebuf[MAX_NAME_LENGTH]; + FormatEx(namebuf, sizeof(namebuf), "Cutie"); + + SetClientName(i, namebuf); + + BotIDs[index] = i; + ++index; + } + } +} + +public void OnMapStart() +{ + ServerCommand("sv_cheats 1"); + + ServerCommand("bot_chatter off"); + ServerCommand("bot_stop 1"); + + ServerCommand("bot_quota %d", BOT_Count); + ServerCommand("bot_zombie 1"); + ServerCommand("bot_stop 1"); + ServerCommand("mp_autoteambalance 0"); + ServerCommand("bot_join_after_player 0"); + ServerCommand("mp_limitteams 0"); + + /* + Don't let the bot commands be overwritten + */ + ServerExecute(); + + CreateTimer(1.0, GetBotIDs); + //GetBotIDs(); + + CP_MapStartInit(); +} + +public void OnPluginEnd() +{ + ServerCommand("bot_quota 0"); +} + +public void OnMapEnd() +{ + ServerCommand("bot_quota 0"); + + CP_OnMapEnd(); +} + +public void SetPlayerReplayFrame(int client, int targetclient, int frame) +{ + any frameinfo[FRAME_Length]; + Player_GetFrame(client, frame, frameinfo); + + float pos[3]; + float frameangles[2]; + float velocity[3]; + + GetArrayVector3(frameinfo, FRAME_PosX, pos); + GetArrayVector2(frameinfo, FRAME_AngX, frameangles); + GetArrayVector3(frameinfo, FRAME_VelX, velocity); + + float viewangles[3]; + CopyVector2ToVector3(frameangles, viewangles); + + TeleportEntity(targetclient, pos, viewangles, velocity); +} + +public void HandleReplayRewind(int client) +{ + int lastindex = Player_GetRecordedFramesCount(client) - 1; + int factor = Player_GetRewindSpeed(client); + int curframe = Player_GetRewindFrame(client); + + /* + Rewind + */ + if (Player_GetHasRewindKeyDown(client)) + { + Player_SetRewindFrame(client, curframe - factor); + } + + /* + Fast forard + */ + if (Player_GetHasFastForwardKeyDown(client)) + { + Player_SetRewindFrame(client, curframe + factor); + } + + curframe = Player_GetRewindFrame(client); + + if (curframe < 0) + { + Player_SetRewindFrame(client, 0); + } + + else if (curframe > lastindex) + { + Player_SetRewindFrame(client, lastindex); + } + + curframe = Player_GetRewindFrame(client); + + /* + Should display this in a center bottom panel thing instead + */ + if (lastindex > 0) + { + float tickinterval = GetTickInterval(); + + int timeframe = Player_GetRewindFrame(client) - Player_GetStartTimeReplayTick(client); + + if (timeframe < 0) + { + timeframe = 0; + } + + float curtime = timeframe * tickinterval; + + char curtimebuf[64]; + FormatTimeSpan(curtimebuf, sizeof(curtimebuf), curtime); + + PrintCenterText(client, "%d / %d\nTime: %s", curframe, lastindex, curtimebuf); + } +} + +public Action OnPlayerRunCmd(int client, int& buttons, int& impulse, float wishvel[3], float wishangles[3]) +{ + Action ret = Plugin_Continue; + + if (!IsFakeClient(client) && Player_GetIsCreatingCheckpoint(client)) + { + CP_Update(client); + return ret; + } + + bool isfake = false; + int fakeid = 0; + + if (IsFakeClient(client)) + { + isfake = true; + fakeid = client; + + client = Bot_GetLinkedPlayerIndex(client); + } + + //PrintToServer("%d", isfake); + + if (Player_GetIsSegmenting(client) && !isfake) + { + if (Player_GetIsRewinding(client)) + { + HandleReplayRewind(client); + SetPlayerReplayFrame(client, client, Player_GetRewindFrame(client)); + ret = Plugin_Handled; + } + + /* + Recording + */ + else + { + float pos[3]; + GetClientAbsOrigin(client, pos); + + float viewangles[3]; + GetClientEyeAngles(client, viewangles); + + float frameangles[2]; + CopyVector3ToVector2(viewangles, frameangles); + + /* + zzz + */ + float velocity[3]; + GetEntPropVector(client, Prop_Data, "m_vecAbsVelocity", velocity); + + any frameinfo[FRAME_Length]; + + frameinfo[FRAME_Buttons] = buttons; + + CopyVector3ToArray(pos, frameinfo, FRAME_PosX); + CopyVector2ToArray(frameangles, frameinfo, FRAME_AngX); + CopyVector3ToArray(velocity, frameinfo, FRAME_VelX); + + Player_PushFrame(client, frameinfo); + + Player_SetRewindFrame(client, Player_GetRecordedFramesCount(client) - 1); + + //PrintToServer("Client: %d Frame: %d", client, RecordFramesList[client].Length); + + ret = Plugin_Changed; + } + } + + /* + Rewinding while recording + */ + else if (Player_GetIsPlayingReplay(client) && Player_GetIsRewinding(client) && !isfake) + { + HandleReplayRewind(client); + SetPlayerReplayFrame(client, client, Player_GetRewindFrame(client)); + + ret = Plugin_Handled; + } + + /* + Playing + */ + else + { + if (Player_GetIsPlayingReplay(client) && isfake) + { + int curframe = Player_GetRewindFrame(client); + + any frameinfo[FRAME_Length]; + Player_GetFrame(client, curframe, frameinfo); + + float pos[3]; + float frameangles[2]; + float velocity[3]; + + GetArrayVector3(frameinfo, FRAME_PosX, pos); + GetArrayVector2(frameinfo, FRAME_AngX, frameangles); + GetArrayVector3(frameinfo, FRAME_VelX, velocity); + + float viewangles[3]; + CopyVector2ToVector3(frameangles, viewangles); + + bool normalproc = true; + + /* + Paused while watching a replay, this will allow the player + to edit a bot while it's playing + */ + if (Player_GetIsRewinding(client)) + { + normalproc = false; + + HandleReplayRewind(client); + + TeleportEntity(fakeid, pos, viewangles, velocity); + } + + { + /* + Parameter overrides to ensure a smooth playback + */ + wishvel[0] = 0.0; + wishvel[1] = 0.0; + wishvel[2] = 0.0; + + wishangles[0] = frameangles[0]; + wishangles[1] = frameangles[1]; + + buttons = frameinfo[FRAME_Buttons]; + + if (curframe == 0) + { + TeleportEntity(fakeid, pos, viewangles, velocity); + } + + else + { + float curpos[3]; + GetClientAbsOrigin(fakeid, curpos); + + /* + Force the bot back on course if it's off by this much squared, just for teleports and things + */ + #define BotCorrectDistance 96.0 * 96.0 + + float distance = GetVectorDistance(pos, curpos, true); + + if (distance > BotCorrectDistance) + { + //PrintToChat(client, "%0.2f, %0.2f", distance, BotCorrectDistance); + + TeleportEntity(fakeid, pos, viewangles, NULL_VECTOR); + } + + /* + Normal processing with just adjusting velocity between the recorded points + */ + else + { + float newvel[3]; + MakeVectorFromPoints(curpos, pos, newvel); + ScaleVector(newvel, 1.0 / GetTickInterval()); + + TeleportEntity(fakeid, NULL_VECTOR, viewangles, newvel); + } + } + + ret = Plugin_Changed; + } + + /* + When editing a bot it should not increment the current frame + */ + if (normalproc) + { + Player_IncrementRewindFrame(client); + curframe = Player_GetRewindFrame(client); + + int length = Player_GetRecordedFramesCount(client); + + if (curframe >= length) + { + Player_SetRewindFrame(client, 0); + } + } + } + } + + //PrintToServer("Client: %d Buttons: %d Angles: %0.2f %0.2f", client, buttons, angles[0], angles[1]); + return ret; +}