diff --git a/addons/sourcemod/scripting/saxtonhale.sp b/addons/sourcemod/scripting/saxtonhale.sp index c450c008..bbf37eb8 100644 --- a/addons/sourcemod/scripting/saxtonhale.sp +++ b/addons/sourcemod/scripting/saxtonhale.sp @@ -134,9 +134,18 @@ enum COLLISION_GROUP_PUSHAWAY, // Nonsolid on client and server, pushaway in player code COLLISION_GROUP_NPC_ACTOR, // Used so NPCs in scripts ignore the player. - COLLISION_GROUP_NPC_SCRIPTED, // USed for NPCs in scripts that should not collide with each other - - LAST_SHARED_COLLISION_GROUP + COLLISION_GROUP_NPC_SCRIPTED = 19, // Used for NPCs in scripts that should not collide with each other. + + LAST_SHARED_COLLISION_GROUP, + + TF_COLLISIONGROUP_GRENADE = 20, + TFCOLLISION_GROUP_OBJECT, + TFCOLLISION_GROUP_OBJECT_SOLIDTOPLAYERMOVEMENT, + TFCOLLISION_GROUP_COMBATOBJECT, + TFCOLLISION_GROUP_ROCKETS, // Solid to players, but not player movement. ensures touch calls are originating from rocket + TFCOLLISION_GROUP_RESPAWNROOMS, + TFCOLLISION_GROUP_TANK, + TFCOLLISION_GROUP_ROCKET_BUT_NOT_WITH_OTHER_ROCKETS }; // entity effects @@ -179,6 +188,35 @@ enum DAMAGE_AIM, }; +enum SolidType_t +{ + SOLID_NONE = 0, // no solid model + SOLID_BSP = 1, // a BSP tree + SOLID_BBOX = 2, // an AABB + SOLID_OBB = 3, // an OBB (not implemented yet) + SOLID_OBB_YAW = 4, // an OBB, constrained so that it can only yaw + SOLID_CUSTOM = 5, // Always call into the entity for tests + SOLID_VPHYSICS = 6, // solid vphysics object, get vcollide from the model and collide with that + SOLID_LAST, +}; + +enum SolidFlags_t +{ + FSOLID_CUSTOMRAYTEST = 0x0001, // Ignore solid type + always call into the entity for ray tests + FSOLID_CUSTOMBOXTEST = 0x0002, // Ignore solid type + always call into the entity for swept box tests + FSOLID_NOT_SOLID = 0x0004, // Are we currently not solid? + FSOLID_TRIGGER = 0x0008, // This is something may be collideable but fires touch functions + // even when it's not collideable (when the FSOLID_NOT_SOLID flag is set) + FSOLID_NOT_STANDABLE = 0x0010, // You can't stand on this + FSOLID_VOLUME_CONTENTS = 0x0020, // Contains volumetric contents (like water) + FSOLID_FORCE_WORLD_ALIGNED = 0x0040, // Forces the collision rep to be world-aligned even if it's SOLID_BSP or SOLID_VPHYSICS + FSOLID_USE_TRIGGER_BOUNDS = 0x0080, // Uses a special trigger bounds separate from the normal OBB + FSOLID_ROOT_PARENT_ALIGNED = 0x0100, // Collisions are defined in root parent's local coordinate space + FSOLID_TRIGGER_TOUCH_DEBRIS = 0x0200, // This trigger will touch debris objects + + FSOLID_MAX_BITS = 10 +}; + enum { CHANNEL_INTRO = 0, @@ -338,7 +376,9 @@ ConVar tf_arena_preround_time; #include "vsh/abilities/ability_body_eat.sp" #include "vsh/abilities/ability_brave_jump.sp" +#include "vsh/abilities/ability_conditions.sp" #include "vsh/abilities/ability_dash_jump.sp" +#include "vsh/abilities/ability_dash_strike.sp" #include "vsh/abilities/ability_groundpound.sp" #include "vsh/abilities/ability_rage_bomb.sp" #include "vsh/abilities/ability_rage_bomb_projectile.sp" @@ -369,6 +409,7 @@ ConVar tf_arena_preround_time; #include "vsh/bosses/boss_painiscupcakes.sp" #include "vsh/bosses/boss_pyrocar.sp" #include "vsh/bosses/boss_redmond.sp" +#include "vsh/bosses/boss_samyro.sp" #include "vsh/bosses/boss_seeldier.sp" #include "vsh/bosses/boss_seeman.sp" #include "vsh/bosses/boss_uberranger.sp" @@ -637,6 +678,7 @@ public void OnPluginStart() SaxtonHale_RegisterClass("PainisCupcake", VSHClassType_Boss); SaxtonHale_RegisterClass("PyroCar", VSHClassType_Boss); SaxtonHale_RegisterClass("Redmond", VSHClassType_Boss); + SaxtonHale_RegisterClass("Samyro", VSHClassType_Boss); SaxtonHale_RegisterClass("Seeldier", VSHClassType_Boss); SaxtonHale_RegisterClass("SeeMan", VSHClassType_Boss); SaxtonHale_RegisterClass("UberRanger", VSHClassType_Boss); @@ -654,11 +696,13 @@ public void OnPluginStart() SaxtonHale_RegisterClass("Zombie", VSHClassType_Boss); //Register ability + SaxtonHale_RegisterClass("AddCond", VSHClassType_Ability); SaxtonHale_RegisterClass("BodyEat", VSHClassType_Ability); SaxtonHale_RegisterClass("Bomb", VSHClassType_Ability); SaxtonHale_RegisterClass("BombProjectile", VSHClassType_Ability); SaxtonHale_RegisterClass("BraveJump", VSHClassType_Ability); SaxtonHale_RegisterClass("DashJump", VSHClassType_Ability); + SaxtonHale_RegisterClass("DashStrike", VSHClassType_Ability); SaxtonHale_RegisterClass("GroundPound", VSHClassType_Ability); SaxtonHale_RegisterClass("RageAddCond", VSHClassType_Ability); SaxtonHale_RegisterClass("RageFreeze", VSHClassType_Ability); diff --git a/addons/sourcemod/scripting/vsh/abilities/ability_conditions.sp b/addons/sourcemod/scripting/vsh/abilities/ability_conditions.sp new file mode 100644 index 00000000..e1cbb87e --- /dev/null +++ b/addons/sourcemod/scripting/vsh/abilities/ability_conditions.sp @@ -0,0 +1,88 @@ +static ArrayList g_aConditions[TF_MAXPLAYERS + 1]; + +public void AddCond_Create(SaxtonHaleBase boss) +{ + if (g_aConditions[boss.iClient] == null) + g_aConditions[boss.iClient] = new ArrayList(); + g_aConditions[boss.iClient].Clear(); + + boss.SetPropFloat("AddCond", "CondDuration", 30.0); + boss.SetPropFloat("AddCond", "CondCooldownWait", 0.0); + boss.SetPropFloat("AddCond", "CondDuration", 8.0); + boss.SetPropFloat("AddCond", "CondMaxCharge", 1.0); +} + +public void AddCond_AddCond(SaxtonHaleBase boss, TFCond cond) +{ + g_aConditions[boss.iClient].Push(cond); +} + +public void AddCond_GetHudInfo(SaxtonHaleBase boss, char[] sMessage, int iLength, int iColor[4]) +{ + int iCharge; + + float flCondCooldownWait = boss.GetPropFloat("AddCond", "CondCooldownWait"); + if (flCondCooldownWait < GetGameTime()) + { + iCharge = RoundToFloor(boss.GetPropFloat("AddCond", "CondMaxCharge") * 100.0); + } + else + { + float flPercentage = (flCondCooldownWait - GetGameTime()) / boss.GetPropFloat("AddCond", "CondCooldown"); + iCharge = RoundToFloor((boss.GetPropFloat("AddCond", "CondMaxCharge") - flPercentage) * 100.0); + } + + if (iCharge >= 100) + Format(sMessage, iLength, "Ability Charge: %d%%%% - Press MOUSE2 to use!", iCharge); + else + Format(sMessage, iLength, "Ability Charge: %d%%%%", iCharge); +} + +public void AddCond_OnButtonPress(SaxtonHaleBase boss, int iButton) +{ + if (iButton == IN_ATTACK2 && GameRules_GetRoundState() != RoundState_Preround && !TF2_IsPlayerInCondition(boss.iClient, TFCond_Dazed)) + { + float flCondCooldownWait = boss.GetPropFloat("AddCond", "CondCooldownWait"); + if (flCondCooldownWait < GetGameTime()) + { + flCondCooldownWait = GetGameTime(); + boss.SetPropFloat("AddCond", "CondCooldownWait", flCondCooldownWait); + } + + float flPercentage = (flCondCooldownWait - GetGameTime()) / boss.GetPropFloat("AddCond", "CondCooldown"); + float flCharge = boss.GetPropFloat("AddCond", "CondMaxCharge") - flPercentage; + + if (flCharge < 1.0) + return; + + for (int i = 0; i < g_aConditions[boss.iClient].Length; i++) + { + TF2_AddCondition(boss.iClient, g_aConditions[boss.iClient].Get(i), boss.GetPropFloat("AddCond", "CondDuration")); + } + + flCondCooldownWait += boss.GetPropFloat("AddCond", "CondCooldown"); + boss.SetPropFloat("AddCond", "CondCooldownWait", flCondCooldownWait); + + char sSound[PLATFORM_MAX_PATH]; + boss.CallFunction("GetSoundAbility", sSound, sizeof(sSound), "AddCond"); + if (!StrEmpty(sSound)) + EmitSoundToAll(sSound, boss.iClient, SNDCHAN_VOICE, SNDLEVEL_SCREAMING); + } +} + +public void AddCond_OnRage(SaxtonHaleBase boss) +{ + if (boss.GetPropInt("AddCond", "RemoveOnRage")) + { + for (int i = 0; i < g_aConditions[boss.iClient].Length; i++) + { + TF2_RemoveCondition(boss.iClient, g_aConditions[boss.iClient].Get(i)); + } + + //If conditions are removed due to rage, refund remaining duration as cooldown reduction + float flCondCooldownWait = boss.GetPropFloat("AddCond", "CondCooldownWait"); + float flDurationRemaining = flCondCooldownWait - GetGameTime() - boss.GetPropFloat("AddCond", "CondCooldown") + boss.GetPropFloat("AddCond", "CondDuration"); + if (0 < flDurationRemaining < boss.GetPropFloat("AddCond", "CondDuration")) + boss.SetPropFloat("AddCond", "CondCooldownWait", flCondCooldownWait - flDurationRemaining); + } +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/vsh/abilities/ability_dash_strike.sp b/addons/sourcemod/scripting/vsh/abilities/ability_dash_strike.sp new file mode 100644 index 00000000..1a10c52d --- /dev/null +++ b/addons/sourcemod/scripting/vsh/abilities/ability_dash_strike.sp @@ -0,0 +1,323 @@ +#define DASHSTRIKE_SOUND "weapons/draw_sword.wav" +#define DASHSTRIKE_PARTICLE "wrenchmotron_teleport_flash" + +enum DashStrikeMode +{ + DashStrikeMode_None, + DashStrikeMode_Invisible, + DashStrikeMode_Dash, + DashStrikeMode_Rage +} + +static DashStrikeMode g_nDashStrikeMode[TF_MAXPLAYERS]; +static float g_flDashStrikeCooldownWait[TF_MAXPLAYERS]; +static float g_flDashStrikeProgress[TF_MAXPLAYERS]; +static bool g_bDashStrikeHitEntity[TF_MAXPLAYERS][2048]; +static int g_iDashStrikeDamage[TF_MAXPLAYERS]; + +public DashStrike_Create(SaxtonHaleBase boss) +{ + boss.SetPropFloat("DashStrike", "Cooldown", 0.5); + boss.SetPropFloat("DashStrike", "DashDistance", 1000.0); + boss.SetPropFloat("DashStrike", "RageDistance", 10000.0); + boss.SetPropInt("DashStrike", "DashDamage", 100); + boss.SetPropInt("DashStrike", "RageDamage", 500); + boss.SetPropFloat("DashStrike", "Speed", 10000.0); + + g_flDashStrikeCooldownWait[boss.iClient] = 0.0; + g_flDashStrikeProgress[boss.iClient] = 0.0; + g_nDashStrikeMode[boss.iClient] = DashStrikeMode_None; +} + +public void DashStrike_StartDash(SaxtonHaleBase boss) +{ + SetEntityMoveType(boss.iClient, MOVETYPE_NONE); + TF2_AddCondition(boss.iClient, TFCond_FreezeInput, TFCondDuration_Infinite); + + g_flDashStrikeProgress[boss.iClient] = 0.0; + for (int i = 0; i < sizeof(g_bDashStrikeHitEntity[]); i++) + g_bDashStrikeHitEntity[boss.iClient][i] = false; +} + +public void DashStrike_EndDash(SaxtonHaleBase boss) +{ + SetEntityMoveType(boss.iClient, MOVETYPE_WALK); + TF2_RemoveCondition(boss.iClient, TFCond_FreezeInput); + + g_nDashStrikeMode[boss.iClient] = DashStrikeMode_None; +} + +public void DashStrike_OnRage(SaxtonHaleBase boss) +{ + g_nDashStrikeMode[boss.iClient] = DashStrikeMode_Invisible; + SDKHook(boss.iClient, SDKHook_SetTransmit, DashStrike_SetTransmit); + SDKHook(boss.iClient, SDKHook_ShouldCollide, DashStrike_ShouldCollide); + + int iColor[4] = {255, 255, 255, 255}; + boss.CallFunction("GetRenderColor", iColor); + SetEntityRenderColor(boss.iClient, iColor[0], iColor[1], iColor[2], 32); + SetEntityRenderMode(boss.iClient, RENDER_TRANSCOLOR); + + float vecOrigin[3], vecAngles[3]; + GetClientAbsOrigin(boss.iClient, vecOrigin); + GetClientEyeAngles(boss.iClient, vecAngles); + CreateTimer(3.0, Timer_EntityCleanup, TF2_SpawnParticle(DASHSTRIKE_PARTICLE, vecOrigin, vecAngles)); +} + +public void DashStrike_OnThink(SaxtonHaleBase boss) +{ + if (g_flDashStrikeCooldownWait[boss.iClient] <= GetGameTime()) + g_flDashStrikeCooldownWait[boss.iClient] = 0.0; + + if (g_nDashStrikeMode[boss.iClient] == DashStrikeMode_Invisible) + { + //Calculate where dest would be + float flProgress; + + float vecOrigin[3], vecAngle[3], vecMins[3], vecMaxs[3]; + GetClientAbsOrigin(boss.iClient, vecOrigin); + GetClientEyeAngles(boss.iClient, vecAngle); + GetClientMins(boss.iClient, vecMins); + GetClientMaxs(boss.iClient, vecMaxs); + + do + { + float flDistance = boss.GetPropFloat("DashStrike", "Speed") * GetGameFrameTime(); + if (flDistance > boss.GetPropFloat("DashStrike", "RageDistance") - flProgress) + flDistance = boss.GetPropFloat("DashStrike", "RageDistance") - flProgress; + + flProgress += DashStrike_DoTrace(boss.iClient, vecOrigin, vecAngle, flDistance, vecOrigin); + } + while (flProgress < boss.GetPropFloat("DashStrike", "RageDistance") && !TR_DidHit()); + + //Line effect + float vecStart[3]; + GetClientAbsOrigin(boss.iClient, vecStart); + vecStart[2] += 8.0; + vecOrigin[2] += 8.0; + + TE_SetupBeamPoints(vecStart, vecOrigin, g_iSpritesLaserbeam, g_iSpritesGlow, 0, 10, 0.1, 3.0, 3.0, 10, 0.0, {0, 255, 0, 255}, 10); + TE_SendToClient(boss.iClient); + + //Ring effect + float flDiameter = vecMaxs[0] - vecMins[0]; + TE_SetupBeamRingPoint(vecOrigin, flDiameter, flDiameter + 1.0, g_iSpritesLaserbeam, g_iSpritesGlow, 0, 10, 0.1, 3.0, 0.0, {0, 255, 0, 255}, 10, 0); + TE_SendToClient(boss.iClient); + } + else if (g_nDashStrikeMode[boss.iClient] == DashStrikeMode_Dash || g_nDashStrikeMode[boss.iClient] == DashStrikeMode_Rage) + { + //How far do we go + float flDistance = boss.GetPropFloat("DashStrike", "Speed") * GetGameFrameTime(); + float flMaxDistance = g_nDashStrikeMode[boss.iClient] == DashStrikeMode_Dash ? boss.GetPropFloat("DashStrike", "DashDistance") : boss.GetPropFloat("DashStrike", "RageDistance"); + if (flDistance > flMaxDistance - g_flDashStrikeProgress[boss.iClient]) + flDistance = flMaxDistance - g_flDashStrikeProgress[boss.iClient]; + + float vecOrigin[3], vecAngle[3], vecEnd[3], vecVelocity[3]; + GetClientAbsOrigin(boss.iClient, vecOrigin); + GetClientEyeAngles(boss.iClient, vecAngle); + + switch (g_nDashStrikeMode[boss.iClient]) + { + case DashStrikeMode_Dash: DashStrike_DoTrace(boss.iClient, vecOrigin, vecAngle, flDistance, vecEnd, vecVelocity, boss.GetPropInt("DashStrike", "DashDamage")); + case DashStrikeMode_Rage: DashStrike_DoTrace(boss.iClient, vecOrigin, vecAngle, flDistance, vecEnd, vecVelocity, boss.GetPropInt("DashStrike", "RageDamage")); + } + + ScaleVector(vecVelocity, boss.GetPropFloat("DashStrike", "Speed")); + TeleportEntity(boss.iClient, vecEnd, NULL_VECTOR, vecVelocity); + + g_flDashStrikeProgress[boss.iClient] += flDistance; + if (g_flDashStrikeProgress[boss.iClient] >= flMaxDistance || TR_DidHit()) + DashStrike_EndDash(boss); + } +} + +public void DashStrike_GetHudText(SaxtonHaleBase boss, char[] sMessage, int iLength) +{ + if (g_nDashStrikeMode[boss.iClient] == DashStrikeMode_Invisible) + { + Format(sMessage, iLength, "%s\nRight click to use Dash-Strike!", sMessage); + } + else if (g_flDashStrikeCooldownWait[boss.iClient] != 0.0) + { + int iSec = RoundToNearest(g_flDashStrikeCooldownWait[boss.iClient]-GetGameTime()); + Format(sMessage, iLength, "%s\nDash-Strike cooldown %i second%s remaining!", sMessage, iSec, (iSec > 1) ? "s" : ""); + } + else + { + Format(sMessage, iLength, "%s\nRight click to use Dash-Strike!", sMessage); + } +} + +public void DashStrike_OnButtonPress(SaxtonHaleBase boss, int button) +{ + //Use dash-strike if not in cooldown and not during rage + if (button == IN_ATTACK2) + { + if (g_nDashStrikeMode[boss.iClient] == DashStrikeMode_Invisible) + { + SDKUnhook(boss.iClient, SDKHook_SetTransmit, DashStrike_SetTransmit); + SDKUnhook(boss.iClient, SDKHook_ShouldCollide, DashStrike_ShouldCollide); + + int iColor[4] = {255, 255, 255, 255}; + boss.CallFunction("GetRenderColor", iColor); + SetEntityRenderColor(boss.iClient, iColor[0], iColor[1], iColor[2], iColor[3]); + SetEntityRenderMode(boss.iClient, RENDER_NORMAL); + + float vecOrigin[3], vecAngles[3]; + GetClientAbsOrigin(boss.iClient, vecOrigin); + GetClientEyeAngles(boss.iClient, vecAngles); + CreateTimer(3.0, Timer_EntityCleanup, TF2_SpawnParticle(DASHSTRIKE_PARTICLE, vecOrigin, vecAngles)); + + BroadcastSoundToTeam(TFTeam_Spectator, DASHSTRIKE_SOUND); + g_nDashStrikeMode[boss.iClient] = DashStrikeMode_Rage; + DashStrike_StartDash(boss); + } + else if (g_nDashStrikeMode[boss.iClient] == DashStrikeMode_None && g_flDashStrikeCooldownWait[boss.iClient] == 0.0) + { + EmitSoundToAll(DASHSTRIKE_SOUND, boss.iClient, SNDCHAN_VOICE, SNDLEVEL_SCREAMING); + g_nDashStrikeMode[boss.iClient] = DashStrikeMode_Dash; + DashStrike_StartDash(boss); + + g_flDashStrikeCooldownWait[boss.iClient] = GetGameTime() + boss.GetPropFloat("DashStrike", "Cooldown"); + } + } +} + +public Action DashStrike_OnTakeDamage(SaxtonHaleBase boss, int &attacker, int &inflictor, float &damage, int &damagetype, int &weapon, float damageForce[3], float damagePosition[3], int damagecustom) +{ + if (g_nDashStrikeMode[boss.iClient] == DashStrikeMode_Invisible) + return Plugin_Stop; + + return Plugin_Continue; +} + +public void DashStrike_OnPlayerKilled(SaxtonHaleBase boss, Event event) +{ + //Allow use dash now on kill + g_flDashStrikeCooldownWait[boss.iClient] = 0.0; +} + +public Action DashStrike_OnSoundPlayed(SaxtonHaleBase boss, int clients[MAXPLAYERS], int &numClients, char sample[PLATFORM_MAX_PATH], int &channel, float &volume, int &level, int &pitch, int &flags, char soundEntry[PLATFORM_MAX_PATH], int &seed) +{ + if (g_nDashStrikeMode[boss.iClient] != DashStrikeMode_None) + return Plugin_Handled; //Silent any sounds during dash + + return Plugin_Continue; +} + +public void DashStrike_OnEntityCreated(SaxtonHaleBase boss, int iEntity, const char[] sClassname) +{ + if (strcmp(sClassname, "tf_ragdoll") == 0) + RequestFrame(DashStrike_RagdollSpawn, EntIndexToEntRef(iEntity)); +} + +public void DashStrike_Destroy(SaxtonHaleBase boss) +{ + SDKUnhook(boss.iClient, SDKHook_SetTransmit, DashStrike_SetTransmit); + SDKUnhook(boss.iClient, SDKHook_ShouldCollide, DashStrike_ShouldCollide); + + if (g_nDashStrikeMode[boss.iClient] != DashStrikeMode_None) + DashStrike_EndDash(boss); +} + +public void DashStrike_Precache(SaxtonHaleBase boss) +{ + PrecacheParticleSystem(DASHSTRIKE_PARTICLE); +} + +float DashStrike_DoTrace(int iClient, const float vecStart[3], const float vecAngle[3], float flDistance, float vecEnd[3], float vecVelocity[3] = {0.0, 0.0, 0.0}, int iDamage = 0) +{ + g_iDashStrikeDamage[iClient] = iDamage; + + float vecMins[3], vecMaxs[3]; + GetClientMins(iClient, vecMins); + GetClientMaxs(iClient, vecMaxs); + + GetAngleVectors(vecAngle, vecVelocity, NULL_VECTOR, NULL_VECTOR); + ScaleVector(vecVelocity, flDistance); + AddVectors(vecVelocity, vecStart, vecEnd); + + //Start hull to see how far we can go + TR_TraceHullFilter(vecStart, vecEnd, vecMins, vecMaxs, MASK_PLAYERSOLID, TraceRay_DashStrike, iClient); + TR_GetEndPosition(vecEnd); + + MakeVectorFromPoints(vecStart, vecEnd, vecVelocity); + float flDistanceMade = GetVectorLength(vecVelocity); + if (flDistanceMade < flDistance) + { + //There still distance left to use, try without taking into account with vertical + float vecBuffer[3]; + vecBuffer = vecAngle; + vecBuffer[0] = 0.0; + GetAngleVectors(vecBuffer, vecVelocity, NULL_VECTOR, NULL_VECTOR); + ScaleVector(vecVelocity, flDistance - flDistanceMade); + + vecBuffer = vecEnd; + AddVectors(vecBuffer, vecVelocity, vecEnd); + + TR_TraceHullFilter(vecBuffer, vecEnd, vecMins, vecMaxs, MASK_PLAYERSOLID, TraceRay_DashStrike, iClient); + TR_GetEndPosition(vecEnd); + + MakeVectorFromPoints(vecBuffer, vecEnd, vecVelocity); + flDistanceMade += GetVectorLength(vecVelocity); + } + + NormalizeVector(vecVelocity, vecVelocity); + g_iDashStrikeDamage[iClient] = 0; + return flDistanceMade; +} + +bool TraceRay_DashStrike(int iEntity, int iMask, int iClient) +{ + if (g_bDashStrikeHitEntity[iClient][iEntity]) + return false; //Already hit this entity, don't damage again + + g_bDashStrikeHitEntity[iClient][iEntity] = true; + + if (0 < iEntity <= MaxClients) + { + if (g_iDashStrikeDamage[iClient] > 0 && GetClientTeam(iEntity) != GetClientTeam(iClient)) + SDKHooks_TakeDamage(iEntity, iClient, iClient, float(g_iDashStrikeDamage[iClient]), DMG_CLUB|DMG_PREVENT_PHYSICS_FORCE, TF2_GetItemInSlot(iClient, WeaponSlot_Melee)); + + return false; + } + else if (iEntity > MaxClients) + { + char sClassname[256]; + GetEntityClassname(iEntity, sClassname, sizeof(sClassname)); + if (StrContains(sClassname, "obj_") == 0) + { + if (g_iDashStrikeDamage[iClient] > 0 && GetEntProp(iEntity, Prop_Send, "m_iTeamNum") != GetClientTeam(iClient)) + SDKHooks_TakeDamage(iEntity, iClient, iClient, float(g_iDashStrikeDamage[iClient]), DMG_CLUB, TF2_GetItemInSlot(iClient, WeaponSlot_Melee)); + + return false; + } + } + + //Dont want to collide dropped weapon and ammo pack + return GetEntProp(iEntity, Prop_Send, "m_CollisionGroup") != COLLISION_GROUP_DEBRIS; +} + +public void DashStrike_RagdollSpawn(int iRef) +{ + int iEntity = EntRefToEntIndex(iRef); + if (iEntity <= 0 || !IsValidEntity(iEntity)) return; + + SetEntProp(iEntity, Prop_Send, "m_iDamageCustom", TF_CUSTOM_DECAPITATION); +} + +public Action DashStrike_SetTransmit(int iEntity, int iClient) +{ + if (iEntity == iClient || !IsPlayerAlive(iClient) || TF2_GetClientTeam(iClient) == TF2_GetClientTeam(iEntity)) + return Plugin_Continue; + + return Plugin_Stop; +} + +public bool DashStrike_ShouldCollide(int iClient, int iCollisionGroup, int iMask, bool bOriginal) +{ + if (iCollisionGroup == COLLISION_GROUP_PLAYER || iCollisionGroup == TFCOLLISION_GROUP_OBJECT_SOLIDTOPLAYERMOVEMENT) + return false; + + return bOriginal; +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/vsh/abilities/ability_rage_conditions.sp b/addons/sourcemod/scripting/vsh/abilities/ability_rage_conditions.sp index 3e09fc5d..2c010bd8 100644 --- a/addons/sourcemod/scripting/vsh/abilities/ability_rage_conditions.sp +++ b/addons/sourcemod/scripting/vsh/abilities/ability_rage_conditions.sp @@ -1,14 +1,17 @@ static ArrayList g_aConditions[TF_MAXPLAYERS]; -public void RageAddCond_AddCond(SaxtonHaleBase boss, TFCond cond) +void RageAddCond_AddCond(SaxtonHaleBase boss, TFCond cond, bool bSuperRage = false) { - g_aConditions[boss.iClient].Push(cond); + int iLength = g_aConditions[boss.iClient].Length; + g_aConditions[boss.iClient].Resize(iLength+1); + g_aConditions[boss.iClient].Set(iLength, cond, 0); + g_aConditions[boss.iClient].Set(iLength, bSuperRage, 1); } public void RageAddCond_Create(SaxtonHaleBase boss) { if (g_aConditions[boss.iClient] == null) - g_aConditions[boss.iClient] = new ArrayList(); + g_aConditions[boss.iClient] = new ArrayList(2); g_aConditions[boss.iClient].Clear(); boss.SetPropFloat("RageAddCond", "RageCondDuration", 5.0); @@ -24,5 +27,10 @@ public void RageAddCond_OnRage(SaxtonHaleBase boss) flDuration *= boss.GetPropFloat("RageAddCond", "RageCondSuperRageMultiplier"); for (int i = 0; i < iLength; i++) - TF2_AddCondition(boss.iClient, g_aConditions[boss.iClient].Get(i), flDuration); -} + { + bool bSuperRageCond = g_aConditions[boss.iClient].Get(i, 1); + + if (!bSuperRageCond || bSuperRageCond && boss.bSuperRage) + TF2_AddCondition(boss.iClient, g_aConditions[boss.iClient].Get(i, 0), flDuration); + } +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/vsh/bosses/boss_samyro.sp b/addons/sourcemod/scripting/vsh/bosses/boss_samyro.sp new file mode 100644 index 00000000..02fbce38 --- /dev/null +++ b/addons/sourcemod/scripting/vsh/bosses/boss_samyro.sp @@ -0,0 +1,187 @@ +#define SAMYRO_MUSIC "vsh_rewrite/samyro/samyro_music.mp3" + +static int g_iSamyroModelHat; +static int g_iSamyroModelMask; +static int g_iSamyroModelKatana; +static int g_iSamyroModelHands; + +static float g_flClientRageGainLastTime[TF_MAXPLAYERS + 1]; + +static char g_strSamyroRoundStart[][] = { + "vo/pyro_battlecry01.mp3", + "vo/pyro_battlecry02.mp3" +}; + +static char g_strSamyroRoundWin[][] = { + "vo/taunts/pyro/pyro_taunt_head_pain_21.mp3", + "vo/taunts/pyro/pyro_taunt_head_pain_22.mp3" +}; + +static char g_strSamyroRoundLose[][] = { + "vo/pyro_paincrticialdeath01.mp3", + "vo/pyro_paincrticialdeath02.mp3", + "vo/pyro_paincrticialdeath03.mp3", + "vo/taunts/pyro/pyro_taunt_rps_lose_03.mp3" +}; + +static char g_strSamyroKill[][] = { + "vo/taunts/pyro_taunts01.mp3", + "vo/taunts/pyro_taunts02.mp3", + "vo/taunts/pyro_taunts03.mp3", + "vo/taunts/pyro_taunts04.mp3", + "vo/compmode/cm_pyro_pregamelostlast_02.mp3", + "vo/compmode/cm_pyro_pregamelostlast_03.mp3" +}; + +static char g_strSamyroLastMan[][] = { + "vo/cm_pyro_pregamewonlast_01.mp3" +}; + +static char g_strSamyroAbility[][] = { + "items/samurai/tf_samurai_noisemaker_seta_01.wav", + "items/samurai/tf_samurai_noisemaker_seta_02.wav", + "items/samurai/tf_samurai_noisemaker_seta_03.wav" +}; + +public void Samyro_Create(SaxtonHaleBase boss) +{ + boss.CreateClass("WallClimb"); + boss.CreateClass("DashStrike"); + + boss.iBaseHealth = 700; + boss.iHealthPerPlayer = 650; + boss.nClass = TFClass_Pyro; + boss.iMaxRageDamage = 2500; +} + +public void Samyro_GetBossName(SaxtonHaleBase boss, char[] sName, int length) +{ + strcopy(sName, length, "Samyro"); +} + +public void Samyro_GetBossInfo(SaxtonHaleBase boss, char[] sInfo, int length) +{ + StrCat(sInfo, length, "\nHealth: Low"); + StrCat(sInfo, length, "\n "); + StrCat(sInfo, length, "\nAbilities"); + StrCat(sInfo, length, "\n- Passive Rage Gain"); + StrCat(sInfo, length, "\n- Wall Climb"); + StrCat(sInfo, length, "\n- Agility powerup on secondary attack"); + StrCat(sInfo, length, "\n "); + StrCat(sInfo, length, "\nRage"); + StrCat(sInfo, length, "\n- Gain the Haste powerup and a defensive buff for 12 seconds"); + StrCat(sInfo, length, "\n- Rage overrides Agility powerup"); + StrCat(sInfo, length, "\n- 200%% Rage: Become übercharged and extend duration by 2 seconds"); +} + +public void Samyro_OnSpawn(SaxtonHaleBase boss) +{ + char attribs[128]; + Format(attribs, sizeof(attribs), "2 ; 2.80 ; 252 ; 0.5 ; 259 ; 1.0 ; 180 ; 0.0 ; 226 ; 0.0"); + int iWeapon = boss.CallFunction("CreateWeapon", 357, "tf_weapon_katana", 100, TFQual_Collectors, attribs); + if (iWeapon > MaxClients) + { + SetEntProp(iWeapon, Prop_Send, "m_nModelIndexOverrides", g_iSamyroModelKatana); + + int iViewModel = CreateViewModel(boss.iClient, g_iSamyroModelKatana); + SetEntPropEnt(iViewModel, Prop_Send, "m_hWeaponAssociatedWith", iWeapon); + SetEntPropEnt(iWeapon, Prop_Send, "m_hExtraWearableViewModel", iViewModel); + + CreateViewModel(boss.iClient, g_iSamyroModelHands); + SetEntProp(GetEntPropEnt(boss.iClient, Prop_Send, "m_hViewModel"), Prop_Send, "m_fEffects", EF_NODRAW); + + SetEntPropEnt(boss.iClient, Prop_Send, "m_hActiveWeapon", iWeapon); + } + /* + Half-Zatoichi attributes: + + 2: damage bonus + 252: reduction in push force taken from damage + 259: deals 3x falling damage to the player you land on + 180: 0% health restored on kill + 226: not honorbound + */ + + int iWearable = -1; + + iWearable = boss.CallFunction("CreateWeapon", 627, "tf_wearable", 1, TFQual_Collectors, ""); //The Flamboyant Flamenco + if (iWearable > MaxClients) + SetEntProp(iWearable, Prop_Send, "m_nModelIndexOverrides", g_iSamyroModelHat); + + iWearable = boss.CallFunction("CreateWeapon", 570, "tf_wearable", 1, TFQual_Collectors, ""); //The Last Breath + if (iWearable > MaxClients) + SetEntProp(iWearable, Prop_Send, "m_nModelIndexOverrides", g_iSamyroModelMask); +} + +public void Samyro_OnThink(SaxtonHaleBase boss) +{ + if (GameRules_GetRoundState() == RoundState_Preround) + return; + + //Passive rage gain + if (g_flClientRageGainLastTime[boss.iClient] <= GetGameTime() - 0.05) + { + boss.CallFunction("AddRage", 1); + g_flClientRageGainLastTime[boss.iClient] = GetGameTime(); + } +} + +public void Samyro_OnRage(SaxtonHaleBase boss) +{ + //Prevent boss from using secondary ability while rage is active by adding duration to cooldown + if (boss.HasClass("AddCond") && boss.HasClass("RageAddCond")) + { + float flRageDuration = boss.bSuperRage ? boss.GetPropFloat("RageAddCond", "RageCondDuration") * boss.GetPropFloat("RageAddCond", "RageCondSuperRageMultiplier") : boss.GetPropFloat("RageAddCond", "RageCondDuration"); + boss.SetPropFloat("AddCond", "CondCooldownWait", boss.GetPropFloat("AddCond", "CondCooldownWait") + flRageDuration); + } +} + +public void Samyro_GetSound(SaxtonHaleBase boss, char[] sSound, int length, SaxtonHaleSound iSoundType) +{ + switch (iSoundType) + { + case VSHSound_RoundStart: strcopy(sSound, length, g_strSamyroRoundStart[GetRandomInt(0, sizeof(g_strSamyroRoundStart) - 1)]); + case VSHSound_Win: strcopy(sSound, length, g_strSamyroRoundWin[GetRandomInt(0, sizeof(g_strSamyroRoundWin) - 1)]); + case VSHSound_Lose: strcopy(sSound, length, g_strSamyroRoundLose[GetRandomInt(0, sizeof(g_strSamyroRoundLose) - 1)]); + case VSHSound_Lastman: strcopy(sSound, length, g_strSamyroLastMan[GetRandomInt(0, sizeof(g_strSamyroLastMan) - 1)]); + } +} + +public void Samyro_GetSoundAbility(SaxtonHaleBase boss, char[] sSound, int length, const char[] sType) +{ + if (strcmp(sType, "AddCond") == 0) + strcopy(sSound, length, g_strSamyroAbility[GetRandomInt(0, sizeof(g_strSamyroAbility) - 1)]); +} + +public void Samyro_GetSoundKill(SaxtonHaleBase boss, char[] sSound, int length, TFClassType nClass) +{ + strcopy(sSound, length, g_strSamyroKill[GetRandomInt(0, sizeof(g_strSamyroKill) - 1)]); +} + +public void Samyro_GetMusicInfo(SaxtonHaleBase boss, char[] sSound, int length, float &time) +{ + strcopy(sSound, length, SAMYRO_MUSIC); + time = 195.0; +} + +public void Samyro_Precache(SaxtonHaleBase boss) +{ + g_iSamyroModelHat = PrecacheModel("models/player/items/pyro/fwk_pyro_flamenco.mdl"); + g_iSamyroModelMask = PrecacheModel("models/workshop/player/items/pyro/pyro_halloween_gasmask/pyro_halloween_gasmask.mdl"); + g_iSamyroModelKatana = PrecacheModel("models/workshop_partner/weapons/c_models/c_shogun_katana/c_shogun_katana.mdl"); + g_iSamyroModelHands = PrecacheModel("models/weapons/c_models/c_pyro_arms.mdl"); + + PrepareSound(SAMYRO_MUSIC); + + for (int i = 0; i < sizeof(g_strSamyroRoundStart); i++) PrecacheSound(g_strSamyroRoundStart[i]); + for (int i = 0; i < sizeof(g_strSamyroRoundWin); i++) PrecacheSound(g_strSamyroRoundWin[i]); + for (int i = 0; i < sizeof(g_strSamyroRoundLose); i++) PrecacheSound(g_strSamyroRoundLose[i]); + for (int i = 0; i < sizeof(g_strSamyroKill); i++) PrecacheSound(g_strSamyroKill[i]); + for (int i = 0; i < sizeof(g_strSamyroLastMan); i++) PrecacheSound(g_strSamyroLastMan[i]); + for (int i = 0; i < sizeof(g_strSamyroAbility); i++) PrecacheSound(g_strSamyroAbility[i]); +} + +public bool Samyro_IsBossHidden(SaxtonHaleBase boss) +{ + return true; +} \ No newline at end of file diff --git a/addons/sourcemod/scripting/vsh/console.sp b/addons/sourcemod/scripting/vsh/console.sp index d91a7645..af28a66e 100644 --- a/addons/sourcemod/scripting/vsh/console.sp +++ b/addons/sourcemod/scripting/vsh/console.sp @@ -8,6 +8,7 @@ void Console_Init() AddCommandListener(Console_JoinTeamCommand, "spectate"); AddCommandListener(Console_JoinClass, "joinclass"); AddCommandListener(Console_BuildCommand, "build"); + AddCommandListener(Console_DropItem, "dropitem"); } public Action Console_VoiceCommand(int iClient, const char[] sCommand, int iArgs) @@ -163,4 +164,13 @@ public Action Console_BuildCommand(int iClient, const char[] sCommand, int iArgs } return boss.CallFunction("OnBuild", nType, nMode); +} + +public Action Console_DropItem(int iClient, const char[] sCommand, int iArgs) +{ + //Prevent boss from dropping powerups + if (SaxtonHaleBase(iClient).bValid) + return Plugin_Handled; + + return Plugin_Continue; } \ No newline at end of file diff --git a/addons/sourcemod/scripting/vsh/stocks.sp b/addons/sourcemod/scripting/vsh/stocks.sp index 6d8abced..44d1051a 100644 --- a/addons/sourcemod/scripting/vsh/stocks.sp +++ b/addons/sourcemod/scripting/vsh/stocks.sp @@ -955,6 +955,11 @@ stock void ColorToTextStr(const int iColor[4], char[] sBuffer, int iLength) Format(sBuffer, iLength, "\x07%02X%02X%02X", iColor[0], iColor[1], iColor[2]); } +stock bool IsEntitySolid(int iEntity) +{ + return GetEntProp(iEntity, Prop_Send, "m_nSolidType") != view_as(SOLID_NONE) && !(GetEntProp(iEntity, Prop_Send, "m_usSolidFlags") & view_as(FSOLID_NOT_SOLID)); +} + stock void PrepareSound(const char[] sSoundPath) { PrecacheSound(sSoundPath, true); diff --git a/sound/vsh_rewrite/samyro/samyro_music.mp3 b/sound/vsh_rewrite/samyro/samyro_music.mp3 new file mode 100644 index 00000000..672d141f Binary files /dev/null and b/sound/vsh_rewrite/samyro/samyro_music.mp3 differ