439 lines
17 KiB
SourcePawn
439 lines
17 KiB
SourcePawn
// Clockwork Castletown v1.0.0 by Professor Fartsalot for the FireHost Redux Community
|
|
#include <clientprefs>
|
|
#include <fcc_helper>
|
|
#include <morecolors>
|
|
#include <regex>
|
|
#include <sdktools>
|
|
#include <sourcemod>
|
|
#include <tf2_stocks>
|
|
#pragma newdecls required
|
|
#pragma semicolon 1
|
|
static char PLUGIN_VERSION[8] = "1.0.5";
|
|
|
|
public Plugin myinfo = {
|
|
name = "Fartsy's Clockwork Chaos - Framework",
|
|
author = "Fartsy",
|
|
description = "Framework for Fartsy's Clockwork Chaos (PL Mods)",
|
|
version = PLUGIN_VERSION,
|
|
url = "https://forums.firehostredux.com"
|
|
};
|
|
|
|
public void OnPluginStart() {
|
|
FccLogger(1, "####### STARTUP SEQUENCE INITIATED... PREPARE FOR THE END TIMES #######");
|
|
RegisterAndPrecacheAllFiles();
|
|
RegisterAllCommands();
|
|
SetupCoreData();
|
|
CPrintToChatAll("{darkred}Plugin Reloaded. If you do not hear music, please do !sounds and configure your preferences.");
|
|
// cvarSNDDefault = CreateConVar("sm_fartsyscc_sound", "3", "Default sound for new users, 3 = Everything, 2 = Sounds Only, 1 = Music Only, 0 = Nothing");
|
|
// SetCookieMenuItem(FartsysSNDSelected, 0, "Fartsys Clockwork Chaos Sound Preferences");
|
|
Format(LoggerInfo, sizeof(LoggerInfo), "####### STARTUP COMPLETE (v%s) #######", PLUGIN_VERSION);
|
|
FccLogger(1, LoggerInfo);
|
|
}
|
|
|
|
// Music system, see FartsysAss.sp:46
|
|
public void OnGameFrame() {
|
|
/* if (core.tickMusic) {
|
|
core.ticksMusic++;
|
|
if (core.ticksMusic >= core.refireTime) {
|
|
if (core.shouldStopMusic) {
|
|
for (int i = 1; i <= MaxClients; i++) {
|
|
StopSound(i, core.SNDCHAN, core.cachedPath);
|
|
core.shouldStopMusic = false;
|
|
}
|
|
}
|
|
core.songName = BGMArray[core.BGMINDEX].songName;
|
|
core.refireTime = BGMArray[core.BGMINDEX].refireTime;
|
|
core.ticksMusic = (core.tickOffset ? BGMArray[core.BGMINDEX].ticksOffset : 0);
|
|
CustomSoundEmitter(BGMArray[core.BGMINDEX].realPath, BGMArray[core.BGMINDEX].SNDLVL, true, 1, 1.0, 100);
|
|
CreateTimer(1.0, SyncMusic);
|
|
}
|
|
}*/
|
|
}
|
|
|
|
|
|
// Restart music for the new client
|
|
public Action RefireMusicForClient(Handle timer, int client) {
|
|
if (IsValidClient(client)) {
|
|
// if (GetClientTeam(client) == 0) CreateTimer(1.0, RefireMusicForClient, client);
|
|
// else if (GetClientTeam(client) == 2) CSEClient(client, BGMArray[core.BGMINDEX].realPath, BGMArray[core.BGMINDEX].SNDLVL, true, 1, 1.0, 100);
|
|
}
|
|
return Plugin_Stop;
|
|
}
|
|
|
|
// Queue music for new clients, also track their health.
|
|
public void OnClientPostAdminCheck(int client) {
|
|
// if (!IsFakeClient(client) && core.bgmPlaying) CreateTimer(1.0, RefireMusicForClient, client);
|
|
int steamID = GetSteamAccountID(client);
|
|
if (!steamID || steamID <= 10000) return;
|
|
// if (!core.bgmPlaying) SetupMusic(GetRandomInt(1, 4)); //Change these variables, I have no idea how this should work at the moment.
|
|
}
|
|
|
|
// Now that command definitions are done, lets make some things happen.
|
|
public void OnMapStart() {
|
|
// CreateTimer(1.0, SelectAdminTimer);
|
|
// FastFire("OnUser1 rain:Alpha:0:0.0:1");
|
|
// sudo(1002);
|
|
}
|
|
|
|
// Adverts for round information
|
|
public Action PerformMatchAdverts(Handle timer) {
|
|
// if (core.isMatch) {
|
|
char buffer[16];
|
|
char tbuffer[16];
|
|
char HintText[256];
|
|
// int sPos = RoundToFloor(core.ticksMusic / 66.6666666666);
|
|
// int tPos = RoundToFloor(core.refireTime / 66.6666666666);
|
|
// Format(buffer, 16, "%02d:%02d", sPos / 60, sPos % 60);
|
|
// Format(tbuffer, 16, "%02d:%02d", tPos / 60, tPos % 60);
|
|
// Format(HintText, sizeof(HintText), (bombState[0].isMoving ? "Payload: MOVING (%i/%i) | !sacpoints: %i/%i \n Music: %s (%s/%s)" : bombState[0].isReady ? "Payload: READY (%i/%i) | !sacpoints: %i/%i \n Music: %s (%s/%s)" : "Payload: PREPARING (%i/%i) | !sacpoints: %i/%i \n Music: %s (%s/%s)"), bombState[0].state, bombState[0].stateMax, core.sacPoints, core.sacPointsMax, core.songName, buffer, tbuffer);
|
|
// CreateTimer(2.5, PerformMatchAdverts);
|
|
for (int i = 1; i <= MaxClients; i++) {
|
|
if (IsValidClient(i)) {
|
|
PrintHintText(i, HintText);
|
|
StopSound(i, SNDCHAN_STATIC, "UI/hint.wav");
|
|
}
|
|
}
|
|
//}
|
|
return Plugin_Stop;
|
|
}
|
|
|
|
// Command: Get current song
|
|
public Action Command_GetCurrentSong(int client, int args) {
|
|
/*char buffer[16];
|
|
char tbuffer[16];
|
|
int sPos = RoundToFloor(core.ticksMusic / 66.6666666666);
|
|
int tPos = RoundToFloor(core.refireTime / 66.6666666666);
|
|
Format(buffer, 16, "%02d:%02d", sPos / 60, sPos % 60);
|
|
Format(tbuffer, 16, "%02d:%02d", tPos / 60, tPos % 60);
|
|
CPrintToChat(client, "The current song is: {limegreen}%s {orange}(%s / %s)", core.songName, buffer, tbuffer);*/
|
|
return Plugin_Handled;
|
|
}
|
|
|
|
// Command: Return the client to spawn
|
|
public Action Command_Return(int client, int args) {
|
|
if (!IsPlayerAlive(client)) {
|
|
CPrintToChat(client, "{red}[Core] You must be alive to use this command...");
|
|
return Plugin_Handled;
|
|
}
|
|
char name[128];
|
|
GetClientName(client, name, sizeof(name));
|
|
CPrintToChatAll("{darkviolet}[{forestgreen}CORE{darkviolet}] {white}Client {red}%s {white}began casting {darkviolet}/return{white}.", name);
|
|
// CustomSoundEmitter(SFXArray[41], 65, false, 0, 1.0, 100);
|
|
CreateTimer(5.0, ReturnClient, client);
|
|
return Plugin_Handled;
|
|
}
|
|
|
|
// Return the client to spawn
|
|
public Action ReturnClient(Handle timer, int clientID) {
|
|
// TeleportEntity(clientID, Return, NULL_VECTOR, NULL_VECTOR);
|
|
// CSEClient(clientID, SFXArray[42], 65, false, 0, 1.0, 100);
|
|
return Plugin_Handled;
|
|
}
|
|
|
|
// Join us on Discord!
|
|
public Action Command_Discord(int client, int args) {
|
|
CPrintToChat(client, "{darkviolet}[{forestgreen}CORE{darkviolet}] {white}Our Discord server URL is {darkviolet}https://discord.com/invite/HjQsDy6e2H{white}.");
|
|
ShowMOTDPanel(client, "FireHostRedux Discord", "https://discord.com/invite/HjQsDy6e2H", MOTDPANEL_TYPE_URL);
|
|
return Plugin_Handled;
|
|
}
|
|
|
|
// Check who died by what and announce it to chat.
|
|
/*
|
|
public Action EventDeath(Event Spawn_Event, const char[] Spawn_Name, bool Spawn_Broadcast) {
|
|
int client = GetClientOfUserId(Spawn_Event.GetInt("userid"));
|
|
int attacker = GetClientOfUserId(Spawn_Event.GetInt("attacker"));
|
|
char name[64];
|
|
char weapon[32];
|
|
Format(name, sizeof(name), attacker == 0 ? "[INTENTIONAL GAME DESIGN]" : "%N", IsValidClient(attacker) ? client : attacker);
|
|
Spawn_Event.GetString("weapon", weapon, sizeof(weapon));
|
|
if (0 < client <= MaxClients && IsClientInGame(client)) {
|
|
int damagebits = Spawn_Event.GetInt("damagebits");
|
|
if (IsValidClient(attacker) && core.sacrificedByClient) SacrificeClient(client, attacker, core.bombReset);
|
|
if (!attacker) {
|
|
switch (damagebits){
|
|
case 1:{
|
|
|
|
}
|
|
}
|
|
}
|
|
if (attacker != client) {
|
|
if (!FB_Database) return Plugin_Handled;
|
|
char query[256];
|
|
int steamID = (IsValidClient(attacker) ? GetSteamAccountID(attacker) : GetSteamAccountID(client));
|
|
Format(query, sizeof(query), IsValidClient(attacker) && !StrEqual(weapon, "world") ? "UPDATE ass_activity SET kills = kills +1, killssession = killssession + 1, lastkilledname = '%s', lastweaponused = '%s' WHERE steamid = %i;" : !StrEqual(weapon, "world") && !IsValidClient(attacker) ? "UPDATE ass_activity SET deaths = deaths + 1, deathssession = deathssession + 1, killedbyname = '%s', killedbyweapon = '%s' WHERE steamid = %i;" : "RETURN", name, weapon, steamID);
|
|
if (StrEqual(query, "RETURN")) return Plugin_Handled;
|
|
FB_Database.Query(Database_FastQuery, query);
|
|
}
|
|
}
|
|
return Plugin_Handled;
|
|
}*/
|
|
|
|
// Create a temp entity and fire an input - ADVANCED Mode
|
|
public Action FastFire(char[] input) {
|
|
int entity = CreateEntityByName("info_target");
|
|
if (!IsValidEdict(entity)) return Plugin_Handled;
|
|
DispatchSpawn(entity);
|
|
ActivateEntity(entity);
|
|
SetVariantString(input);
|
|
AcceptEntityInput(entity, "AddOutput");
|
|
AcceptEntityInput(entity, "FireUser1");
|
|
CreateTimer(0.0, DeleteEdict, entity);
|
|
return Plugin_Continue;
|
|
}
|
|
|
|
// Custom sound emitter, I don't know how many fucking times I've rewritten this! See potato.sp
|
|
// int flags:
|
|
// SND_NOFLAGS= 0, /**< Nothing */
|
|
// SND_CHANGEVOL = 1, /**< Change sound volume */
|
|
// SND_CHANGEPITCH = 2, /**< Change sound pitch */
|
|
// SND_STOP = 3, /**< Stop the sound */
|
|
// SND_SPAWNING = 4, /**< Used in some cases for ambients */
|
|
// SND_DELAY = 5, /**< Sound has an initial delay */
|
|
// SND_STOPLOOPING = 6, /**< Stop looping all sounds on the entity */
|
|
// SND_SPEAKER = 7, /**< Being played by a mic through a speaker */
|
|
// SND_SHOULDPAUSE = 8 /**< Pause if game is paused */
|
|
void CustomSoundEmitter(char[] sndName, int TSNDLVL, bool isBGM, int flags, float vol, int pitch) {
|
|
for (int i = 1; i <= MaxClients; i++) {
|
|
if (!IsValidClient(i)) continue;
|
|
// if (isBGM && (soundPreference[i] == 1 || soundPreference[i] == 3) || !isBGM && soundPreference[i] >= 2) EmitSoundToClient(i, sndName, _, core.SNDCHAN, TSNDLVL, flags, vol, pitch, _, _, _, _, _);
|
|
}
|
|
}
|
|
// Play sound to client. Ripped straight from potato. Allows us to play sounds directly to people when they join.
|
|
void CSEClient(int client, char[] sndName, int TSNDLVL, bool isBGM, int flags, float vol, int pitch) {
|
|
if (!IsValidClient(client)) return;
|
|
// if (isBGM && (soundPreference[client] == 1 || soundPreference[client] == 3) || !isBGM && soundPreference[client] >= 2) EmitSoundToClient(client, sndName, _, core.SNDCHAN, TSNDLVL, flags, vol, pitch, _, _, _, _, _);
|
|
}
|
|
|
|
// Remove edict allocated by temp entity
|
|
public Action DeleteEdict(Handle timer, any entity) {
|
|
if (IsValidEdict(entity)) RemoveEdict(entity);
|
|
return Plugin_Stop;
|
|
}
|
|
|
|
// Log debug info
|
|
void FccLogger(int logLevel, char[] logData) {
|
|
switch (logLevel) {
|
|
case 0: {
|
|
LogMessage("[DEBUG]: %s", logData);
|
|
}
|
|
case 1: {
|
|
LogMessage("[INFO]: %s", logData);
|
|
}
|
|
case 2: {
|
|
LogMessage("[WARN]: %s", logData);
|
|
}
|
|
case 3: {
|
|
LogMessage("[ERROR]: %s", logData);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Operator, core of the entire map
|
|
public Action Command_Operator(int args) {
|
|
char arg1[16];
|
|
GetCmdArg(1, arg1, sizeof(arg1));
|
|
int x = StringToInt(arg1);
|
|
PrintToServer("Directly calling sudo with %i", args);
|
|
sudo(x);
|
|
return Plugin_Continue;
|
|
}
|
|
|
|
void sudo(int task) {
|
|
Format(LoggerDbg, sizeof(LoggerDbg), "Calling sudo with %i", task);
|
|
FccLogger(0, LoggerDbg);
|
|
switch (task) {
|
|
// Match End, pick winning team based on distance
|
|
case 0: {
|
|
FastFire(PLR > PLB ? "OnUser1 WinRed:RoundWin::0.0:1" : PLR < PLB ? "OnUser1 WinBlu:RoundWin::0.0:1" : "OnUser1 WinStalemate:RoundWin::0.0:1");
|
|
return;
|
|
}
|
|
// Setup Begin
|
|
case 1: {
|
|
FccLogger(0, "Setup beginning....");
|
|
FastFire("OnUser1 PL1.TrackTrain:Stop::0.5:1");
|
|
FastFire("OnUser1 UI.PLCore:SwitchOverlay:1::0.0:1");
|
|
FastFire("OnUser1 UI.PLBlue00:SwitchOverlay:1::0.0:1");
|
|
FastFire("OnUser1 UI.PLBlue01:SwitchOverlay:1::0.0:1");
|
|
FastFire("OnUser1 UI.PLBlue02:SwitchOverlay:1::0.0:1");
|
|
if(RecedeTimer != INVALID_HANDLE) KillTimer(RecedeTimer);
|
|
isMatch = true;
|
|
PLB = 0;
|
|
PLL = 0;
|
|
PLM = false;
|
|
PLR = 0;
|
|
PLT = "N/A";
|
|
PLRL = 20;
|
|
BLC = false;
|
|
REC = false;
|
|
RECEDE = false;
|
|
CreateTimer(0.5, PayloadUpdateTimer);
|
|
CPrintToChatAll("{darkgreen} Clockwork Castletown ({lime}v%s{darkgreen}) - Escort the crystal the longest distance to win!", PLUGIN_VERSION);
|
|
return;
|
|
}
|
|
// Setup End / round start
|
|
case 2: {
|
|
FccLogger(1, "Match started!");
|
|
return;
|
|
}
|
|
case 3: {
|
|
// Payload started moving by blue
|
|
PLT = "BLU";
|
|
PLM = true;
|
|
FastFire("OnUser1 PL1.TrackTrain:StartForward::0.0:1");
|
|
KillTimer(RecedeTimer);
|
|
RecedeTimer = INVALID_HANDLE;
|
|
RECEDE = false;
|
|
return;
|
|
}
|
|
// Payload started moving by red
|
|
case 4: {
|
|
PLT = "RED";
|
|
PLM = true;
|
|
FastFire("OnUser1 PL1.TrackTrain:StartBackward::0.0:1");
|
|
KillTimer(RecedeTimer);
|
|
RecedeTimer = INVALID_HANDLE;
|
|
RECEDE = false;
|
|
return;
|
|
}
|
|
// Payload stopped moving
|
|
case 5: {
|
|
PLT = "NEUTRAL";
|
|
PLM = false;
|
|
FastFire("OnUser1 PL1.TrackTrain:Stop::0.0:1");
|
|
RecedeTimer = CreateTimer(45.0, BeginRecede, PLL);
|
|
return;
|
|
}
|
|
// Payload on blue side of map
|
|
case 6: {
|
|
PLL = 2;
|
|
return;
|
|
}
|
|
// Payload red side of map
|
|
case 7: {
|
|
PLL = 1;
|
|
return;
|
|
}
|
|
// Point 1 capture (this would be 75% capture from blue's perspective)
|
|
case 8: {
|
|
FastFire("OnUser1 PL4.CP:SetOwner:3:0.0:1"); //Change this to instead enable a capture area, stop the payload and disable capture area while the CP is capturing.. Re enable it after 15s. Check if blue is pushing and not receding.
|
|
return;
|
|
}
|
|
// Point -1 capture (this would be 75% capture from red's perspective)
|
|
case 9: {
|
|
FastFire("OnUser1 PL2.CP:SetOwner:2:0.0:1"); // see case 8
|
|
return;
|
|
}
|
|
// Op codes for payload progression
|
|
case 10: {
|
|
PrintToServer("Got 10, PLL %i", PLL);
|
|
switch(PLL){
|
|
case 1:{
|
|
PrintToChatAll("DEBUG: Checking if RECEDE: %b STREQUAL PLT RED: %b, PLB %i < PLPOS %i PLRL: %i...", RECEDE, StrEqual(PLT, "RED"), PLB, PLPOS[PLRL], PLRL);
|
|
if(RECEDE || StrEqual(PLT, "RED")) PLRL--; //If it's receding back to mid or being pushed by red back to mid... This is occuring on red's side of the map.
|
|
else PLRL++;//It's moving forward by blue to red.
|
|
if(PLB < PLPOS[PLRL]){
|
|
PLB+=5;
|
|
FastFire("OnUser1 test:scorebluepoints::0.0:1");
|
|
}
|
|
return;
|
|
}
|
|
case 2:{
|
|
PrintToChatAll("DEBUG: Checking if RECEDE: %b STREQUAL PLT BLU: %b, PLR %i < PLPOS %i PLRL: %i...", RECEDE, StrEqual(PLT, "BLU"), PLR, PLPOS[PLRL], PLRL);
|
|
if(RECEDE || StrEqual(PLT, "BLU")) PLRL++; //If it's receding back to mid or being pushed by blu back to mid... this is occuring on blu's side of the map.
|
|
else PLRL--; //It's moving forward by red to blue.
|
|
if(PLR < PLPOS[PLRL]){
|
|
PLR+=5;
|
|
FastFire("OnUser1 test:scoreredpoints::0.0:1");
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
// PrintToChatAll("BLU %i% RED %i%, PLM: %b, PLL: %i, PLT: %s", PLB, PLR, PLM, PLL, PLT);
|
|
return;
|
|
}
|
|
//Blu reached end of track (FF)
|
|
case 11: {
|
|
//FastFire("OnUser1 WinBlu:RoundWin::0.0:1"); //Change this to instead enable a capture area, stop the payload, and disable payload capture area while the CP is capturing... Victory after 15s (onCapTeam2)
|
|
}
|
|
//Red reached end of track (00)
|
|
case 12:{
|
|
//FastFire("OnUser1 WinRed:RoundWin::0.0:1"); //Change this, see case 11...
|
|
}
|
|
//Payload reached neutral point
|
|
case 13:{
|
|
PLT = "N/A";
|
|
PLL = 0;
|
|
PLM = false;
|
|
RECEDE = false;
|
|
PLRL = 20;
|
|
FastFire("OnUser1 PL1.TrackTrain:Stop::0.0:1");
|
|
}
|
|
//Debug
|
|
case 42:{
|
|
PLR+=5;
|
|
return;
|
|
}
|
|
case 69:{
|
|
PLB +=5;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Recede payload after 45 seconds
|
|
public Action BeginRecede(Handle timer, int side) {
|
|
if (side > 0) FastFire(side == 1 ? "OnUser1 PL1.TrackTrain:StartBackward::0.0:1" : "OnUser1 PL1.TrackTrain:StartForward::0.0:1");
|
|
RECEDE = true;
|
|
return Plugin_Stop;
|
|
}
|
|
|
|
// Show payload state
|
|
public Action PayloadUpdateTimer(Handle timer){
|
|
if(!isMatch) return Plugin_Stop;
|
|
CCH[30].BLU = BLC ? "hud/cconflict/cc_blu0B" : "hud/cconflict/cc_blu0A";
|
|
CCH[10].RED = REC ? "hud/cconflict/cc_red20" : "hud/cconflict/cc_red1F";
|
|
char state[64];
|
|
Format(state, sizeof(state), StrEqual(PLT, "BLU") ? CCH[PLRL].BLU : StrEqual(PLT, "RED") ? CCH[PLRL].RED : RECEDE ? CCH[PLRL].NEUTRAL : CCH[PLRL].IDLE);
|
|
ShowOverlayAll(state);
|
|
PrintToServer(state);
|
|
CreateTimer(0.5, PayloadUpdateTimer);
|
|
return Plugin_Stop;
|
|
}
|
|
|
|
// Recede payload after 45 seconds
|
|
public Action BeginRecede(Handle timer, int side) {
|
|
if (side > 0) FastFire(side == 1 ? "OnUser1 PL1.TrackTrain:StartBackward::0.0:1" : "OnUser1 PL1.TrackTrain:StartForward::0.0:1");
|
|
RECEDE = true;
|
|
return Plugin_Stop;
|
|
}
|
|
|
|
// Show payload state
|
|
public Action PayloadUpdateTimer(Handle timer){
|
|
if(!isMatch) return Plugin_Stop;
|
|
CCH[30].BLU = BLC ? "hud/cconflict/cc_blu0B" : "hud/cconflict/cc_blu0A";
|
|
CCH[10].RED = REC ? "hud/cconflict/cc_red20" : "hud/cconflict/cc_red1F";
|
|
char state[64];
|
|
Format(state, sizeof(state), StrEqual(PLT, "BLU") ? CCH[PLRL].BLU : StrEqual(PLT, "RED") ? CCH[PLRL].RED : RECEDE ? CCH[PLRL].NEUTRAL : CCH[PLRL].IDLE);
|
|
ShowOverlayAll(state);
|
|
PrintToServer(state);
|
|
CreateTimer(0.5, PayloadUpdateTimer);
|
|
return Plugin_Stop;
|
|
}
|
|
|
|
//Finish adding idle huds
|
|
//Add real spawn points and collisions
|
|
|
|
//Fix payload not going to final blue point...
|
|
|
|
//Finish adding map collisions, use Invisible for areas that are blocked by mesh...
|
|
//Finish fixing up clipping flag models and investigate clipping fence at spawns
|
|
|
|
//Music system and mini status hud
|
|
//Add back the %score% variable to hud. Make sure to use the actual score entry from base playerdestruction...
|
|
//Add capture point logic, each point taking 30 seconds to cap
|
|
//Add map easter eggs and other hazards...
|
|
|
|
//Brainstorming...
|
|
/* Crystal: MOVING (Team) //or Crystal: RECEDING (in 45s)?
|
|
* Song: FFXIV - Locus (XX:XX/XX:XX)
|
|
*/ |