Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
DosMike committed May 26, 2022
0 parents commit 7c6b525
Show file tree
Hide file tree
Showing 4 changed files with 381 additions and 0 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Toss Buildings

Pretty simple idea: Engi can now throw his buildings while carrying them.
To throw buildings, press your Reload key. They will construct while in air, bounce off walls and finally be usable at the desitnation.
You can't throw during Waiting For Players, If a building lands in a spawn room or sticks into other things, it will blow up.

Credits to zen for the idea :)

## Dependencies
* VPhysics Extension - because it's the best way to accelerate physics props
* TF2Utils - because that can check if things are in spawn rooms
* SMLib Transitional-Syntax - because it makes things easier for me (*COMPILE ONLY*)
9 changes: 9 additions & 0 deletions sp.sauce
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
sourcemod 1.10

auth github ${GITHUB_TOKEN}

dependency github nosoop/SM-TFUtils latest
dependency github bcserv/smlib transitional_syntax-SNAPSHOT
dependency limetech vphysics latest

spcomp -O2 tossbuildings.sp
20 changes: 20 additions & 0 deletions tbobj.games.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
"Games" {
"tf" {
"Signatures" {
// one of two methods with: "robo_sapper", starts with member lookup, first non-virtual call returns ~vtable fun 30
"StartBuilding" {
"library" "server"
"linux" "@_ZN16CTFWeaponBuilder13StartBuildingEv"
"windows" "\x55\x8B\xEC\x51\x8B\xD1\x53\x89"
//LKASM 55 8b ec 51 8b d1 53 89 55 fc
//MASK ff ff ff ff ff ff ff ff 00 00
}
}
//"Offsets" {
// "IsPlacementPosValid" {
// "linux" "335"
// "windows" "334"
// }
//}
}
}
340 changes: 340 additions & 0 deletions tossbuildings.sp
Original file line number Diff line number Diff line change
@@ -0,0 +1,340 @@
#include <sourcemod>
#include <sdktools>
#include <sdkhooks>
#include <tf2>
#include <tf2_stocks>
#include <smlib>
#include <vphysics>
#include <tf2utils>

#pragma newdecls required
#pragma semicolon 1

#define PLUGIN_VERSION "22w21a"

public Plugin myinfo = {
name = "[TF2] Toss Buildings",
description = "Use /toss to throw picked up buildings",
author = "reBane, zen",
version = PLUGIN_VERSION,
url = "N/A",
};

enum {
BUILDING_INVALID_OBJECT = ((1<<8)-1), // s8_t:-1
BUILDING_DISPENSER = 0,
BUILDING_TELEPORTER,
BUILDING_SENTRYGUN,
BUILDING_ATTACHMENT_SAPPER,
}
enum {
BS_IDLE,
BS_SELECTING,
BS_PLACING,
BS_PLACING_INVALID,
};

bool g_bPlayerThrow[MAXPLAYERS+1];
Handle sdk_fnStartBuilding;
//Handle sdk_fnIsPlacementPosValid;
ArrayList g_aAirbornObjects;
float g_flClientLastBeep[MAXPLAYERS+1];

#define TBLOCK_WFP (1<<0)
int g_iBlockFlags;

public void OnPluginStart() {
GameData data = new GameData("tbobj.games");
if (data == null)
SetFailState("Could not load gamedata: File is missing");

StartPrepSDKCall(SDKCall_Entity); //weapon
PrepSDKCall_SetFromConf(data, SDKConf_Signature, "StartBuilding");
if ((sdk_fnStartBuilding = EndPrepSDKCall())==null)
SetFailState("Could not load gamedata: StartBuilding Signature missing or outdated");

// StartPrepSDKCall(SDKCall_Entity); //building
// PrepSDKCall_SetFromConf(data, SDKConf_Virtual, "IsPlacementPosValid");
// PrepSDKCall_SetReturnInfo(SDKType_PlainOldData, SDKPass_Plain);
// if ((sdk_fnIsPlacementPosValid = EndPrepSDKCall())==null)
// SetFailState("Could not load gamedata: IsPlacementPosValid Offset missing or outdated");

delete data;

HookEvent("player_carryobject", OnPlayerCarryObject);
HookEvent("player_builtobject", OnPlayerBuiltObject);
HookEvent("player_dropobject", OnPlayerBuiltObject);

g_aAirbornObjects = new ArrayList(3); //phys parent, object, thrown angle (yaw)
}

public void OnMapStart() {
g_aAirbornObjects.Clear();
CreateTimer(0.1, Timer_PlaceBuildings, _, TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE);
}

public void OnClientDisconnect(int client) {
g_bPlayerThrow[client] = false;
g_flClientLastBeep[client] = 0.0;
}

public Action OnPlayerRunCmd(int client, int &buttons, int &impulse, float vel[3], float angles[3], int &weapon, int &subtype, int &cmdnum, int &tickcount, int &seed, int mouse[2]) {
if (!(1<=client<=MaxClients) || !IsClientInGame(client) || IsFakeClient(client)) return Plugin_Continue;
if ((buttons & IN_RELOAD)!=0 && !g_bPlayerThrow[client]) {
//trigger force build and throw on Reload
g_bPlayerThrow[client] = true;
if (CheckThrowPos(client)) StartBuilding(client);
g_bPlayerThrow[client] = false;
}
return Plugin_Continue;
}

public void OnPlayerCarryObject(Event event, const char[] name, bool dontBroadcast) {
int owner = GetClientOfUserId(event.GetInt("userid"));
int objecttype = event.GetInt("object");
int building = event.GetInt("index");
if ((BUILDING_DISPENSER <= objecttype <= BUILDING_SENTRYGUN) && IsClientInGame(owner) && IsValidEdict(building)) {
PrintHintText(owner, "Press [RELOAD] to toss the building");
}
}
public void OnPlayerBuiltObject(Event event, const char[] name, bool dontBroadcast) {
int owner = GetClientOfUserId(event.GetInt("userid"));
int objecttype = event.GetInt("object");
int building = event.GetInt("index");

if ((BUILDING_DISPENSER <= objecttype <= BUILDING_SENTRYGUN) && IsClientInGame(owner) && IsValidEdict(building) && g_bPlayerThrow[owner]) {
g_bPlayerThrow[owner] = false;
RequestFrame(ThrowBuilding,EntIndexToEntRef(building));
}
}

public void TF2_OnWaitingForPlayersStart() {
g_iBlockFlags |= TBLOCK_WFP;
}
public void TF2_OnWaitingForPlayersEnd() {
g_iBlockFlags &=~ TBLOCK_WFP;
}

public Action Timer_PlaceBuildings(Handle timer) {
ValidateThrown();
}

