Add Multidirectional Airstrafe and fix some projectile nospread issues

This commit is contained in:
BenCat07 2021-08-14 21:43:48 +02:00 committed by LightCat
parent 7bd167f43e
commit 3fc68c9c5c
5 changed files with 286 additions and 171 deletions

View File

@ -1,9 +1,15 @@
<Tab name="Movement" padding="6 6 6 6"> <Tab name="Movement" padding="6 6 6 6">
<Box padding="12 6 6 6" width="content" height="content" name="Bunny hop"> <Box padding="12 6 6 6" width="content" height="content" name="General">
<List width="150"> <List width="150">
<AutoVariable width="fill" target="bunnyhop.enable" label="Enable bunny hop"/> <AutoVariable width="fill" target="bunnyhop.enable" label="Enable bunny hop"/>
<AutoVariable width="fill" target="bunnyhop.chance" label="Bhop chance" tooltip="% chance of a successful bunnyhop."/> <AutoVariable width="fill" target="bunnyhop.chance" label="Bhop chance" tooltip="% chance of a successful bunnyhop."/>
<AutoVariable width="fill" target="misc.autostrafe" label="Enable auto strafe"/> <LabeledObject width="fill" label="Auto strafe">
<Select target="misc.autostrafe">
<Option name="Off" value="0"/>
<Option name="Regular" value="1"/>
<Option name="Multidir" value="2"/>
</Select>
</LabeledObject>
</List> </List>
</Box> </Box>
<Box padding="12 6 6 6" width="content" height="content" name="FollowBot" x="170"> <Box padding="12 6 6 6" width="content" height="content" name="FollowBot" x="170">

View File

