Fix ignore

This commit is contained in:
Jackzie 2024-07-15 11:38:22 -05:00
parent 774fa24bbf
commit b9b4dde366
11 changed files with 3525 additions and 4 deletions

2
.gitignore vendored
View file

@ -33,3 +33,5 @@ scripting/ssh.sp
scripting/l4d2_witch_force_attack_cmd.sp
l4d2_stats_plugin/
!sql/*
!scripting/.gitignore
!plugins/.gitignore

Binary file not shown.

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,122 @@
int Native_StartEdit(Handle plugin, int numParams) {
int client = GetNativeCell(1);
int entity = GetNativeCell(2);
Editor[client].Import(entity, false);
PrivateForward fwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell, Param_Cell);
fwd.AddFunction(INVALID_HANDLE, GetNativeFunction(3));
Editor[client].SetCallback(fwd, true);
return 0;
}
int Native_StartSpawner(Handle plugin, int numParams) {
int client = GetNativeCell(1);
g_PropData[client].Selector.Cancel();
PrivateForward fwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell, Param_Cell);
fwd.AddFunction(INVALID_HANDLE, GetNativeFunction(2));
Editor[client].SetCallback(fwd, false);
ShowCategoryList(client, ROOT_CATEGORY);
return 0;
}
int Native_CancelEdit(Handle plugin, int numParams) {
int client = GetNativeCell(1);
Editor[client].Cancel();
return 0;
}
int Native_IsEditorActive(Handle plugin, int numParams) {
int client = GetNativeCell(1);
Editor[client].IsActive();
return 0;
}
int Native_StartSelector(Handle plugin, int numParams) {
int client = GetNativeCell(1);
int color[3] = { 0, 255, 0 };
PrivateForward fwd = new PrivateForward(ET_Single, Param_Cell, Param_Cell);
fwd.AddFunction(plugin, GetNativeFunction(2));
GetNativeArray(3, color, 3);
int limit = GetNativeCell(4);
g_PropData[client].Selector.Start(color, 0, limit);
g_PropData[client].Selector.SetOnEnd(fwd);
return 0;
}
int Native_CancelSelector(Handle plugin, int numParams) {
int client = GetNativeCell(1);
g_PropData[client].Selector.Cancel();
return 0;
}
int Native_IsSelectorActive(Handle plugin, int numParams) {
int client = GetNativeCell(1);
g_PropData[client].Selector.IsActive();
return 0;
}
int Native_Selector_Start(Handle plugin, int numParams) {
int client = GetNativeCell(1);
int color[3] = { 0, 255, 0 };
GetNativeArray(2, color, 3);
int flags = GetNativeCell(3);
int limit = GetNativeCell(4);
g_PropData[client].Selector.Start(color, flags, limit);
return 0;
}
int Native_Selector_GetCount(Handle plugin, int numParams) {
int client = GetNativeCell(1);
if(!g_PropData[client].Selector.IsActive()) {
return -1;
} else {
return g_PropData[client].Selector.list.Length;
}
}
int Native_Selector_GetActive(Handle plugin, int numParams) {
int client = GetNativeCell(1);
return g_PropData[client].Selector.IsActive();
}
int Native_Selector_SetOnEnd(Handle plugin, int numParams) {
int client = GetNativeCell(1);
PrivateForward fwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell);
fwd.AddFunction(plugin, GetNativeFunction(2));
g_PropData[client].Selector.SetOnEnd(fwd);
return 0;
}
int Native_Selector_SetOnPreSelect(Handle plugin, int numParams) {
int client = GetNativeCell(1);
PrivateForward fwd = new PrivateForward(ET_Single, Param_Cell, Param_Cell);
if(!fwd.AddFunction(plugin, GetNativeFunction(2))) return 0;
g_PropData[client].Selector.SetOnPreSelect(fwd);
return 1;
}
int Native_Selector_SetOnPostSelect(Handle plugin, int numParams) {
int client = GetNativeCell(1);
PrivateForward fwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell);
if(!fwd.AddFunction(plugin, GetNativeFunction(2))) return 0;
g_PropData[client].Selector.SetOnPostSelect(fwd);
return 1;
}
int Native_Selector_SetOnUnselect(Handle plugin, int numParams) {
int client = GetNativeCell(1);
PrivateForward fwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell);
if(!fwd.AddFunction(plugin, GetNativeFunction(2))) return 0;
g_PropData[client].Selector.SetOnUnselect(fwd);
return 1;
}
int Native_Selector_AddEntity(Handle plugin, int numParams) {
int client = GetNativeCell(1);
int entity = GetNativeCell(2);
g_PropData[client].Selector.AddEntity(entity, false);
return 0;
}
int Native_Selector_RemoveEntity(Handle plugin, int numParams) {
int client = GetNativeCell(1);
int entity = GetNativeCell(2);
g_PropData[client].Selector.RemoveEntity(entity);
return 0;
}
int Native_Selector_Cancel(Handle plugin, int numParams) {
int client = GetNativeCell(1);
g_PropData[client].Selector.Cancel();
return 0;
}
int Native_Selector_End(Handle plugin, int numParams) {
int client = GetNativeCell(1);
g_PropData[client].Selector.End();
return 0;
}

View file

@ -0,0 +1,708 @@
int g_pendingSaveClient;
ArrayList g_previewItems;
CategoryData ROOT_CATEGORY;
ArrayList g_spawnedItems; // ArrayList(block=2)<entRef, [creator]>
ArrayList g_savedItems; // ArrayList<entRef>
StringMap g_recentItems; // Key: model[128], value: RecentEntry
/* Wish to preface this file:
* It's kinda messy. The main structs are:
* - ItemData
* - CategoryData
The rest are kinda necessary, for sorting reasons (SearchData, RecentEntry).
*/
enum ChatPrompt {
Prompt_None,
Prompt_Search,
Prompt_SaveScene,
Prompt_SaveSchematic,
Prompt_SaveCollection
}
enum SaveType {
Save_None,
Save_Scene,
Save_Schematic
}
int GLOW_MANAGER[3] = { 52, 174, 235 };
enum struct Schematic {
char name[64];
char creatorSteamid[32];
char creatorName[32];
ArrayList entities;
void Reset() {
this.name[0] = '\0';
this.creatorSteamid[0] = '\0';
this.creatorName[0] = '\0';
if(this.entities != null) delete this.entities;
}
void AddEntity(int entity, int client) {
SaveData save;
save.FromEntity(entity);
this.entities.PushArray(save);
}
void New(int client, const char[] name) {
if(client > 0) {
GetClientName(client, this.creatorName, sizeof(this.creatorName));
GetClientAuthId(client, AuthId_Steam2, this.creatorSteamid, sizeof(this.creatorSteamid));
}
strcopy(this.name, sizeof(this.name), name);
this.entities = new ArrayList(sizeof(SaveData));
}
bool Save() {
char path[PLATFORM_MAX_PATH];
BuildPath(Path_SM, path, sizeof(path), "data/prop_spawner/schematics/%s.schem", this.name);
CreateDirectory("data/prop_spawner/schematics", 0775);
KeyValues kv = new KeyValues(this.name);
kv.SetString("creator_steamid", this.creatorSteamid);
kv.SetString("creator_name", this.creatorName);
kv.JumpToKey("entities");
this.entities = new ArrayList(sizeof(SaveData));
SaveData ent;
while(kv.GotoNextKey()) {
kv.GetVector("offset", ent.origin);
kv.GetVector("angles", ent.angles);
kv.GetColor4("color", ent.color);
kv.GetString("model", ent.model, sizeof(ent.model));
this.entities.PushArray(ent);
}
kv.ExportToFile(path);
delete kv;
return true;
}
bool Import(const char[] name) {
char path[PLATFORM_MAX_PATH];
BuildPath(Path_SM, path, sizeof(path), "data/prop_spawner/schematics/%s.schem", name);
KeyValues kv = new KeyValues("root");
if(kv.ImportFromFile(path)) {
delete kv;
return false;
}
strcopy(this.name, sizeof(this.name), name);
kv.GetString("creator_steamid", this.creatorSteamid, sizeof(this.creatorSteamid));
kv.GetString("creator_name", this.creatorName, sizeof(this.creatorName));
kv.JumpToKey("entities");
this.entities = new ArrayList(sizeof(SaveData));
SaveData ent;
while(kv.GotoNextKey()) {
kv.GetVector("offset", ent.origin);
kv.GetVector("angles", ent.angles);
kv.GetColor4("color", ent.color);
kv.GetString("model", ent.model, sizeof(ent.model));
this.entities.PushArray(ent);
}
delete kv;
return true;
}
/// Spawns all schematics entities, returns list of entities, first being parent.
ArrayList SpawnEntities(const float origin[3], bool asPreview = true) {
if(this.entities == null) return null;
SaveData ent;
int parent = -1;
ArrayList spawnedEntities = new ArrayList();
for(int i = 0; i < this.entities.Length; i++) {
this.entities.GetArray(i, ent, sizeof(ent));
int entity = ent.ToEntity(origin, asPreview);
spawnedEntities.Push(EntIndexToEntRef(entity));
if(i == 0) {
SetParent(entity, parent)
} else {
parent = entity;
}
}
return spawnedEntities;
}
}
public any Native_SpawnSchematic(Handle plugin, int numParams) {
char name[32];
float pos[3];
float ang[3];
GetNativeString(0, name, sizeof(name));
GetNativeArray(1, pos, 3);
GetNativeArray(1, ang, 3);
Schematic schem;
if(!schem.Import(name)) {
return false;
}
ArrayList list = schem.SpawnEntities(pos, false);
delete list;
return true;
}
enum struct PropSelectorIterator {
ArrayList _list;
int _index;
int Entity;
void _Init(ArrayList list) {
this._list = list;
this._index = -1;
}
bool Next() {
this._index++;
return this._index + 1 < this._list.Length;
}
}
enum struct PropSelector {
int selectColor[3];
int limit;
ArrayList list;
PrivateForward endCallback;
PrivateForward selectPreCallback;
PrivateForward selectPostCallback;
PrivateForward unSelectCallback;
int _client;
PropSelectorIterator Iter() {
PropSelectorIterator iter;
iter._Init(this.list);
return iter;
}
void Reset() {
if(this.endCallback) delete this.endCallback;
if(this.selectPreCallback) delete this.selectPreCallback;
if(this.selectPostCallback) delete this.selectPostCallback;
if(this.unSelectCallback) delete this.unSelectCallback;
if(this.list) delete this.list;
}
void Start(int color[3], int flags = 0, int limit = 0) {
this.selectColor = color;
this.limit = 0;
this.list = new ArrayList();
SendEditorMessage(this._client, "\x05Left click\x01 to select, \x05right click\x01 to unselect");
SendEditorMessage(this._client, "Press \x05WALK+USE\x01 to confirm, \x05DUCK+USE\x01 to cancel");
if(Editor[this._client].IsActive()) {
Editor[this._client].Cancel();
}
}
void SetOnEnd(PrivateForward callback) {
this.endCallback = callback;
}
void SetOnPreSelect(PrivateForward callback) {
this.selectPreCallback = callback;
}
void SetOnPostSelect(PrivateForward callback) {
this.selectPostCallback = callback;
}
void SetOnUnselect(PrivateForward callback) {
this.unSelectCallback = callback;
}
void StartDirect(int color[3], SelectDoneCallback callback, int limit = 0) {
PrivateForward fwd = new PrivateForward(ET_Ignore, Param_Cell, Param_Cell);
fwd.AddFunction(INVALID_HANDLE, callback);
this.Start(color, 0, limit);
this.SetOnEnd(fwd);
}
bool IsActive() {
return this.list != null;
}
ArrayList End() {
if(this.list == null) return null;
SendEditorMessage(this._client, "Selection completed");
// Reset glows, remove selection from our spawned props
for(int i = 0; i < this.list.Length; i++) {
int ref = this.list.Get(i);
if(IsValidEntity(ref)) {
L4D2_RemoveEntityGlow(ref);
RemoveSpawnedProp(ref);
}
}
if(this.endCallback) {
if(GetForwardFunctionCount(this.endCallback) == 0) {
PrintToServer("[Editor] Warn: Selector.End(): callback has no functions assigned to it.");
}
Call_StartForward(this.endCallback);
Call_PushCell(this._client);
Call_PushCell(this.list.Clone());
int result = Call_Finish();
if(result != SP_ERROR_NONE) {
PrintToServer("[Editor] Warn: Selector.End() forward error: %d", result);
}
}
ArrayList copy = this.list;
this.list = null;
this.Reset();
return copy;
}
void Cancel() {
if(this.endCallback) {
Call_StartForward(this.endCallback);
Call_PushCell(this._client);
Call_PushCell(INVALID_HANDLE);
Call_Finish();
}
if(this.list) {
for(int i = 0; i < this.list.Length; i++) {
int ref = this.list.Get(i);
L4D2_RemoveEntityGlow(ref);
}
}
PrintToChat(this._client, "\x04[Editor]\x01 Selection cancelled.");
this.Reset();
}
int GetEntityRefIndex(int ref) {
int index = this.list.FindValue(ref);
if(index > -1) {
return index;
}
return -1;
}
void Clear() {
if(this.list) {
this.list.Clear();
}
}
/** Removes entity from list
* @return returns entity ref of entity removed
*/
int RemoveEntity(int entity) {
if(this.list == null) return -2;
L4D2_RemoveEntityGlow(entity);
int ref = EntIndexToEntRef(entity);
int index = this.GetEntityRefIndex(ref);
if(index > -1) {
this.list.Erase(index);
if(this.unSelectCallback != null) {
Call_StartForward(this.unSelectCallback)
Call_PushCell(this._client);
Call_PushCell(EntRefToEntIndex(ref));
Call_Finish();
}
return ref;
}
return INVALID_ENT_REFERENCE;
}
/**
* Adds entity to list
* @return index into list of entity
* @return -1 if already added
* @return -2 if callback rejected
*/
int AddEntity(int entity, bool useCallback = true) {
if(this.list == null) return -2;
int ref = EntIndexToEntRef(entity);
if(this.GetEntityRefIndex(ref) == -1) {
if(this.selectPreCallback != null && useCallback) {
Call_StartForward(this.selectPreCallback)
Call_PushCell(this._client);
Call_PushCell(entity);
bool allowed = true;
PrintToServer("Selector.AddEntity: PRE CALLBACK pre finish");
Call_Finish(allowed);
PrintToServer("Selector.AddEntity: PRE CALLBACK pre result %b", allowed);
if(!allowed) return -2;
}
L4D2_SetEntityGlow(entity, L4D2Glow_Constant, 10000, 0, this.selectColor, false);
int index = this.list.Push(ref);
PrintToServer("Selector.AddEntity: post CALLBACK pre");
if(this.selectPostCallback != null && useCallback) {
Call_StartForward(this.selectPostCallback)
Call_PushCell(this._client);
Call_PushCell(entity);
//Call_PushCell(index);
Call_Finish();
}
PrintToServer("Selector.AddEntity: post CALLBACK post");
return index;
}
return -1;
}
}
enum struct PlayerPropData {
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[64];
ChatPrompt chatPrompt;
PropSelector Selector;
SaveType pendingSaveType;
Schematic schematic;
int highlightedEntityRef;
int managerEntityRef;
void Init(int client) {
this.Selector._client = client;
}
// Called on PlayerDisconnect
void Reset() {
if(this.Selector.IsActive()) this.Selector.Cancel();
this.chatPrompt = Prompt_None;
this.clearListBuffer = false;
this.lastCategoryIndex = 0;
this.lastItemIndex = 0;
this.lastActiveTime = 0;
this.classnameOverride[0] = '\0';
this.CleanupBuffers();
this.pendingSaveType = Save_None;
this.schematic.Reset();
this.managerEntityRef = INVALID_ENT_REFERENCE;
this.StopHighlight();
}
void StartHighlight(int entity) {
this.highlightedEntityRef = EntIndexToEntRef(entity);
L4D2_SetEntityGlow(entity, L4D2Glow_Constant, 10000, 0, GLOW_MANAGER, false);
}
void StopHighlight() {
if(IsValidEntity(this.highlightedEntityRef)) {
L4D2_RemoveEntityGlow(this.highlightedEntityRef);
}
this.highlightedEntityRef = INVALID_ENT_REFERENCE;
}
void StartSchematic(int client, const char[] name) {
this.schematic.New(client, name);
this.pendingSaveType = Save_Schematic;
PrintToChat(client, "\x04[Editor]\x01 Started new schematic: \x05%s", name);
ShowCategoryList(client, ROOT_CATEGORY);
}
// Sets the list buffer
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 CleanupBuffers() {
this.ClearItemBuffer();
if(this.categoryStack != null) {
delete this.categoryStack;
}
this.clearListBuffer = false;
}
}
PlayerPropData g_PropData[MAXPLAYERS+1];
enum struct CategoryData {
// The display name of category
char name[64];
// If set, overwrites the classname it is spawned as
char classnameOverride[64];
bool hasItems; // true: items is ArrayList<ItemData>, false: items is ArrayList<CategoryData>
ArrayList items;
}
enum struct ItemData {
char model[128];
char name[64];
void FromSearchData(SearchData search) {
strcopy(this.model, sizeof(this.model), search.model);
strcopy(this.name, sizeof(this.name), search.name);
}
}
enum struct SearchData {
char model[128];
char name[64];
int index;
void FromItemData(ItemData item) {
strcopy(this.model, sizeof(this.model), item.model);
strcopy(this.name, sizeof(this.name), item.name);
}
}
methodmap ColorObject < JSONObject {
/// Creates a new Color with RGB between 0-255
public ColorObject(int r = 255, int g = 255, int b = 255, int alpha = 255) {
JSONObject obj = new JSONObject();
obj.SetInt("r", r);
obj.SetInt("g", g);
obj.SetInt("b", b);
obj.SetInt("alpha", alpha);
return view_as<ColorObject>(obj);
}
property int R {
public get() { return view_as<JSONObject>(this).GetInt("r"); }
public set(int value) { view_as<JSONObject>(this).SetInt("r", value); }
}
property int G {
public get() { return view_as<JSONObject>(this).GetInt("g"); }
public set(int value) { view_as<JSONObject>(this).SetInt("g", value); }
}
property int B {
public get() { return view_as<JSONObject>(this).GetInt("b"); }
public set(int value) { view_as<JSONObject>(this).SetInt("b", value); }
}
property int Alpha {
public get() { return view_as<JSONObject>(this).GetInt("alpha"); }
public set(int value) { view_as<JSONObject>(this).SetInt("alpha", value); }
}
}
methodmap Coordinates < JSONObject {
public Coordinates(float x = 0.0, float y = 0.0, float z = 0.0) {
JSONObject obj = new JSONObject();
obj.SetFloat("x", x);
obj.SetFloat("y", y);
obj.SetFloat("z", z);
return view_as<Coordinates>(obj);
}
property float X {
public get() { return view_as<JSONObject>(this).GetFloat("x"); }
public set(float value) { view_as<JSONObject>(this).SetFloat("x", value); }
}
property float Y {
public get() { return view_as<JSONObject>(this).GetFloat("y"); }
public set(float value) { view_as<JSONObject>(this).SetFloat("y", value); }
}
property float Z {
public get() { return view_as<JSONObject>(this).GetFloat("z"); }
public set(float value) { view_as<JSONObject>(this).SetFloat("z", value); }
}
public static Coordinates FromVec(const float vec[3]) {
return new Coordinates(vec[0], vec[1], vec[2]);
}
public void ToVec(float vec[3]) {
vec[0] = this.X;
vec[1] = this.Y;
vec[2] = this.Z;
}
}
methodmap SpawnerEntity < JSONObject {
property buildType BuildType {
public get() {
return view_as<buildType>(view_as<JSONObject>(this).GetInt("buildType"));
}
public set(buildType value) {
view_as<JSONObject>(this).SetInt("buildType", value);
}
}
property ColorObject Color {
public get() { return view_as<ColorObject>(view_as<JSONObject>(this).Get("color")); }
}
property Coordinates Origin {
public get() { return view_as<Coordinates>(view_as<JSONObject>(this).Get("origin")); }
public set(Coordinates newVec) {
view_as<JSONObject>(this).Remove("origin");
view_as<JSONObject>(this).Set("origin", newVec);
}
}
property Coordinates Angles {
public get() { return view_as<Coordinates>(view_as<JSONObject>(this).Get("angles")); }
public set(Coordinates newVec) {
view_as<JSONObject>(this).Remove("angles");
view_as<JSONObject>(this).Set("angles", newVec);
}
}
public int GetModel(char[] output, int maxlen) {
return view_as<JSONObject>(this).GetString("model", output, maxlen);
}
public void SetModel(const char[] input) {
view_as<JSONObject>(this).SetString("model", input);
}
public static SpawnerEntity FromEntity(int entity) {
char buffer[128];
GetEntityClassname(entity, buffer, sizeof(buffer));
JSONObject obj = new JSONObject();
obj.SetString("type", buffer);
GetEntPropString(entity, Prop_Data, "m_ModelName", buffer, sizeof(buffer));
obj.SetString("model", buffer);
int color[4];
GetEntityRenderColor(entity, color[0], color[1], color[2], color[3]);
obj.Set("color", view_as<JSONObject>(new ColorObject(color[0], color[1], color[2], color[3])));
// this.type = Build_Solid;
// if(StrEqual(this.model, "prop_physics")) this.type = Build_Physics;
// else if(StrEqual(this.model, "prop_dynamic")) {
// if(GetEntProp(entity, Prop_Send, "m_nSolidType") == 0) {
// this.type = Build_NonSolid;
// }
// }
SpawnerEntity se = view_as<SpawnerEntity>(obj);
float vec[3];
GetEntPropVector(entity, Prop_Send, "m_vecOrigin", vec);
se.Origin = Coordinates.FromVec(vec);
GetEntPropVector(entity, Prop_Send, "m_angRotation", vec);
se.Angles = Coordinates.FromVec(vec);
return se;
}
public int CreateEntity(const float offset[3], bool asPreview = true) {
}
}
enum struct SaveData {
char model[128];
buildType type;
float origin[3];
float angles[3];
int color[4];
void FromEntity(int entity) {
// Use this.model as a buffer:
GetEntityClassname(entity, this.model, sizeof(this.model));
this.type = Build_Solid;
if(StrEqual(this.model, "prop_physics")) this.type = Build_Physics;
else if(StrEqual(this.model, "prop_dynamic")) {
if(GetEntProp(entity, Prop_Send, "m_nSolidType") == 0) {
this.type = Build_NonSolid;
}
}
GetEntPropString(entity, Prop_Data, "m_ModelName", this.model, sizeof(this.model));
GetEntPropVector(entity, Prop_Send, "m_vecOrigin", this.origin);
GetEntPropVector(entity, Prop_Send, "m_angRotation", this.angles);
GetEntityRenderColor(entity, this.color[0],this.color[1],this.color[2],this.color[3]);
}
int ToEntity(const float offset[3], bool asPreview = true) {
int entity = -1;
if(this.type == Build_Physics)
entity = CreateEntityByName("prop_physics");
else
entity = CreateEntityByName("prop_dynamic_override");
if(entity == -1) {
return -1;
}
PrecacheModel(this.model);
DispatchKeyValue(entity, "model", this.model);
DispatchKeyValue(entity, "targetname", "saved_prop");
if(asPreview) {
DispatchKeyValue(entity, "rendermode", "1");
DispatchKeyValue(entity, "solid", "0");
} else {
DispatchKeyValue(entity, "solid", this.type == Build_NonSolid ? "0" : "6");
}
float pos[3];
for(int i = 0; i < 3; i++)
pos[i] = this.origin[i] + offset[i];
TeleportEntity(entity, pos, this.angles, NULL_VECTOR);
if(!DispatchSpawn(entity)) {
return -1;
}
int alpha = asPreview ? 200 : this.color[3];
SetEntityRenderColor(entity, this.color[0], this.color[1], this.color[2], alpha);
if(asPreview)
g_previewItems.Push(EntIndexToEntRef(entity));
else
AddSpawnedItem(entity);
return entity;
}
void Serialize(char[] output, int maxlen) {
Format(
output, maxlen, "%s,%d,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%d,%d,%d,%d",
this.model, this.type, this.origin[0], this.origin[1], this.origin[2],
this.angles[0], this.angles[1], this.angles[2],
this.color[0], this.color[1], this.color[2], this.color[3]
);
}
void Deserialize(const char[] input) {
char buffer[16];
int index = SplitString(input, ",", this.model, sizeof(this.model));
index += SplitString(input[index], ",", buffer, sizeof(buffer));
this.type = view_as<buildType>(StringToInt(buffer));
for(int i = 0; i < 3; i++) {
index += SplitString(input[index], ",", buffer, sizeof(buffer));
this.origin[i] = StringToFloat(buffer);
}
for(int i = 0; i < 3; i++) {
index += SplitString(input[index], ",", buffer, sizeof(buffer));
this.angles[i] = StringToFloat(buffer);
}
for(int i = 0; i < 4; i++) {
index += SplitString(input[index], ",", buffer, sizeof(buffer));
this.color[i] = StringToInt(buffer);
}
}
}
enum struct RecentEntry {
char name[64];
int count;
}
#include <editor/props/db.sp>
#include <editor/props/methods.sp>
#include <editor/props/cmd.sp>
#include <editor/props/menu_handlers.sp>
#include <editor/props/menu_methods.sp>

View file

@ -0,0 +1,139 @@
Action Command_Props(int client, int args) {
char arg[32];
GetCmdArg(1, arg, sizeof(arg));
if(args == 0 || StrEqual(arg, "help")) {
PrintToChat(client, "See console for available sub-commands");
PrintToConsole(client, "help - this");
PrintToConsole(client, "list <classname/index/owner> - lists all props and their distances");
PrintToConsole(client, "search <search query>");
PrintToConsole(client, "edit <last/#index>");
PrintToConsole(client, "del <last/#index/tool>");
PrintToConsole(client, "add <cursor/tool>");
PrintToConsole(client, "favorite - favorites active editor entity");
PrintToConsole(client, "controls - list all the controls");
PrintToConsole(client, "reload - reload prop list");
PrintToConsole(client, "schem[atic] <new/save/edit/delete/load> <name>");
} else if(StrEqual(arg, "schem") || StrEqual(arg, "schematic")) {
char arg2[16];
GetCmdArg(2, arg2, sizeof(arg2));
if(StrEqual(arg2, "new")) {
char name[32];
GetCmdArg(3, name, sizeof(name));
if(name[0] == '\0') {
PrintToChat(client, "\x04[Editor]\x01 Please enter a name");
} else {
g_PropData[client].StartSchematic(client, name);
}
} else if(StrEqual(arg2, "save")) {
if(g_PropData[client].pendingSaveType == Save_Schematic) {
g_PropData[client].schematic.Save();
} else {
PrintToChat(client, "\x04[Editor]\x01 No schematic to save.");
}
} else {
PrintToChat(client, "\x04[Editor]\x01 Unknown option: %s", arg2);
}
} else if(StrEqual(arg, "list")) {
char arg2[16];
GetCmdArg(2, arg2, sizeof(arg2));
bool isClassname = StrEqual(arg2, "classname");
bool isIndex = StrEqual(arg2, "index");
bool isOwner = StrEqual(arg2, "owner");
if(args == 1 || isClassname || isIndex || isOwner) {
PrintToChat(client, "\x04[Editor]\x01 Please specify: \x05classname, index, owner. ");
return Plugin_Handled;
}
float pos[3], propPos[3], dist;
GetAbsOrigin(client, pos);
for(int i = 0; i < g_spawnedItems.Length; i++) {
int ref = GetSpawnedItem(i);
if(ref > -1) {
GetEntPropVector(ref, Prop_Send, "m_vecOrigin", propPos);
dist = GetVectorDistance(pos, propPos, false);
if(isIndex) {
int entity = EntRefToEntIndex(ref);
PrintToConsole(client, "%d. ent #%d - %.0fu away", i, entity, dist);
} else if(isClassname) {
char classname[32];
GetEntityClassname(ref, classname, sizeof(classname));
PrintToConsole(client, "%d. %s - %.0fu away", i, classname, dist);
} else if(isOwner) {
int spawner = g_spawnedItems.Get(i, 1);
int player = GetClientOfUserId(spawner);
if(player > 0) {
PrintToConsole(client, "%d. %N - %.0fu away", i, player, dist);
} else {
PrintToConsole(client, "%d. (disconnected) - %.0fu away", i, dist);
}
}
}
}
PrintToChat(client, "\x04[Editor]\x01 Check console");
} else if(StrEqual(arg, "search")) {
if(args == 1) {
PrintToChat(client, "\x04[Editor]\x01 Enter your search query:");
g_PropData[client].chatPrompt = Prompt_Search;
} else {
char query[32];
GetCmdArg(2, query, sizeof(query));
DoSearch(client, query);
}
} else if(StrEqual(arg, "edit")) {
char arg2[32];
GetCmdArg(2, arg2, sizeof(arg2));
int index;
if(StrEqual(arg2, "last")) {
// Get last one
index = GetSpawnedIndex(client, -1);
} else {
index = StringToInt(arg2);
}
if(index >= 0 && index < g_spawnedItems.Length) {
int entity = EntRefToEntIndex(g_spawnedItems.Get(index));
Editor[client].Import(entity);
PrintToChat(client, "\x04[Editor]\x01 Editing entity \x05%d", entity);
} else {
PrintToChat(client, "\x04[Editor]\x01 Invalid index, out of bounds. Enter a value between [0, %d]", g_spawnedItems.Length - 1);
}
} else if(StrEqual(arg, "del")) {
char arg2[32];
GetCmdArg(2, arg2, sizeof(arg2));
int index;
if(StrEqual(arg2, "last")) {
// Get last one
index = GetSpawnedIndex(client, -1);
} else {
index = StringToInt(arg2);
}
if(index >= 0 && index < g_spawnedItems.Length) {
int entity = EntRefToEntIndex(g_spawnedItems.Get(index));
if(entity > 0 && IsValidEntity(entity)) {
RemoveEntity(entity);
}
g_spawnedItems.Erase(index);
PrintToChat(client, "\x04[Editor]\x01 Deleted entity \x05%d", entity);
} else {
PrintToChat(client, "\x04[Editor]\x01 Invalid index, out of bounds. Enter a value between [0, %d]", g_spawnedItems.Length - 1);
}
} else if(StrEqual(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");
}
return Plugin_Handled;
}

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

@ -0,0 +1,517 @@
TopMenuObject g_propSpawnerCategory;
public void OnAdminMenuReady(Handle topMenuHandle) {
TopMenu topMenu = TopMenu.FromHandle(topMenuHandle);
if(g_topMenu != topMenuHandle) {
g_propSpawnerCategory = topMenu.AddCategory("hats_editor", Category_Handler, "sm_prop");
if(g_propSpawnerCategory != INVALID_TOPMENUOBJECT) {
topMenu.AddItem("editor_spawn", AdminMenu_Spawn, g_propSpawnerCategory, "sm_prop");
// topMenu.AddItem("editor_edit", AdminMenu_Edit, g_propSpawnerCategory, "sm_prop");
topMenu.AddItem("editor_delete", AdminMenu_Delete, g_propSpawnerCategory, "sm_prop");
topMenu.AddItem("editor_saveload", AdminMenu_SaveLoad, g_propSpawnerCategory, "sm_prop");
topMenu.AddItem("editor_manager", AdminMenu_Manager, g_propSpawnerCategory, "sm_prop");
topMenu.AddItem("editor_selector", AdminMenu_Selector, g_propSpawnerCategory, "sm_prop");
}
g_topMenu = topMenu;
}
}
/////////////
// HANDLERS
/////////////
void Category_Handler(TopMenu topmenu, TopMenuAction action, TopMenuObject topobj_id, int param, char[] buffer, int maxlength) {
if(action == TopMenuAction_DisplayTitle) {
Format(buffer, maxlength, "Select a task:");
} else if(action == TopMenuAction_DisplayOption) {
Format(buffer, maxlength, "Spawn Props");
}
}
void AdminMenu_Selector(TopMenu topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength) {
if(action == TopMenuAction_DisplayOption) {
Format(buffer, maxlength, "Selector");
} else if(action == TopMenuAction_SelectOption) {
ShowManagerSelectorMenu(param);
}
}
void AdminMenu_Spawn(TopMenu topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength) {
if(action == TopMenuAction_DisplayOption) {
Format(buffer, maxlength, "Spawn Props");
} else if(action == TopMenuAction_SelectOption) {
ConVar cheats = FindConVar("sm_cheats");
if(cheats != null && !cheats.BoolValue) {
CReplyToCommand(param, "\x04[Editor] \x01Set \x05sm_cheats\x01 to \x051\x01 to use the prop spawner");
return;
}
ShowSpawnRoot(param);
}
}
int Spawn_RootHandler(Menu menu, MenuAction action, int client, int param2) {
if (action == MenuAction_Select) {
char info[2];
menu.GetItem(param2, info, sizeof(info));
switch(info[0]) {
case 'f': Spawn_ShowFavorites(client);
case 'r': Spawn_ShowRecents(client);
case 's': Spawn_ShowSearch(client);
case 'n': ShowCategoryList(client, ROOT_CATEGORY);
}
// TODO: handle back (to top menu)
} else if (action == MenuAction_Cancel) {
if(param2 == MenuCancel_ExitBack) {
DisplayTopMenuCategory(g_topMenu, g_propSpawnerCategory, client);
}
} else if (action == MenuAction_End)
delete menu;
return 0;
}
// void AdminMenu_Edit(TopMenu topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength) {
// if(action == TopMenuAction_DisplayOption) {
// Format(buffer, maxlength, "Edit Props");
// } else if(action == TopMenuAction_SelectOption) {
// ShowEditList(param);
// }
// }
void AdminMenu_Delete(TopMenu topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength) {
if(action == TopMenuAction_DisplayOption) {
Format(buffer, maxlength, "Delete Props");
} else if(action == TopMenuAction_SelectOption) {
ShowDeleteList(param);
}
}
void AdminMenu_SaveLoad(TopMenu topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength) {
if(action == TopMenuAction_DisplayOption) {
Format(buffer, maxlength, "Save / Load");
} else if(action == TopMenuAction_SelectOption) {
Spawn_ShowSaveLoadMainMenu(param);
}
}
void AdminMenu_Manager(TopMenu topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength) {
if(action == TopMenuAction_DisplayOption) {
Format(buffer, maxlength, "Manage Props");
} else if(action == TopMenuAction_SelectOption) {
Spawn_ShowManagerMainMenu(param);
}
}
int SaveLoadMainMenuHandler(Menu menu, MenuAction action, int client, int param2) {
if (action == MenuAction_Select) {
char info[2];
menu.GetItem(param2, info, sizeof(info));
SaveType type = view_as<SaveType>(StringToInt(info));
ShowSaves(client, type);
} else if (action == MenuAction_Cancel) {
if(param2 == MenuCancel_ExitBack) {
DisplayTopMenuCategory(g_topMenu, g_propSpawnerCategory, client);
}
} else if (action == MenuAction_End)
delete menu;
return 0;
}
int SaveLoadSceneHandler(Menu menu, MenuAction action, int client, int param2) {
if (action == MenuAction_Select) {
char saveName[64];
menu.GetItem(param2, saveName, sizeof(saveName));
if(saveName[0] == '\0') {
// Save new
FormatTime(saveName, sizeof(saveName), "%Y-%m-%d_%H-%I-%M");
if(CreateSceneSave(saveName)) {
PrintToChat(client, "\x04[Editor]\x01 Saved as \x05%s/%s.txt", g_currentMap, saveName);
} else {
PrintToChat(client, "\x04[Editor]\x01 Unable to save. Sorry.");
}
} else if(g_pendingSaveClient != 0 && g_pendingSaveClient != client) {
PrintToChat(client, "\x04[Editor]\x01 Another user is currently loading a save.");
} else if(g_PropData[client].pendingSaveType == Save_Schematic) {
PrintToChat(client, "\x04[Editor]\x01 Please complete or cancel current schematic to continue.");
} else if(LoadScene(saveName, true)) {
ConfirmSave(client, saveName);
g_pendingSaveClient = client;
PrintToChat(client, "\x04[Editor]\x01 Previewing save \x05%s", saveName);
PrintToChat(client, "\x04[Editor]\x01 Press \x05Shift + Middle Mouse\x01 to spawn, \x05Middle Mouse\x01 to cancel");
} else {
PrintToChat(client, "\x04[Editor]\x01 Could not load save file.");
}
} else if (action == MenuAction_Cancel) {
if(param2 == MenuCancel_ExitBack) {
Spawn_ShowSaveLoadMainMenu(client);
}
} else if (action == MenuAction_End)
delete menu;
return 0;
}
int SaveLoadSchematicHandler(Menu menu, MenuAction action, int client, int param2) {
if (action == MenuAction_Select) {
char saveName[64];
menu.GetItem(param2, saveName, sizeof(saveName));
Schematic schem;
if(saveName[0] == '\0') {
if(g_PropData[client].pendingSaveType == Save_Schematic) {
if(g_PropData[client].schematic.Save()) {
PrintToChat(client, "\x04[Editor]\x01 Saved schematic as \x05%s", g_PropData[client].schematic.name);
} else {
PrintToChat(client, "\x04[Editor]\x01 Failed to save schematic.");
}
g_PropData[client].schematic.Reset();
g_PropData[client].pendingSaveType = Save_None;
} else {
g_PropData[client].chatPrompt = Prompt_SaveSchematic;
PrintToChat(client, "\x04[Editor]\x01 Enter in chat a name for schematic");
}
} else if(schem.Import(saveName)) {
float pos[3];
GetCursorLocation(client, pos);
ArrayList list = schem.SpawnEntities(pos, true);
SaveData save;
int parent = list.GetArray(0, save);
delete list;
Editor[client].Import(parent);
if(g_pendingSaveClient != 0 && g_pendingSaveClient != client) {
PrintToChat(client, "\x04[Editor]\x01 Another user is currently loading a scene.");
} else {
g_pendingSaveClient = client;
PrintToChat(client, "\x04[Editor]\x01 Previewing schematic \x05%s", saveName);
PrintToChat(client, "\x04[Editor]\x01 Press \x05Shift + Middle Mouse\x01 to spawn, \x05Middle Mouse\x01 to cancel");
}
} else {
PrintToChat(client, "\x04[Editor]\x01 Could not load save file.");
}
} else if (action == MenuAction_Cancel) {
if(param2 == MenuCancel_ExitBack) {
Spawn_ShowSaveLoadMainMenu(client);
}
} else if (action == MenuAction_End)
delete menu;
return 0;
}
int SaveLoadConfirmHandler(Menu menu, MenuAction action, int client, int param2) {
if (action == MenuAction_Select) {
ClearSavePreview();
char info[64];
menu.GetItem(param2, info, sizeof(info));
if(info[0] != '\0') {
PrintToChat(client, "\x04[Editor]\x01 Loaded scene \x05%s", info);
LoadScene(info, false);
}
} else if (action == MenuAction_Cancel) {
if(param2 == MenuCancel_ExitBack) {
Spawn_ShowSaveLoadMainMenu(client);
}
} else if (action == MenuAction_End)
delete menu;
return 0;
}
int ManagerHandler(Menu menu, MenuAction action, int client, int param2) {
if (action == MenuAction_Select) {
char info[8];
menu.GetItem(param2, info, sizeof(info));
if(info[0] != '\0') {
int index = StringToInt(info);
int ref = g_spawnedItems.Get(index);
// TODO: add delete confirm
if(!IsValidEntity(ref)) {
SendEditorMessage(client, "Entity has disappeared");
} else {
int entity = EntRefToEntIndex(ref);
g_PropData[client].managerEntityRef = ref;
g_PropData[client].StartHighlight(entity);
ShowManagerEntityMenu(client, entity);
}
}
} else if (action == MenuAction_Cancel) {
if(param2 == MenuCancel_ExitBack) {
DisplayTopMenuCategory(g_topMenu, g_propSpawnerCategory, client);
}
} else if (action == MenuAction_End)
delete menu;
return 0;
}
int ManagerEntityHandler(Menu menu, MenuAction action, int client, int param2) {
if (action == MenuAction_Select) {
g_PropData[client].StopHighlight();
char info[32];
menu.GetItem(param2, info, sizeof(info));
int ref = g_PropData[client].managerEntityRef;
if(!IsValidEntity(ref)) {
SendEditorMessage(client, "Entity disappeared");
} else if(StrEqual(info, "edit")) {
Editor[client].ImportEntity(EntRefToEntIndex(ref), Edit_Manager);
return 0;
} else if(StrEqual(info, "delete")) {
for(int i = 0; i < g_spawnedItems.Length; i++) {
int spawnedRef = g_spawnedItems.Get(i);
if(spawnedRef == ref) {
g_spawnedItems.Erase(i);
break;
}
}
if(IsValidEntity(ref)) {
RemoveEntity(ref);
}
return 0;
} else if(StrEqual(info, "view")) {
ReplyToCommand(client, "Maybe soon.");
} else if(StrEqual(info, "select")) {
int entity = EntRefToEntIndex(ref);
g_PropData[client].Selector.AddEntity(entity);
} else {
SendEditorMessage(client, "Unknown option / not implemented");
}
ShowManagerSelectorMenu(client);
} else if (action == MenuAction_Cancel) {
g_PropData[client].StopHighlight();
if(param2 == MenuCancel_ExitBack) {
Spawn_ShowManagerMainMenu(client);
}
} else if (action == MenuAction_End)
delete menu;
return 0;
}
int ManagerSelectorMainMenuHandler(Menu menu, MenuAction action, int client, int param2) {
if (action == MenuAction_Select) {
EntitySelector sel = EntitySelector.FromClient(client);
if(!sel.Active) {
return 0;
}
char info[32];
menu.GetItem(param2, info, sizeof(info));
if(StrEqual(info, "list")) {
SendEditorMessage(client, "Not implemented");
} else if(StrEqual(info, "actions")) {
ShowManagerSelectorActionsMenu(client);
} else if(StrEqual(info, "add-self")) {
int userid = GetClientUserId(client);
int count;
for(int i = 0; i < g_spawnedItems.Length; i++) {
int ref = g_spawnedItems.Get(i);
int spawnedBy = g_spawnedItems.Get(i, 1);
if(spawnedBy == userid) {
sel.AddEntity(EntRefToEntIndex(ref));
count++;
}
}
ReplyToCommand(client, "Added %d entities", count);
ShowManagerSelectorMenu(client);
} else if(StrEqual(info, "add-all")) {
int count;
for(int i = 0; i < g_spawnedItems.Length; i++) {
int ref = g_spawnedItems.Get(i);
sel.AddEntity(EntRefToEntIndex(ref));
count++;
}
ReplyToCommand(client, "Added %d entities", count);
ShowManagerSelectorMenu(client);
} else if(StrEqual(info, "cancel")) {
g_PropData[client].Selector.Cancel();
}
} else if (action == MenuAction_Cancel) {
g_PropData[client].Selector.Cancel();
} else if (action == MenuAction_End)
delete menu;
return 0;
}
int ManagerSelectorActionHandler(Menu menu, MenuAction action, int client, int param2) {
if (action == MenuAction_Select) {
if(!g_PropData[client].Selector.IsActive()) {
return 0;
}
char info[32];
menu.GetItem(param2, info, sizeof(info));
if(StrEqual(info, "delete")) {
int count;
for(int i = 0; i < g_PropData[client].Selector.list.Length; i++) {
int ref = g_PropData[client].Selector.list.Get(i);
if(IsValidEntity(ref)) {
RemoveEntity(ref);
count++;
}
}
ArrayList list = g_PropData[client].Selector.End();
delete list;
SendEditorMessage(client, "Deleted %d entities", count);
Spawn_ShowManagerMainMenu(client);
} else if(StrEqual(info, "clear")) {
g_PropData[client].Selector.Clear();
SendEditorMessage(client, "Cleared selection.");
Spawn_ShowManagerMainMenu(client);
} else if(StrEqual(info, "save_scene")) {
ArrayList items = g_PropData[client].Selector.End();
g_PropData[client].SetItemBuffer(items, true);
g_PropData[client].chatPrompt = Prompt_SaveScene;
SendEditorMessage(client, "Enter name for scene:");
} else if(StrEqual(info, "save_collection")) {
ArrayList items = g_PropData[client].Selector.End();
g_PropData[client].SetItemBuffer(items, true);
g_PropData[client].chatPrompt = Prompt_SaveCollection;
SendEditorMessage(client, "Enter name for collection:");
} else {
SendEditorMessage(client, "Unknown option / not implemented");
}
} else if (action == MenuAction_Cancel) {
if(param2 == MenuCancel_ExitBack) {
ShowManagerSelectorMenu(client);
}
} else if (action == MenuAction_End)
delete menu;
return 0;
}
int COLOR_DELETE[3] = { 255, 0, 0 }
int DeleteHandler(Menu menu, MenuAction action, int client, int param2) {
if (action == MenuAction_Select) {
char info[128];
menu.GetItem(param2, info, sizeof(info));
int ref = StringToInt(info[2]);
int option = StringToInt(info);
if(option == -1) {
// Delete all (everyone)
int count = DeleteAll();
PrintToChat(client, "\x04[Editor]\x01 Deleted \x05%d\x01 items", count);
ShowDeleteList(client);
} else if(option == -2) {
// Delete all (mine only)
int count = DeleteAll(client);
PrintToChat(client, "\x04[Editor]\x01 Deleted \x05%d\x01 items", count);
ShowDeleteList(client);
} else if(option == -3) {
if(g_PropData[client].Selector.IsActive()) {
g_PropData[client].Selector.End();
PrintToChat(client, "\x04[Editor]\x01 Delete tool cancelled");
} else {
g_PropData[client].Selector.StartDirect(COLOR_DELETE, OnDeleteToolEnd);
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 {
int index = g_spawnedItems.FindValue(ref);
if(IsValidEntity(ref)) {
RemoveEntity(ref);
}
if(index > -1) {
g_spawnedItems.Erase(index);
index--;
} else { index = 0; }
ShowDeleteList(client, index);
}
} else if (action == MenuAction_Cancel) {
if(param2 == MenuCancel_ExitBack) {
DisplayTopMenuCategory(g_topMenu, g_propSpawnerCategory, client);
}
} else if (action == MenuAction_End)
delete menu;
return 0;
}
int 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];
menu.GetItem(param2, info, sizeof(info));
char index[4];
char model[128];
int nameIndex = SplitString(info, "|", index, sizeof(index));
nameIndex += SplitString(info[nameIndex], "|", model, sizeof(model));
g_PropData[client].lastItemIndex = StringToInt(index);
if(Editor[client].PreviewModel(model, g_PropData[client].classnameOverride)) {
Editor[client].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);
}
// Use same item menu again:
ShowItemMenu(client);
} else if(action == MenuAction_Cancel) {
g_PropData[client].ClearItemBuffer();
if(param2 == MenuCancel_ExitBack) {
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();
}
} else if (action == MenuAction_End) {
delete menu;
}
return 0;
}
// int EditHandler(Menu menu, MenuAction action, int client, int param2) {
// if (action == MenuAction_Select) {
// char info[8];
// menu.GetItem(param2, info, sizeof(info));
// int ref = StringToInt(info);
// int index = g_spawnedItems.FindValue(ref);
// int entity = EntRefToEntIndex(ref);
// if(entity > 0) {
// Editor[client].Import(entity, false);
// PrintToChat(client, "\x04[Editor]\x01 Editing entity \x05%d", entity);
// } else {
// PrintToChat(client, "\x04[Editor]\x01 Entity disappeared.");
// if(index > -1) {
// g_spawnedItems.Erase(index);
// index--;
// } else { index = 0; }
// }
// ShowEditList(client, index);
// } else if (action == MenuAction_Cancel) {
// if(param2 == MenuCancel_ExitBack) {
// DisplayTopMenuCategory(g_topMenu, g_propSpawnerCategory, client);
// }
// } else if (action == MenuAction_End)
// delete menu;
// return 0;
// }

View file

@ -0,0 +1,317 @@
/////////////
// METHODS
/////////////
void ShowSpawnRoot(int client) {
Menu menu = new Menu(Spawn_RootHandler);
menu.SetTitle("Choose spawn list:");
menu.AddItem("f", "Favorites (Broken :D)");
menu.AddItem("r", "Recently Spawned Props");
menu.AddItem("s", "Search for Props");
menu.AddItem("n", "Browse Props");
menu.ExitBackButton = true;
menu.ExitButton = true;
menu.Display(client, MENU_TIME_FOREVER);
}
void Spawn_ShowRecents(int client) {
if(g_recentItems == null) LoadRecents();
ArrayList items = GetRecentsItemList();
if(items.Length == 0) {
CReplyToCommand(client, "\x04[Editor] \x01No recent props spawned.");
DisplayTopMenuCategory(g_topMenu, g_propSpawnerCategory, client);
return;
}
ShowTempItemMenu(client, items, "Recents");
}
void Spawn_ShowSearch(int client) {
g_PropData[client].chatPrompt = Prompt_Search;
CReplyToCommand(client, "\x04[Editor] \x01Please enter search query in chat:");
}
void ShowDeleteList(int client, int index = -3) {
if(g_spawnedItems.Length == 0) {
SendEditorMessage(client, "No spawned items to delete");
DisplayTopMenuCategory(g_topMenu, g_propSpawnerCategory, client);
return;
}
Menu menu = new Menu(DeleteHandler);
menu.SetTitle("Delete Props");
menu.AddItem("-1", "Delete All");
menu.AddItem("-2", "Delete All (Mine Only)");
menu.AddItem("-3", "Delete Tool");
// menu.AddItem("-4", "Delete Last Save");
char info[8];
char buffer[128];
for(int i = 0; i < g_spawnedItems.Length; i++) {
int ref = GetSpawnedItem(i);
if(ref == -1) continue;
Format(info, sizeof(info), "0|%d", ref);
GetEntPropString(ref, Prop_Data, "m_ModelName", buffer, sizeof(buffer));
index = FindCharInString(buffer, '/', true);
if(index != -1)
menu.AddItem(info, buffer[index + 1]);
}
menu.ExitBackButton = true;
menu.ExitButton = true;
// Add +3 to the index for the 3 "Delete ..." buttons
// TODO: restore the delete index issue, use /7*7
menu.DisplayAt(client, 0, MENU_TIME_FOREVER);
}
// void ShowEditList(int client, int index = 0) {
// if(g_spawnedItems.Length == 0) {
// SendEditorMessage(client, "No spawned items to edit");
// DisplayTopMenuCategory(g_topMenu, g_propSpawnerCategory, client);
// return;
// }
// Menu menu = new Menu(EditHandler);
// menu.SetTitle("Edit Prop");
// char info[8];
// char buffer[32];
// for(int i = 0; i < g_spawnedItems.Length; i++) {
// int ref = GetSpawnedItem(i);
// if(ref == -1) continue;
// Format(info, sizeof(info), "%d", ref);
// GetEntPropString(ref, Prop_Data, "m_ModelName", buffer, sizeof(buffer));
// index = FindCharInString(buffer, '/', true);
// if(index != -1)
// menu.AddItem(info, buffer[index + 1]);
// }
// menu.ExitBackButton = true;
// menu.ExitButton = true;
// // Add +2 to the index for the two "Delete ..." buttons
// menu.DisplayAt(client, index, MENU_TIME_FOREVER);
// }
void ShowCategoryList(int client, CategoryData category) {
LoadCategories();
char info[4];
// No category list provided, use the global one.
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);
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;
// Round to page instead of index (int division)
int index = g_PropData[client].lastCategoryIndex / 7 * 7;
menu.DisplayAt(client, index, MENU_TIME_FOREVER);
}
void _showItemMenu(int client, ArrayList items, const char[] title = "", bool clearArray = false, const char[] classnameOverride = "") {
if(items == null) {
// Use previous list buffer
items = g_PropData[client].itemBuffer;
if(items == null) {
LogError("Previous list does not exist and no new list was provided ShowItemMenu(%N)", client);
PrintToChat(client, "\x04[Editor]\x01 An error occurred (no list)");
return;
}
} else {
// 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);
}
if(items.Length == 0) {
PrintToChat(client, "\x04[Editor]\x01 No items to show.");
return;
}
Menu itemMenu = new Menu(SpawnItemHandler);
if(title[0] != '\0')
itemMenu.SetTitle(title);
ItemData item;
char info[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, for recents to work
Format(info, sizeof(info), "%d|%s|%s", i, item.model, item.name);
itemMenu.AddItem(info, item.name);
}
itemMenu.ExitBackButton = true;
itemMenu.ExitButton = true;
// We don't want to start at the index but the page of the index
int index = (g_PropData[client].lastItemIndex / 7) * 7;
itemMenu.DisplayAt(client, index, MENU_TIME_FOREVER);
}
/**
* 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));
}
void Spawn_ShowSaveLoadMainMenu(int client) {
Menu menu = new Menu(SaveLoadMainMenuHandler);
menu.SetTitle("Save / Load");
// Id is SaveType
menu.AddItem("1", "Map Scenes");
menu.AddItem("2", "Schematics");
menu.ExitBackButton = true;
menu.ExitButton = true;
menu.Display(client, MENU_TIME_FOREVER);
}
void Spawn_ShowManagerMainMenu(int client, int index = 0) {
if(g_spawnedItems.Length == 0) {
SendEditorMessage(client, "No spawned items to manage");
DisplayTopMenuCategory(g_topMenu, g_propSpawnerCategory, client);
return;
}
Menu menu = new Menu(ManagerHandler);
menu.SetTitle("Manager");
// Id is SaveType
char info[8];
char buffer[128];
for(int i = 0; i < g_spawnedItems.Length; i++) {
int ref = GetSpawnedItem(i);
if(ref == -1) continue;
IntToString(i, info, sizeof(info));
GetEntPropString(ref, Prop_Data, "m_ModelName", buffer, sizeof(buffer));
index = FindCharInString(buffer, '/', true);
if(index != -1)
menu.AddItem(info, buffer[index + 1]);
}
menu.ExitBackButton = true;
menu.ExitButton = true;
menu.DisplayAt(client, index, MENU_TIME_FOREVER);
}
void ShowManagerEntityMenu(int client, int entity) {
if(!IsValidEntity(entity)) {
SendEditorMessage(client, "Item has vanished");
Spawn_ShowManagerMainMenu(client);
return;
}
Menu menu = new Menu(ManagerEntityHandler);
menu.SetTitle("Manage %d", entity);
menu.AddItem("edit", "Edit");
menu.AddItem("delete", "Delete");
menu.AddItem("select", "Select");
menu.AddItem("view", "View");
menu.ExitBackButton = true;
menu.ExitButton = true;
menu.Display(client, MENU_TIME_FOREVER);
}
void ShowManagerSelectorMenu(int client) {
EntitySelector sel = EntitySelector.FromClient(client);
if(!sel.Active) {
sel.Start(GLOW_MANAGER);
sel.SetOnEnd(OnManagerSelectorEnd);
sel.SetOnPostSelect(OnManagerSelectorSelect);
sel.SetOnUnselect(OnManagerSelectorSelect);
}
Menu menu = new Menu(ManagerSelectorMainMenuHandler);
menu.SetTitle("Selector");
menu.AddItem("list", "> List Entities");
menu.AddItem("actions", "> Actions");
menu.AddItem("add-self", "Add All Self-Spawned");
menu.AddItem("add-all", "Add All Spawned");
menu.ExitBackButton = false;
menu.ExitButton = true;
menu.Display(client, MENU_TIME_FOREVER);
}
void ShowManagerSelectorActionsMenu(int client) {
Menu menu = new Menu(ManagerSelectorActionHandler);
menu.SetTitle("Selector: Select action");
char display[32];
Format(display, sizeof(display), "Entities: %d", g_PropData[client].Selector.list.Length);
menu.AddItem("", display, ITEMDRAW_DISABLED);
// menu.AddItem("edit", "Edit");
menu.AddItem("delete", "Delete Entities");
menu.AddItem("clear", "Clear Selection");
// menu.AddItem("select", "Select");
menu.AddItem("save_scene", "Save as Scene");
menu.AddItem("save_collection", "Save as Collection");
menu.ExitBackButton = true;
menu.ExitButton = true;
menu.Display(client, MENU_TIME_FOREVER);
}
void ShowSaves(int client, SaveType type) {
ArrayList saves;
Menu newMenu;
if(type == Save_Scene) {
newMenu = new Menu(SaveLoadSceneHandler);
newMenu.SetTitle("Save & Load > Map Scenes");
newMenu.AddItem("", "[Save New Scene]");
saves = LoadScenes();
} else if(type == Save_Schematic) {
newMenu = new Menu(SaveLoadSchematicHandler);
newMenu.SetTitle("Save & Load > Schematics");
if(g_PropData[client].pendingSaveType == Save_Schematic) {
newMenu.AddItem("", "[Save Schematic]");
} else {
newMenu.AddItem("", "[Start New Schematic]");
// Don't load saves when in middle of creating schematic
saves = LoadSchematics();
}
}
if(saves != null) {
char name[64];
for(int i = 0; i < saves.Length; i++) {
saves.GetString(i, name, sizeof(name));
newMenu.AddItem(name, name);
}
delete saves;
}
newMenu.ExitBackButton = true;
newMenu.ExitButton = true;
newMenu.Display(client, MENU_TIME_FOREVER);
}

View file

@ -0,0 +1,565 @@
ArrayList LoadScenes() {
char path[PLATFORM_MAX_PATH];
BuildPath(Path_SM, path, sizeof(path), "data/prop_spawner/saves/%s", g_currentMap);
FileType fileType;
DirectoryListing listing = OpenDirectory(path);
if(listing == null) return null;
char buffer[64];
ArrayList saves = new ArrayList(ByteCountToCells(64));
while(listing.GetNext(buffer, sizeof(buffer), fileType)) {
if(buffer[0] == '.') continue;
saves.PushString(buffer);
}
delete listing;
return saves;
}
ArrayList LoadSchematics() {
char path[PLATFORM_MAX_PATH];
BuildPath(Path_SM, path, sizeof(path), "data/prop_spawner/schematics");
FileType fileType;
DirectoryListing listing = OpenDirectory(path);
if(listing == null) return null;
char buffer[64];
ArrayList saves = new ArrayList(ByteCountToCells(64));
while(listing.GetNext(buffer, sizeof(buffer), fileType) && fileType == FileType_File) {
if(buffer[0] == '.') continue;
saves.PushString(buffer);
}
delete listing;
return saves;
}
bool LoadScene(const char[] save, bool asPreview = false) {
char path[PLATFORM_MAX_PATH];
BuildPath(Path_SM, path, sizeof(path), "data/prop_spawner/saves/%s/%s", g_currentMap, save);
// ArrayList savedItems = new ArrayList(sizeof(SaveData));
File file = OpenFile(path, "r");
if(file == null) return false;
char buffer[256];
if(asPreview) {
// Kill any previous preview
if(g_previewItems != null) ClearSavePreview();
g_previewItems = new ArrayList();
}
SaveData data;
while(file.ReadLine(buffer, sizeof(buffer))) {
if(buffer[0] == '#') continue;
data.Deserialize(buffer);
int entity = data.ToEntity(NULL_VECTOR, asPreview);
if(entity == -1) {
PrintToServer("[Editor] LoadScene(\"%s\", %b): failed to create %s", save, asPreview, buffer);
continue;
}
}
delete file;
return true;
}
void ConfirmSave(int client, const char[] name) {
Menu newMenu = new Menu(SaveLoadConfirmHandler);
newMenu.AddItem(name, "Spawn");
newMenu.AddItem("", "Cancel");
newMenu.ExitBackButton = false;
newMenu.ExitButton = false;
newMenu.Display(client, 0);
}
void ClearSavePreview() {
if(g_previewItems != null) {
for(int i = 0; i < g_previewItems.Length; i++) {
int ref = g_previewItems.Get(i);
if(IsValidEntity(ref)) {
RemoveEntity(ref);
}
}
delete g_previewItems;
}
g_pendingSaveClient = 0;
}
void AddSpawnedItem(int entity, int client = 0) {
if(client > 0 && g_PropData[client].pendingSaveType == Save_Schematic) {
g_PropData[client].schematic.AddEntity(entity, client);
}
// TODO: confirm if we want it to be in list, otherwise we need to clean manually
int userid = client > 0 ? GetClientUserId(client) : 0;
int index = g_spawnedItems.Push(EntIndexToEntRef(entity));
g_spawnedItems.Set(index, userid, 1);
}
bool CreateCollection(const char[] folder, const char[] name, ArrayList entities, int client = 0) {
char path[PLATFORM_MAX_PATH], pathTemp[PLATFORM_MAX_PATH];
BuildPath(Path_SM, path, sizeof(path), "data/prop_spawner/collections", folder);
CreateDirectory(path, 509);
Format(path, sizeof(path), "%s/%s", path, folder);
CreateDirectory(path, 509);
Format(pathTemp, sizeof(pathTemp), "%s/%s.json.tmp", path, name);
Format(path, sizeof(path), "%s/%s.json", path, name);
char buffer[132];
JSONObject root = new JSONObject();
FormatTime(buffer, sizeof(buffer), "%Y-%m-%dT%H:%M:%S.%f");
root.SetString("created", buffer);
if(client > 0) {
GetClientAuthId(client, AuthId_Steam2, buffer, sizeof(buffer));
root.SetString("creator_steamid", buffer);
float vec[3];
GetClientAbsOrigin(client, vec);
JSONObject origin = view_as<JSONObject>(Coordinates.FromVec(vec));
root.Set("origin", origin);
GetClientEyeAngles(client, vec);
JSONObject angles = view_as<JSONObject>(Coordinates.FromVec(vec));
root.Set("angles", angles);
}
JSONArray entArr = new JSONArray();
for(int i = 0; i < entities.Length; i++) {
int ref = entities.Get(i);
if(IsValidEntity(ref)) {
SpawnerEntity ent = SpawnerEntity.FromEntity(EntRefToEntIndex(ref));
entArr.Push(ent);
}
}
root.Set("entities", entArr);
root.ToFile(pathTemp, JSON_INDENT(4));
RenameFile(path, pathTemp);
SetFilePermissions(path, FPERM_U_WRITE | FPERM_U_READ | FPERM_G_WRITE | FPERM_G_READ | FPERM_O_READ);
LogAction(client, -1, "created collection \"%s\" in \"%s\"", name, path);
return true;
}
bool CreateSceneSave(const char[] name, ArrayList items = null, int client = 0) {
char path[PLATFORM_MAX_PATH];
BuildPath(Path_SM, path, sizeof(path), "data/prop_spawner/saves/%s", g_currentMap);
CreateDirectory(path, 509);
Format(path, sizeof(path), "%s/%s.txt", path, name);
File file = OpenFile(path, "w");
if(file == null) {
PrintToServer("[Editor] Could not save: %s", path);
return false;
}
// TODO: switch to json
char buffer[132];
SaveData data;
if(items == null) items = g_spawnedItems;
for(int i = 0; i < items.Length; i++) {
int ref = items.Get(i);
if(IsValidEntity(ref)) {
data.FromEntity(ref);
data.Serialize(buffer, sizeof(buffer));
file.WriteLine("%s", buffer);
}
}
file.Flush();
delete file;
return true;
}
void UnloadSave() {
if(g_savedItems != null) {
delete g_savedItems;
}
}
public void LoadCategories() {
if(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(ROOT_CATEGORY.items, path);
ROOT_CATEGORY.items.SortCustom(SortCategories);
}
int SortCategories(int index1, int index2, ArrayList array, Handle hndl) {
CategoryData cat1;
array.GetArray(index1, cat1);
CategoryData cat2;
array.GetArray(index2, cat2);
return strcmp(cat1.name, cat2.name);
}
public void UnloadCategories() {
if(ROOT_CATEGORY.items == null) return;
_UnloadCategories(ROOT_CATEGORY.items);
delete ROOT_CATEGORY.items;
}
void _UnloadCategories(ArrayList list) {
CategoryData cat;
for(int i = 0; i < list.Length; i++) {
list.GetArray(i, cat);
_UnloadCategory(cat);
}
}
void _UnloadCategory(CategoryData cat) {
// Is a sub-category:
if(!cat.hasItems) {
_UnloadCategories(cat.items);
}
delete cat.items;
}
void LoadFolder(ArrayList parent, const char[] rootPath) {
char buffer[PLATFORM_MAX_PATH];
FileType fileType;
DirectoryListing listing = OpenDirectory(rootPath);
if(listing == null) {
LogError("Cannot open \"%s\"", rootPath);
}
while(listing.GetNext(buffer, sizeof(buffer), fileType)) {
if(fileType == FileType_Directory) {
// TODO: support subcategory
if(buffer[0] == '.') continue;
CategoryData data;
Format(data.name, sizeof(data.name), "%s", buffer);
data.items = new ArrayList(sizeof(CategoryData));
Format(buffer, sizeof(buffer), "%s/%s", rootPath, buffer);
LoadFolder(data.items, buffer);
parent.PushArray(data);
} else if(fileType == FileType_File) {
Format(buffer, sizeof(buffer), "%s/%s", rootPath, buffer);
LoadProps(parent, buffer);
}
}
delete listing;
}
void LoadProps(ArrayList parent, const char[] filePath) {
File file = OpenFile(filePath, "r");
if(file == null) {
PrintToServer("[Props] Cannot open file \"%s\"", filePath);
return;
}
CategoryData category;
category.items = new ArrayList(sizeof(ItemData));
category.hasItems = true;
char buffer[128];
if(!file.ReadLine(buffer, sizeof(buffer))) {
delete file;
return;
}
ReplaceString(buffer, sizeof(buffer), "\n", "");
ReplaceString(buffer, sizeof(buffer), "\r", "");
Format(category.name, sizeof(category.name), "%s", buffer);
while(file.ReadLine(buffer, sizeof(buffer))) {
if(buffer[0] == '#') continue;
ReplaceString(buffer, sizeof(buffer), "\n", "");
ReplaceString(buffer, sizeof(buffer), "\r", "");
ItemData item;
int index = SplitString(buffer, ":", item.model, sizeof(item.model));
if(index == -1) {
index = SplitString(buffer, " ", item.model, sizeof(item.model));
if(index == -1) {
// No name provided, use the model's filename
index = FindCharInString(buffer, '/', true);
strcopy(item.name, sizeof(item.name), item.model[index + 1]);
} else {
strcopy(item.name, sizeof(item.name), buffer[index]);
}
category.items.PushArray(item);
} else if(StrEqual(item.model, "Classname")) {
strcopy(category.classnameOverride, sizeof(category.classnameOverride), buffer[index]);
} else if(StrEqual(item.model, "Type")) {
Format(category.classnameOverride, sizeof(category.classnameOverride), "_%s", buffer[index]);
}
}
parent.PushArray(category);
delete file;
}
bool recentsChanged = false;
bool SaveRecents() {
if(!recentsChanged) return true; // Nothing to do, nothing changed
if(g_recentItems == null) return false;
char path[PLATFORM_MAX_PATH];
BuildPath(Path_SM, path, sizeof(path), "data/prop_spawner/recents_cache.csv");
File file = OpenFile(path, "w");
if(file == null) {
PrintToServer("[Editor] Could not write to %s", path);
return false;
}
StringMapSnapshot snapshot = g_recentItems.Snapshot();
char model[128];
RecentEntry entry;
for(int i = 0; i < snapshot.Length; i++) {
snapshot.GetKey(i, model, sizeof(model));
g_recentItems.GetArray(model, entry, sizeof(entry));
file.WriteLine("%s,%s,%d", model, entry.name, entry.count);
}
file.Flush();
delete file;
delete snapshot;
recentsChanged = false;
return true;
}
bool LoadRecents() {
if(g_recentItems != null) delete g_recentItems;
char path[PLATFORM_MAX_PATH];
BuildPath(Path_SM, path, sizeof(path), "data/prop_spawner/recents_cache.csv");
File file = OpenFile(path, "r");
if(file == null) return false;
g_recentItems = new StringMap();
char buffer[128+64+16];
char model[128];
RecentEntry entry;
while(file.ReadLine(buffer, sizeof(buffer))) {
int index = SplitString(buffer, ",", model, sizeof(model));
index += SplitString(buffer[index], ",", entry.name, sizeof(entry.name));
entry.count = StringToInt(buffer[index]);
g_recentItems.SetArray(model, entry, sizeof(entry));
}
delete file;
return true;
}
// Returns an ArrayList<ItemData> of all the recents
ArrayList GetRecentsItemList() {
ArrayList items = new ArrayList(sizeof(ItemData));
StringMapSnapshot snapshot = g_recentItems.Snapshot();
char model[128];
RecentEntry entry;
ItemData item;
for(int i = 0; i < snapshot.Length; i++) {
snapshot.GetKey(i, model, sizeof(model));
g_recentItems.GetArray(model, entry, sizeof(entry));
strcopy(item.model, sizeof(item.model), model);
strcopy(item.name, sizeof(item.name), entry.name);
}
// This is pretty expensive in terms of allocations but shrug
items.SortCustom(SortRecents);
delete snapshot;
return items;
}
int SortRecents(int index1, int index2, ArrayList array, Handle handle) {
ItemData data1;
array.GetArray(index1, data1);
ItemData data2;
array.GetArray(index2, data2);
int count1, count2;
RecentEntry entry;
if(g_recentItems.GetArray(data1.model, entry, sizeof(entry))) return 0; //skip if somehow no entry
count1 = entry.count;
if(g_recentItems.GetArray(data2.model, entry, sizeof(entry))) return 0; //skip if somehow no entry
count2 = entry.count;
return count2 - count1; // desc
}
void AddRecent(const char[] model, const char[] name) {
if(g_recentItems == null) {
if(!LoadRecents()) return;
}
RecentEntry entry;
if(!g_recentItems.GetArray(model, entry, sizeof(entry))) {
entry.count = 0;
strcopy(entry.name, sizeof(entry.name), name);
}
entry.count++;
recentsChanged = true;
g_recentItems.SetArray(model, entry, sizeof(entry));
}
public Action OnClientSayCommand(int client, const char[] command, const char[] sArgs) {
if(g_PropData[client].chatPrompt == Prompt_None) {
return Plugin_Continue;
}
switch(g_PropData[client].chatPrompt) {
case Prompt_Search: DoSearch(client, sArgs);
case Prompt_SaveScene: {
if(CreateSceneSave(sArgs, g_PropData[client].itemBuffer, client)) {
PrintToChat(client, "\x04[Editor]\x01 Saved as \x05%s/%s.txt", g_currentMap, sArgs);
} else {
PrintToChat(client, "\x04[Editor]\x01 Unable to save. Sorry.");
}
}
case Prompt_SaveCollection: {
if(CreateCollection("global", sArgs, g_PropData[client].itemBuffer, client)) {
SendEditorMessage(client, "Saved \x05%s/%s.json\x04 successfully.", "global", sArgs);
} else {
SendEditorMessage(client, "Failed to save collection.");
}
// TODO: figure out how to know which way to return
ShowManagerSelectorMenu(client);
}
default:
PrintToChat(client, "\x04[Editor]\x01 Not implemented.");
}
g_PropData[client].chatPrompt = Prompt_None;
return Plugin_Handled;
}
void DoSearch(int client, const char[] query) {
ArrayList results = SearchItems(query);
if(results.Length == 0) {
CPrintToChat(client, "\x04[Editor]\x01 No results found. :(");
} else {
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
int GetSpawnedIndex(int client, int index) {
int userid = GetClientUserId(client);
if(index >= 0) {
for(int i = index; i < g_spawnedItems.Length; i++) {
int spawnedBy = g_spawnedItems.Get(i, 1);
if(spawnedBy == userid) {
return i;
}
}
} else {
for(int i = g_spawnedItems.Length + index; i >= 0; i--) {
int spawnedBy = g_spawnedItems.Get(i, 1);
if(spawnedBy == userid) {
return i;
}
}
}
return -1;
}
#define MAX_SEARCH_RESULTS 10
ArrayList SearchItems(const char[] query) {
// We have to put it into SearchData enum struct, then convert it back to ItemResult
LoadCategories();
ArrayList results = new ArrayList(sizeof(SearchData));
_searchCategory(results, ROOT_CATEGORY.items, query);
results.SortCustom(SortSearch);
ArrayList items = new ArrayList(sizeof(ItemData));
ItemData item;
SearchData data;
for(int i = 0; i < results.Length; i++) {
results.GetArray(i, data);
item.FromSearchData(data);
items.PushArray(item);
}
delete results;
return items;
}
int SortSearch(int index1, int index2, ArrayList array, Handle handle) {
SearchData data1;
array.GetArray(index1, data1);
SearchData data2;
array.GetArray(index2, data2);
return data1.index - data2.index;
}
void _searchCategory(ArrayList results, ArrayList categories, const char[] query) {
CategoryData cat;
if(categories == null) return;
for(int i = 0; i < categories.Length; i++) {
categories.GetArray(i, cat);
if(cat.hasItems) {
//cat.items is of CatetoryData
if(!_searchItems(results, cat.items, query)) return;
} else {
//cat.items is of ItemData
_searchCategory(results, cat.items, query);
}
}
}
bool _searchItems(ArrayList results, ArrayList items, const char[] query) {
ItemData item;
SearchData search;
if(items == null) return false;
for(int i = 0; i < items.Length; i++) {
items.GetArray(i, item);
int searchIndex = StrContains(item.name, query, false);
if(searchIndex > -1) {
search.FromItemData(item);
search.index = searchIndex;
results.PushArray(search);
if(results.Length > MAX_SEARCH_RESULTS) return false;
}
}
return true;
}
int GetSpawnedItem(int index) {
if(index < 0 || index >= g_spawnedItems.Length) return -1;
int ref = g_spawnedItems.Get(index);
if(!IsValidEntity(ref)) {
g_spawnedItems.Erase(index);
return -1;
}
return ref;
}
bool RemoveSpawnedProp(int ref) {
// int ref = EntIndexToEntRef(entity);
int index = g_spawnedItems.FindValue(ref);
if(index > -1) {
g_spawnedItems.Erase(index);
return true;
}
return false;
}
void OnDeleteToolEnd(int client, ArrayList entities) {
int count;
for(int i = 0; i < entities.Length; i++) {
int ref = entities.Get(i);
if(IsValidEntity(ref)) {
count++;
RemoveSpawnedProp(ref);
RemoveEntity(ref);
}
}
delete entities;
PrintToChat(client, "\x04[Editor]\x01 \x05%d\x01 entities deleted", count);
}
void OnManagerSelectorEnd(int client, ArrayList entities) {
// TODO: implement manager selector cb
ReplyToCommand(client, "Not Implemented");
Spawn_ShowManagerMainMenu(client);
if(entities != null) {
delete entities;
}
}
void OnManagerSelectorSelect(int client, int entity) {
// update entity count
// ShowManagerSelectorMenu(client);
}
int DeleteAll(int onlyPlayer = 0) {
int userid = onlyPlayer > 0 ? GetClientUserId(onlyPlayer) : 0;
int count;
for(int i = 0; i < g_spawnedItems.Length; i++) {
int ref = g_spawnedItems.Get(i);
int spawnedBy = g_spawnedItems.Get(i, 1);
// Skip if wishing to only delete certain items:
if(onlyPlayer == 0 || spawnedBy == userid) {
if(IsValidEntity(ref)) {
RemoveEntity(ref);
}
// TODO: erasing while removing
g_spawnedItems.Erase(i);
i--; // go back up one
count++;
}
}
return count;
}
#define SHOW_HINT_MIN_DURATION 600 // 600 s (10min)
void ShowHint(int client) {
int time = GetTime();
int lastActive = g_PropData[client].lastActiveTime;
g_PropData[client].lastActiveTime = time;
if(time - lastActive < SHOW_HINT_MIN_DURATION) return;
PrintToChat(client, "\x01Change Mode: \x05ZOOM");
PrintToChat(client, "\x01Place: \x05USE(E) \x01Cancel: \x05WALK(SHIFT) + USE(E)");
PrintToChat(client, "\x01Rotate: \x05Hold RELOAD(R) + MOVE MOUSE\x01 Change Axis: \x05Left Click \x01Snap Angle: \x05Right Click");
PrintToChat(client, "\x01Type \x05/prop favorite\x01 to (un)favorite.");
PrintToChat(client, "\x01More 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

@ -14,6 +14,7 @@
#include <smlib/effects>
#include <multicolors>
#include <adminmenu>
#include <ripext>
int g_iLaserIndex;
@ -94,7 +95,7 @@ public void OnPluginStart() {
char targetName[32];
while((entity = FindEntityByClassname(entity, "func_brush")) != INVALID_ENT_REFERENCE) {
GetEntPropString(entity, Prop_Data, "m_iName", targetName, sizeof(targetName));
if(StrContains(targetName, "l4d2_hats_") == 0) {
if(StrContains(targetName, "editor") == 0) {
createdWalls.Push(EntIndexToEntRef(entity));
SDKHook(entity, SDKHook_Use, OnWallClicked);
}
@ -200,7 +201,8 @@ public Action OnPlayerRunCmd(int client, int& buttons, int& impulse, float vel[3
} else if(buttons & IN_USE) {
if(buttons & IN_SPEED) {
//Delete
g_PropData[client].Selector.End();
ArrayList items = g_PropData[client].Selector.End();
delete items;
} else if(buttons & IN_DUCK) {
//Cancel
g_PropData[client].Selector.Cancel();
@ -406,7 +408,7 @@ public void OnPluginEnd() {
if(g_spawnedItems != null) {
delete g_spawnedItems;
}
TriggerInput("prop_preview", "Kill");
TriggerInput("editor_preview", "Kill");
}
public bool TraceEntityFilterPlayer(int entity, int contentsMask, any data) {