public bool TEF_HitSelfFilter(int entity, int contentsMask, any data) {
return entity > MaxClients && entity != data;
}
public void ThrowBuilding(any buildref) {
int building = EntRefToEntIndex(buildref);
if (building == INVALID_ENT_REFERENCE) return;
int owner = GetEntPropEnt(building, Prop_Send, "m_hBuilder");
if (owner < 1 || owner > MaxClients || !IsClientInGame(owner)) return;

float eyes[3];
float origin[3];
float angles[3];
float fwd[3];
float velocity[3];
GetClientEyePosition(owner, origin);
eyes = origin;
//set origin in front of player
GetClientEyeAngles(owner, angles);
angles[0]=angles[2]=0.0;
GetAngleVectors(angles, fwd, NULL_VECTOR, NULL_VECTOR);
ScaleVector(fwd, 64.0);
AddVectors(origin, fwd, origin);
//get angles/velocity
GetClientEyeAngles(owner, angles);
GetAngleVectors(angles, fwd, NULL_VECTOR, NULL_VECTOR);
ScaleVector(fwd, 520.0);
fwd[2]+=160.0;//bit more archy
Entity_GetAbsVelocity(owner, velocity);
AddVectors(velocity, fwd, velocity);

int phys = CreateEntityByName("prop_physics_multiplayer");
if (phys == INVALID_ENT_REFERENCE) return;

char buffer[PLATFORM_MAX_PATH];
switch (GetEntProp(building, Prop_Send, "m_iObjectType")) {
case BUILDING_SENTRYGUN: DispatchKeyValue(phys, "model", "models/buildables/sentry1.mdl");
case BUILDING_DISPENSER: DispatchKeyValue(phys, "model", "models/buildables/dispenser_light.mdl");
case BUILDING_TELEPORTER: DispatchKeyValue(phys, "model", "models/buildables/teleporter_light.mdl");
}
DispatchKeyValue(phys, "physicsmode", "1");
DispatchKeyValueVector(phys, "origin", origin);
DispatchKeyValueVector(phys, "angles", angles);
Format(buffer, sizeof(buffer), "%i", GetEntProp(building, Prop_Send, "m_nSkin"));
DispatchKeyValue(phys, "skin", buffer);
if (GetEntProp(building, Prop_Send, "m_bDisposableBuilding")) buffer = "0.66";
else if (GetEntProp(building, Prop_Send, "m_bMiniBuilding")) buffer = "0.75";
else buffer = "1.0";
DispatchKeyValue(phys, "modelscale", buffer);//mini sentries are .75
DispatchKeyValue(phys, "solid", "6");
if (!DispatchSpawn(phys)) {
PrintToChat(owner, "Failed to spawn sentry prop");
return;
}
ActivateEntity(phys);

SetEntProp(building, Prop_Send, "m_bDisabled", 1);
Entity_SetSolidFlags(building, FSOLID_NOT_SOLID);
SetEntityRenderMode(building, RENDER_NONE);
TeleportEntity(building, origin, NULL_VECTOR, NULL_VECTOR);
SetVariantString("!activator");
AcceptEntityInput(building, "SetParent", phys);
Phys_ApplyForceCenter(phys, velocity);// works best

any onade[3];
onade[0]=EntIndexToEntRef(phys);
onade[1]=EntIndexToEntRef(building);
onade[2]=angles[1];
g_aAirbornObjects.PushArray(onade);
}

public bool TEF_HitThrownFilter(int entity, int contentsMask, any data) {
int edicts[3];
g_aAirbornObjects.GetArray(data,edicts);
return entity > MaxClients && entity != EntRefToEntIndex(edicts[0]) && entity != EntRefToEntIndex(edicts[1]);
}

void ValidateThrown() {
for (int i=g_aAirbornObjects.Length-1; i>=0; i--) {
any data[3];
g_aAirbornObjects.GetArray(i,data);
int phys = EntRefToEntIndex(data[0]);
int obj = EntRefToEntIndex(data[1]);
float yaw = data[2];
//if at least one of the entities went away, something went wrong
// -> remove and continue
if (!IsValidEdict(phys)) {
if (IsValidEdict(obj)) AcceptEntityInput(obj, "Kill");
g_aAirbornObjects.Erase(i);
PrintToServer("Phys entity invalid");
continue;
} else if (!IsValidEdict(obj)) {
if (IsValidEdict(phys)) AcceptEntityInput(phys, "Kill");
g_aAirbornObjects.Erase(i);
PrintToServer("Building entity invalid");
continue;
}

float pos[3], vec[3];
Entity_GetAbsOrigin(phys, pos);
vec = pos;
vec[2] -= 16.0;
pos[2] += 48.0;
Handle trace = TR_TraceRayFilterEx(pos, vec, MASK_SOLID, RayType_EndPoint, TEF_HitThrownFilter, i);
if (!TR_DidHit(trace)) {
delete trace;
continue; //no ground below (FL_ONGROUND failed?)
} else {
TR_GetEndPosition(pos, trace);
TR_GetPlaneNormal(trace, vec); //vanilla is not checking this
delete trace;
//check surface slope
float up[3]; up[2]=1.0;
float slope = ArcCosine( GetVectorDotProduct(vec, up) ) * 180.0/3.1415927;
if (slope > 35.0) {
//this slope is too steep to place a building
continue;
}
//construct angles by random direction, using standard right to get propper forward
float angles[3];
angles[1]=yaw;
//clear parent
AcceptEntityInput(obj, "ClearParent");
//fix building
float zeros[3];
TeleportEntity(obj, pos, angles, zeros); //use 0-velocity to calm down bouncyness
//restore other props
SetEntProp(obj, Prop_Send, "m_bDisabled", 0);
Entity_RemoveSolidFlags(obj, FSOLID_NOT_SOLID);
SetEntityRenderMode(obj, RENDER_NORMAL);
//check valid
CreateTimer(0.1, ValidateBuilding, EntIndexToEntRef(obj), TIMER_FLAG_NO_MAPCHANGE);
}
RemoveEntity(phys);
g_aAirbornObjects.Erase(i);
}
}

