mirror of
https://github.com/Jackzmc/sourcemod-plugins.git
synced 2025-05-06 19:03:20 +00:00
Changes
This commit is contained in:
commit
a305fa5202
9 changed files with 4170 additions and 0 deletions
902
scripting/include/hats/editor.sp
Normal file
902
scripting/include/hats/editor.sp
Normal file
|
@ -0,0 +1,902 @@
|
||||||
|
int BUILDER_COLOR[4] = { 0, 255, 0, 235 };
|
||||||
|
int GLOW_BLUE[4] = { 3, 148, 252 };
|
||||||
|
int GLOW_RED_ALPHA[4] = { 255, 0, 0, 235 };
|
||||||
|
int GLOW_RED[3] = { 255, 0, 0};
|
||||||
|
int GLOW_WHITE[4] = { 255, 255, 255, 255 };
|
||||||
|
int GLOW_GREEN[4] = { 3, 252, 53 };
|
||||||
|
float ORIGIN_SIZE[3] = { 2.0, 2.0, 2.0 };
|
||||||
|
|
||||||
|
char ON_OFF_STRING[2][] = {
|
||||||
|
"\x05ON\x01",
|
||||||
|
"\x05OFF\x01"
|
||||||
|
}
|
||||||
|
char COLOR_INDEX[4] = "RGBA";
|
||||||
|
|
||||||
|
enum editMode {
|
||||||
|
INACTIVE = 0,
|
||||||
|
MOVE_ORIGIN,
|
||||||
|
SCALE,
|
||||||
|
COLOR,
|
||||||
|
FREELOOK,
|
||||||
|
}
|
||||||
|
char MODE_NAME[5][] = {
|
||||||
|
"Error",
|
||||||
|
"Move & Rotate",
|
||||||
|
"Scale",
|
||||||
|
"Color",
|
||||||
|
"Freelook"
|
||||||
|
}
|
||||||
|
|
||||||
|
enum editFlag {
|
||||||
|
Edit_None,
|
||||||
|
Edit_Copy = 1,
|
||||||
|
Edit_Preview = 2,
|
||||||
|
Edit_WallCreator = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
enum buildType {
|
||||||
|
Build_Solid,
|
||||||
|
Build_Physics,
|
||||||
|
Build_NonSolid,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum CompleteType {
|
||||||
|
Complete_WallSuccess,
|
||||||
|
Complete_WallError,
|
||||||
|
Complete_PropSpawned,
|
||||||
|
Complete_PropError,
|
||||||
|
Complete_EditSuccess
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ArrayList createdWalls;
|
||||||
|
|
||||||
|
enum struct EditorData {
|
||||||
|
int client;
|
||||||
|
char classname[32];
|
||||||
|
char data[32];
|
||||||
|
|
||||||
|
float origin[3];
|
||||||
|
float angles[3];
|
||||||
|
float prevOrigin[3]; // for cancelling edits
|
||||||
|
float prevAngles[3];
|
||||||
|
|
||||||
|
float mins[3];
|
||||||
|
float size[3];
|
||||||
|
|
||||||
|
int color[4];
|
||||||
|
int colorIndex;
|
||||||
|
int axis;
|
||||||
|
int snapAngle;
|
||||||
|
int moveSpeed;
|
||||||
|
float moveDistance;
|
||||||
|
int entity;
|
||||||
|
bool hasCollision; /// possibly merge into .flags
|
||||||
|
bool hasCollisionRotate; //^
|
||||||
|
|
||||||
|
editMode mode;
|
||||||
|
buildType buildType;
|
||||||
|
editFlag flags;
|
||||||
|
|
||||||
|
void Reset(bool initial = false) {
|
||||||
|
// Clear preview entity
|
||||||
|
if(this.entity != INVALID_ENT_REFERENCE && this.flags & Edit_Preview && IsValidEntity(this.entity)) {
|
||||||
|
RemoveEntity(this.entity);
|
||||||
|
}
|
||||||
|
this.entity = INVALID_ENT_REFERENCE;
|
||||||
|
this.data[0] = '\0';
|
||||||
|
this.size[0] = this.size[1] = this.size[2] = 5.0;
|
||||||
|
this.angles[0] = this.angles[1] = this.angles[2] = 0.0;
|
||||||
|
this.colorIndex = 0;
|
||||||
|
this.axis = 1;
|
||||||
|
this.moveDistance = 200.0;
|
||||||
|
this.flags = Edit_None;
|
||||||
|
this.buildType = Build_Solid;
|
||||||
|
this.classname[0] = '\0';
|
||||||
|
this.CalculateMins();
|
||||||
|
this.SetMode(INACTIVE);
|
||||||
|
// Settings that don't get reset on new spawns:
|
||||||
|
if(initial) {
|
||||||
|
this.color[0] = this.color[1] = this.color[2] = this.color[3] = 255;
|
||||||
|
this.moveSpeed = 1;
|
||||||
|
this.snapAngle = 30;
|
||||||
|
this.hasCollision = true;
|
||||||
|
this.hasCollisionRotate = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CalculateMins() {
|
||||||
|
this.mins[0] = -this.size[0];
|
||||||
|
this.mins[1] = -this.size[1];
|
||||||
|
this.mins[2] = -this.size[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
void Draw(int color[4], float lifetime, float amplitude = 0.1) {
|
||||||
|
if(this.flags & Edit_WallCreator || this.entity == INVALID_ENT_REFERENCE) {
|
||||||
|
Effect_DrawBeamBoxRotatableToAll(this.origin, this.mins, this.size, this.angles, g_iLaserIndex, 0, 0, 30, lifetime, 0.4, 0.4, 0, amplitude, color, 0);
|
||||||
|
} else {
|
||||||
|
TeleportEntity(this.entity, this.origin, this.angles, NULL_VECTOR);
|
||||||
|
}
|
||||||
|
Effect_DrawAxisOfRotationToAll(this.origin, this.angles, ORIGIN_SIZE, g_iLaserIndex, 0, 0, 30, 0.2, 0.1, 0.1, 0, 0.0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Updates the entity with certain changed settings
|
||||||
|
void UpdateEntity() {
|
||||||
|
int alpha = this.color[3];
|
||||||
|
// Keep previews transparent
|
||||||
|
if(this.flags & Edit_Preview) {
|
||||||
|
alpha = 200;
|
||||||
|
}
|
||||||
|
SetEntityRenderColor(this.entity, this.color[0], this.color[1], this.color[2], alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CheckEntity() {
|
||||||
|
if(this.entity != INVALID_ENT_REFERENCE) {
|
||||||
|
if(!IsValidEntity(this.entity)) {
|
||||||
|
PrintToChat(this.client, "\x04[Editor]\x01 Entity has vanished, editing cancelled.");
|
||||||
|
this.Reset();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsActive() {
|
||||||
|
return this.mode != INACTIVE && this.CheckEntity();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetMode(editMode mode) {
|
||||||
|
this.mode = mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetData(const char[] data) {
|
||||||
|
strcopy(this.data, sizeof(this.data), data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CycleMode() {
|
||||||
|
// Remove frozen state when cycling
|
||||||
|
int flags = GetEntityFlags(this.client) & ~FL_FROZEN;
|
||||||
|
SetEntityFlags(this.client, flags);
|
||||||
|
switch(this.mode) {
|
||||||
|
// MODES:
|
||||||
|
// - MOVE & ROTAT
|
||||||
|
// - SCALE or COLOR
|
||||||
|
// - FREELOOK
|
||||||
|
case MOVE_ORIGIN: {
|
||||||
|
if(this.flags & Edit_WallCreator) {
|
||||||
|
this.mode = SCALE;
|
||||||
|
} else if(this.flags & Edit_Preview) {
|
||||||
|
this.mode = COLOR;
|
||||||
|
} else {
|
||||||
|
this.mode = FREELOOK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case SCALE: {
|
||||||
|
this.mode = FREELOOK;
|
||||||
|
}
|
||||||
|
case COLOR: {
|
||||||
|
this.mode = FREELOOK;
|
||||||
|
}
|
||||||
|
case FREELOOK: {
|
||||||
|
this.mode = MOVE_ORIGIN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PrintToChat(this.client, "\x04[Editor]\x01 Mode: \x05%s\x01 (Press \x04RELOAD\x01 to change)", MODE_NAME[this.mode]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ToggleCollision(float tick) {
|
||||||
|
if(tick - cmdThrottle[this.client] <= 0.25) return;
|
||||||
|
this.hasCollision = !this.hasCollision
|
||||||
|
PrintToChat(this.client, "\x04[Editor]\x01 Collision: %s", ON_OFF_STRING[view_as<int>(this.hasCollision)]);
|
||||||
|
cmdThrottle[this.client] = tick;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ToggleCollisionRotate(float tick) {
|
||||||
|
if(tick - cmdThrottle[this.client] <= 0.25) return;
|
||||||
|
this.hasCollisionRotate = !this.hasCollisionRotate
|
||||||
|
PrintToChat(this.client, "\x04[Editor]\x01 Rotate with Collision: %s", ON_OFF_STRING[view_as<int>(this.hasCollisionRotate)]);
|
||||||
|
cmdThrottle[this.client] = tick;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CycleAxis(float tick) {
|
||||||
|
if(tick - cmdThrottle[this.client] <= 0.15) return;
|
||||||
|
if(this.axis == 0) {
|
||||||
|
this.axis = 1;
|
||||||
|
PrintToChat(this.client, "\x04[Editor]\x01 Rotate Axis: \x05HEADING (Y)\x01");
|
||||||
|
} else if(this.axis == 1) {
|
||||||
|
this.axis = 2;
|
||||||
|
PrintToChat(this.client, "\x04[Editor]\x01 Rotate Axis: \x05PITCH (X)\x01");
|
||||||
|
} else {
|
||||||
|
this.axis = 0;
|
||||||
|
PrintToChat(this.client, "\x04[Editor]\x01 Rotate Axis: \x05ROLL (Z)\x01");
|
||||||
|
}
|
||||||
|
cmdThrottle[this.client] = tick;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CycleSnapAngle(float tick) {
|
||||||
|
if(tick - cmdThrottle[this.client] <= 0.15) return;
|
||||||
|
switch(this.snapAngle) {
|
||||||
|
case 1: this.snapAngle = 15;
|
||||||
|
case 15: this.snapAngle = 30;
|
||||||
|
case 30: this.snapAngle = 45;
|
||||||
|
case 45: this.snapAngle = 90;
|
||||||
|
case 90: this.snapAngle = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// this.angles[0] = SnapTo(this.angles[0], float(this.snapAngle));
|
||||||
|
// this.angles[1] = SnapTo(this.angles[1], float(this.snapAngle));
|
||||||
|
// this.angles[2] = SnapTo(this.angles[2], float(this.snapAngle));
|
||||||
|
|
||||||
|
if(this.snapAngle == 1)
|
||||||
|
PrintToChat(this.client, "\x04[Editor]\x01 Rotate Snap Degrees: \x04(OFF)\x01", this.snapAngle);
|
||||||
|
else
|
||||||
|
PrintToChat(this.client, "\x04[Editor]\x01 Rotate Snap Degrees: \x05%d\x01", this.snapAngle);
|
||||||
|
cmdThrottle[this.client] = tick;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CycleSpeed(float tick) {
|
||||||
|
if(tick - cmdThrottle[this.client] <= 0.25) return;
|
||||||
|
this.moveSpeed++;
|
||||||
|
if(this.moveSpeed > 10) this.moveSpeed = 1;
|
||||||
|
PrintToChat(this.client, "\x04[Editor]\x01 Scale Speed: \x05%d\x01", this.moveSpeed);
|
||||||
|
cmdThrottle[this.client] = tick;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CycleBuildType() {
|
||||||
|
// No tick needed, is handled externally
|
||||||
|
if(this.classname[0] != '\0') {
|
||||||
|
PrintToChat(this.client, "\x04[Editor]\x01 Spawn as: \x05%s\x01 (fixed)", this.classname);
|
||||||
|
} else if(this.buildType == Build_Physics) {
|
||||||
|
this.buildType = Build_Solid;
|
||||||
|
PrintToChat(this.client, "\x04[Editor]\x01 Spawn as: \x05Solid\x01");
|
||||||
|
} else if(this.buildType == Build_Solid) {
|
||||||
|
this.buildType = Build_Physics;
|
||||||
|
PrintToChat(this.client, "\x04[Editor]\x01 Spawn as: \x05Physics\x01");
|
||||||
|
} else {
|
||||||
|
this.buildType = Build_NonSolid;
|
||||||
|
PrintToChat(this.client, "\x04[Editor]\x01 Spawn as: \x05Non Solid\x01");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CycleColorComponent(float tick) {
|
||||||
|
if(tick - cmdThrottle[this.client] <= 0.25) return;
|
||||||
|
this.colorIndex++;
|
||||||
|
if(this.colorIndex > 3) this.colorIndex = 0;
|
||||||
|
char component[16];
|
||||||
|
for(int i = 0; i < 4; i++) {
|
||||||
|
if(this.colorIndex == i)
|
||||||
|
Format(component, sizeof(component), "%s \x05 %c \x01", component, COLOR_INDEX[i]);
|
||||||
|
else
|
||||||
|
Format(component, sizeof(component), "%s %c", component, COLOR_INDEX[i]);
|
||||||
|
}
|
||||||
|
PrintToChat(this.client, "\x04[Editor]\x01 Color: %s", component);
|
||||||
|
cmdThrottle[this.client] = tick;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IncrementSize(int axis, float amount) {
|
||||||
|
this.size[axis] += amount;
|
||||||
|
if(this.size[axis] < 0.0) {
|
||||||
|
this.size[axis] = 0.0;
|
||||||
|
}
|
||||||
|
this.CalculateMins();
|
||||||
|
}
|
||||||
|
|
||||||
|
void IncreaseColor(int amount) {
|
||||||
|
int newValue = this.color[this.colorIndex] + amount;
|
||||||
|
if(newValue > 255) newValue = 255;
|
||||||
|
else if(newValue < 0) newValue = 0;
|
||||||
|
this.color[this.colorIndex] = newValue;
|
||||||
|
this.UpdateEntity();
|
||||||
|
PrintCenterText(this.client, "%d %d %d %d", this.color[0], this.color[1], this.color[2], this.color[3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Complete the edit, wall creation, or spawning
|
||||||
|
CompleteType Done(int& entity) {
|
||||||
|
if(this.flags & Edit_WallCreator) {
|
||||||
|
return this._FinishWall(entity) ? Complete_WallSuccess : Complete_WallError;
|
||||||
|
} else if(this.flags & Edit_Preview) {
|
||||||
|
return this._FinishPreview(entity) ? Complete_PropSpawned : Complete_PropError;
|
||||||
|
} else {
|
||||||
|
// Is edit, do nothing, just reset
|
||||||
|
this.Reset();
|
||||||
|
entity = 0;
|
||||||
|
return Complete_EditSuccess;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _FinishWall(int& id) {
|
||||||
|
if(~this.flags & Edit_WallCreator) {
|
||||||
|
this.Reset();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Don't need to build a new one if we editing:
|
||||||
|
int blocker = this.entity;
|
||||||
|
bool isEdit = false;
|
||||||
|
if(blocker != INVALID_ENT_REFERENCE) {
|
||||||
|
RemoveEntity(this.entity);
|
||||||
|
isEdit = true;
|
||||||
|
}
|
||||||
|
blocker = CreateEntityByName("func_brush");
|
||||||
|
if(blocker == -1) return false;
|
||||||
|
DispatchKeyValueVector(blocker, "mins", this.mins);
|
||||||
|
DispatchKeyValueVector(blocker, "maxs", this.size);
|
||||||
|
DispatchKeyValueVector(blocker, "boxmins", this.mins);
|
||||||
|
DispatchKeyValueVector(blocker, "boxmaxs", this.size);
|
||||||
|
DispatchKeyValue(blocker, "excludednpc", "player");
|
||||||
|
|
||||||
|
DispatchKeyValueVector(blocker, "angles", this.angles);
|
||||||
|
DispatchKeyValue(blocker, "model", DUMMY_MODEL);
|
||||||
|
DispatchKeyValue(blocker, "intialstate", "1");
|
||||||
|
// DispatchKeyValueVector(blocker, "angles", this.angles);
|
||||||
|
DispatchKeyValue(blocker, "BlockType", "4");
|
||||||
|
char name[32];
|
||||||
|
Format(name, sizeof(name), "l4d2_hats_%d", createdWalls.Length);
|
||||||
|
DispatchKeyValue(blocker, "targetname", name);
|
||||||
|
// DispatchKeyValue(blocker, "excludednpc", "player");
|
||||||
|
TeleportEntity(blocker, this.origin, this.angles, NULL_VECTOR);
|
||||||
|
if(!DispatchSpawn(blocker)) return false;
|
||||||
|
SetEntPropVector(blocker, Prop_Send, "m_vecMins", this.mins);
|
||||||
|
SetEntPropVector(blocker, Prop_Send, "m_vecMaxs", this.size);
|
||||||
|
SetEntProp(blocker, Prop_Send, "m_nSolidType", 2);
|
||||||
|
int enteffects = GetEntProp(blocker, Prop_Send, "m_fEffects");
|
||||||
|
enteffects |= 32; //EF_NODRAW
|
||||||
|
SetEntProp(blocker, Prop_Send, "m_fEffects", enteffects);
|
||||||
|
AcceptEntityInput(blocker, "Enable");
|
||||||
|
SDKHook(blocker, SDKHook_Use, OnWallClicked);
|
||||||
|
|
||||||
|
this.Draw(GLOW_GREEN, 5.0, 1.0);
|
||||||
|
this.Reset();
|
||||||
|
if(!isEdit) {
|
||||||
|
id = createdWalls.Push(EntIndexToEntRef(blocker));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int _CreateWeapon() {
|
||||||
|
int entity = -1;
|
||||||
|
if(StrEqual(this.classname, "weapon_melee")) {
|
||||||
|
entity = CreateEntityByName("weapon_melee_spawn");
|
||||||
|
DispatchKeyValue(entity, "melee_weapon", this.data);
|
||||||
|
} else {
|
||||||
|
entity = CreateEntityByName(this.classname);
|
||||||
|
DispatchKeyValue(entity, "spawnflags", "8");
|
||||||
|
}
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
int _CreateProp() {
|
||||||
|
int entity = -1;
|
||||||
|
if(this.classname[0] != '\0') {
|
||||||
|
entity = CreateEntityByName(this.classname);
|
||||||
|
} else if(this.buildType == Build_Physics)
|
||||||
|
entity = CreateEntityByName("prop_physics");
|
||||||
|
else
|
||||||
|
entity = CreateEntityByName("prop_dynamic_override");
|
||||||
|
if(entity == -1) return false;
|
||||||
|
|
||||||
|
char model[128];
|
||||||
|
GetEntPropString(this.entity, Prop_Data, "m_ModelName", model, sizeof(model));
|
||||||
|
DispatchKeyValue(entity, "model", model);
|
||||||
|
if(this.buildType == Build_NonSolid)
|
||||||
|
DispatchKeyValue(entity, "solid", "0");
|
||||||
|
else
|
||||||
|
DispatchKeyValue(entity, "solid", "6");
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _FinishPreview(int& entity) {
|
||||||
|
if(StrContains(this.classname, "weapon") > -1) {
|
||||||
|
entity = this._CreateWeapon();
|
||||||
|
} else {
|
||||||
|
entity = this._CreateProp();
|
||||||
|
}
|
||||||
|
|
||||||
|
DispatchKeyValue(entity, "targetname", "propspawner_prop");
|
||||||
|
TeleportEntity(entity, this.origin, this.angles, NULL_VECTOR);
|
||||||
|
if(!DispatchSpawn(entity)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
SetEntityRenderColor(entity, this.color[0], this.color[1], this.color[2], this.color[3]);
|
||||||
|
SetEntityRenderColor(this.entity, 255, 128, 255, 200); // reset ghost color
|
||||||
|
GlowEntity(entity, 1.4);
|
||||||
|
|
||||||
|
if(this.mode == FREELOOK)
|
||||||
|
this.SetMode(MOVE_ORIGIN);
|
||||||
|
|
||||||
|
AddSpawnedItem(entity, this.client);
|
||||||
|
char model[128];
|
||||||
|
GetEntPropString(entity, Prop_Data, "m_ModelName", model, sizeof(model));
|
||||||
|
AddRecent(model, this.data);
|
||||||
|
|
||||||
|
// Don't kill preview until cancel
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Turns current entity into a copy (not for walls)
|
||||||
|
int Copy() {
|
||||||
|
if(this.entity == INVALID_ENT_REFERENCE) return -1;
|
||||||
|
char classname[64];
|
||||||
|
GetEntityClassname(this.entity, classname, sizeof(classname));
|
||||||
|
|
||||||
|
int entity = CreateEntityByName(classname);
|
||||||
|
if(entity == -1) return -1;
|
||||||
|
GetEntPropString(this.entity, Prop_Data, "m_ModelName", classname, sizeof(classname));
|
||||||
|
DispatchKeyValue(entity, "model", classname);
|
||||||
|
|
||||||
|
|
||||||
|
Format(classname, sizeof(classname), "hats_copy_%d", this.entity);
|
||||||
|
DispatchKeyValue(entity, "targetname", classname);
|
||||||
|
|
||||||
|
DispatchKeyValue(entity, "solid", "6");
|
||||||
|
|
||||||
|
DispatchSpawn(entity);
|
||||||
|
TeleportEntity(entity, this.origin, this.angles, NULL_VECTOR);
|
||||||
|
this.entity = entity;
|
||||||
|
this.flags |= Edit_Copy;
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start editing a new wall entity
|
||||||
|
void StartWall() {
|
||||||
|
this.Reset();
|
||||||
|
this.flags |= Edit_WallCreator;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PreviewWeapon(const char[] classname) {
|
||||||
|
int entity;
|
||||||
|
// Melee weapons don't have weapon_ prefix
|
||||||
|
this.Reset();
|
||||||
|
// TODO: prevent use of preview _spawn
|
||||||
|
if(StrContains(classname, "weapon_") == -1) {
|
||||||
|
// no weapon_ prefix, its a melee
|
||||||
|
PrintToServer("Spawning weapon_melee: %s", classname);
|
||||||
|
entity = CreateEntityByName("weapon_melee");
|
||||||
|
if(entity == -1) return false;
|
||||||
|
DispatchKeyValue(entity, "melee_weapon", classname);
|
||||||
|
strcopy(this.classname, sizeof(this.classname), "weapon_melee");
|
||||||
|
this.SetData(classname);
|
||||||
|
} else {
|
||||||
|
PrintToServer("Spawning normal weapon: %s", classname);
|
||||||
|
entity = CreateEntityByName(classname);
|
||||||
|
if(entity == -1) return false;
|
||||||
|
DispatchKeyValue(entity, "spawnflags", "8");
|
||||||
|
Format(this.classname, sizeof(this.classname), "%s_spawn", classname);
|
||||||
|
}
|
||||||
|
DispatchKeyValue(entity, "targetname", "prop_preview");
|
||||||
|
DispatchKeyValue(entity, "rendercolor", "255 128 255");
|
||||||
|
DispatchKeyValue(entity, "renderamt", "200");
|
||||||
|
DispatchKeyValue(entity, "rendermode", "1");
|
||||||
|
TeleportEntity(entity, this.origin, NULL_VECTOR, NULL_VECTOR); // MUST teleport before spawn or it crashes
|
||||||
|
if(!DispatchSpawn(entity)) {
|
||||||
|
PrintToServer("Failed to spawn");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this.entity = entity;
|
||||||
|
this.flags |= (Edit_Copy | Edit_Preview);
|
||||||
|
this.SetMode(MOVE_ORIGIN);
|
||||||
|
// Seems some entities fail here:
|
||||||
|
return IsValidEntity(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PreviewModel(const char[] model, const char[] classname = "") {
|
||||||
|
// Check for an invalid model
|
||||||
|
// this.origin is not cleared by this.Reset();
|
||||||
|
GetClientAbsOrigin(this.client, this.origin);
|
||||||
|
if(StrEqual(classname, "_weapon") || StrEqual(classname, "_melee")) {
|
||||||
|
return this.PreviewWeapon(model);
|
||||||
|
}
|
||||||
|
if(PrecacheModel(model) == 0) {
|
||||||
|
PrintToServer("Invalid model: %s", model);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this.Reset();
|
||||||
|
int entity = CreateEntityByName("prop_door_rotating");
|
||||||
|
if(classname[0] == '\0') {
|
||||||
|
entity = CreateEntityByName("prop_dynamic_override");
|
||||||
|
} else {
|
||||||
|
strcopy(this.classname, sizeof(this.classname), classname);
|
||||||
|
entity = CreateEntityByName(classname);
|
||||||
|
}
|
||||||
|
if(entity == -1) {
|
||||||
|
PrintToServer("Invalid classname: %s", classname);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
DispatchKeyValue(entity, "model", model);
|
||||||
|
DispatchKeyValue(entity, "targetname", "prop_preview");
|
||||||
|
DispatchKeyValue(entity, "solid", "0");
|
||||||
|
DispatchKeyValue(entity, "rendercolor", "255 128 255");
|
||||||
|
DispatchKeyValue(entity, "renderamt", "200");
|
||||||
|
DispatchKeyValue(entity, "rendermode", "1");
|
||||||
|
TeleportEntity(entity, this.origin, NULL_VECTOR, NULL_VECTOR);
|
||||||
|
if(!DispatchSpawn(entity)) {
|
||||||
|
PrintToServer("Failed to spawn");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this.entity = entity;
|
||||||
|
this.flags |= (Edit_Copy | Edit_Preview);
|
||||||
|
this.SetMode(MOVE_ORIGIN);
|
||||||
|
// Seems some entities fail here:
|
||||||
|
return IsValidEntity(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adds an existing entity to the editor, to move it.
|
||||||
|
// asWallCopy: to instead copy the wall's size and position (walls only)
|
||||||
|
void Import(int entity, bool asWallCopy = false, editMode mode = SCALE) {
|
||||||
|
this.Reset();
|
||||||
|
GetEntPropVector(entity, Prop_Send, "m_vecOrigin", this.origin);
|
||||||
|
GetEntPropVector(entity, Prop_Send, "m_angRotation", this.angles);
|
||||||
|
this.prevOrigin = this.origin;
|
||||||
|
this.prevAngles = this.angles;
|
||||||
|
GetEntPropVector(entity, Prop_Send, "m_vecMins", this.mins);
|
||||||
|
GetEntPropVector(entity, Prop_Send, "m_vecMaxs", this.size);
|
||||||
|
if(!asWallCopy) {
|
||||||
|
this.entity = entity;
|
||||||
|
}
|
||||||
|
this.SetMode(mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cancels the current placement. If the edit is a copy/preview, the entity is also deleted
|
||||||
|
// If entity is not a wall, it will be returned
|
||||||
|
void Cancel() {
|
||||||
|
// Delete any copies:
|
||||||
|
if(this.flags & Edit_Copy || this.flags & Edit_Preview) {
|
||||||
|
RemoveEntity(this.entity);
|
||||||
|
} else if(~this.flags & Edit_WallCreator) {
|
||||||
|
// Is an edit of a prop
|
||||||
|
TeleportEntity(this.entity, this.prevOrigin, this.prevAngles, NULL_VECTOR);
|
||||||
|
}
|
||||||
|
this.SetMode(INACTIVE);
|
||||||
|
CPrintToChat(this.client, "\x04[Editor]\x01 Cancelled.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorData Editor[MAXPLAYERS+1];
|
||||||
|
|
||||||
|
Action OnWallClicked(int entity, int activator, int caller, UseType type, float value) {
|
||||||
|
int wallId = FindWallId(entity);
|
||||||
|
if(wallId > 0) {
|
||||||
|
GlowWall(wallId, GLOW_BLUE);
|
||||||
|
AcceptEntityInput(entity, "Toggle");
|
||||||
|
} else {
|
||||||
|
PrintHintText(activator, "Invalid wall entity (%d)", entity);
|
||||||
|
}
|
||||||
|
return Plugin_Continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: Stacker, copy tool, new command?
|
||||||
|
public Action Command_MakeWall(int client, int args) {
|
||||||
|
if(Editor[client].IsActive()) {
|
||||||
|
ReplyToCommand(client, "\x04[Editor]\x01 You are currently editing an entity. Finish editing your current entity with with \x05/edit done\x01 or cancel with \x04/edit cancel\x01");
|
||||||
|
} else {
|
||||||
|
Editor[client].StartWall();
|
||||||
|
if(args > 0) {
|
||||||
|
// Get values for X, Y and Z axis (defaulting to 1.0):
|
||||||
|
char arg2[8];
|
||||||
|
for(int i = 0; i < 3; i++) {
|
||||||
|
GetCmdArg(i + 1, arg2, sizeof(arg2));
|
||||||
|
float value;
|
||||||
|
if(StringToFloatEx(arg2, value) == 0) {
|
||||||
|
value = 1.0;
|
||||||
|
}
|
||||||
|
Editor[client].size[i] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
float rot[3];
|
||||||
|
GetClientEyeAngles(client, rot);
|
||||||
|
// Flip X and Y depending on rotation
|
||||||
|
// TODO: Validate
|
||||||
|
if(rot[2] > 45 && rot[2] < 135 || rot[2] > -135 && rot[2] < -45) {
|
||||||
|
float temp = Editor[client].size[0];
|
||||||
|
Editor[client].size[0] = Editor[client].size[1];
|
||||||
|
Editor[client].size[1] = temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
Editor[client].CalculateMins();
|
||||||
|
}
|
||||||
|
|
||||||
|
Editor[client].SetMode(SCALE);
|
||||||
|
GetCursorLimited(client, 100.0, Editor[client].origin, Filter_IgnorePlayer);
|
||||||
|
PrintToChat(client, "\x04[Editor]\x01 New Wall Started. End with \x05/wall build\x01 or \x04/wall cancel\x01");
|
||||||
|
PrintToChat(client, "\x04[Editor]\x01 Mode: \x05Scale\x01");
|
||||||
|
}
|
||||||
|
return Plugin_Handled;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: move wall ids to own subcommand
|
||||||
|
Action Command_Editor(int client, int args) {
|
||||||
|
if(args == 0) {
|
||||||
|
PrintToChat(client, "\x04[Editor]\x01 Created Walls: \x05%d\x01", createdWalls.Length);
|
||||||
|
for(int i = 1; i <= createdWalls.Length; i++) {
|
||||||
|
GlowWall(i, GLOW_WHITE, 20.0);
|
||||||
|
}
|
||||||
|
return Plugin_Handled;
|
||||||
|
}
|
||||||
|
char arg1[16], arg2[16];
|
||||||
|
GetCmdArg(1, arg1, sizeof(arg1));
|
||||||
|
GetCmdArg(2, arg2, sizeof(arg2));
|
||||||
|
if(StrEqual(arg1, "build") || StrEqual(arg1, "done")) {
|
||||||
|
// Remove frozen flag from user, as some modes use this
|
||||||
|
int flags = GetEntityFlags(client) & ~FL_FROZEN;
|
||||||
|
SetEntityFlags(client, flags);
|
||||||
|
|
||||||
|
int entity;
|
||||||
|
CompleteType result = Editor[client].Done(entity);
|
||||||
|
switch(result) {
|
||||||
|
case Complete_WallSuccess: {
|
||||||
|
if(entity > 0)
|
||||||
|
PrintToChat(client, "\x04[Editor]\x01 Wall Creation: \x05Wall #%d Created\x01", entity + 1);
|
||||||
|
else
|
||||||
|
PrintToChat(client, "\x04[Editor]\x01 Wall Edit: \x04Complete\x01");
|
||||||
|
}
|
||||||
|
case Complete_PropSpawned:
|
||||||
|
PrintToChat(client, "\x04[Editor]\x01 Prop Spawned: \x04%d\x01", entity);
|
||||||
|
|
||||||
|
case Complete_EditSuccess:
|
||||||
|
PrintToChat(client, "\x04[Editor]\x01 Entity Edited: \x04%d\x01", entity);
|
||||||
|
|
||||||
|
default:
|
||||||
|
PrintToChat(client, "\x04[Editor]\x01 Unknown result");
|
||||||
|
}
|
||||||
|
} else if(StrEqual(arg1, "cancel")) {
|
||||||
|
int flags = GetEntityFlags(client) & ~FL_FROZEN;
|
||||||
|
SetEntityFlags(client, flags);
|
||||||
|
Editor[client].Cancel();
|
||||||
|
if(Editor[client].flags & Edit_Preview)
|
||||||
|
PrintToChat(client, "\x04[Editor]\x01 Prop Spawer: \x04Cancelled\x01");
|
||||||
|
else if(Editor[client].flags & Edit_WallCreator) {
|
||||||
|
PrintToChat(client, "\x04[Editor]\x01 Wall Creation: \x04Cancelled\x01");
|
||||||
|
} else {
|
||||||
|
PrintToChat(client, "\x04[Editor]\x01 Entity Edit: \x04Cancelled\x01");
|
||||||
|
}
|
||||||
|
} else if(StrEqual(arg1, "export")) {
|
||||||
|
// TODO: support exp #id
|
||||||
|
float origin[3], angles[3], size[3];
|
||||||
|
if(Editor[client].IsActive()) {
|
||||||
|
origin = Editor[client].origin;
|
||||||
|
angles = Editor[client].angles;
|
||||||
|
size = Editor[client].size;
|
||||||
|
Export(client, arg2, Editor[client].entity, origin, angles, size);
|
||||||
|
} else {
|
||||||
|
int id = GetWallId(client, arg2);
|
||||||
|
if(id == -1) return Plugin_Handled;
|
||||||
|
int entity = GetWallEntity(id);
|
||||||
|
GetEntPropVector(entity, Prop_Send, "m_vecOrigin", origin);
|
||||||
|
if(HasEntProp(entity, Prop_Send, "m_vecAngles"))
|
||||||
|
GetEntPropVector(entity, Prop_Send, "m_vecAngles", angles);
|
||||||
|
GetEntPropVector(entity, Prop_Send, "m_vecMaxs", size);
|
||||||
|
Export(client, arg2, entity, origin, angles, size);
|
||||||
|
}
|
||||||
|
} else if(StrEqual(arg1, "delete")) {
|
||||||
|
if(Editor[client].IsActive() && args == 1) {
|
||||||
|
int entity = Editor[client].entity;
|
||||||
|
if(IsValidEntity(entity)) {
|
||||||
|
PrintToChat(client, "\x04[Editor]\x01 You are not editing any existing entity, use \x05/wall cancel\x01 to stop or \x05/wall delete <id/all>");
|
||||||
|
} else if(entity > MaxClients) {
|
||||||
|
RemoveEntity(entity);
|
||||||
|
Editor[client].Reset();
|
||||||
|
PrintToChat(client, "\x04[Editor]\x01 Deleted current entity");
|
||||||
|
} else {
|
||||||
|
PrintToChat(client, "\x04[Editor]\x01 Cannot delete player entities.");
|
||||||
|
}
|
||||||
|
} else if(StrEqual(arg2, "all")) {
|
||||||
|
int walls = createdWalls.Length;
|
||||||
|
for(int i = 1; i <= createdWalls.Length; i++) {
|
||||||
|
DeleteWall(i);
|
||||||
|
}
|
||||||
|
PrintToChat(client, "\x04[Editor]\x01 Deleted \x05%d\x01 Walls", walls);
|
||||||
|
} else {
|
||||||
|
int id = GetWallId(client, arg2);
|
||||||
|
if(id > -1) {
|
||||||
|
DeleteWall(id);
|
||||||
|
PrintToChat(client, "\x04[Editor]\x01 Deleted Wall: \x05#%d\x01", id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if(StrEqual(arg1, "create")) {
|
||||||
|
ReplyToCommand(client, "\x04[Editor]\x01 Syntax: /mkwall [size x] [size y] [size z]");
|
||||||
|
} else if(StrEqual(arg1, "toggle")) {
|
||||||
|
if(StrEqual(arg2, "all")) {
|
||||||
|
int walls = createdWalls.Length;
|
||||||
|
for(int i = 1; i <= createdWalls.Length; i++) {
|
||||||
|
int entity = GetWallEntity(i);
|
||||||
|
AcceptEntityInput(entity, "Toggle");
|
||||||
|
GlowWall(i, GLOW_BLUE);
|
||||||
|
}
|
||||||
|
PrintToChat(client, "\x04[Editor]\x01 Toggled \x05%d\x01 walls", walls);
|
||||||
|
} else {
|
||||||
|
int id = GetWallId(client, arg2);
|
||||||
|
if(id > -1) {
|
||||||
|
int entity = GetWallEntity(id);
|
||||||
|
AcceptEntityInput(entity, "Toggle");
|
||||||
|
GlowWall(id, GLOW_BLUE);
|
||||||
|
PrintToChat(client, "\x04[Editor]\x01 Toggled Wall: \x05#%d\x01", id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if(StrEqual(arg1, "filter")) {
|
||||||
|
if(args < 3) {
|
||||||
|
ReplyToCommand(client, "\x04[Editor]\x01 Syntax: \x05/walls filter <id/all> <filter type>\x04");
|
||||||
|
ReplyToCommand(client, "\x04[Editor]\x01 Valid filters: \x05player");
|
||||||
|
return Plugin_Handled;
|
||||||
|
}
|
||||||
|
|
||||||
|
char arg3[32];
|
||||||
|
GetCmdArg(3, arg3, sizeof(arg3));
|
||||||
|
|
||||||
|
SetVariantString(arg3);
|
||||||
|
if(StrEqual(arg2, "all")) {
|
||||||
|
int walls = createdWalls.Length;
|
||||||
|
for(int i = 1; i <= createdWalls.Length; i++) {
|
||||||
|
int entity = GetWallEntity(i);
|
||||||
|
AcceptEntityInput(entity, "SetExcluded");
|
||||||
|
}
|
||||||
|
PrintToChat(client, "\x04[Editor]\x01 Set %d walls' filter to \x05%s\x01", walls, arg3);
|
||||||
|
} else {
|
||||||
|
int id = GetWallId(client, arg2);
|
||||||
|
if(id > -1) {
|
||||||
|
int entity = GetWallEntity(id);
|
||||||
|
AcceptEntityInput(entity, "SetExcluded");
|
||||||
|
PrintToChat(client, "\x04[Editor]\x01 Set wall #%d filter to \x05%s\x01", id, arg3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if(StrEqual(arg1, "edit")) {
|
||||||
|
int id = GetWallId(client, arg2);
|
||||||
|
if(id > -1) {
|
||||||
|
int entity = GetWallEntity(id);
|
||||||
|
Editor[client].Import(entity);
|
||||||
|
PrintToChat(client, "\x04[Editor]\x01 Editing wall \x05%d\x01. End with \x05/wall done\x01 or \x04/wall cancel\x01", id);
|
||||||
|
PrintToChat(client, "\x04[Editor]\x01 Mode: \x05Scale\x01");
|
||||||
|
}
|
||||||
|
} else if(StrEqual(arg1, "edite") || (arg1[0] == 'c' && arg1[1] == 'u')) {
|
||||||
|
int index = GetLookingEntity(client, Filter_ValidHats); //GetClientAimTarget(client, false);
|
||||||
|
if(index > 0) {
|
||||||
|
Editor[client].Import(index, false, MOVE_ORIGIN);
|
||||||
|
char classname[32];
|
||||||
|
char targetname[32];
|
||||||
|
GetEntityClassname(index, classname, sizeof(classname));
|
||||||
|
GetEntPropString(index, Prop_Data, "m_target", targetname, sizeof(targetname));
|
||||||
|
PrintToChat(client, "\x04[Editor]\x01 Editing entity \x05%d (%s) [%s]\x01. End with \x05/wall done\x01", index, classname, targetname);
|
||||||
|
PrintToChat(client, "\x04[Editor]\x01 Mode: \x05Move & Rotate\x01");
|
||||||
|
} else {
|
||||||
|
ReplyToCommand(client, "\x04[Editor]\x01 Invalid or non existent entity");
|
||||||
|
}
|
||||||
|
} else if(StrEqual(arg1, "copy")) {
|
||||||
|
if(Editor[client].IsActive()) {
|
||||||
|
int oldEntity = Editor[client].entity;
|
||||||
|
if(oldEntity == INVALID_ENT_REFERENCE) {
|
||||||
|
PrintToChat(client, "\x04[Editor]\x01 Finish editing your wall first: \x05/wall done\x01 or \x04/wall cancel\x01");
|
||||||
|
} else {
|
||||||
|
int entity = Editor[client].Copy();
|
||||||
|
PrintToChat(client, "\x04[Editor]\x01 Editing copy \x05%d\x01 of entity \x05%d\x01. End with \x05/edit done\x01 or \x04/edit cancel\x01", entity, oldEntity);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
int id = GetWallId(client, arg2);
|
||||||
|
if(id > -1) {
|
||||||
|
int entity = GetWallEntity(id);
|
||||||
|
Editor[client].Import(entity, true);
|
||||||
|
GetCursorLimited(client, 100.0, Editor[client].origin, Filter_IgnorePlayer);
|
||||||
|
PrintToChat(client, "\x04[Editor]\x01 Editing copy of wall \x05%d\x01. End with \x05/wall build\x01 or \x04/wall cancel\x01", id);
|
||||||
|
PrintToChat(client, "\x04[Editor]\x01 Mode: \x05Scale\x01");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if(StrEqual(arg1, "list")) {
|
||||||
|
for(int i = 1; i <= createdWalls.Length; i++) {
|
||||||
|
int entity = GetWallEntity(i);
|
||||||
|
ReplyToCommand(client, "Wall #%d - EntIndex: %d", i, EntRefToEntIndex(entity));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ReplyToCommand(client, "\x04[Editor]\x01 See console for list of commands");
|
||||||
|
GetCmdArg(0, arg1, sizeof(arg1));
|
||||||
|
PrintToConsole(client, "%s done / build - Finishes editing, creates wall if making wall", arg1);
|
||||||
|
PrintToConsole(client, "%s cancel - Cancels editing (for entity edits is same as done)", arg1);
|
||||||
|
PrintToConsole(client, "%s list - Lists all walls", arg1);
|
||||||
|
PrintToConsole(client, "%s filter <id/all> <filter type> - Sets classname filter for walls, doesnt really work", arg1);
|
||||||
|
PrintToConsole(client, "%s toggle <id/all> - Toggles if wall is active (collides)", arg1);
|
||||||
|
PrintToConsole(client, "%s delete <id/all> - Deletes the wall(s)", arg1);
|
||||||
|
PrintToConsole(client, "%s edit <id> - Edits wall id", arg1);
|
||||||
|
PrintToConsole(client, "%s copy [id] - If editing creates a new copy of wall/entity, else copies wall id", arg1);
|
||||||
|
PrintToConsole(client, "%s cursor - Starts editing the entity you looking at", arg1);
|
||||||
|
}
|
||||||
|
return Plugin_Handled;
|
||||||
|
}
|
||||||
|
|
||||||
|
int GetWallId(int client, const char[] arg) {
|
||||||
|
int id;
|
||||||
|
if(StringToIntEx(arg, id) > 0 && id > 0 && id <= createdWalls.Length) {
|
||||||
|
int entity = GetWallEntity(id);
|
||||||
|
if(!IsValidEntity(entity)) {
|
||||||
|
ReplyToCommand(client, "\x04[Editor]\x01 The wall with specified id no longer exists.");
|
||||||
|
createdWalls.Erase(id - 1);
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
return id;
|
||||||
|
} else {
|
||||||
|
ReplyToCommand(client, "\x04[Editor]\x01 Invalid wall id, must be between 0 - %d", createdWalls.Length - 1 );
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int GetWallEntity(int id) {
|
||||||
|
if(id <= 0 || id > createdWalls.Length) {
|
||||||
|
ThrowError("Invalid wall id (%d)", id);
|
||||||
|
}
|
||||||
|
return createdWalls.Get(id - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tries to find the id of the wall based off entity
|
||||||
|
int FindWallId(int entity) {
|
||||||
|
for(int i = 1; i <= createdWalls.Length; i++) {
|
||||||
|
int entRef = createdWalls.Get(i - 1);
|
||||||
|
int ent = EntRefToEntIndex(entRef);
|
||||||
|
if(ent == entity) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GlowWall(int id, int glowColor[4], float lifetime = 5.0) {
|
||||||
|
int ref = GetWallEntity(id);
|
||||||
|
if(IsValidEntity(ref)) {
|
||||||
|
float pos[3], mins[3], maxs[3], angles[3];
|
||||||
|
GetEntPropVector(ref, Prop_Send, "m_angRotation", angles);
|
||||||
|
GetEntPropVector(ref, Prop_Send, "m_vecOrigin", pos);
|
||||||
|
GetEntPropVector(ref, Prop_Send, "m_vecMins", mins);
|
||||||
|
GetEntPropVector(ref, Prop_Send, "m_vecMaxs", maxs);
|
||||||
|
Effect_DrawBeamBoxRotatableToAll(pos, mins, maxs, angles, g_iLaserIndex, 0, 0, 30, lifetime, 0.4, 0.4, 0, 1.0, glowColor, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeleteWall(int id) {
|
||||||
|
GlowWall(id, GLOW_RED_ALPHA);
|
||||||
|
int ref = GetWallEntity(id);
|
||||||
|
if(IsValidEntity(ref)) {
|
||||||
|
RemoveEntity(ref);
|
||||||
|
}
|
||||||
|
createdWalls.Erase(id - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Export(int client, const char[] expType, int entity, const float origin[3], const float angles[3], const float size[3]) {
|
||||||
|
char sPath[PLATFORM_MAX_PATH];
|
||||||
|
char currentMap[64];
|
||||||
|
GetCurrentMap(currentMap, sizeof(currentMap));
|
||||||
|
|
||||||
|
BuildPath(Path_SM, sPath, sizeof(sPath), "data/exports");
|
||||||
|
CreateDirectory(sPath, 1406);
|
||||||
|
BuildPath(Path_SM, sPath, sizeof(sPath), "data/exports/%s.cfg", currentMap);
|
||||||
|
File file = OpenFile(sPath, "w");
|
||||||
|
if(file == null) {
|
||||||
|
PrintToServer("[Editor] Export: Cannot open \"%s\", cant write", sPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
PrintWriteLine(client, file, "{");
|
||||||
|
if(entity != INVALID_ENT_REFERENCE) {
|
||||||
|
char model[64];
|
||||||
|
GetEntPropString(entity, Prop_Data, "m_ModelName", model, sizeof(model));
|
||||||
|
if(StrEqual(expType, "json")) {
|
||||||
|
PrintWriteLine(client, file, "\t\"model\": \"%s\",", model);
|
||||||
|
} else{
|
||||||
|
PrintWriteLine(client, file, "\t\"model\" \"%s\"", model);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(StrEqual(expType, "json")) {
|
||||||
|
PrintWriteLine(client, file, "\t\"origin\": [%.2f, %.2f, %.2f],", origin[0], origin[1], origin[2]);
|
||||||
|
PrintWriteLine(client, file, "\t\"angles\": [%.2f, %.2f, %.2f],", angles[0], angles[1], angles[2]);
|
||||||
|
PrintWriteLine(client, file, "\t\"size\": [%.2f, %.2f, %.2f]", size[0], size[1], size[2]);
|
||||||
|
} else {
|
||||||
|
PrintWriteLine(client, file, "\t\"origin\" \"%.2f %.2f %.2f\"", origin[0], origin[1], origin[2]);
|
||||||
|
PrintWriteLine(client, file, "\t\"angles\" \"%.2f %.2f %.2f\"", angles[0], angles[1], angles[2]);
|
||||||
|
PrintWriteLine(client, file, "\t\"size\" \"%.2f %.2f %.2f\"", size[0], size[1], size[2]);
|
||||||
|
}
|
||||||
|
PrintWriteLine(client, file, "}");
|
||||||
|
delete file;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrintWriteLine(int client, File file, const char[] format, any ...) {
|
||||||
|
char line[100];
|
||||||
|
VFormat(line, sizeof(line), format, 4);
|
||||||
|
if(file != null)
|
||||||
|
file.WriteLine(line);
|
||||||
|
PrintToChat(client, line);
|
||||||
|
}
|
251
scripting/include/hats/hat_presets.sp
Normal file
251
scripting/include/hats/hat_presets.sp
Normal file
|
@ -0,0 +1,251 @@
|
||||||
|
#define MODEL_LENGTH 64
|
||||||
|
char SURVIVOR_NAMES[8][] = { "nick", "rochelle", "coach", "ellis", "bill", "zoey", "francis", "louis"};
|
||||||
|
|
||||||
|
enum struct PresetLocation {
|
||||||
|
int survivorSet;
|
||||||
|
float offset[3];
|
||||||
|
float angles[3];
|
||||||
|
}
|
||||||
|
|
||||||
|
enum struct HatPreset {
|
||||||
|
float offset[3];
|
||||||
|
float angles[3];
|
||||||
|
char model[MODEL_LENGTH];
|
||||||
|
char type[32];
|
||||||
|
float size;
|
||||||
|
|
||||||
|
ArrayList locations;
|
||||||
|
|
||||||
|
int Spawn() {
|
||||||
|
if(!PrecacheModel(this.model)) {
|
||||||
|
LogError("[Hats] Failed to precache preset model: \"%s\"", this.model);
|
||||||
|
}
|
||||||
|
int entity = CreateEntityByName(this.type);
|
||||||
|
if(entity == -1) {
|
||||||
|
LogError("[Hats] Failed to spawn hat for type %s: \"%s\"", this.type, this.model);
|
||||||
|
}
|
||||||
|
DispatchKeyValue(entity, "solid", "6");
|
||||||
|
DispatchKeyValue(entity, "model", this.model);
|
||||||
|
if(HasEntProp(entity, Prop_Send, "m_flModelScale"))
|
||||||
|
SetEntPropFloat(entity, Prop_Send, "m_flModelScale", this.size);
|
||||||
|
if(!DispatchSpawn(entity)) {
|
||||||
|
LogError("Could not spawn entity of type %s model \"%s\"", this.type, this.model);
|
||||||
|
}
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Apply(int client) {
|
||||||
|
float offset[3], angles[3];
|
||||||
|
int entity = this.Spawn();
|
||||||
|
EquipHat(client, entity, this.type, HAT_PRESET);
|
||||||
|
this.GetLocation(client, offset, angles);
|
||||||
|
hatData[client].offset = offset;
|
||||||
|
hatData[client].angles = angles;
|
||||||
|
CreateTimer(0.1, Timer_RemountSimple, GetClientUserId(client));
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GetLocation(int client, float offset[3], float angles[3]) {
|
||||||
|
if(this.locations != null) {
|
||||||
|
int survivorSet = GetEntProp(client, Prop_Send, "m_survivorCharacter");
|
||||||
|
if(survivorSet < 0 || survivorSet > 7) survivorSet = 0;
|
||||||
|
PresetLocation location;
|
||||||
|
for(int i = 0; i < this.locations.Length; i++) {
|
||||||
|
this.locations.GetArray(i, location, sizeof(location));
|
||||||
|
if(location.survivorSet == survivorSet) {
|
||||||
|
offset = location.offset;
|
||||||
|
angles = location.angles;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
offset = this.offset;
|
||||||
|
angles = this.angles;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Action Timer_RemountSimple(Handle h, int userid) {
|
||||||
|
int client = GetClientOfUserId(userid);
|
||||||
|
if(client > 0) {
|
||||||
|
int entity = GetHat(client);
|
||||||
|
if(entity > 0)
|
||||||
|
TeleportEntity(entity, hatData[client].offset, hatData[client].angles, NULL_VECTOR);
|
||||||
|
}
|
||||||
|
return Plugin_Handled;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoadPresets() {
|
||||||
|
KeyValues kv = new KeyValues("Presets");
|
||||||
|
|
||||||
|
char sPath[PLATFORM_MAX_PATH];
|
||||||
|
BuildPath(Path_SM, sPath, sizeof(sPath), "data/hat_presets.cfg");
|
||||||
|
|
||||||
|
if(!FileExists(sPath) || !kv.ImportFromFile(sPath)) {
|
||||||
|
PrintToServer("[Hats] Missing presets list");
|
||||||
|
delete kv;
|
||||||
|
}
|
||||||
|
StringMap presets = new StringMap();
|
||||||
|
|
||||||
|
kv.GotoFirstSubKey();
|
||||||
|
// char type[32];
|
||||||
|
int count = 0;
|
||||||
|
char name[32];
|
||||||
|
do {
|
||||||
|
kv.GetSectionName(name, sizeof(name));
|
||||||
|
HatPreset preset;
|
||||||
|
// kv.GetString("type", entCfg.type, sizeof(entCfg.type), "prop_physics");
|
||||||
|
kv.GetString("model", preset.model, sizeof(preset.model), "");
|
||||||
|
kv.GetString("type", preset.type, sizeof(preset.type), "prop_dynamic");
|
||||||
|
preset.size = kv.GetFloat("size", 1.0);
|
||||||
|
if(preset.model[0] == '\0') {
|
||||||
|
PrintToServer("[Hats] Warn: Skipping %s, no model", name);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(kv.JumpToKey("default")) {
|
||||||
|
kv.GetVector("offset", preset.offset, NULL_VECTOR);
|
||||||
|
kv.GetVector("angles", preset.angles, NULL_VECTOR);
|
||||||
|
kv.GoBack();
|
||||||
|
} else {
|
||||||
|
PrintToServer("[Hats] Warn: Missing default for %s", name);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(int i = 0; i < 7; i++) {
|
||||||
|
if(kv.JumpToKey(SURVIVOR_NAMES[i])) {
|
||||||
|
if(preset.locations == null) {
|
||||||
|
preset.locations = new ArrayList(sizeof(PresetLocation));
|
||||||
|
}
|
||||||
|
PresetLocation location;
|
||||||
|
location.survivorSet = i; // TODO: confirm with l4d/l4d2 modes?
|
||||||
|
kv.GetVector("offset", location.offset, NULL_VECTOR);
|
||||||
|
kv.GetVector("angles", location.angles, NULL_VECTOR);
|
||||||
|
preset.locations.PushArray(location);
|
||||||
|
kv.GoBack();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
count++;
|
||||||
|
presets.SetArray(name, preset, sizeof(preset));
|
||||||
|
} while(kv.GotoNextKey(true));
|
||||||
|
kv.GoBack();
|
||||||
|
|
||||||
|
PrintToServer("[Hats] Loaded %d presets", count);
|
||||||
|
|
||||||
|
|
||||||
|
if(g_HatPresets != null) {
|
||||||
|
HatPreset preset;
|
||||||
|
StringMapSnapshot snapshot = g_HatPresets.Snapshot();
|
||||||
|
for(int i = 0; i <= snapshot.Length; i++) {
|
||||||
|
snapshot.GetKey(i, name, sizeof(name));
|
||||||
|
g_HatPresets.GetArray(name, preset, sizeof(preset));
|
||||||
|
if(preset.locations != null) {
|
||||||
|
delete preset.locations;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete g_HatPresets;
|
||||||
|
}
|
||||||
|
g_HatPresets = presets;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Action Command_DoAHatPreset(int client, int args) {
|
||||||
|
AdminId adminId = GetUserAdmin(client);
|
||||||
|
if(cvar_sm_hats_enabled.IntValue == 1) {
|
||||||
|
if(adminId == INVALID_ADMIN_ID) {
|
||||||
|
PrintToChat(client, "[Hats] Hats are for admins only");
|
||||||
|
return Plugin_Handled;
|
||||||
|
} else if(!adminId.HasFlag(Admin_Cheats)) {
|
||||||
|
PrintToChat(client, "[Hats] You do not have permission");
|
||||||
|
return Plugin_Handled;
|
||||||
|
}
|
||||||
|
} else if(cvar_sm_hats_enabled.IntValue == 0) {
|
||||||
|
ReplyToCommand(client, "[Hats] Hats are disabled");
|
||||||
|
return Plugin_Handled;
|
||||||
|
} else if(GetClientTeam(client) != 2 && ~cvar_sm_hats_flags.IntValue & view_as<int>(HatConfig_InfectedHats)) {
|
||||||
|
PrintToChat(client, "[Hats] Hats are only available for survivors.");
|
||||||
|
return Plugin_Handled;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int entity = GetHat(client);
|
||||||
|
if(entity > 0) {
|
||||||
|
if(args > 0) {
|
||||||
|
char arg[64];
|
||||||
|
GetCmdArg(1, arg, sizeof(arg));
|
||||||
|
if(arg[0] == 'v') {
|
||||||
|
ReplyToCommand(client, "\t{");
|
||||||
|
GetEntPropString(entity, Prop_Data, "m_ModelName", arg, sizeof(arg));
|
||||||
|
ReplyToCommand(client, "\t\t\"model\"\t\"%s\"", arg);
|
||||||
|
ReplyToCommand(client, "\t\t\"default\"\n\t\t{");
|
||||||
|
ReplyToCommand(client, "\t\t\t\"origin\"\t\"%f %f %f\"", hatData[client].offset[0], hatData[client].offset[1], hatData[client].offset[2]);
|
||||||
|
ReplyToCommand(client, "\t\t\t\"angles\"\t\"%f %f %f\"", hatData[client].angles[0], hatData[client].angles[1], hatData[client].angles[2]);
|
||||||
|
ReplyToCommand(client, "\t\t}");
|
||||||
|
ReplyToCommand(client, "\t\t\"size\"\t\"%.1f\"", GetEntPropFloat(entity, Prop_Send, "m_flModelScale"));
|
||||||
|
GetEntityClassname(entity, arg, sizeof(arg));
|
||||||
|
ReplyToCommand(client, "\t\t\"type\"\t\"%.1f\"", arg);
|
||||||
|
ReplyToCommand(client, "\t}");
|
||||||
|
ReplyToCommand(client, "Flags: %d", hatData[client].flags);
|
||||||
|
} else {
|
||||||
|
ReplyToCommand(client, "Unknown option");
|
||||||
|
}
|
||||||
|
// ReplyToCommand(client, "CurOffset: %f %f %f", );
|
||||||
|
return Plugin_Handled;
|
||||||
|
}
|
||||||
|
if(HasFlag(client, HAT_PRESET)) {
|
||||||
|
ClearHat(client);
|
||||||
|
RemoveEntity(entity);
|
||||||
|
PrintToChat(client, "[Hats] Cleared your hat preset");
|
||||||
|
hatPresetCookie.Set(client, "");
|
||||||
|
ActivePreset[client][0] = '\0';
|
||||||
|
|
||||||
|
} else {
|
||||||
|
PrintToChat(client, "[Hats] You already have a hat. Clear your hat to apply a preset.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Menu menu = new Menu(HatPresetHandler);
|
||||||
|
menu.SetTitle("Choose a Hat", client);
|
||||||
|
char id[32];
|
||||||
|
StringMapSnapshot snapshot = g_HatPresets.Snapshot();
|
||||||
|
if(snapshot.Length == 0) {
|
||||||
|
PrintToChat(client, "[Hats] Seems there is no presets...");
|
||||||
|
delete snapshot;
|
||||||
|
return Plugin_Handled;
|
||||||
|
}
|
||||||
|
for(int i = 0; i < snapshot.Length; i++) {
|
||||||
|
snapshot.GetKey(i, id, sizeof(id));
|
||||||
|
menu.AddItem(id, id);
|
||||||
|
}
|
||||||
|
menu.Display(client, 0);
|
||||||
|
delete snapshot;
|
||||||
|
}
|
||||||
|
return Plugin_Handled;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int HatPresetHandler(Menu menu, MenuAction action, int param1, int param2) {
|
||||||
|
if (action == MenuAction_Select) {
|
||||||
|
static char info[32];
|
||||||
|
menu.GetItem(param2, info, sizeof(info));
|
||||||
|
HatPreset preset;
|
||||||
|
if(g_HatPresets.GetArray(info, preset, sizeof(preset))) {
|
||||||
|
strcopy(ActivePreset[param1], 32, info);
|
||||||
|
hatPresetCookie.Set(param1, info);
|
||||||
|
preset.Apply(param1);
|
||||||
|
ReplyToCommand(param1, "[Hats] Hat preset \"%s\" applied, enjoy!", info);
|
||||||
|
} else {
|
||||||
|
ReplyToCommand(param1, "Unknown hat preset \"%s\"", info);
|
||||||
|
}
|
||||||
|
} else if (action == MenuAction_End)
|
||||||
|
delete menu;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RestoreActivePreset(int client) {
|
||||||
|
if(ActivePreset[client][0] != '\0') {
|
||||||
|
HatPreset preset;
|
||||||
|
g_HatPresets.GetArray(ActivePreset[client], preset, sizeof(preset));
|
||||||
|
preset.Apply(client);
|
||||||
|
}
|
||||||
|
}
|
856
scripting/include/hats/hats.sp
Normal file
856
scripting/include/hats/hats.sp
Normal file
|
@ -0,0 +1,856 @@
|
||||||
|
enum hatFlags {
|
||||||
|
HAT_NONE = 0,
|
||||||
|
HAT_POCKET = 1,
|
||||||
|
HAT_REVERSED = 2,
|
||||||
|
HAT_COMMANDABLE = 4,
|
||||||
|
HAT_RAINBOW = 8,
|
||||||
|
HAT_PRESET = 16
|
||||||
|
}
|
||||||
|
enum struct HatInstance {
|
||||||
|
int entity; // The entity REFERENCE
|
||||||
|
int visibleEntity; // Thee visible entity REF
|
||||||
|
Handle yeetGroundTimer;
|
||||||
|
|
||||||
|
// Original data for entity
|
||||||
|
float orgPos[3];
|
||||||
|
float orgAng[3];
|
||||||
|
float offset[3];
|
||||||
|
float angles[3];
|
||||||
|
int collisionGroup;
|
||||||
|
int solidType;
|
||||||
|
int moveType;
|
||||||
|
|
||||||
|
float scale;
|
||||||
|
int flags;
|
||||||
|
float rainbowColor[3];
|
||||||
|
int rainbowTicks;
|
||||||
|
bool rainbowReverse;
|
||||||
|
char attachPoint[32];
|
||||||
|
}
|
||||||
|
enum struct PlayerHatData {
|
||||||
|
char activePresetName[32];
|
||||||
|
int lastRequestTime;
|
||||||
|
|
||||||
|
void Reset() {
|
||||||
|
this.activePresetName[0] = '\0';
|
||||||
|
this.lastRequestTime = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
enum hatFeatures {
|
||||||
|
HatConfig_None = 0,
|
||||||
|
HatConfig_PlayerHats = 1,
|
||||||
|
HatConfig_RespectAdminImmunity = 2,
|
||||||
|
HatConfig_FakeHat = 4,
|
||||||
|
HatConfig_NoSaferoomHats = 8,
|
||||||
|
HatConfig_PlayerHatConsent = 16,
|
||||||
|
HatConfig_InfectedHats = 32,
|
||||||
|
HatConfig_ReversedHats = 64,
|
||||||
|
HatConfig_DeleteThrownHats = 128
|
||||||
|
}
|
||||||
|
char ActivePreset[MAXPLAYERS+1][32];
|
||||||
|
int lastHatrequestTime[MAXPLAYERS+1];
|
||||||
|
PlayerHatData g_hatData[MAXPLAYERS+1];
|
||||||
|
HatInstance hatData[MAXPLAYERS+1];
|
||||||
|
StringMap g_HatPresets;
|
||||||
|
|
||||||
|
#define MAX_FORBIDDEN_CLASSNAMES 14
|
||||||
|
char FORBIDDEN_CLASSNAMES[MAX_FORBIDDEN_CLASSNAMES][] = {
|
||||||
|
"prop_door_rotating_checkpoint",
|
||||||
|
"env_physics_blocker",
|
||||||
|
"env_player_blocker",
|
||||||
|
"func_brush",
|
||||||
|
"func_simpleladder",
|
||||||
|
"prop_door_rotating",
|
||||||
|
"func_button",
|
||||||
|
"func_elevator",
|
||||||
|
"func_button_timed",
|
||||||
|
"func_tracktrain",
|
||||||
|
"func_movelinear",
|
||||||
|
// "infected",
|
||||||
|
"func_lod",
|
||||||
|
"func_door",
|
||||||
|
"prop_ragdoll"
|
||||||
|
};
|
||||||
|
|
||||||
|
#define MAX_FORBIDDEN_MODELS 1
|
||||||
|
char FORBIDDEN_MODELS[MAX_FORBIDDEN_MODELS][] = {
|
||||||
|
"models/props_vehicles/c130.mdl",
|
||||||
|
};
|
||||||
|
|
||||||
|
#define MAX_REVERSE_CLASSNAMES 2
|
||||||
|
// Classnames that should automatically trigger reverse infected
|
||||||
|
static char REVERSE_CLASSNAMES[MAX_REVERSE_CLASSNAMES][] = {
|
||||||
|
"infected",
|
||||||
|
"func_movelinear"
|
||||||
|
};
|
||||||
|
|
||||||
|
Action Command_Hat(int client, int args) {
|
||||||
|
static char cmdName[8];
|
||||||
|
GetCmdArg(0, cmdName, sizeof(cmdName));
|
||||||
|
AdminId adminId = GetUserAdmin(client);
|
||||||
|
bool isForced = adminId != INVALID_ADMIN_ID && StrEqual(cmdName, "sm_hatf");
|
||||||
|
|
||||||
|
int hatter = GetHatter(client);
|
||||||
|
if(hatter > 0) {
|
||||||
|
ClearHat(hatter, HasFlag(hatter, HAT_REVERSED));
|
||||||
|
PrintToChat(hatter, "[Hats] %N has unhatted themselves", client);
|
||||||
|
return Plugin_Handled;
|
||||||
|
}
|
||||||
|
// if(g_Hat[client].Ha)
|
||||||
|
}
|
||||||
|
|
||||||
|
Action Command_DoAHat(int client, int args) {
|
||||||
|
if(UnhatSelf(client)) {
|
||||||
|
return Plugin_Handled;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char cmdName[8];
|
||||||
|
GetCmdArg(0, cmdName, sizeof(cmdName));
|
||||||
|
AdminId adminId = GetUserAdmin(client);
|
||||||
|
bool isForced = adminId != INVALID_ADMIN_ID && StrEqual(cmdName, "sm_hatf");
|
||||||
|
if(cvar_sm_hats_enabled.IntValue == 1) {
|
||||||
|
if(adminId == INVALID_ADMIN_ID) {
|
||||||
|
PrintToChat(client, "[Hats] Hats are for admins only");
|
||||||
|
return Plugin_Handled;
|
||||||
|
} else if(!adminId.HasFlag(Admin_Cheats)) {
|
||||||
|
PrintToChat(client, "[Hats] You do not have permission");
|
||||||
|
return Plugin_Handled;
|
||||||
|
}
|
||||||
|
} else if(cvar_sm_hats_enabled.IntValue == 0) {
|
||||||
|
ReplyToCommand(client, "[Hats] Hats are disabled");
|
||||||
|
return Plugin_Handled;
|
||||||
|
} else if(GetClientTeam(client) != 2 && ~cvar_sm_hats_flags.IntValue & view_as<int>(HatConfig_InfectedHats)) {
|
||||||
|
PrintToChat(client, "[Hats] Hats are only available for survivors.");
|
||||||
|
return Plugin_Handled;
|
||||||
|
}
|
||||||
|
|
||||||
|
int entity = GetHat(client);
|
||||||
|
if(entity > 0) {
|
||||||
|
if(HasFlag(client, HAT_PRESET)) {
|
||||||
|
PrintToChat(client, "[Hats] Your hat is a preset, use /hatp to remove it.");
|
||||||
|
return Plugin_Handled;
|
||||||
|
}
|
||||||
|
char arg[4];
|
||||||
|
GetCmdArg(1, arg, sizeof(arg));
|
||||||
|
if(arg[0] == 'e') {
|
||||||
|
ReplyToCommand(client, "\t\t\"origin\"\t\"%f %f %f\"", hatData[client].offset[0], hatData[client].offset[1], hatData[client].offset[2]);
|
||||||
|
ReplyToCommand(client, "\t\t\"angles\"\t\"%f %f %f\"", hatData[client].angles[0], hatData[client].angles[1], hatData[client].angles[2]);
|
||||||
|
return Plugin_Handled;
|
||||||
|
} else if(arg[0] == 'v') {
|
||||||
|
ReplyToCommand(client, "Flags: %d", hatData[client].flags);
|
||||||
|
// ReplyToCommand(client, "CurOffset: %f %f %f", );
|
||||||
|
return Plugin_Handled;
|
||||||
|
} else if(arg[0] == 'a') {
|
||||||
|
ShowAttachPointMenu(client);
|
||||||
|
return Plugin_Handled;
|
||||||
|
}
|
||||||
|
// int orgEntity = entity;
|
||||||
|
if(HasFlag(client, HAT_REVERSED)) {
|
||||||
|
entity = client;
|
||||||
|
}
|
||||||
|
ClearParent(entity);
|
||||||
|
|
||||||
|
if(arg[0] == 's') {
|
||||||
|
char sizeStr[4];
|
||||||
|
GetCmdArg(2, sizeStr, sizeof(sizeStr));
|
||||||
|
float size = StringToFloat(sizeStr);
|
||||||
|
if(size == 0.0) {
|
||||||
|
ReplyToCommand(client, "[Hats] Invalid size");
|
||||||
|
return Plugin_Handled;
|
||||||
|
}
|
||||||
|
if(HasEntProp(entity, Prop_Send, "m_flModelScale"))
|
||||||
|
SetEntPropFloat(entity, Prop_Send, "m_flModelScale", size);
|
||||||
|
else
|
||||||
|
PrintHintText(client, "Hat does not support scaling");
|
||||||
|
// Change the size of it's parent instead
|
||||||
|
int child = -1;
|
||||||
|
while((child = FindEntityByClassname(child, "*")) != INVALID_ENT_REFERENCE )
|
||||||
|
{
|
||||||
|
int parent = GetEntPropEnt(child, Prop_Data, "m_pParent");
|
||||||
|
if(parent == entity) {
|
||||||
|
if(HasEntProp(child, Prop_Send, "m_flModelScale")) {
|
||||||
|
PrintToConsole(client, "found child %d for %d", child, entity);
|
||||||
|
SetEntPropFloat(child, Prop_Send, "m_flModelScale", size);
|
||||||
|
} else {
|
||||||
|
PrintToChat(client, "Child %d for %d cannot be scaled", child, entity);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Reattach entity:
|
||||||
|
EquipHat(client, entity);
|
||||||
|
return Plugin_Handled;
|
||||||
|
} else if(arg[0] == 'r' && arg[1] == 'a') {
|
||||||
|
SetFlag(client, HAT_RAINBOW);
|
||||||
|
hatData[client].rainbowTicks = 0;
|
||||||
|
hatData[client].rainbowReverse = false;
|
||||||
|
hatData[client].rainbowColor[0] = 0.0;
|
||||||
|
hatData[client].rainbowColor[1] = 255.0;
|
||||||
|
hatData[client].rainbowColor[2] = 255.0;
|
||||||
|
EquipHat(client, entity);
|
||||||
|
ReplyToCommand(client, "Rainbow hats enabled");
|
||||||
|
return Plugin_Handled;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-enable physics and restore collision/solidity
|
||||||
|
AcceptEntityInput(entity, "EnableMotion");
|
||||||
|
SetEntProp(entity, Prop_Send, "m_CollisionGroup", hatData[client].collisionGroup);
|
||||||
|
SetEntProp(entity, Prop_Send, "m_nSolidType", hatData[client].solidType);
|
||||||
|
|
||||||
|
// Remove frozen flag (only "infected" and "witch" are frozen, but just incase:)
|
||||||
|
int flags = GetEntityFlags(entity) & ~FL_FROZEN;
|
||||||
|
SetEntityFlags(entity, flags);
|
||||||
|
|
||||||
|
// Clear visible hats (HatConfig_FakeHat is enabled)
|
||||||
|
int visibleEntity = EntRefToEntIndex(hatData[client].visibleEntity);
|
||||||
|
SDKUnhook(entity, SDKHook_SetTransmit, OnRealTransmit);
|
||||||
|
if(visibleEntity > 0) {
|
||||||
|
SDKUnhook(visibleEntity, SDKHook_SetTransmit, OnVisibleTransmit);
|
||||||
|
RemoveEntity(visibleEntity);
|
||||||
|
hatData[client].visibleEntity = INVALID_ENT_REFERENCE;
|
||||||
|
}
|
||||||
|
// Grant temp god & remove after time
|
||||||
|
tempGod[client] = true;
|
||||||
|
if(client <= MaxClients) {
|
||||||
|
SDKHook(client, SDKHook_OnTakeDamageAlive, OnTakeDamageAlive);
|
||||||
|
CreateTimer(2.0, Timer_RemoveGod, GetClientUserId(client));
|
||||||
|
}
|
||||||
|
if(entity <= MaxClients) {
|
||||||
|
SDKHook(entity, SDKHook_OnTakeDamageAlive, OnTakeDamageAlive);
|
||||||
|
CreateTimer(2.0, Timer_RemoveGod, GetClientUserId(entity));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore movement:
|
||||||
|
if(entity <= MaxClients) {
|
||||||
|
// If player, remove roll & and just default to WALK movetype
|
||||||
|
hatData[client].orgAng[2] = 0.0;
|
||||||
|
SetEntityMoveType(entity, MOVETYPE_WALK);
|
||||||
|
} else {
|
||||||
|
// If not player, then just use whatever they were pre-hat
|
||||||
|
SetEntProp(entity, Prop_Send, "movetype", hatData[client].moveType);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(arg[0] == 'y') { // Hat yeeting:
|
||||||
|
char classname[16];
|
||||||
|
GetEntityClassname(entity, classname, sizeof(classname));
|
||||||
|
if(StrEqual(classname, "prop_dynamic")) {
|
||||||
|
ReplyToCommand(client, "You cannot yeet this prop (it has no physics)");
|
||||||
|
return Plugin_Handled;
|
||||||
|
}
|
||||||
|
GetClientEyeAngles(client, hatData[client].orgAng);
|
||||||
|
GetClientAbsOrigin(client, hatData[client].orgPos);
|
||||||
|
hatData[client].orgPos[2] += 45.0;
|
||||||
|
float ang[3], vel[3];
|
||||||
|
|
||||||
|
// Calculate the angle to throw at
|
||||||
|
GetClientEyeAngles(client, ang);
|
||||||
|
ang[2] = 0.0;
|
||||||
|
if(ang[0] > 0.0) ang[0] = -ang[0];
|
||||||
|
// ang[0] = -45.0;
|
||||||
|
|
||||||
|
// Calculate velocity to throw based on direction
|
||||||
|
vel[0] = Cosine(DegToRad(ang[1])) * GetRandomFloat(1300.0, 1700.0);
|
||||||
|
vel[1] = Sine(DegToRad(ang[1])) * GetRandomFloat(1300.0, 1700.0);
|
||||||
|
vel[2] = GetRandomFloat(700.0, 900.0);
|
||||||
|
if(entity <= MaxClients) {
|
||||||
|
// For players, use the built in fling function
|
||||||
|
TeleportEntity(entity, hatData[client].orgPos, hatData[client].orgAng, NULL_VECTOR);
|
||||||
|
L4D2_CTerrorPlayer_Fling(entity, client, vel);
|
||||||
|
} /*else if(visibleEntity > 0) {
|
||||||
|
PrintToChat(client, "Yeeting fake car...");
|
||||||
|
ClearParent(visibleEntity);
|
||||||
|
|
||||||
|
SetEntProp(visibleEntity, Prop_Send, "movetype", 6);
|
||||||
|
|
||||||
|
AcceptEntityInput(visibleEntity, "EnableMotion");
|
||||||
|
|
||||||
|
TeleportEntity(entity, OUT_OF_BOUNDS, hatData[client].orgAng, NULL_VECTOR);
|
||||||
|
TeleportEntity(visibleEntity, hatData[client].orgPos, hatData[client].orgAng, vel);
|
||||||
|
DataPack pack;
|
||||||
|
CreateDataTimer(4.0, Timer_PropYeetEnd, pack);
|
||||||
|
pack.WriteCell(hatData[client].entity);
|
||||||
|
pack.WriteCell(hatData[client].visibleEntity);
|
||||||
|
pack.WriteCell(hatData[client].collisionGroup);
|
||||||
|
pack.WriteCell(hatData[client].solidType);
|
||||||
|
pack.WriteCell(hatData[client].moveType);
|
||||||
|
hatData[client].visibleEntity = INVALID_ENT_REFERENCE;
|
||||||
|
hatData[client].entity = INVALID_ENT_REFERENCE;
|
||||||
|
} */ else {
|
||||||
|
// For actual props, offset it 35 units above and 80 units infront to reduce collision-incaps and then throw
|
||||||
|
GetHorizontalPositionFromClient(client, 80.0, hatData[client].orgPos);
|
||||||
|
hatData[client].orgPos[2] += 35.0;
|
||||||
|
TeleportEntity(entity, hatData[client].orgPos, hatData[client].orgAng, vel);
|
||||||
|
// Sleep the physics after enoug time for it to most likely have landed
|
||||||
|
if(hatData[client].yeetGroundTimer != null) {
|
||||||
|
// TODO: FIX null issue
|
||||||
|
delete hatData[client].yeetGroundTimer;
|
||||||
|
}
|
||||||
|
DataPack pack1;
|
||||||
|
hatData[client].yeetGroundTimer = CreateDataTimer(0.5, Timer_GroundKill, pack1, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE);
|
||||||
|
pack1.WriteCell(hatData[client].entity);
|
||||||
|
pack1.WriteCell(GetClientUserId(client));
|
||||||
|
DataPack pack2;
|
||||||
|
CreateDataTimer(7.7, Timer_PropSleep, pack2);
|
||||||
|
pack2.WriteCell(hatData[client].entity);
|
||||||
|
pack2.WriteCell(GetClientUserId(client));
|
||||||
|
}
|
||||||
|
PrintToChat(client, "[Hats] Yeeted hat");
|
||||||
|
hatData[client].entity = INVALID_ENT_REFERENCE;
|
||||||
|
return Plugin_Handled;
|
||||||
|
} else if(arg[0] == 'c') {
|
||||||
|
float pos[3];
|
||||||
|
// Grabs a cursor position with some checks to prevent placing into (in)visible walls
|
||||||
|
if(GetSmartCursorLocation(client, pos)) {
|
||||||
|
if(CanHatBePlaced(client, pos)) {
|
||||||
|
if(entity <= MaxClients)
|
||||||
|
L4D_WarpToValidPositionIfStuck(entity);
|
||||||
|
hatData[client].orgPos = pos;
|
||||||
|
TeleportEntity(entity, hatData[client].orgPos, hatData[client].orgAng, NULL_VECTOR);
|
||||||
|
PrintToChat(client, "[Hats] Placed hat on cursor.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
PrintToChat(client, "[Hats] Could not find cursor position.");
|
||||||
|
}
|
||||||
|
} else if(arg[0] == 'p' || (entity <= MaxClients && arg[0] != 'r')) {
|
||||||
|
// Place the hat down on the cursor if specified OR if entity is hat
|
||||||
|
float pos[3], ang[3];
|
||||||
|
if(HasFlag(client, HAT_REVERSED)) {
|
||||||
|
// If we are reversed, then place ourselves where our "hatter" is
|
||||||
|
GetClientEyePosition(entity, hatData[client].orgPos);
|
||||||
|
GetClientEyeAngles(entity, hatData[client].orgAng);
|
||||||
|
TeleportEntity(client, hatData[client].orgPos, hatData[client].orgAng, NULL_VECTOR);
|
||||||
|
PrintToChat(entity, "[Hats] Placed hat in front of you.");
|
||||||
|
} else {
|
||||||
|
// If we are normal, then get position infront of us, offset by model size
|
||||||
|
GetClientEyePosition(client, pos);
|
||||||
|
GetClientEyeAngles(client, ang);
|
||||||
|
GetHorizontalPositionFromOrigin(pos, ang, 80.0, pos);
|
||||||
|
ang[0] = 0.0;
|
||||||
|
float mins[3];
|
||||||
|
GetEntPropVector(entity, Prop_Data, "m_vecMins", mins);
|
||||||
|
pos[2] += mins[2];
|
||||||
|
// Find the nearest ground (raytrace bottom->up)
|
||||||
|
FindGround(pos, pos);
|
||||||
|
// Check if the destination is acceptable (not saferooms if enabled)
|
||||||
|
if(CanHatBePlaced(client, pos)) {
|
||||||
|
hatData[client].orgPos = pos;
|
||||||
|
hatData[client].orgAng = ang;
|
||||||
|
TeleportEntity(entity, hatData[client].orgPos, hatData[client].orgAng, NULL_VECTOR);
|
||||||
|
PrintToChat(client, "[Hats] Placed hat in front of you.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if(arg[0] == 'd') {
|
||||||
|
// Use the new wall editor
|
||||||
|
Editor[client].Reset();
|
||||||
|
Editor[client].entity = EntIndexToEntRef(entity);
|
||||||
|
Editor[client].SetMode(MOVE_ORIGIN);
|
||||||
|
PrintToChat(client, "\x04[Hats] \x01Beta Prop Mover active for \x04%d", entity);
|
||||||
|
} else {
|
||||||
|
PrintToChat(client, "[Hats] Restored hat to its original position.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore the scale pre-hat
|
||||||
|
if(hatData[client].scale > 0 && HasEntProp(entity, Prop_Send, "m_flModelScale"))
|
||||||
|
SetEntPropFloat(entity, Prop_Send, "m_flModelScale", hatData[client].scale);
|
||||||
|
|
||||||
|
// If no other options performed, then restore to original position and remove our reference
|
||||||
|
AcceptEntityInput(entity, "Sleep");
|
||||||
|
TeleportEntity(entity, hatData[client].orgPos, hatData[client].orgAng, NULL_VECTOR);
|
||||||
|
hatData[client].entity = INVALID_ENT_REFERENCE;
|
||||||
|
} else {
|
||||||
|
// Find a new hatable entity
|
||||||
|
int flags = 0;
|
||||||
|
entity = GetLookingEntity(client, Filter_ValidHats);
|
||||||
|
if(entity <= 0) {
|
||||||
|
PrintCenterText(client, "[Hats] No entity found");
|
||||||
|
return Plugin_Handled;
|
||||||
|
} else if(entity == EntRefToEntIndex(Editor[client].entity)) {
|
||||||
|
// Prevent making an entity you editing a hat
|
||||||
|
return Plugin_Handled;
|
||||||
|
} else if(!isForced && cvar_sm_hats_max_distance.FloatValue > 0.0 && entity >= MaxClients) {
|
||||||
|
float posP[3], posE[3];
|
||||||
|
GetClientEyePosition(client, posP);
|
||||||
|
GetEntPropVector(entity, Prop_Data, "m_vecOrigin", posE);
|
||||||
|
if(GetVectorDistance(posP, posE) > cvar_sm_hats_max_distance.FloatValue) {
|
||||||
|
PrintCenterText(client, "[Hats] Entity too far away");
|
||||||
|
return Plugin_Handled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make hat reversed if 'r' passed in
|
||||||
|
char arg[4];
|
||||||
|
if(args > 0) {
|
||||||
|
GetCmdArg(1, arg, sizeof(arg));
|
||||||
|
if(arg[0] == 'r') {
|
||||||
|
flags |= view_as<int>(HAT_REVERSED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int parent = GetEntPropEnt(entity, Prop_Data, "m_hParent");
|
||||||
|
if(parent > 0 && entity > MaxClients) {
|
||||||
|
PrintToConsole(client, "[Hats] Selected a child entity, selecting parent (child %d -> parent %d)", entity, parent);
|
||||||
|
entity = parent;
|
||||||
|
} else if(entity <= MaxClients) { // Checks for hatting a player entity
|
||||||
|
if(IsFakeClient(entity) && L4D_GetIdlePlayerOfBot(entity) != -1) {
|
||||||
|
PrintToChat(client, "[Hats] Cannot hat idle bots");
|
||||||
|
return Plugin_Handled;
|
||||||
|
} else if(GetClientTeam(entity) != 2 && ~cvar_sm_hats_flags.IntValue & view_as<int>(HatConfig_InfectedHats)) {
|
||||||
|
PrintToChat(client, "[Hats] Cannot make enemy a hat... it's dangerous");
|
||||||
|
return Plugin_Handled;
|
||||||
|
} else if(entity == EntRefToEntIndex(Editor[client].entity)) {
|
||||||
|
// Old check left in in case you hatting child entity
|
||||||
|
PrintToChat(client, "[Hats] You are currently editing this entity");
|
||||||
|
return Plugin_Handled;
|
||||||
|
} else if(inSaferoom[client] && cvar_sm_hats_flags.IntValue & view_as<int>(HatConfig_NoSaferoomHats)) {
|
||||||
|
PrintToChat(client, "[Hats] Hats are not allowed in the saferoom");
|
||||||
|
return Plugin_Handled;
|
||||||
|
} else if(!IsPlayerAlive(entity) || GetEntProp(entity, Prop_Send, "m_isHangingFromLedge") || L4D_IsPlayerCapped(entity)) {
|
||||||
|
PrintToChat(client, "[Hats] Player is either dead, hanging, or in the process of dying.");
|
||||||
|
return Plugin_Handled;
|
||||||
|
} else if(EntRefToEntIndex(hatData[entity].entity) == client) {
|
||||||
|
PrintToChat(client, "[Hats] Woah you can't be making a black hole, jesus be careful.");
|
||||||
|
return Plugin_Handled;
|
||||||
|
} else if(~cvar_sm_hats_flags.IntValue & view_as<int>(HatConfig_PlayerHats)) {
|
||||||
|
PrintToChat(client, "[Hats] Player hats are disabled");
|
||||||
|
return Plugin_Handled;
|
||||||
|
} else if(!CanTarget(entity)) {
|
||||||
|
PrintToChat(client, "[Hats] Player has disabled player hats for themselves.");
|
||||||
|
return Plugin_Handled;
|
||||||
|
} else if(!CanTarget(client)) {
|
||||||
|
PrintToChat(client, "[Hats] Cannot hat a player when you have player hats turned off");
|
||||||
|
return Plugin_Handled;
|
||||||
|
} else if(cvar_sm_hats_flags.IntValue & view_as<int>(HatConfig_RespectAdminImmunity)) {
|
||||||
|
AdminId targetAdmin = GetUserAdmin(entity);
|
||||||
|
AdminId clientAdmin = GetUserAdmin(client);
|
||||||
|
if(targetAdmin != INVALID_ADMIN_ID && clientAdmin == INVALID_ADMIN_ID) {
|
||||||
|
PrintToChat(client, "[Hats] Cannot target an admin");
|
||||||
|
return Plugin_Handled;
|
||||||
|
} else if(targetAdmin != INVALID_ADMIN_ID && targetAdmin.ImmunityLevel > clientAdmin.ImmunityLevel) {
|
||||||
|
PrintToChat(client, "[Hats] Cannot target %N, they are immune to you", entity);
|
||||||
|
return Plugin_Handled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!isForced &&
|
||||||
|
!IsFakeClient(entity) &&
|
||||||
|
cvar_sm_hats_flags.IntValue & view_as<int>(HatConfig_PlayerHatConsent) &&
|
||||||
|
~flags & view_as<int>(HAT_REVERSED)
|
||||||
|
) {
|
||||||
|
int lastRequestDiff = GetTime() - lastHatRequestTime[client];
|
||||||
|
if(lastRequestDiff < PLAYER_HAT_REQUEST_COOLDOWN) {
|
||||||
|
PrintToChat(client, "[Hats] Player hat under %d seconds cooldown", PLAYER_HAT_REQUEST_COOLDOWN - lastRequestDiff);
|
||||||
|
return Plugin_Handled;
|
||||||
|
}
|
||||||
|
|
||||||
|
Menu menu = new Menu(HatConsentHandler);
|
||||||
|
menu.SetTitle("%N: Requests to hat you", client);
|
||||||
|
char id[8];
|
||||||
|
Format(id, sizeof(id), "%d|1", GetClientUserId(client));
|
||||||
|
menu.AddItem(id, "Accept");
|
||||||
|
Format(id, sizeof(id), "%d|0", GetClientUserId(client));
|
||||||
|
menu.AddItem(id, "Reject");
|
||||||
|
menu.Display(entity, 12);
|
||||||
|
PrintHintText(client, "Sent hat request to %N", entity);
|
||||||
|
PrintToChat(entity, "[Hats] %N requests to hat you, 1 to Accept, 2 to Reject. Expires in 12 seconds.", client);
|
||||||
|
return Plugin_Handled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
char classname[64];
|
||||||
|
GetEntityClassname(entity, classname, sizeof(classname));
|
||||||
|
|
||||||
|
// Check for any class that should always be reversed
|
||||||
|
if(~flags & view_as<int>(HAT_REVERSED)) {
|
||||||
|
for(int i = 0; i < MAX_REVERSE_CLASSNAMES; i++) {
|
||||||
|
if(StrEqual(REVERSE_CLASSNAMES[i], classname)) {
|
||||||
|
flags |= view_as<int>(HAT_REVERSED);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EquipHat(client, entity, classname, flags);
|
||||||
|
}
|
||||||
|
return Plugin_Handled;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#define MAX_ATTACHMENT_POINTS 20
|
||||||
|
char ATTACHMENT_POINTS[MAX_ATTACHMENT_POINTS][] = {
|
||||||
|
"eyes",
|
||||||
|
"molotov",
|
||||||
|
"pills",
|
||||||
|
"grenade",
|
||||||
|
"primary",
|
||||||
|
"medkit",
|
||||||
|
"melee",
|
||||||
|
"survivor_light",
|
||||||
|
"bleedout",
|
||||||
|
"forward",
|
||||||
|
"survivor_neck",
|
||||||
|
"muzzle_flash",
|
||||||
|
"spine",
|
||||||
|
"legL",
|
||||||
|
"legR",
|
||||||
|
"thighL",
|
||||||
|
"thighR",
|
||||||
|
"lfoot",
|
||||||
|
"rfoot",
|
||||||
|
"mouth",
|
||||||
|
};
|
||||||
|
|
||||||
|
void ShowAttachPointMenu(int client) {
|
||||||
|
Menu menu = new Menu(AttachPointHandler);
|
||||||
|
menu.SetTitle("Choose an attach point");
|
||||||
|
for(int i = 0; i < MAX_ATTACHMENT_POINTS; i++) {
|
||||||
|
menu.AddItem(ATTACHMENT_POINTS[i], ATTACHMENT_POINTS[i]);
|
||||||
|
}
|
||||||
|
menu.Display(client, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int AttachPointHandler(Menu menu, MenuAction action, int client, int param2) {
|
||||||
|
if (action == MenuAction_Select) {
|
||||||
|
char attachPoint[32];
|
||||||
|
menu.GetItem(param2, attachPoint, sizeof(attachPoint));
|
||||||
|
if(!HasHat(client)) {
|
||||||
|
ReplyToCommand(client, "No hat is equipped");
|
||||||
|
} else {
|
||||||
|
int hat = GetHat(client);
|
||||||
|
char classname[32];
|
||||||
|
GetEntityClassname(hat, classname, sizeof(classname));
|
||||||
|
EquipHat(client, hat, classname, hatData[client].flags, attachPoint);
|
||||||
|
CReplyToCommand(client, "Attachment point set to {olive}%s", attachPoint);
|
||||||
|
}
|
||||||
|
} else if (action == MenuAction_End)
|
||||||
|
delete menu;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles consent that a person to be hatted by another player
|
||||||
|
int HatConsentHandler(Menu menu, MenuAction action, int target, int param2) {
|
||||||
|
if (action == MenuAction_Select) {
|
||||||
|
char info[8];
|
||||||
|
menu.GetItem(param2, info, sizeof(info));
|
||||||
|
char str[2][8];
|
||||||
|
ExplodeString(info, "|", str, 2, 8, false);
|
||||||
|
int activator = GetClientOfUserId(StringToInt(str[0]));
|
||||||
|
int hatAction = StringToInt(str[1]);
|
||||||
|
if(activator == 0) {
|
||||||
|
ReplyToCommand(target, "Player has disconnected");
|
||||||
|
return 0;
|
||||||
|
} else if(hatAction == 1) {
|
||||||
|
EquipHat(activator, target, "player", 0);
|
||||||
|
} else {
|
||||||
|
ClientCommand(activator, "play player/orch_hit_csharp_short.wav");
|
||||||
|
PrintHintText(activator, "%N refused your request", target);
|
||||||
|
lastHatRequestTime[activator] = GetTime();
|
||||||
|
}
|
||||||
|
} else if (action == MenuAction_End)
|
||||||
|
delete menu;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsHatsEnabled(int client) {
|
||||||
|
return (cvar_sm_hats_enabled.IntValue == 1 && GetUserAdmin(client) != INVALID_ADMIN_ID) || cvar_sm_hats_enabled.IntValue == 2
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClearHats() {
|
||||||
|
for(int i = 1; i <= MaxClients; i++) {
|
||||||
|
if(HasHat(i)) {
|
||||||
|
ClearHat(i, false);
|
||||||
|
}
|
||||||
|
if(IsClientConnected(i) && IsClientInGame(i)) SetEntityMoveType(i, MOVETYPE_WALK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void ClearHat(int i, bool restore = false) {
|
||||||
|
|
||||||
|
int entity = EntRefToEntIndex(hatData[i].entity);
|
||||||
|
int visibleEntity = EntRefToEntIndex(hatData[i].visibleEntity);
|
||||||
|
int modifyEntity = HasFlag(i, HAT_REVERSED) ? i : entity;
|
||||||
|
|
||||||
|
if(visibleEntity > 0) {
|
||||||
|
SDKUnhook(visibleEntity, SDKHook_SetTransmit, OnVisibleTransmit);
|
||||||
|
RemoveEntity(visibleEntity);
|
||||||
|
}
|
||||||
|
if(modifyEntity > 0) {
|
||||||
|
SDKUnhook(modifyEntity, SDKHook_SetTransmit, OnRealTransmit);
|
||||||
|
ClearParent(modifyEntity);
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int flags = GetEntityFlags(entity) & ~FL_FROZEN;
|
||||||
|
SetEntityFlags(entity, flags);
|
||||||
|
// if(HasEntProp(entity, Prop_Send, "m_flModelScale"))
|
||||||
|
// SetEntPropFloat(entity, Prop_Send, "m_flModelScale", 1.0);
|
||||||
|
SetEntProp(modifyEntity, Prop_Send, "m_CollisionGroup", hatData[i].collisionGroup);
|
||||||
|
// SetEntProp(modifyEntity, Prop_Send, "m_nSolidType", hatData[i].solidType);
|
||||||
|
SetEntProp(modifyEntity, Prop_Send, "movetype", hatData[i].moveType);
|
||||||
|
|
||||||
|
hatData[i].entity = INVALID_ENT_REFERENCE;
|
||||||
|
hatData[i].visibleEntity = INVALID_ENT_REFERENCE;
|
||||||
|
|
||||||
|
if(HasFlag(i, HAT_REVERSED)) {
|
||||||
|
entity = i;
|
||||||
|
i = modifyEntity;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(entity <= MAXPLAYERS) {
|
||||||
|
AcceptEntityInput(entity, "EnableLedgeHang");
|
||||||
|
}
|
||||||
|
if(restore) {
|
||||||
|
// If hat is a player, override original position to hat wearer's
|
||||||
|
if(entity <= MAXPLAYERS && HasEntProp(i, Prop_Send, "m_vecOrigin")) {
|
||||||
|
GetEntPropVector(i, Prop_Send, "m_vecOrigin", hatData[i].orgPos);
|
||||||
|
}
|
||||||
|
// Restore to original position
|
||||||
|
if(HasFlag(i, HAT_REVERSED)) {
|
||||||
|
TeleportEntity(i, hatData[i].orgPos, hatData[i].orgAng, NULL_VECTOR);
|
||||||
|
} else {
|
||||||
|
TeleportEntity(entity, hatData[i].orgPos, hatData[i].orgAng, NULL_VECTOR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HasHat(int client) {
|
||||||
|
return GetHat(client) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int GetHat(int client) {
|
||||||
|
if(hatData[client].entity == INVALID_ENT_REFERENCE) return -1;
|
||||||
|
int index = EntRefToEntIndex(hatData[client].entity);
|
||||||
|
if(index <= 0) return -1;
|
||||||
|
if(!IsValidEntity(index)) return -1;
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
int GetHatter(int client) {
|
||||||
|
for(int i = 1; i <= MaxClients; i++) {
|
||||||
|
if(EntRefToEntIndex(hatData[i].entity) == client) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CanTarget(int victim) {
|
||||||
|
static char buf[2];
|
||||||
|
noHatVictimCookie.Get(victim, buf, sizeof(buf));
|
||||||
|
return StringToInt(buf) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsHatAllowedInSaferoom(int client) {
|
||||||
|
if(L4D_IsMissionFinalMap()) return true;
|
||||||
|
if(HasFlag(client, HAT_PRESET)) return true;
|
||||||
|
char name[32];
|
||||||
|
GetEntityClassname(hatData[client].entity, name, sizeof(name));
|
||||||
|
// Don't allow non-weapons in saferoom
|
||||||
|
if(StrEqual(name, "prop_physics") || StrEqual(name, "prop_dynamic")) {
|
||||||
|
GetEntPropString(hatData[client].entity, Prop_Data, "m_ModelName", name, sizeof(name));
|
||||||
|
if(StrContains(name, "gnome") != -1 || StrContains(name, "propanecanist") != -1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
float mins[3], maxs[3];
|
||||||
|
GetEntPropVector(hatData[client].entity, Prop_Data, "m_vecMins", mins);
|
||||||
|
GetEntPropVector(hatData[client].entity, Prop_Data, "m_vecMaxs", maxs);
|
||||||
|
PrintToConsoleAll("Dropping hat for %N: prop_something (%s) (min %.0f,%.0f,%.0f) (max %.0f,%.0f,%.0f)", client, name, mins[0], mins[1], mins[2], maxs[0], maxs[1], maxs[2]);
|
||||||
|
return false;
|
||||||
|
} else if(StrEqual(name, "player") || StrContains(name, "weapon_") > -1 || StrContains(name, "upgrade_") > -1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
PrintToConsole(client, "Dropping hat: %s", name);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsHatAllowed(int client) {
|
||||||
|
char name[32];
|
||||||
|
GetEntityClassname(hatData[client].entity, name, sizeof(name));
|
||||||
|
if(StrEqual(name, "prop_physics") || StrEqual(name, "prop_dynamic")) {
|
||||||
|
GetEntPropString(hatData[client].entity, Prop_Data, "m_ModelName", name, sizeof(name));
|
||||||
|
if(StrContains(name, "models/props_vehicles/c130.mdl") != -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CanHatBePlaced(int client, const float pos[3]) {
|
||||||
|
if(cvar_sm_hats_flags.IntValue & view_as<int>(HatConfig_NoSaferoomHats)) {
|
||||||
|
if(IsHatAllowedInSaferoom(client)) return true;
|
||||||
|
Address nav = L4D_GetNearestNavArea(pos, 200.0);
|
||||||
|
if(nav != Address_Null) {
|
||||||
|
int spawnFlags = L4D_GetNavArea_SpawnAttributes(nav) ;
|
||||||
|
if(spawnFlags & NAV_SPAWN_CHECKPOINT) {
|
||||||
|
PrintToServer("\"%L\" tried to place hat in saferoom, denied.", client);
|
||||||
|
PrintToChat(client, "[Hats] Hat is not allowed in saferoom and has been returned.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetFlag(int client, hatFlags flag) {
|
||||||
|
hatData[client].flags |= view_as<int>(flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HasFlag(int client, hatFlags flag) {
|
||||||
|
return hatData[client].flags & view_as<int>(flag) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EquipHat(int client, int entity, const char[] classname = "", int flags = HAT_NONE, const char[] attachPoint = "eyes") {
|
||||||
|
if(HasHat(client))
|
||||||
|
ClearHat(client, true);
|
||||||
|
|
||||||
|
// Player specific tweaks
|
||||||
|
int visibleEntity;
|
||||||
|
if(entity == 0) {
|
||||||
|
ThrowError("Attempted to equip world (client = %d)", client);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
hatData[client].entity = EntIndexToEntRef(entity);
|
||||||
|
int modifyEntity = HasFlag(client, HAT_REVERSED) ? client : entity;
|
||||||
|
hatData[client].collisionGroup = GetEntProp(modifyEntity, Prop_Send, "m_CollisionGroup");
|
||||||
|
hatData[client].solidType = GetEntProp(modifyEntity, Prop_Send, "m_nSolidType");
|
||||||
|
hatData[client].moveType = GetEntProp(modifyEntity, Prop_Send, "movetype");
|
||||||
|
strcopy(hatData[client].attachPoint, 32, attachPoint);
|
||||||
|
|
||||||
|
if(client <= MaxClients) SDKHook(client, SDKHook_OnTakeDamageAlive, OnTakeDamageAlive);
|
||||||
|
if(entity <= MaxClients) SDKHook(entity, SDKHook_OnTakeDamageAlive, OnTakeDamageAlive);
|
||||||
|
|
||||||
|
if(modifyEntity <= MaxClients) {
|
||||||
|
AcceptEntityInput(modifyEntity, "DisableLedgeHang");
|
||||||
|
} else if(cvar_sm_hats_flags.IntValue & view_as<int>(HatConfig_FakeHat)) {
|
||||||
|
return;
|
||||||
|
// char model[64];
|
||||||
|
// GetEntPropString(entity, Prop_Data, "m_ModelName", model, sizeof(model));
|
||||||
|
// visibleEntity = CreateEntityByName("prop_dynamic");
|
||||||
|
// DispatchKeyValue(visibleEntity, "model", model);
|
||||||
|
// DispatchKeyValue(visibleEntity, "disableshadows", "1");
|
||||||
|
// DispatchSpawn(visibleEntity);
|
||||||
|
// SetEntProp(visibleEntity, Prop_Send, "m_CollisionGroup", 1);
|
||||||
|
// hatData[client].visibleEntity = EntIndexToEntRef(visibleEntity);
|
||||||
|
// SDKHook(visibleEntity, SDKHook_SetTransmit, OnVisibleTransmit);
|
||||||
|
// SDKHook(entity, SDKHook_SetTransmit, OnRealTransmit);
|
||||||
|
}
|
||||||
|
SDKHook(client, SDKHook_OnTakeDamageAlive, OnTakeDamageAlive);
|
||||||
|
// Temp remove the hat to be yoinked by another player
|
||||||
|
for(int i = 1; i <= MaxClients; i++) {
|
||||||
|
if(i != client && EntRefToEntIndex(hatData[i].entity) == entity) {
|
||||||
|
ClearHat(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called on initial hat
|
||||||
|
if(classname[0] != '\0') {
|
||||||
|
if(entity <= MaxClients && !IsFakeClient(entity)) {
|
||||||
|
PrintToChat(entity, "[Hats] %N has hatted you, type /hat to dismount at any time", client);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset things:
|
||||||
|
hatData[client].flags = 0;
|
||||||
|
hatData[client].offset[0] = hatData[client].offset[1] = hatData[client].offset[2] = 0.0;
|
||||||
|
hatData[client].angles[0] = hatData[client].angles[1] = hatData[client].angles[2] = 0.0;
|
||||||
|
|
||||||
|
if(flags & view_as<int>(HAT_PRESET)) {
|
||||||
|
hatData[client].flags |= view_as<int>(HAT_PRESET);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(modifyEntity <= MaxClients) {
|
||||||
|
if(HasFlag(client, HAT_REVERSED)) {
|
||||||
|
hatData[client].offset[2] += 7.2;
|
||||||
|
} else {
|
||||||
|
hatData[client].offset[2] += 4.2;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
float mins[3];
|
||||||
|
GetEntPropVector(modifyEntity, Prop_Send, "m_vecMins", mins);
|
||||||
|
hatData[client].offset[2] += mins[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
if(cvar_sm_hats_flags.IntValue & view_as<int>(HatConfig_ReversedHats) && flags & view_as<int>(HAT_REVERSED)) {
|
||||||
|
SetFlag(client, HAT_REVERSED);
|
||||||
|
if(StrEqual(classname, "infected") || (entity <= MaxClients && IsFakeClient(entity))) {
|
||||||
|
SetFlag(client, HAT_COMMANDABLE);
|
||||||
|
}
|
||||||
|
PrintToChat(client, "[Hats] Set yourself as %s (%d)'s hat", classname, entity);
|
||||||
|
if(entity <= MaxClients) {
|
||||||
|
LogAction(client, entity, "\"%L\" made themselves \"%L\" (%s)'s hat (%d, %d)", client, entity, classname, entity, visibleEntity);
|
||||||
|
PrintToChat(entity, "[Hats] %N has set themselves as your hat", client);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// TODO: freeze tank
|
||||||
|
if(StrEqual(classname, "infected") || StrEqual(classname, "witch") || (entity <= MaxClients && GetClientTeam(entity) == 3 && L4D2_GetPlayerZombieClass(entity) == L4D2ZombieClass_Tank)) {
|
||||||
|
int eflags = GetEntityFlags(entity) | FL_FROZEN;
|
||||||
|
SetEntityFlags(entity, eflags);
|
||||||
|
hatData[client].offset[2] = 36.0;
|
||||||
|
}
|
||||||
|
if(entity <= MaxClients)
|
||||||
|
PrintToChat(client, "[Hats] Set %N (%d) as a hat", entity, entity);
|
||||||
|
else
|
||||||
|
PrintToChat(client, "[Hats] Set %s (%d) as a hat", classname, entity);
|
||||||
|
if(entity <= MaxClients)
|
||||||
|
LogAction(client, entity, "\"%L\" picked up \"%L\" (%s) as a hat (%d, %d)", client, entity, classname, entity, visibleEntity);
|
||||||
|
else
|
||||||
|
LogAction(client, -1, "\"%L\" picked up %s as a hat (%d, %d)", client, classname, entity, visibleEntity);
|
||||||
|
}
|
||||||
|
hatData[client].scale = -1.0;
|
||||||
|
|
||||||
|
}
|
||||||
|
AcceptEntityInput(modifyEntity, "DisableMotion");
|
||||||
|
|
||||||
|
// Get the data (position, angle, movement shit)
|
||||||
|
|
||||||
|
GetEntPropVector(modifyEntity, Prop_Send, "m_vecOrigin", hatData[client].orgPos);
|
||||||
|
GetEntPropVector(modifyEntity, Prop_Send, "m_angRotation", hatData[client].orgAng);
|
||||||
|
hatData[client].collisionGroup = GetEntProp(modifyEntity, Prop_Send, "m_CollisionGroup");
|
||||||
|
hatData[client].solidType = GetEntProp(modifyEntity, Prop_Send, "m_nSolidType");
|
||||||
|
hatData[client].moveType = GetEntProp(modifyEntity, Prop_Send, "movetype");
|
||||||
|
|
||||||
|
|
||||||
|
if(!HasFlag(client, HAT_POCKET)) {
|
||||||
|
// TeleportEntity(entity, EMPTY_ANG, EMPTY_ANG, NULL_VECTOR);
|
||||||
|
if(HasFlag(client, HAT_REVERSED)) {
|
||||||
|
SetParent(client, entity);
|
||||||
|
if(StrEqual(classname, "infected")) {
|
||||||
|
SetParentAttachment(modifyEntity, "head", true);
|
||||||
|
TeleportEntity(modifyEntity, hatData[client].offset, hatData[client].angles, NULL_VECTOR);
|
||||||
|
SetParentAttachment(modifyEntity, "head", true);
|
||||||
|
} else {
|
||||||
|
SetParentAttachment(modifyEntity, attachPoint, true);
|
||||||
|
TeleportEntity(modifyEntity, hatData[client].offset, hatData[client].angles, NULL_VECTOR);
|
||||||
|
SetParentAttachment(modifyEntity, attachPoint, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(HasFlag(client, HAT_COMMANDABLE)) {
|
||||||
|
ChooseRandomPosition(hatData[client].offset);
|
||||||
|
L4D2_CommandABot(entity, client, BOT_CMD_MOVE, hatData[client].offset);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
SetParent(entity, client);
|
||||||
|
SetParentAttachment(modifyEntity, attachPoint, true);
|
||||||
|
TeleportEntity(modifyEntity, hatData[client].offset, hatData[client].angles, NULL_VECTOR);
|
||||||
|
SetParentAttachment(modifyEntity, attachPoint, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(visibleEntity > 0) {
|
||||||
|
SetParent(visibleEntity, client);
|
||||||
|
SetParentAttachment(visibleEntity, attachPoint, true);
|
||||||
|
hatData[client].offset[2] += 10.0;
|
||||||
|
TeleportEntity(visibleEntity, hatData[client].offset, hatData[client].angles, NULL_VECTOR);
|
||||||
|
SetParentAttachment(visibleEntity, attachPoint, true);
|
||||||
|
#if defined DEBUG_HAT_SHOW_FAKE
|
||||||
|
L4D2_SetEntityGlow(visibleEntity, L4D2Glow_Constant, 0, 0, color2, false);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined DEBUG_HAT_SHOW_FAKE
|
||||||
|
L4D2_SetEntityGlow(modifyEntity, L4D2Glow_Constant, 0, 0, color, false);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// SetEntProp(modifyEntity, Prop_Send, "m_nSolidType", 0);
|
||||||
|
SetEntProp(modifyEntity, Prop_Send, "m_CollisionGroup", 1);
|
||||||
|
SetEntProp(modifyEntity, Prop_Send, "movetype", MOVETYPE_NONE);
|
||||||
|
}
|
||||||
|
}
|
150
scripting/include/hats/props/base.sp
Normal file
150
scripting/include/hats/props/base.sp
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
char g_pendingSaveName[64];
|
||||||
|
int g_pendingSaveClient;
|
||||||
|
|
||||||
|
/* Wish to preface this file:
|
||||||
|
* It's kinda messy. The main structs are:
|
||||||
|
* - ItemData
|
||||||
|
* - CategoryData
|
||||||
|
|
||||||
|
The rest are kinda necessary, for sorting reasons (SearchData, RecentEntry).
|
||||||
|
|
||||||
|
*/
|
||||||
|
enum ChatPrompt {
|
||||||
|
Prompt_None,
|
||||||
|
Prompt_Search,
|
||||||
|
Prompt_Save
|
||||||
|
}
|
||||||
|
enum struct PlayerPropData {
|
||||||
|
ArrayList listBuffer;
|
||||||
|
bool clearListBuffer;
|
||||||
|
int lastCategoryIndex;
|
||||||
|
int lastItemIndex;
|
||||||
|
// When did the user last interact with prop spawner? (Shows hints after long inactivity)
|
||||||
|
int lastActiveTime;
|
||||||
|
char classnameOverride[32];
|
||||||
|
|
||||||
|
ChatPrompt chatPrompt;
|
||||||
|
ArrayList markedProps;
|
||||||
|
|
||||||
|
// Called on PlayerDisconnect
|
||||||
|
void Reset() {
|
||||||
|
if(this.listBuffer != null) delete this.listBuffer;
|
||||||
|
if(this.markedProps != null) delete this.markedProps;
|
||||||
|
this.chatPrompt = Prompt_None;
|
||||||
|
this.clearListBuffer = false;
|
||||||
|
this.lastCategoryIndex = 0;
|
||||||
|
this.lastItemIndex = 0;
|
||||||
|
this.lastShowedHint = 0;
|
||||||
|
this.classnameOverride[0] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sets the list buffer
|
||||||
|
void SetList(ArrayList list, bool cleanupAfterUse = false) {
|
||||||
|
this.listBuffer = list;
|
||||||
|
this.clearListBuffer = cleanupAfterUse;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is currently only called on item/category handler cancel (to clear search/recents buffer)
|
||||||
|
void CleanupBuffer() {
|
||||||
|
if(this.listBuffer != null && this.clearListBuffer) {
|
||||||
|
delete this.listBuffer;
|
||||||
|
this.clearListBuffer = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PlayerPropData g_PropData[MAXPLAYERS+1];
|
||||||
|
|
||||||
|
enum struct CategoryData {
|
||||||
|
// The display name of category
|
||||||
|
char name[64];
|
||||||
|
// If set, overwrites the classname it is spawned as
|
||||||
|
char classnameOverride[32];
|
||||||
|
bool hasItems; // true: items is ArrayList<ItemData>, false: items is ArrayList<CategoryData>
|
||||||
|
ArrayList items;
|
||||||
|
}
|
||||||
|
enum struct ItemData {
|
||||||
|
char model[128];
|
||||||
|
char name[64];
|
||||||
|
|
||||||
|
void FromSearchData(SearchData search) {
|
||||||
|
strcopy(this.model, sizeof(this.model), search.model);
|
||||||
|
strcopy(this.name, sizeof(this.name), search.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
enum struct SearchData {
|
||||||
|
char model[128];
|
||||||
|
char name[64];
|
||||||
|
int index;
|
||||||
|
|
||||||
|
void FromItemData(ItemData item) {
|
||||||
|
strcopy(this.model, sizeof(this.model), item.model);
|
||||||
|
strcopy(this.name, sizeof(this.name), item.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
enum struct SaveData {
|
||||||
|
char model[128];
|
||||||
|
buildType type;
|
||||||
|
float origin[3];
|
||||||
|
float angles[3];
|
||||||
|
int color[4];
|
||||||
|
|
||||||
|
void FromEntity(int entity) {
|
||||||
|
// Use this.model as a buffer:
|
||||||
|
GetEntityClassname(entity, this.model, sizeof(this.model));
|
||||||
|
this.type = Build_Solid;
|
||||||
|
if(StrEqual(this.model, "prop_physics")) this.type = Build_Physics;
|
||||||
|
else if(StrEqual(this.model, "prop_dynamic")) {
|
||||||
|
if(GetEntProp(entity, Prop_Send, "m_nSolidType") == 0) {
|
||||||
|
this.type = Build_NonSolid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GetEntPropString(entity, Prop_Data, "m_ModelName", this.model, sizeof(this.model));
|
||||||
|
GetEntPropVector(entity, Prop_Send, "m_vecOrigin", this.origin);
|
||||||
|
GetEntPropVector(entity, Prop_Send, "m_angRotation", this.angles);
|
||||||
|
GetEntityRenderColor(entity, this.color[0],this.color[1],this.color[2],this.color[3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Serialize(char[] output, int maxlen) {
|
||||||
|
Format(
|
||||||
|
output, maxlen, "%s,%d,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%d,%d,%d,%d",
|
||||||
|
this.model, this.type, this.origin[0], this.origin[1], this.origin[2],
|
||||||
|
this.angles[0], this.angles[1], this.angles[2],
|
||||||
|
this.color[0], this.color[1], this.color[2], this.color[3]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Deserialize(const char[] input) {
|
||||||
|
char buffer[16];
|
||||||
|
int index = SplitString(input, ",", this.model, sizeof(this.model));
|
||||||
|
index += SplitString(input[index], ",", buffer, sizeof(buffer));
|
||||||
|
this.type = view_as<buildType>(StringToInt(buffer));
|
||||||
|
for(int i = 0; i < 3; i++) {
|
||||||
|
index += SplitString(input[index], ",", buffer, sizeof(buffer));
|
||||||
|
this.origin[i] = StringToFloat(buffer);
|
||||||
|
}
|
||||||
|
for(int i = 0; i < 3; i++) {
|
||||||
|
index += SplitString(input[index], ",", buffer, sizeof(buffer));
|
||||||
|
this.angles[i] = StringToFloat(buffer);
|
||||||
|
}
|
||||||
|
for(int i = 0; i < 4; i++) {
|
||||||
|
index += SplitString(input[index], ",", buffer, sizeof(buffer));
|
||||||
|
this.color[i] = StringToInt(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum struct RecentEntry {
|
||||||
|
char name[64];
|
||||||
|
int count;
|
||||||
|
}
|
||||||
|
|
||||||
|
ArrayList g_categories; // ArrayList<CategoryData>
|
||||||
|
ArrayList g_spawnedItems; // ArrayList(block=2)<entRef, [creator]>
|
||||||
|
ArrayList g_savedItems; // ArrayList<entRef>
|
||||||
|
StringMap g_recentItems; // Key: model[128], value: RecentEntry
|
||||||
|
|
||||||
|
#include <hats/props/methods.sp>
|
||||||
|
#include <hats/props/cmd.sp>
|
||||||
|
#include <hats/props/menu_handlers.sp>
|
||||||
|
#include <hats/props/menu_methods.sp>
|
103
scripting/include/hats/props/cmd.sp
Normal file
103
scripting/include/hats/props/cmd.sp
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
Action Command_Props(int client, int args) {
|
||||||
|
char arg[32];
|
||||||
|
GetCmdArg(1, arg, sizeof(arg));
|
||||||
|
if(args == 0 || StrEqual(arg, "help")) {
|
||||||
|
PrintToChat(client, "See console for available sub-commands");
|
||||||
|
PrintToConsole(client, "help - this");
|
||||||
|
PrintToConsole(client, "list <classname/index/owner> - lists all props and their distances");
|
||||||
|
PrintToConsole(client, "search <search query>");
|
||||||
|
PrintToConsole(client, "edit <last/#index>");
|
||||||
|
PrintToConsole(client, "del <last/#index/tool>");
|
||||||
|
PrintToConsole(client, "add <cursor/tool>");
|
||||||
|
PrintToConsole(client, "controls - list all the controls");
|
||||||
|
} else if(StrEqual(arg, "list")) {
|
||||||
|
char arg2[16];
|
||||||
|
GetCmdArg(2, arg2, sizeof(arg2));
|
||||||
|
bool isClassname = StrEqual(arg2, "classname");
|
||||||
|
bool isIndex = StrEqual(arg2, "index");
|
||||||
|
bool isOwner = StrEqual(arg2, "owner");
|
||||||
|
if(args == 1 || isClassname || isIndex || isOwner) {
|
||||||
|
PrintToChat(client, "\x04[Editor]\x01 Please specify: \x05classname, index, owner. ");
|
||||||
|
return Plugin_Handled;
|
||||||
|
}
|
||||||
|
int userid = GetClientUserId(client);
|
||||||
|
float pos[3], propPos[3], dist;
|
||||||
|
GetAbsOrigin(client, pos);
|
||||||
|
for(int i = 0; i < g_spawnedItems.Length; i++) {
|
||||||
|
int ref = GetSpawnedItem(i);
|
||||||
|
if(ref > -1) {
|
||||||
|
GetEntPropVector(ref, Prop_Send, "m_vecOrigin", propPos);
|
||||||
|
dist = GetVectorDistance(pos, propPos, false);
|
||||||
|
if(isIndex) {
|
||||||
|
int entity = EntRefToEntIndex(ref);
|
||||||
|
PrintToConsole(client, "%d. ent #%d - %.0fu away", i, entity, dist);
|
||||||
|
} else if(isClassname) {
|
||||||
|
char classname[32];
|
||||||
|
GetEntityClassname(ref, classname, sizeof(classname));
|
||||||
|
PrintToConsole(client, "%d. %s - %.0fu away", i, classname, dist);
|
||||||
|
} else if(isOwner) {
|
||||||
|
int spawner = g_spawnedItems.Get(i, 1);
|
||||||
|
int player = GetClientOfUserId(spawner);
|
||||||
|
if(player > 0) {
|
||||||
|
PrintToConsole(client, "%d. %N - %.0fu away", i, player, dist);
|
||||||
|
} else {
|
||||||
|
PrintToConsole(client, "%d. (disconnected) - %.0fu away", i, dist);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PrintToChat(client, "\x04[Editor]\x01 Check console");
|
||||||
|
} else if(StrEqual(arg, "search")) {
|
||||||
|
if(args == 1) {
|
||||||
|
PrintToChat(client, "\x04[Editor]\x01 Enter your search query:");
|
||||||
|
g_PropData[client].chatPrompt = Prompt_Search;
|
||||||
|
} else {
|
||||||
|
char query[32];
|
||||||
|
GetCmdArg(2, query, sizeof(query));
|
||||||
|
DoSearch(client, query);
|
||||||
|
}
|
||||||
|
} else if(StrEqual(arg, "edit")) {
|
||||||
|
char arg2[32];
|
||||||
|
GetCmdArg(2, arg2, sizeof(arg2));
|
||||||
|
int index;
|
||||||
|
if(StrEqual(arg2, "last")) {
|
||||||
|
// Get last one
|
||||||
|
index = GetSpawnedIndex(client, -1);
|
||||||
|
} else {
|
||||||
|
index = StringToInt(arg2);
|
||||||
|
}
|
||||||
|
if(index >= 0 && index < g_spawnedItems.Length) {
|
||||||
|
int entity = EntRefToEntIndex(g_spawnedItems.Get(index));
|
||||||
|
Editor[client].Import(entity);
|
||||||
|
PrintToChat(client, "\x04[Editor]\x01 Editing entity \x05%d", entity);
|
||||||
|
} else {
|
||||||
|
PrintToChat(client, "\x04[Editor]\x01 Invalid index, out of bounds. Enter a value between [0, %d]", g_spawnedItems.Length - 1);
|
||||||
|
}
|
||||||
|
} else if(StrEqual(arg, "del")) {
|
||||||
|
char arg2[32];
|
||||||
|
GetCmdArg(2, arg2, sizeof(arg2));
|
||||||
|
int index;
|
||||||
|
if(StrEqual(arg2, "last")) {
|
||||||
|
// Get last one
|
||||||
|
index = GetSpawnedIndex(client, -1);
|
||||||
|
} else {
|
||||||
|
index = StringToInt(arg2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(index >= 0 && index < g_spawnedItems.Length) {
|
||||||
|
int entity = EntRefToEntIndex(g_spawnedItems.Get(index));
|
||||||
|
if(entity > 0 && IsValidEntity(entity)) {
|
||||||
|
RemoveEntity(entity);
|
||||||
|
}
|
||||||
|
g_spawnedItems.Erase(index);
|
||||||
|
PrintToChat(client, "\x04[Editor]\x01 Deleted entity \x05%d", entity);
|
||||||
|
} else {
|
||||||
|
PrintToChat(client, "\x04[Editor]\x01 Invalid index, out of bounds. Enter a value between [0, %d]", g_spawnedItems.Length - 1);
|
||||||
|
}
|
||||||
|
} else if(StrEqual("controls")) {
|
||||||
|
PrintToChat(client, "View controls at https://admin.jackz.me/docs/props");
|
||||||
|
} else {
|
||||||
|
PrintToChat(client, "\x05Not implemented");
|
||||||
|
}
|
||||||
|
return Plugin_Handled;
|
||||||
|
}
|
239
scripting/include/hats/props/menu_handlers.sp
Normal file
239
scripting/include/hats/props/menu_handlers.sp
Normal file
|
@ -0,0 +1,239 @@
|
||||||
|
TopMenuObject g_propSpawnerCategory;
|
||||||
|
public void OnAdminMenuReady(Handle topMenuHandle) {
|
||||||
|
TopMenu topMenu = TopMenu.FromHandle(topMenuHandle);
|
||||||
|
if(g_topMenu != topMenuHandle) {
|
||||||
|
g_propSpawnerCategory = topMenu.AddCategory("hats_editor", Category_Handler);
|
||||||
|
if(g_propSpawnerCategory != INVALID_TOPMENUOBJECT) {
|
||||||
|
topMenu.AddItem("editor_spawn", AdminMenu_Spawn, g_propSpawnerCategory, "sm_prop");
|
||||||
|
topMenu.AddItem("editor_edit", AdminMenu_Edit, g_propSpawnerCategory, "sm_prop");
|
||||||
|
topMenu.AddItem("editor_delete", AdminMenu_Delete, g_propSpawnerCategory, "sm_prop");
|
||||||
|
topMenu.AddItem("editor_saveload", AdminMenu_SaveLoad, g_propSpawnerCategory, "sm_prop");
|
||||||
|
}
|
||||||
|
g_topMenu = topMenu;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////
|
||||||
|
// HANDLERS
|
||||||
|
/////////////
|
||||||
|
void Category_Handler(TopMenu topmenu, TopMenuAction action, TopMenuObject topobj_id, int param, char[] buffer, int maxlength) {
|
||||||
|
if(action == TopMenuAction_DisplayTitle) {
|
||||||
|
Format(buffer, maxlength, "Select a task:");
|
||||||
|
} else if(action == TopMenuAction_DisplayOption) {
|
||||||
|
Format(buffer, maxlength, "Spawn Props (Beta)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AdminMenu_Spawn(TopMenu topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength) {
|
||||||
|
if(action == TopMenuAction_DisplayOption) {
|
||||||
|
Format(buffer, maxlength, "Spawn Props");
|
||||||
|
} else if(action == TopMenuAction_SelectOption) {
|
||||||
|
ConVar cheats = FindConVar("sm_cheats");
|
||||||
|
if(cheats != null && !cheats.BoolValue) {
|
||||||
|
CReplyToCommand(param, "\x04[Editor] \x01Set \x05sm_cheats\x01 to \x051\x01 to use the prop spawner");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ShowSpawnRoot(param);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int Spawn_RootHandler(Menu menu, MenuAction action, int client, int param2) {
|
||||||
|
if (action == MenuAction_Select) {
|
||||||
|
char info[2];
|
||||||
|
menu.GetItem(param2, info, sizeof(info));
|
||||||
|
switch(info[0]) {
|
||||||
|
case 'f': Spawn_ShowFavorites(client);
|
||||||
|
case 'r': Spawn_ShowRecents(client);
|
||||||
|
case 's': Spawn_ShowSearch(client);
|
||||||
|
case 'n': ShowCategoryList(client);
|
||||||
|
}
|
||||||
|
// TODO: handle back (to top menu)
|
||||||
|
} else if (action == MenuAction_Cancel) {
|
||||||
|
if(param2 == MenuCancel_ExitBack) {
|
||||||
|
DisplayTopMenuCategory(g_topMenu, g_propSpawnerCategory, client);
|
||||||
|
}
|
||||||
|
} else if (action == MenuAction_End)
|
||||||
|
delete menu;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
void AdminMenu_Edit(TopMenu topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength) {
|
||||||
|
if(action == TopMenuAction_DisplayOption) {
|
||||||
|
Format(buffer, maxlength, "Edit Props");
|
||||||
|
} else if(action == TopMenuAction_SelectOption) {
|
||||||
|
ShowEditList(param);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void AdminMenu_Delete(TopMenu topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength) {
|
||||||
|
if(action == TopMenuAction_DisplayOption) {
|
||||||
|
Format(buffer, maxlength, "Delete Props");
|
||||||
|
} else if(action == TopMenuAction_SelectOption) {
|
||||||
|
ShowDeleteList(param);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AdminMenu_SaveLoad(TopMenu topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength) {
|
||||||
|
if(action == TopMenuAction_DisplayOption) {
|
||||||
|
Format(buffer, maxlength, "Save / Load");
|
||||||
|
} else if(action == TopMenuAction_SelectOption) {
|
||||||
|
Menu menu = new Menu(SaveLoadHandler);
|
||||||
|
menu.SetTitle("Save / Load");
|
||||||
|
char name[64];
|
||||||
|
// TODO: possibly let you overwrite saves?
|
||||||
|
menu.AddItem("", "[New Save]");
|
||||||
|
ArrayList saves = LoadSaves();
|
||||||
|
if(saves != null) {
|
||||||
|
for(int i = 0; i < saves.Length; i++) {
|
||||||
|
saves.GetString(i, name, sizeof(name));
|
||||||
|
menu.AddItem(name, name);
|
||||||
|
}
|
||||||
|
delete saves;
|
||||||
|
}
|
||||||
|
menu.ExitBackButton = true;
|
||||||
|
menu.ExitButton = true;
|
||||||
|
menu.Display(param, MENU_TIME_FOREVER);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int SpawnCategoryHandler(Menu menu, MenuAction action, int client, int param2) {
|
||||||
|
if (action == MenuAction_Select) {
|
||||||
|
char info[8];
|
||||||
|
menu.GetItem(param2, info, sizeof(info));
|
||||||
|
int index = StringToInt(info);
|
||||||
|
// Use g_categories, but if this is nested, then when a nested is selected, we need to use that list
|
||||||
|
ShowItemMenu(client, index);
|
||||||
|
} else if (action == MenuAction_Cancel) {
|
||||||
|
if(param2 == MenuCancel_ExitBack) {
|
||||||
|
ShowSpawnRoot(client);
|
||||||
|
}
|
||||||
|
} else if (action == MenuAction_End)
|
||||||
|
delete menu;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int SaveLoadHandler(Menu menu, MenuAction action, int client, int param2) {
|
||||||
|
if (action == MenuAction_Select) {
|
||||||
|
char saveName[64];
|
||||||
|
menu.GetItem(param2, saveName, sizeof(saveName));
|
||||||
|
if(saveName[0] == '\0') {
|
||||||
|
// Save new
|
||||||
|
FormatTime(saveName, sizeof(saveName), "%Y-%m-%d_%H-%I-%M");
|
||||||
|
if(CreateSave(saveName)) {
|
||||||
|
PrintToChat(client, "\x04[Editor]\x01 Saved as \x05%s/%s.txt", g_currentMap, saveName);
|
||||||
|
} else {
|
||||||
|
PrintToChat(client, "\x04[Editor]\x01 Unable to save. Sorry.");
|
||||||
|
}
|
||||||
|
} else if(LoadSave(saveName, true)) {
|
||||||
|
strcopy(g_pendingSaveName, sizeof(g_pendingSaveName), saveName);
|
||||||
|
g_pendingSaveClient = client;
|
||||||
|
PrintToChat(client, "\x04[Editor]\x01 Previewing save \x05%s", saveName);
|
||||||
|
PrintToChat(client, "\x04[Editor]\x01 Press \x05Shift + Middle Mouse\x01 to spawn, \x05Middle Mouse\x01 to cancel");
|
||||||
|
} else {
|
||||||
|
PrintToChat(client, "\x04[Editor]\x01 Could not load save file.");
|
||||||
|
}
|
||||||
|
} else if (action == MenuAction_Cancel) {
|
||||||
|
if(param2 == MenuCancel_ExitBack) {
|
||||||
|
DisplayTopMenuCategory(g_topMenu, g_propSpawnerCategory, client);
|
||||||
|
}
|
||||||
|
} else if (action == MenuAction_End)
|
||||||
|
delete menu;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int DeleteHandler(Menu menu, MenuAction action, int client, int param2) {
|
||||||
|
if (action == MenuAction_Select) {
|
||||||
|
char info[8];
|
||||||
|
menu.GetItem(param2, info, sizeof(info));
|
||||||
|
int index = StringToInt(info);
|
||||||
|
if(index == -1) {
|
||||||
|
// Delete all (everyone)
|
||||||
|
int count = DeleteAll();
|
||||||
|
PrintToChat(client, "\x04[Editor]\x01 Deleted \x05%d\x01 items", count);
|
||||||
|
ShowDeleteList(client);
|
||||||
|
} else if(index == -2) {
|
||||||
|
// Delete all (mine only)
|
||||||
|
int count = DeleteAll(client);
|
||||||
|
PrintToChat(client, "\x04[Editor]\x01 Deleted \x05%d\x01 items", count);
|
||||||
|
ShowDeleteList(client);
|
||||||
|
} else if(index == -3) {
|
||||||
|
if(g_PropData[client].markedProps != null) {
|
||||||
|
EndDeleteTool(client, false);
|
||||||
|
} else {
|
||||||
|
g_PropData[client].markedProps = new ArrayList();
|
||||||
|
PrintToChat(client, "\x04[Editor]\x01 Delete tool active. Press \x05E (Interact)\x01 to mark props.");
|
||||||
|
}
|
||||||
|
ShowDeleteList(client);
|
||||||
|
} else {
|
||||||
|
int ref = g_spawnedItems.Get(index);
|
||||||
|
// TODO: add delete confirm
|
||||||
|
if(IsValidEntity(ref)) {
|
||||||
|
RemoveEntity(ref);
|
||||||
|
}
|
||||||
|
g_spawnedItems.Erase(index);
|
||||||
|
if(index > 0) {
|
||||||
|
index--;
|
||||||
|
}
|
||||||
|
ShowDeleteList(client, index);
|
||||||
|
}
|
||||||
|
} else if (action == MenuAction_Cancel) {
|
||||||
|
if(param2 == MenuCancel_ExitBack) {
|
||||||
|
DisplayTopMenuCategory(g_topMenu, g_propSpawnerCategory, client);
|
||||||
|
}
|
||||||
|
} else if (action == MenuAction_End)
|
||||||
|
delete menu;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int SpawnItemHandler(Menu menu, MenuAction action, int client, int param2) {
|
||||||
|
if (action == MenuAction_Select) {
|
||||||
|
char info[132];
|
||||||
|
menu.GetItem(param2, info, sizeof(info));
|
||||||
|
char index[4];
|
||||||
|
char model[128];
|
||||||
|
int nameIndex = SplitString(info, "|", index, sizeof(index));
|
||||||
|
nameIndex += SplitString(info[nameIndex], "|", model, sizeof(model));
|
||||||
|
g_PropData[client].lastItemIndex = StringToInt(index);
|
||||||
|
if(Editor[client].PreviewModel(model, g_PropData[client].classnameOverride)) {
|
||||||
|
Editor[client].SetData(info[nameIndex]);
|
||||||
|
PrintHintText(client, "%s\n%s", info[nameIndex], model);
|
||||||
|
ShowHint(client);
|
||||||
|
} else {
|
||||||
|
PrintToChat(client, "\x04[Editor]\x01 Error spawning preview \x01(%s)", model);
|
||||||
|
}
|
||||||
|
|
||||||
|
ShowItemMenuAny(client, null); // Use last menu
|
||||||
|
// ShowItemMenu(client, g_PropData[client].lastCategoryIndex);
|
||||||
|
} else if(action == MenuAction_Cancel) {
|
||||||
|
if(param2 == MenuCancel_ExitBack) {
|
||||||
|
ShowCategoryList(client, g_PropData[client].listBuffer);
|
||||||
|
}
|
||||||
|
g_PropData[client].CleanupBuffer();
|
||||||
|
|
||||||
|
} else if (action == MenuAction_End) {
|
||||||
|
delete menu;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int EditHandler(Menu menu, MenuAction action, int client, int param2) {
|
||||||
|
if (action == MenuAction_Select) {
|
||||||
|
char info[8];
|
||||||
|
menu.GetItem(param2, info, sizeof(info));
|
||||||
|
int index = StringToInt(info);
|
||||||
|
int ref = g_spawnedItems.Get(index);
|
||||||
|
int entity = EntRefToEntIndex(ref);
|
||||||
|
if(entity > 0) {
|
||||||
|
Editor[client].Import(entity, false);
|
||||||
|
PrintToChat(client, "\x04[Editor]\x01 Editing entity \x05%d", entity);
|
||||||
|
} else {
|
||||||
|
PrintToChat(client, "\x04[Editor]\x01 Entity disappeared.");
|
||||||
|
g_spawnedItems.Erase(index);
|
||||||
|
index--;
|
||||||
|
}
|
||||||
|
ShowEditList(client, index);
|
||||||
|
} else if (action == MenuAction_Cancel) {
|
||||||
|
if(param2 == MenuCancel_ExitBack) {
|
||||||
|
DisplayTopMenuCategory(g_topMenu, g_propSpawnerCategory, client);
|
||||||
|
}
|
||||||
|
} else if (action == MenuAction_End)
|
||||||
|
delete menu;
|
||||||
|
return 0;
|
||||||
|
}
|
174
scripting/include/hats/props/menu_methods.sp
Normal file
174
scripting/include/hats/props/menu_methods.sp
Normal file
|
@ -0,0 +1,174 @@
|
||||||
|
/////////////
|
||||||
|
// METHODS
|
||||||
|
/////////////
|
||||||
|
void ShowSpawnRoot(int client) {
|
||||||
|
Menu menu = new Menu(Spawn_RootHandler);
|
||||||
|
menu.SetTitle("Choose list:");
|
||||||
|
menu.AddItem("f", "Favorites (WIP)");
|
||||||
|
menu.AddItem("r", "Recents");
|
||||||
|
menu.AddItem("s", "Search");
|
||||||
|
menu.AddItem("n", "Prop List");
|
||||||
|
menu.ExitBackButton = true;
|
||||||
|
menu.ExitButton = true;
|
||||||
|
menu.Display(client, MENU_TIME_FOREVER);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spawn_ShowFavorites(int client) {
|
||||||
|
PrintToChat(client, "In development");
|
||||||
|
return;
|
||||||
|
// Menu menu = new Menu(SpawnItemHandler);
|
||||||
|
// char model[128];
|
||||||
|
// for(int i = 0; i <= g_spawnedItems.Length; i++) {
|
||||||
|
// int ref = g_spawnedItems.Get(i);
|
||||||
|
// if(IsValidEntity(ref)) {
|
||||||
|
// GetEntPropString(ref, Prop_Data, "m_ModelName", model, sizeof(model));
|
||||||
|
// menu.AddItem(model, model);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// menu.ExitBackButton = true;
|
||||||
|
// menu.ExitButton = true;
|
||||||
|
// menu.Display(client, MENU_TIME_FOREVER);
|
||||||
|
}
|
||||||
|
void Spawn_ShowRecents(int client) {
|
||||||
|
CReplyToCommand(client, "\x04[Editor] \x01Disabled due to crash issues :D");
|
||||||
|
return;
|
||||||
|
if(g_recentItems == null) LoadRecents();
|
||||||
|
ArrayList items = GetRecentsItemList();
|
||||||
|
if(items.Length == 0) {
|
||||||
|
CReplyToCommand(client, "\x04[Editor] \x01No recent props spawned.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ShowItemMenuAny(client, items, "Recents", true);
|
||||||
|
}
|
||||||
|
void Spawn_ShowSearch(int client) {
|
||||||
|
g_PropData[client].chatPrompt = Prompt_Search;
|
||||||
|
CReplyToCommand(client, "\x04[Editor] \x01Please enter search query in chat:");
|
||||||
|
}
|
||||||
|
void ShowDeleteList(int client, int index = -3) {
|
||||||
|
Menu menu = new Menu(DeleteHandler);
|
||||||
|
menu.SetTitle("Delete Props");
|
||||||
|
|
||||||
|
menu.AddItem("-1", "Delete All");
|
||||||
|
menu.AddItem("-2", "Delete All (Mine Only)");
|
||||||
|
menu.AddItem("-3", "Delete Tool");
|
||||||
|
// menu.AddItem("-4", "Delete Last Save");
|
||||||
|
char info[8];
|
||||||
|
char buffer[128];
|
||||||
|
for(int i = 0; i < g_spawnedItems.Length; i++) {
|
||||||
|
int ref = GetSpawnedItem(i);
|
||||||
|
if(ref == -1) continue;
|
||||||
|
IntToString(i, info, sizeof(info));
|
||||||
|
GetEntPropString(ref, Prop_Data, "m_ModelName", buffer, sizeof(buffer));
|
||||||
|
index = FindCharInString(buffer, '/', true);
|
||||||
|
if(index != -1)
|
||||||
|
menu.AddItem(info, buffer[index + 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
menu.ExitBackButton = true;
|
||||||
|
menu.ExitButton = true;
|
||||||
|
// Add +3 to the index for the 3 "Delete ..." buttons
|
||||||
|
// TODO: restore the delete index issue, use /7*7
|
||||||
|
menu.DisplayAt(client, 0, MENU_TIME_FOREVER);
|
||||||
|
}
|
||||||
|
void ShowEditList(int client, int index = 0) {
|
||||||
|
Menu menu = new Menu(EditHandler);
|
||||||
|
menu.SetTitle("Edit Prop");
|
||||||
|
|
||||||
|
char info[8];
|
||||||
|
char buffer[32];
|
||||||
|
for(int i = 0; i < g_spawnedItems.Length; i++) {
|
||||||
|
int ref = GetSpawnedItem(i);
|
||||||
|
if(ref == -1) continue;
|
||||||
|
IntToString(i, info, sizeof(info));
|
||||||
|
GetEntPropString(ref, Prop_Data, "m_ModelName", buffer, sizeof(buffer));
|
||||||
|
index = FindCharInString(buffer, '/', true);
|
||||||
|
if(index != -1)
|
||||||
|
menu.AddItem(info, buffer[index + 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
menu.ExitBackButton = true;
|
||||||
|
menu.ExitButton = true;
|
||||||
|
// Add +2 to the index for the two "Delete ..." buttons
|
||||||
|
menu.DisplayAt(client, index, MENU_TIME_FOREVER);
|
||||||
|
}
|
||||||
|
void ShowCategoryList(int client, ArrayList categoryList = null) {
|
||||||
|
LoadCategories();
|
||||||
|
Menu menu = new Menu(SpawnCategoryHandler);
|
||||||
|
menu.SetTitle("Choose a category");
|
||||||
|
CategoryData cat;
|
||||||
|
char info[4];
|
||||||
|
// No category list provided, use the global one.
|
||||||
|
PrintToServer("ShowCategoryList (root = %b)", categoryList == null);
|
||||||
|
if(categoryList == null) {
|
||||||
|
categoryList = g_categories;
|
||||||
|
}
|
||||||
|
g_PropData[client].SetList(categoryList, false);
|
||||||
|
for(int i = 0; i < categoryList.Length; i++) {
|
||||||
|
categoryList.GetArray(i, cat);
|
||||||
|
Format(info, sizeof(info), "%d", i);
|
||||||
|
// TODO: maybe add > folder indicator
|
||||||
|
menu.AddItem(info, cat.name);
|
||||||
|
}
|
||||||
|
menu.ExitBackButton = true;
|
||||||
|
menu.ExitButton = true;
|
||||||
|
// Round to page instead of index (int division)
|
||||||
|
int index = g_PropData[client].lastCategoryIndex / 7 * 7;
|
||||||
|
menu.DisplayAt(client, index, MENU_TIME_FOREVER);
|
||||||
|
}
|
||||||
|
void ShowItemMenuAny(int client, ArrayList items, const char[] title = "", bool clearArray = false, const char[] classnameOverride = "") {
|
||||||
|
if(items == null) {
|
||||||
|
items = g_PropData[client].listBuffer;
|
||||||
|
if(items == null) {
|
||||||
|
LogError("Items is null and listBuffer is null as well");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
g_PropData[client].SetList(items, clearArray);
|
||||||
|
g_PropData[client].lastItemIndex = 0;
|
||||||
|
strcopy(g_PropData[client].classnameOverride, 32, classnameOverride);
|
||||||
|
}
|
||||||
|
if(items.Length == 0) {
|
||||||
|
PrintToChat(client, "\x04[Editor]\x01 No items to show.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Menu itemMenu = new Menu(SpawnItemHandler);
|
||||||
|
if(title[0] != '\0')
|
||||||
|
itemMenu.SetTitle(title);
|
||||||
|
ItemData item;
|
||||||
|
char info[128+64+8];
|
||||||
|
for(int i = 0; i < items.Length; i++) {
|
||||||
|
items.GetArray(i, item);
|
||||||
|
// Sadly need to duplicate item.name.
|
||||||
|
Format(info, sizeof(info), "%d|%s|%s", i, item.model, item.name);
|
||||||
|
itemMenu.AddItem(info, item.name);
|
||||||
|
}
|
||||||
|
itemMenu.ExitBackButton = true;
|
||||||
|
itemMenu.ExitButton = true;
|
||||||
|
// We don't want to start at the index but the page of the index
|
||||||
|
int index = (g_PropData[client].lastItemIndex / 7) * 7;
|
||||||
|
itemMenu.DisplayAt(client, index, MENU_TIME_FOREVER);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calls ShowItemMenuAny with the correct category automatically
|
||||||
|
bool ShowItemMenu(int client, int index) {
|
||||||
|
if(g_PropData[client].lastCategoryIndex != index) {
|
||||||
|
g_PropData[client].lastCategoryIndex = index;
|
||||||
|
g_PropData[client].lastItemIndex = 0; //Reset
|
||||||
|
}
|
||||||
|
CategoryData category;
|
||||||
|
// Use the list in the buffer
|
||||||
|
g_PropData[client].listBuffer.GetArray(index, category);
|
||||||
|
if(category.items == null) {
|
||||||
|
LogError("Category %s has null items array (index=%d)", category.name, index);
|
||||||
|
} else if(category.hasItems) {
|
||||||
|
PrintToServer("Selected category has item entries, showing item menu");
|
||||||
|
ShowItemMenuAny(client, category.items, category.name, false, category.classnameOverride);
|
||||||
|
} else {
|
||||||
|
PrintToServer("Selected category has nested categories, showing");
|
||||||
|
// Has nested categories
|
||||||
|
// Reset the category index for nested
|
||||||
|
g_PropData[client].lastCategoryIndex = 0;
|
||||||
|
g_PropData[client].SetList(category.items);
|
||||||
|
ShowCategoryList(client, g_PropData[client].listBuffer);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
489
scripting/include/hats/props/methods.sp
Normal file
489
scripting/include/hats/props/methods.sp
Normal file
|
@ -0,0 +1,489 @@
|
||||||
|
|
||||||
|
ArrayList LoadSaves() {
|
||||||
|
char path[PLATFORM_MAX_PATH];
|
||||||
|
BuildPath(Path_SM, path, sizeof(path), "data/prop_spawner/saves/%s", g_currentMap);
|
||||||
|
FileType fileType;
|
||||||
|
DirectoryListing listing = OpenDirectory(path);
|
||||||
|
if(listing == null) return null;
|
||||||
|
char buffer[64];
|
||||||
|
ArrayList saves = new ArrayList(ByteCountToCells(64));
|
||||||
|
while(listing.GetNext(buffer, sizeof(buffer), fileType)) {
|
||||||
|
if(buffer[0] == '.') continue;
|
||||||
|
saves.PushString(buffer);
|
||||||
|
}
|
||||||
|
delete listing;
|
||||||
|
return saves;
|
||||||
|
}
|
||||||
|
|
||||||
|
ArrayList g_previewItems;
|
||||||
|
|
||||||
|
bool LoadSave(const char[] save, bool asPreview = false) {
|
||||||
|
char path[PLATFORM_MAX_PATH];
|
||||||
|
BuildPath(Path_SM, path, sizeof(path), "data/prop_spawner/saves/%s/%s", g_currentMap, save);
|
||||||
|
// ArrayList savedItems = new ArrayList(sizeof(SaveData));
|
||||||
|
File file = OpenFile(path, "r");
|
||||||
|
if(file == null) return false;
|
||||||
|
char buffer[256];
|
||||||
|
if(asPreview) {
|
||||||
|
// Kill any previous preview
|
||||||
|
if(g_previewItems != null) ClearSavePreview();
|
||||||
|
g_previewItems = new ArrayList();
|
||||||
|
}
|
||||||
|
SaveData data;
|
||||||
|
while(file.ReadLine(buffer, sizeof(buffer))) {
|
||||||
|
if(buffer[0] == '#') continue;
|
||||||
|
data.Deserialize(buffer);
|
||||||
|
int entity = -1;
|
||||||
|
if(data.type == Build_Physics)
|
||||||
|
entity = CreateEntityByName("prop_physics");
|
||||||
|
else
|
||||||
|
entity = CreateEntityByName("prop_dynamic");
|
||||||
|
if(entity == -1) continue;
|
||||||
|
PrecacheModel(data.model);
|
||||||
|
DispatchKeyValue(entity, "model", data.model);
|
||||||
|
DispatchKeyValue(entity, "targetname", "saved_prop");
|
||||||
|
if(asPreview) {
|
||||||
|
DispatchKeyValue(entity, "rendermode", "1");
|
||||||
|
DispatchKeyValue(entity, "solid", "0");
|
||||||
|
} else {
|
||||||
|
DispatchKeyValue(entity, "solid", data.type == Build_NonSolid ? "0" : "6");
|
||||||
|
}
|
||||||
|
TeleportEntity(entity, data.origin, data.angles, NULL_VECTOR);
|
||||||
|
if(!DispatchSpawn(entity)) continue;
|
||||||
|
int alpha = asPreview ? 200 : data.color[3];
|
||||||
|
SetEntityRenderColor(entity, data.color[0], data.color[1], data.color[2], alpha);
|
||||||
|
|
||||||
|
if(asPreview)
|
||||||
|
g_savedItems.Push(EntIndexToEntRef(entity));
|
||||||
|
else
|
||||||
|
AddSpawnedItem(entity);
|
||||||
|
}
|
||||||
|
delete file;
|
||||||
|
if(asPreview) {
|
||||||
|
delete g_previewItems;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClearSavePreview() {
|
||||||
|
if(g_previewItems != null) {
|
||||||
|
for(int i = 0; i < g_previewItems.Length; i++) {
|
||||||
|
int ref = g_previewItems.Get(i);
|
||||||
|
if(IsValidEntity(ref)) {
|
||||||
|
RemoveEntity(ref);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete g_previewItems;
|
||||||
|
}
|
||||||
|
g_pendingSaveClient = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddSpawnedItem(int entity, int client = 0) {
|
||||||
|
int index = g_spawnedItems.Push(EntIndexToEntRef(entity));
|
||||||
|
if(client == 0)
|
||||||
|
g_spawnedItems.Set(index, 0, 1);
|
||||||
|
else
|
||||||
|
g_spawnedItems.Set(index, GetClientUserId(client), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CreateSave(const char[] name) {
|
||||||
|
char path[PLATFORM_MAX_PATH];
|
||||||
|
BuildPath(Path_SM, path, sizeof(path), "data/prop_spawner/saves/%s", g_currentMap);
|
||||||
|
CreateDirectory(path, 509);
|
||||||
|
Format(path, sizeof(path), "%s/%s.txt", path, name);
|
||||||
|
File file = OpenFile(path, "w");
|
||||||
|
if(file == null) {
|
||||||
|
PrintToServer("[Editor] Could not save: %s", path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
char buffer[132];
|
||||||
|
SaveData data;
|
||||||
|
for(int i = 0; i < g_spawnedItems.Length; i++) {
|
||||||
|
int ref = g_spawnedItems.Get(i);
|
||||||
|
if(IsValidEntity(ref)) {
|
||||||
|
data.FromEntity(ref);
|
||||||
|
data.Serialize(buffer, sizeof(buffer));
|
||||||
|
file.WriteLine("%s", buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file.Flush();
|
||||||
|
delete file;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UnloadSave() {
|
||||||
|
if(g_savedItems != null) {
|
||||||
|
delete g_savedItems;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LoadCategories() {
|
||||||
|
if(g_categories != null) return;
|
||||||
|
g_categories = new ArrayList(sizeof(CategoryData));
|
||||||
|
char path[PLATFORM_MAX_PATH];
|
||||||
|
BuildPath(Path_SM, path, sizeof(path), "data/prop_spawner/models");
|
||||||
|
LoadFolder(g_categories, path);
|
||||||
|
g_categories.SortCustom(SortCategories);
|
||||||
|
}
|
||||||
|
int SortCategories(int index1, int index2, ArrayList array, Handle hndl) {
|
||||||
|
CategoryData cat1;
|
||||||
|
array.GetArray(index1, cat1);
|
||||||
|
CategoryData cat2;
|
||||||
|
array.GetArray(index2, cat2);
|
||||||
|
return strcmp(cat1.name, cat2.name);
|
||||||
|
}
|
||||||
|
public void UnloadCategories() {
|
||||||
|
if(g_categories == null) return;
|
||||||
|
_UnloadCategories(g_categories);
|
||||||
|
delete g_categories;
|
||||||
|
}
|
||||||
|
void _UnloadCategories(ArrayList list) {
|
||||||
|
CategoryData cat;
|
||||||
|
for(int i = 0; i < list.Length; i++) {
|
||||||
|
list.GetArray(i, cat);
|
||||||
|
_UnloadCategory(cat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void _UnloadCategory(CategoryData cat) {
|
||||||
|
// Is a sub-category:
|
||||||
|
if(!cat.hasItems) {
|
||||||
|
_UnloadCategories(cat.items);
|
||||||
|
}
|
||||||
|
delete cat.items;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoadFolder(ArrayList parent, const char[] rootPath) {
|
||||||
|
char buffer[PLATFORM_MAX_PATH];
|
||||||
|
FileType fileType;
|
||||||
|
DirectoryListing listing = OpenDirectory(rootPath);
|
||||||
|
if(listing == null) {
|
||||||
|
LogError("Cannot open \"%s\"", rootPath);
|
||||||
|
}
|
||||||
|
while(listing.GetNext(buffer, sizeof(buffer), fileType)) {
|
||||||
|
if(fileType == FileType_Directory) {
|
||||||
|
// TODO: support subcategory
|
||||||
|
if(buffer[0] == '.') continue;
|
||||||
|
CategoryData data;
|
||||||
|
Format(data.name, sizeof(data.name), "%s>>", buffer);
|
||||||
|
data.items = new ArrayList(sizeof(CategoryData));
|
||||||
|
|
||||||
|
Format(buffer, sizeof(buffer), "%s/%s", rootPath, buffer);
|
||||||
|
LoadFolder(data.items, buffer);
|
||||||
|
parent.PushArray(data);
|
||||||
|
} else if(fileType == FileType_File) {
|
||||||
|
Format(buffer, sizeof(buffer), "%s/%s", rootPath, buffer);
|
||||||
|
LoadProps(parent, buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete listing;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoadProps(ArrayList parent, const char[] filePath) {
|
||||||
|
File file = OpenFile(filePath, "r");
|
||||||
|
if(file == null) {
|
||||||
|
PrintToServer("[Props] Cannot open file \"%s\"", filePath);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
CategoryData category;
|
||||||
|
category.items = new ArrayList(sizeof(ItemData));
|
||||||
|
category.hasItems = true;
|
||||||
|
char buffer[128];
|
||||||
|
if(!file.ReadLine(buffer, sizeof(buffer))) {
|
||||||
|
delete file;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ReplaceString(buffer, sizeof(buffer), "\n", "");
|
||||||
|
ReplaceString(buffer, sizeof(buffer), "\r", "");
|
||||||
|
Format(category.name, sizeof(category.name), "%s>", buffer);
|
||||||
|
while(file.ReadLine(buffer, sizeof(buffer))) {
|
||||||
|
if(buffer[0] == '#') continue;
|
||||||
|
ReplaceString(buffer, sizeof(buffer), "\n", "");
|
||||||
|
ReplaceString(buffer, sizeof(buffer), "\r", "");
|
||||||
|
ItemData item;
|
||||||
|
int index = SplitString(buffer, ":", item.model, sizeof(item.model));
|
||||||
|
if(index == -1) {
|
||||||
|
index = SplitString(buffer, " ", item.model, sizeof(item.model));
|
||||||
|
if(index == -1) {
|
||||||
|
// No name provided, use the model's filename
|
||||||
|
index = FindCharInString(buffer, '/', true);
|
||||||
|
strcopy(item.name, sizeof(item.name), item.model[index + 1]);
|
||||||
|
} else {
|
||||||
|
strcopy(item.name, sizeof(item.name), buffer[index]);
|
||||||
|
}
|
||||||
|
category.items.PushArray(item);
|
||||||
|
} else if(StrEqual(item.model, "Classname")) {
|
||||||
|
strcopy(category.classnameOverride, sizeof(category.classnameOverride), buffer[index]);
|
||||||
|
} else if(StrEqual(item.model, "Type")) {
|
||||||
|
Format(category.classnameOverride, sizeof(category.classnameOverride), "_%s", buffer[index]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parent.PushArray(category);
|
||||||
|
delete file;
|
||||||
|
}
|
||||||
|
bool recentsChanged = false;
|
||||||
|
bool SaveRecents() {
|
||||||
|
if(!recentsChanged) return true; // Nothing to do, nothing changed
|
||||||
|
if(g_recentItems == null) return false;
|
||||||
|
char path[PLATFORM_MAX_PATH];
|
||||||
|
BuildPath(Path_SM, path, sizeof(path), "data/prop_spawner/recents_cache.csv");
|
||||||
|
File file = OpenFile(path, "w");
|
||||||
|
if(file == null) {
|
||||||
|
PrintToServer("[Editor] Could not write to %s", path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
StringMapSnapshot snapshot = g_recentItems.Snapshot();
|
||||||
|
char model[128];
|
||||||
|
RecentEntry entry;
|
||||||
|
for(int i = 0; i < snapshot.Length; i++) {
|
||||||
|
snapshot.GetKey(i, model, sizeof(model));
|
||||||
|
g_recentItems.GetArray(model, entry, sizeof(entry));
|
||||||
|
file.WriteLine("%s,%s,%d", model, entry.name, entry.count);
|
||||||
|
}
|
||||||
|
file.Flush();
|
||||||
|
delete file;
|
||||||
|
delete snapshot;
|
||||||
|
recentsChanged = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool LoadRecents() {
|
||||||
|
return false;
|
||||||
|
if(g_recentItems != null) delete g_recentItems;
|
||||||
|
char path[PLATFORM_MAX_PATH];
|
||||||
|
BuildPath(Path_SM, path, sizeof(path), "data/prop_spawner/recents_cache.csv");
|
||||||
|
File file = OpenFile(path, "r");
|
||||||
|
if(file == null) return false;
|
||||||
|
g_recentItems = new StringMap();
|
||||||
|
char buffer[128+64+16];
|
||||||
|
char model[128];
|
||||||
|
RecentEntry entry;
|
||||||
|
while(file.ReadLine(buffer, sizeof(buffer))) {
|
||||||
|
int index = SplitString(buffer, ",", model, sizeof(model));
|
||||||
|
index += SplitString(buffer[index], ",", entry.name, sizeof(entry.name));
|
||||||
|
entry.count = StringToInt(buffer[index]);
|
||||||
|
g_recentItems.SetArray(model, entry, sizeof(entry));
|
||||||
|
}
|
||||||
|
delete file;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns an ArrayList<ItemData> of all the recents
|
||||||
|
ArrayList GetRecentsItemList() {
|
||||||
|
ArrayList items = new ArrayList(sizeof(ItemData));
|
||||||
|
StringMapSnapshot snapshot = g_recentItems.Snapshot();
|
||||||
|
char model[128];
|
||||||
|
RecentEntry entry;
|
||||||
|
ItemData item;
|
||||||
|
for(int i = 0; i < snapshot.Length; i++) {
|
||||||
|
snapshot.GetKey(i, model, sizeof(model));
|
||||||
|
g_recentItems.GetArray(model, entry, sizeof(entry));
|
||||||
|
strcopy(item.model, sizeof(item.model), model);
|
||||||
|
strcopy(item.name, sizeof(item.name), entry.name);
|
||||||
|
}
|
||||||
|
// This is pretty expensive in terms of allocations but shrug
|
||||||
|
items.SortCustom(SortRecents);
|
||||||
|
delete snapshot;
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
int SortRecents(int index1, int index2, ArrayList array, Handle handle) {
|
||||||
|
ItemData data1;
|
||||||
|
array.GetArray(index1, data1);
|
||||||
|
ItemData data2;
|
||||||
|
array.GetArray(index2, data2);
|
||||||
|
|
||||||
|
int count1, count2;
|
||||||
|
RecentEntry entry;
|
||||||
|
if(g_recentItems.GetArray(data1.model, entry, sizeof(entry))) return 0; //skip if somehow no entry
|
||||||
|
count1 = entry.count;
|
||||||
|
if(g_recentItems.GetArray(data2.model, entry, sizeof(entry))) return 0; //skip if somehow no entry
|
||||||
|
count2 = entry.count;
|
||||||
|
return count2 - count1; // desc
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddRecent(const char[] model, const char[] name) {
|
||||||
|
if(g_recentItems == null) {
|
||||||
|
if(!LoadRecents()) return;
|
||||||
|
}
|
||||||
|
RecentEntry entry;
|
||||||
|
if(!g_recentItems.GetArray(model, entry, sizeof(entry))) {
|
||||||
|
entry.count = 0;
|
||||||
|
strcopy(entry.name, sizeof(entry.name), name);
|
||||||
|
}
|
||||||
|
entry.count++;
|
||||||
|
recentsChanged = true;
|
||||||
|
g_recentItems.SetArray(model, entry, sizeof(entry));
|
||||||
|
}
|
||||||
|
public Action OnClientSayCommand(int client, const char[] command, const char[] sArgs) {
|
||||||
|
if(g_PropData[client].chatPrompt == Prompt_None) {
|
||||||
|
return Plugin_Continue;
|
||||||
|
}
|
||||||
|
switch(g_PropData[client].chatPrompt) {
|
||||||
|
case Prompt_Search: DoSearch(client, sArgs);
|
||||||
|
case Prompt_Save: {
|
||||||
|
if(CreateSave(sArgs)) {
|
||||||
|
PrintToChat(client, "\x04[Editor]\x01 Saved as \x05%s/%s.txt", g_currentMap, sArgs);
|
||||||
|
} else {
|
||||||
|
PrintToChat(client, "\x04[Editor]\x01 Unable to save. Sorry.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g_PropData[client].chatPrompt = Prompt_None;
|
||||||
|
return Plugin_Handled;
|
||||||
|
}
|
||||||
|
void DoSearch(int client, const char[] query) {
|
||||||
|
ArrayList results = SearchItems(query);
|
||||||
|
if(results.Length == 0) {
|
||||||
|
CPrintToChat(client, "\x04[Editor]\x01 No results found. :(");
|
||||||
|
} else {
|
||||||
|
ShowItemMenuAny(client, results, "", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Gets the index of the spawned item, starting at index. negative to go from back
|
||||||
|
int GetSpawnedIndex(int client, int index) {
|
||||||
|
int userid = GetClientUserId(client);
|
||||||
|
if(index >= 0) {
|
||||||
|
for(int i = index; i < g_spawnedItems.Length; i++) {
|
||||||
|
int spawnedBy = g_spawnedItems.Get(i, 1);
|
||||||
|
if(spawnedBy == userid) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for(int i = g_spawnedItems.Length + index; i >= 0; i--) {
|
||||||
|
int spawnedBy = g_spawnedItems.Get(i, 1);
|
||||||
|
if(spawnedBy == userid) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
#define MAX_SEARCH_RESULTS 10
|
||||||
|
ArrayList SearchItems(const char[] query) {
|
||||||
|
// We have to put it into SearchData enum struct, then convert it back to ItemResult
|
||||||
|
LoadCategories();
|
||||||
|
ArrayList results = new ArrayList(sizeof(SearchData));
|
||||||
|
_searchCategory(results, g_categories, query);
|
||||||
|
results.SortCustom(SortSearch);
|
||||||
|
ArrayList items = new ArrayList(sizeof(ItemData));
|
||||||
|
ItemData item;
|
||||||
|
SearchData data;
|
||||||
|
for(int i = 0; i < results.Length; i++) {
|
||||||
|
results.GetArray(i, data);
|
||||||
|
PrintToConsoleAll("%d | data=\"%s\"", i, data.model);
|
||||||
|
item.FromSearchData(data);
|
||||||
|
PrintToConsoleAll("%d | item=\"%s\"", i, item.model);
|
||||||
|
items.PushArray(item);
|
||||||
|
}
|
||||||
|
delete results;
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
int SortSearch(int index1, int index2, ArrayList array, Handle handle) {
|
||||||
|
SearchData data1;
|
||||||
|
array.GetArray(index1, data1);
|
||||||
|
SearchData data2;
|
||||||
|
array.GetArray(index2, data2);
|
||||||
|
return data1.index - data2.index;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _searchCategory(ArrayList results, ArrayList categories, const char[] query) {
|
||||||
|
CategoryData cat;
|
||||||
|
if(categories == null) return;
|
||||||
|
for(int i = 0; i < categories.Length; i++) {
|
||||||
|
categories.GetArray(i, cat);
|
||||||
|
if(cat.hasItems) {
|
||||||
|
//cat.items is of CatetoryData
|
||||||
|
if(!_searchItems(results, cat.items, query)) return;
|
||||||
|
} else {
|
||||||
|
//cat.items is of ItemData
|
||||||
|
_searchCategory(results, cat.items, query);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bool _searchItems(ArrayList results, ArrayList items, const char[] query) {
|
||||||
|
ItemData item;
|
||||||
|
SearchData search;
|
||||||
|
for(int i = 0; i < items.Length; i++) {
|
||||||
|
items.GetArray(i, item);
|
||||||
|
int searchIndex = StrContains(item.name, query, false);
|
||||||
|
if(searchIndex > -1) {
|
||||||
|
search.FromItemData(item);
|
||||||
|
search.index = searchIndex;
|
||||||
|
results.PushArray(search);
|
||||||
|
if(results.Length > MAX_SEARCH_RESULTS) return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int GetSpawnedItem(int index) {
|
||||||
|
if(index < 0 || index >= g_spawnedItems.Length) return -1;
|
||||||
|
int ref = g_spawnedItems.Get(index);
|
||||||
|
if(!IsValidEntity(ref)) {
|
||||||
|
g_spawnedItems.Erase(index);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return ref;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RemoveSpawnedProp(int ref) {
|
||||||
|
// int ref = EntIndexToEntRef(entity);
|
||||||
|
int index = g_spawnedItems.FindValue(ref);
|
||||||
|
if(index > -1) {
|
||||||
|
g_spawnedItems.Erase(index);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EndDeleteTool(int client, bool deleteEntities = false) {
|
||||||
|
if(g_PropData[client].markedProps != null) {
|
||||||
|
int count;
|
||||||
|
for(int i = 0; i < g_PropData[client].markedProps.Length; i++) {
|
||||||
|
int ref = g_PropData[client].markedProps.Get(i);
|
||||||
|
if(IsValidEntity(ref)) {
|
||||||
|
count++;
|
||||||
|
if(deleteEntities) {
|
||||||
|
RemoveSpawnedProp(ref);
|
||||||
|
RemoveEntity(ref);
|
||||||
|
}
|
||||||
|
else L4D2_RemoveEntityGlow(EntRefToEntIndex(ref));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete g_PropData[client].markedProps;
|
||||||
|
if(deleteEntities)
|
||||||
|
PrintToChat(client, "\x04[Editor]\x01 \x05%d\x01 entities deleted", count);
|
||||||
|
else
|
||||||
|
PrintToChat(client, "\x04[Editor]\x01 Delete tool cancelled");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int DeleteAll(int onlyPlayer = 0) {
|
||||||
|
int userid = onlyPlayer > 0 ? GetClientUserId(onlyPlayer) : 0;
|
||||||
|
int count;
|
||||||
|
for(int i = 0; i < g_spawnedItems.Length; i++) {
|
||||||
|
int ref = g_spawnedItems.Get(i);
|
||||||
|
int spawnedBy = g_spawnedItems.Get(i, 1);
|
||||||
|
// Skip if wishing to only delete certain items:
|
||||||
|
if(onlyPlayer != 0 && spawnedBy != userid) continue;
|
||||||
|
if(IsValidEntity(ref)) {
|
||||||
|
RemoveEntity(ref);
|
||||||
|
}
|
||||||
|
g_spawnedItems.Erase(i);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define SHOW_HINT_MIN_DURATION 600 // 600 s (10min)
|
||||||
|
void ShowHint(int client) {
|
||||||
|
int time = GetTime();
|
||||||
|
int lastActive = g_PropData[client].lastActiveTime;
|
||||||
|
g_PropData[client].lastActiveTime = time;
|
||||||
|
if(time - lastActive < SHOW_HINT_MIN_DURATION) return;
|
||||||
|
PrintToChat(client, "\x05R: \x01Change Mode");
|
||||||
|
PrintToChat(client, "\x05Middle Click: \x01Cancel Placement \x05Shift + Middle Click: \x01Place \x05Ctrl + Middle Click: \x01Change Type");
|
||||||
|
PrintToChat(client, "\x05E: \x01Rotate (hold, use mouse) \x05Left Click: \x01Change Axis \x05Right Click: \x01Snap Angle");
|
||||||
|
PrintToChat(client, "More information & cheatsheat: \x05%s", "https://admin.jackz.me/docs/props");
|
||||||
|
}
|
1006
scripting/l4d2_hats.sp
Normal file
1006
scripting/l4d2_hats.sp
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue