diff --git a/Couch/scripting/Couch.sp b/Couch/scripting/Couch.sp index ddf253a..b627e2a 100644 --- a/Couch/scripting/Couch.sp +++ b/Couch/scripting/Couch.sp @@ -64,6 +64,7 @@ ConVar g_cvModel; ConVar g_cvPushScale; ConVar g_cvPistolOnly; ConVar g_cvEffect; +ConVar g_cvEffectIntensity; ConVar g_cvIgnoreClip; ConVar g_cvCollisionGroup; @@ -116,6 +117,7 @@ public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max CreateNative("Couch_IsPlaced", Native_IsPlaced); CreateNative("Couch_Spawn", Native_Spawn); CreateNative("Couch_Bring", Native_Bring); + CreateNative("Couch_Goto", Native_Goto); CreateNative("Couch_Remove", Native_Remove); CreateNative("Couch_IsEffectEnabled", Native_IsEffectEnabled); CreateNative("Couch_SetEffectEnabled", Native_SetEffectEnabled); @@ -141,12 +143,14 @@ public void OnPluginStart() g_cvPushScale = CreateConVar("sm_couch_push_scale", "1.0", "Multiplier on the force bullets apply to the couch. 1.0 is the engine's own push.", FCVAR_NOTIFY, true, 0.0, true, 100.0); g_cvPistolOnly = CreateConVar("sm_couch_pistol_only", "0", "Only pistols and knives move the couch; other guns and grenades are ignored.", FCVAR_NOTIFY, true, 0.0, true, 1.0); g_cvEffect = CreateConVar("sm_couch_effect", "0", "Colour cycle and pulsing light on the couch.", FCVAR_NOTIFY, true, 0.0, true, 1.0); + g_cvEffectIntensity = CreateConVar("sm_couch_effect_intensity", "3", "Brightness of the couch effect light. 0 is off, 3 is the default, 8 is very bright. Applies immediately.", FCVAR_NOTIFY, true, 0.0, true, 10.0); g_cvIgnoreClip = CreateConVar("sm_couch_ignore_clip", "1", "Let the couch pass through func_clip_vphysics brushes.", FCVAR_NOTIFY, true, 0.0, true, 1.0); g_cvTierRequirement = CreateConVar("sm_couch_tier_requirement", "0", "Minimum playtime tier needed to shoot the couch.", FCVAR_NOTIFY, true, 0.0); g_cvCollisionGroup = CreateConVar("sm_couch_collision_group", "5", "Collision group given to the couch at spawn. 5 (player), 0 (prop)", FCVAR_NOTIFY, true, 0.0, true, 20.0); g_cvModel.AddChangeHook(OnModelChanged); g_cvEffect.AddChangeHook(OnEffectChanged); + g_cvEffectIntensity.AddChangeHook(OnEffectIntensityChanged); AutoExecConfig(true, "Couch"); @@ -338,6 +342,56 @@ bool TraceFilter_NotSelfOrCouch(int entity, int contentsMask, any client) return entity != client && entity != GetCouchIndex(); } +// Landing spot for a player sent to the couch: on top of it when it is upright, otherwise beside it. +// The couch is kept solid in the trace so the player never arrives inside it. +#define COUCH_GOTO_LIFT 8.0 +#define COUCH_GOTO_SIDE_GAP 24.0 + +bool FindCouchLandingSpot(int client, int couch, float vecSpot[3]) +{ + float vecOrigin[3]; + float vecMaxs[3]; + GetEntPropVector(couch, Prop_Send, "m_vecOrigin", vecOrigin); + GetEntPropVector(couch, Prop_Send, "m_vecMaxs", vecMaxs); + + // Tried in order: on top when upright, on top when tipped over, then each side. + float fTop = vecMaxs[2] + COUCH_GOTO_LIFT; + float fTipped = GetVectorLength(vecMaxs) + COUCH_GOTO_LIFT; + float fSideX = vecMaxs[0] + COUCH_GOTO_SIDE_GAP; + float fSideY = vecMaxs[1] + COUCH_GOTO_SIDE_GAP; + + float vecOffsets[6][3]; + vecOffsets[0][2] = fTop; + vecOffsets[1][2] = fTipped; + vecOffsets[2][0] = fSideX; vecOffsets[2][2] = COUCH_GOTO_LIFT; + vecOffsets[3][0] = -fSideX; vecOffsets[3][2] = COUCH_GOTO_LIFT; + vecOffsets[4][1] = fSideY; vecOffsets[4][2] = COUCH_GOTO_LIFT; + vecOffsets[5][1] = -fSideY; vecOffsets[5][2] = COUCH_GOTO_LIFT; + + float vecMins[3]; + float vecPlayerMaxs[3]; + GetClientMins(client, vecMins); + GetClientMaxs(client, vecPlayerMaxs); + + for (int i = 0; i < sizeof(vecOffsets); i++) + { + AddVectors(vecOrigin, vecOffsets[i], vecSpot); + + TR_TraceHullFilter(vecSpot, vecSpot, vecMins, vecPlayerMaxs, MASK_PLAYERSOLID, TraceFilter_NotSelf, client); + if (!TR_DidHit()) + return true; + } + + // Nothing clear; go high above the couch so the command still does something. + AddVectors(vecOrigin, vecOffsets[1], vecSpot); + return false; +} + +bool TraceFilter_NotSelf(int entity, int contentsMask, any client) +{ + return entity != client; +} + // The only place the couch is created. Returns the entity or -1. int SpawnCouchAt(const float vecOrigin[3], const float vecAngles[3]) { @@ -664,6 +718,22 @@ public void OnEffectChanged(ConVar convar, const char[] oldValue, const char[] n ApplyEffectState(); } +public void OnEffectIntensityChanged(ConVar convar, const char[] oldValue, const char[] newValue) +{ + ApplyLightIntensity(); +} + +// light_dynamic takes brightness as an input, so a running light updates in place. +void ApplyLightIntensity() +{ + int light = GetLightIndex(); + if (light == -1) + return; + + SetVariantInt(g_cvEffectIntensity.IntValue); + AcceptEntityInput(light, "brightness"); +} + // Brings the light and beacon in line with sm_couch_effect. Safe to call at any time. void ApplyEffectState() { @@ -699,7 +769,7 @@ int SpawnCouchLight(int couch) DispatchKeyValue(light, "pitch", "-90"); DispatchKeyValue(light, "angles", "0 0 0"); DispatchKeyValue(light, "_light", "255 255 255 255"); - DispatchKeyValue(light, "brightness", "3"); + DispatchKeyValueInt(light, "brightness", g_cvEffectIntensity.IntValue); DispatchKeyValue(light, "distance", "130"); DispatchKeyValue(light, "spotlight_radius", "70"); @@ -839,6 +909,26 @@ public any Native_Bring(Handle plugin, int numParams) return true; } +public any Native_Goto(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + if (client < 1 || client > MaxClients || !IsClientInGame(client)) + return ThrowNativeError(SP_ERROR_NATIVE, "Client %d is not in game", client); + + int couch = GetCouchIndex(); + if (couch == -1) + return false; + + float vecSpot[3]; + FindCouchLandingSpot(client, couch, vecSpot); + + // Arrives still, keeps their view angles. + float vecStill[3]; + TeleportEntity(client, vecSpot, NULL_VECTOR, vecStill); + + return true; +} + public any Native_Remove(Handle plugin, int numParams) { bool bHadSomething = g_bPlaced || GetCouchIndex() != -1; diff --git a/Couch/scripting/CouchAdmin.sp b/Couch/scripting/CouchAdmin.sp index 944d3a7..04c1bb7 100644 --- a/Couch/scripting/CouchAdmin.sp +++ b/Couch/scripting/CouchAdmin.sp @@ -23,6 +23,7 @@ public void OnPluginStart() RegAdminCmd("sm_couch", Command_Couch, ADMFLAG_CHANGEMAP, "Opens the couch menu"); RegAdminCmd("sm_spawncouch", Command_SpawnCouch, ADMFLAG_CHANGEMAP, "Spawns the couch where you are aiming"); RegAdminCmd("sm_bringcouch", Command_BringCouch, ADMFLAG_CHANGEMAP, "Brings the couch to where you are aiming"); + RegAdminCmd("sm_gotocouch", Command_GotoCouch, ADMFLAG_CHANGEMAP, "Teleports you to the couch"); RegAdminCmd("sm_removecouch", Command_RemoveCouch, ADMFLAG_CHANGEMAP, "Removes the couch"); RegAdminCmd("sm_couchpistolonly", Command_PistolOnly, ADMFLAG_CHANGEMAP, "sm_couchpistolonly [0|1] - only pistols and knives move the couch; no argument toggles"); } @@ -75,6 +76,15 @@ public Action Command_BringCouch(int client, int args) return Plugin_Handled; } +public Action Command_GotoCouch(int client, int args) +{ + if (!RequireInGame(client) || !RequireCore(client)) + return Plugin_Handled; + + DoGoto(client); + return Plugin_Handled; +} + // Needs no position, so it also works from the server console. public Action Command_RemoveCouch(int client, int args) { @@ -128,7 +138,7 @@ void DoSpawn(int client) return; } - ShowActivity2(client, PREFIX, "\x01Spawned the \x04couch\x01."); + ShowActivity2(client, PREFIX, "\x01Spawned the \x04couch\x01, it will respawn here each round until removed."); LogAction(client, -1, "\"%L\" spawned the couch.", client); } @@ -144,6 +154,24 @@ void DoBring(int client) LogAction(client, -1, "\"%L\" brought the couch.", client); } +void DoGoto(int client) +{ + if (!IsPlayerAlive(client)) + { + ReplyToCommand(client, "[SM] You must be alive to go to the couch."); + return; + } + + if (!Couch_Goto(client)) + { + ReplyToCommand(client, "[SM] There is no couch to go to. Use sm_spawncouch first."); + return; + } + + ShowActivity2(client, PREFIX, "\x01Teleported to the \x04couch\x01."); + LogAction(client, -1, "\"%L\" teleported to the couch.", client); +} + void DoRemove(int client) { if (!Couch_Remove()) @@ -190,6 +218,7 @@ void ShowCouchMenu(int client) // With a couch out, bring is the way to move it; without one there is nothing to bring. menu.AddItem("spawn", "Spawn couch at crosshair", bHasCouch ? ITEMDRAW_DISABLED : ITEMDRAW_DEFAULT); menu.AddItem("bring", "Bring couch to crosshair", bHasCouch ? ITEMDRAW_DEFAULT : ITEMDRAW_DISABLED); + menu.AddItem("goto", "Teleport to couch", bHasCouch ? ITEMDRAW_DEFAULT : ITEMDRAW_DISABLED); if (bHasCouch) menu.AddItem("remove", "Remove couch"); @@ -230,6 +259,8 @@ public int MenuHandler_Couch(Menu menu, MenuAction action, int param1, int param DoSpawn(client); else if (StrEqual(sInfo, "bring")) DoBring(client); + else if (StrEqual(sInfo, "goto")) + DoGoto(client); else if (StrEqual(sInfo, "remove")) DoRemove(client); else if (StrEqual(sInfo, "effect")) diff --git a/Couch/scripting/include/Couch.inc b/Couch/scripting/include/Couch.inc index 6996402..32d0378 100644 --- a/Couch/scripting/include/Couch.inc +++ b/Couch/scripting/include/Couch.inc @@ -67,6 +67,15 @@ native int Couch_Spawn(int client); */ native bool Couch_Bring(int client); +/** + * Teleports the client onto the couch, or beside it when the top is blocked. The client keeps + * their view angles and arrives with no velocity. + * + * @param client Client to teleport. Must be in game. + * @return True on success, false if there is no couch. + */ +native bool Couch_Goto(int client); + /** * Removes the couch and forgets its placement. * @@ -131,6 +140,7 @@ public SharedPlugin __pl_Couch = MarkNativeAsOptional("Couch_IsPlaced"); MarkNativeAsOptional("Couch_Spawn"); MarkNativeAsOptional("Couch_Bring"); + MarkNativeAsOptional("Couch_Goto"); MarkNativeAsOptional("Couch_Remove"); MarkNativeAsOptional("Couch_IsEffectEnabled"); MarkNativeAsOptional("Couch_SetEffectEnabled");