bool CheckThrowPos(int client) {
if (g_iBlockFlags != 0) return false;
float eyes[3];
float origin[3];
float angles[3];
float fwd[3];
GetClientEyePosition(client, origin);
eyes = origin;
//set origin in front of player
GetClientEyeAngles(client, angles);
angles[0]=angles[2]=0.0;
GetAngleVectors(angles, fwd, NULL_VECTOR, NULL_VECTOR);
ScaleVector(fwd, 64.0);
AddVectors(origin, fwd, origin);
//ensure we see the target
Handle trace = TR_TraceRayFilterEx(eyes, origin, MASK_SOLID, RayType_EndPoint, TEF_HitSelfFilter, client);
bool hit = TR_DidHit(trace);
delete trace;
//can't see throw point (prevent through walls)? make noise
if (hit) Beep(client);
return !hit;
}

bool StartBuilding(int client) {
if (!IsClientInGame(client) || !IsPlayerAlive(client))
return false;
int weapon = Client_GetActiveWeapon(client);
int item = IsValidEdict(weapon) ? GetEntProp(weapon, Prop_Send, "m_iItemDefinitionIndex") : -1;
if (item != 28)
return false; //require builder
int type = GetEntProp(weapon, Prop_Send, "m_iObjectType");
if (!(BUILDING_DISPENSER <= type <= BUILDING_SENTRYGUN))
return false; //supported buildings
//trying to fix a crash related to dereferencing a 0 owner?
SetEntPropEnt(weapon, Prop_Send, "m_hOwner", client);
SetEntProp(weapon, Prop_Send, "m_iBuildState", BS_PLACING);
SDKCall(sdk_fnStartBuilding, weapon);
return true;
}
//crashes, idk why
//bool IsPlacementPosValid(int building) {
// char classname[64];
// if (!IsValidEdict(building)
// || !GetEntityClassname(building, classname, sizeof(classname))
// || !(StrEqual(classname, "obj_sentrygun")
// || StrEqual(classname, "obj_teleporter")
// || StrEqual(classname, "obj_dispenser")
// ))
// ThrowError("Entity is not a building");
// return SDKCall(sdk_fnIsPlacementPosValid, building);
//}

public Action ValidateBuilding(Handle timer, any building) {
int obj = EntRefToEntIndex(building);
if (obj == INVALID_ENT_REFERENCE) return Plugin_Stop;

float mins[3],maxs[3],origin[3];
float four[3];
four[0]=four[1]=four[2]=4.0;
Entity_GetAbsOrigin(obj,origin);
Entity_GetMinSize(obj,mins);
Entity_GetMaxSize(obj,maxs);
AddVectors(mins,four,mins);
SubtractVectors(maxs,four,maxs);

Handle trace = TR_TraceHullFilterEx(origin, origin, mins, maxs, MASK_SOLID, TEF_HitSelfFilter, obj);
bool hit = TR_DidHit(trace);
delete trace;
if (hit || TF2Util_IsPointInRespawnRoom(origin, obj)) {
SetVariantInt(1000);
AcceptEntityInput(obj, "RemoveHealth");
}
return Plugin_Stop;
}

void Beep(int client) {
if (!(1<=client<=MaxClients) || !IsClientInGame(client) || IsFakeClient(client)) return;
if (GetClientTime(client) - g_flClientLastBeep[client] >= 1.0) {
g_flClientLastBeep[client] = GetClientTime(client);
EmitSoundToClient(client, "common/wpn_denyselect.wav");//should aready be precached by game
}
}

0 comments on commit 7c6b525

Please sign in to comment.