mirror of
https://github.com/Jackzmc/sourcemod-plugins.git
synced 2025-05-05 19:03:22 +00:00
Split out editor from hats
This commit is contained in:
parent
c71924e9e1
commit
89d4cfbd17
16 changed files with 3391 additions and 6566 deletions
BIN
plugins/l4d2_editor.smx
Normal file
BIN
plugins/l4d2_editor.smx
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -1,122 +0,0 @@
|
|||
|
||||
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;
|
||||
}
|
|
@ -1,569 +0,0 @@
|
|||
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
|
||||
}
|
||||
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, "Left click to select, right click to unselect");
|
||||
SendEditorMessage(this._client, "Press WALK+USE to confirm, DUCK+USE to 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;
|
||||
}
|
||||
|
||||
void End() {
|
||||
if(this.list == null) return;
|
||||
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);
|
||||
}
|
||||
} else {
|
||||
PrintToServer("[Editor] Warn: Selector.End() called but no callback assigned, voiding list");
|
||||
}
|
||||
this.Reset();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/** 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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 <hats/props/db.sp>
|
||||
#include <hats/props/methods.sp>
|
||||
#include <hats/props/cmd.sp>
|
||||
#include <hats/props/menu_handlers.sp>
|
||||
#include <hats/props/menu_methods.sp>
|
|
@ -1,139 +0,0 @@
|
|||
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;
|
||||
}
|
|
@ -1,125 +0,0 @@
|
|||
#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;
|
||||
}
|
|
@ -1,481 +0,0 @@
|
|||
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, "Manager (ALPHA)");
|
||||
} 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) {
|
||||
Spawn_ShowSaveLoadMainMenu(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");
|
||||
return 0;
|
||||
}
|
||||
if(StrEqual(info, "edit")) {
|
||||
Editor[client].ImportEntity(EntRefToEntIndex(ref), Edit_Manager);
|
||||
Spawn_ShowManagerMainMenu(client);
|
||||
} 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);
|
||||
}
|
||||
Spawn_ShowManagerMainMenu(client);
|
||||
} else if(StrEqual(info, "view")) {
|
||||
ReplyToCommand(client, "Maybe soon.");
|
||||
} else if(StrEqual(info, "select")) {
|
||||
ShowManagerSelectorMenu(client);
|
||||
int entity = EntRefToEntIndex(ref);
|
||||
g_PropData[client].Selector.AddEntity(entity);
|
||||
} else {
|
||||
SendEditorMessage(client, "Unknown option / not implemented");
|
||||
}
|
||||
} 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) {
|
||||
if(!EntitySelector.FromClient(client).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, "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")) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
g_PropData[client].Selector.End();
|
||||
Spawn_ShowManagerMainMenu(client);
|
||||
} else if(StrEqual(info, "save")) {
|
||||
// TODO: implement
|
||||
SendEditorMessage(client, "Not implemented");
|
||||
} else {
|
||||
SendEditorMessage(client, "Unknown option / not implemented");
|
||||
}
|
||||
} else if (action == MenuAction_Cancel) {
|
||||
if(param2 == MenuCancel_ExitBack) {
|
||||
Spawn_ShowSaveLoadMainMenu(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;
|
||||
}
|
|
@ -1,315 +0,0 @@
|
|||
/////////////
|
||||
// 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");
|
||||
// menu.AddItem("select", "Select");
|
||||
menu.AddItem("save", "Save");
|
||||
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);
|
||||
}
|
|
@ -1,513 +0,0 @@
|
|||
|
||||
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 CreateSceneSave(const char[] name) {
|
||||
char path[PLATFORM_MAX_PATH];
|
||||
BuildPath(Path_SM, path, sizeof(path), "data/prop_spawner/saves/%s", g_currentMap);
|
||||
CreateDirectory(path, 509);
|
||||
Format(path, sizeof(path), "%s/%s.txt", path, name);
|
||||
File file = OpenFile(path, "w");
|
||||
if(file == null) {
|
||||
PrintToServer("[Editor] Could not save: %s", path);
|
||||
return false;
|
||||
}
|
||||
char buffer[132];
|
||||
SaveData data;
|
||||
for(int i = 0; i < g_spawnedItems.Length; i++) {
|
||||
int ref = g_spawnedItems.Get(i);
|
||||
if(IsValidEntity(ref)) {
|
||||
data.FromEntity(ref);
|
||||
data.Serialize(buffer, sizeof(buffer));
|
||||
file.WriteLine("%s", buffer);
|
||||
}
|
||||
}
|
||||
file.Flush();
|
||||
delete file;
|
||||
return true;
|
||||
}
|
||||
|
||||
void UnloadSave() {
|
||||
if(g_savedItems != null) {
|
||||
delete g_savedItems;
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadCategories() {
|
||||
if(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)) {
|
||||
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_SaveSchematic: {
|
||||
g_PropData[client].StartSchematic(client, sArgs);
|
||||
}
|
||||
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");
|
||||
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, "\x05ZOOM: \x01Change Mode");
|
||||
PrintToChat(client, "\x05USE: \x01Place \x05Shift + USE: \x01Cancel \x05Ctrl + USE: \x01Change Type");
|
||||
PrintToChat(client, "\x05R: \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);
|
||||
}
|
|
@ -1,155 +0,0 @@
|
|||
#if defined _editor_included_
|
||||
#endinput
|
||||
#endif
|
||||
#define _editor_included_
|
||||
|
||||
public SharedPlugin __pl_editor_ = {
|
||||
name = "editor",
|
||||
file = "hats.smx",
|
||||
#if defined REQUIRE_PLUGIN
|
||||
required = 1,
|
||||
#else
|
||||
required = 0,
|
||||
#endif
|
||||
};
|
||||
|
||||
#if !defined REQUIRE_PLUGIN
|
||||
public void __pl_editor__SetNTVOptional()
|
||||
{
|
||||
MarkNativeAsOptional("SpawnSchematic");
|
||||
MarkNativeAsOptional("StartEdit");
|
||||
MarkNativeAsOptional("StartSpawner");
|
||||
MarkNativeAsOptional("CancelEdit");
|
||||
MarkNativeAsOptional("IsEditorActive");
|
||||
|
||||
MarkNativeAsOptional("StartSelector");
|
||||
MarkNativeAsOptional("CancelSelector");
|
||||
MarkNativeAsOptional("IsSelectorActive");
|
||||
|
||||
MarkNativeAsOptional("Selector.Count.get");
|
||||
MarkNativeAsOptional("Selector.Active.get");
|
||||
MarkNativeAsOptional("Selector.Start");
|
||||
MarkNativeAsOptional("Selector.SetOnEnd");
|
||||
MarkNativeAsOptional("Selector.SetOnPreSelect");
|
||||
MarkNativeAsOptional("Selector.SetOnPostSelect");
|
||||
MarkNativeAsOptional("Selector.SetOnUnselect");
|
||||
MarkNativeAsOptional("Selector.AddEntity");
|
||||
MarkNativeAsOptional("Selector.RemoveEntity");
|
||||
MarkNativeAsOptional("Selector.Cancel");
|
||||
MarkNativeAsOptional("Selector.End");
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
native bool SpawnSchematic(const char name[32], const float pos[3], const float angles[3] = NULL_VECTOR);
|
||||
|
||||
/** Called when edit is done or cancelled
|
||||
* @param client - client doing the edit
|
||||
* @param entity - The entity edited
|
||||
* @param result - Result of the edit, or cancelled
|
||||
* @return boolean - only for StartSpawner, true to continue, false to end spawning
|
||||
*/
|
||||
typeset EditorDoneCallback {
|
||||
function void (int client, int entity, CompleteType result);
|
||||
function bool (int client, int entity, CompleteType result);
|
||||
}
|
||||
|
||||
/** Called when an item is to be selected.
|
||||
* @return boolean - TRUE to allow item to be selected, FALSE to reject
|
||||
*/
|
||||
typedef SelectPreAddCallback = function bool (int client, int entity);
|
||||
/** Called when an item has been selected */
|
||||
typedef SelectPostAddCallback = function void (int client, int entity);
|
||||
|
||||
/** Called when an item is to be unselected. */
|
||||
typedef SelectRemoveCallback = function void (int client, int entity);
|
||||
/** Called when a user is done selecting items
|
||||
* @param client - client doing the selection
|
||||
* @param entities - if null, selection was cancelled. if not null, contains list of entity references, must be deleted.
|
||||
*/
|
||||
typedef SelectDoneCallback = function void (int client, ArrayList entities);
|
||||
|
||||
/** Starts editing an entity
|
||||
* @param client - The client that is editing
|
||||
* @param entity - The entity to edit
|
||||
* @param doneCallback - Called when edit is done
|
||||
*/
|
||||
native void StartEdit(int client, int entity, EditorDoneCallback doneCallback);
|
||||
/** Let client pick prop(s) to spawn
|
||||
* @param client - The client that is editing
|
||||
* @param entity - The entity to edit
|
||||
* @param doneCallback - Called when edit is done
|
||||
*/
|
||||
native void StartSpawner(int client, EditorDoneCallback doneCallback);
|
||||
native void CancelEdit(int client);
|
||||
// Includes non-plugin started edits
|
||||
native bool IsEditorActive(int client);
|
||||
|
||||
/** Starts a selection, where the client can click on entities to select or deselect them.
|
||||
* @param client - the client that can select
|
||||
* @param callback - called when user is done seleting or cancelled
|
||||
* @param highlightColor - the color to highlight selected items, default solid green
|
||||
* @param maxEntities - the max number of selections, 0 for infinite
|
||||
*/
|
||||
native void StartSelector(int client, SelectDoneCallback callback, int highlightColor[3] = { 0, 255, 0 }, int maxEntities = 0);
|
||||
|
||||
methodmap EntitySelector {
|
||||
public EntitySelector(int client) {
|
||||
return view_as<EntitySelector>(client);
|
||||
}
|
||||
|
||||
public static EntitySelector FromClient(int client) {
|
||||
return view_as<EntitySelector>(client);
|
||||
}
|
||||
|
||||
/** Starts a new selector for client
|
||||
* @param highlightColor - the color to highlight selected items, default solid green
|
||||
* @param flags - not used.
|
||||
* @param maxEntities - the max number of selections, 0 for infinite
|
||||
*/
|
||||
public native EntitySelector Start(int highlightColor[3], int flags = 0, int maxEntities = 0);
|
||||
|
||||
|
||||
property int Count {
|
||||
/** Returns the number of entities in selector. Returns -1 if not active */
|
||||
public native get();
|
||||
}
|
||||
|
||||
property bool Active {
|
||||
public native get();
|
||||
}
|
||||
|
||||
/** Sets the callback for when the selector is ended (or cancelled) */
|
||||
public native void SetOnEnd(SelectDoneCallback callback);
|
||||
|
||||
/** Sets the callback for when an item is to be added to the selector. */
|
||||
public native void SetOnPreSelect(SelectPreAddCallback callback);
|
||||
|
||||
/** Sets the callback for when an item has been added to the selector. */
|
||||
public native void SetOnPostSelect(SelectPostAddCallback callback);
|
||||
|
||||
/** Sets the callback for when an item is removed from selector. */
|
||||
public native void SetOnUnselect(SelectRemoveCallback callback);
|
||||
|
||||
/** Adds an entity to selection. Does not call SelectAddCallback */
|
||||
public native void AddEntity(int entity);
|
||||
|
||||
/** Removes an entity from selection. Does not call SelectAddCallback */
|
||||
public native void RemoveEntity(int entity);
|
||||
|
||||
public native void Cancel();
|
||||
|
||||
public native void End();
|
||||
}
|
||||
|
||||
|
||||
native void CancelSelector(int client);
|
||||
native bool IsSelectorActive(int client);
|
||||
|
||||
enum CompleteType {
|
||||
Complete_WallSuccess,
|
||||
Complete_WallError,
|
||||
Complete_PropSpawned,
|
||||
Complete_PropError,
|
||||
Complete_EditSuccess
|
||||
}
|
591
scripting/l4d2_editor.sp
Normal file
591
scripting/l4d2_editor.sp
Normal file
|
@ -0,0 +1,591 @@
|
|||
#pragma semicolon 1
|
||||
#pragma newdecls required
|
||||
|
||||
#define PLUGIN_VERSION "1.0"
|
||||
|
||||
#define DUMMY_MODEL "models/props/cs_office/vending_machine.mdl"
|
||||
|
||||
#include <sourcemod>
|
||||
#include <sdktools>
|
||||
#include <left4dhooks>
|
||||
#include <clientprefs>
|
||||
#include <jutils>
|
||||
#include <gamemodes/ents>
|
||||
#include <smlib/effects>
|
||||
#include <multicolors>
|
||||
#include <adminmenu>
|
||||
|
||||
int g_iLaserIndex;
|
||||
|
||||
float cmdThrottle[MAXPLAYERS+1];
|
||||
|
||||
TopMenu g_topMenu;
|
||||
|
||||
char g_currentMap[64];
|
||||
|
||||
//int g_markedMode
|
||||
|
||||
#include <editor/editor.sp>
|
||||
#include <editor/props/base.sp>
|
||||
#include <editor/natives.sp>
|
||||
#include <editor>
|
||||
|
||||
public Plugin myinfo = {
|
||||
name = "L4D2 Hats & Editor",
|
||||
author = "jackzmc",
|
||||
description = "",
|
||||
version = PLUGIN_VERSION,
|
||||
url = "https://github.com/Jackzmc/sourcemod-plugins"
|
||||
};
|
||||
|
||||
public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) {
|
||||
RegPluginLibrary("editor");
|
||||
// CreateNative("SpawnSchematic", Native_SpawnSchematic);
|
||||
CreateNative("StartEdit", Native_StartEdit);
|
||||
CreateNative("StartSpawner", Native_StartSpawner);
|
||||
CreateNative("CancelEdit", Native_CancelEdit);
|
||||
CreateNative("IsEditorActive", Native_IsEditorActive);
|
||||
|
||||
|
||||
CreateNative("StartSelector", Native_StartSelector);
|
||||
CreateNative("CancelSelector", Native_CancelSelector);
|
||||
CreateNative("IsSelectorActive", Native_IsSelectorActive);
|
||||
|
||||
CreateNative("EntitySelector.Start", Native_Selector_Start);
|
||||
CreateNative("EntitySelector.Count.get", Native_Selector_GetCount);
|
||||
CreateNative("EntitySelector.Active.get", Native_Selector_GetActive);
|
||||
CreateNative("EntitySelector.SetOnEnd", Native_Selector_SetOnEnd);
|
||||
CreateNative("EntitySelector.SetOnPreSelect", Native_Selector_SetOnPreSelect);
|
||||
CreateNative("EntitySelector.SetOnPostSelect", Native_Selector_SetOnPostSelect);
|
||||
CreateNative("EntitySelector.SetOnUnselect", Native_Selector_SetOnUnselect);
|
||||
CreateNative("EntitySelector.AddEntity", Native_Selector_AddEntity);
|
||||
CreateNative("EntitySelector.RemoveEntity", Native_Selector_RemoveEntity);
|
||||
CreateNative("EntitySelector.Cancel", Native_Selector_Cancel);
|
||||
CreateNative("EntitySelector.End", Native_Selector_End);
|
||||
return APLRes_Success;
|
||||
}
|
||||
|
||||
|
||||
public void OnPluginStart() {
|
||||
EngineVersion g_Game = GetEngineVersion();
|
||||
if(g_Game != Engine_Left4Dead && g_Game != Engine_Left4Dead2) {
|
||||
SetFailState("This plugin is for L4D/L4D2 only.");
|
||||
}
|
||||
|
||||
createdWalls = new ArrayList();
|
||||
g_spawnedItems = new ArrayList(2);
|
||||
ROOT_CATEGORY.name = "Categories";
|
||||
|
||||
LoadTranslations("common.phrases");
|
||||
HookEvent("player_spawn", Event_PlayerSpawn);
|
||||
|
||||
RegAdminCmd("sm_mkwall", Command_MakeWall, ADMFLAG_CUSTOM2);
|
||||
RegAdminCmd("sm_edit", Command_Editor, ADMFLAG_CUSTOM2);
|
||||
RegAdminCmd("sm_wall", Command_Editor, ADMFLAG_CUSTOM2);
|
||||
RegAdminCmd("sm_prop", Command_Props, ADMFLAG_CUSTOM2);
|
||||
|
||||
if(SQL_CheckConfig(DATABASE_CONFIG_NAME)) {
|
||||
if(!ConnectDB()) {
|
||||
LogError("Failed to connect to database.");
|
||||
}
|
||||
}
|
||||
|
||||
int entity = -1;
|
||||
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) {
|
||||
createdWalls.Push(EntIndexToEntRef(entity));
|
||||
SDKHook(entity, SDKHook_Use, OnWallClicked);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
for(int i = 1; i <= MaxClients; i++) {
|
||||
Editor[i].client = i;
|
||||
Editor[i].Reset(true);
|
||||
g_PropData[i].Init(i);
|
||||
}
|
||||
|
||||
TopMenu topMenu;
|
||||
if (LibraryExists("adminmenu") && ((topMenu = GetAdminTopMenu()) != null)) {
|
||||
OnAdminMenuReady(topMenu);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnLibraryRemoved(const char[] name) {
|
||||
if (StrEqual(name, "adminmenu", false)) {
|
||||
g_topMenu = null;
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Tries to find a valid location at user's cursor, avoiding placing into solid walls (such as invisible walls) or objects
|
||||
stock bool GetSmartCursorLocation(int client, float outPos[3]) {
|
||||
float start[3], angle[3], ceilPos[3], wallPos[3], normal[3];
|
||||
// Get the cursor location
|
||||
GetClientEyePosition(client, start);
|
||||
GetClientEyeAngles(client, angle);
|
||||
TR_TraceRayFilter(start, angle, MASK_SOLID, RayType_Infinite, Filter_NoPlayers, client);
|
||||
if(TR_DidHit()) {
|
||||
TR_GetEndPosition(outPos);
|
||||
// Check if the position is a wall
|
||||
TR_GetPlaneNormal(null, normal);
|
||||
if(normal[2] < 0.1) {
|
||||
|
||||
// Find a suitable position above
|
||||
start[0] = outPos[0];
|
||||
start[1] = outPos[1];
|
||||
start[2] = outPos[2] += 100.0;
|
||||
TR_TraceRayFilter(outPos, start, MASK_SOLID, RayType_EndPoint, TraceEntityFilterPlayer, client);
|
||||
bool ceilCollided = TR_DidHit();
|
||||
bool ceilOK = !TR_AllSolid();
|
||||
TR_GetEndPosition(ceilPos);
|
||||
// float distCeil = GetVectorDistance(outPos, ceilPos, true);
|
||||
|
||||
// Find a suitable position backwards
|
||||
angle[0] = 70.0;
|
||||
angle[1] += 180.0;
|
||||
TR_TraceRayFilter(outPos, angle, MASK_SOLID, RayType_Infinite, TraceEntityFilterPlayer, client);
|
||||
bool wallCollided = TR_DidHit();
|
||||
TR_GetEndPosition(wallPos);
|
||||
float distWall = GetVectorDistance(outPos, wallPos, true);
|
||||
|
||||
if(ceilCollided && wallCollided)
|
||||
|
||||
if(wallCollided && distWall < 62500) {
|
||||
outPos = wallPos;
|
||||
} else if(ceilOK) {
|
||||
outPos = ceilPos;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool g_inRotate[MAXPLAYERS+1];
|
||||
public Action OnPlayerRunCmd(int client, int& buttons, int& impulse, float vel[3], float angles[3], int& weapon, int& subtype, int& cmdnum, int& tickcount, int& seed, int mouse[2]) {
|
||||
float tick = GetGameTime();
|
||||
int oldButtons = GetEntProp(client, Prop_Data, "m_nOldButtons");
|
||||
if(g_pendingSaveClient == client) {
|
||||
if(g_PropData[client].pendingSaveType == Save_Schematic) {
|
||||
// move cursor? or should be editor anyway
|
||||
}
|
||||
} else if(g_PropData[client].Selector.IsActive()) {
|
||||
SetWeaponDelay(client, 0.5);
|
||||
if(tick - cmdThrottle[client] >= 0.20) {
|
||||
if(buttons & IN_ATTACK) {
|
||||
int entity = GetLookingEntity(client, Filter_ValidHats);
|
||||
if(entity > 0) {
|
||||
if(g_PropData[client].Selector.AddEntity(entity) != -1) {
|
||||
PrecacheSound("ui/beep07.wav");
|
||||
EmitSoundToClient(client, "ui/beep07.wav", entity, SND_CHANGEVOL, .volume = 0.5);
|
||||
}
|
||||
} else {
|
||||
PrintHintText(client, "No entity found");
|
||||
}
|
||||
} else if(buttons & IN_ATTACK2) {
|
||||
int entity = GetLookingEntity(client, Filter_ValidHats);
|
||||
if(entity > 0) {
|
||||
if(g_PropData[client].Selector.RemoveEntity(entity)) {
|
||||
PrecacheSound("ui/beep22.wav");
|
||||
EmitSoundToClient(client, "ui/beep22.wav", entity, SND_CHANGEVOL, .volume = 0.5);
|
||||
}
|
||||
}
|
||||
} else if(buttons & IN_USE) {
|
||||
if(buttons & IN_SPEED) {
|
||||
//Delete
|
||||
g_PropData[client].Selector.End();
|
||||
} else if(buttons & IN_DUCK) {
|
||||
//Cancel
|
||||
g_PropData[client].Selector.Cancel();
|
||||
}
|
||||
}
|
||||
cmdThrottle[client] = tick;
|
||||
}
|
||||
} else if(Editor[client].IsActive()) {
|
||||
// if(buttons & IN_USE && buttons & IN_RELOAD) {
|
||||
// ClientCommand(client, "sm_wall done");
|
||||
// return Plugin_Handled;
|
||||
// }
|
||||
bool allowMove = true;
|
||||
switch(Editor[client].mode) {
|
||||
case MOVE_ORIGIN: {
|
||||
SetWeaponDelay(client, 0.5);
|
||||
|
||||
bool isRotate;
|
||||
int flags = GetEntityFlags(client);
|
||||
if(buttons & IN_RELOAD) {
|
||||
if(!g_inRotate[client]) {
|
||||
g_inRotate[client] = true;
|
||||
}
|
||||
if(!(oldButtons & IN_JUMP) && (buttons & IN_JUMP)) {
|
||||
buttons &= ~IN_JUMP;
|
||||
Editor[client].CycleStacker();
|
||||
} else if(!(oldButtons & IN_SPEED) && (buttons & IN_SPEED)) {
|
||||
Editor[client].ToggleCollision();
|
||||
return Plugin_Handled;
|
||||
} else if(!(oldButtons & IN_DUCK) && (buttons & IN_DUCK)) {
|
||||
Editor[client].ToggleCollisionRotate();
|
||||
return Plugin_Handled;
|
||||
} else {
|
||||
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(!(oldButtons & IN_ATTACK) && (buttons & IN_ATTACK)) Editor[client].CycleAxis();
|
||||
else if(!(oldButtons & IN_ATTACK2) && (buttons & IN_ATTACK2)) Editor[client].CycleSnapAngle(tick);
|
||||
|
||||
// Rotation control:
|
||||
// Turn off rotate when player wants rotate
|
||||
Editor[client].hasCollisionRotate = false;
|
||||
if(tick - cmdThrottle[client] > 0.1) {
|
||||
if(Editor[client].axis == 0) {
|
||||
int mouseXAbs = IntAbs(mouse[0]);
|
||||
int mouseYAbs = IntAbs(mouse[1]);
|
||||
bool XOverY = mouseXAbs > mouseYAbs;
|
||||
if(mouseYAbs > 10 && !XOverY) {
|
||||
Editor[client].IncrementAxis(0, mouse[1]);
|
||||
} else if(mouseXAbs > 10 && XOverY) {
|
||||
Editor[client].IncrementAxis(1, mouse[0]);
|
||||
}
|
||||
}
|
||||
else if(Editor[client].axis == 1) {
|
||||
if(mouse[0] > 10) Editor[client].angles[2] += Editor[client].snapAngle;
|
||||
else if(mouse[0] < -10) Editor[client].angles[2] -= Editor[client].snapAngle;
|
||||
}
|
||||
cmdThrottle[client] = tick;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if(g_inRotate[client]) {
|
||||
g_inRotate[client] = false;
|
||||
}
|
||||
// Move position
|
||||
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
|
||||
if(!isRotate && flags & FL_FROZEN) {
|
||||
flags = flags & ~FL_FROZEN;
|
||||
SetEntityFlags(client, flags);
|
||||
}
|
||||
if(Editor[client].stackerDirection == Stack_Off)
|
||||
CalculateEditorPosition(client, Filter_IgnorePlayerAndWall);
|
||||
}
|
||||
case SCALE: {
|
||||
SetWeaponDelay(client, 0.5);
|
||||
allowMove = false;
|
||||
if(buttons & IN_USE) {
|
||||
Editor[client].CycleSpeed(tick);
|
||||
} else {
|
||||
if(buttons & IN_MOVELEFT) {
|
||||
Editor[client].IncrementSize(0, -1.0);
|
||||
} else if(buttons & IN_MOVERIGHT) {
|
||||
Editor[client].IncrementSize(0, 1.0);
|
||||
Editor[client].size[0] += Editor[client].moveSpeed;
|
||||
}
|
||||
if(buttons & IN_FORWARD) {
|
||||
Editor[client].IncrementSize(0, 1.0);
|
||||
} else if(buttons & IN_BACK) {
|
||||
Editor[client].IncrementSize(0, -1.0);
|
||||
}
|
||||
if(buttons & IN_JUMP) {
|
||||
Editor[client].IncrementSize(0, 1.0);
|
||||
} else if(buttons & IN_DUCK) {
|
||||
Editor[client].IncrementSize(0, -1.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
case COLOR: {
|
||||
SetWeaponDelay(client, 0.5);
|
||||
PrintHintText(client, "%d %d %d %d", Editor[client].color[0], Editor[client].color[1], Editor[client].color[2], Editor[client].color[3]);
|
||||
if(buttons & IN_USE) {
|
||||
Editor[client].CycleColorComponent(tick);
|
||||
} else if(buttons & IN_ATTACK2) {
|
||||
Editor[client].IncreaseColor(1);
|
||||
allowMove = false;
|
||||
} else if(buttons & IN_ATTACK) {
|
||||
Editor[client].IncreaseColor(-1);
|
||||
allowMove = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(buttons & IN_DUCK) {
|
||||
|
||||
}
|
||||
if(Editor[client].mode != COLOR && !(oldButtons & IN_USE) && buttons & IN_USE) {
|
||||
if(buttons & IN_SPEED) {
|
||||
Editor[client].Cancel();
|
||||
} else if(buttons & IN_DUCK) {
|
||||
Editor[client].CycleBuildType();
|
||||
// Editor[client].ShowExtraOptions();
|
||||
} else {
|
||||
int entity;
|
||||
Editor[client].Done(entity);
|
||||
}
|
||||
|
||||
} else if(!(oldButtons & IN_ZOOM) && buttons & IN_ZOOM) {
|
||||
Editor[client].CycleMode(); // ZOOM: Cycle forward
|
||||
}
|
||||
|
||||
Editor[client].Draw(BUILDER_COLOR, 0.1, 0.1);
|
||||
return allowMove ? Plugin_Continue : Plugin_Handled;
|
||||
}
|
||||
|
||||
return Plugin_Continue;
|
||||
}
|
||||
|
||||
int IntAbs(int a) {
|
||||
if(a < 0) {
|
||||
return a * -1;
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
void Event_PlayerSpawn(Event event, const char[] name, bool dontBroadcast) {
|
||||
int client = GetClientOfUserId(event.GetInt("userid"));
|
||||
if(client > 0) {
|
||||
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) {
|
||||
Editor[client].Reset();
|
||||
g_PropData[client].Reset();
|
||||
if(g_pendingSaveClient == client) {
|
||||
g_pendingSaveClient = 0;
|
||||
ClearSavePreview();
|
||||
}
|
||||
}
|
||||
|
||||
public void OnMapStart() {
|
||||
PrecacheModel(DUMMY_MODEL);
|
||||
g_iLaserIndex = PrecacheModel("materials/sprites/laserbeam.vmt");
|
||||
for(int i = 1; i <= MaxClients; i++) {
|
||||
cmdThrottle[i] = 0.0;
|
||||
}
|
||||
GetCurrentMap(g_currentMap, sizeof(g_currentMap));
|
||||
}
|
||||
|
||||
|
||||
public void OnMapEnd() {
|
||||
g_spawnedItems.Clear();
|
||||
for(int i = 1; i <= createdWalls.Length; i++) {
|
||||
DeleteWall(i);
|
||||
}
|
||||
createdWalls.Clear();
|
||||
UnloadCategories();
|
||||
UnloadSave();
|
||||
SaveRecents();
|
||||
}
|
||||
public void OnPluginEnd() {
|
||||
for(int i = 1; i <= MaxClients; i++) {
|
||||
if(IsClientConnected(i) && IsClientInGame(i)) {
|
||||
int flags = GetEntityFlags(i) & ~FL_FROZEN;
|
||||
SetEntityFlags(i, flags);
|
||||
}
|
||||
}
|
||||
if(g_spawnedItems != null) {
|
||||
delete g_spawnedItems;
|
||||
}
|
||||
TriggerInput("prop_preview", "Kill");
|
||||
}
|
||||
|
||||
public bool TraceEntityFilterPlayer(int entity, int contentsMask, any data) {
|
||||
return entity != data;
|
||||
}
|
||||
|
||||
int GetLookingEntity(int client, TraceEntityFilter filter) {
|
||||
static float pos[3], ang[3];
|
||||
GetClientEyePosition(client, pos);
|
||||
GetClientEyeAngles(client, ang);
|
||||
TR_TraceRayFilter(pos, ang, MASK_SOLID, RayType_Infinite, filter, client);
|
||||
if(TR_DidHit()) {
|
||||
return TR_GetEntityIndex();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
stock bool Filter_OnlyPlayers(int entity, int mask, int data) {
|
||||
return entity > 0 && entity <= MaxClients && entity != data;
|
||||
}
|
||||
|
||||
stock bool Filter_NoPlayers(int entity, int mask, int data) {
|
||||
return entity > MaxClients && entity != data;
|
||||
}
|
||||
|
||||
stock bool Filter_IgnorePlayerAndWall(int entity, int mask, int data) {
|
||||
if(entity > MaxClients && entity != data && EntRefToEntIndex(Editor[data].entity) != entity) {
|
||||
static char classname[16];
|
||||
GetEntityClassname(entity, classname, sizeof(classname));
|
||||
// Ignore infected
|
||||
return !StrEqual(classname, "infected");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool Filter_ValidHats(int entity, int mask, int data) {
|
||||
if(entity == data) return false;
|
||||
if(entity <= MaxClients && entity > 0) {
|
||||
return true;
|
||||
}
|
||||
return CheckBlacklist(entity);
|
||||
}
|
||||
|
||||
|
||||
#define MAX_FORBIDDEN_CLASSNAMES 10
|
||||
static char FORBIDDEN_CLASSNAMES[MAX_FORBIDDEN_CLASSNAMES][] = {
|
||||
// "env_physics_blocker",
|
||||
// "env_player_blocker",
|
||||
"func_brush",
|
||||
"func_simpleladder",
|
||||
"func_button",
|
||||
"func_elevator",
|
||||
"func_button_timed",
|
||||
"func_movelinear",
|
||||
"func_tracktrain",
|
||||
// "infected",
|
||||
"func_lod",
|
||||
"prop_ragdoll",
|
||||
"move_rope"
|
||||
};
|
||||
|
||||
#define MAX_FORBIDDEN_MODELS 2
|
||||
char FORBIDDEN_MODELS[MAX_FORBIDDEN_MODELS][] = {
|
||||
"models/props_vehicles/c130.mdl",
|
||||
"models/props_vehicles/helicopter_rescue.mdl"
|
||||
};
|
||||
|
||||
bool CheckBlacklist(int entity) {
|
||||
if(entity == 0) return false;
|
||||
static char buffer[64];
|
||||
GetEntityClassname(entity, buffer, sizeof(buffer));
|
||||
for(int i = 0; i < MAX_FORBIDDEN_CLASSNAMES; i++) {
|
||||
if(StrEqual(FORBIDDEN_CLASSNAMES[i], buffer)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if(StrContains(buffer, "prop_") > -1) {
|
||||
GetEntPropString(entity, Prop_Data, "m_ModelName", buffer, sizeof(buffer));
|
||||
for(int i = 0; i < MAX_FORBIDDEN_MODELS; i++) {
|
||||
if(StrEqual(FORBIDDEN_MODELS[i], buffer)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
GetEntPropString(entity, Prop_Data, "m_iName", buffer, sizeof(buffer));
|
||||
if(StrEqual(buffer, "l4d2_randomizer")) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
|
||||
stock void TriggerInput(const char[] targetName, const char[] input) {
|
||||
int entity = -1;
|
||||
char _targetName[32];
|
||||
while((entity = FindEntityByClassname(entity, "*")) != INVALID_ENT_REFERENCE) {
|
||||
GetEntPropString(entity, Prop_Data, "m_iName", _targetName, sizeof(_targetName));
|
||||
if(StrEqual(_targetName, targetName)) {
|
||||
AcceptEntityInput(entity, input);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
stock bool FindGround(const float start[3], float end[3]) {
|
||||
float angle[3];
|
||||
angle[0] = 90.0;
|
||||
|
||||
Handle trace = TR_TraceRayEx(start, angle, MASK_SHOT, RayType_Infinite);
|
||||
if(!TR_DidHit(trace)) {
|
||||
delete trace;
|
||||
return false;
|
||||
}
|
||||
TR_GetEndPosition(end, trace);
|
||||
delete trace;
|
||||
return true;
|
||||
}
|
||||
|
||||
stock bool L4D_IsPlayerCapped(int client) {
|
||||
if(GetEntPropEnt(client, Prop_Send, "m_pummelAttacker") > 0 ||
|
||||
GetEntPropEnt(client, Prop_Send, "m_carryAttacker") > 0 ||
|
||||
GetEntPropEnt(client, Prop_Send, "m_pounceAttacker") > 0 ||
|
||||
GetEntPropEnt(client, Prop_Send, "m_jockeyAttacker") > 0 ||
|
||||
GetEntPropEnt(client, Prop_Send, "m_pounceAttacker") > 0 ||
|
||||
GetEntPropEnt(client, Prop_Send, "m_tongueOwner") > 0)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
stock void LookAtPoint(int entity, const float destination[3]){
|
||||
float angles[3], pos[3], result[3];
|
||||
GetEntPropVector(entity, Prop_Send, "m_vecOrigin", pos);
|
||||
MakeVectorFromPoints(destination, pos, result);
|
||||
GetVectorAngles(result, angles);
|
||||
if(angles[0] >= 270){
|
||||
angles[0] -= 270;
|
||||
angles[0] = (90-angles[0]);
|
||||
} else {
|
||||
if(angles[0] <= 90){
|
||||
angles[0] *= -1;
|
||||
}
|
||||
}
|
||||
angles[1] -= 180;
|
||||
TeleportEntity(entity, NULL_VECTOR, angles, NULL_VECTOR);
|
||||
}
|
||||
|
||||
stock float SnapTo(const float value, const float degree) {
|
||||
return float(RoundFloat(value / degree)) * degree;
|
||||
}
|
||||
|
||||
stock bool CalculateEditorPosition(int client, TraceEntityFilter filter) {
|
||||
if (client > 0 && client <= MaxClients && IsClientInGame(client)) {
|
||||
float clientEye[3], clientAngle[3], direction[3];
|
||||
GetClientEyePosition(client, clientEye);
|
||||
GetClientEyeAngles(client, clientAngle);
|
||||
|
||||
GetAngleVectors(clientAngle, direction, NULL_VECTOR, NULL_VECTOR);
|
||||
ScaleVector(direction, Editor[client].moveDistance);
|
||||
AddVectors(clientEye, direction, Editor[client].origin);
|
||||
|
||||
if(Editor[client].hasCollision) {
|
||||
TR_TraceRayFilter(clientEye, Editor[client].origin, MASK_OPAQUE, RayType_EndPoint, filter, client);
|
||||
if (TR_DidHit(INVALID_HANDLE)) {
|
||||
TR_GetEndPosition(Editor[client].origin);
|
||||
GetEntPropVector(Editor[client].entity, Prop_Send, "m_vecMins", direction);
|
||||
Editor[client].origin[2] -= direction[2];
|
||||
if(Editor[client].hasCollisionRotate) {
|
||||
TR_GetPlaneNormal(INVALID_HANDLE, Editor[client].angles);
|
||||
GetVectorAngles(Editor[client].angles, Editor[client].angles);
|
||||
Editor[client].angles[0] += 90.0; //need to rotate for some reason
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue