diff --git a/plugins/l4d2_editor.smx b/plugins/l4d2_editor.smx new file mode 100644 index 0000000..41841d6 Binary files /dev/null and b/plugins/l4d2_editor.smx differ diff --git a/plugins/l4d2_hats.smx b/plugins/l4d2_hats.smx index a5e62f6..298fbf3 100644 Binary files a/plugins/l4d2_hats.smx and b/plugins/l4d2_hats.smx differ diff --git a/plugins/l4d2_randomizer.smx b/plugins/l4d2_randomizer.smx index 11fb734..edebd1d 100644 Binary files a/plugins/l4d2_randomizer.smx and b/plugins/l4d2_randomizer.smx differ diff --git a/scripting/include/hats/editor.sp b/scripting/include/hats/editor.sp deleted file mode 100644 index 783c767..0000000 --- a/scripting/include/hats/editor.sp +++ /dev/null @@ -1,1024 +0,0 @@ -int BUILDER_COLOR[4] = { 0, 255, 0, 235 }; -int GLOW_BLUE[4] = { 3, 148, 252 }; -int GLOW_RED_ALPHA[4] = { 255, 0, 0, 235 }; -int GLOW_WHITE[4] = { 255, 255, 255, 255 }; -int GLOW_GREEN[4] = { 3, 252, 53 }; -float ORIGIN_SIZE[3] = { 2.0, 2.0, 2.0 }; - -char ON_OFF_STRING[2][] = { - "\x05OFF\x01", - "\x05ON\x01" -} -char COLOR_INDEX[4] = "RGBA"; - -enum editMode { - INACTIVE = 0, - MOVE_ORIGIN, - SCALE, - COLOR, - FREELOOK, -} -char MODE_NAME[5][] = { - "Error", - "Move & Rotate", - "Scale", - "Color", - "Freelook" -} - -enum { - Edit_None, - Edit_Copy = 1, - Edit_Preview = 2, - Edit_WallCreator = 4, - Edit_Manager = 8 -} - -enum buildType { - Build_Solid, - Build_Physics, - Build_NonSolid, - // TODO: Build_Weapon (spawn as weapon?) -} - - -enum StackerDirection { - Stack_Off, - Stack_Left, - Stack_Right, - Stack_Forward, - Stack_Backward, - Stack_Up, - Stack_Down -} - -char STACK_DIRECTION_NAME[7][] = { - "\x05OFF", - "\x04Left", - "\x04Right", - "\x04Forward", - "\x04Backward", - "\x04Up", - "\x04Down", -} - -ArrayList createdWalls; - -enum struct EditorData { - int client; - char classname[64]; - char data[32]; - char name[32]; - - float origin[3]; - float angles[3]; - float prevOrigin[3]; // for cancelling edits - float prevAngles[3]; - - float mins[3]; - float size[3]; - - int color[4]; - int colorIndex; - int axis; - int snapAngle; - float rotateSpeed; - int moveSpeed; - float moveDistance; - int entity; - bool hasCollision; /// possibly merge into .flags - bool hasCollisionRotate; //^ - StackerDirection stackerDirection; - - editMode mode; - buildType buildType; - int flags; - - PrivateForward callback; - bool isEditCallback; - - void Reset(bool initial = false) { - // Clear preview entity - if(this.entity != INVALID_ENT_REFERENCE && (this.flags & Edit_Preview) && IsValidEntity(this.entity)) { - RemoveEntity(this.entity); - } - this.stackerDirection = Stack_Off; - this.entity = INVALID_ENT_REFERENCE; - this.data[0] = '\0'; - this.name[0] = '\0'; - this.size[0] = this.size[1] = this.size[2] = 5.0; - this.angles[0] = this.angles[1] = this.angles[2] = 0.0; - this.colorIndex = 0; - this.axis = 0; - this.moveDistance = 200.0; - this.flags = Edit_None; - this.classname[0] = '\0'; - this.CalculateMins(); - this.SetMode(INACTIVE); - this.rotateSpeed = 0.1; - // Settings that don't get reset on new spawns: - if(initial) { - this.color[0] = this.color[1] = this.color[2] = this.color[3] = 255; - this.moveSpeed = 1; - this.snapAngle = 30; - this.hasCollision = true; - this.hasCollisionRotate = false; - this.buildType = Build_Solid; - } - } - - void CalculateMins() { - this.mins[0] = -this.size[0]; - this.mins[1] = -this.size[1]; - this.mins[2] = -this.size[2]; - } - - void Draw(int color[4], float lifetime, float amplitude = 0.1) { - if(this.flags & Edit_WallCreator || this.entity == INVALID_ENT_REFERENCE) { - Effect_DrawBeamBoxRotatableToAll(this.origin, this.mins, this.size, this.angles, g_iLaserIndex, 0, 0, 30, lifetime, 0.4, 0.4, 0, amplitude, color, 0); - } else { - if(this.snapAngle != 1) { - this.angles[0] = RoundToNearestInterval(this.angles[0], this.snapAngle); - this.angles[1] = RoundToNearestInterval(this.angles[1], this.snapAngle); - this.angles[2] = RoundToNearestInterval(this.angles[2], this.snapAngle); - - } - TeleportEntity(this.entity, this.origin, this.angles, NULL_VECTOR); - } - Effect_DrawAxisOfRotationToAll(this.origin, this.angles, ORIGIN_SIZE, g_iLaserIndex, 0, 0, 30, 0.2, 0.1, 0.1, 0, 0.0, 0); - } - - // Updates the entity with certain changed settings - void UpdateEntity() { - int alpha = this.color[3]; - // Keep previews transparent - SetEntityRenderColor(this.entity, this.color[0], this.color[1], this.color[2], alpha); - } - - bool CheckEntity() { - if(this.entity != INVALID_ENT_REFERENCE) { - if(this.entity == -1 && !IsValidEntity(this.entity)) { - PrintToChat(this.client, "\x04[Editor]\x01 Entity has vanished, editing cancelled."); - this.Reset(); - return false; - } - } - return true; - } - - bool IsActive() { - return this.mode != INACTIVE && this.CheckEntity(); - } - - void SetMode(editMode mode) { - this.mode = mode; - } - - void SetData(const char[] data) { - strcopy(this.data, sizeof(this.data), data); - } - void SetName(const char[] name) { - strcopy(this.name, sizeof(this.name), name); - } - void SetCallback(PrivateForward callback, bool isEditCallback) { - this.callback = callback; - this.isEditCallback = isEditCallback; - } - - void CycleMode() { - // Remove frozen state when cycling - int flags = GetEntityFlags(this.client) & ~FL_FROZEN; - SetEntityFlags(this.client, flags); - switch(this.mode) { - // MODES: - // - MOVE & ROTAT - // - SCALE or COLOR - // - FREELOOK - case MOVE_ORIGIN: { - if(this.flags & Edit_WallCreator) { - this.mode = SCALE; - } else if(this.flags & Edit_Preview) { - this.mode = COLOR; - } else { - this.mode = FREELOOK; - } - } - case SCALE: { - this.mode = FREELOOK; - } - case COLOR: { - this.mode = FREELOOK; - } - case FREELOOK: { - this.mode = MOVE_ORIGIN; - } - } - PrintToChat(this.client, "\x04[Editor]\x01 Mode: \x05%s\x01 (Press \x04RELOAD\x01 to change)", MODE_NAME[this.mode]); - } - - void CycleStacker() { - int newDirection = view_as(this.stackerDirection) + 1; - if(newDirection == view_as(Stack_Down)) newDirection = 0; - this.stackerDirection = view_as(newDirection); - - PrintToChat(this.client, "\x04[Editor]\x01 Stacker: %s\x01", STACK_DIRECTION_NAME[this.stackerDirection]); - } - - void ToggleCollision() { - this.hasCollision = !this.hasCollision - PrintToChat(this.client, "\x04[Editor]\x01 Collision: %s", ON_OFF_STRING[view_as(this.hasCollision)]); - } - - void ToggleCollisionRotate() { - this.hasCollisionRotate = !this.hasCollisionRotate - PrintToChat(this.client, "\x04[Editor]\x01 Rotate with Collision: %s", ON_OFF_STRING[view_as(this.hasCollisionRotate)]); - } - - void CycleAxis() { - // if(tick - cmdThrottle[this.client] <= 0.1) return; - if(this.axis == 0) { - this.axis = 1; - PrintToChat(this.client, "\x04[Editor]\x01 Rotate Axis: \x05ROLL (Z)\x01"); - } else { - this.axis = 0; - PrintToChat(this.client, "\x04[Editor]\x01 Rotate Axis: \x05PITCH AND HEADING (X, Y)\x01"); - } - // cmdThrottle[this.client] = tick; - } - - void IncrementAxis(int axis, int mouse) { - if(this.snapAngle == 1) { - this.angles[axis] += mouse * this.rotateSpeed; - } else { - if(mouse > 0) this.angles[axis] += this.snapAngle; - else if(mouse < 0) this.angles[axis] -= this.snapAngle; - } - } - - void CycleSnapAngle(float tick) { - if(tick - cmdThrottle[this.client] <= 0.1) return; - switch(this.snapAngle) { - case 1: this.snapAngle = 15; - case 15: this.snapAngle = 30; - case 30: this.snapAngle = 45; - case 45: this.snapAngle = 90; - case 90: this.snapAngle = 1; - } - - // this.angles[0] = SnapTo(this.angles[0], float(this.snapAngle)); - // this.angles[1] = SnapTo(this.angles[1], float(this.snapAngle)); - // this.angles[2] = SnapTo(this.angles[2], float(this.snapAngle)); - - if(this.snapAngle == 1) - PrintToChat(this.client, "\x04[Editor]\x01 Rotate Snap Degrees: \x04(OFF)\x01", this.snapAngle); - else - PrintToChat(this.client, "\x04[Editor]\x01 Rotate Snap Degrees: \x05%d\x01", this.snapAngle); - cmdThrottle[this.client] = tick; - } - - void CycleSpeed(float tick) { - if(tick - cmdThrottle[this.client] <= 0.25) return; - this.moveSpeed++; - if(this.moveSpeed > 10) this.moveSpeed = 1; - PrintToChat(this.client, "\x04[Editor]\x01 Scale Speed: \x05%d\x01", this.moveSpeed); - cmdThrottle[this.client] = tick; - } - - void CycleBuildType() { - // No tick needed, is handled externally - if(this.classname[0] != '\0') { - PrintToChat(this.client, "\x04[Editor]\x01 Spawn as: \x05%s\x01 (fixed)", this.classname); - } else if(this.buildType == Build_Physics) { - this.buildType = Build_Solid; - PrintToChat(this.client, "\x04[Editor]\x01 Spawn as: \x05Solid\x01"); - } else if(this.buildType == Build_Solid) { - this.buildType = Build_Physics; - PrintToChat(this.client, "\x04[Editor]\x01 Spawn as: \x05Physics\x01"); - } else { - this.buildType = Build_NonSolid; - PrintToChat(this.client, "\x04[Editor]\x01 Spawn as: \x05Non Solid\x01"); - } - } - - void CycleColorComponent(float tick) { - if(tick - cmdThrottle[this.client] <= 0.25) return; - this.colorIndex++; - if(this.colorIndex > 3) this.colorIndex = 0; - char component[16]; - for(int i = 0; i < 4; i++) { - if(this.colorIndex == i) - Format(component, sizeof(component), "%s \x05%c\x01", component, COLOR_INDEX[i]); - else - Format(component, sizeof(component), "%s %c", component, COLOR_INDEX[i]); - } - PrintToChat(this.client, "\x04[Editor]\x01 Color: %s", component); - cmdThrottle[this.client] = tick; - } - - void IncrementSize(int axis, float amount) { - this.size[axis] += amount; - if(this.size[axis] < 0.0) { - this.size[axis] = 0.0; - } - this.CalculateMins(); - } - - void IncreaseColor(int amount) { - int newValue = this.color[this.colorIndex] + amount; - if(newValue > 255) newValue = 255; - else if(newValue < 0) newValue = 0; - this.color[this.colorIndex] = newValue; - this.UpdateEntity(); - PrintCenterText(this.client, "%d %d %d %d", this.color[0], this.color[1], this.color[2], this.color[3]); - } - - // Complete the edit, wall creation, or spawning - CompleteType Done(int& entity) { - CompleteType type; - if(this.flags & Edit_WallCreator) { - type = this._FinishWall(entity) ? Complete_WallSuccess : Complete_WallError; - } else if(this.flags & Edit_Preview) { - type = this._FinishPreview(entity) ? Complete_PropSpawned : Complete_PropError; - } else { - // Is edit, do nothing, just reset - PrintHintText(this.client, "Edit Complete"); - this.Reset(); - entity = 0; - - type = Complete_EditSuccess; - } - if(this.callback) { - Call_StartForward(this.callback); - Call_PushCell(this.client); - Call_PushCell(entity); - Call_PushCell(type); - bool result; - Call_Finish(result); - // Cancel menu: - if(this.isEditCallback) delete this.callback; - if(this.isEditCallback || !result) { - // No native way to close a menu, so open a dummy menu and close it: - // Handler doesn't matter, no options are added - Menu menu = new Menu(Spawn_RootHandler); - menu.Display(this.client, 1); - } else { - delete this.callback; - } - } - return type; - } - - bool _FinishWall(int& id) { - if(~this.flags & Edit_WallCreator) { - this.Reset(); - return false; - } - // Don't need to build a new one if we editing: - int blocker = this.entity; - bool isEdit = false; - if(blocker != INVALID_ENT_REFERENCE) { - RemoveEntity(this.entity); - isEdit = true; - } - blocker = CreateEntityByName("func_brush"); - if(blocker == -1) return false; - DispatchKeyValueVector(blocker, "mins", this.mins); - DispatchKeyValueVector(blocker, "maxs", this.size); - DispatchKeyValueVector(blocker, "boxmins", this.mins); - DispatchKeyValueVector(blocker, "boxmaxs", this.size); - DispatchKeyValue(blocker, "excludednpc", "player"); - - DispatchKeyValueVector(blocker, "angles", this.angles); - DispatchKeyValue(blocker, "model", DUMMY_MODEL); - DispatchKeyValue(blocker, "intialstate", "1"); - // DispatchKeyValueVector(blocker, "angles", this.angles); - DispatchKeyValue(blocker, "BlockType", "4"); - char name[32]; - Format(name, sizeof(name), "l4d2_hats_%d", createdWalls.Length); - DispatchKeyValue(blocker, "targetname", name); - // DispatchKeyValue(blocker, "excludednpc", "player"); - TeleportEntity(blocker, this.origin, this.angles, NULL_VECTOR); - if(!DispatchSpawn(blocker)) return false; - SetEntPropVector(blocker, Prop_Send, "m_vecMins", this.mins); - SetEntPropVector(blocker, Prop_Send, "m_vecMaxs", this.size); - SetEntProp(blocker, Prop_Send, "m_nSolidType", 2); - int enteffects = GetEntProp(blocker, Prop_Send, "m_fEffects"); - enteffects |= 32; //EF_NODRAW - SetEntProp(blocker, Prop_Send, "m_fEffects", enteffects); - AcceptEntityInput(blocker, "Enable"); - SDKHook(blocker, SDKHook_Use, OnWallClicked); - - this.Draw(GLOW_GREEN, 5.0, 1.0); - this.Reset(); - if(!isEdit) { - id = createdWalls.Push(EntIndexToEntRef(blocker)); - PrintToChat(this.client, "\x04[Editor]\x01 Created wall \x05#%d\x01.", id); - } - return true; - } - - bool _FinishPreview(int& entity) { - if(StrContains(this.classname, "weapon") > -1) { - entity = this._CreateWeapon(); - } else { - entity = this._CreateProp(); - } - - DispatchKeyValue(entity, "targetname", "propspawner_prop"); - TeleportEntity(entity, this.origin, this.angles, NULL_VECTOR); - if(!DispatchSpawn(entity)) { - return false; - } - SetEntityRenderColor(entity, this.color[0], this.color[1], this.color[2], this.color[3]); - SetEntityRenderColor(this.entity, 255, 128, 255, 200); // reset ghost color - GlowEntity(entity, 1.1); - - // Confusing when we resume into freelook, so reset - if(this.mode == FREELOOK) - this.SetMode(MOVE_ORIGIN); - - // Add to spawn list and add to recent list - AddSpawnedItem(entity, this.client); - char model[128]; - GetEntPropString(entity, Prop_Data, "m_ModelName", model, sizeof(model)); - AddRecent(model, this.name); - - // Get the new position for preview with regards to this.stackerDirection - if(this.stackerDirection != Stack_Off) { - float size[3]; - GetEntityDimensions(this.entity, size); - float sign = 1.0; - if(this.stackerDirection == Stack_Left || this.stackerDirection == Stack_Right) { - if(this.stackerDirection == Stack_Left) sign = -1.0; - GetSidePositionFromOrigin(this.origin, this.angles, sign * size[1] * 0.90, this.origin); - } else if(this.stackerDirection == Stack_Forward || this.stackerDirection == Stack_Backward) { - if(this.stackerDirection == Stack_Backward) sign = -1.0; - GetHorizontalPositionFromOrigin(this.origin, this.angles, sign * size[0] * 0.90, this.origin); - } else { - if(this.stackerDirection == Stack_Down) sign = -1.0; - this.origin[2] += (size[2] * sign); - } - } - PrintHintText(this.client, "%s\n%s", this.classname, this.data); - // PrintToChat(this.client, "\x04[Editor]\x01 Editing copy \x05%d\x01 of entity \x05%d\x01. End with \x05/edit done\x01 or \x04/edit cancel\x01", entity, oldEntity); - // Don't kill preview until cancel - return true; - } - - int _CreateWeapon() { - int entity = -1; - entity = CreateEntityByName(this.classname); - if(entity == -1) return -1; - if(StrEqual(this.classname, "weapon_melee_spawn")) { - DispatchKeyValue(entity, "melee_weapon", this.data); - } - DispatchKeyValue(entity, "count", "1"); - DispatchKeyValue(entity, "spawnflags", "10"); - return entity; - } - - int _CreateProp() { - int entity = -1; - if(this.classname[0] != '\0') { - entity = CreateEntityByName(this.classname); - } else if(this.buildType == Build_Physics) - entity = CreateEntityByName("prop_physics"); - else - entity = CreateEntityByName("prop_dynamic_override"); - if(entity == -1) return false; - - char model[128]; - GetEntPropString(this.entity, Prop_Data, "m_ModelName", model, sizeof(model)); - DispatchKeyValue(entity, "model", model); - if(this.buildType == Build_NonSolid) - DispatchKeyValue(entity, "solid", "0"); - else - DispatchKeyValue(entity, "solid", "6"); - return entity; - } - - // Turns current entity into a copy (not for walls) - int Copy() { - if(this.entity == INVALID_ENT_REFERENCE) return -1; - char classname[64]; - GetEntityClassname(this.entity, classname, sizeof(classname)); - - int entity = CreateEntityByName(classname); - if(entity == -1) return -1; - GetEntPropString(this.entity, Prop_Data, "m_ModelName", classname, sizeof(classname)); - DispatchKeyValue(entity, "model", classname); - - - Format(classname, sizeof(classname), "hats_copy_%d", this.entity); - DispatchKeyValue(entity, "targetname", classname); - - DispatchKeyValue(entity, "solid", "6"); - - DispatchSpawn(entity); - if(StrEqual(this.classname, "prop_wall_breakable")) { - DispatchKeyValue(entity, "classname", "prop_door_rotating"); - } - TeleportEntity(entity, this.origin, this.angles, NULL_VECTOR); - this.entity = entity; - this.flags |= Edit_Copy; - return entity; - } - - // Start editing a new wall entity - void StartWall() { - this.Reset(); - this.flags |= Edit_WallCreator; - } - - bool PreviewWeapon(const char[] classname, const char[] data) { - int entity; - // Melee weapons don't have weapon_ prefix - this.Reset(); - // Rotate on it's side: - this.angles[2] = 90.0; - if(StrEqual(classname, "weapon_melee_spawn")) { - // no weapon_ prefix, its a melee - entity = CreateEntityByName(classname); - if(entity == -1) return false; - DispatchKeyValue(entity, "melee_weapon", data); - this.SetData(data); - strcopy(this.classname, sizeof(this.classname), classname); - } else { - entity = CreateEntityByName(data); - if(entity == -1) return false; - strcopy(this.classname, sizeof(this.classname), data); - } - DispatchKeyValue(entity, "count", "1"); - DispatchKeyValue(entity, "spawnflags", "10"); - DispatchKeyValue(entity, "targetname", "prop_preview"); - DispatchKeyValue(entity, "rendercolor", "255 128 255"); - DispatchKeyValue(entity, "renderamt", "200"); - DispatchKeyValue(entity, "rendermode", "1"); - TeleportEntity(entity, this.origin, NULL_VECTOR, NULL_VECTOR); // MUST teleport before spawn or it crashes - if(!DispatchSpawn(entity)) { - PrintToServer("Failed to spawn"); - return false; - } - this.entity = entity; - this.flags |= (Edit_Copy | Edit_Preview); - this.SetMode(MOVE_ORIGIN); - // Seems some entities fail here: - return IsValidEntity(entity); - } - - bool PreviewModel(const char[] model, const char[] classname = "") { - // Check for an invalid model - // this.origin is not cleared by this.Reset(); - this.Reset(); - GetClientAbsOrigin(this.client, this.origin); - if(StrEqual(classname, "_weapon") || StrEqual(classname, "weapon_melee_spawn")) { - // Pass in melee ID as data: - return this.PreviewWeapon(classname, model); - } - if(PrecacheModel(model) == 0) { - PrintToServer("Invalid model: %s", model); - return false; - } - this.Reset(); - int entity = CreateEntityByName("prop_door_rotating"); - if(classname[0] == '\0') { - entity = CreateEntityByName("prop_dynamic_override"); - } else { - strcopy(this.classname, sizeof(this.classname), classname); - entity = CreateEntityByName(classname); - } - if(entity == -1) { - PrintToServer("Invalid classname: %s", classname); - return false; - } - DispatchKeyValue(entity, "model", model); - DispatchKeyValue(entity, "targetname", "prop_preview"); - DispatchKeyValue(entity, "solid", "0"); - DispatchKeyValue(entity, "rendercolor", "255 128 255"); - DispatchKeyValue(entity, "renderamt", "255"); - DispatchKeyValue(entity, "rendermode", "1"); - TeleportEntity(entity, this.origin, NULL_VECTOR, NULL_VECTOR); - if(!DispatchSpawn(entity)) { - PrintToServer("Failed to spawn"); - return false; - } - this.entity = entity; - this.flags |= (Edit_Copy | Edit_Preview); - this.SetMode(MOVE_ORIGIN); - // Seems some entities fail here: - return IsValidEntity(entity); - } - - /** - * Adds an existing entity to the editor, to move it. - * asWallCopy: to instead copy the wall's size and position (walls only) - * @deprecated - */ - void Import(int entity, bool asWallCopy = false, editMode mode = SCALE) { - this.Reset(); - GetEntPropVector(entity, Prop_Send, "m_vecOrigin", this.origin); - GetEntPropVector(entity, Prop_Send, "m_angRotation", this.angles); - this.prevOrigin = this.origin; - this.prevAngles = this.angles; - GetEntPropVector(entity, Prop_Send, "m_vecMins", this.mins); - GetEntPropVector(entity, Prop_Send, "m_vecMaxs", this.size); - if(!asWallCopy) { - this.entity = entity; - } - this.SetMode(mode); - } - - /** - * Imports an entity - */ - void ImportEntity(int entity, int flags = 0, editMode mode = SCALE) { - this.Reset(); - this.flags = flags; - GetEntPropVector(entity, Prop_Send, "m_vecOrigin", this.origin); - GetEntPropVector(entity, Prop_Send, "m_angRotation", this.angles); - this.prevOrigin = this.origin; - this.prevAngles = this.angles; - GetEntPropVector(entity, Prop_Send, "m_vecMins", this.mins); - GetEntPropVector(entity, Prop_Send, "m_vecMaxs", this.size); - this.entity = entity; - this.SetMode(mode); - } - - // Cancels the current placement. If the edit is a copy/preview, the entity is also deleted - // If entity is not a wall, it will be returned - void Cancel() { - // Delete any copies: - if(this.flags & Edit_Copy || this.flags & Edit_Preview) { - RemoveEntity(this.entity); - } else if(~this.flags & Edit_WallCreator) { - // Is an edit of a prop - TeleportEntity(this.entity, this.prevOrigin, this.prevAngles, NULL_VECTOR); - } - this.SetMode(INACTIVE); - PrintHintText(this.client, "Cancelled"); - if(this.callback) { - delete this.callback; - } - // CPrintToChat(this.client, "\x04[Editor]\x01 Cancelled."); - } -} - -void SendEditorMessage(int client, const char[] format, any ...) { - char message[256]; - VFormat(message, sizeof(message), format, 3); - CPrintToChat(client, "\x04[Editor]\x01 %s", message); -} - -stock float RoundToNearestInterval(float value, int interval) { - return float(RoundFloat(value / float(interval)) * interval); -} -EditorData Editor[MAXPLAYERS+1]; - -Action OnWallClicked(int entity, int activator, int caller, UseType type, float value) { - int wallId = FindWallId(entity); - if(wallId > 0) { - GlowWall(wallId, GLOW_BLUE); - AcceptEntityInput(entity, "Toggle"); - } else { - PrintHintText(activator, "Invalid wall entity (%d)", entity); - } - return Plugin_Continue; -} - - - -// TODO: Stacker, copy tool, new command? -public Action Command_MakeWall(int client, int args) { - if(Editor[client].IsActive()) { - ReplyToCommand(client, "\x04[Editor]\x01 You are currently editing an entity. Finish editing your current entity with with \x05/edit done\x01 or cancel with \x04/edit cancel\x01"); - } else { - Editor[client].StartWall(); - if(args > 0) { - // Get values for X, Y and Z axis (defaulting to 1.0): - char arg2[8]; - for(int i = 0; i < 3; i++) { - GetCmdArg(i + 1, arg2, sizeof(arg2)); - float value; - if(StringToFloatEx(arg2, value) == 0) { - value = 1.0; - } - Editor[client].size[i] = value; - } - - float rot[3]; - GetClientEyeAngles(client, rot); - // Flip X and Y depending on rotation - // TODO: Validate - if(rot[2] > 45 && rot[2] < 135 || rot[2] > -135 && rot[2] < -45) { - float temp = Editor[client].size[0]; - Editor[client].size[0] = Editor[client].size[1]; - Editor[client].size[1] = temp; - } - - Editor[client].CalculateMins(); - } - - Editor[client].SetMode(SCALE); - GetCursorLimited(client, 100.0, Editor[client].origin, Filter_IgnorePlayer); - PrintToChat(client, "\x04[Editor]\x01 New Wall Started. End with \x05/wall build\x01 or \x04/wall cancel\x01"); - PrintToChat(client, "\x04[Editor]\x01 Mode: \x05Scale\x01"); - } - return Plugin_Handled; -} - -// TODO: move wall ids to own subcommand -Action Command_Editor(int client, int args) { - if(args == 0) { - PrintToChat(client, "\x04[Editor]\x01 Created Walls: \x05%d\x01", createdWalls.Length); - for(int i = 1; i <= createdWalls.Length; i++) { - GlowWall(i, GLOW_WHITE, 20.0); - } - return Plugin_Handled; - } - char arg1[16], arg2[16]; - GetCmdArg(1, arg1, sizeof(arg1)); - GetCmdArg(2, arg2, sizeof(arg2)); - if(StrEqual(arg1, "build") || StrEqual(arg1, "done")) { - // Remove frozen flag from user, as some modes use this - int flags = GetEntityFlags(client) & ~FL_FROZEN; - SetEntityFlags(client, flags); - - int entity; - CompleteType result = Editor[client].Done(entity); - switch(result) { - case Complete_WallSuccess: { - if(entity > 0) - PrintToChat(client, "\x04[Editor]\x01 Wall Creation: \x05Wall #%d Created\x01", entity + 1); - else - PrintToChat(client, "\x04[Editor]\x01 Wall Edit: \x04Complete\x01"); - } - case Complete_PropSpawned: - PrintToChat(client, "\x04[Editor]\x01 Prop Spawned: \x04%d\x01", entity); - - case Complete_EditSuccess: - PrintToChat(client, "\x04[Editor]\x01 Entity Edited: \x04%d\x01", entity); - - default: - PrintToChat(client, "\x04[Editor]\x01 Unknown result"); - } - } else if(StrEqual(arg1, "cancel")) { - int flags = GetEntityFlags(client) & ~FL_FROZEN; - SetEntityFlags(client, flags); - Editor[client].Cancel(); - if(Editor[client].flags & Edit_Preview) - PrintToChat(client, "\x04[Editor]\x01 Prop Spawer: \x04Cancelled\x01"); - else if(Editor[client].flags & Edit_WallCreator) { - PrintToChat(client, "\x04[Editor]\x01 Wall Creation: \x04Cancelled\x01"); - } else { - PrintToChat(client, "\x04[Editor]\x01 Entity Edit: \x04Cancelled\x01"); - } - } else if(StrEqual(arg1, "export")) { - // TODO: support exp #id - float origin[3], angles[3], size[3]; - if(Editor[client].IsActive()) { - origin = Editor[client].origin; - angles = Editor[client].angles; - size = Editor[client].size; - Export(client, arg2, Editor[client].entity, origin, angles, size); - } else { - int id = GetWallId(client, arg2); - if(id == -1) return Plugin_Handled; - int entity = GetWallEntity(id); - GetEntPropVector(entity, Prop_Send, "m_vecOrigin", origin); - if(HasEntProp(entity, Prop_Send, "m_vecAngles")) - GetEntPropVector(entity, Prop_Send, "m_vecAngles", angles); - GetEntPropVector(entity, Prop_Send, "m_vecMaxs", size); - Export(client, arg2, entity, origin, angles, size); - } - } else if(StrEqual(arg1, "delete")) { - if(Editor[client].IsActive() && args == 1) { - int entity = Editor[client].entity; - if(IsValidEntity(entity)) { - PrintToChat(client, "\x04[Editor]\x01 You are not editing any existing entity, use \x05/wall cancel\x01 to stop or \x05/wall delete "); - } else if(entity > MaxClients) { - RemoveEntity(entity); - Editor[client].Reset(); - PrintToChat(client, "\x04[Editor]\x01 Deleted current entity"); - } else { - PrintToChat(client, "\x04[Editor]\x01 Cannot delete player entities."); - } - } else if(StrEqual(arg2, "all")) { - int walls = createdWalls.Length; - for(int i = 1; i <= createdWalls.Length; i++) { - DeleteWall(i); - } - PrintToChat(client, "\x04[Editor]\x01 Deleted \x05%d\x01 Walls", walls); - } else { - int id = GetWallId(client, arg2); - if(id > -1) { - DeleteWall(id); - PrintToChat(client, "\x04[Editor]\x01 Deleted Wall: \x05#%d\x01", id); - } - } - } else if(StrEqual(arg1, "create")) { - ReplyToCommand(client, "\x04[Editor]\x01 Syntax: /mkwall [size x] [size y] [size z]"); - } else if(StrEqual(arg1, "toggle")) { - if(StrEqual(arg2, "all")) { - int walls = createdWalls.Length; - for(int i = 1; i <= createdWalls.Length; i++) { - int entity = GetWallEntity(i); - AcceptEntityInput(entity, "Toggle"); - GlowWall(i, GLOW_BLUE); - } - PrintToChat(client, "\x04[Editor]\x01 Toggled \x05%d\x01 walls", walls); - } else { - int id = GetWallId(client, arg2); - if(id > -1) { - int entity = GetWallEntity(id); - AcceptEntityInput(entity, "Toggle"); - GlowWall(id, GLOW_BLUE); - PrintToChat(client, "\x04[Editor]\x01 Toggled Wall: \x05#%d\x01", id); - } - } - } else if(StrEqual(arg1, "filter")) { - if(args < 3) { - ReplyToCommand(client, "\x04[Editor]\x01 Syntax: \x05/walls filter \x04"); - ReplyToCommand(client, "\x04[Editor]\x01 Valid filters: \x05player"); - return Plugin_Handled; - } - - char arg3[32]; - GetCmdArg(3, arg3, sizeof(arg3)); - - SetVariantString(arg3); - if(StrEqual(arg2, "all")) { - int walls = createdWalls.Length; - for(int i = 1; i <= createdWalls.Length; i++) { - int entity = GetWallEntity(i); - AcceptEntityInput(entity, "SetExcluded"); - } - PrintToChat(client, "\x04[Editor]\x01 Set %d walls' filter to \x05%s\x01", walls, arg3); - } else { - int id = GetWallId(client, arg2); - if(id > -1) { - int entity = GetWallEntity(id); - AcceptEntityInput(entity, "SetExcluded"); - PrintToChat(client, "\x04[Editor]\x01 Set wall #%d filter to \x05%s\x01", id, arg3); - } - } - } else if(StrEqual(arg1, "edit")) { - int id = GetWallId(client, arg2); - if(id > -1) { - int entity = GetWallEntity(id); - Editor[client].Import(entity); - PrintToChat(client, "\x04[Editor]\x01 Editing wall \x05%d\x01. End with \x05/wall done\x01 or \x04/wall cancel\x01", id); - PrintToChat(client, "\x04[Editor]\x01 Mode: \x05Scale\x01"); - } - } else if(StrEqual(arg1, "edite") || (arg1[0] == 'c' && arg1[1] == 'u')) { - int index = GetLookingEntity(client, Filter_ValidHats); //GetClientAimTarget(client, false); - if(index > 0) { - Editor[client].Import(index, false, MOVE_ORIGIN); - char classname[64]; - char targetname[32]; - GetEntityClassname(index, classname, sizeof(classname)); - GetEntPropString(index, Prop_Data, "m_target", targetname, sizeof(targetname)); - PrintToChat(client, "\x04[Editor]\x01 Editing entity \x05%d (%s) [%s]\x01. End with \x05/wall done\x01", index, classname, targetname); - PrintToChat(client, "\x04[Editor]\x01 Mode: \x05Move & Rotate\x01"); - } else { - ReplyToCommand(client, "\x04[Editor]\x01 Invalid or non existent entity"); - } - } else if(StrEqual(arg1, "copy")) { - if(Editor[client].IsActive()) { - int oldEntity = Editor[client].entity; - if(oldEntity == INVALID_ENT_REFERENCE) { - PrintToChat(client, "\x04[Editor]\x01 Finish editing your wall first: \x05/wall done\x01 or \x04/wall cancel\x01"); - } else { - int entity = Editor[client].Copy(); - PrintToChat(client, "\x04[Editor]\x01 Editing copy \x05%d\x01 of entity \x05%d\x01. End with \x05/edit done\x01 or \x04/edit cancel\x01", entity, oldEntity); - } - } else { - int id = GetWallId(client, arg2); - if(id > -1) { - int entity = GetWallEntity(id); - Editor[client].Import(entity, true); - GetCursorLimited(client, 100.0, Editor[client].origin, Filter_IgnorePlayer); - PrintToChat(client, "\x04[Editor]\x01 Editing copy of wall \x05%d\x01. End with \x05/wall build\x01 or \x04/wall cancel\x01", id); - PrintToChat(client, "\x04[Editor]\x01 Mode: \x05Scale\x01"); - } - } - } else if(StrEqual(arg1, "list")) { - for(int i = 1; i <= createdWalls.Length; i++) { - int entity = GetWallEntity(i); - ReplyToCommand(client, "Wall #%d - EntIndex: %d", i, EntRefToEntIndex(entity)); - } - } else { - ReplyToCommand(client, "\x04[Editor]\x01 See console for list of commands"); - GetCmdArg(0, arg1, sizeof(arg1)); - PrintToConsole(client, "%s done / build - Finishes editing, creates wall if making wall", arg1); - PrintToConsole(client, "%s cancel - Cancels editing (for entity edits is same as done)", arg1); - PrintToConsole(client, "%s list - Lists all walls", arg1); - PrintToConsole(client, "%s filter - Sets classname filter for walls, doesnt really work", arg1); - PrintToConsole(client, "%s toggle - Toggles if wall is active (collides)", arg1); - PrintToConsole(client, "%s delete - Deletes the wall(s)", arg1); - PrintToConsole(client, "%s edit - Edits wall id", arg1); - PrintToConsole(client, "%s copy [id] - If editing creates a new copy of wall/entity, else copies wall id", arg1); - PrintToConsole(client, "%s cursor - Starts editing the entity you looking at", arg1); - } - return Plugin_Handled; -} - -int GetWallId(int client, const char[] arg) { - int id; - if(StringToIntEx(arg, id) > 0 && id > 0 && id <= createdWalls.Length) { - int entity = GetWallEntity(id); - if(!IsValidEntity(entity)) { - ReplyToCommand(client, "\x04[Editor]\x01 The wall with specified id no longer exists."); - createdWalls.Erase(id - 1); - return -2; - } - return id; - } else { - ReplyToCommand(client, "\x04[Editor]\x01 Invalid wall id, must be between 0 - %d", createdWalls.Length - 1 ); - return -1; - } -} - -int GetWallEntity(int id) { - if(id <= 0 || id > createdWalls.Length) { - ThrowError("Invalid wall id (%d)", id); - } - return createdWalls.Get(id - 1); -} - -/// Tries to find the id of the wall based off entity -int FindWallId(int entity) { - for(int i = 1; i <= createdWalls.Length; i++) { - int entRef = createdWalls.Get(i - 1); - int ent = EntRefToEntIndex(entRef); - if(ent == entity) { - return i; - } - } - return -1; -} - -void GlowWall(int id, int glowColor[4], float lifetime = 5.0) { - int ref = GetWallEntity(id); - if(IsValidEntity(ref)) { - float pos[3], mins[3], maxs[3], angles[3]; - GetEntPropVector(ref, Prop_Send, "m_angRotation", angles); - GetEntPropVector(ref, Prop_Send, "m_vecOrigin", pos); - GetEntPropVector(ref, Prop_Send, "m_vecMins", mins); - GetEntPropVector(ref, Prop_Send, "m_vecMaxs", maxs); - Effect_DrawBeamBoxRotatableToAll(pos, mins, maxs, angles, g_iLaserIndex, 0, 0, 30, lifetime, 0.4, 0.4, 0, 1.0, glowColor, 0); - } -} - -void DeleteWall(int id) { - GlowWall(id, GLOW_RED_ALPHA); - int ref = GetWallEntity(id); - if(IsValidEntity(ref)) { - RemoveEntity(ref); - } - createdWalls.Erase(id - 1); -} - - void Export(int client, const char[] expType, int entity, const float origin[3], const float angles[3], const float size[3]) { - char sPath[PLATFORM_MAX_PATH]; - char currentMap[64]; - GetCurrentMap(currentMap, sizeof(currentMap)); - - BuildPath(Path_SM, sPath, sizeof(sPath), "data/exports"); - CreateDirectory(sPath, 1406); - BuildPath(Path_SM, sPath, sizeof(sPath), "data/exports/%s.cfg", currentMap); - File file = OpenFile(sPath, "w"); - if(file == null) { - PrintToServer("[Editor] Export: Cannot open \"%s\", cant write", sPath); - } - - PrintWriteLine(client, file, "{"); - if(entity != INVALID_ENT_REFERENCE) { - char model[64]; - GetEntPropString(entity, Prop_Data, "m_ModelName", model, sizeof(model)); - if(StrEqual(expType, "json")) { - PrintWriteLine(client, file, "\t\"model\": \"%s\",", model); - } else{ - PrintWriteLine(client, file, "\t\"model\" \"%s\"", model); - } - } - - if(StrEqual(expType, "json")) { - PrintWriteLine(client, file, "\t\"origin\": [%.2f, %.2f, %.2f],", origin[0], origin[1], origin[2]); - PrintWriteLine(client, file, "\t\"angles\": [%.2f, %.2f, %.2f],", angles[0], angles[1], angles[2]); - PrintWriteLine(client, file, "\t\"size\": [%.2f, %.2f, %.2f]", size[0], size[1], size[2]); - } else { - PrintWriteLine(client, file, "\t\"origin\" \"%.2f %.2f %.2f\"", origin[0], origin[1], origin[2]); - PrintWriteLine(client, file, "\t\"angles\" \"%.2f %.2f %.2f\"", angles[0], angles[1], angles[2]); - PrintWriteLine(client, file, "\t\"size\" \"%.2f %.2f %.2f\"", size[0], size[1], size[2]); - } - PrintWriteLine(client, file, "}"); - delete file; -} - -void PrintWriteLine(int client, File file, const char[] format, any ...) { - char line[100]; - VFormat(line, sizeof(line), format, 4); - if(file != null) - file.WriteLine(line); - PrintToChat(client, line); -} \ No newline at end of file diff --git a/scripting/include/hats/hats.sp b/scripting/include/hats/hats.sp index d8f846c..5734a1a 100644 --- a/scripting/include/hats/hats.sp +++ b/scripting/include/hats/hats.sp @@ -1,846 +1,842 @@ -enum hatFlags { - HAT_NONE = 0, - HAT_POCKET = 1, - HAT_REVERSED = 2, - HAT_COMMANDABLE = 4, - HAT_RAINBOW = 8, - HAT_PRESET = 16 -} -enum struct HatInstance { - int entity; // The entity REFERENCE - int visibleEntity; // Thee visible entity REF - Handle yeetGroundTimer; - - // Original data for entity - float orgPos[3]; - float orgAng[3]; - float offset[3]; - float angles[3]; - int collisionGroup; - int solidType; - int moveType; - - float scale; - int flags; - float rainbowColor[3]; - int rainbowTicks; - bool rainbowReverse; - char attachPoint[32]; -} -enum hatFeatures { - HatConfig_None = 0, - HatConfig_PlayerHats = 1, - HatConfig_RespectAdminImmunity = 2, - HatConfig_FakeHat = 4, - HatConfig_NoSaferoomHats = 8, - HatConfig_PlayerHatConsent = 16, - HatConfig_InfectedHats = 32, - HatConfig_ReversedHats = 64, - HatConfig_DeleteThrownHats = 128 -} -char ActivePreset[MAXPLAYERS+1][32]; -int lastHatRequestTime[MAXPLAYERS+1]; -HatInstance hatData[MAXPLAYERS+1]; -StringMap g_HatPresets; - -#define MAX_FORBIDDEN_CLASSNAMES 15 -char FORBIDDEN_CLASSNAMES[MAX_FORBIDDEN_CLASSNAMES][] = { - "prop_door_rotating_checkpoint", - "env_physics_blocker", - "env_player_blocker", - "func_brush", - "func_simpleladder", - "prop_door_rotating", - "func_button", - "func_elevator", - "func_button_timed", - "func_tracktrain", - "func_movelinear", - // "infected", - "func_lod", - "func_door", - "prop_ragdoll", - "move_rope" -}; - -#define MAX_FORBIDDEN_MODELS 2 -char FORBIDDEN_MODELS[MAX_FORBIDDEN_MODELS][] = { - "models/props_vehicles/c130.mdl", - "models/props_vehicles/helicopter_rescue.mdl" -}; - - -#define MAX_REVERSE_CLASSNAMES 2 -// Classnames that should automatically trigger reverse infected -static char REVERSE_CLASSNAMES[MAX_REVERSE_CLASSNAMES][] = { - "infected", - "func_movelinear", -}; - -Action Command_DoAHat(int client, int args) { - int hatter = GetHatter(client); - if(hatter > 0) { - ClearHat(hatter, HasFlag(hatter, HAT_REVERSED)); - PrintToChat(hatter, "[Hats] %N has unhatted themselves", client); - return Plugin_Handled; - } - - static char cmdName[8]; - GetCmdArg(0, cmdName, sizeof(cmdName)); - AdminId adminId = GetUserAdmin(client); - bool isForced = adminId != INVALID_ADMIN_ID && StrEqual(cmdName, "sm_hatf"); - if(cvar_sm_hats_enabled.IntValue == 1) { - if(adminId == INVALID_ADMIN_ID) { - PrintToChat(client, "[Hats] Hats are for admins only"); - return Plugin_Handled; - } else if(!adminId.HasFlag(Admin_Custom2)) { - PrintToChat(client, "[Hats] You do not have permission"); - return Plugin_Handled; - } - } else if(cvar_sm_hats_enabled.IntValue == 0) { - ReplyToCommand(client, "[Hats] Hats are disabled"); - return Plugin_Handled; - } else if(GetClientTeam(client) != 2 && ~cvar_sm_hats_flags.IntValue & view_as(HatConfig_InfectedHats)) { - PrintToChat(client, "[Hats] Hats are only available for survivors."); - return Plugin_Handled; - } - - int entity = GetHat(client); - if(entity > 0) { - if(HasFlag(client, HAT_PRESET)) { - PrintToChat(client, "[Hats] Your hat is a preset, use /hatp to remove it."); - return Plugin_Handled; - } - char arg[4]; - GetCmdArg(1, arg, sizeof(arg)); - if(arg[0] == 'e') { - ReplyToCommand(client, "\t\t\"origin\"\t\"%f %f %f\"", hatData[client].offset[0], hatData[client].offset[1], hatData[client].offset[2]); - ReplyToCommand(client, "\t\t\"angles\"\t\"%f %f %f\"", hatData[client].angles[0], hatData[client].angles[1], hatData[client].angles[2]); - return Plugin_Handled; - } else if(arg[0] == 'v') { - ReplyToCommand(client, "Flags: %d", hatData[client].flags); - // ReplyToCommand(client, "CurOffset: %f %f %f", ); - return Plugin_Handled; - } else if(arg[0] == 'a') { - ShowAttachPointMenu(client); - return Plugin_Handled; - } - // int orgEntity = entity; - if(HasFlag(client, HAT_REVERSED)) { - entity = client; - } - ClearParent(entity); - - if(arg[0] == 's') { - char sizeStr[4]; - GetCmdArg(2, sizeStr, sizeof(sizeStr)); - float size = StringToFloat(sizeStr); - if(size == 0.0) { - ReplyToCommand(client, "[Hats] Invalid size"); - return Plugin_Handled; - } - if(HasEntProp(entity, Prop_Send, "m_flModelScale")) - SetEntPropFloat(entity, Prop_Send, "m_flModelScale", size); - else - PrintHintText(client, "Hat does not support scaling"); - // Change the size of it's parent instead - int child = -1; - while((child = FindEntityByClassname(child, "*")) != INVALID_ENT_REFERENCE ) - { - int parent = GetEntPropEnt(child, Prop_Data, "m_pParent"); - if(parent == entity) { - if(HasEntProp(child, Prop_Send, "m_flModelScale")) { - PrintToConsole(client, "found child %d for %d", child, entity); - SetEntPropFloat(child, Prop_Send, "m_flModelScale", size); - } else { - PrintToChat(client, "Child %d for %d cannot be scaled", child, entity); - } - break; - } - } - // Reattach entity: - EquipHat(client, entity); - return Plugin_Handled; - } else if(arg[0] == 'r' && arg[1] == 'a') { - SetFlag(client, HAT_RAINBOW); - hatData[client].rainbowTicks = 0; - hatData[client].rainbowReverse = false; - hatData[client].rainbowColor[0] = 0.0; - hatData[client].rainbowColor[1] = 255.0; - hatData[client].rainbowColor[2] = 255.0; - EquipHat(client, entity); - ReplyToCommand(client, "Rainbow hats enabled"); - return Plugin_Handled; - } - - // Re-enable physics and restore collision/solidity - AcceptEntityInput(entity, "EnableMotion"); - SetEntProp(entity, Prop_Send, "m_CollisionGroup", hatData[client].collisionGroup); - SetEntProp(entity, Prop_Send, "m_nSolidType", hatData[client].solidType); - - // Remove frozen flag (only "infected" and "witch" are frozen, but just incase:) - int flags = GetEntityFlags(entity) & ~FL_FROZEN; - SetEntityFlags(entity, flags); - - // Clear visible hats (HatConfig_FakeHat is enabled) - int visibleEntity = EntRefToEntIndex(hatData[client].visibleEntity); - SDKUnhook(entity, SDKHook_SetTransmit, OnRealTransmit); - if(visibleEntity > 0) { - SDKUnhook(visibleEntity, SDKHook_SetTransmit, OnVisibleTransmit); - RemoveEntity(visibleEntity); - hatData[client].visibleEntity = INVALID_ENT_REFERENCE; - } - // Grant temp god & remove after time - tempGod[client] = true; - if(client <= MaxClients) { - SDKHook(client, SDKHook_OnTakeDamageAlive, OnTakeDamageAlive); - CreateTimer(2.0, Timer_RemoveGod, GetClientUserId(client)); - } - if(entity <= MaxClients) { - SDKHook(entity, SDKHook_OnTakeDamageAlive, OnTakeDamageAlive); - CreateTimer(2.0, Timer_RemoveGod, GetClientUserId(entity)); - } - - // Restore movement: - if(entity <= MaxClients) { - // If player, remove roll & and just default to WALK movetype - hatData[client].orgAng[2] = 0.0; - SetEntityMoveType(entity, MOVETYPE_WALK); - } else { - // If not player, then just use whatever they were pre-hat - SetEntProp(entity, Prop_Send, "movetype", hatData[client].moveType); - } - - if(arg[0] == 'y') { // Hat yeeting: - char classname[16]; - GetEntityClassname(entity, classname, sizeof(classname)); - if(StrEqual(classname, "prop_dynamic")) { - ReplyToCommand(client, "You cannot yeet this prop (it has no physics)"); - return Plugin_Handled; - } - GetClientEyeAngles(client, hatData[client].orgAng); - GetClientAbsOrigin(client, hatData[client].orgPos); - hatData[client].orgPos[2] += 45.0; - float ang[3], vel[3]; - - // Calculate the angle to throw at - GetClientEyeAngles(client, ang); - ang[2] = 0.0; - if(ang[0] > 0.0) ang[0] = -ang[0]; - // ang[0] = -45.0; - - // Calculate velocity to throw based on direction - vel[0] = Cosine(DegToRad(ang[1])) * GetRandomFloat(1300.0, 1700.0); - vel[1] = Sine(DegToRad(ang[1])) * GetRandomFloat(1300.0, 1700.0); - vel[2] = GetRandomFloat(700.0, 900.0); - if(entity <= MaxClients) { - // For players, use the built in fling function - TeleportEntity(entity, hatData[client].orgPos, hatData[client].orgAng, NULL_VECTOR); - L4D2_CTerrorPlayer_Fling(entity, client, vel); - } /*else if(visibleEntity > 0) { - PrintToChat(client, "Yeeting fake car..."); - ClearParent(visibleEntity); - - SetEntProp(visibleEntity, Prop_Send, "movetype", 6); - - AcceptEntityInput(visibleEntity, "EnableMotion"); - - TeleportEntity(entity, OUT_OF_BOUNDS, hatData[client].orgAng, NULL_VECTOR); - TeleportEntity(visibleEntity, hatData[client].orgPos, hatData[client].orgAng, vel); - DataPack pack; - CreateDataTimer(4.0, Timer_PropYeetEnd, pack); - pack.WriteCell(hatData[client].entity); - pack.WriteCell(hatData[client].visibleEntity); - pack.WriteCell(hatData[client].collisionGroup); - pack.WriteCell(hatData[client].solidType); - pack.WriteCell(hatData[client].moveType); - hatData[client].visibleEntity = INVALID_ENT_REFERENCE; - hatData[client].entity = INVALID_ENT_REFERENCE; - } */ else { - // For actual props, offset it 35 units above and 80 units infront to reduce collision-incaps and then throw - GetHorizontalPositionFromClient(client, 80.0, hatData[client].orgPos); - hatData[client].orgPos[2] += 35.0; - TeleportEntity(entity, hatData[client].orgPos, hatData[client].orgAng, vel); - // Sleep the physics after enoug time for it to most likely have landed - if(hatData[client].yeetGroundTimer != null) { - // TODO: FIX null issue - delete hatData[client].yeetGroundTimer; - } - DataPack pack1; - hatData[client].yeetGroundTimer = CreateDataTimer(0.5, Timer_GroundKill, pack1, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); - pack1.WriteCell(hatData[client].entity); - pack1.WriteCell(GetClientUserId(client)); - DataPack pack2; - CreateDataTimer(7.7, Timer_PropSleep, pack2); - pack2.WriteCell(hatData[client].entity); - pack2.WriteCell(GetClientUserId(client)); - } - PrintToChat(client, "[Hats] Yeeted hat"); - hatData[client].entity = INVALID_ENT_REFERENCE; - return Plugin_Handled; - } else if(arg[0] == 'c') { - float pos[3]; - // Grabs a cursor position with some checks to prevent placing into (in)visible walls - if(GetSmartCursorLocation(client, pos)) { - if(CanHatBePlaced(client, pos)) { - if(entity <= MaxClients) - L4D_WarpToValidPositionIfStuck(entity); - hatData[client].orgPos = pos; - TeleportEntity(entity, hatData[client].orgPos, hatData[client].orgAng, NULL_VECTOR); - PrintToChat(client, "[Hats] Placed hat on cursor."); - } - } else { - PrintToChat(client, "[Hats] Could not find cursor position."); - } - } else if(arg[0] == 'p' || (entity <= MaxClients && arg[0] != 'r')) { - // Place the hat down on the cursor if specified OR if entity is hat - float pos[3], ang[3]; - if(HasFlag(client, HAT_REVERSED)) { - // If we are reversed, then place ourselves where our "hatter" is - GetClientEyePosition(entity, hatData[client].orgPos); - GetClientEyeAngles(entity, hatData[client].orgAng); - TeleportEntity(client, hatData[client].orgPos, hatData[client].orgAng, NULL_VECTOR); - PrintToChat(entity, "[Hats] Placed hat in front of you."); - } else { - // If we are normal, then get position infront of us, offset by model size - GetClientEyePosition(client, pos); - GetClientEyeAngles(client, ang); - GetHorizontalPositionFromOrigin(pos, ang, 80.0, pos); - ang[0] = 0.0; - float mins[3]; - GetEntPropVector(entity, Prop_Data, "m_vecMins", mins); - pos[2] += mins[2]; - // Find the nearest ground (raytrace bottom->up) - FindGround(pos, pos); - // Check if the destination is acceptable (not saferooms if enabled) - if(CanHatBePlaced(client, pos)) { - hatData[client].orgPos = pos; - hatData[client].orgAng = ang; - TeleportEntity(entity, hatData[client].orgPos, hatData[client].orgAng, NULL_VECTOR); - PrintToChat(client, "[Hats] Placed hat in front of you."); - } - } - } else if(arg[0] == 'd') { - // Use the new wall editor - Editor[client].Reset(); - Editor[client].entity = EntIndexToEntRef(entity); - Editor[client].SetMode(MOVE_ORIGIN); - PrintToChat(client, "\x04[Hats] \x01Beta Prop Mover active for \x04%d", entity); - } else { - PrintToChat(client, "[Hats] Restored hat to its original position."); - } - - // Restore the scale pre-hat - if(hatData[client].scale > 0 && HasEntProp(entity, Prop_Send, "m_flModelScale")) - SetEntPropFloat(entity, Prop_Send, "m_flModelScale", hatData[client].scale); - - // If no other options performed, then restore to original position and remove our reference - AcceptEntityInput(entity, "Sleep"); - TeleportEntity(entity, hatData[client].orgPos, hatData[client].orgAng, NULL_VECTOR); - hatData[client].entity = INVALID_ENT_REFERENCE; - } else { - // Find a new hatable entity - int flags = 0; - entity = GetLookingEntity(client, Filter_ValidHats); - if(entity <= 0) { - PrintCenterText(client, "[Hats] No entity found"); - return Plugin_Handled; - } else if(entity == EntRefToEntIndex(Editor[client].entity)) { - // Prevent making an entity you editing a hat - return Plugin_Handled; - } else if(!isForced && cvar_sm_hats_max_distance.FloatValue > 0.0 && entity >= MaxClients) { - float posP[3], posE[3]; - GetClientEyePosition(client, posP); - GetEntPropVector(entity, Prop_Data, "m_vecOrigin", posE); - if(GetVectorDistance(posP, posE) > cvar_sm_hats_max_distance.FloatValue) { - PrintCenterText(client, "[Hats] Entity too far away"); - return Plugin_Handled; - } - } - - // Make hat reversed if 'r' passed in - char arg[4]; - if(args > 0) { - GetCmdArg(1, arg, sizeof(arg)); - if(arg[0] == 'r') { - flags |= view_as(HAT_REVERSED); - } - } - - int parent = GetEntPropEnt(entity, Prop_Data, "m_hParent"); - if(parent > 0 && entity > MaxClients) { - PrintToConsole(client, "[Hats] Selected a child entity, selecting parent (child %d -> parent %d)", entity, parent); - entity = parent; - } else if(entity <= MaxClients) { // Checks for hatting a player entity - if(IsFakeClient(entity) && L4D_GetIdlePlayerOfBot(entity) > 0) { - PrintToChat(client, "[Hats] Cannot hat idle bots"); - return Plugin_Handled; - } else if(!isForced && GetClientTeam(entity) != 2 && ~cvar_sm_hats_flags.IntValue & view_as(HatConfig_InfectedHats)) { - PrintToChat(client, "[Hats] Cannot make enemy a hat... it's dangerous"); - return Plugin_Handled; - } else if(entity == EntRefToEntIndex(Editor[client].entity)) { - // Old check left in in case you hatting child entity - PrintToChat(client, "[Hats] You are currently editing this entity"); - return Plugin_Handled; - } else if(inSaferoom[client] && cvar_sm_hats_flags.IntValue & view_as(HatConfig_NoSaferoomHats)) { - PrintToChat(client, "[Hats] Hats are not allowed in the saferoom"); - return Plugin_Handled; - } else if(!IsPlayerAlive(entity) || GetEntProp(entity, Prop_Send, "m_isHangingFromLedge") || L4D_IsPlayerCapped(entity)) { - PrintToChat(client, "[Hats] Player is either dead, hanging, or in the process of dying."); - return Plugin_Handled; - } else if(EntRefToEntIndex(hatData[entity].entity) == entity || EntRefToEntIndex(hatData[entity].entity) == client) { - PrintToChat(client, "[Hats] Woah you can't be making a black hole, jesus be careful."); - return Plugin_Handled; - } else if(~cvar_sm_hats_flags.IntValue & view_as(HatConfig_PlayerHats)) { - PrintToChat(client, "[Hats] Player hats are disabled"); - return Plugin_Handled; - } else if(!CanTarget(entity)) { - PrintToChat(client, "[Hats] Player has disabled player hats for themselves."); - return Plugin_Handled; - } else if(!CanTarget(client)) { - PrintToChat(client, "[Hats] Cannot hat a player when you have player hats turned off"); - return Plugin_Handled; - } else if(cvar_sm_hats_flags.IntValue & view_as(HatConfig_RespectAdminImmunity)) { - AdminId targetAdmin = GetUserAdmin(entity); - AdminId clientAdmin = GetUserAdmin(client); - if(targetAdmin != INVALID_ADMIN_ID && clientAdmin == INVALID_ADMIN_ID) { - PrintToChat(client, "[Hats] Cannot target an admin"); - return Plugin_Handled; - } else if(targetAdmin != INVALID_ADMIN_ID && targetAdmin.ImmunityLevel > clientAdmin.ImmunityLevel) { - PrintToChat(client, "[Hats] Cannot target %N, they are immune to you", entity); - return Plugin_Handled; - } - } - if(!isForced && - !IsFakeClient(entity) && - cvar_sm_hats_flags.IntValue & view_as(HatConfig_PlayerHatConsent) && - ~flags & view_as(HAT_REVERSED) - ) { - int lastRequestDiff = GetTime() - lastHatRequestTime[client]; - if(lastRequestDiff < PLAYER_HAT_REQUEST_COOLDOWN) { - PrintToChat(client, "[Hats] Player hat under %d seconds cooldown", PLAYER_HAT_REQUEST_COOLDOWN - lastRequestDiff); - return Plugin_Handled; - } - - Menu menu = new Menu(HatConsentHandler); - menu.SetTitle("%N: Requests to hat you", client); - char id[8]; - Format(id, sizeof(id), "%d|1", GetClientUserId(client)); - menu.AddItem(id, "Accept"); - Format(id, sizeof(id), "%d|0", GetClientUserId(client)); - menu.AddItem(id, "Reject"); - menu.Display(entity, 12); - PrintHintText(client, "Sent hat request to %N", entity); - PrintToChat(entity, "[Hats] %N requests to hat you, 1 to Accept, 2 to Reject. Expires in 12 seconds.", client); - return Plugin_Handled; - } - } - - - char classname[64]; - GetEntityClassname(entity, classname, sizeof(classname)); - - // Check for any class that should always be reversed - if(~flags & view_as(HAT_REVERSED)) { - for(int i = 0; i < MAX_REVERSE_CLASSNAMES; i++) { - if(StrEqual(REVERSE_CLASSNAMES[i], classname)) { - flags |= view_as(HAT_REVERSED); - break; - } - } - } - - EquipHat(client, entity, classname, flags); - } - return Plugin_Handled; -} - - -#define MAX_ATTACHMENT_POINTS 20 -char ATTACHMENT_POINTS[MAX_ATTACHMENT_POINTS][] = { - "eyes", - "molotov", - "pills", - "grenade", - "primary", - "medkit", - "melee", - "survivor_light", - "bleedout", - "forward", - "survivor_neck", - "muzzle_flash", - "spine", - "legL", - "legR", - "thighL", - "thighR", - "lfoot", - "rfoot", - "mouth", -}; - -void ShowAttachPointMenu(int client) { - Menu menu = new Menu(AttachPointHandler); - menu.SetTitle("Choose an attach point"); - for(int i = 0; i < MAX_ATTACHMENT_POINTS; i++) { - menu.AddItem(ATTACHMENT_POINTS[i], ATTACHMENT_POINTS[i]); - } - menu.Display(client, 0); -} - -int AttachPointHandler(Menu menu, MenuAction action, int client, int param2) { - if (action == MenuAction_Select) { - char attachPoint[32]; - menu.GetItem(param2, attachPoint, sizeof(attachPoint)); - if(!HasHat(client)) { - ReplyToCommand(client, "No hat is equipped"); - } else { - int hat = GetHat(client); - char classname[32]; - GetEntityClassname(hat, classname, sizeof(classname)); - EquipHat(client, hat, classname, hatData[client].flags, attachPoint); - CReplyToCommand(client, "Attachment point set to {olive}%s", attachPoint); - } - } else if (action == MenuAction_End) - delete menu; - return 0; -} - -// Handles consent that a person to be hatted by another player -int HatConsentHandler(Menu menu, MenuAction action, int target, int param2) { - if (action == MenuAction_Select) { - char info[8]; - menu.GetItem(param2, info, sizeof(info)); - char str[2][8]; - ExplodeString(info, "|", str, 2, 8, false); - int activator = GetClientOfUserId(StringToInt(str[0])); - int hatAction = StringToInt(str[1]); - if(activator == 0) { - ReplyToCommand(target, "Player has disconnected"); - return 0; - } else if(hatAction == 1) { - if(EntRefToEntIndex(hatData[target].entity) == activator ) - PrintToChat(activator, "[Hats] Woah you can't be making a black hole, jesus be careful."); - else - EquipHat(activator, target, "player", 0); - } else { - ClientCommand(activator, "play player/orch_hit_csharp_short.wav"); - PrintHintText(activator, "%N refused your request", target); - lastHatRequestTime[activator] = GetTime(); - } - } else if (action == MenuAction_End) - delete menu; - return 0; -} - -bool IsHatsEnabled(int client) { - return (cvar_sm_hats_enabled.IntValue == 1 && GetUserAdmin(client) != INVALID_ADMIN_ID) || cvar_sm_hats_enabled.IntValue == 2 -} - -void ClearHats() { - for(int i = 1; i <= MaxClients; i++) { - if(HasHat(i)) { - ClearHat(i, false); - } - if(IsClientConnected(i) && IsClientInGame(i)) SetEntityMoveType(i, MOVETYPE_WALK); - } -} -void ClearHat(int i, bool restore = false) { - - int entity = EntRefToEntIndex(hatData[i].entity); - int visibleEntity = EntRefToEntIndex(hatData[i].visibleEntity); - int modifyEntity = HasFlag(i, HAT_REVERSED) ? i : entity; - - if(visibleEntity > 0) { - SDKUnhook(visibleEntity, SDKHook_SetTransmit, OnVisibleTransmit); - RemoveEntity(visibleEntity); - } - if(modifyEntity > 0) { - SDKUnhook(modifyEntity, SDKHook_SetTransmit, OnRealTransmit); - ClearParent(modifyEntity); - } else { - return; - } - - int flags = GetEntityFlags(entity) & ~FL_FROZEN; - SetEntityFlags(entity, flags); - // if(HasEntProp(entity, Prop_Send, "m_flModelScale")) - // SetEntPropFloat(entity, Prop_Send, "m_flModelScale", 1.0); - SetEntProp(modifyEntity, Prop_Send, "m_CollisionGroup", hatData[i].collisionGroup); - // SetEntProp(modifyEntity, Prop_Send, "m_nSolidType", hatData[i].solidType); - SetEntProp(modifyEntity, Prop_Send, "movetype", hatData[i].moveType); - - hatData[i].entity = INVALID_ENT_REFERENCE; - hatData[i].visibleEntity = INVALID_ENT_REFERENCE; - - if(HasFlag(i, HAT_REVERSED)) { - entity = i; - i = modifyEntity; - } - - if(entity <= MAXPLAYERS) { - AcceptEntityInput(entity, "EnableLedgeHang"); - } - if(restore) { - // If hat is a player, override original position to hat wearer's - if(entity <= MAXPLAYERS && HasEntProp(i, Prop_Send, "m_vecOrigin")) { - GetEntPropVector(i, Prop_Send, "m_vecOrigin", hatData[i].orgPos); - } - // Restore to original position - if(HasFlag(i, HAT_REVERSED)) { - TeleportEntity(i, hatData[i].orgPos, hatData[i].orgAng, NULL_VECTOR); - } else { - TeleportEntity(entity, hatData[i].orgPos, hatData[i].orgAng, NULL_VECTOR); - } - } -} - -bool HasHat(int client) { - return GetHat(client) > 0; -} - -int GetHat(int client) { - if(hatData[client].entity == INVALID_ENT_REFERENCE) return -1; - int index = EntRefToEntIndex(hatData[client].entity); - if(index <= 0) return -1; - if(!IsValidEntity(index)) return -1; - return index; -} - -int GetHatter(int client) { - for(int i = 1; i <= MaxClients; i++) { - if(EntRefToEntIndex(hatData[i].entity) == client) { - return i; - } - } - return -1; -} - -bool CanTarget(int victim) { - static char buf[2]; - noHatVictimCookie.Get(victim, buf, sizeof(buf)); - return StringToInt(buf) == 0; -} - -bool IsHatAllowedInSaferoom(int client) { - if(L4D_IsMissionFinalMap()) return true; - if(HasFlag(client, HAT_PRESET)) return true; - char name[32]; - GetEntityClassname(hatData[client].entity, name, sizeof(name)); - // Don't allow non-weapons in saferoom - if(StrEqual(name, "prop_physics") || StrEqual(name, "prop_dynamic")) { - GetEntPropString(hatData[client].entity, Prop_Data, "m_ModelName", name, sizeof(name)); - if(StrContains(name, "gnome") != -1 || StrContains(name, "propanecanist") != -1) { - return true; - } - float mins[3], maxs[3]; - GetEntPropVector(hatData[client].entity, Prop_Data, "m_vecMins", mins); - GetEntPropVector(hatData[client].entity, Prop_Data, "m_vecMaxs", maxs); - PrintToConsoleAll("Dropping hat for %N: prop_something (%s) (min %.0f,%.0f,%.0f) (max %.0f,%.0f,%.0f)", client, name, mins[0], mins[1], mins[2], maxs[0], maxs[1], maxs[2]); - return false; - } else if(StrEqual(name, "player") || StrContains(name, "weapon_") > -1 || StrContains(name, "upgrade_") > -1) { - return true; - } - PrintToConsole(client, "Dropping hat: %s", name); - return false; -} - -bool IsHatAllowed(int client) { - char name[32]; - GetEntityClassname(hatData[client].entity, name, sizeof(name)); - if(StrEqual(name, "prop_physics") || StrEqual(name, "prop_dynamic")) { - GetEntPropString(hatData[client].entity, Prop_Data, "m_ModelName", name, sizeof(name)); - if(StrContains(name, "models/props_vehicles/c130.mdl") != -1) { - return false; - } - } - return true; -} - -bool CanHatBePlaced(int client, const float pos[3]) { - if(cvar_sm_hats_flags.IntValue & view_as(HatConfig_NoSaferoomHats)) { - if(IsHatAllowedInSaferoom(client)) return true; - Address nav = L4D_GetNearestNavArea(pos, 200.0); - if(nav != Address_Null) { - int spawnFlags = L4D_GetNavArea_SpawnAttributes(nav) ; - if(spawnFlags & NAV_SPAWN_CHECKPOINT) { - PrintToServer("\"%L\" tried to place hat in saferoom, denied.", client); - PrintToChat(client, "[Hats] Hat is not allowed in saferoom and has been returned."); - return false; - } - } - } - return true; -} - -void SetFlag(int client, hatFlags flag) { - hatData[client].flags |= view_as(flag); -} - -bool HasFlag(int client, hatFlags flag) { - return hatData[client].flags & view_as(flag) != 0; -} - -void EquipHat(int client, int entity, const char[] classname = "", int flags = HAT_NONE, const char[] attachPoint = "eyes") { - if(HasHat(client)) - ClearHat(client, true); - - // Player specific tweaks - int visibleEntity; - if(entity == 0) { - ThrowError("Attempted to equip world (client = %d)", client); - return; - } - - hatData[client].entity = EntIndexToEntRef(entity); - int modifyEntity = HasFlag(client, HAT_REVERSED) ? client : entity; - hatData[client].collisionGroup = GetEntProp(modifyEntity, Prop_Send, "m_CollisionGroup"); - hatData[client].solidType = GetEntProp(modifyEntity, Prop_Send, "m_nSolidType"); - hatData[client].moveType = GetEntProp(modifyEntity, Prop_Send, "movetype"); - strcopy(hatData[client].attachPoint, 32, attachPoint); - - if(client <= MaxClients) SDKHook(client, SDKHook_OnTakeDamageAlive, OnTakeDamageAlive); - if(entity <= MaxClients) SDKHook(entity, SDKHook_OnTakeDamageAlive, OnTakeDamageAlive); - - if(modifyEntity <= MaxClients) { - AcceptEntityInput(modifyEntity, "DisableLedgeHang"); - } else if(cvar_sm_hats_flags.IntValue & view_as(HatConfig_FakeHat)) { - return; - // char model[64]; - // GetEntPropString(entity, Prop_Data, "m_ModelName", model, sizeof(model)); - // visibleEntity = CreateEntityByName("prop_dynamic"); - // DispatchKeyValue(visibleEntity, "model", model); - // DispatchKeyValue(visibleEntity, "disableshadows", "1"); - // DispatchSpawn(visibleEntity); - // SetEntProp(visibleEntity, Prop_Send, "m_CollisionGroup", 1); - // hatData[client].visibleEntity = EntIndexToEntRef(visibleEntity); - // SDKHook(visibleEntity, SDKHook_SetTransmit, OnVisibleTransmit); - // SDKHook(entity, SDKHook_SetTransmit, OnRealTransmit); - } - SDKHook(client, SDKHook_OnTakeDamageAlive, OnTakeDamageAlive); - // Temp remove the hat to be yoinked by another player - for(int i = 1; i <= MaxClients; i++) { - if(i != client && EntRefToEntIndex(hatData[i].entity) == entity) { - ClearHat(i); - } - } - - // Called on initial hat - if(classname[0] != '\0') { - if(entity <= MaxClients && !IsFakeClient(entity)) { - PrintToChat(entity, "[Hats] %N has hatted you, type /hat to dismount at any time", client); - } - - // Reset things: - hatData[client].flags = 0; - hatData[client].offset[0] = hatData[client].offset[1] = hatData[client].offset[2] = 0.0; - hatData[client].angles[0] = hatData[client].angles[1] = hatData[client].angles[2] = 0.0; - - if(flags & view_as(HAT_PRESET)) { - hatData[client].flags |= view_as(HAT_PRESET); - } - - if(modifyEntity <= MaxClients) { - if(HasFlag(client, HAT_REVERSED)) { - hatData[client].offset[2] += 7.2; - } else { - hatData[client].offset[2] += 4.2; - } - } else { - float mins[3], maxs[3]; - GetEntPropVector(modifyEntity, Prop_Send, "m_vecMaxs", maxs); - GetEntPropVector(modifyEntity, Prop_Send, "m_vecMins", mins); - PrintToServer("%s mins: %f height:%f", classname, mins[2], maxs[2] - mins[2]); - if(StrContains(classname, "weapon_molotov") > -1 || StrContains(classname, "weapon_pipe_bomb") > -1 || StrContains(classname, "weapon_vomitjar") > -1) { - hatData[client].offset[2] += 10.0; - } else { - hatData[client].offset[2] += 10.0 + mins[2]; - } - } - - if(cvar_sm_hats_flags.IntValue & view_as(HatConfig_ReversedHats) && flags & view_as(HAT_REVERSED)) { - SetFlag(client, HAT_REVERSED); - if(StrEqual(classname, "infected") || (entity <= MaxClients && IsFakeClient(entity))) { - SetFlag(client, HAT_COMMANDABLE); - } - PrintToChat(client, "[Hats] Set yourself as %s (%d)'s hat", classname, entity); - if(entity <= MaxClients) { - LogAction(client, entity, "\"%L\" made themselves \"%L\" (%s)'s hat (%d, %d)", client, entity, classname, entity, visibleEntity); - PrintToChat(entity, "[Hats] %N has set themselves as your hat", client); - } - } else { - // TODO: freeze tank - if(StrEqual(classname, "infected") || StrEqual(classname, "witch") || (entity <= MaxClients && GetClientTeam(entity) == 3 && L4D2_GetPlayerZombieClass(entity) == L4D2ZombieClass_Tank)) { - int eflags = GetEntityFlags(entity) | FL_FROZEN; - SetEntityFlags(entity, eflags); - hatData[client].offset[2] = 36.0; - } - if(entity <= MaxClients) - PrintToChat(client, "[Hats] Set %N (%d) as a hat", entity, entity); - else - PrintToChat(client, "[Hats] Set %s (%d) as a hat", classname, entity); - if(entity <= MaxClients) - LogAction(client, entity, "\"%L\" picked up \"%L\" (%s) as a hat (%d, %d)", client, entity, classname, entity, visibleEntity); - else - LogAction(client, -1, "\"%L\" picked up %s as a hat (%d, %d)", client, classname, entity, visibleEntity); - } - hatData[client].scale = -1.0; - - } - AcceptEntityInput(modifyEntity, "DisableMotion"); - - // Get the data (position, angle, movement shit) - - GetEntPropVector(modifyEntity, Prop_Send, "m_vecOrigin", hatData[client].orgPos); - GetEntPropVector(modifyEntity, Prop_Send, "m_angRotation", hatData[client].orgAng); - hatData[client].collisionGroup = GetEntProp(modifyEntity, Prop_Send, "m_CollisionGroup"); - hatData[client].solidType = GetEntProp(modifyEntity, Prop_Send, "m_nSolidType"); - hatData[client].moveType = GetEntProp(modifyEntity, Prop_Send, "movetype"); - - - if(!HasFlag(client, HAT_POCKET)) { - // TeleportEntity(entity, EMPTY_ANG, EMPTY_ANG, NULL_VECTOR); - if(HasFlag(client, HAT_REVERSED)) { - SetParent(client, entity); - if(StrEqual(classname, "infected")) { - SetParentAttachment(modifyEntity, "head", true); - TeleportEntity(modifyEntity, hatData[client].offset, hatData[client].angles, NULL_VECTOR); - SetParentAttachment(modifyEntity, "head", true); - } else { - SetParentAttachment(modifyEntity, attachPoint, true); - TeleportEntity(modifyEntity, hatData[client].offset, hatData[client].angles, NULL_VECTOR); - SetParentAttachment(modifyEntity, attachPoint, true); - } - - if(HasFlag(client, HAT_COMMANDABLE)) { - ChooseRandomPosition(hatData[client].offset); - L4D2_CommandABot(entity, client, BOT_CMD_MOVE, hatData[client].offset); - } - } else { - SetParent(entity, client); - SetParentAttachment(modifyEntity, attachPoint, true); - TeleportEntity(modifyEntity, hatData[client].offset, hatData[client].angles, NULL_VECTOR); - SetParentAttachment(modifyEntity, attachPoint, true); - } - - if(visibleEntity > 0) { - SetParent(visibleEntity, client); - SetParentAttachment(visibleEntity, attachPoint, true); - hatData[client].offset[2] += 10.0; - TeleportEntity(visibleEntity, hatData[client].offset, hatData[client].angles, NULL_VECTOR); - SetParentAttachment(visibleEntity, attachPoint, true); - #if defined DEBUG_HAT_SHOW_FAKE - L4D2_SetEntityGlow(visibleEntity, L4D2Glow_Constant, 0, 0, color2, false); - #endif - } - - #if defined DEBUG_HAT_SHOW_FAKE - L4D2_SetEntityGlow(modifyEntity, L4D2Glow_Constant, 0, 0, color, false); - #endif - - // SetEntProp(modifyEntity, Prop_Send, "m_nSolidType", 0); - SetEntProp(modifyEntity, Prop_Send, "m_CollisionGroup", 1); - SetEntProp(modifyEntity, Prop_Send, "movetype", MOVETYPE_NONE); - } -} +enum hatFlags { + HAT_NONE = 0, + HAT_POCKET = 1, + HAT_REVERSED = 2, + HAT_COMMANDABLE = 4, + HAT_RAINBOW = 8, + HAT_PRESET = 16 +} +enum struct HatInstance { + int entity; // The entity REFERENCE + int visibleEntity; // Thee visible entity REF + Handle yeetGroundTimer; + + // Original data for entity + float orgPos[3]; + float orgAng[3]; + float offset[3]; + float angles[3]; + int collisionGroup; + int solidType; + int moveType; + + float scale; + int flags; + float rainbowColor[3]; + int rainbowTicks; + bool rainbowReverse; + char attachPoint[32]; +} +enum hatFeatures { + HatConfig_None = 0, + HatConfig_PlayerHats = 1, + HatConfig_RespectAdminImmunity = 2, + HatConfig_FakeHat = 4, + HatConfig_NoSaferoomHats = 8, + HatConfig_PlayerHatConsent = 16, + HatConfig_InfectedHats = 32, + HatConfig_ReversedHats = 64, + HatConfig_DeleteThrownHats = 128 +} +char ActivePreset[MAXPLAYERS+1][32]; +int lastHatRequestTime[MAXPLAYERS+1]; +HatInstance hatData[MAXPLAYERS+1]; +StringMap g_HatPresets; + +#define MAX_FORBIDDEN_CLASSNAMES 15 +char FORBIDDEN_CLASSNAMES[MAX_FORBIDDEN_CLASSNAMES][] = { + "prop_door_rotating_checkpoint", + "env_physics_blocker", + "env_player_blocker", + "func_brush", + "func_simpleladder", + "prop_door_rotating", + "func_button", + "func_elevator", + "func_button_timed", + "func_tracktrain", + "func_movelinear", + // "infected", + "func_lod", + "func_door", + "prop_ragdoll", + "move_rope" +}; + +#define MAX_FORBIDDEN_MODELS 2 +char FORBIDDEN_MODELS[MAX_FORBIDDEN_MODELS][] = { + "models/props_vehicles/c130.mdl", + "models/props_vehicles/helicopter_rescue.mdl" +}; + + +#define MAX_REVERSE_CLASSNAMES 2 +// Classnames that should automatically trigger reverse infected +static char REVERSE_CLASSNAMES[MAX_REVERSE_CLASSNAMES][] = { + "infected", + "func_movelinear", +}; + +Action Command_DoAHat(int client, int args) { + int hatter = GetHatter(client); + if(hatter > 0) { + ClearHat(hatter, HasFlag(hatter, HAT_REVERSED)); + PrintToChat(hatter, "[Hats] %N has unhatted themselves", client); + return Plugin_Handled; + } + + static char cmdName[8]; + GetCmdArg(0, cmdName, sizeof(cmdName)); + AdminId adminId = GetUserAdmin(client); + bool isForced = adminId != INVALID_ADMIN_ID && StrEqual(cmdName, "sm_hatf"); + if(cvar_sm_hats_enabled.IntValue == 1) { + if(adminId == INVALID_ADMIN_ID) { + PrintToChat(client, "[Hats] Hats are for admins only"); + return Plugin_Handled; + } else if(!adminId.HasFlag(Admin_Custom2)) { + PrintToChat(client, "[Hats] You do not have permission"); + return Plugin_Handled; + } + } else if(cvar_sm_hats_enabled.IntValue == 0) { + ReplyToCommand(client, "[Hats] Hats are disabled"); + return Plugin_Handled; + } else if(GetClientTeam(client) != 2 && ~cvar_sm_hats_flags.IntValue & view_as(HatConfig_InfectedHats)) { + PrintToChat(client, "[Hats] Hats are only available for survivors."); + return Plugin_Handled; + } + + int entity = GetHat(client); + if(entity > 0) { + if(HasFlag(client, HAT_PRESET)) { + PrintToChat(client, "[Hats] Your hat is a preset, use /hatp to remove it."); + return Plugin_Handled; + } + char arg[4]; + GetCmdArg(1, arg, sizeof(arg)); + if(arg[0] == 'e') { + ReplyToCommand(client, "\t\t\"origin\"\t\"%f %f %f\"", hatData[client].offset[0], hatData[client].offset[1], hatData[client].offset[2]); + ReplyToCommand(client, "\t\t\"angles\"\t\"%f %f %f\"", hatData[client].angles[0], hatData[client].angles[1], hatData[client].angles[2]); + return Plugin_Handled; + } else if(arg[0] == 'v') { + ReplyToCommand(client, "Flags: %d", hatData[client].flags); + // ReplyToCommand(client, "CurOffset: %f %f %f", ); + return Plugin_Handled; + } else if(arg[0] == 'a') { + ShowAttachPointMenu(client); + return Plugin_Handled; + } + // int orgEntity = entity; + if(HasFlag(client, HAT_REVERSED)) { + entity = client; + } + ClearParent(entity); + + if(arg[0] == 's') { + char sizeStr[4]; + GetCmdArg(2, sizeStr, sizeof(sizeStr)); + float size = StringToFloat(sizeStr); + if(size == 0.0) { + ReplyToCommand(client, "[Hats] Invalid size"); + return Plugin_Handled; + } + if(HasEntProp(entity, Prop_Send, "m_flModelScale")) + SetEntPropFloat(entity, Prop_Send, "m_flModelScale", size); + else + PrintHintText(client, "Hat does not support scaling"); + // Change the size of it's parent instead + int child = -1; + while((child = FindEntityByClassname(child, "*")) != INVALID_ENT_REFERENCE ) + { + int parent = GetEntPropEnt(child, Prop_Data, "m_pParent"); + if(parent == entity) { + if(HasEntProp(child, Prop_Send, "m_flModelScale")) { + PrintToConsole(client, "found child %d for %d", child, entity); + SetEntPropFloat(child, Prop_Send, "m_flModelScale", size); + } else { + PrintToChat(client, "Child %d for %d cannot be scaled", child, entity); + } + break; + } + } + // Reattach entity: + EquipHat(client, entity); + return Plugin_Handled; + } else if(arg[0] == 'r' && arg[1] == 'a') { + SetFlag(client, HAT_RAINBOW); + hatData[client].rainbowTicks = 0; + hatData[client].rainbowReverse = false; + hatData[client].rainbowColor[0] = 0.0; + hatData[client].rainbowColor[1] = 255.0; + hatData[client].rainbowColor[2] = 255.0; + EquipHat(client, entity); + ReplyToCommand(client, "Rainbow hats enabled"); + return Plugin_Handled; + } + + // Re-enable physics and restore collision/solidity + AcceptEntityInput(entity, "EnableMotion"); + SetEntProp(entity, Prop_Send, "m_CollisionGroup", hatData[client].collisionGroup); + SetEntProp(entity, Prop_Send, "m_nSolidType", hatData[client].solidType); + + // Remove frozen flag (only "infected" and "witch" are frozen, but just incase:) + int flags = GetEntityFlags(entity) & ~FL_FROZEN; + SetEntityFlags(entity, flags); + + // Clear visible hats (HatConfig_FakeHat is enabled) + int visibleEntity = EntRefToEntIndex(hatData[client].visibleEntity); + SDKUnhook(entity, SDKHook_SetTransmit, OnRealTransmit); + if(visibleEntity > 0) { + SDKUnhook(visibleEntity, SDKHook_SetTransmit, OnVisibleTransmit); + RemoveEntity(visibleEntity); + hatData[client].visibleEntity = INVALID_ENT_REFERENCE; + } + // Grant temp god & remove after time + tempGod[client] = true; + if(client <= MaxClients) { + SDKHook(client, SDKHook_OnTakeDamageAlive, OnTakeDamageAlive); + CreateTimer(2.0, Timer_RemoveGod, GetClientUserId(client)); + } + if(entity <= MaxClients) { + SDKHook(entity, SDKHook_OnTakeDamageAlive, OnTakeDamageAlive); + CreateTimer(2.0, Timer_RemoveGod, GetClientUserId(entity)); + } + + // Restore movement: + if(entity <= MaxClients) { + // If player, remove roll & and just default to WALK movetype + hatData[client].orgAng[2] = 0.0; + SetEntityMoveType(entity, MOVETYPE_WALK); + } else { + // If not player, then just use whatever they were pre-hat + SetEntProp(entity, Prop_Send, "movetype", hatData[client].moveType); + } + + if(arg[0] == 'y') { // Hat yeeting: + char classname[16]; + GetEntityClassname(entity, classname, sizeof(classname)); + if(StrEqual(classname, "prop_dynamic")) { + ReplyToCommand(client, "You cannot yeet this prop (it has no physics)"); + return Plugin_Handled; + } + GetClientEyeAngles(client, hatData[client].orgAng); + GetClientAbsOrigin(client, hatData[client].orgPos); + hatData[client].orgPos[2] += 45.0; + float ang[3], vel[3]; + + // Calculate the angle to throw at + GetClientEyeAngles(client, ang); + ang[2] = 0.0; + if(ang[0] > 0.0) ang[0] = -ang[0]; + // ang[0] = -45.0; + + // Calculate velocity to throw based on direction + vel[0] = Cosine(DegToRad(ang[1])) * GetRandomFloat(1300.0, 1700.0); + vel[1] = Sine(DegToRad(ang[1])) * GetRandomFloat(1300.0, 1700.0); + vel[2] = GetRandomFloat(700.0, 900.0); + if(entity <= MaxClients) { + // For players, use the built in fling function + TeleportEntity(entity, hatData[client].orgPos, hatData[client].orgAng, NULL_VECTOR); + L4D2_CTerrorPlayer_Fling(entity, client, vel); + } /*else if(visibleEntity > 0) { + PrintToChat(client, "Yeeting fake car..."); + ClearParent(visibleEntity); + + SetEntProp(visibleEntity, Prop_Send, "movetype", 6); + + AcceptEntityInput(visibleEntity, "EnableMotion"); + + TeleportEntity(entity, OUT_OF_BOUNDS, hatData[client].orgAng, NULL_VECTOR); + TeleportEntity(visibleEntity, hatData[client].orgPos, hatData[client].orgAng, vel); + DataPack pack; + CreateDataTimer(4.0, Timer_PropYeetEnd, pack); + pack.WriteCell(hatData[client].entity); + pack.WriteCell(hatData[client].visibleEntity); + pack.WriteCell(hatData[client].collisionGroup); + pack.WriteCell(hatData[client].solidType); + pack.WriteCell(hatData[client].moveType); + hatData[client].visibleEntity = INVALID_ENT_REFERENCE; + hatData[client].entity = INVALID_ENT_REFERENCE; + } */ else { + // For actual props, offset it 35 units above and 80 units infront to reduce collision-incaps and then throw + GetHorizontalPositionFromClient(client, 80.0, hatData[client].orgPos); + hatData[client].orgPos[2] += 35.0; + TeleportEntity(entity, hatData[client].orgPos, hatData[client].orgAng, vel); + // Sleep the physics after enoug time for it to most likely have landed + if(hatData[client].yeetGroundTimer != null) { + // TODO: FIX null issue + delete hatData[client].yeetGroundTimer; + } + DataPack pack1; + hatData[client].yeetGroundTimer = CreateDataTimer(0.5, Timer_GroundKill, pack1, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); + pack1.WriteCell(hatData[client].entity); + pack1.WriteCell(GetClientUserId(client)); + DataPack pack2; + CreateDataTimer(7.7, Timer_PropSleep, pack2); + pack2.WriteCell(hatData[client].entity); + pack2.WriteCell(GetClientUserId(client)); + } + PrintToChat(client, "[Hats] Yeeted hat"); + hatData[client].entity = INVALID_ENT_REFERENCE; + return Plugin_Handled; + } else if(arg[0] == 'c') { + float pos[3]; + // Grabs a cursor position with some checks to prevent placing into (in)visible walls + if(GetSmartCursorLocation(client, pos)) { + if(CanHatBePlaced(client, pos)) { + if(entity <= MaxClients) + L4D_WarpToValidPositionIfStuck(entity); + hatData[client].orgPos = pos; + TeleportEntity(entity, hatData[client].orgPos, hatData[client].orgAng, NULL_VECTOR); + PrintToChat(client, "[Hats] Placed hat on cursor."); + } + } else { + PrintToChat(client, "[Hats] Could not find cursor position."); + } + } else if(arg[0] == 'p' || (entity <= MaxClients && arg[0] != 'r')) { + // Place the hat down on the cursor if specified OR if entity is hat + float pos[3], ang[3]; + if(HasFlag(client, HAT_REVERSED)) { + // If we are reversed, then place ourselves where our "hatter" is + GetClientEyePosition(entity, hatData[client].orgPos); + GetClientEyeAngles(entity, hatData[client].orgAng); + TeleportEntity(client, hatData[client].orgPos, hatData[client].orgAng, NULL_VECTOR); + PrintToChat(entity, "[Hats] Placed hat in front of you."); + } else { + // If we are normal, then get position infront of us, offset by model size + GetClientEyePosition(client, pos); + GetClientEyeAngles(client, ang); + GetHorizontalPositionFromOrigin(pos, ang, 80.0, pos); + ang[0] = 0.0; + float mins[3]; + GetEntPropVector(entity, Prop_Data, "m_vecMins", mins); + pos[2] += mins[2]; + // Find the nearest ground (raytrace bottom->up) + FindGround(pos, pos); + // Check if the destination is acceptable (not saferooms if enabled) + if(CanHatBePlaced(client, pos)) { + hatData[client].orgPos = pos; + hatData[client].orgAng = ang; + TeleportEntity(entity, hatData[client].orgPos, hatData[client].orgAng, NULL_VECTOR); + PrintToChat(client, "[Hats] Placed hat in front of you."); + } + } + } else { + PrintToChat(client, "[Hats] Restored hat to its original position."); + } + + // Restore the scale pre-hat + if(hatData[client].scale > 0 && HasEntProp(entity, Prop_Send, "m_flModelScale")) + SetEntPropFloat(entity, Prop_Send, "m_flModelScale", hatData[client].scale); + + // If no other options performed, then restore to original position and remove our reference + AcceptEntityInput(entity, "Sleep"); + TeleportEntity(entity, hatData[client].orgPos, hatData[client].orgAng, NULL_VECTOR); + hatData[client].entity = INVALID_ENT_REFERENCE; + } else { + // Find a new hatable entity + int flags = 0; + entity = GetLookingEntity(client, Filter_ValidHats); + if(entity <= 0) { + PrintCenterText(client, "[Hats] No entity found"); + return Plugin_Handled; + } else if(false) { + // TODO: editor API call to check + // Prevent making an entity you editing a hat + return Plugin_Handled; + } else if(!isForced && cvar_sm_hats_max_distance.FloatValue > 0.0 && entity >= MaxClients) { + float posP[3], posE[3]; + GetClientEyePosition(client, posP); + GetEntPropVector(entity, Prop_Data, "m_vecOrigin", posE); + if(GetVectorDistance(posP, posE) > cvar_sm_hats_max_distance.FloatValue) { + PrintCenterText(client, "[Hats] Entity too far away"); + return Plugin_Handled; + } + } + + // Make hat reversed if 'r' passed in + char arg[4]; + if(args > 0) { + GetCmdArg(1, arg, sizeof(arg)); + if(arg[0] == 'r') { + flags |= view_as(HAT_REVERSED); + } + } + + int parent = GetEntPropEnt(entity, Prop_Data, "m_hParent"); + if(parent > 0 && entity > MaxClients) { + PrintToConsole(client, "[Hats] Selected a child entity, selecting parent (child %d -> parent %d)", entity, parent); + entity = parent; + } else if(entity <= MaxClients) { // Checks for hatting a player entity + if(IsFakeClient(entity) && L4D_GetIdlePlayerOfBot(entity) > 0) { + PrintToChat(client, "[Hats] Cannot hat idle bots"); + return Plugin_Handled; + } else if(!isForced && GetClientTeam(entity) != 2 && ~cvar_sm_hats_flags.IntValue & view_as(HatConfig_InfectedHats)) { + PrintToChat(client, "[Hats] Cannot make enemy a hat... it's dangerous"); + return Plugin_Handled; + } else if(false) { + // TODO: use editor API to check + // Old check left in in case you hatting child entity + PrintToChat(client, "[Hats] You are currently editing this entity"); + return Plugin_Handled; + } else if(inSaferoom[client] && cvar_sm_hats_flags.IntValue & view_as(HatConfig_NoSaferoomHats)) { + PrintToChat(client, "[Hats] Hats are not allowed in the saferoom"); + return Plugin_Handled; + } else if(!IsPlayerAlive(entity) || GetEntProp(entity, Prop_Send, "m_isHangingFromLedge") || L4D_IsPlayerCapped(entity)) { + PrintToChat(client, "[Hats] Player is either dead, hanging, or in the process of dying."); + return Plugin_Handled; + } else if(EntRefToEntIndex(hatData[entity].entity) == entity || EntRefToEntIndex(hatData[entity].entity) == client) { + PrintToChat(client, "[Hats] Woah you can't be making a black hole, jesus be careful."); + return Plugin_Handled; + } else if(~cvar_sm_hats_flags.IntValue & view_as(HatConfig_PlayerHats)) { + PrintToChat(client, "[Hats] Player hats are disabled"); + return Plugin_Handled; + } else if(!CanTarget(entity)) { + PrintToChat(client, "[Hats] Player has disabled player hats for themselves."); + return Plugin_Handled; + } else if(!CanTarget(client)) { + PrintToChat(client, "[Hats] Cannot hat a player when you have player hats turned off"); + return Plugin_Handled; + } else if(cvar_sm_hats_flags.IntValue & view_as(HatConfig_RespectAdminImmunity)) { + AdminId targetAdmin = GetUserAdmin(entity); + AdminId clientAdmin = GetUserAdmin(client); + if(targetAdmin != INVALID_ADMIN_ID && clientAdmin == INVALID_ADMIN_ID) { + PrintToChat(client, "[Hats] Cannot target an admin"); + return Plugin_Handled; + } else if(targetAdmin != INVALID_ADMIN_ID && targetAdmin.ImmunityLevel > clientAdmin.ImmunityLevel) { + PrintToChat(client, "[Hats] Cannot target %N, they are immune to you", entity); + return Plugin_Handled; + } + } + if(!isForced && + !IsFakeClient(entity) && + cvar_sm_hats_flags.IntValue & view_as(HatConfig_PlayerHatConsent) && + ~flags & view_as(HAT_REVERSED) + ) { + int lastRequestDiff = GetTime() - lastHatRequestTime[client]; + if(lastRequestDiff < PLAYER_HAT_REQUEST_COOLDOWN) { + PrintToChat(client, "[Hats] Player hat under %d seconds cooldown", PLAYER_HAT_REQUEST_COOLDOWN - lastRequestDiff); + return Plugin_Handled; + } + + Menu menu = new Menu(HatConsentHandler); + menu.SetTitle("%N: Requests to hat you", client); + char id[8]; + Format(id, sizeof(id), "%d|1", GetClientUserId(client)); + menu.AddItem(id, "Accept"); + Format(id, sizeof(id), "%d|0", GetClientUserId(client)); + menu.AddItem(id, "Reject"); + menu.Display(entity, 12); + PrintHintText(client, "Sent hat request to %N", entity); + PrintToChat(entity, "[Hats] %N requests to hat you, 1 to Accept, 2 to Reject. Expires in 12 seconds.", client); + return Plugin_Handled; + } + } + + + char classname[64]; + GetEntityClassname(entity, classname, sizeof(classname)); + + // Check for any class that should always be reversed + if(~flags & view_as(HAT_REVERSED)) { + for(int i = 0; i < MAX_REVERSE_CLASSNAMES; i++) { + if(StrEqual(REVERSE_CLASSNAMES[i], classname)) { + flags |= view_as(HAT_REVERSED); + break; + } + } + } + + EquipHat(client, entity, classname, flags); + } + return Plugin_Handled; +} + + +#define MAX_ATTACHMENT_POINTS 20 +char ATTACHMENT_POINTS[MAX_ATTACHMENT_POINTS][] = { + "eyes", + "molotov", + "pills", + "grenade", + "primary", + "medkit", + "melee", + "survivor_light", + "bleedout", + "forward", + "survivor_neck", + "muzzle_flash", + "spine", + "legL", + "legR", + "thighL", + "thighR", + "lfoot", + "rfoot", + "mouth", +}; + +void ShowAttachPointMenu(int client) { + Menu menu = new Menu(AttachPointHandler); + menu.SetTitle("Choose an attach point"); + for(int i = 0; i < MAX_ATTACHMENT_POINTS; i++) { + menu.AddItem(ATTACHMENT_POINTS[i], ATTACHMENT_POINTS[i]); + } + menu.Display(client, 0); +} + +int AttachPointHandler(Menu menu, MenuAction action, int client, int param2) { + if (action == MenuAction_Select) { + char attachPoint[32]; + menu.GetItem(param2, attachPoint, sizeof(attachPoint)); + if(!HasHat(client)) { + ReplyToCommand(client, "No hat is equipped"); + } else { + int hat = GetHat(client); + char classname[32]; + GetEntityClassname(hat, classname, sizeof(classname)); + EquipHat(client, hat, classname, hatData[client].flags, attachPoint); + CReplyToCommand(client, "Attachment point set to {olive}%s", attachPoint); + } + } else if (action == MenuAction_End) + delete menu; + return 0; +} + +// Handles consent that a person to be hatted by another player +int HatConsentHandler(Menu menu, MenuAction action, int target, int param2) { + if (action == MenuAction_Select) { + char info[8]; + menu.GetItem(param2, info, sizeof(info)); + char str[2][8]; + ExplodeString(info, "|", str, 2, 8, false); + int activator = GetClientOfUserId(StringToInt(str[0])); + int hatAction = StringToInt(str[1]); + if(activator == 0) { + ReplyToCommand(target, "Player has disconnected"); + return 0; + } else if(hatAction == 1) { + if(EntRefToEntIndex(hatData[target].entity) == activator ) + PrintToChat(activator, "[Hats] Woah you can't be making a black hole, jesus be careful."); + else + EquipHat(activator, target, "player", 0); + } else { + ClientCommand(activator, "play player/orch_hit_csharp_short.wav"); + PrintHintText(activator, "%N refused your request", target); + lastHatRequestTime[activator] = GetTime(); + } + } else if (action == MenuAction_End) + delete menu; + return 0; +} + +bool IsHatsEnabled(int client) { + return (cvar_sm_hats_enabled.IntValue == 1 && GetUserAdmin(client) != INVALID_ADMIN_ID) || cvar_sm_hats_enabled.IntValue == 2 +} + +void ClearHats() { + for(int i = 1; i <= MaxClients; i++) { + if(HasHat(i)) { + ClearHat(i, false); + } + if(IsClientConnected(i) && IsClientInGame(i)) SetEntityMoveType(i, MOVETYPE_WALK); + } +} +void ClearHat(int i, bool restore = false) { + + int entity = EntRefToEntIndex(hatData[i].entity); + int visibleEntity = EntRefToEntIndex(hatData[i].visibleEntity); + int modifyEntity = HasFlag(i, HAT_REVERSED) ? i : entity; + + if(visibleEntity > 0) { + SDKUnhook(visibleEntity, SDKHook_SetTransmit, OnVisibleTransmit); + RemoveEntity(visibleEntity); + } + if(modifyEntity > 0) { + SDKUnhook(modifyEntity, SDKHook_SetTransmit, OnRealTransmit); + ClearParent(modifyEntity); + } else { + return; + } + + int flags = GetEntityFlags(entity) & ~FL_FROZEN; + SetEntityFlags(entity, flags); + // if(HasEntProp(entity, Prop_Send, "m_flModelScale")) + // SetEntPropFloat(entity, Prop_Send, "m_flModelScale", 1.0); + SetEntProp(modifyEntity, Prop_Send, "m_CollisionGroup", hatData[i].collisionGroup); + // SetEntProp(modifyEntity, Prop_Send, "m_nSolidType", hatData[i].solidType); + SetEntProp(modifyEntity, Prop_Send, "movetype", hatData[i].moveType); + + hatData[i].entity = INVALID_ENT_REFERENCE; + hatData[i].visibleEntity = INVALID_ENT_REFERENCE; + + if(HasFlag(i, HAT_REVERSED)) { + entity = i; + i = modifyEntity; + } + + if(entity <= MAXPLAYERS) { + AcceptEntityInput(entity, "EnableLedgeHang"); + } + if(restore) { + // If hat is a player, override original position to hat wearer's + if(entity <= MAXPLAYERS && HasEntProp(i, Prop_Send, "m_vecOrigin")) { + GetEntPropVector(i, Prop_Send, "m_vecOrigin", hatData[i].orgPos); + } + // Restore to original position + if(HasFlag(i, HAT_REVERSED)) { + TeleportEntity(i, hatData[i].orgPos, hatData[i].orgAng, NULL_VECTOR); + } else { + TeleportEntity(entity, hatData[i].orgPos, hatData[i].orgAng, NULL_VECTOR); + } + } +} + +bool HasHat(int client) { + return GetHat(client) > 0; +} + +int GetHat(int client) { + if(hatData[client].entity == INVALID_ENT_REFERENCE) return -1; + int index = EntRefToEntIndex(hatData[client].entity); + if(index <= 0) return -1; + if(!IsValidEntity(index)) return -1; + return index; +} + +int GetHatter(int client) { + for(int i = 1; i <= MaxClients; i++) { + if(EntRefToEntIndex(hatData[i].entity) == client) { + return i; + } + } + return -1; +} + +bool CanTarget(int victim) { + static char buf[2]; + noHatVictimCookie.Get(victim, buf, sizeof(buf)); + return StringToInt(buf) == 0; +} + +bool IsHatAllowedInSaferoom(int client) { + if(L4D_IsMissionFinalMap()) return true; + if(HasFlag(client, HAT_PRESET)) return true; + char name[32]; + GetEntityClassname(hatData[client].entity, name, sizeof(name)); + // Don't allow non-weapons in saferoom + if(StrEqual(name, "prop_physics") || StrEqual(name, "prop_dynamic")) { + GetEntPropString(hatData[client].entity, Prop_Data, "m_ModelName", name, sizeof(name)); + if(StrContains(name, "gnome") != -1 || StrContains(name, "propanecanist") != -1) { + return true; + } + float mins[3], maxs[3]; + GetEntPropVector(hatData[client].entity, Prop_Data, "m_vecMins", mins); + GetEntPropVector(hatData[client].entity, Prop_Data, "m_vecMaxs", maxs); + PrintToConsoleAll("Dropping hat for %N: prop_something (%s) (min %.0f,%.0f,%.0f) (max %.0f,%.0f,%.0f)", client, name, mins[0], mins[1], mins[2], maxs[0], maxs[1], maxs[2]); + return false; + } else if(StrEqual(name, "player") || StrContains(name, "weapon_") > -1 || StrContains(name, "upgrade_") > -1) { + return true; + } + PrintToConsole(client, "Dropping hat: %s", name); + return false; +} + +bool IsHatAllowed(int client) { + char name[32]; + GetEntityClassname(hatData[client].entity, name, sizeof(name)); + if(StrEqual(name, "prop_physics") || StrEqual(name, "prop_dynamic")) { + GetEntPropString(hatData[client].entity, Prop_Data, "m_ModelName", name, sizeof(name)); + if(StrContains(name, "models/props_vehicles/c130.mdl") != -1) { + return false; + } + } + return true; +} + +bool CanHatBePlaced(int client, const float pos[3]) { + if(cvar_sm_hats_flags.IntValue & view_as(HatConfig_NoSaferoomHats)) { + if(IsHatAllowedInSaferoom(client)) return true; + Address nav = L4D_GetNearestNavArea(pos, 200.0); + if(nav != Address_Null) { + int spawnFlags = L4D_GetNavArea_SpawnAttributes(nav) ; + if(spawnFlags & NAV_SPAWN_CHECKPOINT) { + PrintToServer("\"%L\" tried to place hat in saferoom, denied.", client); + PrintToChat(client, "[Hats] Hat is not allowed in saferoom and has been returned."); + return false; + } + } + } + return true; +} + +void SetFlag(int client, hatFlags flag) { + hatData[client].flags |= view_as(flag); +} + +bool HasFlag(int client, hatFlags flag) { + return hatData[client].flags & view_as(flag) != 0; +} + +void EquipHat(int client, int entity, const char[] classname = "", int flags = HAT_NONE, const char[] attachPoint = "eyes") { + if(HasHat(client)) + ClearHat(client, true); + + // Player specific tweaks + int visibleEntity; + if(entity == 0) { + ThrowError("Attempted to equip world (client = %d)", client); + return; + } + + hatData[client].entity = EntIndexToEntRef(entity); + int modifyEntity = HasFlag(client, HAT_REVERSED) ? client : entity; + hatData[client].collisionGroup = GetEntProp(modifyEntity, Prop_Send, "m_CollisionGroup"); + hatData[client].solidType = GetEntProp(modifyEntity, Prop_Send, "m_nSolidType"); + hatData[client].moveType = GetEntProp(modifyEntity, Prop_Send, "movetype"); + strcopy(hatData[client].attachPoint, 32, attachPoint); + + if(client <= MaxClients) SDKHook(client, SDKHook_OnTakeDamageAlive, OnTakeDamageAlive); + if(entity <= MaxClients) SDKHook(entity, SDKHook_OnTakeDamageAlive, OnTakeDamageAlive); + + if(modifyEntity <= MaxClients) { + AcceptEntityInput(modifyEntity, "DisableLedgeHang"); + } else if(cvar_sm_hats_flags.IntValue & view_as(HatConfig_FakeHat)) { + return; + // char model[64]; + // GetEntPropString(entity, Prop_Data, "m_ModelName", model, sizeof(model)); + // visibleEntity = CreateEntityByName("prop_dynamic"); + // DispatchKeyValue(visibleEntity, "model", model); + // DispatchKeyValue(visibleEntity, "disableshadows", "1"); + // DispatchSpawn(visibleEntity); + // SetEntProp(visibleEntity, Prop_Send, "m_CollisionGroup", 1); + // hatData[client].visibleEntity = EntIndexToEntRef(visibleEntity); + // SDKHook(visibleEntity, SDKHook_SetTransmit, OnVisibleTransmit); + // SDKHook(entity, SDKHook_SetTransmit, OnRealTransmit); + } + SDKHook(client, SDKHook_OnTakeDamageAlive, OnTakeDamageAlive); + // Temp remove the hat to be yoinked by another player + for(int i = 1; i <= MaxClients; i++) { + if(i != client && EntRefToEntIndex(hatData[i].entity) == entity) { + ClearHat(i); + } + } + + // Called on initial hat + if(classname[0] != '\0') { + if(entity <= MaxClients && !IsFakeClient(entity)) { + PrintToChat(entity, "[Hats] %N has hatted you, type /hat to dismount at any time", client); + } + + // Reset things: + hatData[client].flags = 0; + hatData[client].offset[0] = hatData[client].offset[1] = hatData[client].offset[2] = 0.0; + hatData[client].angles[0] = hatData[client].angles[1] = hatData[client].angles[2] = 0.0; + + if(flags & view_as(HAT_PRESET)) { + hatData[client].flags |= view_as(HAT_PRESET); + } + + if(modifyEntity <= MaxClients) { + if(HasFlag(client, HAT_REVERSED)) { + hatData[client].offset[2] += 7.2; + } else { + hatData[client].offset[2] += 4.2; + } + } else { + float mins[3], maxs[3]; + GetEntPropVector(modifyEntity, Prop_Send, "m_vecMaxs", maxs); + GetEntPropVector(modifyEntity, Prop_Send, "m_vecMins", mins); + PrintToServer("%s mins: %f height:%f", classname, mins[2], maxs[2] - mins[2]); + if(StrContains(classname, "weapon_molotov") > -1 || StrContains(classname, "weapon_pipe_bomb") > -1 || StrContains(classname, "weapon_vomitjar") > -1) { + hatData[client].offset[2] += 10.0; + } else { + hatData[client].offset[2] += 10.0 + mins[2]; + } + } + + if(cvar_sm_hats_flags.IntValue & view_as(HatConfig_ReversedHats) && flags & view_as(HAT_REVERSED)) { + SetFlag(client, HAT_REVERSED); + if(StrEqual(classname, "infected") || (entity <= MaxClients && IsFakeClient(entity))) { + SetFlag(client, HAT_COMMANDABLE); + } + PrintToChat(client, "[Hats] Set yourself as %s (%d)'s hat", classname, entity); + if(entity <= MaxClients) { + LogAction(client, entity, "\"%L\" made themselves \"%L\" (%s)'s hat (%d, %d)", client, entity, classname, entity, visibleEntity); + PrintToChat(entity, "[Hats] %N has set themselves as your hat", client); + } + } else { + // TODO: freeze tank + if(StrEqual(classname, "infected") || StrEqual(classname, "witch") || (entity <= MaxClients && GetClientTeam(entity) == 3 && L4D2_GetPlayerZombieClass(entity) == L4D2ZombieClass_Tank)) { + int eflags = GetEntityFlags(entity) | FL_FROZEN; + SetEntityFlags(entity, eflags); + hatData[client].offset[2] = 36.0; + } + if(entity <= MaxClients) + PrintToChat(client, "[Hats] Set %N (%d) as a hat", entity, entity); + else + PrintToChat(client, "[Hats] Set %s (%d) as a hat", classname, entity); + if(entity <= MaxClients) + LogAction(client, entity, "\"%L\" picked up \"%L\" (%s) as a hat (%d, %d)", client, entity, classname, entity, visibleEntity); + else + LogAction(client, -1, "\"%L\" picked up %s as a hat (%d, %d)", client, classname, entity, visibleEntity); + } + hatData[client].scale = -1.0; + + } + AcceptEntityInput(modifyEntity, "DisableMotion"); + + // Get the data (position, angle, movement shit) + + GetEntPropVector(modifyEntity, Prop_Send, "m_vecOrigin", hatData[client].orgPos); + GetEntPropVector(modifyEntity, Prop_Send, "m_angRotation", hatData[client].orgAng); + hatData[client].collisionGroup = GetEntProp(modifyEntity, Prop_Send, "m_CollisionGroup"); + hatData[client].solidType = GetEntProp(modifyEntity, Prop_Send, "m_nSolidType"); + hatData[client].moveType = GetEntProp(modifyEntity, Prop_Send, "movetype"); + + + if(!HasFlag(client, HAT_POCKET)) { + // TeleportEntity(entity, EMPTY_ANG, EMPTY_ANG, NULL_VECTOR); + if(HasFlag(client, HAT_REVERSED)) { + SetParent(client, entity); + if(StrEqual(classname, "infected")) { + SetParentAttachment(modifyEntity, "head", true); + TeleportEntity(modifyEntity, hatData[client].offset, hatData[client].angles, NULL_VECTOR); + SetParentAttachment(modifyEntity, "head", true); + } else { + SetParentAttachment(modifyEntity, attachPoint, true); + TeleportEntity(modifyEntity, hatData[client].offset, hatData[client].angles, NULL_VECTOR); + SetParentAttachment(modifyEntity, attachPoint, true); + } + + if(HasFlag(client, HAT_COMMANDABLE)) { + ChooseRandomPosition(hatData[client].offset); + L4D2_CommandABot(entity, client, BOT_CMD_MOVE, hatData[client].offset); + } + } else { + SetParent(entity, client); + SetParentAttachment(modifyEntity, attachPoint, true); + TeleportEntity(modifyEntity, hatData[client].offset, hatData[client].angles, NULL_VECTOR); + SetParentAttachment(modifyEntity, attachPoint, true); + } + + if(visibleEntity > 0) { + SetParent(visibleEntity, client); + SetParentAttachment(visibleEntity, attachPoint, true); + hatData[client].offset[2] += 10.0; + TeleportEntity(visibleEntity, hatData[client].offset, hatData[client].angles, NULL_VECTOR); + SetParentAttachment(visibleEntity, attachPoint, true); + #if defined DEBUG_HAT_SHOW_FAKE + L4D2_SetEntityGlow(visibleEntity, L4D2Glow_Constant, 0, 0, color2, false); + #endif + } + + #if defined DEBUG_HAT_SHOW_FAKE + L4D2_SetEntityGlow(modifyEntity, L4D2Glow_Constant, 0, 0, color, false); + #endif + + // SetEntProp(modifyEntity, Prop_Send, "m_nSolidType", 0); + SetEntProp(modifyEntity, Prop_Send, "m_CollisionGroup", 1); + SetEntProp(modifyEntity, Prop_Send, "movetype", MOVETYPE_NONE); + } +} diff --git a/scripting/include/hats/natives.sp b/scripting/include/hats/natives.sp deleted file mode 100644 index f2911ad..0000000 --- a/scripting/include/hats/natives.sp +++ /dev/null @@ -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; -} \ No newline at end of file diff --git a/scripting/include/hats/props/base.sp b/scripting/include/hats/props/base.sp deleted file mode 100644 index 60e3e2f..0000000 --- a/scripting/include/hats/props/base.sp +++ /dev/null @@ -1,569 +0,0 @@ -int g_pendingSaveClient; -ArrayList g_previewItems; -CategoryData ROOT_CATEGORY; -ArrayList g_spawnedItems; // ArrayList(block=2) -ArrayList g_savedItems; // ArrayList -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, false: items is ArrayList - 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(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 -#include -#include -#include -#include \ No newline at end of file diff --git a/scripting/include/hats/props/cmd.sp b/scripting/include/hats/props/cmd.sp deleted file mode 100644 index 8167c3b..0000000 --- a/scripting/include/hats/props/cmd.sp +++ /dev/null @@ -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 - lists all props and their distances"); - PrintToConsole(client, "search "); - PrintToConsole(client, "edit "); - PrintToConsole(client, "del "); - PrintToConsole(client, "add "); - PrintToConsole(client, "favorite - favorites active editor entity"); - PrintToConsole(client, "controls - list all the controls"); - PrintToConsole(client, "reload - reload prop list"); - PrintToConsole(client, "schem[atic] "); - } 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; -} \ No newline at end of file diff --git a/scripting/include/hats/props/db.sp b/scripting/include/hats/props/db.sp deleted file mode 100644 index 37af63e..0000000 --- a/scripting/include/hats/props/db.sp +++ /dev/null @@ -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; -} \ No newline at end of file diff --git a/scripting/include/hats/props/menu_handlers.sp b/scripting/include/hats/props/menu_handlers.sp deleted file mode 100644 index 55133b3..0000000 --- a/scripting/include/hats/props/menu_handlers.sp +++ /dev/null @@ -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(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; -} diff --git a/scripting/include/hats/props/menu_methods.sp b/scripting/include/hats/props/menu_methods.sp deleted file mode 100644 index 716bb1e..0000000 --- a/scripting/include/hats/props/menu_methods.sp +++ /dev/null @@ -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); -} \ No newline at end of file diff --git a/scripting/include/hats/props/methods.sp b/scripting/include/hats/props/methods.sp deleted file mode 100644 index 227c750..0000000 --- a/scripting/include/hats/props/methods.sp +++ /dev/null @@ -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 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); -} \ No newline at end of file diff --git a/scripting/include/hats_editor.inc b/scripting/include/hats_editor.inc deleted file mode 100644 index c2f7330..0000000 --- a/scripting/include/hats_editor.inc +++ /dev/null @@ -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(client); - } - - public static EntitySelector FromClient(int client) { - return view_as(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 -} diff --git a/scripting/l4d2_editor.sp b/scripting/l4d2_editor.sp new file mode 100644 index 0000000..cfc204e --- /dev/null +++ b/scripting/l4d2_editor.sp @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include + +int g_iLaserIndex; + +float cmdThrottle[MAXPLAYERS+1]; + +TopMenu g_topMenu; + +char g_currentMap[64]; + +//int g_markedMode + +#include +#include +#include +#include + +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; +} \ No newline at end of file diff --git a/scripting/l4d2_hats.sp b/scripting/l4d2_hats.sp index c10dbaf..dbaca63 100644 --- a/scripting/l4d2_hats.sp +++ b/scripting/l4d2_hats.sp @@ -1,1073 +1,754 @@ -#pragma semicolon 1 -#pragma newdecls required - -#define PLUGIN_VERSION "1.0" -#define PLAYER_HAT_REQUEST_COOLDOWN 10 -// #define DEBUG_GLOW 1 -static float EMPTY_ANG[3] = { 0.0, 0.0, 0.0 }; - -#define DUMMY_MODEL "models/props/cs_office/vending_machine.mdl" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -bool tempGod[MAXPLAYERS+1]; -bool inSaferoom[MAXPLAYERS+1]; - -int g_iLaserIndex; - -float cmdThrottle[MAXPLAYERS+1]; -static bool onLadder[MAXPLAYERS+1]; - -Cookie noHatVictimCookie; -Cookie hatPresetCookie; - -ConVar cvar_sm_hats_enabled; -ConVar cvar_sm_hats_flags; -ConVar cvar_sm_hats_rainbow_speed; -ConVar cvar_sm_hats_blacklist_enabled; -ConVar cvar_sm_hats_max_distance; - -TopMenu g_topMenu; - -char g_currentMap[64]; - -//int g_markedMode - -#include -#include -#include -#include -#include -#include - -public Plugin myinfo = { - name = "L4D2 Hats & Editor", - author = "jackzmc", - description = "", - version = PLUGIN_VERSION, - url = "https://github.com/Jackzmc/sourcemod-plugins" -}; - -ArrayList NavAreas; -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_entered_checkpoint", OnEnterSaferoom); - HookEvent("player_left_checkpoint", OnLeaveSaferoom); - HookEvent("player_bot_replace", Event_PlayerToIdle); - HookEvent("bot_player_replace", Event_PlayerOutOfIdle); - HookEvent("player_spawn", Event_PlayerSpawn); - - RegConsoleCmd("sm_hat", Command_DoAHat, "Hats"); - RegAdminCmd("sm_hatf", Command_DoAHat, ADMFLAG_ROOT, "Hats"); - 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); - RegConsoleCmd("sm_hatp", Command_DoAHatPreset); - - cvar_sm_hats_blacklist_enabled = CreateConVar("sm_hats_blacklist_enabled", "1", "Is the prop blacklist enabled", FCVAR_NONE, true, 0.0, true, 1.0); - cvar_sm_hats_enabled = CreateConVar("sm_hats_enabled", "1.0", "Enable hats.\n0=OFF, 1=Admins Only, 2=Any", FCVAR_NONE, true, 0.0, true, 2.0); - cvar_sm_hats_enabled.AddChangeHook(Event_HatsEnableChanged); - cvar_sm_hats_flags = CreateConVar("sm_hats_features", "153", "Toggle certain features. Add bits together\n1 = Player Hats\n2 = Respect Admin Immunity\n4 = Create a fake hat for hat wearer to view instead, and for yeeting\n8 = No saferoom hats\n16 = Player hatting requires victim consent\n32 = Infected Hats\n64 = Reverse hats\n128 = Delete Thrown Hats", FCVAR_CHEAT, true, 0.0); - cvar_sm_hats_rainbow_speed = CreateConVar("sm_hats_rainbow_speed", "1", "Speed of rainbow", FCVAR_NONE, true, 0.0); - cvar_sm_hats_max_distance = CreateConVar("sm_hats_distance", "240", "The max distance away you can hat something. 0 = disable", FCVAR_NONE, true, 0.0); - - if(SQL_CheckConfig(DATABASE_CONFIG_NAME)) { - if(!ConnectDB()) { - LogError("Failed to connect to database."); - } - } - - - noHatVictimCookie = new Cookie("hats_no_target", "Disables other players from making you their hat", CookieAccess_Public); - noHatVictimCookie.SetPrefabMenu(CookieMenu_OnOff_Int, "Disable player hats for self", OnLocalPlayerHatCookieSelect); - - hatPresetCookie = new Cookie("hats_preset", "Sets the preset hat you spawn with", CookieAccess_Public); - - 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); - hatData[i].yeetGroundTimer = null; - } - - LoadPresets(); - - TopMenu topMenu; - if (LibraryExists("adminmenu") && ((topMenu = GetAdminTopMenu()) != null)) { - OnAdminMenuReady(topMenu); - } -} - -public void OnLibraryRemoved(const char[] name) { - if (StrEqual(name, "adminmenu", false)) { - g_topMenu = null; - } -} - -/////////////////////////////////////////////////////////////////////////////////////////////// - -public void OnEnterSaferoom(Event event, const char[] name, bool dontBroadcast) { - int userid = event.GetInt("userid"); - int client = GetClientOfUserId(userid); - if(client > 0 && client <= MaxClients && IsValidClient(client) && GetClientTeam(client) == 2 && !IsFakeClient(client)) { - inSaferoom[client] = true; - if(cvar_sm_hats_flags.IntValue & view_as(HatConfig_NoSaferoomHats)) { - if(HasHat(client) && !HasFlag(client, HAT_PRESET)) { - if(!IsHatAllowedInSaferoom(client)) { - PrintToChat(client, "[Hats] Hat is not allowed in the saferoom and has been returned"); - ClearHat(client, true); - } else { - CreateTimer(2.0, Timer_PlaceHat, userid); - } - } - } - } -} - -public void OnLeaveSaferoom(Event event, const char[] name, bool dontBroadcast) { - int userid = event.GetInt("userid"); - int client = GetClientOfUserId(userid); - if(client > 0 && client <= MaxClients && IsValidClient(client) && GetClientTeam(client) == 2) { - inSaferoom[client] = false; - } -} - -Action Timer_PlaceHat(Handle h, int userid) { - int client = GetClientOfUserId(userid); - if(client > 0 && HasHat(client)) { - GetClientAbsOrigin(client, hatData[client].orgPos); - GetClientEyeAngles(client, hatData[client].orgAng); - // GetHorizontalPositionFromOrigin(hatData[client].orgPos, hatData[client].orgAng, 40.0, hatData[client].orgPos); - hatData[client].orgAng[0] = 0.0; - PrintToChat(client, "[Hats] Hat has been placed down"); - ClearHat(client, true); - } - return Plugin_Handled; -} - -// 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; - } -} - -// Periodically fixes hat offsets, as some events/actions/anything can cause entities to offset from their parent -Action Timer_RemountHats(Handle h) { - float p1[3], p2[3]; - for(int i = 1; i <= MaxClients; i++) { - int entity = GetHat(i); - if(IsClientConnected(i) && IsClientInGame(i) && !HasFlag(i, HAT_POCKET)) { - int visibleEntity = EntRefToEntIndex(hatData[i].visibleEntity); - if(entity > 0) { - GetClientAbsOrigin(i, p1); - GetEntPropVector(entity, Prop_Send, "m_vecOrigin", p2); - if(GetVectorDistance(p1, p2) > 40000.0) { - ClearParent(entity); - if(visibleEntity > 0) { - ClearParent(visibleEntity); - } - RequestFrame(Frame_Remount, i); - } - } else if(visibleEntity > 0) { - RemoveEntity(visibleEntity); - hatData[i].visibleEntity = INVALID_ENT_REFERENCE; - } - } - } - return Plugin_Handled; -} - -// Remounts entity in a new frame to ensure their parent was properly cleared -void Frame_Remount(int i) { - int entity = GetHat(i); - if(entity == -1) return; - SetParent(entity, i); - SetParentAttachment(entity, hatData[i].attachPoint, false); - SetParentAttachment(entity, hatData[i].attachPoint, true); - - int visibleEntity = EntRefToEntIndex(hatData[i].visibleEntity); - if(visibleEntity > 0) { - SetParent(visibleEntity, i); - SetParentAttachment(visibleEntity, hatData[i].attachPoint, false); - SetParentAttachment(visibleEntity, hatData[i].attachPoint, true); - } -} - - -// Handles making a prop sleep after a set amount of time (called after hat yeet) -Action Timer_PropSleep(Handle h, DataPack pack) { - pack.Reset(); - int ref = pack.ReadCell(); - int client = GetClientOfUserId(pack.ReadCell()); - if(client > 0 && IsValidEntity(ref)) { - // CheckKill(ref, client); - float vel[3]; - TeleportEntity(ref, NULL_VECTOR, NULL_VECTOR, vel); - PrintToServer("Hats: Yeet delete timeout"); - if(hatData[client].yeetGroundTimer != null) { - delete hatData[client].yeetGroundTimer; - } - } - return Plugin_Continue; -} -Action Timer_GroundKill(Handle h, DataPack pack) { - pack.Reset(); - int ref = pack.ReadCell(); - int client = GetClientOfUserId(pack.ReadCell()); - if(client > 0 && IsValidEntity(ref)) { - float vel[3]; - GetEntPropVector(ref, Prop_Data, "m_vecVelocity", vel); - if(FloatAbs(vel[2]) < 0.2 || IsNearGround(ref)) { - PrintToServer("Hats: Yeet ground check %b %b", FloatAbs(vel[2]) < 0.2, IsNearGround(ref)); - vel[0] = 0.0; - vel[1] = 0.0; - vel[2] = 0.0; - TeleportEntity(ref, NULL_VECTOR, NULL_VECTOR, vel); - // CheckKill(ref, client); - hatData[client].yeetGroundTimer = null; - return Plugin_Stop; - } - return Plugin_Continue; - } - return Plugin_Stop; -} - - - -void CheckKill(int ref, int client) { - // Check if we should delete thrown hat objects, such as physic props - if(cvar_sm_hats_flags.IntValue & view_as(HatConfig_DeleteThrownHats)) { - // Don't delete if someone has hatted it (including ourself): - for(int i = 1; i <= MaxClients; i++) { - if(IsClientConnected(i) && IsClientInGame(i) && hatData[i].entity == ref) { - return; - } - } - - // Check for prop_ class, only yeetable non-player entity we care as they may be large/collidabl - // Things like weapons aren't a problem as you can't "collide" and get thrown - if(EntRefToEntIndex(ref) > MaxClients) { - char classname[64]; - GetEntityClassname(ref, classname, sizeof(classname)); - if(StrContains(classname, "prop_") > -1) { - RemoveEntity(ref); - return; - } - } - } - AcceptEntityInput(ref, "Sleep"); -} - -Action Timer_PropYeetEnd(Handle h, DataPack pack) { - pack.Reset(); - int realEnt = EntRefToEntIndex(pack.ReadCell()); - // int visibleEnt = EntRefToEntIndex(pack.ReadCell()); - // if(IsValidEntity(visibleEnt)) { - // float pos[3], ang[3]; - // GetEntPropVector(visibleEnt, Prop_Send, "m_vecOrigin", pos); - // GetEntPropVector(visibleEnt, Prop_Send, "m_angRotation", ang); - // AcceptEntityInput(visibleEnt, "kill"); - // if(IsValidEntity(realEnt)) { - // TeleportEntity(realEnt, pos, ang, NULL_VECTOR); - // } - // } - if(IsValidEntity(realEnt)) { - SetEntProp(realEnt, Prop_Send, "m_CollisionGroup", pack.ReadCell()); - SetEntProp(realEnt, Prop_Send, "m_nSolidType", pack.ReadCell()); - SetEntProp(realEnt, Prop_Send, "movetype", pack.ReadCell()); - AcceptEntityInput(realEnt, "Sleep"); - } - - return Plugin_Handled; -} - -Action Timer_RemoveGod(Handle h, int userid) { - int client = GetClientOfUserId(userid); - if(client) { - tempGod[client] = false; - SDKUnhook(client, SDKHook_OnTakeDamageAlive, OnTakeDamageAlive); - } - return Plugin_Handled; -} - -void Event_PlayerOutOfIdle(Event event, const char[] name, bool dontBroadcast) { - int bot = GetClientOfUserId(event.GetInt("bot")); - int client = GetClientOfUserId(event.GetInt("player")); - if(GetClientTeam(client) != 2) return; - float pos[3]; - for(int i = 1; i <= MaxClients; i++) { - if(hatData[i].entity == bot) { - GetClientAbsOrigin(i, pos); - ClearHat(i); - hatData[i].entity = EntIndexToEntRef(client); - TeleportEntity(hatData[i].entity, pos, hatData[i].orgAng, NULL_VECTOR); - return; - } - } - PrintToServer("Fixing hatted player to bot: Bot %N to client %N", bot, client); - // Incase they removed hat right after, manually fix them - ClearParent(client); - ClearParent(bot); - SetEntProp(client, Prop_Send, "m_CollisionGroup", 5); - SetEntProp(client, Prop_Send, "m_nSolidType", 2); - SetEntityMoveType(client, MOVETYPE_WALK); - RequestFrame(Frame_FixClient, client); - // SetEntProp(client, Prop_Send, "movetype", MOVETYPE_ISOMETRIC); -} - -void Frame_FixClient(int client) { - if(IsClientConnected(client) && GetClientTeam(client) == 2) { - ClearParent(client); - SetEntProp(client, Prop_Send, "m_CollisionGroup", 5); - SetEntProp(client, Prop_Send, "m_nSolidType", 2); - SetEntityMoveType(client, MOVETYPE_WALK); - } - // SetEntProp(client, Prop_Send, "movetype", MOVETYPE_ISOMETRIC); -} -public void Event_PlayerToIdle(Event event, const char[] name, bool dontBroadcast) { - int bot = GetClientOfUserId(event.GetInt("bot")); - int client = GetClientOfUserId(event.GetInt("player")); - if(GetClientTeam(client) != 2) return; - float pos[3]; - for(int i = 1; i <= MaxClients; i++) { - if(hatData[i].entity == client) { - GetClientAbsOrigin(i, pos); - ClearHat(i); - hatData[i].entity = EntIndexToEntRef(bot); - TeleportEntity(hatData[i].entity, pos, hatData[i].orgAng, NULL_VECTOR); - return; - } - } - // Incase they removed hat right after, manually fix them - ClearParent(bot); - SetEntProp(bot, Prop_Send, "m_CollisionGroup", 5); - SetEntProp(bot, Prop_Send, "m_nSolidType", 2); - SetEntityMoveType(bot, MOVETYPE_WALK); -} - -void OnLocalPlayerHatCookieSelect(int client, CookieMenuAction action, any info, char[] buffer, int maxlen) { - if(action != CookieMenuAction_SelectOption) return; - bool value = StringToInt(buffer) == 1; - if(value) { - for(int i = 1; i <= MaxClients; i++) { - int hat = GetHat(i); - if(hat == client) { - ClearHat(i, false); - PrintToChat(i, "%N has blocked player hats for themselves", client); - } - } - ClearHat(client, false); - } -} - -public void Event_HatsEnableChanged(ConVar convar, const char[] sOldValue, const char[] sNewValue) { - if(convar.IntValue == 0) { - ClearHats(); - } else if(convar.IntValue == 1) { - for(int i = 1; i <= MaxClients; i++) { - if(IsClientConnected(i) && GetUserAdmin(i) == INVALID_ADMIN_ID && HasHat(i)) { - ClearHat(i, false); - } - } - } -} - -ArrayList GetSpawnLocations() { - ArrayList list = new ArrayList(); - ArrayList newList = new ArrayList(); - L4D_GetAllNavAreas(list); - for(int i = 0; i < list.Length; i++) { - Address nav = list.Get(i); - if(L4D_GetNavArea_SpawnAttributes(nav) & NAV_SPAWN_THREAT) { - newList.Push(nav); - } - } - delete list; - PrintToServer("[Hats] Got %d valid locations", newList.Length); - return newList; -} - - -void ChooseRandomPosition(float pos[3], int ignoreClient = 0) { - if(NavAreas.Length > 0 && GetURandomFloat() > 0.5) { - int nav = NavAreas.Get(GetURandomInt() % (NavAreas.Length - 1)); - L4D_FindRandomSpot(nav, pos); - } else { - int survivor = GetRandomClient(5, 1); - if(ignoreClient > 0 && survivor == ignoreClient) survivor = GetRandomClient(5, 1); - if(survivor > 0) { - GetClientAbsOrigin(survivor, pos); - } - } -} - -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(); - ////////////////////////////// - // OnPlayerRunCmd :: HATS - ///////////////////////////// - int oldButtons = GetEntProp(client, Prop_Data, "m_nOldButtons"); - if(IsHatsEnabled(client)) { - int entity = GetHat(client); - int visibleEntity = EntRefToEntIndex(hatData[client].visibleEntity); - if(entity > 0) { - // Crash prevention: Prevent hat from touching ladder as that can cause server crashes - if(!onLadder[client] && GetEntityMoveType(client) == MOVETYPE_LADDER) { - onLadder[client] = true; - ClearParent(entity); - - // If hat is not a player, we teleport them to the void (0, 0, 0) - // Ostherwise, we just simply dismount the player while hatter is on ladder - if(entity >= MaxClients) - TeleportEntity(entity, EMPTY_ANG, NULL_VECTOR, NULL_VECTOR); - if(visibleEntity > 0) { - hatData[client].visibleEntity = INVALID_ENT_REFERENCE; - RemoveEntity(visibleEntity); - } - } - // Player is no longer on ladder, restore hat: - else if(onLadder[client] && GetEntityMoveType(client) != MOVETYPE_LADDER) { - onLadder[client] = false; - EquipHat(client, entity); - } - - // Do the same crash protection for the hat itself, just to be safe: - if(entity <= MaxClients) { - if(!onLadder[entity] && GetEntityMoveType(entity) == MOVETYPE_LADDER) { - onLadder[entity] = true; - ClearParent(entity); - } else if(onLadder[entity] && GetEntityMoveType(entity) != MOVETYPE_LADDER) { - onLadder[entity] = false; - EquipHat(client, entity); - } - } - - // Rainbow hat processing - if(HasFlag(client, HAT_RAINBOW)) { - // Decrement and flip, possibly when rainbowticks - if(hatData[client].rainbowReverse) { - hatData[client].rainbowColor[0] -= cvar_sm_hats_rainbow_speed.FloatValue; - } else { - hatData[client].rainbowColor[0] += cvar_sm_hats_rainbow_speed.FloatValue; - } - - if(hatData[client].rainbowColor[0] > 360.0) { - hatData[client].rainbowReverse = true; - hatData[client].rainbowColor[0] = 360.0; - } else if(hatData[client].rainbowColor[0] < 0.0) { - hatData[client].rainbowReverse = false; - hatData[client].rainbowColor[0] = 0.0; - } - - static int rgb[3]; - HSVToRGBInt(hatData[client].rainbowColor, rgb); - SetEntityRenderColor(entity, rgb[0], rgb[1], rgb[2]); - hatData[client].rainbowTicks = -cvar_sm_hats_rainbow_speed.IntValue; - EquipHat(client, entity); - } - - // If bot is commandable and reversed (player reverse-hat common/survivor), change position: - if(HasFlag(client, HAT_COMMANDABLE | HAT_REVERSED) && tickcount % 200 == 0) { - float pos[3]; - ChooseRandomPosition(pos, client); - L4D2_CommandABot(entity, client, BOT_CMD_MOVE, pos); - } - } - // Detect E + R to offset hat or place down - if(buttons & IN_USE && buttons & IN_RELOAD) { - if(entity > 0) { - if(buttons & IN_ZOOM) { - // Offset controls: - // if(buttons & IN_JUMP) hatData[client].offset[2] += 1.0; - // if(buttons & IN_DUCK) hatData[client].offset[2] -= 1.0; - if(buttons & IN_FORWARD) hatData[client].offset[0] += 1.0; - if(buttons & IN_BACK) hatData[client].offset[0] -= 1.0; - if(buttons & IN_MOVELEFT) hatData[client].offset[1] += 1.0; - if(buttons & IN_MOVERIGHT) hatData[client].offset[1] -= 1.0; - TeleportEntity(entity, hatData[client].offset, angles, vel); - return Plugin_Handled; - } else if(tick - cmdThrottle[client] > 0.25) { - if(buttons & IN_ATTACK) { // doesn't work reliably for some reason - ClientCommand(client, "sm_hat y"); - } else if(buttons & IN_DUCK) { - ClientCommand(client, "sm_hat p"); - } - } - } else if(tick - cmdThrottle[client] > 0.25 && L4D2_GetPlayerUseAction(client) == L4D2UseAction_None) { - ClientCommand(client, "sm_hat"); - } - cmdThrottle[client] = tick; - hatData[client].angles = angles; - return Plugin_Handled; - } - } - - ////////////////////////////// - // OnPlayerRunCmd :: ENTITY EDITOR - ///////////////////////////// - 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; -} - -// Don't show real entity to hat wearer (Show for ALL but hat wearer) -Action OnRealTransmit(int entity, int client) { - #if defined DEBUG_HAT_SHOW_FAKE - return Plugin_Continue; - #endif - if(hatData[client].entity != INVALID_ENT_REFERENCE && EntRefToEntIndex(hatData[client].entity) == entity) - return Plugin_Handled; - return Plugin_Continue; -} - -// Only show to hat wearer (do not show to ALL) -Action OnVisibleTransmit(int entity, int client) { - #if defined DEBUG_HAT_SHOW_FAKE - return Plugin_Continue; - #endif - if(hatData[client].visibleEntity != INVALID_ENT_REFERENCE && EntRefToEntIndex(hatData[client].visibleEntity) != entity) - return Plugin_Handled; - return Plugin_Continue; -} - - -public Action OnTakeDamageAlive(int victim, int& attacker, int& inflictor, float& damage, int& damagetype) { - if(victim > MaxClients || victim <= 0) return Plugin_Continue; - if(damage > 0.0 && tempGod[victim]) { - damage = 0.0; - return Plugin_Handled; - } - if(attacker > MaxClients || attacker <= 0) return Plugin_Continue; - if(victim == EntRefToEntIndex(hatData[attacker].entity) || attacker == EntRefToEntIndex(hatData[victim].entity)) { - damage = 0.0; - return Plugin_Handled; - } - return Plugin_Continue; -} - -void Event_PlayerSpawn(Event event, const char[] name, bool dontBroadcast) { - int client = GetClientOfUserId(event.GetInt("userid")); - if(client > 0) { - if(!HasHat(client) && !IsFakeClient(client)) { - hatPresetCookie.Get(client, ActivePreset[client], 32); - if(ActivePreset[client][0] != '\0') { - RestoreActivePreset(client); - ReplyToCommand(client, "[Hats] Applied your hat preset! Clear it with /hatp"); - } - } - SDKHook(client, SDKHook_WeaponCanUse, OnWeaponUse); - } -} - -Action OnWeaponUse(int client, int weapon) { - int ref = EntIndexToEntRef(weapon); - // Prevent picking up weapons that are previews - for(int i = 1; i <= MaxClients; i++) { - if(Editor[i].entity == ref && Editor[i].flags & Edit_Preview) { - return Plugin_Handled; - } - } - return Plugin_Continue; -} - -public void OnClientDisconnect(int client) { - tempGod[client] = false; - Editor[client].Reset(); - g_PropData[client].Reset(); - if(hatData[client].yeetGroundTimer != null) - delete hatData[client].yeetGroundTimer; - if(g_pendingSaveClient == client) { - g_pendingSaveClient = 0; - ClearSavePreview(); - } - ClearHat(client, true); -} - -public void OnEntityDestroyed(int entity) { - for(int i = 1; i <= MaxClients; i++) { - if(IsClientConnected(i) && IsClientInGame(i) && !IsFakeClient(i)) { - if(hatData[i].entity != INVALID_ENT_REFERENCE && EntRefToEntIndex(hatData[i].entity) == entity) { - ClearHat(i); - PrintHintText(i, "Hat has vanished"); - ClientCommand(i, "play ui/menu_back.wav"); - break; - } - } - } -} -public void OnMapStart() { - PrecacheModel(DUMMY_MODEL); - g_iLaserIndex = PrecacheModel("materials/sprites/laserbeam.vmt"); - CreateTimer(30.0, Timer_RemountHats, _, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); - for(int i = 1; i <= MaxClients; i++) { - cmdThrottle[i] = 0.0; - tempGod[i] = false; - } - GetCurrentMap(g_currentMap, sizeof(g_currentMap)); - NavAreas = GetSpawnLocations(); -} - - -public void OnMapEnd() { - delete NavAreas; - g_spawnedItems.Clear(); - for(int i = 1; i <= createdWalls.Length; i++) { - if(hatData[i].yeetGroundTimer != null) { - delete hatData[i].yeetGroundTimer; - } - DeleteWall(i); - } - createdWalls.Clear(); - ClearHats(); - UnloadCategories(); - UnloadSave(); - SaveRecents(); -} -public void OnPluginEnd() { - ClearHats(); - 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) { - if(EntRefToEntIndex(hatData[data].entity) == entity) { - return false; - } - 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 && EntRefToEntIndex(hatData[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) { - int client = GetRealClient(data); - if(client == -1) client = data; - return CanTarget(client); // Don't target if player targetting off - } - return CheckBlacklist(entity); -} - -bool CheckBlacklist(int entity) { - if(entity == 0) return false; - if(cvar_sm_hats_blacklist_enabled.BoolValue) { - 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; +#pragma semicolon 1 +#pragma newdecls required + +#define PLUGIN_VERSION "1.0" +#define PLAYER_HAT_REQUEST_COOLDOWN 10 +// #define DEBUG_GLOW 1 +static float EMPTY_ANG[3] = { 0.0, 0.0, 0.0 }; + +#define DUMMY_MODEL "models/props/cs_office/vending_machine.mdl" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +bool tempGod[MAXPLAYERS+1]; +bool inSaferoom[MAXPLAYERS+1]; + +float cmdThrottle[MAXPLAYERS+1]; +static bool onLadder[MAXPLAYERS+1]; + +Cookie noHatVictimCookie; +Cookie hatPresetCookie; + +ConVar cvar_sm_hats_enabled; +ConVar cvar_sm_hats_flags; +ConVar cvar_sm_hats_rainbow_speed; +ConVar cvar_sm_hats_blacklist_enabled; +ConVar cvar_sm_hats_max_distance; + +char g_currentMap[64]; + +//int g_markedMode + +#include +#include + +public Plugin myinfo = { + name = "L4D2 Hats & Editor", + author = "jackzmc", + description = "", + version = PLUGIN_VERSION, + url = "https://github.com/Jackzmc/sourcemod-plugins" +}; + +ArrayList NavAreas; +public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) { + 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."); + } + + LoadTranslations("common.phrases"); + HookEvent("player_entered_checkpoint", OnEnterSaferoom); + HookEvent("player_left_checkpoint", OnLeaveSaferoom); + HookEvent("player_bot_replace", Event_PlayerToIdle); + HookEvent("bot_player_replace", Event_PlayerOutOfIdle); + HookEvent("player_spawn", Event_PlayerSpawn); + + RegConsoleCmd("sm_hat", Command_DoAHat, "Hats"); + RegAdminCmd("sm_hatf", Command_DoAHat, ADMFLAG_ROOT, "Hats"); + RegConsoleCmd("sm_hatp", Command_DoAHatPreset); + + cvar_sm_hats_blacklist_enabled = CreateConVar("sm_hats_blacklist_enabled", "1", "Is the prop blacklist enabled", FCVAR_NONE, true, 0.0, true, 1.0); + cvar_sm_hats_enabled = CreateConVar("sm_hats_enabled", "1.0", "Enable hats.\n0=OFF, 1=Admins Only, 2=Any", FCVAR_NONE, true, 0.0, true, 2.0); + cvar_sm_hats_enabled.AddChangeHook(Event_HatsEnableChanged); + cvar_sm_hats_flags = CreateConVar("sm_hats_features", "153", "Toggle certain features. Add bits together\n1 = Player Hats\n2 = Respect Admin Immunity\n4 = Create a fake hat for hat wearer to view instead, and for yeeting\n8 = No saferoom hats\n16 = Player hatting requires victim consent\n32 = Infected Hats\n64 = Reverse hats\n128 = Delete Thrown Hats", FCVAR_CHEAT, true, 0.0); + cvar_sm_hats_rainbow_speed = CreateConVar("sm_hats_rainbow_speed", "1", "Speed of rainbow", FCVAR_NONE, true, 0.0); + cvar_sm_hats_max_distance = CreateConVar("sm_hats_distance", "240", "The max distance away you can hat something. 0 = disable", FCVAR_NONE, true, 0.0); + + noHatVictimCookie = new Cookie("hats_no_target", "Disables other players from making you their hat", CookieAccess_Public); + noHatVictimCookie.SetPrefabMenu(CookieMenu_OnOff_Int, "Disable player hats for self", OnLocalPlayerHatCookieSelect); + + hatPresetCookie = new Cookie("hats_preset", "Sets the preset hat you spawn with", CookieAccess_Public); + + for(int i = 1; i <= MaxClients; i++) { + hatData[i].yeetGroundTimer = null; + } + + LoadPresets(); +} + +/////////////////////////////////////////////////////////////////////////////////////////////// + +public void OnEnterSaferoom(Event event, const char[] name, bool dontBroadcast) { + int userid = event.GetInt("userid"); + int client = GetClientOfUserId(userid); + if(client > 0 && client <= MaxClients && IsValidClient(client) && GetClientTeam(client) == 2 && !IsFakeClient(client)) { + inSaferoom[client] = true; + if(cvar_sm_hats_flags.IntValue & view_as(HatConfig_NoSaferoomHats)) { + if(HasHat(client) && !HasFlag(client, HAT_PRESET)) { + if(!IsHatAllowedInSaferoom(client)) { + PrintToChat(client, "[Hats] Hat is not allowed in the saferoom and has been returned"); + ClearHat(client, true); + } else { + CreateTimer(2.0, Timer_PlaceHat, userid); + } + } + } + } +} + +public void OnLeaveSaferoom(Event event, const char[] name, bool dontBroadcast) { + int userid = event.GetInt("userid"); + int client = GetClientOfUserId(userid); + if(client > 0 && client <= MaxClients && IsValidClient(client) && GetClientTeam(client) == 2) { + inSaferoom[client] = false; + } +} + +Action Timer_PlaceHat(Handle h, int userid) { + int client = GetClientOfUserId(userid); + if(client > 0 && HasHat(client)) { + GetClientAbsOrigin(client, hatData[client].orgPos); + GetClientEyeAngles(client, hatData[client].orgAng); + // GetHorizontalPositionFromOrigin(hatData[client].orgPos, hatData[client].orgAng, 40.0, hatData[client].orgPos); + hatData[client].orgAng[0] = 0.0; + PrintToChat(client, "[Hats] Hat has been placed down"); + ClearHat(client, true); + } + return Plugin_Handled; +} + +// 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; + } +} + +// Periodically fixes hat offsets, as some events/actions/anything can cause entities to offset from their parent +Action Timer_RemountHats(Handle h) { + float p1[3], p2[3]; + for(int i = 1; i <= MaxClients; i++) { + int entity = GetHat(i); + if(IsClientConnected(i) && IsClientInGame(i) && !HasFlag(i, HAT_POCKET)) { + int visibleEntity = EntRefToEntIndex(hatData[i].visibleEntity); + if(entity > 0) { + GetClientAbsOrigin(i, p1); + GetEntPropVector(entity, Prop_Send, "m_vecOrigin", p2); + if(GetVectorDistance(p1, p2) > 40000.0) { + ClearParent(entity); + if(visibleEntity > 0) { + ClearParent(visibleEntity); + } + RequestFrame(Frame_Remount, i); + } + } else if(visibleEntity > 0) { + RemoveEntity(visibleEntity); + hatData[i].visibleEntity = INVALID_ENT_REFERENCE; + } + } + } + return Plugin_Handled; +} + +// Remounts entity in a new frame to ensure their parent was properly cleared +void Frame_Remount(int i) { + int entity = GetHat(i); + if(entity == -1) return; + SetParent(entity, i); + SetParentAttachment(entity, hatData[i].attachPoint, false); + SetParentAttachment(entity, hatData[i].attachPoint, true); + + int visibleEntity = EntRefToEntIndex(hatData[i].visibleEntity); + if(visibleEntity > 0) { + SetParent(visibleEntity, i); + SetParentAttachment(visibleEntity, hatData[i].attachPoint, false); + SetParentAttachment(visibleEntity, hatData[i].attachPoint, true); + } +} + + +// Handles making a prop sleep after a set amount of time (called after hat yeet) +Action Timer_PropSleep(Handle h, DataPack pack) { + pack.Reset(); + int ref = pack.ReadCell(); + int client = GetClientOfUserId(pack.ReadCell()); + if(client > 0 && IsValidEntity(ref)) { + // CheckKill(ref, client); + float vel[3]; + TeleportEntity(ref, NULL_VECTOR, NULL_VECTOR, vel); + PrintToServer("Hats: Yeet delete timeout"); + if(hatData[client].yeetGroundTimer != null) { + delete hatData[client].yeetGroundTimer; + } + } + return Plugin_Continue; +} +Action Timer_GroundKill(Handle h, DataPack pack) { + pack.Reset(); + int ref = pack.ReadCell(); + int client = GetClientOfUserId(pack.ReadCell()); + if(client > 0 && IsValidEntity(ref)) { + float vel[3]; + GetEntPropVector(ref, Prop_Data, "m_vecVelocity", vel); + if(FloatAbs(vel[2]) < 0.2 || IsNearGround(ref)) { + PrintToServer("Hats: Yeet ground check %b %b", FloatAbs(vel[2]) < 0.2, IsNearGround(ref)); + vel[0] = 0.0; + vel[1] = 0.0; + vel[2] = 0.0; + TeleportEntity(ref, NULL_VECTOR, NULL_VECTOR, vel); + // CheckKill(ref, client); + hatData[client].yeetGroundTimer = null; + return Plugin_Stop; + } + return Plugin_Continue; + } + return Plugin_Stop; +} + + + +void CheckKill(int ref, int client) { + // Check if we should delete thrown hat objects, such as physic props + if(cvar_sm_hats_flags.IntValue & view_as(HatConfig_DeleteThrownHats)) { + // Don't delete if someone has hatted it (including ourself): + for(int i = 1; i <= MaxClients; i++) { + if(IsClientConnected(i) && IsClientInGame(i) && hatData[i].entity == ref) { + return; + } + } + + // Check for prop_ class, only yeetable non-player entity we care as they may be large/collidabl + // Things like weapons aren't a problem as you can't "collide" and get thrown + if(EntRefToEntIndex(ref) > MaxClients) { + char classname[64]; + GetEntityClassname(ref, classname, sizeof(classname)); + if(StrContains(classname, "prop_") > -1) { + RemoveEntity(ref); + return; + } + } + } + AcceptEntityInput(ref, "Sleep"); +} + +Action Timer_PropYeetEnd(Handle h, DataPack pack) { + pack.Reset(); + int realEnt = EntRefToEntIndex(pack.ReadCell()); + // int visibleEnt = EntRefToEntIndex(pack.ReadCell()); + // if(IsValidEntity(visibleEnt)) { + // float pos[3], ang[3]; + // GetEntPropVector(visibleEnt, Prop_Send, "m_vecOrigin", pos); + // GetEntPropVector(visibleEnt, Prop_Send, "m_angRotation", ang); + // AcceptEntityInput(visibleEnt, "kill"); + // if(IsValidEntity(realEnt)) { + // TeleportEntity(realEnt, pos, ang, NULL_VECTOR); + // } + // } + if(IsValidEntity(realEnt)) { + SetEntProp(realEnt, Prop_Send, "m_CollisionGroup", pack.ReadCell()); + SetEntProp(realEnt, Prop_Send, "m_nSolidType", pack.ReadCell()); + SetEntProp(realEnt, Prop_Send, "movetype", pack.ReadCell()); + AcceptEntityInput(realEnt, "Sleep"); + } + + return Plugin_Handled; +} + +Action Timer_RemoveGod(Handle h, int userid) { + int client = GetClientOfUserId(userid); + if(client) { + tempGod[client] = false; + SDKUnhook(client, SDKHook_OnTakeDamageAlive, OnTakeDamageAlive); + } + return Plugin_Handled; +} + +void Event_PlayerOutOfIdle(Event event, const char[] name, bool dontBroadcast) { + int bot = GetClientOfUserId(event.GetInt("bot")); + int client = GetClientOfUserId(event.GetInt("player")); + if(GetClientTeam(client) != 2) return; + float pos[3]; + for(int i = 1; i <= MaxClients; i++) { + if(hatData[i].entity == bot) { + GetClientAbsOrigin(i, pos); + ClearHat(i); + hatData[i].entity = EntIndexToEntRef(client); + TeleportEntity(hatData[i].entity, pos, hatData[i].orgAng, NULL_VECTOR); + return; + } + } + PrintToServer("Fixing hatted player to bot: Bot %N to client %N", bot, client); + // Incase they removed hat right after, manually fix them + ClearParent(client); + ClearParent(bot); + SetEntProp(client, Prop_Send, "m_CollisionGroup", 5); + SetEntProp(client, Prop_Send, "m_nSolidType", 2); + SetEntityMoveType(client, MOVETYPE_WALK); + RequestFrame(Frame_FixClient, client); + // SetEntProp(client, Prop_Send, "movetype", MOVETYPE_ISOMETRIC); +} + +void Frame_FixClient(int client) { + if(IsClientConnected(client) && GetClientTeam(client) == 2) { + ClearParent(client); + SetEntProp(client, Prop_Send, "m_CollisionGroup", 5); + SetEntProp(client, Prop_Send, "m_nSolidType", 2); + SetEntityMoveType(client, MOVETYPE_WALK); + } + // SetEntProp(client, Prop_Send, "movetype", MOVETYPE_ISOMETRIC); +} +public void Event_PlayerToIdle(Event event, const char[] name, bool dontBroadcast) { + int bot = GetClientOfUserId(event.GetInt("bot")); + int client = GetClientOfUserId(event.GetInt("player")); + if(GetClientTeam(client) != 2) return; + float pos[3]; + for(int i = 1; i <= MaxClients; i++) { + if(hatData[i].entity == client) { + GetClientAbsOrigin(i, pos); + ClearHat(i); + hatData[i].entity = EntIndexToEntRef(bot); + TeleportEntity(hatData[i].entity, pos, hatData[i].orgAng, NULL_VECTOR); + return; + } + } + // Incase they removed hat right after, manually fix them + ClearParent(bot); + SetEntProp(bot, Prop_Send, "m_CollisionGroup", 5); + SetEntProp(bot, Prop_Send, "m_nSolidType", 2); + SetEntityMoveType(bot, MOVETYPE_WALK); +} + +void OnLocalPlayerHatCookieSelect(int client, CookieMenuAction action, any info, char[] buffer, int maxlen) { + if(action != CookieMenuAction_SelectOption) return; + bool value = StringToInt(buffer) == 1; + if(value) { + for(int i = 1; i <= MaxClients; i++) { + int hat = GetHat(i); + if(hat == client) { + ClearHat(i, false); + PrintToChat(i, "%N has blocked player hats for themselves", client); + } + } + ClearHat(client, false); + } +} + +public void Event_HatsEnableChanged(ConVar convar, const char[] sOldValue, const char[] sNewValue) { + if(convar.IntValue == 0) { + ClearHats(); + } else if(convar.IntValue == 1) { + for(int i = 1; i <= MaxClients; i++) { + if(IsClientConnected(i) && GetUserAdmin(i) == INVALID_ADMIN_ID && HasHat(i)) { + ClearHat(i, false); + } + } + } +} + +ArrayList GetSpawnLocations() { + ArrayList list = new ArrayList(); + ArrayList newList = new ArrayList(); + L4D_GetAllNavAreas(list); + for(int i = 0; i < list.Length; i++) { + Address nav = list.Get(i); + if(L4D_GetNavArea_SpawnAttributes(nav) & NAV_SPAWN_THREAT) { + newList.Push(nav); + } + } + delete list; + PrintToServer("[Hats] Got %d valid locations", newList.Length); + return newList; +} + + +void ChooseRandomPosition(float pos[3], int ignoreClient = 0) { + if(NavAreas.Length > 0 && GetURandomFloat() > 0.5) { + int nav = NavAreas.Get(GetURandomInt() % (NavAreas.Length - 1)); + L4D_FindRandomSpot(nav, pos); + } else { + int survivor = GetRandomClient(5, 1); + if(ignoreClient > 0 && survivor == ignoreClient) survivor = GetRandomClient(5, 1); + if(survivor > 0) { + GetClientAbsOrigin(survivor, pos); + } + } +} + +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(); + ////////////////////////////// + // OnPlayerRunCmd :: HATS + ///////////////////////////// + if(IsHatsEnabled(client)) { + int entity = GetHat(client); + int visibleEntity = EntRefToEntIndex(hatData[client].visibleEntity); + if(entity > 0) { + // Crash prevention: Prevent hat from touching ladder as that can cause server crashes + if(!onLadder[client] && GetEntityMoveType(client) == MOVETYPE_LADDER) { + onLadder[client] = true; + ClearParent(entity); + + // If hat is not a player, we teleport them to the void (0, 0, 0) + // Ostherwise, we just simply dismount the player while hatter is on ladder + if(entity >= MaxClients) + TeleportEntity(entity, EMPTY_ANG, NULL_VECTOR, NULL_VECTOR); + if(visibleEntity > 0) { + hatData[client].visibleEntity = INVALID_ENT_REFERENCE; + RemoveEntity(visibleEntity); + } + } + // Player is no longer on ladder, restore hat: + else if(onLadder[client] && GetEntityMoveType(client) != MOVETYPE_LADDER) { + onLadder[client] = false; + EquipHat(client, entity); + } + + // Do the same crash protection for the hat itself, just to be safe: + if(entity <= MaxClients) { + if(!onLadder[entity] && GetEntityMoveType(entity) == MOVETYPE_LADDER) { + onLadder[entity] = true; + ClearParent(entity); + } else if(onLadder[entity] && GetEntityMoveType(entity) != MOVETYPE_LADDER) { + onLadder[entity] = false; + EquipHat(client, entity); + } + } + + // Rainbow hat processing + if(HasFlag(client, HAT_RAINBOW)) { + // Decrement and flip, possibly when rainbowticks + if(hatData[client].rainbowReverse) { + hatData[client].rainbowColor[0] -= cvar_sm_hats_rainbow_speed.FloatValue; + } else { + hatData[client].rainbowColor[0] += cvar_sm_hats_rainbow_speed.FloatValue; + } + + if(hatData[client].rainbowColor[0] > 360.0) { + hatData[client].rainbowReverse = true; + hatData[client].rainbowColor[0] = 360.0; + } else if(hatData[client].rainbowColor[0] < 0.0) { + hatData[client].rainbowReverse = false; + hatData[client].rainbowColor[0] = 0.0; + } + + static int rgb[3]; + HSVToRGBInt(hatData[client].rainbowColor, rgb); + SetEntityRenderColor(entity, rgb[0], rgb[1], rgb[2]); + hatData[client].rainbowTicks = -cvar_sm_hats_rainbow_speed.IntValue; + EquipHat(client, entity); + } + + // If bot is commandable and reversed (player reverse-hat common/survivor), change position: + if(HasFlag(client, HAT_COMMANDABLE | HAT_REVERSED) && tickcount % 200 == 0) { + float pos[3]; + ChooseRandomPosition(pos, client); + L4D2_CommandABot(entity, client, BOT_CMD_MOVE, pos); + } + } + // Detect E + R to offset hat or place down + if(buttons & IN_USE && buttons & IN_RELOAD) { + if(entity > 0) { + if(buttons & IN_ZOOM) { + // Offset controls: + // if(buttons & IN_JUMP) hatData[client].offset[2] += 1.0; + // if(buttons & IN_DUCK) hatData[client].offset[2] -= 1.0; + if(buttons & IN_FORWARD) hatData[client].offset[0] += 1.0; + if(buttons & IN_BACK) hatData[client].offset[0] -= 1.0; + if(buttons & IN_MOVELEFT) hatData[client].offset[1] += 1.0; + if(buttons & IN_MOVERIGHT) hatData[client].offset[1] -= 1.0; + TeleportEntity(entity, hatData[client].offset, angles, vel); + return Plugin_Handled; + } else if(tick - cmdThrottle[client] > 0.25) { + if(buttons & IN_ATTACK) { // doesn't work reliably for some reason + ClientCommand(client, "sm_hat y"); + } else if(buttons & IN_DUCK) { + ClientCommand(client, "sm_hat p"); + } + } + } else if(tick - cmdThrottle[client] > 0.25 && L4D2_GetPlayerUseAction(client) == L4D2UseAction_None) { + ClientCommand(client, "sm_hat"); + } + cmdThrottle[client] = tick; + hatData[client].angles = angles; + return Plugin_Handled; + } + } + return Plugin_Continue; +} + +// Don't show real entity to hat wearer (Show for ALL but hat wearer) +Action OnRealTransmit(int entity, int client) { + #if defined DEBUG_HAT_SHOW_FAKE + return Plugin_Continue; + #endif + if(hatData[client].entity != INVALID_ENT_REFERENCE && EntRefToEntIndex(hatData[client].entity) == entity) + return Plugin_Handled; + return Plugin_Continue; +} + +// Only show to hat wearer (do not show to ALL) +Action OnVisibleTransmit(int entity, int client) { + #if defined DEBUG_HAT_SHOW_FAKE + return Plugin_Continue; + #endif + if(hatData[client].visibleEntity != INVALID_ENT_REFERENCE && EntRefToEntIndex(hatData[client].visibleEntity) != entity) + return Plugin_Handled; + return Plugin_Continue; +} + + +public Action OnTakeDamageAlive(int victim, int& attacker, int& inflictor, float& damage, int& damagetype) { + if(victim > MaxClients || victim <= 0) return Plugin_Continue; + if(damage > 0.0 && tempGod[victim]) { + damage = 0.0; + return Plugin_Handled; + } + if(attacker > MaxClients || attacker <= 0) return Plugin_Continue; + if(victim == EntRefToEntIndex(hatData[attacker].entity) || attacker == EntRefToEntIndex(hatData[victim].entity)) { + damage = 0.0; + return Plugin_Handled; + } + return Plugin_Continue; +} + +void Event_PlayerSpawn(Event event, const char[] name, bool dontBroadcast) { + int client = GetClientOfUserId(event.GetInt("userid")); + if(client > 0) { + if(!HasHat(client) && !IsFakeClient(client)) { + hatPresetCookie.Get(client, ActivePreset[client], 32); + if(ActivePreset[client][0] != '\0') { + RestoreActivePreset(client); + ReplyToCommand(client, "[Hats] Applied your hat preset! Clear it with /hatp"); + } + } + } +} + +public void OnClientDisconnect(int client) { + tempGod[client] = false; + if(hatData[client].yeetGroundTimer != null) + delete hatData[client].yeetGroundTimer; + ClearHat(client, true); +} + +public void OnEntityDestroyed(int entity) { + for(int i = 1; i <= MaxClients; i++) { + if(IsClientConnected(i) && IsClientInGame(i) && !IsFakeClient(i)) { + if(hatData[i].entity != INVALID_ENT_REFERENCE && EntRefToEntIndex(hatData[i].entity) == entity) { + ClearHat(i); + PrintHintText(i, "Hat has vanished"); + ClientCommand(i, "play ui/menu_back.wav"); + break; + } + } + } +} +public void OnMapStart() { + CreateTimer(30.0, Timer_RemountHats, _, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); + for(int i = 1; i <= MaxClients; i++) { + cmdThrottle[i] = 0.0; + tempGod[i] = false; + } + GetCurrentMap(g_currentMap, sizeof(g_currentMap)); + NavAreas = GetSpawnLocations(); +} + + +public void OnMapEnd() { + delete NavAreas; + ClearHats(); +} +public void OnPluginEnd() { + ClearHats(); + for(int i = 1; i <= MaxClients; i++) { + if(IsClientConnected(i) && IsClientInGame(i)) { + int flags = GetEntityFlags(i) & ~FL_FROZEN; + SetEntityFlags(i, flags); + } + } +} + +public bool TraceEntityFilterPlayer(int entity, int contentsMask, any data) { + if(EntRefToEntIndex(hatData[data].entity) == entity) { + return false; + } + 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(hatData[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) { + int client = GetRealClient(data); + if(client == -1) client = data; + return CanTarget(client); // Don't target if player targetting off + } + return CheckBlacklist(entity); +} + +bool CheckBlacklist(int entity) { + if(entity == 0) return false; + if(cvar_sm_hats_blacklist_enabled.BoolValue) { + 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); } \ No newline at end of file diff --git a/scripting/l4d2_randomizer.sp b/scripting/l4d2_randomizer.sp index ee1740b..49da6b3 100644 --- a/scripting/l4d2_randomizer.sp +++ b/scripting/l4d2_randomizer.sp @@ -1,1206 +1,1206 @@ -#pragma semicolon 1 -#pragma newdecls required - -//#define DEBUG - -#define PLUGIN_VERSION "1.0" -#define DEBUG_SCENE_PARSE 1 -#define DEBUG_BLOCKERS 1 - -#include -#include -//#include -#include -// #include -#include -#include -#include -#undef REQUIRE_PLUGIN -#include - -int g_iLaserIndex; -#if defined DEBUG_BLOCKERS -#include -#endif -#define ENT_PROP_NAME "l4d2_randomizer" -#define ENT_ENV_NAME "l4d2_randomizer" -#define ENT_BLOCKER_NAME "l4d2_randomizer" -#include - -#define MAX_SCENE_NAME_LENGTH 32 -#define MAX_INPUTS_CLASSNAME_LENGTH 64 - - -ConVar cvarEnabled; -enum struct ActiveSceneData { - char name[MAX_SCENE_NAME_LENGTH]; - int variantIndex; -} -MapData g_MapData; -BuilderData g_builder; -char currentMap[64]; - -enum struct BuilderData { - JSONObject mapData; - - JSONObject selectedSceneData; - char selectedSceneId[64]; - - JSONObject selectedVariantData; - int selectedVariantIndex; - - void Cleanup() { - this.selectedSceneData = null; - this.selectedVariantData = null; - this.selectedVariantIndex = -1; - this.selectedSceneId[0] = '\0'; - if(this.mapData != null) - delete this.mapData; - // JSONcleanup_and_delete(this.mapData); - } - - bool SelectScene(const char[] group) { - if(!g_builder.mapData.HasKey(group)) return false; - this.selectedSceneData = view_as(g_builder.mapData.Get(group)); - strcopy(this.selectedSceneId, sizeof(this.selectedSceneId), group); - return true; - } - - /** - * Select a variant, enter -1 to not select any (scene's entities) - */ - bool SelectVariant(int index = -1) { - if(this.selectedSceneData == null) LogError("SelectVariant called, but no group selected"); - JSONArray variants = view_as(this.selectedSceneData.Get("variants")); - if(index >= variants.Length) return false; - else if(index < -1) return false; - else if(index > -1) { - this.selectedVariantData = view_as(variants.Get(index)); - } else { - this.selectedVariantData = null; - } - this.selectedVariantIndex = index; - return true; - } - - void AddEntity(int entity, ExportType exportType = Export_Model) { - JSONArray entities; - if(g_builder.selectedVariantData == null) { - // Create .entities if doesn't exist: - if(!g_builder.selectedSceneData.HasKey("entities")) { - g_builder.selectedSceneData.Set("entities", new JSONArray()); - } - entities = view_as(g_builder.selectedSceneData.Get("entities")); - } else { - entities = view_as(g_builder.selectedVariantData.Get("entities")); - } - JSONObject entityData = ExportEntity(entity, Export_Model); - entities.Push(entityData); - } -} - -#include - -public Plugin myinfo = -{ - name = "L4D2 Randomizer", - author = "jackzmc", - description = "", - version = PLUGIN_VERSION, - url = "https://github.com/Jackzmc/sourcemod-plugins" -}; - -public void OnPluginStart() { - EngineVersion g_Game = GetEngineVersion(); - if(g_Game != Engine_Left4Dead && g_Game != Engine_Left4Dead2) { - SetFailState("This plugin is for L4D/L4D2 only."); - } - - RegAdminCmd("sm_rcycle", Command_CycleRandom, ADMFLAG_CHEATS); - RegAdminCmd("sm_expent", Command_ExportEnt, ADMFLAG_GENERIC); - RegAdminCmd("sm_rbuild", Command_RandomizerBuild, ADMFLAG_CHEATS); - - cvarEnabled = CreateConVar("sm_randomizer_enabled", "0"); - - g_MapData.activeScenes = new ArrayList(sizeof(ActiveSceneData)); -} - - -// TODO: on round start -public void OnMapStart() { - g_iLaserIndex = PrecacheModel("materials/sprites/laserbeam.vmt", true); - GetCurrentMap(currentMap, sizeof(currentMap)); - if(cvarEnabled.BoolValue) - CreateTimer(5.0, Timer_Run); -} - -public void OnMapEnd() { - g_builder.Cleanup(); - Cleanup(); -} - -public void OnMapInit(const char[] map) { - // if(cvarEnabled.BoolValue) { - // if(LoadMapData(currentMap, FLAG_NONE) && g_MapData.lumpEdits.Length > 0) { - // Log("Found %d lump edits, running...", g_MapData.lumpEdits.Length); - // LumpEditData lump; - // for(int i = 0; i < g_MapData.lumpEdits.Length; i++) { - // g_MapData.lumpEdits.GetArray(i, lump); - // lump.Trigger(); - // } - // hasRan = true; - // } - // } -} - -public void OnConfigsExecuted() { - -} - -Action Timer_Run(Handle h) { - if(cvarEnabled.BoolValue) - RunMap(currentMap, FLAG_NONE); - return Plugin_Handled; -} - -stock int GetLookingEntity(int client, TraceEntityFilter filter) { - 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 int GetLookingPosition(int client, TraceEntityFilter filter, float pos[3]) { - float ang[3]; - GetClientEyePosition(client, pos); - GetClientEyeAngles(client, ang); - TR_TraceRayFilter(pos, ang, MASK_SOLID, RayType_Infinite, filter, client); - if(TR_DidHit()) { - TR_GetEndPosition(pos); - return TR_GetEntityIndex(); - } - return -1; -} - - -public Action Command_CycleRandom(int client, int args) { - if(args > 0) { - DeleteCustomEnts(); - - int flags = GetCmdArgInt(1) | view_as(FLAG_REFRESH); - RunMap(currentMap, flags); - if(client > 0) - PrintCenterText(client, "Cycled flags=%d", flags); - } else { - ReplyToCommand(client, "Active Scenes:"); - ActiveSceneData scene; - for(int i = 0; i < g_MapData.activeScenes.Length; i++) { - g_MapData.activeScenes.GetArray(i, scene); - ReplyToCommand(client, "\t%s: variant #%d", scene.name, scene.variantIndex); - } - } - return Plugin_Handled; -} - -Action Command_ExportEnt(int client, int args) { - float origin[3]; - int entity = GetLookingPosition(client, Filter_IgnorePlayer, origin); - float angles[3]; - float size[3]; - char arg1[32]; - GetCmdArg(1, arg1, sizeof(arg1)); - if(entity > 0) { - - GetEntPropVector(entity, Prop_Send, "m_vecOrigin", origin); - GetEntPropVector(entity, Prop_Send, "m_angRotation", angles); - GetEntPropVector(entity, Prop_Send, "m_vecMaxs", size); - - char model[64]; - ReplyToCommand(client, "{"); - GetEntityClassname(entity, model, sizeof(model)); - if(StrContains(model, "prop_") == -1) { - ReplyToCommand(client, "\t\"scale\": [%.2f, %.2f, %.2f],", size[0], size[1], size[2]); - } - if(StrEqual(arg1, "hammerid")) { - int hammerid = GetEntProp(entity, Prop_Data, "m_iHammerID"); - ReplyToCommand(client, "\t\"type\": \"hammerid\","); - ReplyToCommand(client, "\t\"model\": \"%d\",", hammerid); - } else if(StrEqual(arg1, "targetname")) { - GetEntPropString(entity, Prop_Data, "m_iName", model, sizeof(model)); - ReplyToCommand(client, "\t\"type\": \"targetname\","); - ReplyToCommand(client, "\t\"model\": \"%s\",", model); - } else { - GetEntPropString(entity, Prop_Data, "m_ModelName", model, sizeof(model)); - ReplyToCommand(client, "\t\"model\": \"%s\",", model); - } - ReplyToCommand(client, "\t\"origin\": [%.2f, %.2f, %.2f],", origin[0], origin[1], origin[2]); - ReplyToCommand(client, "\t\"angles\": [%.2f, %.2f, %.2f]", angles[0], angles[1], angles[2]); - ReplyToCommand(client, "}"); - } else { - if(!StrEqual(arg1, "cursor")) - GetEntPropVector(client, Prop_Send, "m_vecOrigin", origin); - GetEntPropVector(client, Prop_Send, "m_angRotation", angles); - ReplyToCommand(client, "{"); - ReplyToCommand(client, "\t\"type\": \"%s\",", arg1); - ReplyToCommand(client, "\t\"scale\": [%.2f, %.2f, %.2f],", size[0], size[1], size[2]); - ReplyToCommand(client, "\t\"origin\": [%.2f, %.2f, %.2f],", origin[0], origin[1], origin[2]); - ReplyToCommand(client, "\t\"angles\": [%.2f, %.2f, %.2f]", angles[0], angles[1], angles[2]); - ReplyToCommand(client, "}"); - } - return Plugin_Handled; -} -Action Command_RandomizerBuild(int client, int args) { - char arg[64]; - GetCmdArg(1, arg, sizeof(arg)); - if(StrEqual(arg, "new")) { - JSONObject temp = LoadMapJson(currentMap); - GetCmdArg(2, arg, sizeof(arg)); - if(temp != null && !StrEqual(arg, "confirm")) { - delete temp; - ReplyToCommand(client, "Existing map data found, enter /rbuild new confirm to overwrite."); - return Plugin_Handled; - } - g_builder.Cleanup(); - g_builder.mapData = new JSONObject(); - SaveMapJson(currentMap, g_builder.mapData); - ReplyToCommand(client, "Started new map data for %s", currentMap); - } else if(StrEqual(arg, "load")) { - if(args >= 2) { - GetCmdArg(2, arg, sizeof(arg)); - } else { - strcopy(arg, sizeof(arg), currentMap); - } - g_builder.Cleanup(); - g_builder.mapData = LoadMapJson(arg); - if(g_builder.mapData != null) { - ReplyToCommand(client, "Loaded map data for %s", arg); - } else { - ReplyToCommand(client, "No map data found for %s", arg); - } - } else if(StrEqual(arg, "menu")) { - OpenMainMenu(client); - } else if(g_builder.mapData == null) { - ReplyToCommand(client, "No map data for %s, either load with /rbuild load, or start new /rbuild new", currentMap); - return Plugin_Handled; - } else if(StrEqual(arg, "save")) { - SaveMapJson(currentMap, g_builder.mapData); - ReplyToCommand(client, "Saved %s", currentMap); - } else if(StrEqual(arg, "scenes")) { - Command_RandomizerBuild_Scenes(client, args); - } else if(StrEqual(arg, "sel") || StrEqual(arg, "selector")) { - if(g_builder.selectedVariantData == null) { - ReplyToCommand(client, "Please load map data, select a scene and a variant."); - return Plugin_Handled; - } - StartSelector(client, OnSelectorDone); - } else if(StrEqual(arg, "spawner")) { - if(g_builder.selectedVariantData == null) { - ReplyToCommand(client, "Please load map data, select a scene and a variant."); - return Plugin_Handled; - } - StartSpawner(client, OnSpawnerDone); - ReplyToCommand(client, "Spawn props to add to variant"); - } else if(StrEqual(arg, "cursor")) { - if(g_builder.selectedVariantData == null) { - ReplyToCommand(client, "Please load map data, select a scene and a variant."); - return Plugin_Handled; - } - float origin[3]; - char arg1[32]; - int entity = GetLookingPosition(client, Filter_IgnorePlayer, origin); - GetCmdArg(2, arg1, sizeof(arg1)); - ExportType exportType = Export_Model; - if(StrEqual(arg1, "hammerid")) { - exportType = Export_HammerId; - } else if(StrEqual(arg1, "targetname")) { - exportType = Export_TargetName; - } - if(entity > 0) { - g_builder.AddEntity(entity, exportType); - ReplyToCommand(client, "Added entity #%d to variant #%d", entity, g_builder.selectedVariantIndex); - } else { - ReplyToCommand(client, "No entity found"); - } - } else if(StrEqual(arg, "entityid")) { - char arg1[32]; - int entity = GetCmdArgInt(2); - GetCmdArg(3, arg1, sizeof(arg)); - ExportType exportType = Export_Model; - if(StrEqual(arg1, "hammerid")) { - exportType = Export_HammerId; - } else if(StrEqual(arg1, "targetname")) { - exportType = Export_TargetName; - } - if(entity > 0) { - g_builder.AddEntity(entity, exportType); - ReplyToCommand(client, "Added entity #%d to variant #%d", entity, g_builder.selectedVariantIndex); - } else { - ReplyToCommand(client, "No entity found"); - } - } else { - ReplyToCommand(client, "Unknown arg. Try: new, load, save, scenes, cursor"); - } - return Plugin_Handled; -} - -enum ExportType { - Export_HammerId, - Export_TargetName, - Export_Model -} -JSONObject ExportEntity(int entity, ExportType exportType = Export_Model) { - float origin[3], angles[3], size[3]; - GetEntPropVector(entity, Prop_Send, "m_vecOrigin", origin); - GetEntPropVector(entity, Prop_Send, "m_angRotation", angles); - GetEntPropVector(entity, Prop_Send, "m_vecMaxs", size); - - char model[64]; - JSONObject entityData = new JSONObject(); - GetEntityClassname(entity, model, sizeof(model)); - if(StrContains(model, "prop_") == -1) { - entityData.Set("scale", VecToArray(size)); - } - if(exportType == Export_HammerId) { - int hammerid = GetEntProp(entity, Prop_Data, "m_iHammerID"); - entityData.SetString("type", "hammerid"); - char id[16]; - IntToString(hammerid, id, sizeof(id)); - entityData.SetString("model", id); - } else if(exportType == Export_TargetName) { - GetEntPropString(entity, Prop_Data, "m_iName", model, sizeof(model)); - entityData.SetString("type", "targetname"); - entityData.SetString("model", model); - } else { - GetEntPropString(entity, Prop_Data, "m_ModelName", model, sizeof(model)); - entityData.SetString("model", model); - } - entityData.Set("origin", VecToArray(origin)); - entityData.Set("angles", VecToArray(angles)); - return entityData; -} - -bool OnSpawnerDone(int client, int entity, CompleteType result) { - PrintToServer("Randomizer OnSpawnerDone"); - if(result == Complete_PropSpawned && entity > 0) { - JSONObject entityData = ExportEntity(entity, Export_Model); - JSONArray entities = view_as(g_builder.selectedVariantData.Get("entities")); - entities.Push(entityData); - ReplyToCommand(client, "Added entity to variant"); - RemoveEntity(entity); - } - return result == Complete_PropSpawned; -} -void OnSelectorDone(int client, ArrayList entities) { - JSONArray entArray = view_as(g_builder.selectedVariantData.Get("entities")); - if(entities != null) { - JSONObject entityData; - for(int i = 0; i < entities.Length; i++) { - int ref = entities.Get(i); - entityData = ExportEntity(ref, Export_Model); - entArray.Push(entityData); - delete entityData; //? - RemoveEntity(ref); - } - PrintToChat(client, "Added %d entities to variant", entities.Length); - delete entities; - } -} - -JSONArray VecToArray(float vec[3]) { - JSONArray arr = new JSONArray(); - arr.PushFloat(vec[0]); - arr.PushFloat(vec[1]); - arr.PushFloat(vec[2]); - return arr; -} - -void Command_RandomizerBuild_Scenes(int client, int args) { - char arg[16]; - GetCmdArg(2, arg, sizeof(arg)); - if(StrEqual(arg, "new")) { - if(args < 4) { - ReplyToCommand(client, "Syntax: /rbuild scenes new "); - } else { - char name[64]; - GetCmdArg(3, name, sizeof(name)); - GetCmdArg(4, arg, sizeof(arg)); - float chance = StringToFloat(arg); - JSONObject scene = new JSONObject(); - scene.SetFloat("chance", chance); - scene.Set("variants", new JSONArray()); - g_builder.mapData.Set(name, scene); - g_builder.SelectScene(name); - JSONArray variants = view_as(g_builder.selectedSceneData.Get("variants")); - - JSONObject variantObj = new JSONObject(); - variantObj.SetInt("weight", 1); - variantObj.Set("entities", new JSONArray()); - variants.Push(variantObj); - g_builder.SelectVariant(0); - ReplyToCommand(client, "Created & selected scene & variant %s#0", name); - StartSelector(client, OnSelectorDone); - } - } else if(StrEqual(arg, "select") || StrEqual(arg, "load") || StrEqual(arg, "choose")) { - GetCmdArg(3, arg, sizeof(arg)); - if(g_builder.SelectScene(arg)) { - int variantIndex; - if(GetCmdArgIntEx(4, variantIndex)) { - if(g_builder.SelectVariant(variantIndex)) { - ReplyToCommand(client, "Selected scene: %s#%d", arg, variantIndex); - } else { - ReplyToCommand(client, "Unknown variant for scene"); - } - } else { - ReplyToCommand(client, "Selected scene: %s", arg); - } - } else { - ReplyToCommand(client, "No scene found"); - } - } else if(StrEqual(arg, "variants")) { - Command_RandomizerBuild_Variants(client, args); - } else if(args > 1) { - ReplyToCommand(client, "Unknown argument, try: new, select, variants"); - } else { - ReplyToCommand(client, "Scenes:"); - JSONObjectKeys iterator = g_builder.mapData.Keys(); - while(iterator.ReadKey(arg, sizeof(arg))) { - if(StrEqual(arg, g_builder.selectedSceneId)) { - ReplyToCommand(client, "\t%s (selected)", arg); - } else { - ReplyToCommand(client, "\t%s", arg); - } - } - } -} - -void Command_RandomizerBuild_Variants(int client, int args) { - if(g_builder.selectedSceneId[0] == '\0') { - ReplyToCommand(client, "No scene selected, select with /rbuild groups select "); - return; - } - char arg[16]; - GetCmdArg(3, arg, sizeof(arg)); - if(StrEqual(arg, "new")) { - // /rbuild group variants new [weight] - int weight; - if(!GetCmdArgIntEx(4, weight)) { - weight = 1; - } - JSONArray variants = view_as(g_builder.selectedSceneData.Get("variants")); - JSONObject variantObj = new JSONObject(); - variantObj.SetInt("weight", weight); - variantObj.Set("entities", new JSONArray()); - int index = variants.Push(variantObj); - g_builder.SelectVariant(index); - ReplyToCommand(client, "Created variant #%d", index); - } else if(StrEqual(arg, "select")) { - int index = GetCmdArgInt(4); - if(g_builder.SelectVariant(index)) { - ReplyToCommand(client, "Selected variant: %s#%d", g_builder.selectedSceneId, index); - } else { - ReplyToCommand(client, "No variant found"); - } - } else { - ReplyToCommand(client, "Variants:"); - JSONObject variantObj; - JSONArray variants = view_as(g_builder.selectedSceneData.Get("variants")); - for(int i = 0; i < variants.Length; i++) { - variantObj = view_as(variants.Get(i)); - int weight = 1; - if(variantObj.HasKey("weight")) - weight = variantObj.GetInt("weight"); - JSONArray entities = view_as(variantObj.Get("entities")); - ReplyToCommand(client, " #%d. [W:%d] [#E:%d]", i, weight, entities.Length); - } - } -} - - -enum struct SceneData { - char name[MAX_SCENE_NAME_LENGTH]; - float chance; - char group[MAX_SCENE_NAME_LENGTH]; - ArrayList variants; - - void Cleanup() { - g_MapData.activeScenes.Clear(); - SceneVariantData choice; - for(int i = 0; i < this.variants.Length; i++) { - this.variants.GetArray(i, choice); - choice.Cleanup(); - } - delete this.variants; - } -} - -enum struct SceneVariantData { - int weight; - ArrayList inputsList; - ArrayList entities; - ArrayList forcedScenes; - - void Cleanup() { - delete this.inputsList; - delete this.entities; - delete this.forcedScenes; - } -} - -enum struct VariantEntityData { - char type[32]; - char model[64]; - float origin[3]; - float angles[3]; - float scale[3]; - int color[4]; -} - -enum InputType { - Input_Classname, - Input_Targetname, - Input_HammerId -} -enum struct VariantInputData { - char name[MAX_INPUTS_CLASSNAME_LENGTH]; - InputType type; - char input[32]; - - void Trigger() { - int entity = -1; - switch(this.type) { - case Input_Classname: { - entity = FindEntityByClassname(entity, this.name); - this._trigger(entity); - } - case Input_Targetname: { - char targetname[32]; - while((entity = FindEntityByClassname(entity, "*")) != INVALID_ENT_REFERENCE) { - GetEntPropString(entity, Prop_Data, "m_iName", targetname, sizeof(targetname)); - if(StrEqual(targetname, this.name)) { - this._trigger(entity); - } - } - } - case Input_HammerId: { - int targetId = StringToInt(this.name); - while((entity = FindEntityByClassname(entity, "*")) != INVALID_ENT_REFERENCE) { - int hammerId = GetEntProp(entity, Prop_Data, "m_iHammerID"); - if(hammerId == targetId ) { - this._trigger(entity); - break; - } - } - } - } - } - - void _trigger(int entity) { - if(entity > 0 && IsValidEntity(entity)) { - if(StrEqual(this.input, "_allow_ladder")) { - if(HasEntProp(entity, Prop_Send, "m_iTeamNum")) { - SetEntProp(entity, Prop_Send, "m_iTeamNum", 0); - } else { - Log("Warn: Entity (%d) with id \"%s\" has no teamnum for \"_allow_ladder\"", entity, this.name); - } - } else if(StrEqual(this.input, "_lock")) { - AcceptEntityInput(entity, "Close"); - AcceptEntityInput(entity, "Lock"); - } else if(StrEqual(this.input, "_lock_nobreak")) { - AcceptEntityInput(entity, "Close"); - AcceptEntityInput(entity, "Lock"); - AcceptEntityInput(entity, "SetUnbreakable"); - }else { - char cmd[32]; - // Split input "a b" to a with variant "b" - int len = SplitString(this.input, " ", cmd, sizeof(cmd)); - if(len > -1) SetVariantString(this.input[len]); - - Debug("_trigger(%d): %s (v=%s)", entity, this.input, cmd); - AcceptEntityInput(entity, this.input); - } - } - } -} - -enum struct LumpEditData { - char name[MAX_INPUTS_CLASSNAME_LENGTH]; - InputType type; - char action[32]; - char value[64]; - - int _findLumpIndex(int startIndex = 0, EntityLumpEntry entry) { - int length = EntityLump.Length(); - char val[64]; - Debug("Scanning for \"%s\" (type=%d)", this.name, this.type); - for(int i = startIndex; i < length; i++) { - entry = EntityLump.Get(i); - int index = entry.FindKey("hammerid"); - if(index != -1) { - entry.Get(index, "", 0, val, sizeof(val)); - if(StrEqual(val, this.name)) { - return i; - } - } - - index = entry.FindKey("classname"); - if(index != -1) { - entry.Get(index, "", 0, val, sizeof(val)); - Debug("%s vs %s", val, this.name); - if(StrEqual(val, this.name)) { - return i; - } - } - - index = entry.FindKey("targetname"); - if(index != -1) { - entry.Get(index, "", 0, val, sizeof(val)); - if(StrEqual(val, this.name)) { - return i; - } - } - delete entry; - } - Log("Warn: Could not find any matching lump for \"%s\" (type=%d)", this.name, this.type); - return -1; - } - - void Trigger() { - int index = 0; - EntityLumpEntry entry; - while((index = this._findLumpIndex(index, entry) != -1)) { - // for(int i = 0; i < entry.Length; i++) { - // entry.Get(i, a, sizeof(a), v, sizeof(v)); - // Debug("%s=%s", a, v); - // } - this._trigger(entry); - } - } - - void _updateKey(EntityLumpEntry entry, const char[] key, const char[] value) { - int index = entry.FindKey(key); - if(index != -1) { - Debug("update key %s = %s", key, value); - entry.Update(index, key, value); - } - } - - void _trigger(EntityLumpEntry entry) { - if(StrEqual(this.action, "setclassname")) { - this._updateKey(entry, "classname", this.value); - } - - delete entry; - } -} - -enum struct MapData { - StringMap scenesKv; - ArrayList scenes; - ArrayList lumpEdits; - ArrayList activeScenes; -} - -enum loadFlags { - FLAG_NONE = 0, - FLAG_ALL_SCENES = 1, // Pick all scenes, no random chance - FLAG_ALL_VARIANTS = 2, // Pick all variants (for debug purposes), - FLAG_REFRESH = 4, // Load data bypassing cache - FLAG_FORCE_ACTIVE = 8 // Similar to ALL_SCENES, bypasses % chance -} - -// Reads (mapname).json file and parses it -public JSONObject LoadMapJson(const char[] map) { - Debug("Loading config for %s", map); - char filePath[PLATFORM_MAX_PATH]; - BuildPath(Path_SM, filePath, sizeof(filePath), "data/randomizer/%s.json", map); - if(!FileExists(filePath)) { - Log("[Randomizer] No map config file (data/randomizer/%s.json), not loading", map); - return null; - } - - JSONObject data = JSONObject.FromFile(filePath); - if(data == null) { - LogError("Could not parse map config file (data/randomizer/%s.json)", map); - return null; - } - return data; -} -public void SaveMapJson(const char[] map, JSONObject json) { - Debug("Saving config for %s", map); - char filePath[PLATFORM_MAX_PATH], filePathTemp[PLATFORM_MAX_PATH]; - BuildPath(Path_SM, filePathTemp, sizeof(filePath), "data/randomizer/%s.json.tmp", map); - BuildPath(Path_SM, filePath, sizeof(filePath), "data/randomizer/%s.json", map); - - json.ToFile(filePathTemp, JSON_INDENT(4)); - RenameFile(filePath, filePathTemp); - SetFilePermissions(filePath, FPERM_U_WRITE | FPERM_U_READ | FPERM_G_WRITE | FPERM_G_READ | FPERM_O_READ); -} - -public bool LoadMapData(const char[] map, int flags) { - JSONObject data = LoadMapJson(map); - if(data == null) { - return false; - } - - Debug("Starting parsing json data"); - - Cleanup(); - g_MapData.scenes = new ArrayList(sizeof(SceneData)); - g_MapData.scenesKv = new StringMap(); - g_MapData.lumpEdits = new ArrayList(sizeof(LumpEditData)); - g_MapData.activeScenes.Clear(); - - Profiler profiler = new Profiler(); - profiler.Start(); - - JSONObjectKeys iterator = data.Keys(); - char key[32]; - while(iterator.ReadKey(key, sizeof(key))) { - if(key[0] == '_') { - if(StrEqual(key, "_lumps")) { - JSONArray lumpsList = view_as(data.Get(key)); - if(lumpsList != null) { - for(int l = 0; l < lumpsList.Length; l++) { - loadLumpData(g_MapData.lumpEdits, view_as(lumpsList.Get(l))); - } - } - } else { - Debug("Unknown special entry \"%s\", skipping", key); - } - } else { - // if(data.GetType(key) != JSONType_Object) { - // Debug("Invalid normal entry \"%s\" (not an object), skipping", key); - // continue; - // } - JSONObject scene = view_as(data.Get(key)); - // Parses scene data and inserts to scenes - loadScene(key, scene); - } - } - - delete data; - profiler.Stop(); - Log("Parsed map file for %s(%d) and found %d scenes in %.4f seconds", map, flags, g_MapData.scenes.Length, profiler.Time); - delete profiler; - return true; -} - -// Calls LoadMapData (read&parse (mapname).json) then select scenes -public bool RunMap(const char[] map, int flags) { - if(g_MapData.scenes == null || flags & view_as(FLAG_REFRESH)) { - if(!LoadMapData(map, flags)) { - return false; - } - } - Profiler profiler = new Profiler(); - - profiler.Start(); - selectScenes(flags); - profiler.Stop(); - - Log("Done processing in %.4f seconds", g_MapData.scenes.Length, profiler.Time); - return true; -} - -void loadScene(const char key[MAX_SCENE_NAME_LENGTH], JSONObject sceneData) { - SceneData scene; - scene.name = key; - scene.chance = sceneData.GetFloat("chance"); - if(scene.chance < 0.0 || scene.chance > 1.0) { - LogError("Scene \"%s\" has invalid chance (%f)", scene.name, scene.chance); - return; - } - // TODO: load "entities", merge with choice.entities - sceneData.GetString("group", scene.group, sizeof(scene.group)); - scene.variants = new ArrayList(sizeof(SceneVariantData)); - if(!sceneData.HasKey("variants")) { - ThrowError("Failed to load: Scene \"%s\" has missing \"variants\" array", scene.name); - return; - } - JSONArray entities; - if(sceneData.HasKey("entities")) { - entities = view_as(sceneData.Get("entities")); - } - - JSONArray variants = view_as(sceneData.Get("variants")); - for(int i = 0; i < variants.Length; i++) { - // Parses choice and loads to scene.choices - loadChoice(scene, view_as(variants.Get(i)), entities); - } - g_MapData.scenes.PushArray(scene); - g_MapData.scenesKv.SetArray(scene.name, scene, sizeof(scene)); -} - -void loadChoice(SceneData scene, JSONObject choiceData, JSONArray extraEntities) { - SceneVariantData choice; - choice.weight = 1; - if(choiceData.HasKey("weight")) - choice.weight = choiceData.GetInt("weight"); - choice.entities = new ArrayList(sizeof(VariantEntityData)); - choice.inputsList = new ArrayList(sizeof(VariantInputData)); - choice.forcedScenes = new ArrayList(ByteCountToCells(MAX_SCENE_NAME_LENGTH)); - // Load in any variant-based entities - if(choiceData.HasKey("entities")) { - JSONArray entities = view_as(choiceData.Get("entities")); - for(int i = 0; i < entities.Length; i++) { - // Parses entities and loads to choice.entities - loadChoiceEntity(choice.entities, view_as(entities.Get(i))); - } - delete entities; - } - // Load in any entities that the scene has - if(extraEntities != null) { - for(int i = 0; i < extraEntities.Length; i++) { - // Parses entities and loads to choice.entities - loadChoiceEntity(choice.entities, view_as(extraEntities.Get(i))); - } - delete extraEntities; - } - // Load all inputs - if(choiceData.HasKey("inputs")) { - JSONArray inputsList = view_as(choiceData.Get("inputs")); - for(int i = 0; i < inputsList.Length; i++) { - loadChoiceInput(choice.inputsList, view_as(inputsList.Get(i))); - } - delete inputsList; - } - if(choiceData.HasKey("force_scenes")) { - JSONArray scenes = view_as(choiceData.Get("force_scenes")); - char sceneId[32]; - for(int i = 0; i < scenes.Length; i++) { - scenes.GetString(i, sceneId, sizeof(sceneId)); - choice.forcedScenes.PushString(sceneId); - } - delete scenes; - } - scene.variants.PushArray(choice); -} - -void loadChoiceInput(ArrayList list, JSONObject inputData) { - VariantInputData input; - // Check classname -> targetname -> hammerid - if(!inputData.GetString("classname", input.name, sizeof(input.name))) { - if(inputData.GetString("targetname", input.name, sizeof(input.name))) { - input.type = Input_Targetname; - } else { - if(inputData.GetString("hammerid", input.name, sizeof(input.name))) { - input.type = Input_HammerId; - } else { - int id = inputData.GetInt("hammerid"); - if(id > 0) { - input.type = Input_HammerId; - IntToString(id, input.name, sizeof(input.name)); - } else { - LogError("Missing valid input specification (hammerid, classname, targetname)"); - return; - } - } - } - } - inputData.GetString("input", input.input, sizeof(input.input)); - list.PushArray(input); -} - -void loadLumpData(ArrayList list, JSONObject inputData) { - LumpEditData input; - // Check classname -> targetname -> hammerid - if(!inputData.GetString("classname", input.name, sizeof(input.name))) { - if(inputData.GetString("targetname", input.name, sizeof(input.name))) { - input.type = Input_Targetname; - } else { - if(inputData.GetString("hammerid", input.name, sizeof(input.name))) { - input.type = Input_HammerId; - } else { - int id = inputData.GetInt("hammerid"); - if(id > 0) { - input.type = Input_HammerId; - IntToString(id, input.name, sizeof(input.name)); - } else { - LogError("Missing valid input specification (hammerid, classname, targetname)"); - return; - } - } - } - } - inputData.GetString("action", input.action, sizeof(input.action)); - inputData.GetString("value", input.value, sizeof(input.value)); - list.PushArray(input); -} - -void loadChoiceEntity(ArrayList list, JSONObject entityData) { - VariantEntityData entity; - entityData.GetString("model", entity.model, sizeof(entity.model)); - if(!entityData.GetString("type", entity.type, sizeof(entity.type))) { - entity.type = "prop_dynamic"; - } else if(entity.type[0] == '_') { - LogError("Invalid custom entity type \"%s\"", entity.type); - return; - } - GetVector(entityData, "origin", entity.origin); - GetVector(entityData, "angles", entity.angles); - GetVector(entityData, "scale", entity.scale); - GetColor(entityData, "color", entity.color); - list.PushArray(entity); -} - -bool GetVector(JSONObject obj, const char[] key, float out[3]) { - if(!obj.HasKey(key)) return false; - JSONArray vecArray = view_as(obj.Get(key)); - if(vecArray != null) { - out[0] = vecArray.GetFloat(0); - out[1] = vecArray.GetFloat(1); - out[2] = vecArray.GetFloat(2); - } - return true; -} - -void GetColor(JSONObject obj, const char[] key, int out[4], int defaultColor[4] = { 255, 255, 255, 255 }) { - if(obj.HasKey(key)) { - JSONArray vecArray = view_as(obj.Get(key)); - out[0] = vecArray.GetInt(0); - out[1] = vecArray.GetInt(1); - out[2] = vecArray.GetInt(2); - if(vecArray.Length == 4) - out[3] = vecArray.GetInt(3); - else - out[3] = 255; - } else { - out = defaultColor; - } -} - -void selectScenes(int flags = 0) { - SceneData scene; - StringMap groups = new StringMap(); - ArrayList list; - // Select and spawn non-group scenes - // TODO: refactor to use .scenesKv - for(int i = 0; i < g_MapData.scenes.Length; i++) { - g_MapData.scenes.GetArray(i, scene); - // TODO: Exclusions - // Select scene if not in group, or add to list of groups - if(scene.group[0] == '\0') { - selectScene(scene, flags); - } else { - // Load it into group list - if(!groups.GetValue(scene.group, list)) { - list = new ArrayList(); - } - list.Push(i); - groups.SetValue(scene.group, list); - } - } - - // Iterate through groups and select a random scene: - StringMapSnapshot snapshot = groups.Snapshot(); - char key[MAX_SCENE_NAME_LENGTH]; - for(int i = 0; i < snapshot.Length; i++) { - snapshot.GetKey(i, key, sizeof(key)); - groups.GetValue(key, list); - // Select a random scene from the group: - int index = GetURandomInt() % list.Length; - index = list.Get(index); - g_MapData.scenes.GetArray(index, scene); - - Debug("Selected scene \"%s\" for group %s (%d members)", scene.name, key, list.Length); - selectScene(scene, flags); - delete list; - } - // Traverse active scenes, loading any other scene it requires (via .force_scenes) - ActiveSceneData aScene; - SceneVariantData choice; - ArrayList forcedScenes = new ArrayList(ByteCountToCells(MAX_SCENE_NAME_LENGTH)); - for(int i = 0; i < g_MapData.activeScenes.Length; i++) { - g_MapData.activeScenes.GetArray(i, aScene); - g_MapData.scenes.GetArray(i, scene); - scene.variants.GetArray(aScene.variantIndex, choice); - if(choice.forcedScenes != null) { - for(int j = 0; j < choice.forcedScenes.Length; j++) { - choice.forcedScenes.GetString(j, key, sizeof(key)); - forcedScenes.PushString(key); - } - } - } - // Iterate and activate any forced scenes - for(int i = 0; i < forcedScenes.Length; i++) { - forcedScenes.GetString(i, key, sizeof(key)); - // Check if scene was already loaded - bool isSceneAlreadyLoaded = false; - for(int j = 0; j < g_MapData.activeScenes.Length; i++) { - g_MapData.activeScenes.GetArray(j, aScene); - if(StrEqual(aScene.name, key)) { - isSceneAlreadyLoaded = true; - break; - } - } - if(isSceneAlreadyLoaded) continue; - g_MapData.scenesKv.GetArray(key, scene, sizeof(scene)); - selectScene(scene, flags | view_as(FLAG_FORCE_ACTIVE)); - } - - delete forcedScenes; - delete snapshot; - delete groups; -} - -void selectScene(SceneData scene, int flags) { - // Use the .chance field unless FLAG_ALL_SCENES or FLAG_FORCE_ACTIVE is set - if(~flags & view_as(FLAG_ALL_SCENES) && ~flags & view_as(FLAG_FORCE_ACTIVE) && GetURandomFloat() > scene.chance) { - return; - } - - if(scene.variants.Length == 0) { - LogError("Warn: No variants were found for scene \"%s\"", scene.name); - return; - } - - ArrayList choices = new ArrayList(); - SceneVariantData choice; - int index; - Debug("Scene %s has %d variants", scene.name, scene.variants.Length); - // Weighted random: Push N times dependent on weight - for(int i = 0; i < scene.variants.Length; i++) { - scene.variants.GetArray(i, choice); - if(flags & view_as(FLAG_ALL_VARIANTS)) { - spawnVariant(choice); - } else { - if(choice.weight <= 0) { - PrintToServer("Warn: Variant %d in scene %s has invalid weight", i, scene.name); - continue; - } - for(int c = 0; c < choice.weight; c++) { - choices.Push(i); - } - } - } - Debug("Total choices: %d", choices.Length); - if(flags & view_as(FLAG_ALL_VARIANTS)) { - delete choices; - } else if(choices.Length > 0) { - index = GetURandomInt() % choices.Length; - index = choices.Get(index); - delete choices; - Log("Spawned scene \"%s\" with variant #%d", scene.name, index); - scene.variants.GetArray(index, choice); - spawnVariant(choice); - } - ActiveSceneData aScene; - strcopy(aScene.name, sizeof(aScene.name), scene.name); - aScene.variantIndex = index; - g_MapData.activeScenes.PushArray(aScene); -} - -void spawnVariant(SceneVariantData choice) { - VariantEntityData entity; - for(int i = 0; i < choice.entities.Length; i++) { - choice.entities.GetArray(i, entity); - spawnEntity(entity); - } - - if(choice.inputsList.Length > 0) { - VariantInputData input; - for(int i = 0; i < choice.inputsList.Length; i++) { - choice.inputsList.GetArray(i, input); - input.Trigger(); - } - } -} - -void spawnEntity(VariantEntityData entity) { - if(StrEqual(entity.type, "env_fire")) { - Debug("spawning \"%s\" at (%.1f %.1f %.1f) rot (%.0f %.0f %.0f)", entity.type, entity.origin[0], entity.origin[1], entity.origin[2], entity.angles[0], entity.angles[1], entity.angles[2]); - CreateFire(entity.origin, 20.0, 100.0, 0.0); - } else if(StrEqual(entity.type, "env_physics_blocker") || StrEqual(entity.type, "env_player_blocker")) { - CreateEnvBlockerScaled(entity.type, entity.origin, entity.scale); - } else if(StrEqual(entity.type, "infodecal")) { - CreateDecal(entity.model, entity.origin); - } else if(StrContains(entity.type, "prop_") == 0) { - if(entity.model[0] == '\0') { - LogError("Missing model for entity with type \"%s\"", entity.type); - return; - } - PrecacheModel(entity.model); - int prop = CreateProp(entity.type, entity.model, entity.origin, entity.angles); - SetEntityRenderColor(prop, entity.color[0], entity.color[1], entity.color[2], entity.color[3]); - } else if(StrEqual(entity.type, "hammerid")) { - int targetId = StringToInt(entity.model); - if(targetId > 0) { - int ent = -1; - while((ent = FindEntityByClassname(ent, "*")) != INVALID_ENT_REFERENCE) { - int hammerId = GetEntProp(ent, Prop_Data, "m_iHammerID"); - if(hammerId == targetId) { - Debug("moved entity (hammerid=%d) to %.0f %.0f %.0f rot %.0f %.0f %.0f", targetId, entity.origin[0], entity.origin[1], entity.origin[2], entity.angles[0], entity.angles[1], entity.angles[2]); - TeleportEntity(ent, entity.origin, entity.angles, NULL_VECTOR); - return; - } - } - } - Debug("Warn: Could not find entity (hammerid=%d) (model=%s)", targetId, entity.model); - } else if(StrEqual(entity.type, "targetname")) { - int ent = -1; - char targetname[64]; - bool found = false; - while((ent = FindEntityByClassname(ent, "*")) != INVALID_ENT_REFERENCE) { - GetEntPropString(ent, Prop_Data, "m_iName", targetname, sizeof(targetname)); - if(StrEqual(entity.model, targetname)) { - Debug("moved entity (targetname=%s) to %.0f %.0f %.0f rot %.0f %.0f %.0f", entity.model, entity.origin[0], entity.origin[1], entity.origin[2], entity.angles[0], entity.angles[1], entity.angles[2]); - TeleportEntity(ent, entity.origin, entity.angles, NULL_VECTOR); - found = true; - } - } - if(!found) - Debug("Warn: Could not find entity (targetname=%s)", entity.model); - } else if(StrEqual(entity.type, "classname")) { - int ent = -1; - char classname[64]; - bool found; - while((ent = FindEntityByClassname(ent, classname)) != INVALID_ENT_REFERENCE) { - Debug("moved entity (classname=%s) to %.0f %.0f %.0f rot %.0f %.0f %.0f", entity.model, entity.origin[0], entity.origin[1], entity.origin[2], entity.angles[0], entity.angles[1], entity.angles[2]); - TeleportEntity(ent, entity.origin, entity.angles, NULL_VECTOR); - found = true; - } - if(!found) - Debug("Warn: Could not find entity (classname=%s)", entity.model); - } else { - LogError("Unknown entity type \"%s\"", entity.type); - } -} - -void Debug(const char[] format, any ...) { - #if defined DEBUG_SCENE_PARSE - char buffer[192]; - - VFormat(buffer, sizeof(buffer), format, 2); - - PrintToServer("[Randomizer::Debug] %s", buffer); - PrintToConsoleAll("[Randomizer::Debug] %s", buffer); - - #endif -} - -void Log(const char[] format, any ...) { - char buffer[192]; - - VFormat(buffer, sizeof(buffer), format, 2); - - PrintToServer("[Randomizer] %s", buffer); -} - -void Cleanup() { - if(g_MapData.scenes != null) { - SceneData scene; - for(int i = 0; i < g_MapData.scenes.Length; i++) { - g_MapData.scenes.GetArray(i, scene); - scene.Cleanup(); - } - delete g_MapData.scenes; - } - delete g_MapData.lumpEdits; - - DeleteCustomEnts(); - g_MapData.activeScenes.Clear(); +#pragma semicolon 1 +#pragma newdecls required + +//#define DEBUG + +#define PLUGIN_VERSION "1.0" +#define DEBUG_SCENE_PARSE 1 +#define DEBUG_BLOCKERS 1 + +#include +#include +//#include +#include +// #include +#include +#include +#include +#undef REQUIRE_PLUGIN +#include + +int g_iLaserIndex; +#if defined DEBUG_BLOCKERS +#include +#endif +#define ENT_PROP_NAME "l4d2_randomizer" +#define ENT_ENV_NAME "l4d2_randomizer" +#define ENT_BLOCKER_NAME "l4d2_randomizer" +#include + +#define MAX_SCENE_NAME_LENGTH 32 +#define MAX_INPUTS_CLASSNAME_LENGTH 64 + + +ConVar cvarEnabled; +enum struct ActiveSceneData { + char name[MAX_SCENE_NAME_LENGTH]; + int variantIndex; +} +MapData g_MapData; +BuilderData g_builder; +char currentMap[64]; + +enum struct BuilderData { + JSONObject mapData; + + JSONObject selectedSceneData; + char selectedSceneId[64]; + + JSONObject selectedVariantData; + int selectedVariantIndex; + + void Cleanup() { + this.selectedSceneData = null; + this.selectedVariantData = null; + this.selectedVariantIndex = -1; + this.selectedSceneId[0] = '\0'; + if(this.mapData != null) + delete this.mapData; + // JSONcleanup_and_delete(this.mapData); + } + + bool SelectScene(const char[] group) { + if(!g_builder.mapData.HasKey(group)) return false; + this.selectedSceneData = view_as(g_builder.mapData.Get(group)); + strcopy(this.selectedSceneId, sizeof(this.selectedSceneId), group); + return true; + } + + /** + * Select a variant, enter -1 to not select any (scene's entities) + */ + bool SelectVariant(int index = -1) { + if(this.selectedSceneData == null) LogError("SelectVariant called, but no group selected"); + JSONArray variants = view_as(this.selectedSceneData.Get("variants")); + if(index >= variants.Length) return false; + else if(index < -1) return false; + else if(index > -1) { + this.selectedVariantData = view_as(variants.Get(index)); + } else { + this.selectedVariantData = null; + } + this.selectedVariantIndex = index; + return true; + } + + void AddEntity(int entity, ExportType exportType = Export_Model) { + JSONArray entities; + if(g_builder.selectedVariantData == null) { + // Create .entities if doesn't exist: + if(!g_builder.selectedSceneData.HasKey("entities")) { + g_builder.selectedSceneData.Set("entities", new JSONArray()); + } + entities = view_as(g_builder.selectedSceneData.Get("entities")); + } else { + entities = view_as(g_builder.selectedVariantData.Get("entities")); + } + JSONObject entityData = ExportEntity(entity, Export_Model); + entities.Push(entityData); + } +} + +#include + +public Plugin myinfo = +{ + name = "L4D2 Randomizer", + author = "jackzmc", + description = "", + version = PLUGIN_VERSION, + url = "https://github.com/Jackzmc/sourcemod-plugins" +}; + +public void OnPluginStart() { + EngineVersion g_Game = GetEngineVersion(); + if(g_Game != Engine_Left4Dead && g_Game != Engine_Left4Dead2) { + SetFailState("This plugin is for L4D/L4D2 only."); + } + + RegAdminCmd("sm_rcycle", Command_CycleRandom, ADMFLAG_CHEATS); + RegAdminCmd("sm_expent", Command_ExportEnt, ADMFLAG_GENERIC); + RegAdminCmd("sm_rbuild", Command_RandomizerBuild, ADMFLAG_CHEATS); + + cvarEnabled = CreateConVar("sm_randomizer_enabled", "0"); + + g_MapData.activeScenes = new ArrayList(sizeof(ActiveSceneData)); +} + + +// TODO: on round start +public void OnMapStart() { + g_iLaserIndex = PrecacheModel("materials/sprites/laserbeam.vmt", true); + GetCurrentMap(currentMap, sizeof(currentMap)); + if(cvarEnabled.BoolValue) + CreateTimer(5.0, Timer_Run); +} + +public void OnMapEnd() { + g_builder.Cleanup(); + Cleanup(); +} + +public void OnMapInit(const char[] map) { + // if(cvarEnabled.BoolValue) { + // if(LoadMapData(currentMap, FLAG_NONE) && g_MapData.lumpEdits.Length > 0) { + // Log("Found %d lump edits, running...", g_MapData.lumpEdits.Length); + // LumpEditData lump; + // for(int i = 0; i < g_MapData.lumpEdits.Length; i++) { + // g_MapData.lumpEdits.GetArray(i, lump); + // lump.Trigger(); + // } + // hasRan = true; + // } + // } +} + +public void OnConfigsExecuted() { + +} + +Action Timer_Run(Handle h) { + if(cvarEnabled.BoolValue) + RunMap(currentMap, FLAG_NONE); + return Plugin_Handled; +} + +stock int GetLookingEntity(int client, TraceEntityFilter filter) { + 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 int GetLookingPosition(int client, TraceEntityFilter filter, float pos[3]) { + float ang[3]; + GetClientEyePosition(client, pos); + GetClientEyeAngles(client, ang); + TR_TraceRayFilter(pos, ang, MASK_SOLID, RayType_Infinite, filter, client); + if(TR_DidHit()) { + TR_GetEndPosition(pos); + return TR_GetEntityIndex(); + } + return -1; +} + + +public Action Command_CycleRandom(int client, int args) { + if(args > 0) { + DeleteCustomEnts(); + + int flags = GetCmdArgInt(1) | view_as(FLAG_REFRESH); + RunMap(currentMap, flags); + if(client > 0) + PrintCenterText(client, "Cycled flags=%d", flags); + } else { + ReplyToCommand(client, "Active Scenes:"); + ActiveSceneData scene; + for(int i = 0; i < g_MapData.activeScenes.Length; i++) { + g_MapData.activeScenes.GetArray(i, scene); + ReplyToCommand(client, "\t%s: variant #%d", scene.name, scene.variantIndex); + } + } + return Plugin_Handled; +} + +Action Command_ExportEnt(int client, int args) { + float origin[3]; + int entity = GetLookingPosition(client, Filter_IgnorePlayer, origin); + float angles[3]; + float size[3]; + char arg1[32]; + GetCmdArg(1, arg1, sizeof(arg1)); + if(entity > 0) { + + GetEntPropVector(entity, Prop_Send, "m_vecOrigin", origin); + GetEntPropVector(entity, Prop_Send, "m_angRotation", angles); + GetEntPropVector(entity, Prop_Send, "m_vecMaxs", size); + + char model[64]; + ReplyToCommand(client, "{"); + GetEntityClassname(entity, model, sizeof(model)); + if(StrContains(model, "prop_") == -1) { + ReplyToCommand(client, "\t\"scale\": [%.2f, %.2f, %.2f],", size[0], size[1], size[2]); + } + if(StrEqual(arg1, "hammerid")) { + int hammerid = GetEntProp(entity, Prop_Data, "m_iHammerID"); + ReplyToCommand(client, "\t\"type\": \"hammerid\","); + ReplyToCommand(client, "\t\"model\": \"%d\",", hammerid); + } else if(StrEqual(arg1, "targetname")) { + GetEntPropString(entity, Prop_Data, "m_iName", model, sizeof(model)); + ReplyToCommand(client, "\t\"type\": \"targetname\","); + ReplyToCommand(client, "\t\"model\": \"%s\",", model); + } else { + GetEntPropString(entity, Prop_Data, "m_ModelName", model, sizeof(model)); + ReplyToCommand(client, "\t\"model\": \"%s\",", model); + } + ReplyToCommand(client, "\t\"origin\": [%.2f, %.2f, %.2f],", origin[0], origin[1], origin[2]); + ReplyToCommand(client, "\t\"angles\": [%.2f, %.2f, %.2f]", angles[0], angles[1], angles[2]); + ReplyToCommand(client, "}"); + } else { + if(!StrEqual(arg1, "cursor")) + GetEntPropVector(client, Prop_Send, "m_vecOrigin", origin); + GetEntPropVector(client, Prop_Send, "m_angRotation", angles); + ReplyToCommand(client, "{"); + ReplyToCommand(client, "\t\"type\": \"%s\",", arg1); + ReplyToCommand(client, "\t\"scale\": [%.2f, %.2f, %.2f],", size[0], size[1], size[2]); + ReplyToCommand(client, "\t\"origin\": [%.2f, %.2f, %.2f],", origin[0], origin[1], origin[2]); + ReplyToCommand(client, "\t\"angles\": [%.2f, %.2f, %.2f]", angles[0], angles[1], angles[2]); + ReplyToCommand(client, "}"); + } + return Plugin_Handled; +} +Action Command_RandomizerBuild(int client, int args) { + char arg[64]; + GetCmdArg(1, arg, sizeof(arg)); + if(StrEqual(arg, "new")) { + JSONObject temp = LoadMapJson(currentMap); + GetCmdArg(2, arg, sizeof(arg)); + if(temp != null && !StrEqual(arg, "confirm")) { + delete temp; + ReplyToCommand(client, "Existing map data found, enter /rbuild new confirm to overwrite."); + return Plugin_Handled; + } + g_builder.Cleanup(); + g_builder.mapData = new JSONObject(); + SaveMapJson(currentMap, g_builder.mapData); + ReplyToCommand(client, "Started new map data for %s", currentMap); + } else if(StrEqual(arg, "load")) { + if(args >= 2) { + GetCmdArg(2, arg, sizeof(arg)); + } else { + strcopy(arg, sizeof(arg), currentMap); + } + g_builder.Cleanup(); + g_builder.mapData = LoadMapJson(arg); + if(g_builder.mapData != null) { + ReplyToCommand(client, "Loaded map data for %s", arg); + } else { + ReplyToCommand(client, "No map data found for %s", arg); + } + } else if(StrEqual(arg, "menu")) { + OpenMainMenu(client); + } else if(g_builder.mapData == null) { + ReplyToCommand(client, "No map data for %s, either load with /rbuild load, or start new /rbuild new", currentMap); + return Plugin_Handled; + } else if(StrEqual(arg, "save")) { + SaveMapJson(currentMap, g_builder.mapData); + ReplyToCommand(client, "Saved %s", currentMap); + } else if(StrEqual(arg, "scenes")) { + Command_RandomizerBuild_Scenes(client, args); + } else if(StrEqual(arg, "sel") || StrEqual(arg, "selector")) { + if(g_builder.selectedVariantData == null) { + ReplyToCommand(client, "Please load map data, select a scene and a variant."); + return Plugin_Handled; + } + StartSelector(client, OnSelectorDone); + } else if(StrEqual(arg, "spawner")) { + if(g_builder.selectedVariantData == null) { + ReplyToCommand(client, "Please load map data, select a scene and a variant."); + return Plugin_Handled; + } + StartSpawner(client, OnSpawnerDone); + ReplyToCommand(client, "Spawn props to add to variant"); + } else if(StrEqual(arg, "cursor")) { + if(g_builder.selectedVariantData == null) { + ReplyToCommand(client, "Please load map data, select a scene and a variant."); + return Plugin_Handled; + } + float origin[3]; + char arg1[32]; + int entity = GetLookingPosition(client, Filter_IgnorePlayer, origin); + GetCmdArg(2, arg1, sizeof(arg1)); + ExportType exportType = Export_Model; + if(StrEqual(arg1, "hammerid")) { + exportType = Export_HammerId; + } else if(StrEqual(arg1, "targetname")) { + exportType = Export_TargetName; + } + if(entity > 0) { + g_builder.AddEntity(entity, exportType); + ReplyToCommand(client, "Added entity #%d to variant #%d", entity, g_builder.selectedVariantIndex); + } else { + ReplyToCommand(client, "No entity found"); + } + } else if(StrEqual(arg, "entityid")) { + char arg1[32]; + int entity = GetCmdArgInt(2); + GetCmdArg(3, arg1, sizeof(arg)); + ExportType exportType = Export_Model; + if(StrEqual(arg1, "hammerid")) { + exportType = Export_HammerId; + } else if(StrEqual(arg1, "targetname")) { + exportType = Export_TargetName; + } + if(entity > 0) { + g_builder.AddEntity(entity, exportType); + ReplyToCommand(client, "Added entity #%d to variant #%d", entity, g_builder.selectedVariantIndex); + } else { + ReplyToCommand(client, "No entity found"); + } + } else { + ReplyToCommand(client, "Unknown arg. Try: new, load, save, scenes, cursor"); + } + return Plugin_Handled; +} + +enum ExportType { + Export_HammerId, + Export_TargetName, + Export_Model +} +JSONObject ExportEntity(int entity, ExportType exportType = Export_Model) { + float origin[3], angles[3], size[3]; + GetEntPropVector(entity, Prop_Send, "m_vecOrigin", origin); + GetEntPropVector(entity, Prop_Send, "m_angRotation", angles); + GetEntPropVector(entity, Prop_Send, "m_vecMaxs", size); + + char model[64]; + JSONObject entityData = new JSONObject(); + GetEntityClassname(entity, model, sizeof(model)); + if(StrContains(model, "prop_") == -1) { + entityData.Set("scale", VecToArray(size)); + } + if(exportType == Export_HammerId) { + int hammerid = GetEntProp(entity, Prop_Data, "m_iHammerID"); + entityData.SetString("type", "hammerid"); + char id[16]; + IntToString(hammerid, id, sizeof(id)); + entityData.SetString("model", id); + } else if(exportType == Export_TargetName) { + GetEntPropString(entity, Prop_Data, "m_iName", model, sizeof(model)); + entityData.SetString("type", "targetname"); + entityData.SetString("model", model); + } else { + GetEntPropString(entity, Prop_Data, "m_ModelName", model, sizeof(model)); + entityData.SetString("model", model); + } + entityData.Set("origin", VecToArray(origin)); + entityData.Set("angles", VecToArray(angles)); + return entityData; +} + +bool OnSpawnerDone(int client, int entity, CompleteType result) { + PrintToServer("Randomizer OnSpawnerDone"); + if(result == Complete_PropSpawned && entity > 0) { + JSONObject entityData = ExportEntity(entity, Export_Model); + JSONArray entities = view_as(g_builder.selectedVariantData.Get("entities")); + entities.Push(entityData); + ReplyToCommand(client, "Added entity to variant"); + RemoveEntity(entity); + } + return result == Complete_PropSpawned; +} +void OnSelectorDone(int client, ArrayList entities) { + JSONArray entArray = view_as(g_builder.selectedVariantData.Get("entities")); + if(entities != null) { + JSONObject entityData; + for(int i = 0; i < entities.Length; i++) { + int ref = entities.Get(i); + entityData = ExportEntity(ref, Export_Model); + entArray.Push(entityData); + delete entityData; //? + RemoveEntity(ref); + } + PrintToChat(client, "Added %d entities to variant", entities.Length); + delete entities; + } +} + +JSONArray VecToArray(float vec[3]) { + JSONArray arr = new JSONArray(); + arr.PushFloat(vec[0]); + arr.PushFloat(vec[1]); + arr.PushFloat(vec[2]); + return arr; +} + +void Command_RandomizerBuild_Scenes(int client, int args) { + char arg[16]; + GetCmdArg(2, arg, sizeof(arg)); + if(StrEqual(arg, "new")) { + if(args < 4) { + ReplyToCommand(client, "Syntax: /rbuild scenes new "); + } else { + char name[64]; + GetCmdArg(3, name, sizeof(name)); + GetCmdArg(4, arg, sizeof(arg)); + float chance = StringToFloat(arg); + JSONObject scene = new JSONObject(); + scene.SetFloat("chance", chance); + scene.Set("variants", new JSONArray()); + g_builder.mapData.Set(name, scene); + g_builder.SelectScene(name); + JSONArray variants = view_as(g_builder.selectedSceneData.Get("variants")); + + JSONObject variantObj = new JSONObject(); + variantObj.SetInt("weight", 1); + variantObj.Set("entities", new JSONArray()); + variants.Push(variantObj); + g_builder.SelectVariant(0); + ReplyToCommand(client, "Created & selected scene & variant %s#0", name); + StartSelector(client, OnSelectorDone); + } + } else if(StrEqual(arg, "select") || StrEqual(arg, "load") || StrEqual(arg, "choose")) { + GetCmdArg(3, arg, sizeof(arg)); + if(g_builder.SelectScene(arg)) { + int variantIndex; + if(GetCmdArgIntEx(4, variantIndex)) { + if(g_builder.SelectVariant(variantIndex)) { + ReplyToCommand(client, "Selected scene: %s#%d", arg, variantIndex); + } else { + ReplyToCommand(client, "Unknown variant for scene"); + } + } else { + ReplyToCommand(client, "Selected scene: %s", arg); + } + } else { + ReplyToCommand(client, "No scene found"); + } + } else if(StrEqual(arg, "variants")) { + Command_RandomizerBuild_Variants(client, args); + } else if(args > 1) { + ReplyToCommand(client, "Unknown argument, try: new, select, variants"); + } else { + ReplyToCommand(client, "Scenes:"); + JSONObjectKeys iterator = g_builder.mapData.Keys(); + while(iterator.ReadKey(arg, sizeof(arg))) { + if(StrEqual(arg, g_builder.selectedSceneId)) { + ReplyToCommand(client, "\t%s (selected)", arg); + } else { + ReplyToCommand(client, "\t%s", arg); + } + } + } +} + +void Command_RandomizerBuild_Variants(int client, int args) { + if(g_builder.selectedSceneId[0] == '\0') { + ReplyToCommand(client, "No scene selected, select with /rbuild groups select "); + return; + } + char arg[16]; + GetCmdArg(3, arg, sizeof(arg)); + if(StrEqual(arg, "new")) { + // /rbuild group variants new [weight] + int weight; + if(!GetCmdArgIntEx(4, weight)) { + weight = 1; + } + JSONArray variants = view_as(g_builder.selectedSceneData.Get("variants")); + JSONObject variantObj = new JSONObject(); + variantObj.SetInt("weight", weight); + variantObj.Set("entities", new JSONArray()); + int index = variants.Push(variantObj); + g_builder.SelectVariant(index); + ReplyToCommand(client, "Created variant #%d", index); + } else if(StrEqual(arg, "select")) { + int index = GetCmdArgInt(4); + if(g_builder.SelectVariant(index)) { + ReplyToCommand(client, "Selected variant: %s#%d", g_builder.selectedSceneId, index); + } else { + ReplyToCommand(client, "No variant found"); + } + } else { + ReplyToCommand(client, "Variants:"); + JSONObject variantObj; + JSONArray variants = view_as(g_builder.selectedSceneData.Get("variants")); + for(int i = 0; i < variants.Length; i++) { + variantObj = view_as(variants.Get(i)); + int weight = 1; + if(variantObj.HasKey("weight")) + weight = variantObj.GetInt("weight"); + JSONArray entities = view_as(variantObj.Get("entities")); + ReplyToCommand(client, " #%d. [W:%d] [#E:%d]", i, weight, entities.Length); + } + } +} + + +enum struct SceneData { + char name[MAX_SCENE_NAME_LENGTH]; + float chance; + char group[MAX_SCENE_NAME_LENGTH]; + ArrayList variants; + + void Cleanup() { + g_MapData.activeScenes.Clear(); + SceneVariantData choice; + for(int i = 0; i < this.variants.Length; i++) { + this.variants.GetArray(i, choice); + choice.Cleanup(); + } + delete this.variants; + } +} + +enum struct SceneVariantData { + int weight; + ArrayList inputsList; + ArrayList entities; + ArrayList forcedScenes; + + void Cleanup() { + delete this.inputsList; + delete this.entities; + delete this.forcedScenes; + } +} + +enum struct VariantEntityData { + char type[32]; + char model[64]; + float origin[3]; + float angles[3]; + float scale[3]; + int color[4]; +} + +enum InputType { + Input_Classname, + Input_Targetname, + Input_HammerId +} +enum struct VariantInputData { + char name[MAX_INPUTS_CLASSNAME_LENGTH]; + InputType type; + char input[32]; + + void Trigger() { + int entity = -1; + switch(this.type) { + case Input_Classname: { + entity = FindEntityByClassname(entity, this.name); + this._trigger(entity); + } + case Input_Targetname: { + char targetname[32]; + while((entity = FindEntityByClassname(entity, "*")) != INVALID_ENT_REFERENCE) { + GetEntPropString(entity, Prop_Data, "m_iName", targetname, sizeof(targetname)); + if(StrEqual(targetname, this.name)) { + this._trigger(entity); + } + } + } + case Input_HammerId: { + int targetId = StringToInt(this.name); + while((entity = FindEntityByClassname(entity, "*")) != INVALID_ENT_REFERENCE) { + int hammerId = GetEntProp(entity, Prop_Data, "m_iHammerID"); + if(hammerId == targetId ) { + this._trigger(entity); + break; + } + } + } + } + } + + void _trigger(int entity) { + if(entity > 0 && IsValidEntity(entity)) { + if(StrEqual(this.input, "_allow_ladder")) { + if(HasEntProp(entity, Prop_Send, "m_iTeamNum")) { + SetEntProp(entity, Prop_Send, "m_iTeamNum", 0); + } else { + Log("Warn: Entity (%d) with id \"%s\" has no teamnum for \"_allow_ladder\"", entity, this.name); + } + } else if(StrEqual(this.input, "_lock")) { + AcceptEntityInput(entity, "Close"); + AcceptEntityInput(entity, "Lock"); + } else if(StrEqual(this.input, "_lock_nobreak")) { + AcceptEntityInput(entity, "Close"); + AcceptEntityInput(entity, "Lock"); + AcceptEntityInput(entity, "SetUnbreakable"); + }else { + char cmd[32]; + // Split input "a b" to a with variant "b" + int len = SplitString(this.input, " ", cmd, sizeof(cmd)); + if(len > -1) SetVariantString(this.input[len]); + + Debug("_trigger(%d): %s (v=%s)", entity, this.input, cmd); + AcceptEntityInput(entity, this.input); + } + } + } +} + +enum struct LumpEditData { + char name[MAX_INPUTS_CLASSNAME_LENGTH]; + InputType type; + char action[32]; + char value[64]; + + int _findLumpIndex(int startIndex = 0, EntityLumpEntry entry) { + int length = EntityLump.Length(); + char val[64]; + Debug("Scanning for \"%s\" (type=%d)", this.name, this.type); + for(int i = startIndex; i < length; i++) { + entry = EntityLump.Get(i); + int index = entry.FindKey("hammerid"); + if(index != -1) { + entry.Get(index, "", 0, val, sizeof(val)); + if(StrEqual(val, this.name)) { + return i; + } + } + + index = entry.FindKey("classname"); + if(index != -1) { + entry.Get(index, "", 0, val, sizeof(val)); + Debug("%s vs %s", val, this.name); + if(StrEqual(val, this.name)) { + return i; + } + } + + index = entry.FindKey("targetname"); + if(index != -1) { + entry.Get(index, "", 0, val, sizeof(val)); + if(StrEqual(val, this.name)) { + return i; + } + } + delete entry; + } + Log("Warn: Could not find any matching lump for \"%s\" (type=%d)", this.name, this.type); + return -1; + } + + void Trigger() { + int index = 0; + EntityLumpEntry entry; + while((index = this._findLumpIndex(index, entry) != -1)) { + // for(int i = 0; i < entry.Length; i++) { + // entry.Get(i, a, sizeof(a), v, sizeof(v)); + // Debug("%s=%s", a, v); + // } + this._trigger(entry); + } + } + + void _updateKey(EntityLumpEntry entry, const char[] key, const char[] value) { + int index = entry.FindKey(key); + if(index != -1) { + Debug("update key %s = %s", key, value); + entry.Update(index, key, value); + } + } + + void _trigger(EntityLumpEntry entry) { + if(StrEqual(this.action, "setclassname")) { + this._updateKey(entry, "classname", this.value); + } + + delete entry; + } +} + +enum struct MapData { + StringMap scenesKv; + ArrayList scenes; + ArrayList lumpEdits; + ArrayList activeScenes; +} + +enum loadFlags { + FLAG_NONE = 0, + FLAG_ALL_SCENES = 1, // Pick all scenes, no random chance + FLAG_ALL_VARIANTS = 2, // Pick all variants (for debug purposes), + FLAG_REFRESH = 4, // Load data bypassing cache + FLAG_FORCE_ACTIVE = 8 // Similar to ALL_SCENES, bypasses % chance +} + +// Reads (mapname).json file and parses it +public JSONObject LoadMapJson(const char[] map) { + Debug("Loading config for %s", map); + char filePath[PLATFORM_MAX_PATH]; + BuildPath(Path_SM, filePath, sizeof(filePath), "data/randomizer/%s.json", map); + if(!FileExists(filePath)) { + Log("[Randomizer] No map config file (data/randomizer/%s.json), not loading", map); + return null; + } + + JSONObject data = JSONObject.FromFile(filePath); + if(data == null) { + LogError("Could not parse map config file (data/randomizer/%s.json)", map); + return null; + } + return data; +} +public void SaveMapJson(const char[] map, JSONObject json) { + Debug("Saving config for %s", map); + char filePath[PLATFORM_MAX_PATH], filePathTemp[PLATFORM_MAX_PATH]; + BuildPath(Path_SM, filePathTemp, sizeof(filePath), "data/randomizer/%s.json.tmp", map); + BuildPath(Path_SM, filePath, sizeof(filePath), "data/randomizer/%s.json", map); + + json.ToFile(filePathTemp, JSON_INDENT(4)); + RenameFile(filePath, filePathTemp); + SetFilePermissions(filePath, FPERM_U_WRITE | FPERM_U_READ | FPERM_G_WRITE | FPERM_G_READ | FPERM_O_READ); +} + +public bool LoadMapData(const char[] map, int flags) { + JSONObject data = LoadMapJson(map); + if(data == null) { + return false; + } + + Debug("Starting parsing json data"); + + Cleanup(); + g_MapData.scenes = new ArrayList(sizeof(SceneData)); + g_MapData.scenesKv = new StringMap(); + g_MapData.lumpEdits = new ArrayList(sizeof(LumpEditData)); + g_MapData.activeScenes.Clear(); + + Profiler profiler = new Profiler(); + profiler.Start(); + + JSONObjectKeys iterator = data.Keys(); + char key[32]; + while(iterator.ReadKey(key, sizeof(key))) { + if(key[0] == '_') { + if(StrEqual(key, "_lumps")) { + JSONArray lumpsList = view_as(data.Get(key)); + if(lumpsList != null) { + for(int l = 0; l < lumpsList.Length; l++) { + loadLumpData(g_MapData.lumpEdits, view_as(lumpsList.Get(l))); + } + } + } else { + Debug("Unknown special entry \"%s\", skipping", key); + } + } else { + // if(data.GetType(key) != JSONType_Object) { + // Debug("Invalid normal entry \"%s\" (not an object), skipping", key); + // continue; + // } + JSONObject scene = view_as(data.Get(key)); + // Parses scene data and inserts to scenes + loadScene(key, scene); + } + } + + delete data; + profiler.Stop(); + Log("Parsed map file for %s(%d) and found %d scenes in %.4f seconds", map, flags, g_MapData.scenes.Length, profiler.Time); + delete profiler; + return true; +} + +// Calls LoadMapData (read&parse (mapname).json) then select scenes +public bool RunMap(const char[] map, int flags) { + if(g_MapData.scenes == null || flags & view_as(FLAG_REFRESH)) { + if(!LoadMapData(map, flags)) { + return false; + } + } + Profiler profiler = new Profiler(); + + profiler.Start(); + selectScenes(flags); + profiler.Stop(); + + Log("Done processing in %.4f seconds", g_MapData.scenes.Length, profiler.Time); + return true; +} + +void loadScene(const char key[MAX_SCENE_NAME_LENGTH], JSONObject sceneData) { + SceneData scene; + scene.name = key; + scene.chance = sceneData.GetFloat("chance"); + if(scene.chance < 0.0 || scene.chance > 1.0) { + LogError("Scene \"%s\" has invalid chance (%f)", scene.name, scene.chance); + return; + } + // TODO: load "entities", merge with choice.entities + sceneData.GetString("group", scene.group, sizeof(scene.group)); + scene.variants = new ArrayList(sizeof(SceneVariantData)); + if(!sceneData.HasKey("variants")) { + ThrowError("Failed to load: Scene \"%s\" has missing \"variants\" array", scene.name); + return; + } + JSONArray entities; + if(sceneData.HasKey("entities")) { + entities = view_as(sceneData.Get("entities")); + } + + JSONArray variants = view_as(sceneData.Get("variants")); + for(int i = 0; i < variants.Length; i++) { + // Parses choice and loads to scene.choices + loadChoice(scene, view_as(variants.Get(i)), entities); + } + g_MapData.scenes.PushArray(scene); + g_MapData.scenesKv.SetArray(scene.name, scene, sizeof(scene)); +} + +void loadChoice(SceneData scene, JSONObject choiceData, JSONArray extraEntities) { + SceneVariantData choice; + choice.weight = 1; + if(choiceData.HasKey("weight")) + choice.weight = choiceData.GetInt("weight"); + choice.entities = new ArrayList(sizeof(VariantEntityData)); + choice.inputsList = new ArrayList(sizeof(VariantInputData)); + choice.forcedScenes = new ArrayList(ByteCountToCells(MAX_SCENE_NAME_LENGTH)); + // Load in any variant-based entities + if(choiceData.HasKey("entities")) { + JSONArray entities = view_as(choiceData.Get("entities")); + for(int i = 0; i < entities.Length; i++) { + // Parses entities and loads to choice.entities + loadChoiceEntity(choice.entities, view_as(entities.Get(i))); + } + delete entities; + } + // Load in any entities that the scene has + if(extraEntities != null) { + for(int i = 0; i < extraEntities.Length; i++) { + // Parses entities and loads to choice.entities + loadChoiceEntity(choice.entities, view_as(extraEntities.Get(i))); + } + delete extraEntities; + } + // Load all inputs + if(choiceData.HasKey("inputs")) { + JSONArray inputsList = view_as(choiceData.Get("inputs")); + for(int i = 0; i < inputsList.Length; i++) { + loadChoiceInput(choice.inputsList, view_as(inputsList.Get(i))); + } + delete inputsList; + } + if(choiceData.HasKey("force_scenes")) { + JSONArray scenes = view_as(choiceData.Get("force_scenes")); + char sceneId[32]; + for(int i = 0; i < scenes.Length; i++) { + scenes.GetString(i, sceneId, sizeof(sceneId)); + choice.forcedScenes.PushString(sceneId); + } + delete scenes; + } + scene.variants.PushArray(choice); +} + +void loadChoiceInput(ArrayList list, JSONObject inputData) { + VariantInputData input; + // Check classname -> targetname -> hammerid + if(!inputData.GetString("classname", input.name, sizeof(input.name))) { + if(inputData.GetString("targetname", input.name, sizeof(input.name))) { + input.type = Input_Targetname; + } else { + if(inputData.GetString("hammerid", input.name, sizeof(input.name))) { + input.type = Input_HammerId; + } else { + int id = inputData.GetInt("hammerid"); + if(id > 0) { + input.type = Input_HammerId; + IntToString(id, input.name, sizeof(input.name)); + } else { + LogError("Missing valid input specification (hammerid, classname, targetname)"); + return; + } + } + } + } + inputData.GetString("input", input.input, sizeof(input.input)); + list.PushArray(input); +} + +void loadLumpData(ArrayList list, JSONObject inputData) { + LumpEditData input; + // Check classname -> targetname -> hammerid + if(!inputData.GetString("classname", input.name, sizeof(input.name))) { + if(inputData.GetString("targetname", input.name, sizeof(input.name))) { + input.type = Input_Targetname; + } else { + if(inputData.GetString("hammerid", input.name, sizeof(input.name))) { + input.type = Input_HammerId; + } else { + int id = inputData.GetInt("hammerid"); + if(id > 0) { + input.type = Input_HammerId; + IntToString(id, input.name, sizeof(input.name)); + } else { + LogError("Missing valid input specification (hammerid, classname, targetname)"); + return; + } + } + } + } + inputData.GetString("action", input.action, sizeof(input.action)); + inputData.GetString("value", input.value, sizeof(input.value)); + list.PushArray(input); +} + +void loadChoiceEntity(ArrayList list, JSONObject entityData) { + VariantEntityData entity; + entityData.GetString("model", entity.model, sizeof(entity.model)); + if(!entityData.GetString("type", entity.type, sizeof(entity.type))) { + entity.type = "prop_dynamic"; + } else if(entity.type[0] == '_') { + LogError("Invalid custom entity type \"%s\"", entity.type); + return; + } + GetVector(entityData, "origin", entity.origin); + GetVector(entityData, "angles", entity.angles); + GetVector(entityData, "scale", entity.scale); + GetColor(entityData, "color", entity.color); + list.PushArray(entity); +} + +bool GetVector(JSONObject obj, const char[] key, float out[3]) { + if(!obj.HasKey(key)) return false; + JSONArray vecArray = view_as(obj.Get(key)); + if(vecArray != null) { + out[0] = vecArray.GetFloat(0); + out[1] = vecArray.GetFloat(1); + out[2] = vecArray.GetFloat(2); + } + return true; +} + +void GetColor(JSONObject obj, const char[] key, int out[4], int defaultColor[4] = { 255, 255, 255, 255 }) { + if(obj.HasKey(key)) { + JSONArray vecArray = view_as(obj.Get(key)); + out[0] = vecArray.GetInt(0); + out[1] = vecArray.GetInt(1); + out[2] = vecArray.GetInt(2); + if(vecArray.Length == 4) + out[3] = vecArray.GetInt(3); + else + out[3] = 255; + } else { + out = defaultColor; + } +} + +void selectScenes(int flags = 0) { + SceneData scene; + StringMap groups = new StringMap(); + ArrayList list; + // Select and spawn non-group scenes + // TODO: refactor to use .scenesKv + for(int i = 0; i < g_MapData.scenes.Length; i++) { + g_MapData.scenes.GetArray(i, scene); + // TODO: Exclusions + // Select scene if not in group, or add to list of groups + if(scene.group[0] == '\0') { + selectScene(scene, flags); + } else { + // Load it into group list + if(!groups.GetValue(scene.group, list)) { + list = new ArrayList(); + } + list.Push(i); + groups.SetValue(scene.group, list); + } + } + + // Iterate through groups and select a random scene: + StringMapSnapshot snapshot = groups.Snapshot(); + char key[MAX_SCENE_NAME_LENGTH]; + for(int i = 0; i < snapshot.Length; i++) { + snapshot.GetKey(i, key, sizeof(key)); + groups.GetValue(key, list); + // Select a random scene from the group: + int index = GetURandomInt() % list.Length; + index = list.Get(index); + g_MapData.scenes.GetArray(index, scene); + + Debug("Selected scene \"%s\" for group %s (%d members)", scene.name, key, list.Length); + selectScene(scene, flags); + delete list; + } + // Traverse active scenes, loading any other scene it requires (via .force_scenes) + ActiveSceneData aScene; + SceneVariantData choice; + ArrayList forcedScenes = new ArrayList(ByteCountToCells(MAX_SCENE_NAME_LENGTH)); + for(int i = 0; i < g_MapData.activeScenes.Length; i++) { + g_MapData.activeScenes.GetArray(i, aScene); + g_MapData.scenes.GetArray(i, scene); + scene.variants.GetArray(aScene.variantIndex, choice); + if(choice.forcedScenes != null) { + for(int j = 0; j < choice.forcedScenes.Length; j++) { + choice.forcedScenes.GetString(j, key, sizeof(key)); + forcedScenes.PushString(key); + } + } + } + // Iterate and activate any forced scenes + for(int i = 0; i < forcedScenes.Length; i++) { + forcedScenes.GetString(i, key, sizeof(key)); + // Check if scene was already loaded + bool isSceneAlreadyLoaded = false; + for(int j = 0; j < g_MapData.activeScenes.Length; i++) { + g_MapData.activeScenes.GetArray(j, aScene); + if(StrEqual(aScene.name, key)) { + isSceneAlreadyLoaded = true; + break; + } + } + if(isSceneAlreadyLoaded) continue; + g_MapData.scenesKv.GetArray(key, scene, sizeof(scene)); + selectScene(scene, flags | view_as(FLAG_FORCE_ACTIVE)); + } + + delete forcedScenes; + delete snapshot; + delete groups; +} + +void selectScene(SceneData scene, int flags) { + // Use the .chance field unless FLAG_ALL_SCENES or FLAG_FORCE_ACTIVE is set + if(~flags & view_as(FLAG_ALL_SCENES) && ~flags & view_as(FLAG_FORCE_ACTIVE) && GetURandomFloat() > scene.chance) { + return; + } + + if(scene.variants.Length == 0) { + LogError("Warn: No variants were found for scene \"%s\"", scene.name); + return; + } + + ArrayList choices = new ArrayList(); + SceneVariantData choice; + int index; + Debug("Scene %s has %d variants", scene.name, scene.variants.Length); + // Weighted random: Push N times dependent on weight + for(int i = 0; i < scene.variants.Length; i++) { + scene.variants.GetArray(i, choice); + if(flags & view_as(FLAG_ALL_VARIANTS)) { + spawnVariant(choice); + } else { + if(choice.weight <= 0) { + PrintToServer("Warn: Variant %d in scene %s has invalid weight", i, scene.name); + continue; + } + for(int c = 0; c < choice.weight; c++) { + choices.Push(i); + } + } + } + Debug("Total choices: %d", choices.Length); + if(flags & view_as(FLAG_ALL_VARIANTS)) { + delete choices; + } else if(choices.Length > 0) { + index = GetURandomInt() % choices.Length; + index = choices.Get(index); + delete choices; + Log("Spawned scene \"%s\" with variant #%d", scene.name, index); + scene.variants.GetArray(index, choice); + spawnVariant(choice); + } + ActiveSceneData aScene; + strcopy(aScene.name, sizeof(aScene.name), scene.name); + aScene.variantIndex = index; + g_MapData.activeScenes.PushArray(aScene); +} + +void spawnVariant(SceneVariantData choice) { + VariantEntityData entity; + for(int i = 0; i < choice.entities.Length; i++) { + choice.entities.GetArray(i, entity); + spawnEntity(entity); + } + + if(choice.inputsList.Length > 0) { + VariantInputData input; + for(int i = 0; i < choice.inputsList.Length; i++) { + choice.inputsList.GetArray(i, input); + input.Trigger(); + } + } +} + +void spawnEntity(VariantEntityData entity) { + if(StrEqual(entity.type, "env_fire")) { + Debug("spawning \"%s\" at (%.1f %.1f %.1f) rot (%.0f %.0f %.0f)", entity.type, entity.origin[0], entity.origin[1], entity.origin[2], entity.angles[0], entity.angles[1], entity.angles[2]); + CreateFire(entity.origin, 20.0, 100.0, 0.0); + } else if(StrEqual(entity.type, "env_physics_blocker") || StrEqual(entity.type, "env_player_blocker")) { + CreateEnvBlockerScaled(entity.type, entity.origin, entity.scale); + } else if(StrEqual(entity.type, "infodecal")) { + CreateDecal(entity.model, entity.origin); + } else if(StrContains(entity.type, "prop_") == 0) { + if(entity.model[0] == '\0') { + LogError("Missing model for entity with type \"%s\"", entity.type); + return; + } + PrecacheModel(entity.model); + int prop = CreateProp(entity.type, entity.model, entity.origin, entity.angles); + SetEntityRenderColor(prop, entity.color[0], entity.color[1], entity.color[2], entity.color[3]); + } else if(StrEqual(entity.type, "hammerid")) { + int targetId = StringToInt(entity.model); + if(targetId > 0) { + int ent = -1; + while((ent = FindEntityByClassname(ent, "*")) != INVALID_ENT_REFERENCE) { + int hammerId = GetEntProp(ent, Prop_Data, "m_iHammerID"); + if(hammerId == targetId) { + Debug("moved entity (hammerid=%d) to %.0f %.0f %.0f rot %.0f %.0f %.0f", targetId, entity.origin[0], entity.origin[1], entity.origin[2], entity.angles[0], entity.angles[1], entity.angles[2]); + TeleportEntity(ent, entity.origin, entity.angles, NULL_VECTOR); + return; + } + } + } + Debug("Warn: Could not find entity (hammerid=%d) (model=%s)", targetId, entity.model); + } else if(StrEqual(entity.type, "targetname")) { + int ent = -1; + char targetname[64]; + bool found = false; + while((ent = FindEntityByClassname(ent, "*")) != INVALID_ENT_REFERENCE) { + GetEntPropString(ent, Prop_Data, "m_iName", targetname, sizeof(targetname)); + if(StrEqual(entity.model, targetname)) { + Debug("moved entity (targetname=%s) to %.0f %.0f %.0f rot %.0f %.0f %.0f", entity.model, entity.origin[0], entity.origin[1], entity.origin[2], entity.angles[0], entity.angles[1], entity.angles[2]); + TeleportEntity(ent, entity.origin, entity.angles, NULL_VECTOR); + found = true; + } + } + if(!found) + Debug("Warn: Could not find entity (targetname=%s)", entity.model); + } else if(StrEqual(entity.type, "classname")) { + int ent = -1; + char classname[64]; + bool found; + while((ent = FindEntityByClassname(ent, classname)) != INVALID_ENT_REFERENCE) { + Debug("moved entity (classname=%s) to %.0f %.0f %.0f rot %.0f %.0f %.0f", entity.model, entity.origin[0], entity.origin[1], entity.origin[2], entity.angles[0], entity.angles[1], entity.angles[2]); + TeleportEntity(ent, entity.origin, entity.angles, NULL_VECTOR); + found = true; + } + if(!found) + Debug("Warn: Could not find entity (classname=%s)", entity.model); + } else { + LogError("Unknown entity type \"%s\"", entity.type); + } +} + +void Debug(const char[] format, any ...) { + #if defined DEBUG_SCENE_PARSE + char buffer[192]; + + VFormat(buffer, sizeof(buffer), format, 2); + + PrintToServer("[Randomizer::Debug] %s", buffer); + PrintToConsoleAll("[Randomizer::Debug] %s", buffer); + + #endif +} + +void Log(const char[] format, any ...) { + char buffer[192]; + + VFormat(buffer, sizeof(buffer), format, 2); + + PrintToServer("[Randomizer] %s", buffer); +} + +void Cleanup() { + if(g_MapData.scenes != null) { + SceneData scene; + for(int i = 0; i < g_MapData.scenes.Length; i++) { + g_MapData.scenes.GetArray(i, scene); + scene.Cleanup(); + } + delete g_MapData.scenes; + } + delete g_MapData.lumpEdits; + + DeleteCustomEnts(); + g_MapData.activeScenes.Clear(); } \ No newline at end of file