@ -28,7 +28,7 @@ namespace hacks::shared::misc
static settings::Boolean render_zoomed{ "visual.render-local-zoomed", "true" }; static settings::Boolean render_zoomed{ "visual.render-local-zoomed", "true" };
#endif #endif
static settings::Boolean anti_afk{ "misc.anti-afk", "false" }; static settings::Boolean anti_afk{ "misc.anti-afk", "false" };
static settings::Boolean auto_strafe{ "misc.autostrafe", "false" }; static settings::Int auto_strafe{ "misc.autostrafe", "0" };
static settings::Boolean tauntslide{ "misc.tauntslide-tf2c", "false" }; static settings::Boolean tauntslide{ "misc.tauntslide-tf2c", "false" };
static settings::Boolean tauntslide_tf2{ "misc.tauntslide", "false" }; static settings::Boolean tauntslide_tf2{ "misc.tauntslide", "false" };
static settings::Boolean flashlight_spam{ "misc.flashlight-spam", "false" }; static settings::Boolean flashlight_spam{ "misc.flashlight-spam", "false" };
@ -182,6 +182,29 @@ void DrawWireframeHitbox(wireframe_data data)
} }
#endif #endif
static float normalizeRad(float a) noexcept
{
return std::isfinite(a) ? std::remainder(a, PI * 2) : 0.0f;
}
static float angleDiffRad(float a1, float a2) noexcept
{
float delta;
delta = normalizeRad(a1 - a2);
if (a1 > a2)
{
if (delta >= PI)
delta -= PI * 2;
}
else
{
if (delta <= -PI)
delta += PI * 2;
}
return delta;
}
void CreateMove() void CreateMove()
{ {
#if ENABLE_VISUALS #if ENABLE_VISUALS
@ -206,18 +229,85 @@ void CreateMove()
// Automatically strafes in the air // Automatically strafes in the air
if (auto_strafe && CE_GOOD(LOCAL_E) && !g_pLocalPlayer->life_state) if (auto_strafe && CE_GOOD(LOCAL_E) && !g_pLocalPlayer->life_state)
{ {
static bool was_jumping = false;
auto flags = CE_INT(LOCAL_E, netvar.iFlags); auto flags = CE_INT(LOCAL_E, netvar.iFlags);
auto movetype = (unsigned) CE_VAR(LOCAL_E, 0x194, unsigned char);
// Noclip
if (movetype != 8)
{
switch (*auto_strafe)
{
case 0: // Off
return;
break;
case 1: // Regular strafe
{
static bool was_jumping = false;
bool is_jumping = current_user_cmd->buttons & IN_JUMP; bool is_jumping = current_user_cmd->buttons & IN_JUMP;
if (!(flags & FL_ONGROUND) && !(flags & FL_INWATER) && (!is_jumping || was_jumping)) if (!(flags & FL_ONGROUND) || !(flags & FL_INWATER) && (!is_jumping || was_jumping))
{
if (current_user_cmd->mousedx) if (current_user_cmd->mousedx)
{
current_user_cmd->sidemove = current_user_cmd->mousedx > 1 ? 450.f : -450.f; current_user_cmd->sidemove = current_user_cmd->mousedx > 1 ? 450.f : -450.f;
}
}
was_jumping = is_jumping; was_jumping = is_jumping;
break;
}
case 2: // Multidirectional Airstrafe,
// Huge Credits to https://github.com/degeneratehyperbola/NEPS, as their airstrafe
// Apparently just works for tf2
// Also credits to "zelta" for porting it to tf2,
// And "Cyanide" for making it work with cathook.
{
static bool lastHeldJump = current_user_cmd->buttons & IN_JUMP;
const float speed = CE_VECTOR(LOCAL_E, netvar.vVelocity).Length2D();
auto vel = CE_VECTOR(LOCAL_E, netvar.vVelocity);
if (flags & FL_ONGROUND || flags & FL_INWATER)
return;
if (current_user_cmd->buttons & IN_JUMP)
return;
if (~current_user_cmd->buttons & current_user_cmd->buttons & IN_JUMP && !lastHeldJump)
return;
if (speed < 2.0f)
return;
constexpr auto perfectDelta = [](float speed) noexcept
{
static auto speedVar = CE_FLOAT(LOCAL_E, netvar.m_flMaxspeed);
static auto airVar = g_ICvar->FindVar("sv_airaccelerate");
// This is hardcoded for tf2, unless you run sourcemod
static auto wishSpeed = 30.0f;
const auto term = wishSpeed / airVar->GetFloat() / speedVar * 100.0f / speed;
if (term < 1.0f && term > -1.0f)
return acosf(term);
return 0.0f;
};
const float pDelta = perfectDelta(speed);
if (pDelta)
{
const float yaw = DEG2RAD(g_pLocalPlayer->v_OrigViewangles.y);
const float velDir = atan2f(vel.y, vel.x) - yaw;
const float wishAng = atan2f(-current_user_cmd->sidemove, current_user_cmd->forwardmove);
const float delta = angleDiffRad(velDir, wishAng); // Helpers::angleDiffRad(velDir, wishAng);
float moveDir = delta < 0.0f ? velDir + pDelta : velDir - pDelta;
current_user_cmd->forwardmove = cosf(moveDir) * 450.0f;
current_user_cmd->sidemove = -sinf(moveDir) * 450.0f;
}
lastHeldJump = current_user_cmd->buttons & current_user_cmd->buttons & IN_JUMP;
break;
}
default:
break;
}
}
} }
// TF2c Tauntslide // TF2c Tauntslide
@ -386,6 +476,7 @@ void Draw()
if (local->m_iClassID() == CL_CLASS(CTFMinigun)) if (local->m_iClassID() == CL_CLASS(CTFMinigun))
AddSideString(format("Weapon state: ", CE_INT(local, netvar.iWeaponState))); AddSideString(format("Weapon state: ", CE_INT(local, netvar.iWeaponState)));
AddSideString(format("ItemDefinitionIndex: ", CE_INT(local, netvar.iItemDefinitionIndex))); AddSideString(format("ItemDefinitionIndex: ", CE_INT(local, netvar.iItemDefinitionIndex)));
AddSideString(format("Maxspeed: ", CE_FLOAT(LOCAL_E, netvar.m_flMaxspeed)));
/*AddSideString(colors::white, "Weapon: %s [%i]", /*AddSideString(colors::white, "Weapon: %s [%i]",
RAW_ENT(g_pLocalPlayer->weapon())->GetClientClass()->GetName(), RAW_ENT(g_pLocalPlayer->weapon())->GetClientClass()->GetName(),
g_pLocalPlayer->weapon()->m_iClassID()); g_pLocalPlayer->weapon()->m_iClassID());
@ -563,6 +654,16 @@ CatCommand set_value("set", "Set value",
var->SetValue(value.c_str()); var->SetValue(value.c_str());
logging::Info("Set '%s' to '%s'", args.Arg(1), value.c_str()); logging::Info("Set '%s' to '%s'", args.Arg(1), value.c_str());
}); });
CatCommand get_value("get", "Set value",
[](const CCommand &args)
{
if (args.ArgC() < 2)
return;
ConVar *var = g_ICvar->FindVar(args.Arg(1));
if (!var)
return;
logging::Info("'%s': '%s'", args.Arg(1), var->GetString());
});
CatCommand say_lines("say_lines", "Say with newlines (\\n)", CatCommand say_lines("say_lines", "Say with newlines (\\n)",
[](const CCommand &args) [](const CCommand &args)
{ {

View File

@ -1349,7 +1349,7 @@ bool GetProjectileData(CachedEntity *weapon, float &speed, float &gravity, float
} }
case CL_CLASS(CTFSyringeGun): case CL_CLASS(CTFSyringeGun):
{ {
rgrav = 0.2f; rgrav = 0.3f;
rspeed = 1000.0f; rspeed = 1000.0f;
break; break;
} }

View File

@ -29,7 +29,7 @@ static bool update_override_textures = false;
std::vector<std::string> world_strings = { "World" }; std::vector<std::string> world_strings = { "World" };
std::vector<std::string> skybox_strings = { "SkyBox" }; std::vector<std::string> skybox_strings = { "SkyBox" };
std::vector<std::string> gui_strings = { "Other", "VGUI" }; std::vector<std::string> gui_strings = { "Other", "VGUI" };
std::vector<std::string> dont_override_strings = { "glass", "door", "water", "tools", "player" }; std::vector<std::string> dont_override_strings = { "glass", "door", "water", "tools", "player", "wall28", "wall26" };
std::vector<std::string> nodraw_strings = { "decal", "overlay", "hay" }; std::vector<std::string> nodraw_strings = { "decal", "overlay", "hay" };
namespace hooked_methods namespace hooked_methods
@ -58,6 +58,8 @@ DEFINE_HOOKED_METHOD(FrameStageNotify, void, void *this_, ClientFrameStage_t sta
// Ensure world mat // Ensure world mat
if (name.find("World") == std::string::npos) if (name.find("World") == std::string::npos)
continue; continue;
if (pMaterial->IsErrorMaterial() || !pMaterial->IsPrecached() || pMaterial->IsTranslucent() || pMaterial->IsSpriteCard())
continue;
// Don't override this stuff // Don't override this stuff
bool good = true; bool good = true;
for (auto &entry : dont_override_strings) for (auto &entry : dont_override_strings)
@ -77,9 +79,9 @@ DEFINE_HOOKED_METHOD(FrameStageNotify, void, void *this_, ClientFrameStage_t sta
if (!pMaterial->GetMaterialVarFlag(MATERIAL_VAR_NO_DRAW)) if (!pMaterial->GetMaterialVarFlag(MATERIAL_VAR_NO_DRAW))
{ {
auto *kv = new KeyValues(pMaterial->GetShaderName()); auto *kv = new KeyValues("LightmappedGeneric" /*pMaterial->GetShaderName()*/);
kv->SetString("$basetexture", (*override_textures_texture).c_str()); kv->SetString("$basetexture", (*override_textures_texture).c_str());
kv->SetString("$basetexturetransform", "center .5 .5 scale 6 6 rotate 0 translate 0 0"); //kv->SetString("$basetexturetransform", "center .5 .5 scale 6 6 rotate 0 translate 0 0");
kv->SetString("$surfaceprop", "concrete"); kv->SetString("$surfaceprop", "concrete");
pMaterial->SetShaderAndParams(kv); pMaterial->SetShaderAndParams(kv);
} }

View File

@ -82,16 +82,7 @@ void CreateMove()
// Credits to https://www.unknowncheats.me/forum/team-fortress-2-a/139094-projectile-nospread.html // Credits to https://www.unknowncheats.me/forum/team-fortress-2-a/139094-projectile-nospread.html
// Set up Random Seed // Set up Random Seed
int cmd_num = current_user_cmd->command_number; int cmd_num = current_late_user_cmd->command_number;
// Crithack uses different things
if (criticals::isEnabled() && g_pLocalPlayer->weapon_mode != weapon_melee && criticals::crit_cmds.find(LOCAL_W->m_IDX) != criticals::crit_cmds.end() && criticals::crit_cmds.find(LOCAL_W->m_IDX)->second.size())
{
int array_index = criticals::current_index;
if (array_index >= criticals::crit_cmds.at(LOCAL_W->m_IDX).size())
array_index = 0;
// Adjust for nospread
cmd_num = criticals::crit_cmds.at(LOCAL_W->m_IDX).at(array_index);
}
RandomSeed(MD5_PseudoRandom(cmd_num) & 0x7FFFFFFF); RandomSeed(MD5_PseudoRandom(cmd_num) & 0x7FFFFFFF);
SharedRandomInt(MD5_PseudoRandom(cmd_num) & 0x7FFFFFFF, "SelectWeightedSequence", 0, 0, 0); SharedRandomInt(MD5_PseudoRandom(cmd_num) & 0x7FFFFFFF, "SelectWeightedSequence", 0, 0, 0);
for (int i = 0; i < 6; ++i) for (int i = 0; i < 6; ++i)
@ -106,7 +97,7 @@ void CreateMove()
{ {
// Check if we released the barrage by releasing m1, also lock bool so people don't just release m1 and tap it again // Check if we released the barrage by releasing m1, also lock bool so people don't just release m1 and tap it again
if (!should_nospread) if (!should_nospread)
should_nospread = !(current_user_cmd->buttons & IN_ATTACK) && g_pLocalPlayer->bAttackLastTick; should_nospread = !(current_late_user_cmd->buttons & IN_ATTACK) && g_pLocalPlayer->bAttackLastTick;
if (!CE_INT(LOCAL_W, netvar.m_iClip1) && CE_INT(LOCAL_W, netvar.iReloadMode) == 0) if (!CE_INT(LOCAL_W, netvar.m_iClip1) && CE_INT(LOCAL_W, netvar.iReloadMode) == 0)
{ {
@ -121,29 +112,29 @@ void CreateMove()
// Huntsman check // Huntsman check
else if (LOCAL_W->m_iClassID() == CL_CLASS(CTFCompoundBow)) else if (LOCAL_W->m_iClassID() == CL_CLASS(CTFCompoundBow))
{ {
if (!g_pLocalPlayer->bAttackLastTick || (current_user_cmd->buttons & IN_ATTACK)) if (!g_pLocalPlayer->bAttackLastTick || (current_late_user_cmd->buttons & IN_ATTACK))
return; return;
} }
// Rest of weapons // Rest of weapons
else if (!(current_user_cmd->buttons & IN_ATTACK)) else if (!(current_late_user_cmd->buttons & IN_ATTACK))
return; return;
switch (LOCAL_W->m_iClassID()) switch (LOCAL_W->m_iClassID())
{ {
case CL_CLASS(CTFSyringeGun): case CL_CLASS(CTFSyringeGun):
{ {
if (g_pLocalPlayer->v_OrigViewangles == current_user_cmd->viewangles) if (g_pLocalPlayer->v_OrigViewangles == current_late_user_cmd->viewangles)
g_pLocalPlayer->bUseSilentAngles = true; g_pLocalPlayer->bUseSilentAngles = true;
float spread = 1.5f; float spread = 1.5f;
current_user_cmd->viewangles.x -= RandomFloat(-spread, spread); current_late_user_cmd->viewangles.x -= RandomFloat(-spread, spread);
current_user_cmd->viewangles.y -= RandomFloat(-spread, spread); current_late_user_cmd->viewangles.y -= RandomFloat(-spread, spread);
fClampAngle(current_user_cmd->viewangles); fClampAngle(current_late_user_cmd->viewangles);
break; break;
} }
case CL_CLASS(CTFCompoundBow): case CL_CLASS(CTFCompoundBow):
{ {
Vector view = re::C_BasePlayer::GetLocalEyeAngles(RAW_ENT(LOCAL_E)); Vector view = re::C_BasePlayer::GetLocalEyeAngles(RAW_ENT(LOCAL_E));
if (g_pLocalPlayer->v_OrigViewangles == current_user_cmd->viewangles) if (g_pLocalPlayer->v_OrigViewangles == current_late_user_cmd->viewangles)
g_pLocalPlayer->bUseSilentAngles = true; g_pLocalPlayer->bUseSilentAngles = true;
Vector spread; Vector spread;
@ -152,25 +143,25 @@ void CreateMove()
re::C_TFWeaponBase::GetProjectileFireSetupHuntsman(RAW_ENT(LOCAL_W), RAW_ENT(LOCAL_E), Vector(23.5f, -8.f, 8.f), &src, &spread, false, 2000.0f); re::C_TFWeaponBase::GetProjectileFireSetupHuntsman(RAW_ENT(LOCAL_W), RAW_ENT(LOCAL_E), Vector(23.5f, -8.f, 8.f), &src, &spread, false, 2000.0f);
spread -= view; spread -= view;
current_user_cmd->viewangles -= spread; current_late_user_cmd->viewangles -= spread;
fClampAngle(current_user_cmd->viewangles); fClampAngle(current_late_user_cmd->viewangles);
break; break;
} }
default: default:
Vector view = re::C_BasePlayer::GetLocalEyeAngles(RAW_ENT(LOCAL_E)); Vector view = re::C_BasePlayer::GetLocalEyeAngles(RAW_ENT(LOCAL_E));
if (g_pLocalPlayer->v_OrigViewangles == current_user_cmd->viewangles) if (g_pLocalPlayer->v_OrigViewangles == current_late_user_cmd->viewangles)
g_pLocalPlayer->bUseSilentAngles = true; g_pLocalPlayer->bUseSilentAngles = true;
Vector spread = re::C_TFWeaponBase::GetSpreadAngles(RAW_ENT(LOCAL_W)); Vector spread = re::C_TFWeaponBase::GetSpreadAngles(RAW_ENT(LOCAL_W));
spread -= view; spread -= view;
current_user_cmd->viewangles -= spread; current_late_user_cmd->viewangles -= spread;
fClampAngle(current_user_cmd->viewangles); fClampAngle(current_late_user_cmd->viewangles);
break; break;
} }
} }
static InitRoutine init([]() { EC::Register(EC::CreateMove, CreateMove, "nospread_cm", EC::very_late); }); static InitRoutine init([]() { EC::Register(EC::CreateMoveLate, CreateMove, "nospread_cm", EC::very_late); });
enum nospread_sync_state enum nospread_sync_state
{ {
@ -306,7 +297,9 @@ void ApplySpreadCorrection(Vector &angles, int seed, float spread)
fClampAngle(angles); fClampAngle(angles);
} }
CatCommand debug_mantissa("test_mantissa", "For debug purposes", [](const CCommand &rCmd) { CatCommand debug_mantissa("test_mantissa", "For debug purposes",
[](const CCommand &rCmd)
{
if (rCmd.ArgC() < 2) if (rCmd.ArgC() < 2)
{ {
g_ICvar->ConsoleColorPrintf(MENU_COLOR, "You must provide float to test.\n"); g_ICvar->ConsoleColorPrintf(MENU_COLOR, "You must provide float to test.\n");
@ -327,7 +320,9 @@ CatCommand debug_mantissa("test_mantissa", "For debug purposes", [](const CComma
return; return;
}); });
static CatCommand nospread_sync("nospread_sync", "Try to sync client and server time", []() { static CatCommand nospread_sync("nospread_sync", "Try to sync client and server time",
[]()
{
if (!bullet) if (!bullet)
{ {
g_ICvar->ConsoleColorPrintf(MENU_COLOR, "Set nospread.enable to true first.\n"); g_ICvar->ConsoleColorPrintf(MENU_COLOR, "Set nospread.enable to true first.\n");
@ -338,7 +333,9 @@ static CatCommand nospread_sync("nospread_sync", "Try to sync client and server
g_ICvar->ConsoleColorPrintf(MENU_COLOR, "Trying to sync Seed...\n"); g_ICvar->ConsoleColorPrintf(MENU_COLOR, "Trying to sync Seed...\n");
}); });
static CatCommand nospread_resync("nospread_resync", "Try to sync client and server time", []() { static CatCommand nospread_resync("nospread_resync", "Try to sync client and server time",
[]()
{
if (!bullet) if (!bullet)
{ {
g_ICvar->ConsoleColorPrintf(MENU_COLOR, "Set nospread.enable to true first.\n"); g_ICvar->ConsoleColorPrintf(MENU_COLOR, "Set nospread.enable to true first.\n");
@ -627,7 +624,8 @@ void CL_SendMove_hook()
int new_packets = 1 + *choked_packets; int new_packets = 1 + *choked_packets;
auto RecheckIfresync_needed = [&new_packets](double asumed_time) -> void { auto RecheckIfresync_needed = [&new_packets](double asumed_time) -> void
{
static Timer s_NextCheck; static Timer s_NextCheck;
// we use it as 1 sec delay // we use it as 1 sec delay
if (s_NextCheck.check(1000) && new_packets == 1 && (no_spread_synced != SYNCED || !LOCAL_E->m_bAlivePlayer()) && !waiting_perf_data) if (s_NextCheck.check(1000) && new_packets == 1 && (no_spread_synced != SYNCED || !LOCAL_E->m_bAlivePlayer()) && !waiting_perf_data)
@ -879,7 +877,9 @@ void CreateMove2()
} }
} }
static InitRoutine init_bulletnospread([]() { static InitRoutine init_bulletnospread(
[]()
{
// Get our detour hooks running // Get our detour hooks running
static auto writeusercmd_addr = gSignatures.GetClientSignature("55 89 E5 57 56 53 83 EC 2C 8B 45 ? 8B 7D ? 8B 5D ? 89 45 ? 8B 40"); static auto writeusercmd_addr = gSignatures.GetClientSignature("55 89 E5 57 56 53 83 EC 2C 8B 45 ? 8B 7D ? 8B 5D ? 89 45 ? 8B 40");
cl_writeusercmd_detour.Init(writeusercmd_addr, (void *) WriteUserCmd_hook); cl_writeusercmd_detour.Init(writeusercmd_addr, (void *) WriteUserCmd_hook);
@ -892,7 +892,9 @@ static InitRoutine init_bulletnospread([]() {
EC::Register(EC::CreateMove, CreateMove2, "nospread_createmove2"); EC::Register(EC::CreateMove, CreateMove2, "nospread_createmove2");
EC::Register(EC::CreateMoveWarp, CreateMove2, "nospread_createmove2w"); EC::Register(EC::CreateMoveWarp, CreateMove2, "nospread_createmove2w");
bullet.installChangeCallback([](settings::VariableBase<bool> &, bool after) { bullet.installChangeCallback(
[](settings::VariableBase<bool> &, bool after)
{
if (!after) if (!after)
{ {
is_syncing = false; is_syncing = false;
@ -902,7 +904,8 @@ static InitRoutine init_bulletnospread([]() {
#if ENABLE_VISUALS #if ENABLE_VISUALS
EC::Register( EC::Register(
EC::Draw, EC::Draw,
[]() { []()
{
if (bullet && (draw || draw_mantissa) && CE_GOOD(LOCAL_E) && LOCAL_E->m_bAlivePlayer()) if (bullet && (draw || draw_mantissa) && CE_GOOD(LOCAL_E) && LOCAL_E->m_bAlivePlayer())
{ {
std::string draw_string = ""; std::string draw_string = "";
@ -949,7 +952,8 @@ static InitRoutine init_bulletnospread([]() {
#endif #endif
EC::Register( EC::Register(
EC::LevelInit, EC::LevelInit,
[]() { []()
{
no_spread_synced = NOT_SYNCED; no_spread_synced = NOT_SYNCED;
last_was_player_perf = false; last_was_player_perf = false;
bad_mantissa = false; bad_mantissa = false;
@ -959,7 +963,8 @@ static InitRoutine init_bulletnospread([]() {
EC::Register( EC::Register(
EC::LevelShutdown, EC::LevelShutdown,
[]() { []()
{
no_spread_synced = NOT_SYNCED; no_spread_synced = NOT_SYNCED;
last_was_player_perf = false; last_was_player_perf = false;
bad_mantissa = false; bad_mantissa = false;
@ -968,7 +973,8 @@ static InitRoutine init_bulletnospread([]() {
"nospread_levelshutdown"); "nospread_levelshutdown");
EC::Register( EC::Register(
EC::Shutdown, EC::Shutdown,
[]() { []()
{
cl_writeusercmd_detour.Shutdown(); cl_writeusercmd_detour.Shutdown();
fx_firebullets_detour.Shutdown(); fx_firebullets_detour.Shutdown();
// net_sendpacket_detour.Shutdown(); // net_sendpacket_detour.Shutdown();