This commit is contained in:
Jackzie 2024-02-15 09:02:36 -06:00
parent 23cbb7aeac
commit 5c37d46bcc
10 changed files with 547 additions and 232 deletions

Binary file not shown.

View file

@ -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");
entity = CreateEntityByName(this.classname);
if(entity == -1) return -1;
if(StrEqual(this.classname, "weapon_melee_spawn")) {
DispatchKeyValue(entity, "melee_weapon", this.data);
} else {
entity = CreateEntityByName(this.classname);
DispatchKeyValue(entity, "spawnflags", "8");
}
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));

View file

@ -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,7 +517,10 @@ int HatConsentHandler(Menu menu, MenuAction action, int target, int param2) {
ReplyToCommand(target, "Player has disconnected");
return 0;
} else if(hatAction == 1) {
EquipHat(activator, target, "player", 0);
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");
PrintHintText(activator, "%N refused your request", target);

View file

@ -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>

View file

@ -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");
}

View 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;
}

View file

@ -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);
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");
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;
}

View file

@ -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
menu.AddItem(info, cat.name);
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);
}
/**
* 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);
}
// 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
void Spawn_ShowFavorites(int client) {
if(g_db == null) {
PrintToChat(client, "\x04[Editor]\x01 Cannot connect to database.");
return;
}
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;
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));
}

View file

@ -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);
}

View file

@ -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) {
// Turn off rotate when player wants rotate
Editor[client].hasCollisionRotate = false;
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);
}
CalculateEditorPosition(client, Filter_IgnorePlayerAndWall);
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,10 +717,9 @@ 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
Editor[client].CycleMode(); // R: Cycle forward
else if(buttons & IN_ZOOM) {
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)) {
hatPresetCookie.Get(client, ActivePreset[client], 32);
if(ActivePreset[client][0] != '\0') {
RestoreActivePreset(client);
ReplyToCommand(client, "[Hats] Applied your hat preset! Clear it with /hatp");
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();