mirror of
https://github.com/Jackzmc/sourcemod-plugins.git
synced 2025-05-05 19:53:20 +00:00
Hats
This commit is contained in:
parent
23cbb7aeac
commit
5c37d46bcc
10 changed files with 547 additions and 232 deletions
Binary file not shown.
|
@ -7,8 +7,8 @@ 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"
|
||||
"\x05OFF\x01",
|
||||
"\x05ON\x01"
|
||||
}
|
||||
char COLOR_INDEX[4] = "RGBA";
|
||||
|
||||
|
@ -48,13 +48,33 @@ enum CompleteType {
|
|||
Complete_EditSuccess
|
||||
}
|
||||
|
||||
enum StackerDirection {
|
||||
Stack_Off,
|
||||
Stack_Left,
|
||||
Stack_Right,
|
||||
Stack_Forward,
|
||||
Stack_Backward,
|
||||
Stack_Up,
|
||||
Stack_Down
|
||||
}
|
||||
|
||||
char STACK_DIRECTION_NAME[7][] = {
|
||||
"\x05OFF",
|
||||
"\x04Left",
|
||||
"\x04Right",
|
||||
"\x04Forward",
|
||||
"\x04Backward",
|
||||
"\x04Up",
|
||||
"\x04Down",
|
||||
}
|
||||
|
||||
ArrayList createdWalls;
|
||||
|
||||
enum struct EditorData {
|
||||
int client;
|
||||
char classname[32];
|
||||
char classname[64];
|
||||
char data[32];
|
||||
char name[32];
|
||||
|
||||
float origin[3];
|
||||
float angles[3];
|
||||
|
@ -73,6 +93,7 @@ enum struct EditorData {
|
|||
int entity;
|
||||
bool hasCollision; /// possibly merge into .flags
|
||||
bool hasCollisionRotate; //^
|
||||
StackerDirection stackerDirection;
|
||||
|
||||
editMode mode;
|
||||
buildType buildType;
|
||||
|
@ -83,15 +104,16 @@ enum struct EditorData {
|
|||
if(this.entity != INVALID_ENT_REFERENCE && this.flags & Edit_Preview && IsValidEntity(this.entity)) {
|
||||
RemoveEntity(this.entity);
|
||||
}
|
||||
this.stackerDirection = Stack_Off;
|
||||
this.entity = INVALID_ENT_REFERENCE;
|
||||
this.data[0] = '\0';
|
||||
this.name[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);
|
||||
|
@ -102,6 +124,7 @@ enum struct EditorData {
|
|||
this.snapAngle = 30;
|
||||
this.hasCollision = true;
|
||||
this.hasCollisionRotate = false;
|
||||
this.buildType = Build_Solid;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -152,6 +175,9 @@ enum struct EditorData {
|
|||
void SetData(const char[] data) {
|
||||
strcopy(this.data, sizeof(this.data), data);
|
||||
}
|
||||
void SetName(const char[] name) {
|
||||
strcopy(this.name, sizeof(this.name), name);
|
||||
}
|
||||
|
||||
void CycleMode() {
|
||||
// Remove frozen state when cycling
|
||||
|
@ -184,6 +210,16 @@ enum struct EditorData {
|
|||
PrintToChat(this.client, "\x04[Editor]\x01 Mode: \x05%s\x01 (Press \x04RELOAD\x01 to change)", MODE_NAME[this.mode]);
|
||||
}
|
||||
|
||||
void CycleStacker(float tick) {
|
||||
if(tick - cmdThrottle[this.client] <= 0.20) return;
|
||||
int newDirection = view_as<int>(this.stackerDirection) + 1;
|
||||
if(newDirection == view_as<int>(Stack_Down)) newDirection = 0;
|
||||
this.stackerDirection = view_as<StackerDirection>(newDirection);
|
||||
|
||||
PrintToChat(this.client, "\x04[Editor]\x01 Stacker: %s\x01", STACK_DIRECTION_NAME[this.stackerDirection]);
|
||||
cmdThrottle[this.client] = tick;
|
||||
}
|
||||
|
||||
void ToggleCollision(float tick) {
|
||||
if(tick - cmdThrottle[this.client] <= 0.25) return;
|
||||
this.hasCollision = !this.hasCollision
|
||||
|
@ -192,14 +228,14 @@ enum struct EditorData {
|
|||
}
|
||||
|
||||
void ToggleCollisionRotate(float tick) {
|
||||
if(tick - cmdThrottle[this.client] <= 0.25) return;
|
||||
if(tick - cmdThrottle[this.client] <= 0.20) 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(tick - cmdThrottle[this.client] <= 0.1) return;
|
||||
if(this.axis == 0) {
|
||||
this.axis = 1;
|
||||
PrintToChat(this.client, "\x04[Editor]\x01 Rotate Axis: \x05HEADING (Y)\x01");
|
||||
|
@ -214,7 +250,7 @@ enum struct EditorData {
|
|||
}
|
||||
|
||||
void CycleSnapAngle(float tick) {
|
||||
if(tick - cmdThrottle[this.client] <= 0.15) return;
|
||||
if(tick - cmdThrottle[this.client] <= 0.1) return;
|
||||
switch(this.snapAngle) {
|
||||
case 1: this.snapAngle = 15;
|
||||
case 15: this.snapAngle = 30;
|
||||
|
@ -352,15 +388,62 @@ enum struct EditorData {
|
|||
return true;
|
||||
}
|
||||
|
||||
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.1);
|
||||
|
||||
// Confusing when we resume into freelook, so reset
|
||||
if(this.mode == FREELOOK)
|
||||
this.SetMode(MOVE_ORIGIN);
|
||||
|
||||
// Add to spawn list and add to recent list
|
||||
AddSpawnedItem(entity, this.client);
|
||||
char model[128];
|
||||
GetEntPropString(entity, Prop_Data, "m_ModelName", model, sizeof(model));
|
||||
AddRecent(model, this.name);
|
||||
|
||||
// Get the new position for preview with regards to this.stackerDirection
|
||||
if(this.stackerDirection != Stack_Off) {
|
||||
float size[3];
|
||||
GetEntityDimensions(this.entity, size);
|
||||
float sign = 1.0;
|
||||
if(this.stackerDirection == Stack_Left || this.stackerDirection == Stack_Right) {
|
||||
if(this.stackerDirection == Stack_Left) sign = -1.0;
|
||||
GetSidePositionFromOrigin(this.origin, this.angles, sign * size[1] * 0.90, this.origin);
|
||||
} else if(this.stackerDirection == Stack_Forward || this.stackerDirection == Stack_Backward) {
|
||||
if(this.stackerDirection == Stack_Backward) sign = -1.0;
|
||||
GetHorizontalPositionFromOrigin(this.origin, this.angles, sign * size[0] * 0.90, this.origin);
|
||||
} else {
|
||||
if(this.stackerDirection == Stack_Down) sign = -1.0;
|
||||
this.origin[2] += (size[2] * sign);
|
||||
}
|
||||
}
|
||||
|
||||
// Don't kill preview until cancel
|
||||
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");
|
||||
if(entity == -1) return -1;
|
||||
if(StrEqual(this.classname, "weapon_melee_spawn")) {
|
||||
DispatchKeyValue(entity, "melee_weapon", this.data);
|
||||
}
|
||||
DispatchKeyValue(entity, "count", "1");
|
||||
DispatchKeyValue(entity, "spawnflags", "10");
|
||||
return entity;
|
||||
}
|
||||
|
||||
|
@ -384,34 +467,6 @@ enum struct EditorData {
|
|||
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;
|
||||
|
@ -430,6 +485,9 @@ enum struct EditorData {
|
|||
DispatchKeyValue(entity, "solid", "6");
|
||||
|
||||
DispatchSpawn(entity);
|
||||
if(StrEqual(this.classname, "prop_wall_breakable")) {
|
||||
DispatchKeyValue(entity, "classname", "prop_door_rotating");
|
||||
}
|
||||
TeleportEntity(entity, this.origin, this.angles, NULL_VECTOR);
|
||||
this.entity = entity;
|
||||
this.flags |= Edit_Copy;
|
||||
|
@ -442,26 +500,26 @@ enum struct EditorData {
|
|||
this.flags |= Edit_WallCreator;
|
||||
}
|
||||
|
||||
bool PreviewWeapon(const char[] classname) {
|
||||
bool PreviewWeapon(const char[] classname, const char[] data) {
|
||||
int entity;
|
||||
// Melee weapons don't have weapon_ prefix
|
||||
this.Reset();
|
||||
// TODO: prevent use of preview _spawn
|
||||
if(StrContains(classname, "weapon_") == -1) {
|
||||
// Rotate on it's side:
|
||||
this.angles[2] = 90.0;
|
||||
if(StrEqual(classname, "weapon_melee_spawn")) {
|
||||
// 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, "melee_weapon", data);
|
||||
this.SetData(data);
|
||||
strcopy(this.classname, sizeof(this.classname), classname);
|
||||
} else {
|
||||
entity = CreateEntityByName(data);
|
||||
if(entity == -1) return false;
|
||||
strcopy(this.classname, sizeof(this.classname), data);
|
||||
}
|
||||
DispatchKeyValue(entity, "count", "1");
|
||||
DispatchKeyValue(entity, "spawnflags", "10");
|
||||
DispatchKeyValue(entity, "targetname", "prop_preview");
|
||||
DispatchKeyValue(entity, "rendercolor", "255 128 255");
|
||||
DispatchKeyValue(entity, "renderamt", "200");
|
||||
|
@ -481,9 +539,11 @@ enum struct EditorData {
|
|||
bool PreviewModel(const char[] model, const char[] classname = "") {
|
||||
// Check for an invalid model
|
||||
// this.origin is not cleared by this.Reset();
|
||||
this.Reset();
|
||||
GetClientAbsOrigin(this.client, this.origin);
|
||||
if(StrEqual(classname, "_weapon") || StrEqual(classname, "_melee")) {
|
||||
return this.PreviewWeapon(model);
|
||||
if(StrEqual(classname, "_weapon") || StrEqual(classname, "weapon_melee_spawn")) {
|
||||
// Pass in melee ID as data:
|
||||
return this.PreviewWeapon(classname, model);
|
||||
}
|
||||
if(PrecacheModel(model) == 0) {
|
||||
PrintToServer("Invalid model: %s", model);
|
||||
|
@ -549,7 +609,6 @@ enum struct EditorData {
|
|||
CPrintToChat(this.client, "\x04[Editor]\x01 Cancelled.");
|
||||
}
|
||||
}
|
||||
|
||||
EditorData Editor[MAXPLAYERS+1];
|
||||
|
||||
Action OnWallClicked(int entity, int activator, int caller, UseType type, float value) {
|
||||
|
@ -751,7 +810,7 @@ Action Command_Editor(int client, int args) {
|
|||
int index = GetLookingEntity(client, Filter_ValidHats); //GetClientAimTarget(client, false);
|
||||
if(index > 0) {
|
||||
Editor[client].Import(index, false, MOVE_ORIGIN);
|
||||
char classname[32];
|
||||
char classname[64];
|
||||
char targetname[32];
|
||||
GetEntityClassname(index, classname, sizeof(classname));
|
||||
GetEntPropString(index, Prop_Data, "m_target", targetname, sizeof(targetname));
|
||||
|
|
|
@ -27,15 +27,6 @@ enum struct HatInstance {
|
|||
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,
|
||||
|
@ -48,8 +39,7 @@ enum hatFeatures {
|
|||
HatConfig_DeleteThrownHats = 128
|
||||
}
|
||||
char ActivePreset[MAXPLAYERS+1][32];
|
||||
int lastHatrequestTime[MAXPLAYERS+1];
|
||||
PlayerHatData g_hatData[MAXPLAYERS+1];
|
||||
int lastHatRequestTime[MAXPLAYERS+1];
|
||||
HatInstance hatData[MAXPLAYERS+1];
|
||||
StringMap g_HatPresets;
|
||||
|
||||
|
@ -84,25 +74,13 @@ static char REVERSE_CLASSNAMES[MAX_REVERSE_CLASSNAMES][] = {
|
|||
"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");
|
||||
|
||||
Action Command_DoAHat(int client, int args) {
|
||||
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));
|
||||
|
@ -391,7 +369,7 @@ Action Command_DoAHat(int client, int args) {
|
|||
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) {
|
||||
if(IsFakeClient(entity) && L4D_GetIdlePlayerOfBot(entity) > 0) {
|
||||
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)) {
|
||||
|
@ -407,7 +385,7 @@ Action Command_DoAHat(int client, int args) {
|
|||
} 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) {
|
||||
} else if(EntRefToEntIndex(hatData[entity].entity) == entity || 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)) {
|
||||
|
@ -539,6 +517,9 @@ int HatConsentHandler(Menu menu, MenuAction action, int target, int param2) {
|
|||
ReplyToCommand(target, "Player has disconnected");
|
||||
return 0;
|
||||
} else if(hatAction == 1) {
|
||||
if(EntRefToEntIndex(hatData[target].entity) == activator )
|
||||
PrintToChat(activator, "[Hats] Woah you can't be making a black hole, jesus be careful.");
|
||||
else
|
||||
EquipHat(activator, target, "player", 0);
|
||||
} else {
|
||||
ClientCommand(activator, "play player/orch_hit_csharp_short.wav");
|
||||
|
|
|
@ -15,40 +15,84 @@ enum ChatPrompt {
|
|||
Prompt_Save
|
||||
}
|
||||
enum struct PlayerPropData {
|
||||
ArrayList listBuffer;
|
||||
ArrayList categoryStack;
|
||||
ArrayList itemBuffer;
|
||||
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];
|
||||
char classnameOverride[64];
|
||||
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.lastActiveTime = 0;
|
||||
this.classnameOverride[0] = '\0';
|
||||
this.CleanupBuffers();
|
||||
}
|
||||
|
||||
// Sets the list buffer
|
||||
void SetList(ArrayList list, bool cleanupAfterUse = false) {
|
||||
this.listBuffer = list;
|
||||
void SetItemBuffer(ArrayList list, bool cleanupAfterUse = false) {
|
||||
// Cleanup previous buffer if exist
|
||||
this.itemBuffer = list;
|
||||
this.clearListBuffer = cleanupAfterUse;
|
||||
}
|
||||
void ClearItemBuffer() {
|
||||
if(this.itemBuffer != null && this.clearListBuffer) {
|
||||
PrintToServer("ClearItemBuffer(): arraylist deleted.");
|
||||
delete this.itemBuffer;
|
||||
}
|
||||
this.clearListBuffer = false;
|
||||
}
|
||||
|
||||
void PushCategory(CategoryData category) {
|
||||
if(this.categoryStack == null) this.categoryStack = new ArrayList(sizeof(CategoryData));
|
||||
this.categoryStack.PushArray(category);
|
||||
}
|
||||
|
||||
bool PopCategory(CategoryData data) {
|
||||
if(this.categoryStack == null || this.categoryStack.Length == 0) return false;
|
||||
int index = this.categoryStack.Length - 1;
|
||||
this.categoryStack.GetArray(index, data);
|
||||
this.categoryStack.Erase(index);
|
||||
return true;
|
||||
}
|
||||
bool PeekCategory(CategoryData data) {
|
||||
if(this.categoryStack == null || this.categoryStack.Length == 0) return false;
|
||||
int index = this.categoryStack.Length - 1;
|
||||
this.categoryStack.GetArray(index, data);
|
||||
return true;
|
||||
}
|
||||
|
||||
void GetCategoryTitle(char[] title, int maxlen) {
|
||||
CategoryData cat;
|
||||
for(int i = 0; i < this.categoryStack.Length; i++) {
|
||||
this.categoryStack.GetArray(i, cat);
|
||||
if(i == 0)
|
||||
Format(title, maxlen, "%s", cat.name);
|
||||
else
|
||||
Format(title, maxlen, "%s>[%s]", title, cat.name);
|
||||
}
|
||||
}
|
||||
|
||||
bool HasCategories() {
|
||||
return this.categoryStack != null && this.categoryStack.Length > 0;
|
||||
}
|
||||
|
||||
// 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;
|
||||
void CleanupBuffers() {
|
||||
this.ClearItemBuffer();
|
||||
if(this.categoryStack != null) {
|
||||
delete this.categoryStack;
|
||||
}
|
||||
this.clearListBuffer = false;
|
||||
}
|
||||
}
|
||||
PlayerPropData g_PropData[MAXPLAYERS+1];
|
||||
|
@ -57,7 +101,7 @@ enum struct CategoryData {
|
|||
// The display name of category
|
||||
char name[64];
|
||||
// If set, overwrites the classname it is spawned as
|
||||
char classnameOverride[32];
|
||||
char classnameOverride[64];
|
||||
bool hasItems; // true: items is ArrayList<ItemData>, false: items is ArrayList<CategoryData>
|
||||
ArrayList items;
|
||||
}
|
||||
|
@ -137,12 +181,12 @@ enum struct RecentEntry {
|
|||
char name[64];
|
||||
int count;
|
||||
}
|
||||
|
||||
ArrayList g_categories; // ArrayList<CategoryData>
|
||||
CategoryData ROOT_CATEGORY;
|
||||
ArrayList g_spawnedItems; // ArrayList(block=2)<entRef, [creator]>
|
||||
ArrayList g_savedItems; // ArrayList<entRef>
|
||||
StringMap g_recentItems; // Key: model[128], value: RecentEntry
|
||||
|
||||
#include <hats/props/db.sp>
|
||||
#include <hats/props/methods.sp>
|
||||
#include <hats/props/cmd.sp>
|
||||
#include <hats/props/menu_handlers.sp>
|
||||
|
|
|
@ -9,7 +9,9 @@ Action Command_Props(int client, int args) {
|
|||
PrintToConsole(client, "edit <last/#index>");
|
||||
PrintToConsole(client, "del <last/#index/tool>");
|
||||
PrintToConsole(client, "add <cursor/tool>");
|
||||
PrintToConsole(client, "favorite - favorites active editor entity");
|
||||
PrintToConsole(client, "controls - list all the controls");
|
||||
PrintToConsole(client, "reload - reload prop list");
|
||||
} else if(StrEqual(arg, "list")) {
|
||||
char arg2[16];
|
||||
GetCmdArg(2, arg2, sizeof(arg2));
|
||||
|
@ -20,7 +22,6 @@ Action Command_Props(int client, int args) {
|
|||
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++) {
|
||||
|
@ -94,8 +95,22 @@ Action Command_Props(int client, int args) {
|
|||
} 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")) {
|
||||
} else if(StrEqual(arg, "controls")) {
|
||||
PrintToChat(client, "View controls at https://admin.jackz.me/docs/props");
|
||||
} else if(StrEqual(arg, "favorite")) {
|
||||
if(g_db == null) {
|
||||
PrintToChat(client, "\x04[Editor]\x01 Cannot connect to database.");
|
||||
} else if(Editor[client].IsActive()) {
|
||||
char model[128];
|
||||
GetEntPropString(Editor[client].entity, Prop_Data, "m_ModelName", model, sizeof(model));
|
||||
ToggleFavorite(client, model, Editor[client].data);
|
||||
} else {
|
||||
PrintToChat(client, "\x04[Editor]\x01 Edit a prop to use this command.");
|
||||
}
|
||||
} else if(StrEqual(arg, "reload")) {
|
||||
PrintHintText(client, "Reloading categories...");
|
||||
UnloadCategories();
|
||||
LoadCategories();
|
||||
} else {
|
||||
PrintToChat(client, "\x05Not implemented");
|
||||
}
|
||||
|
|
125
scripting/include/hats/props/db.sp
Normal file
125
scripting/include/hats/props/db.sp
Normal file
|
@ -0,0 +1,125 @@
|
|||
#define DATABASE_CONFIG_NAME "hats_editor"
|
||||
Database g_db;
|
||||
|
||||
bool ConnectDB() {
|
||||
char error[255];
|
||||
Database db = SQL_Connect(DATABASE_CONFIG_NAME, true, error, sizeof(error));
|
||||
if (db == null) {
|
||||
LogError("Database error %s", error);
|
||||
return false;
|
||||
} else {
|
||||
PrintToServer("l4d2_hats: Connected to database %s", DATABASE_CONFIG_NAME);
|
||||
db.SetCharset("utf8mb4");
|
||||
g_db = db;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void DB_GetFavoritesCallback(Database db, DBResultSet results, const char[] error, int userid) {
|
||||
if(results == null) {
|
||||
PrintToServer("l4d2_hats: DB_GetFavoritesCallback returned error: \"%s\"", error);
|
||||
}
|
||||
int client = GetClientOfUserId(userid);
|
||||
if(client > 0) {
|
||||
if(results == null) {
|
||||
PrintToChat(client, "\x04[Editor]\x01 Error occurred fetching favorites");
|
||||
return;
|
||||
}
|
||||
ArrayList list = new ArrayList(sizeof(ItemData));
|
||||
ItemData item;
|
||||
while(results.FetchRow()) {
|
||||
results.FetchString(0, item.model, sizeof(item.model));
|
||||
DBResult result;
|
||||
results.FetchString(1, item.name, sizeof(item.name), result);
|
||||
if(result == DBVal_Null) {
|
||||
// No name set - use the end part of the model
|
||||
int index = FindCharInString(item.model, '/', true);
|
||||
strcopy(item.name, sizeof(item.name), item.model[index + 1]);
|
||||
}
|
||||
}
|
||||
ShowTempItemMenu(client, list, "Favorites");
|
||||
}
|
||||
}
|
||||
|
||||
void DB_ToggleFavoriteCallback(Database db, DBResultSet results, const char[] error, DataPack pack) {
|
||||
if(results == null) {
|
||||
PrintToServer("l4d2_hats: DB_GetFavoriteCallback returned error: \"%s\"", error);
|
||||
}
|
||||
pack.Reset();
|
||||
int userid = pack.ReadCell();
|
||||
int client = GetClientOfUserId(userid);
|
||||
if(client > 0) {
|
||||
if(results == null) {
|
||||
PrintToChat(client, "\x04[Editor]\x01 Error occurred fetching favorite data");
|
||||
delete pack;
|
||||
return;
|
||||
}
|
||||
char query[256];
|
||||
char model[128];
|
||||
char steamid[32];
|
||||
GetClientAuthId(client, AuthId_Steam2, steamid, sizeof(steamid));
|
||||
pack.ReadString(model, sizeof(model));
|
||||
if(results.FetchRow()) {
|
||||
// Model was favorited, erase it
|
||||
g_db.Format(query, sizeof(query), "DELETE FROM editor_favorites WHERE steamid = '%s' AND model = '%s'", steamid, model);
|
||||
g_db.Query(DB_DeleteFavoriteCallback, query, userid);
|
||||
} else {
|
||||
// Model is not favorited, save it.
|
||||
char name[64];
|
||||
pack.ReadString(name, sizeof(name));
|
||||
// TODO: calculate next position automatically
|
||||
int position = 0;
|
||||
g_db.Format(query, sizeof(query),
|
||||
"INSERT INTO editor_favorites (steamid, model, name, position) VALUES ('%s', '%s', '%s', %d)",
|
||||
steamid, model, name, position
|
||||
);
|
||||
g_db.Query(DB_InsertFavoriteCallback, query, pack);
|
||||
}
|
||||
} else {
|
||||
// Only delete if we lost client - otherwise we will reuse it
|
||||
delete pack;
|
||||
}
|
||||
}
|
||||
|
||||
void DB_DeleteFavoriteCallback(Database db, DBResultSet results, const char[] error, DataPack pack) {
|
||||
if(results == null) {
|
||||
PrintToServer("l4d2_hats: DB_DeleteFavoriteCallback returned error: \"%s\"", error);
|
||||
}
|
||||
pack.Reset();
|
||||
char model[128];
|
||||
char name[64];
|
||||
int client = GetClientOfUserId(pack.ReadCell());
|
||||
if(client > 0) {
|
||||
if(results == null) {
|
||||
PrintToChat(client, "\x04[Editor]\x01 Could not delete favorite");
|
||||
delete pack;
|
||||
return;
|
||||
}
|
||||
pack.ReadString(model, sizeof(model));
|
||||
pack.ReadString(name, sizeof(name));
|
||||
int index = FindCharInString(model, '/', true);
|
||||
PrintToChat(client, "\x04[Editor]\x01 Removed favorite: \"%s\" \x05(%s)", model[index], name);
|
||||
}
|
||||
delete pack;
|
||||
}
|
||||
void DB_InsertFavoriteCallback(Database db, DBResultSet results, const char[] error, DataPack pack) {
|
||||
if(results == null) {
|
||||
PrintToServer("l4d2_hats: DB_InsertFavoriteCallback returned error: \"%s\"", error);
|
||||
}
|
||||
pack.Reset();
|
||||
char model[128];
|
||||
char name[64];
|
||||
int client = GetClientOfUserId(pack.ReadCell());
|
||||
if(client > 0) {
|
||||
if(results == null) {
|
||||
PrintToChat(client, "\x04[Editor]\x01 Could not add favorite");
|
||||
delete pack;
|
||||
return;
|
||||
}
|
||||
pack.ReadString(model, sizeof(model));
|
||||
pack.ReadString(name, sizeof(name));
|
||||
int index = FindCharInString(model, '/', true);
|
||||
PrintToChat(client, "\x04[Editor]\x01 Added favorite: \"%s\" \x05(%s)", model[index], name);
|
||||
}
|
||||
delete pack;
|
||||
}
|
|
@ -45,7 +45,7 @@ int Spawn_RootHandler(Menu menu, MenuAction action, int client, int param2) {
|
|||
case 'f': Spawn_ShowFavorites(client);
|
||||
case 'r': Spawn_ShowRecents(client);
|
||||
case 's': Spawn_ShowSearch(client);
|
||||
case 'n': ShowCategoryList(client);
|
||||
case 'n': ShowCategoryList(client, ROOT_CATEGORY);
|
||||
}
|
||||
// TODO: handle back (to top menu)
|
||||
} else if (action == MenuAction_Cancel) {
|
||||
|
@ -94,22 +94,6 @@ void AdminMenu_SaveLoad(TopMenu topmenu, TopMenuAction action, TopMenuObject obj
|
|||
}
|
||||
}
|
||||
|
||||
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];
|
||||
|
@ -124,9 +108,13 @@ int SaveLoadHandler(Menu menu, MenuAction action, int client, int param2) {
|
|||
}
|
||||
} else if(LoadSave(saveName, true)) {
|
||||
strcopy(g_pendingSaveName, sizeof(g_pendingSaveName), saveName);
|
||||
if(g_pendingSaveClient != 0 && g_pendingSaveClient != client) {
|
||||
PrintToChat(client, "\x04[Editor]\x01 Another user is currently loading a save.");
|
||||
} else {
|
||||
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.");
|
||||
}
|
||||
|
@ -158,7 +146,7 @@ int DeleteHandler(Menu menu, MenuAction action, int client, int param2) {
|
|||
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.");
|
||||
PrintToChat(client, "\x04[Editor]\x01 Delete tool active. Press \x05Left Mouse\x01 to mark props, \x05Right Mouse\x01 to undo. SHIFT+USE to spawn, CTRL+USE to cancel");
|
||||
}
|
||||
ShowDeleteList(client);
|
||||
} else {
|
||||
|
@ -182,6 +170,47 @@ int DeleteHandler(Menu menu, MenuAction action, int client, int param2) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
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);
|
||||
// Reset item index when selecting new category
|
||||
if(g_PropData[client].lastCategoryIndex != index) {
|
||||
g_PropData[client].lastCategoryIndex = index;
|
||||
g_PropData[client].lastItemIndex = 0;
|
||||
}
|
||||
CategoryData category;
|
||||
g_PropData[client].PeekCategory(category); // Just need to get the category.items[index], don't want to pop
|
||||
category.items.GetArray(index, category);
|
||||
if(category.items == null) {
|
||||
LogError("Category %s has null items array (index=%d)", category.name, index);
|
||||
} else if(category.hasItems) {
|
||||
ShowCategoryItemMenu(client, category);
|
||||
} else {
|
||||
// Reset the category index for nested
|
||||
g_PropData[client].lastCategoryIndex = 0;
|
||||
// Make the list now be the selected category's list.
|
||||
ShowCategoryList(client, category);
|
||||
}
|
||||
} else if (action == MenuAction_Cancel) {
|
||||
if(param2 == MenuCancel_ExitBack) {
|
||||
CategoryData category;
|
||||
// Double pop
|
||||
if(g_PropData[client].PopCategory(category) && g_PropData[client].PopCategory(category)) {
|
||||
// Use the last category (go back one)
|
||||
ShowCategoryList(client, category);
|
||||
} else {
|
||||
ShowSpawnRoot(client);
|
||||
}
|
||||
} else {
|
||||
g_PropData[client].CleanupBuffers();
|
||||
}
|
||||
} 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];
|
||||
|
@ -192,21 +221,28 @@ int SpawnItemHandler(Menu menu, MenuAction action, int client, int param2) {
|
|||
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]);
|
||||
Editor[client].SetName(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);
|
||||
// Use same item menu again:
|
||||
ShowItemMenu(client);
|
||||
} else if(action == MenuAction_Cancel) {
|
||||
g_PropData[client].ClearItemBuffer();
|
||||
if(param2 == MenuCancel_ExitBack) {
|
||||
ShowCategoryList(client, g_PropData[client].listBuffer);
|
||||
CategoryData category;
|
||||
if(g_PropData[client].PopCategory(category)) {
|
||||
// Use the last category (go back one)
|
||||
ShowCategoryList(client, category);
|
||||
} else {
|
||||
// If there is no categories, it means we are in a temp menu (search / recents / favorites)
|
||||
ShowSpawnRoot(client);
|
||||
}
|
||||
} else {
|
||||
g_PropData[client].CleanupBuffers();
|
||||
}
|
||||
g_PropData[client].CleanupBuffer();
|
||||
|
||||
} else if (action == MenuAction_End) {
|
||||
delete menu;
|
||||
}
|
||||
|
|
|
@ -12,33 +12,14 @@ void ShowSpawnRoot(int client) {
|
|||
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);
|
||||
ShowTempItemMenu(client, items, "Recents");
|
||||
}
|
||||
void Spawn_ShowSearch(int client) {
|
||||
g_PropData[client].chatPrompt = Prompt_Search;
|
||||
|
@ -91,23 +72,25 @@ void ShowEditList(int client, int index = 0) {
|
|||
// Add +2 to the index for the two "Delete ..." buttons
|
||||
menu.DisplayAt(client, index, MENU_TIME_FOREVER);
|
||||
}
|
||||
void ShowCategoryList(int client, ArrayList categoryList = null) {
|
||||
void ShowCategoryList(int client, CategoryData category) {
|
||||
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);
|
||||
g_PropData[client].PushCategory(category);
|
||||
Menu menu = new Menu(SpawnCategoryHandler);
|
||||
char title[32];
|
||||
g_PropData[client].GetCategoryTitle(title, sizeof(title));
|
||||
menu.SetTitle(title);
|
||||
CategoryData cat;
|
||||
for(int i = 0; i < category.items.Length; i++) {
|
||||
category.items.GetArray(i, cat);
|
||||
Format(info, sizeof(info), "%d", i);
|
||||
// TODO: maybe add > folder indicator
|
||||
if(cat.hasItems)
|
||||
menu.AddItem(info, cat.name);
|
||||
else {
|
||||
Format(title, sizeof(title), "[%s]", cat.name);
|
||||
menu.AddItem(info, title);
|
||||
}
|
||||
}
|
||||
menu.ExitBackButton = true;
|
||||
menu.ExitButton = true;
|
||||
|
@ -115,14 +98,17 @@ void ShowCategoryList(int client, ArrayList categoryList = null) {
|
|||
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 = "") {
|
||||
void _showItemMenu(int client, ArrayList items, const char[] title = "", bool clearArray = false, const char[] classnameOverride = "") {
|
||||
if(items == null) {
|
||||
items = g_PropData[client].listBuffer;
|
||||
// Use previous list buffer
|
||||
items = g_PropData[client].itemBuffer;
|
||||
if(items == null) {
|
||||
LogError("Items is null and listBuffer is null as well");
|
||||
LogError("Previous list does not exist and no new list was provided ShowItemMenu(%N)", client);
|
||||
}
|
||||
} else {
|
||||
g_PropData[client].SetList(items, clearArray);
|
||||
// Populate the buffer with this list
|
||||
g_PropData[client].SetItemBuffer(items, clearArray);
|
||||
// Reset the index, so we start on the first item
|
||||
g_PropData[client].lastItemIndex = 0;
|
||||
strcopy(g_PropData[client].classnameOverride, 32, classnameOverride);
|
||||
}
|
||||
|
@ -134,10 +120,10 @@ void ShowItemMenuAny(int client, ArrayList items, const char[] title = "", bool
|
|||
if(title[0] != '\0')
|
||||
itemMenu.SetTitle(title);
|
||||
ItemData item;
|
||||
char info[128+64+8];
|
||||
char info[8+128+64]; //i[8] + item.model[128] + item.name[64]
|
||||
for(int i = 0; i < items.Length; i++) {
|
||||
items.GetArray(i, item);
|
||||
// Sadly need to duplicate item.name.
|
||||
// Sadly need to duplicate item.name, for recents to work
|
||||
Format(info, sizeof(info), "%d|%s|%s", i, item.model, item.name);
|
||||
itemMenu.AddItem(info, item.name);
|
||||
}
|
||||
|
@ -147,28 +133,52 @@ void ShowItemMenuAny(int client, ArrayList items, const char[] title = "", bool
|
|||
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;
|
||||
/**
|
||||
* Show a list of a category's items to spawn to the client
|
||||
*
|
||||
* @param client client to show menu to
|
||||
* @param category the category to show items of
|
||||
*/
|
||||
void ShowCategoryItemMenu(int client, CategoryData category) {
|
||||
char title[32];
|
||||
g_PropData[client].GetCategoryTitle(title, sizeof(title));
|
||||
Format(title, sizeof(title), "%s>%s", title, category.name);
|
||||
_showItemMenu(client, category.items, title, false, category.classnameOverride);
|
||||
}
|
||||
/**
|
||||
* Show a list of items to spawn to the client
|
||||
*
|
||||
* @param client client to show menu to
|
||||
* @param items A list of ItemData. Optional, null to reuse last list
|
||||
* @param title An optional title to show
|
||||
* @param clearArray Should the items array be destroyed when menu is closed?
|
||||
* @param classnameOverride Override the classname to spawn as
|
||||
*/
|
||||
void ShowItemMenu(int client, ArrayList items = null, const char[] title = "", const char[] classnameOverride = "") {
|
||||
_showItemMenu(client, items, title, false, classnameOverride);
|
||||
}
|
||||
/**
|
||||
* Show a list of items, deleting the arraylist on completion
|
||||
* @param client client to show menu to
|
||||
* @param items A list of ItemData
|
||||
* @param title An optional title to show
|
||||
* @param classnameOverride Override the classname to spawn as
|
||||
*/
|
||||
void ShowTempItemMenu(int client, ArrayList items, const char[] title = "", const char[] classnameOverride = "") {
|
||||
if(items == null) {
|
||||
LogError("ShowTempItemMenu: Given null item list");
|
||||
}
|
||||
_showItemMenu(client, items, title, true, classnameOverride);
|
||||
}
|
||||
|
||||
void Spawn_ShowFavorites(int client) {
|
||||
if(g_db == null) {
|
||||
PrintToChat(client, "\x04[Editor]\x01 Cannot connect to database.");
|
||||
return;
|
||||
}
|
||||
PrintCenterText(client, "Loading favorites...\nPlease wait");
|
||||
char query[256];
|
||||
GetClientAuthId(client, AuthId_Steam2, query, sizeof(query));
|
||||
g_db.Format(query, sizeof(query), "SELECT model, name FROM editor_favorites WHERE steamid = '%s' ORDER BY position DESC", query);
|
||||
g_db.Query(DB_GetFavoritesCallback, query, GetClientUserId(client));
|
||||
}
|
|
@ -37,8 +37,11 @@ bool LoadSave(const char[] save, bool asPreview = false) {
|
|||
if(data.type == Build_Physics)
|
||||
entity = CreateEntityByName("prop_physics");
|
||||
else
|
||||
entity = CreateEntityByName("prop_dynamic");
|
||||
if(entity == -1) continue;
|
||||
entity = CreateEntityByName("prop_dynamic_override");
|
||||
if(entity == -1) {
|
||||
PrintToServer("[Editor] LoadSave(\"%s\", %b): failed to create %s", save, asPreview, buffer);
|
||||
continue;
|
||||
}
|
||||
PrecacheModel(data.model);
|
||||
DispatchKeyValue(entity, "model", data.model);
|
||||
DispatchKeyValue(entity, "targetname", "saved_prop");
|
||||
|
@ -49,19 +52,19 @@ bool LoadSave(const char[] save, bool asPreview = false) {
|
|||
DispatchKeyValue(entity, "solid", data.type == Build_NonSolid ? "0" : "6");
|
||||
}
|
||||
TeleportEntity(entity, data.origin, data.angles, NULL_VECTOR);
|
||||
if(!DispatchSpawn(entity)) continue;
|
||||
if(!DispatchSpawn(entity)) {
|
||||
PrintToServer("[Editor] LoadSave(\"%s\", %b): failed to spawn %s", save, asPreview, buffer);
|
||||
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));
|
||||
g_previewItems.Push(EntIndexToEntRef(entity));
|
||||
else
|
||||
AddSpawnedItem(entity);
|
||||
}
|
||||
delete file;
|
||||
if(asPreview) {
|
||||
delete g_previewItems;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -118,12 +121,12 @@ void UnloadSave() {
|
|||
}
|
||||
|
||||
public void LoadCategories() {
|
||||
if(g_categories != null) return;
|
||||
g_categories = new ArrayList(sizeof(CategoryData));
|
||||
if(ROOT_CATEGORY.items != null) return;
|
||||
ROOT_CATEGORY.items = 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);
|
||||
LoadFolder(ROOT_CATEGORY.items, path);
|
||||
ROOT_CATEGORY.items.SortCustom(SortCategories);
|
||||
}
|
||||
int SortCategories(int index1, int index2, ArrayList array, Handle hndl) {
|
||||
CategoryData cat1;
|
||||
|
@ -133,9 +136,9 @@ int SortCategories(int index1, int index2, ArrayList array, Handle hndl) {
|
|||
return strcmp(cat1.name, cat2.name);
|
||||
}
|
||||
public void UnloadCategories() {
|
||||
if(g_categories == null) return;
|
||||
_UnloadCategories(g_categories);
|
||||
delete g_categories;
|
||||
if(ROOT_CATEGORY.items == null) return;
|
||||
_UnloadCategories(ROOT_CATEGORY.items);
|
||||
delete ROOT_CATEGORY.items;
|
||||
}
|
||||
void _UnloadCategories(ArrayList list) {
|
||||
CategoryData cat;
|
||||
|
@ -164,7 +167,7 @@ void LoadFolder(ArrayList parent, const char[] rootPath) {
|
|||
// TODO: support subcategory
|
||||
if(buffer[0] == '.') continue;
|
||||
CategoryData data;
|
||||
Format(data.name, sizeof(data.name), "%s>>", buffer);
|
||||
Format(data.name, sizeof(data.name), "%s", buffer);
|
||||
data.items = new ArrayList(sizeof(CategoryData));
|
||||
|
||||
Format(buffer, sizeof(buffer), "%s/%s", rootPath, buffer);
|
||||
|
@ -194,7 +197,7 @@ void LoadProps(ArrayList parent, const char[] filePath) {
|
|||
}
|
||||
ReplaceString(buffer, sizeof(buffer), "\n", "");
|
||||
ReplaceString(buffer, sizeof(buffer), "\r", "");
|
||||
Format(category.name, sizeof(category.name), "%s>", buffer);
|
||||
Format(category.name, sizeof(category.name), "%s", buffer);
|
||||
while(file.ReadLine(buffer, sizeof(buffer))) {
|
||||
if(buffer[0] == '#') continue;
|
||||
ReplaceString(buffer, sizeof(buffer), "\n", "");
|
||||
|
@ -246,7 +249,6 @@ bool SaveRecents() {
|
|||
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");
|
||||
|
@ -335,7 +337,9 @@ void DoSearch(int client, const char[] query) {
|
|||
if(results.Length == 0) {
|
||||
CPrintToChat(client, "\x04[Editor]\x01 No results found. :(");
|
||||
} else {
|
||||
ShowItemMenuAny(client, results, "", true);
|
||||
char title[64];
|
||||
Format(title, sizeof(title), "Results for \"%s\"", query);
|
||||
ShowTempItemMenu(client, results, title);
|
||||
}
|
||||
}
|
||||
// Gets the index of the spawned item, starting at index. negative to go from back
|
||||
|
@ -363,7 +367,7 @@ 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);
|
||||
_searchCategory(results, ROOT_CATEGORY.items, query);
|
||||
results.SortCustom(SortSearch);
|
||||
ArrayList items = new ArrayList(sizeof(ItemData));
|
||||
ItemData item;
|
||||
|
@ -404,6 +408,7 @@ void _searchCategory(ArrayList results, ArrayList categories, const char[] query
|
|||
bool _searchItems(ArrayList results, ArrayList items, const char[] query) {
|
||||
ItemData item;
|
||||
SearchData search;
|
||||
if(items == null) return false;
|
||||
for(int i = 0; i < items.Length; i++) {
|
||||
items.GetArray(i, item);
|
||||
int searchIndex = StrContains(item.name, query, false);
|
||||
|
@ -470,6 +475,7 @@ int DeleteAll(int onlyPlayer = 0) {
|
|||
if(IsValidEntity(ref)) {
|
||||
RemoveEntity(ref);
|
||||
}
|
||||
// TODO: erasing while removing
|
||||
g_spawnedItems.Erase(i);
|
||||
count++;
|
||||
}
|
||||
|
@ -485,5 +491,17 @@ void ShowHint(int client) {
|
|||
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, "Type \x05/prop favorite\x01 to (un)favorite.");
|
||||
PrintToChat(client, "More information & cheatsheat: \x05%s", "https://admin.jackz.me/docs/props");
|
||||
}
|
||||
|
||||
void ToggleFavorite(int client, const char[] model, const char[] name = "") {
|
||||
char query[256];
|
||||
GetClientAuthId(client, AuthId_Steam2, query, sizeof(query));
|
||||
DataPack pack;
|
||||
pack.WriteCell(GetClientUserId(client));
|
||||
pack.WriteString(model);
|
||||
pack.WriteString(name);
|
||||
g_db.Format(query, sizeof(query), "SELECT name FROM editor_favorites WHERE steamid = '%s' AND model = '%s'", query, model);
|
||||
g_db.Query(DB_ToggleFavoriteCallback, query, pack);
|
||||
}
|
|
@ -48,7 +48,7 @@ char g_currentMap[64];
|
|||
#include <hats/props/base.sp>
|
||||
|
||||
public Plugin myinfo = {
|
||||
name = "L4D2 Hats",
|
||||
name = "L4D2 Hats & Editor",
|
||||
author = "jackzmc",
|
||||
description = "",
|
||||
version = PLUGIN_VERSION,
|
||||
|
@ -66,6 +66,7 @@ public void OnPluginStart() {
|
|||
|
||||
createdWalls = new ArrayList();
|
||||
g_spawnedItems = new ArrayList(2);
|
||||
ROOT_CATEGORY.name = "Categories";
|
||||
|
||||
LoadTranslations("common.phrases");
|
||||
HookEvent("player_entered_checkpoint", OnEnterSaferoom);
|
||||
|
@ -89,6 +90,13 @@ public void OnPluginStart() {
|
|||
cvar_sm_hats_rainbow_speed = CreateConVar("sm_hats_rainbow_speed", "1", "Speed of rainbow", FCVAR_NONE, true, 0.0);
|
||||
cvar_sm_hats_max_distance = CreateConVar("sm_hats_distance", "240", "The max distance away you can hat something. 0 = disable", FCVAR_NONE, true, 0.0);
|
||||
|
||||
if(SQL_CheckConfig(DATABASE_CONFIG_NAME)) {
|
||||
if(!ConnectDB()) {
|
||||
LogError("Failed to connect to database.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
noHatVictimCookie = new Cookie("hats_no_target", "Disables other players from making you their hat", CookieAccess_Public);
|
||||
noHatVictimCookie.SetPrefabMenu(CookieMenu_OnOff_Int, "Disable player hats for self", OnLocalPlayerHatCookieSelect);
|
||||
|
||||
|
@ -158,7 +166,7 @@ Action Timer_PlaceHat(Handle h, int userid) {
|
|||
if(client > 0 && HasHat(client)) {
|
||||
GetClientAbsOrigin(client, hatData[client].orgPos);
|
||||
GetClientEyeAngles(client, hatData[client].orgAng);
|
||||
GetHorizontalPositionFromOrigin(hatData[client].orgPos, hatData[client].orgAng, 40.0, hatData[client].orgPos);
|
||||
// GetHorizontalPositionFromOrigin(hatData[client].orgPos, hatData[client].orgAng, 40.0, hatData[client].orgPos);
|
||||
hatData[client].orgAng[0] = 0.0;
|
||||
PrintToChat(client, "[Hats] Hat has been placed down");
|
||||
ClearHat(client, true);
|
||||
|
@ -467,6 +475,7 @@ public Action OnPlayerRunCmd(int client, int& buttons, int& impulse, float vel[3
|
|||
//////////////////////////////
|
||||
// OnPlayerRunCmd :: HATS
|
||||
/////////////////////////////
|
||||
int oldButtons = GetEntProp(client, Prop_Data, "m_nOldButtons");
|
||||
if(IsHatsEnabled(client)) {
|
||||
int entity = GetHat(client);
|
||||
int visibleEntity = EntRefToEntIndex(hatData[client].visibleEntity);
|
||||
|
@ -569,6 +578,7 @@ public Action OnPlayerRunCmd(int client, int& buttons, int& impulse, float vel[3
|
|||
if(buttons & IN_ZOOM) {
|
||||
ClearSavePreview();
|
||||
if(buttons & IN_SPEED) {
|
||||
PrintToChat(client, "\x04[Editor]\x01 Loaded save \x05%s", g_pendingSaveName);
|
||||
LoadSave(g_pendingSaveName, false);
|
||||
}
|
||||
}
|
||||
|
@ -612,7 +622,6 @@ public Action OnPlayerRunCmd(int client, int& buttons, int& impulse, float vel[3
|
|||
bool allowMove = true;
|
||||
switch(Editor[client].mode) {
|
||||
case MOVE_ORIGIN: {
|
||||
|
||||
SetWeaponDelay(client, 0.5);
|
||||
|
||||
bool isRotate;
|
||||
|
@ -621,22 +630,27 @@ public Action OnPlayerRunCmd(int client, int& buttons, int& impulse, float vel[3
|
|||
if(!g_inRotate[client]) {
|
||||
g_inRotate[client] = true;
|
||||
}
|
||||
if(buttons & IN_SPEED) {
|
||||
if(buttons & IN_JUMP) {
|
||||
buttons = buttons & ~IN_JUMP;
|
||||
Editor[client].CycleStacker(tick);
|
||||
} if(buttons & IN_SPEED) {
|
||||
Editor[client].ToggleCollision(tick);
|
||||
return Plugin_Handled;
|
||||
} else if(buttons & IN_DUCK) {
|
||||
Editor[client].ToggleCollisionRotate(tick);
|
||||
return Plugin_Handled;
|
||||
} else {
|
||||
// PrintCenterText(client, "%.1f %.1f %.1f", Editor[client].angles[0], Editor[client].angles[1], Editor[client].angles[2]);
|
||||
PrintCenterText(client, "%.1f %.1f %.1f", Editor[client].angles[0], Editor[client].angles[1], Editor[client].angles[2]);
|
||||
isRotate = true;
|
||||
SetEntityFlags(client, flags |= FL_FROZEN);
|
||||
if(buttons & IN_ATTACK) Editor[client].CycleAxis(tick);
|
||||
else if(buttons & IN_ATTACK2) Editor[client].CycleSnapAngle(tick);
|
||||
|
||||
// Rotation control:
|
||||
if(tick - cmdThrottle[client] > 0.20) {
|
||||
// Turn off rotate when player wants rotate
|
||||
Editor[client].hasCollisionRotate = false;
|
||||
if(Editor[client].axis == 3) {
|
||||
if(tick - cmdThrottle[client] > 0.1) {
|
||||
if(Editor[client].axis == 2) {
|
||||
if(mouse[1] > 10) Editor[client].angles[2] += Editor[client].snapAngle;
|
||||
else if(mouse[1] < -10) Editor[client].angles[2] -= Editor[client].snapAngle;
|
||||
} else {
|
||||
|
@ -652,8 +666,9 @@ public Action OnPlayerRunCmd(int client, int& buttons, int& impulse, float vel[3
|
|||
g_inRotate[client] = false;
|
||||
}
|
||||
// Move position
|
||||
if(buttons & IN_ATTACK) Editor[client].moveDistance++;
|
||||
else if(buttons & IN_ATTACK2) Editor[client].moveDistance--;
|
||||
float moveAmount = (buttons & IN_SPEED) ? 2.0 : 1.0;
|
||||
if(buttons & IN_ATTACK) Editor[client].moveDistance += moveAmount;
|
||||
else if(buttons & IN_ATTACK2) Editor[client].moveDistance -= moveAmount;
|
||||
}
|
||||
|
||||
// Clear IN_FROZEN when no longer rotate
|
||||
|
@ -661,12 +676,12 @@ public Action OnPlayerRunCmd(int client, int& buttons, int& impulse, float vel[3
|
|||
flags = flags & ~FL_FROZEN;
|
||||
SetEntityFlags(client, flags);
|
||||
}
|
||||
if(Editor[client].stackerDirection == Stack_Off)
|
||||
CalculateEditorPosition(client, Filter_IgnorePlayerAndWall);
|
||||
}
|
||||
case SCALE: {
|
||||
SetWeaponDelay(client, 0.5);
|
||||
allowMove = false;
|
||||
bool sizeChanged = false;
|
||||
if(buttons & IN_USE) {
|
||||
Editor[client].CycleSpeed(tick);
|
||||
} else {
|
||||
|
@ -702,8 +717,7 @@ public Action OnPlayerRunCmd(int client, int& buttons, int& impulse, float vel[3
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(tick - cmdThrottle[client] >= 0.25) {
|
||||
if(tick - cmdThrottle[client] > 0.13) {
|
||||
if(buttons & IN_RELOAD)
|
||||
Editor[client].CycleMode(); // R: Cycle forward
|
||||
else if(buttons & IN_ZOOM) {
|
||||
|
@ -722,7 +736,6 @@ public Action OnPlayerRunCmd(int client, int& buttons, int& impulse, float vel[3
|
|||
}
|
||||
|
||||
Editor[client].Draw(BUILDER_COLOR, 0.1, 0.1);
|
||||
|
||||
return allowMove ? Plugin_Continue : Plugin_Handled;
|
||||
}
|
||||
|
||||
|
@ -765,22 +778,37 @@ public Action OnTakeDamageAlive(int victim, int& attacker, int& inflictor, float
|
|||
return Plugin_Continue;
|
||||
}
|
||||
|
||||
public void Event_PlayerSpawn(Event event, const char[] name, bool dontBroadcast) {
|
||||
void Event_PlayerSpawn(Event event, const char[] name, bool dontBroadcast) {
|
||||
int client = GetClientOfUserId(event.GetInt("userid"));
|
||||
if(client > 0 && !HasHat(client) && !IsFakeClient(client)) {
|
||||
if(client > 0) {
|
||||
if(!HasHat(client) && !IsFakeClient(client)) {
|
||||
hatPresetCookie.Get(client, ActivePreset[client], 32);
|
||||
if(ActivePreset[client][0] != '\0') {
|
||||
RestoreActivePreset(client);
|
||||
ReplyToCommand(client, "[Hats] Applied your hat preset! Clear it with /hatp");
|
||||
}
|
||||
}
|
||||
SDKHook(client, SDKHook_WeaponCanUse, OnWeaponUse);
|
||||
}
|
||||
}
|
||||
|
||||
Action OnWeaponUse(int client, int weapon) {
|
||||
int ref = EntIndexToEntRef(weapon);
|
||||
// Prevent picking up weapons that are previews
|
||||
for(int i = 1; i <= MaxClients; i++) {
|
||||
if(Editor[i].entity == ref && Editor[i].flags & Edit_Preview) {
|
||||
return Plugin_Handled;
|
||||
}
|
||||
}
|
||||
return Plugin_Continue;
|
||||
}
|
||||
|
||||
public void OnClientDisconnect(int client) {
|
||||
tempGod[client] = false;
|
||||
Editor[client].Reset();
|
||||
g_PropData[client].Reset();
|
||||
hatData[client].yeetGroundTimer = null;
|
||||
if(hatData[client].yeetGroundTimer != null)
|
||||
delete hatData[client].yeetGroundTimer;
|
||||
if(g_pendingSaveClient == client) {
|
||||
g_pendingSaveClient = 0;
|
||||
ClearSavePreview();
|
||||
|
@ -820,7 +848,6 @@ public void OnMapEnd() {
|
|||
if(hatData[i].yeetGroundTimer != null) {
|
||||
delete hatData[i].yeetGroundTimer;
|
||||
}
|
||||
hatData[i].yeetGroundTimer = null;
|
||||
DeleteWall(i);
|
||||
}
|
||||
createdWalls.Clear();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue