This commit is contained in:
Jackzie 2024-07-13 21:27:08 -05:00
parent fd2367f41f
commit 79d37bdd34
45 changed files with 5587 additions and 3877 deletions

View file

@ -36,7 +36,7 @@ int g_BeamSprite;
int g_HaloSprite;
int g_iLaserIndex;
#define MAX_FORBIDDEN_CLASSNAMES 9
#define MAX_FORBIDDEN_CLASSNAMES 10
static char FORBIDDEN_CLASSNAMES[MAX_FORBIDDEN_CLASSNAMES][] = {
// "env_physics_blocker",
// "env_player_blocker",
@ -49,7 +49,8 @@ static char FORBIDDEN_CLASSNAMES[MAX_FORBIDDEN_CLASSNAMES][] = {
"func_tracktrain",
// "infected",
"func_lod",
"prop_ragdoll"
"prop_ragdoll",
"move_rope"
};
#define MAX_FORBIDDEN_MODELS 2
@ -65,9 +66,11 @@ static char HIGHLIGHTED_CLASSNAMES[MAX_HIGHLIGHTED_CLASSNAMES][] = {
"func_brush"
}
ConVar g_cvarEnabled;
public void OnPluginStart()
{
g_cvarEnabled = CreateConVar("sm_grabent_allow", "1", "Is grabent allowed", FCVAR_NONE, true, 0.0, true, 1.0);
RegAdminCmd("sm_grabent_freeze", Cmd_ReleaseFreeze, ADMFLAG_CHEATS, "<0/1> - Toggle entity freeze/unfreeze on release.");
RegAdminCmd("sm_grab", Cmd_Grab, ADMFLAG_CHEATS, "Toggle Grab the entity in your crosshair.");
RegAdminCmd("+grabent", Cmd_Grab, ADMFLAG_CHEATS, "Grab the entity in your crosshair.");
@ -131,11 +134,13 @@ public Action Cmd_ReleaseFreeze(client, args)
//============================================================================
// GRAB ENTITY COMMAND //
//============================================================================
public Action Cmd_Grab(client, args) {
if (client < 1 || client > MaxClients || !IsClientInGame(client))
Action Cmd_Grab(int client, int args) {
if(!g_cvarEnabled.BoolValue) {
ReplyToCommand(client, "[SM] Grabent is disabled");
return Plugin_Handled;
if (g_pGrabbedEnt[client] > 0 && IsValidEntity(g_pGrabbedEnt[client])) {
} else if (client < 1 || client > MaxClients || !IsClientInGame(client)) {
return Plugin_Handled;
} else if (g_pGrabbedEnt[client] > 0 && IsValidEntity(g_pGrabbedEnt[client])) {
Cmd_Release(client, 0);
return Plugin_Handled;
}
@ -572,6 +577,7 @@ bool Filter_IgnoreForbidden(int entity, int mask, int data) {
}
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++) {
@ -591,5 +597,6 @@ bool CheckBlacklist(int entity) {
if(StrEqual(buffer, "l4d2_randomizer")) {
return false;
}
GetEntityClassname(entity, buffer, sizeof(buffer));
return true;
}

View file

@ -461,6 +461,10 @@ Action Command_SetClientModel(int client, int args) {
void SetCharacter(int target, int survivorIndex, L4DModelId modelIndex, bool keepModel) {
SetEntProp(target, Prop_Send, "m_survivorCharacter", survivorIndex);
if(!PrecacheModel(MODELS[view_as<int>(modelIndex)])) {
LogError("SetCharacter: INVALID MODEL: %s", MODELS[view_as<int>(modelIndex)]);
return;
}
SetEntityModel(target, MODELS[view_as<int>(modelIndex)]);
if (IsFakeClient(target)) {
char name[32];
@ -629,6 +633,9 @@ public void OnMapStart() {
PrecacheSound(PRECACHE_SOUNDS[i]);
}
#endif
for(int i = 0; i < 8; i++) {
PrecacheModel(MODELS[i]);
}
HookEntityOutput("info_changelevel", "OnStartTouch", EntityOutput_OnStartTouchSaferoom);
HookEntityOutput("trigger_changelevel", "OnStartTouch", EntityOutput_OnStartTouchSaferoom);

View file

@ -14,7 +14,8 @@
#include <multicolors>
#include <jutils>
#include <socket>
#include <SteamWorks>
#undef REQUIRE_PLUGIN
#tryinclude <SteamWorks>
#pragma newdecls required
@ -85,7 +86,7 @@ public void OnPluginStart() {
g_socket.SetOption(SocketSendBuffer, BUFFER_SIZE);
uptime = GetTime();
cvar_debug = CreateConVar("sm_adminpanel_debug", "1", "Turn on debug mode", FCVAR_DONTRECORD, true, 0.0, true, 1.0);
cvar_debug = CreateConVar("sm_adminpanel_debug", "0", "Turn on debug mode", FCVAR_DONTRECORD, true, 0.0, true, 1.0);
cvar_authToken = CreateConVar("sm_adminpanel_authtoken", "", "The token for authentication", FCVAR_PROTECTED);
cvar_authToken.AddChangeHook(OnCvarChanged);
@ -172,10 +173,7 @@ void OnSocketError(Socket socket, int errorType, int errorNumber, int any) {
}
void SendFullSync() {
PrintToServer("SendFullSync");
if(StartPayload(true)) {
PrintToServer("SendFullSync : Started");
AddGameRecord();
int stage = L4D2_GetCurrentFinaleStage();
if(stage != 0)
@ -254,15 +252,19 @@ void OnSocketReceive(Socket socket, const char[] receiveData, int dataSize, int
}
void ProcessCommand(int id, const char[] command, const char[] cmdNamespace = "") {
char output[128];
char output[1024];
if(!StartPayload(true)) return;
if(cmdNamespace[0] == '\0' || StrEqual(cmdNamespace, "default")) {
SplitString(command, " ", output, sizeof(output));
// If command has no spaces, we need to manually copy the command to the split part
if(SplitString(command, " ", output, sizeof(output)) == -1) {
strcopy(output, sizeof(output), command);
}
if(CommandExists(output)) {
ServerCommandEx(output, sizeof(output), "%s", command);
AddCommandResponseRecord(id, Result_Boolean, 1, output);
} else {
AddCommandResponseRecord(id, Result_Error, -1, "Command does not exist");
Format(output, sizeof(output), "Command \"%s\" does not exist", output);
AddCommandResponseRecord(id, Result_Error, -1, output);
}
} else if(StrEqual(cmdNamespace, "builtin")) {
CommandResultType type;

View file

@ -50,7 +50,9 @@ stock void EntFire(const char[] name, const char[] input) {
}
}
void CreateCoinEntity(float pos[3]) {
// int ent = CreateProp("prop_dynamic", "")
}
void SetupEntities(bool blockers = true, bool props = true, bool portals = true) {
#if defined DEBUG_BLOCKERS

View file

@ -125,6 +125,12 @@ methodmap GuessWhoGame < BaseGame {
}
}
property int TargetCoinCount {
public get() {
return 8;
}
}
public void Start() {
}
@ -301,6 +307,14 @@ methodmap GuessWhoGame < BaseGame {
SDKUnhook(client, SDKHook_OnTakeDamageAlive, OnTakeDamageAlive);
SDKUnhook(client, SDKHook_WeaponEquip, OnWeaponEquip);
}
public void PopulateCoins() {
float pos[3];
for(int i = 0; i < this.TargetCoinCount; i++) {
movePoints.GetRandomPoint(pos);
}
}
}
stock bool ArePlayersJoining() {

View file

@ -13,13 +13,18 @@ Action Timer_RecordPoints(Handle h, int i) {
vecLastLocation[i] = meta.pos;
}
}
Game.MapTime++;
PrintHintText(i, "Points: %d / %d", movePoints.Length, MAX_VALID_LOCATIONS);
return Plugin_Continue;
}
bool firstCheckDone = false;
Action Timer_WaitForPlayers(Handle h) {
if(!isEnabled) return Plugin_Stop;
if(!isEnabled) {
waitTimer = null;
return Plugin_Stop;
}
if(!ArePlayersJoining()) {
Game.Debug("No players pending, ready to go");
if(!firstCheckDone) {
@ -28,6 +33,7 @@ Action Timer_WaitForPlayers(Handle h) {
} else {
firstCheckDone = false;
InitGamemode();
waitTimer = null;
return Plugin_Stop;
}
}

View file

@ -1 +0,0 @@
native bool SpawnSchematic(const char name[32], const float pos[3], const float angles[3] = NULL_VECTOR);

View file

@ -1,7 +1,6 @@
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_RED[3] = { 255, 0, 0};
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 };
@ -27,26 +26,21 @@ char MODE_NAME[5][] = {
"Freelook"
}
enum editFlag {
enum {
Edit_None,
Edit_Copy = 1,
Edit_Preview = 2,
Edit_WallCreator = 4
Edit_WallCreator = 4,
Edit_Manager = 8
}
enum buildType {
Build_Solid,
Build_Physics,
Build_NonSolid,
// TODO: Build_Weapon (spawn as weapon?)
}
enum CompleteType {
Complete_WallSuccess,
Complete_WallError,
Complete_PropSpawned,
Complete_PropError,
Complete_EditSuccess
}
enum StackerDirection {
Stack_Off,
@ -88,6 +82,7 @@ enum struct EditorData {
int colorIndex;
int axis;
int snapAngle;
float rotateSpeed;
int moveSpeed;
float moveDistance;
int entity;
@ -97,11 +92,14 @@ enum struct EditorData {
editMode mode;
buildType buildType;
editFlag flags;
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)) {
if(this.entity != INVALID_ENT_REFERENCE && (this.flags & Edit_Preview) && IsValidEntity(this.entity)) {
RemoveEntity(this.entity);
}
this.stackerDirection = Stack_Off;
@ -111,12 +109,13 @@ enum struct EditorData {
this.size[0] = this.size[1] = this.size[2] = 5.0;
this.angles[0] = this.angles[1] = this.angles[2] = 0.0;
this.colorIndex = 0;
this.axis = 1;
this.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;
@ -138,6 +137,12 @@ enum struct EditorData {
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);
@ -147,9 +152,6 @@ enum struct EditorData {
void UpdateEntity() {
int alpha = this.color[3];
// Keep previews transparent
if(this.flags & Edit_Preview) {
alpha = 200;
}
SetEntityRenderColor(this.entity, this.color[0], this.color[1], this.color[2], alpha);
}
@ -178,6 +180,10 @@ enum struct EditorData {
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
@ -210,43 +216,43 @@ enum struct EditorData {
PrintToChat(this.client, "\x04[Editor]\x01 Mode: \x05%s\x01 (Press \x04RELOAD\x01 to change)", MODE_NAME[this.mode]);
}
void CycleStacker(float tick) {
if(tick - cmdThrottle[this.client] <= 0.10) return;
void CycleStacker() {
int newDirection = view_as<int>(this.stackerDirection) + 1;
if(newDirection == view_as<int>(Stack_Down)) newDirection = 0;
this.stackerDirection = view_as<StackerDirection>(newDirection);
PrintToChat(this.client, "\x04[Editor]\x01 Stacker: %s\x01", STACK_DIRECTION_NAME[this.stackerDirection]);
cmdThrottle[this.client] = tick;
}
void ToggleCollision(float tick) {
if(tick - cmdThrottle[this.client] <= 0.25) return;
void ToggleCollision() {
this.hasCollision = !this.hasCollision
PrintToChat(this.client, "\x04[Editor]\x01 Collision: %s", ON_OFF_STRING[view_as<int>(this.hasCollision)]);
cmdThrottle[this.client] = tick;
}
void ToggleCollisionRotate(float tick) {
if(tick - cmdThrottle[this.client] <= 0.20) return;
void ToggleCollisionRotate() {
this.hasCollisionRotate = !this.hasCollisionRotate
PrintToChat(this.client, "\x04[Editor]\x01 Rotate with Collision: %s", ON_OFF_STRING[view_as<int>(this.hasCollisionRotate)]);
cmdThrottle[this.client] = tick;
}
void CycleAxis(float tick) {
if(tick - cmdThrottle[this.client] <= 0.1) return;
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: \x05HEADING (Y)\x01");
} else if(this.axis == 1) {
this.axis = 2;
PrintToChat(this.client, "\x04[Editor]\x01 Rotate Axis: \x05PITCH (X)\x01");
PrintToChat(this.client, "\x04[Editor]\x01 Rotate Axis: \x05ROLL (Z)\x01");
} else {
this.axis = 0;
PrintToChat(this.client, "\x04[Editor]\x01 Rotate Axis: \x05ROLL (Z)\x01");
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;
}
cmdThrottle[this.client] = tick;
}
void CycleSnapAngle(float tick) {
@ -328,18 +334,38 @@ enum struct EditorData {
// Complete the edit, wall creation, or spawning
CompleteType Done(int& entity) {
CompleteType type;
if(this.flags & Edit_WallCreator) {
return this._FinishWall(entity) ? Complete_WallSuccess : Complete_WallError;
type = this._FinishWall(entity) ? Complete_WallSuccess : Complete_WallError;
} else if(this.flags & Edit_Preview) {
return this._FinishPreview(entity) ? Complete_PropSpawned : Complete_PropError;
type = this._FinishPreview(entity) ? Complete_PropSpawned : Complete_PropError;
} else {
// Is edit, do nothing, just reset
PrintHintText(this.client, "Edit Complete");
this.Reset();
entity = 0;
return Complete_EditSuccess;
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) {
@ -569,7 +595,7 @@ enum struct EditorData {
DispatchKeyValue(entity, "targetname", "prop_preview");
DispatchKeyValue(entity, "solid", "0");
DispatchKeyValue(entity, "rendercolor", "255 128 255");
DispatchKeyValue(entity, "renderamt", "200");
DispatchKeyValue(entity, "renderamt", "255");
DispatchKeyValue(entity, "rendermode", "1");
TeleportEntity(entity, this.origin, NULL_VECTOR, NULL_VECTOR);
if(!DispatchSpawn(entity)) {
@ -583,8 +609,11 @@ enum struct EditorData {
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)
/**
* 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);
@ -599,6 +628,22 @@ enum struct EditorData {
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() {
@ -611,9 +656,22 @@ enum struct EditorData {
}
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) {

View file

@ -43,7 +43,7 @@ int lastHatRequestTime[MAXPLAYERS+1];
HatInstance hatData[MAXPLAYERS+1];
StringMap g_HatPresets;
#define MAX_FORBIDDEN_CLASSNAMES 14
#define MAX_FORBIDDEN_CLASSNAMES 15
char FORBIDDEN_CLASSNAMES[MAX_FORBIDDEN_CLASSNAMES][] = {
"prop_door_rotating_checkpoint",
"env_physics_blocker",
@ -59,7 +59,8 @@ char FORBIDDEN_CLASSNAMES[MAX_FORBIDDEN_CLASSNAMES][] = {
// "infected",
"func_lod",
"func_door",
"prop_ragdoll"
"prop_ragdoll",
"move_rope"
};
#define MAX_FORBIDDEN_MODELS 2
@ -73,7 +74,7 @@ char FORBIDDEN_MODELS[MAX_FORBIDDEN_MODELS][] = {
// Classnames that should automatically trigger reverse infected
static char REVERSE_CLASSNAMES[MAX_REVERSE_CLASSNAMES][] = {
"infected",
"func_movelinear"
"func_movelinear",
};
Action Command_DoAHat(int client, int args) {

View file

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

View file

@ -25,6 +25,8 @@ enum SaveType {
Save_Schematic
}
int GLOW_MANAGER[3] = { 52, 174, 235 };
enum struct Schematic {
char name[64];
char creatorSteamid[32];
@ -134,6 +136,197 @@ public any Native_SpawnSchematic(Handle plugin, int numParams) {
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) {
PrintToServer("Selector.AddEntity: PRE CALLBACK");
// FIXME: crashes server, sourcemod bug
/*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");
//FIXME: crashes server, sourcemod bug
/*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;
@ -144,14 +337,19 @@ enum struct PlayerPropData {
int lastActiveTime;
char classnameOverride[64];
ChatPrompt chatPrompt;
ArrayList markedProps;
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.markedProps != null) delete this.markedProps;
if(this.Selector.IsActive()) this.Selector.Cancel();
this.chatPrompt = Prompt_None;
this.clearListBuffer = false;
this.lastCategoryIndex = 0;
@ -161,6 +359,19 @@ enum struct PlayerPropData {
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) {

View file

@ -8,6 +8,8 @@ public void OnAdminMenuReady(Handle topMenuHandle) {
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;
}
@ -24,6 +26,15 @@ void Category_Handler(TopMenu topmenu, TopMenuAction action, TopMenuObject topob
}
}
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");
@ -79,6 +90,14 @@ void AdminMenu_SaveLoad(TopMenu topmenu, TopMenuAction action, TopMenuObject obj
}
}
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];
@ -87,7 +106,7 @@ int SaveLoadMainMenuHandler(Menu menu, MenuAction action, int client, int param2
ShowSaves(client, type);
} else if (action == MenuAction_Cancel) {
if(param2 == MenuCancel_ExitBack) {
Spawn_ShowSaveLoadMainMenu(client);
DisplayTopMenuCategory(g_topMenu, g_propSpawnerCategory, client);
}
} else if (action == MenuAction_End)
delete menu;
@ -127,6 +146,7 @@ int SaveLoadSceneHandler(Menu menu, MenuAction action, int client, int param2) {
return 0;
}
int SaveLoadSchematicHandler(Menu menu, MenuAction action, int client, int param2) {
if (action == MenuAction_Select) {
char saveName[64];
@ -189,40 +209,160 @@ int SaveLoadConfirmHandler(Menu menu, MenuAction action, int client, int param2)
delete menu;
return 0;
}
int DeleteHandler(Menu menu, MenuAction action, int client, int param2) {
int ManagerHandler(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);
if(index == -1) {
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(index == -2) {
} 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(index == -3) {
if(g_PropData[client].markedProps != null) {
EndDeleteTool(client, false);
} 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].markedProps = new ArrayList();
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 ref = g_spawnedItems.Get(index);
// TODO: add delete confirm
int index = g_spawnedItems.FindValue(ref);
if(IsValidEntity(ref)) {
RemoveEntity(ref);
}
g_spawnedItems.Erase(index);
if(index > 0) {
if(index > -1) {
g_spawnedItems.Erase(index);
index--;
}
} else { index = 0; }
ShowDeleteList(client, index);
}
} else if (action == MenuAction_Cancel) {
@ -317,16 +457,18 @@ int EditHandler(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);
int ref = g_spawnedItems.Get(index);
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.");
g_spawnedItems.Erase(index);
index--;
if(index > -1) {
g_spawnedItems.Erase(index);
index--;
} else { index = 0; }
}
ShowEditList(client, index);
} else if (action == MenuAction_Cancel) {

View file

@ -3,11 +3,11 @@
/////////////
void ShowSpawnRoot(int client) {
Menu menu = new Menu(Spawn_RootHandler);
menu.SetTitle("Choose list:");
menu.AddItem("f", "Favorites (WIP)");
menu.AddItem("r", "Recents");
menu.AddItem("s", "Search");
menu.AddItem("n", "Prop List");
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);
@ -17,6 +17,7 @@ void Spawn_ShowRecents(int client) {
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");
@ -26,6 +27,11 @@ void Spawn_ShowSearch(int client) {
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");
@ -38,7 +44,7 @@ void ShowDeleteList(int client, int index = -3) {
for(int i = 0; i < g_spawnedItems.Length; i++) {
int ref = GetSpawnedItem(i);
if(ref == -1) continue;
IntToString(i, info, sizeof(info));
Format(info, sizeof(info), "0|%d", ref);
GetEntPropString(ref, Prop_Data, "m_ModelName", buffer, sizeof(buffer));
index = FindCharInString(buffer, '/', true);
if(index != -1)
@ -52,6 +58,11 @@ void ShowDeleteList(int client, int index = -3) {
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");
@ -60,7 +71,7 @@ void ShowEditList(int client, int index = 0) {
for(int i = 0; i < g_spawnedItems.Length; i++) {
int ref = GetSpawnedItem(i);
if(ref == -1) continue;
IntToString(i, info, sizeof(info));
Format(info, sizeof(info), "%d", ref);
GetEntPropString(ref, Prop_Data, "m_ModelName", buffer, sizeof(buffer));
index = FindCharInString(buffer, '/', true);
if(index != -1)
@ -104,6 +115,8 @@ void _showItemMenu(int client, ArrayList items, const char[] title = "", bool cl
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
@ -194,6 +207,81 @@ void Spawn_ShowSaveLoadMainMenu(int client) {
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;

View file

@ -378,9 +378,7 @@ ArrayList SearchItems(const char[] query) {
SearchData data;
for(int i = 0; i < results.Length; i++) {
results.GetArray(i, data);
PrintToConsoleAll("%d | data=\"%s\"", i, data.model);
item.FromSearchData(data);
PrintToConsoleAll("%d | item=\"%s\"", i, item.model);
items.PushArray(item);
}
delete results;
@ -446,26 +444,28 @@ bool RemoveSpawnedProp(int ref) {
return false;
}
void EndDeleteTool(int client, bool deleteEntities = false) {
if(g_PropData[client].markedProps != null) {
int count;
for(int i = 0; i < g_PropData[client].markedProps.Length; i++) {
int ref = g_PropData[client].markedProps.Get(i);
if(IsValidEntity(ref)) {
count++;
if(deleteEntities) {
RemoveSpawnedProp(ref);
RemoveEntity(ref);
}
else L4D2_RemoveEntityGlow(EntRefToEntIndex(ref));
}
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 g_PropData[client].markedProps;
if(deleteEntities)
PrintToChat(client, "\x04[Editor]\x01 \x05%d\x01 entities deleted", count);
else
PrintToChat(client, "\x04[Editor]\x01 Delete tool cancelled");
}
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) {

View file

@ -0,0 +1,155 @@
#if defined _editor_included_
#endinput
#endif
#define _editor_included_
public SharedPlugin __pl_editor_ = {
name = "editor",
file = "hats.smx",
#if defined REQUIRE_PLUGIN
required = 1,
#else
required = 0,
#endif
};
#if !defined REQUIRE_PLUGIN
public void __pl_editor__SetNTVOptional()
{
MarkNativeAsOptional("SpawnSchematic");
MarkNativeAsOptional("StartEdit");
MarkNativeAsOptional("StartSpawner");
MarkNativeAsOptional("CancelEdit");
MarkNativeAsOptional("IsEditorActive");
MarkNativeAsOptional("StartSelector");
MarkNativeAsOptional("CancelSelector");
MarkNativeAsOptional("IsSelectorActive");
MarkNativeAsOptional("Selector.Count.get");
MarkNativeAsOptional("Selector.Active.get");
MarkNativeAsOptional("Selector.Start");
MarkNativeAsOptional("Selector.SetOnEnd");
MarkNativeAsOptional("Selector.SetOnPreSelect");
MarkNativeAsOptional("Selector.SetOnPostSelect");
MarkNativeAsOptional("Selector.SetOnUnselect");
MarkNativeAsOptional("Selector.AddEntity");
MarkNativeAsOptional("Selector.RemoveEntity");
MarkNativeAsOptional("Selector.Cancel");
MarkNativeAsOptional("Selector.End");
}
#endif
native bool SpawnSchematic(const char name[32], const float pos[3], const float angles[3] = NULL_VECTOR);
/** Called when edit is done or cancelled
* @param client - client doing the edit
* @param entity - The entity edited
* @param result - Result of the edit, or cancelled
* @return boolean - only for StartSpawner, true to continue, false to end spawning
*/
typeset EditorDoneCallback {
function void (int client, int entity, CompleteType result);
function bool (int client, int entity, CompleteType result);
}
/** Called when an item is to be selected.
* @return boolean - TRUE to allow item to be selected, FALSE to reject
*/
typedef SelectPreAddCallback = function bool (int client, int entity);
/** Called when an item has been selected */
typedef SelectPostAddCallback = function void (int client, int entity);
/** Called when an item is to be unselected. */
typedef SelectRemoveCallback = function void (int client, int entity);
/** Called when a user is done selecting items
* @param client - client doing the selection
* @param entities - if null, selection was cancelled. if not null, contains list of entity references, must be deleted.
*/
typedef SelectDoneCallback = function void (int client, ArrayList entities);
/** Starts editing an entity
* @param client - The client that is editing
* @param entity - The entity to edit
* @param doneCallback - Called when edit is done
*/
native void StartEdit(int client, int entity, EditorDoneCallback doneCallback);
/** Let client pick prop(s) to spawn
* @param client - The client that is editing
* @param entity - The entity to edit
* @param doneCallback - Called when edit is done
*/
native void StartSpawner(int client, EditorDoneCallback doneCallback);
native void CancelEdit(int client);
// Includes non-plugin started edits
native bool IsEditorActive(int client);
/** Starts a selection, where the client can click on entities to select or deselect them.
* @param client - the client that can select
* @param callback - called when user is done seleting or cancelled
* @param highlightColor - the color to highlight selected items, default solid green
* @param maxEntities - the max number of selections, 0 for infinite
*/
native void StartSelector(int client, SelectDoneCallback callback, int highlightColor[3] = { 0, 255, 0 }, int maxEntities = 0);
methodmap EntitySelector {
public EntitySelector(int client) {
return view_as<EntitySelector>(client);
}
public static EntitySelector FromClient(int client) {
return view_as<EntitySelector>(client);
}
/** Starts a new selector for client
* @param highlightColor - the color to highlight selected items, default solid green
* @param flags - not used.
* @param maxEntities - the max number of selections, 0 for infinite
*/
public native EntitySelector Start(int highlightColor[3], int flags = 0, int maxEntities = 0);
property int Count {
/** Returns the number of entities in selector. Returns -1 if not active */
public native get();
}
property bool Active {
public native get();
}
/** Sets the callback for when the selector is ended (or cancelled) */
public native void SetOnEnd(SelectDoneCallback callback);
/** Sets the callback for when an item is to be added to the selector. */
public native void SetOnPreSelect(SelectPreAddCallback callback);
/** Sets the callback for when an item has been added to the selector. */
public native void SetOnPostSelect(SelectPostAddCallback callback);
/** Sets the callback for when an item is removed from selector. */
public native void SetOnUnselect(SelectRemoveCallback callback);
/** Adds an entity to selection. Does not call SelectAddCallback */
public native void AddEntity(int entity);
/** Removes an entity from selection. Does not call SelectAddCallback */
public native void RemoveEntity(int entity);
public native void Cancel();
public native void End();
}
native void CancelSelector(int client);
native bool IsSelectorActive(int client);
enum CompleteType {
Complete_WallSuccess,
Complete_WallError,
Complete_PropSpawned,
Complete_PropError,
Complete_EditSuccess
}

View file

@ -2,21 +2,50 @@
#endinput
#endif
#define _overlay_included
#include <ripext>
native bool SendTempUI(int client, const char[] id, int lifetime, JSONObject element);
public SharedPlugin __pl_overlay = {
name = "overlay",
file = "overlay.smx",
#if defined REQUIRE_PLUGIN
required = 1,
#else
required = 0,
#endif
};
native bool ShowUI(int client, const char[] elemNamespace, const char[] elemId, JSONObject variables);
#define ACTION_ARG_LENGTH 128 // The length each arg (separated by space) can be
native bool HideUI(int client, const char[] elemNamespace, const char[] elemId);
// typedef ActionFallbackHandlerCallback = function void (const char[] actionName, const char[][] args, int numArgs);
// typedef ActionHandlerCallback = function void (const char[][] args, int numArgs);
typedef ActionFallbackHandlerCallback = function void (const char[] actionName, UIActionEvent event, int client);
typedef ActionHandlerCallback = function void (UIActionEvent event, int client);
native bool PlayAudio(int client, const char[] url);
native bool IsOverlayConnected();
forward void OnUIAction(const char[] elemNamespace, const char[] elemId, const char[] action);
// myplugin:action_name
// Handles any action for actionNamespace and actionName
native void RegisterActionHandler(const char[] actionNamespace, const char[] actionName, ActionFallbackHandlerCallback cb);
// Handles all actions for namespace that were not caught by RegisterActionHandler
native void RegisterActionAnyHandler(const char[] actionNamespace, ActionHandlerCallback cb);
typedef UIActionCallback = function void (const char[][] args, int numArgs);
methodmap UIActionEvent {
public UIActionEvent(ArrayList list) {
return view_as<UIActionEvent>(list);
}
public void GetArg(int argNum, char[] output, int maxlen) {
view_as<ArrayList>(this).GetString(argNum, output, maxlen);
}
public void _Delete() {
delete view_as<ArrayList>(this);
}
property int Args {
public get() { return view_as<ArrayList>(this).Length; }
}
}
methodmap UIElement < JSONObject {
public UIElement(const char[] elemNamespace, const char[] elemId) {
@ -24,6 +53,7 @@ methodmap UIElement < JSONObject {
obj.SetString("namespace", elemNamespace);
obj.SetString("elem_id", elemId);
obj.SetBool("visibility", false);
obj.Set("steamids", new JSONArray());
obj.Set("variables", new JSONObject());
return view_as<UIElement>(obj);
@ -35,16 +65,6 @@ methodmap UIElement < JSONObject {
}
public set(bool value) {
view_as<JSONObject>(this).SetBool("visibility", value);
this.Send();
}
}
/** Is the UI element globally sent to all connected players?
* Specify players with .AddClient() or clear with .ClearClients()
*/
property bool Global {
public get() {
return !view_as<JSONObject>(this).HasKey("steamids")
}
}
@ -68,35 +88,13 @@ methodmap UIElement < JSONObject {
view_as<JSONObject>(this).SetBool(id, value);
}
public void SetActionCallback(UIActionCallback callback) {}
public void AddClient(const char[] steamid) {
// if(!IsClientInGame(client) || steamidCache[client][0] == '\0') ThrowError("Client %d is not connected, ingame, or authorized");
JSONObject obj = view_as<JSONObject>(this);
JSONArray steamids = view_as<JSONArray>(obj.Get("steamids"));
if(steamids == null) {
steamids = new JSONArray();
obj.Set("steamids", steamids)
public native bool SendAll();
public native bool SendTo(int client);
public bool SendToMultiple(int[] clientIds, int numClients) {
for(int i = 0; i < numClients; i++) {
this.SendTo(clientIds[i]);
}
steamids.PushString(steamid);
}
public void ClearClients() {
view_as<JSONObject>(this).Remove("steamids");
}
public void Clear() {
view_as<JSONObject>(this).Clear();
}
public void Hide() {
this.Visibility = false;
}
public void Show() {
this.Visibility = true;
}
public native bool Send();
}
methodmap UIPosition < JSONObject {
@ -118,6 +116,25 @@ methodmap UIPosition < JSONObject {
}
}
methodmap UISize < JSONObject {
public UISize(int width, int height) {
JSONObject obj = new JSONObject();
obj.SetInt("width", width);
obj.SetInt("height", height);
return view_as<UISize>(obj);
}
property int Width {
public get() { return view_as<JSONObject>(this).GetInt("width"); }
public set(int value) { view_as<JSONObject>(this).SetInt("height", value); }
}
property int Height {
public get() { return view_as<JSONObject>(this).GetInt("height"); }
public set(int value) { view_as<JSONObject>(this).SetInt("height", value); }
}
}
methodmap UIColor < JSONObject {
/// Creates a new UIColor with RGB between 0-255, alpha is normalized 0.0-1.0
public UIColor(int r = 255, int g = 255, int b = 255) {
@ -168,11 +185,15 @@ methodmap TempUIElementDefaults < JSONObject {
}
property UIPosition Position {
public get() { return view_as<UIPosition>(view_as<JSONObject>(this).Get("position")); }
public set(UIPosition pos) { view_as<JSONObject>(this).Set("position", view_as<JSON>(pos)); }
// public set(UIPosition pos) { view_as<JSONObject>(this).Set("position", view_as<JSON>(pos)); }
}
property UIColor BackgroundColor {
public get() { return view_as<UIColor>(view_as<JSONObject>(this).Get("bgColor")); }
public set(UIColor color) { view_as<JSONObject>(this).Set("bgColor", view_as<JSON>(color)); }
// public set(UIColor color) { view_as<JSONObject>(this).Set("bgColor", view_as<JSON>(color)); }
}
property UISize Size {
public get() { return view_as<UISize>(view_as<JSONObject>(this).Get("size")); }
// public set(UISize size) { view_as<JSONObject>(this).Set("size", view_as<JSON>(size)); }
}
/// Returns or sets opacity, -1 is not set
property int Opacity {
@ -203,6 +224,7 @@ enum UIType {
Element_Unknown = -1,
Element_Text,
Element_List,
Element_Audio
}
enum UIFlags {
Element_None
@ -287,27 +309,29 @@ methodmap TempUI {
public TempUI(const char[] elemId, const char[] type, int lifetime = 0) {
JSONObject obj = new JSONObject();
obj.SetString("elem_id", elemId);
obj.Set("steamids", new JSONArray());
obj.SetInt("expires_seconds", 0);
TempUIElement element = new TempUIElement(type);
obj.Set("element", element);
return view_as<TempUI>(obj);
}
/// How long the temp UI lasts, 0 for never.
property int Duration {
public get() {
return view_as<JSONObject>(this).GetInt("expires_seconds");
}
public set(int value) {
view_as<JSONObject>(this).SetInt("expires_seconds", value);
}
}
property bool Visible {
public get() {
return view_as<JSONObject>(this).GetBool("visibility");
}
public set(bool value) {
view_as<JSONObject>(this).SetBool("visibility", value);
this.Send();
}
}
/** Is the UI element globally sent to all connected players?
* Specify players with .AddClient() or clear with .ClearClients()
*/
property bool Global {
public get() {
return !view_as<JSONObject>(this).HasKey("steamids")
}
}
@ -316,38 +340,127 @@ methodmap TempUI {
return view_as<TempUIElement>(view_as<JSONObject>(this).Get("element"));
}
public set(TempUIElement newElement) {
// Delete old element
JSON elem = view_as<JSONObject>(this).Get("element");
if(elem != null) delete elem;
view_as<JSONObject>(this).Set("element", view_as<JSON>(newElement));
}
}
public void SetActionCallback(UIActionCallback callback) {}
public void AddClient(const char[] steamid) {
// if(!IsClientInGame(client) || steamidCache[client][0] == '\0') ThrowError("Client %d is not connected, ingame, or authorized");
JSONObject obj = view_as<JSONObject>(this);
JSONArray steamids = view_as<JSONArray>(obj.Get("steamids"));
if(steamids == null) {
steamids = new JSONArray();
obj.Set("steamids", steamids)
}
steamids.PushString(steamid);
}
public void ClearClients() {
view_as<JSONObject>(this).Remove("steamids");
}
public void Clear() {
view_as<JSONObject>(this).Clear();
}
public void Hide() {
this.Visibility = false;
public void Hide() {
this.Visible = false;
}
public void Show() {
this.Visibility = true;
this.Visible = true;
}
public native bool Send();
public native bool SendAll();
public native bool SendTo(int client);
public bool SendToMultiple(int[] clientIds, int numClients) {
for(int i = 0; i < numClients; i++) {
this.SendTo(clientIds[i]);
}
}
}
}
enum AudioState {
// Audio stopped, reset to startTime
Audio_Stopped,
// Pauses audio at current time
Audio_Paused,
Audio_Play
}
methodmap ClientList < JSONArray {
public ClientList() {
return view_as<ClientList>(new JSONArray());
}
property int Length {
public get() { return view_as<JSONArray>(this).Length; }
}
public native void AddClient(int client);
public native bool HasClient(int client);
public void Clear() {
view_as<JSONArray>(this).Clear();
}
}
methodmap AudioResource < JSONObject {
public AudioResource(const char[] url, float volume = 0.5) {
JSONObject obj = new JSONObject();
obj.SetString("source", url);
obj.SetFloat("volume", volume);
obj.SetInt("state", 0);
obj.Set("steamids", new JSONArray());
obj.SetBool("repeat", false)
return view_as<AudioResource>(obj);
}
property AudioState State {
public get() {
return view_as<AudioState>(view_as<JSONObject>(this).GetInt("state"));
}
public set(AudioState state) {
view_as<JSONObject>(this).SetInt("state", view_as<int>(state));
}
}
property float Volume {
public get() {
return view_as<JSONObject>(this).GetFloat("volume");
}
public set(float volume) {
view_as<JSONObject>(this).SetFloat("volume", volume);
}
}
property bool Repeat {
public get() {
return view_as<JSONObject>(this).GetBool("repeat");
}
public set(bool repeat) {
view_as<JSONObject>(this).SetBool("repeat", repeat);
}
}
property ClientList Clients {
public get() {
return view_as<ClientList>(view_as<JSONObject>(this).Get("steamids"));
}
}
/// Plays or resumes playing
public native void Play();
/// Stops playing audio, clients will reset to beginning
public native void Stop();
/// Pauses audio, resuming to current play duration
public native void Pause();
public void Clear() {
view_as<JSONObject>(this).Clear();
}
}
#if !defined REQUIRE_PLUGIN
public void __pl_overlay_SetNTVOptional() {
MarkNativeAsOptional("IsOverlayConnected");
MarkNativeAsOptional("RegisterActionAnyHandler");
MarkNativeAsOptional("RegisterActionHandler");
MarkNativeAsOptional("UIElement.SendAll");
MarkNativeAsOptional("UIElement.SendTo");
MarkNativeAsOptional("TempUI.SendAll");
MarkNativeAsOptional("TempUI.SendTo");
MarkNativeAsOptional("AudioResource.Play");
MarkNativeAsOptional("AudioResource.Stop");
MarkNativeAsOptional("AudioResource.Pause");
}
#endif

View file

@ -0,0 +1,135 @@
/// MENUS
public void OpenMainMenu(int client) {
Menu menu = new Menu(BuilderHandler_MainMenu);
menu.SetTitle("Randomizer Builder");
if(g_builder.mapData == null) {
menu.AddItem("load", "Load Map Data");
menu.AddItem("new", "New Map Data");
} else {
menu.AddItem("save", "Save Map Data");
menu.AddItem("selector", "Start Selector");
menu.AddItem("spawner", "Start Spawner");
menu.AddItem("cursor", "Add Entity At Cursor");
menu.AddItem("scenes", "Scenes");
}
menu.Display(client, 0);
}
void OpenScenesMenu(int client) {
Menu menu = new Menu(BuilderHandler_ScenesMenu);
menu.SetTitle("Select a scene");
char id[64], display[32];
JSONObjectKeys iterator = g_builder.mapData.Keys();
while(iterator.ReadKey(id, sizeof(id))) {
if(StrEqual(id, g_builder.selectedSceneId)) {
Format(display, sizeof(display), "%s (selected)", id);
} else {
Format(display, sizeof(display), "%s", id);
}
menu.AddItem(id, display);
}
menu.Display(client, 0);
}
void OpenVariantsMenu(int client) {
Menu menu = new Menu(BuilderHandler_VariantsMenu);
menu.SetTitle("%s > Variants", g_builder.selectedSceneId);
char id[8], display[32];
menu.AddItem("new", "New");
menu.AddItem("-1", "None (Shared Scene)");
JSONArray variants = view_as<JSONArray>(g_builder.selectedSceneData.Get("variants"));
JSONObject varObj;
JSONArray entities;
for(int i = 0; i < variants.Length; i++) {
varObj = view_as<JSONObject>(variants.Get(i));
entities = view_as<JSONArray>(varObj.Get("entities"));
if(i == g_builder.selectedVariantIndex) {
Format(display, sizeof(display), "%d entities (selected)", entities.Length);
} else {
Format(display, sizeof(display), "%d entities", entities.Length);
}
IntToString(i, id, sizeof(id));
menu.AddItem(id, display);
}
menu.Display(client, 0);
}
/// HANDLERS
int BuilderHandler_MainMenu(Menu menu, MenuAction action, int client, int param2) {
if (action == MenuAction_Select) {
char info[32];
menu.GetItem(param2, info, sizeof(info));
if(StrEqual(info, "new")) {
JSONObject temp = LoadMapJson(currentMap);
GetCmdArg(2, info, sizeof(info));
if(temp != null) {
Menu nMenu = new Menu(BuilderHandler_MainMenu);
nMenu.SetTitle("Existing map data exists");
nMenu.AddItem("new_confirm", "Overwrite");
nMenu.Display(client, 0);
delete temp;
return 0;
} else {
FakeClientCommand(client, "sm_rbuild new");
}
} else if(StrEqual(info, "new_confirm")) {
FakeClientCommand(client, "sm_rbuild new confirm");
} else if(StrEqual(info, "scenes")) {
OpenScenesMenu(client);
return 0;
} else {
FakeClientCommand(client, "sm_rbuild %s", info);
} /*else if(StrEqual(info, "cursor")) {
Menu menu = new Menu(BuilderHandler_)
}*/
OpenMainMenu(client);
} else if(action == MenuAction_Cancel) {
if(param2 == MenuCancel_ExitBack) {
}
} else if (action == MenuAction_End) {
delete menu;
}
return 0;
}
int BuilderHandler_ScenesMenu(Menu menu, MenuAction action, int client, int param2) {
if (action == MenuAction_Select) {
char info[64];
menu.GetItem(param2, info, sizeof(info));
if(StrEqual(info, "new")) {
FakeClientCommand(client, "sm_rbuild scenes new");
OpenScenesMenu(client);
} else {
FakeClientCommand(client, "sm_rbuild scenes select %s", info);
OpenVariantsMenu(client);
}
} else if(action == MenuAction_Cancel) {
if(param2 == MenuCancel_ExitBack) {
}
} else if (action == MenuAction_End) {
delete menu;
}
return 0;
}
int BuilderHandler_VariantsMenu(Menu menu, MenuAction action, int client, int param2) {
if (action == MenuAction_Select) {
char info[64];
menu.GetItem(param2, info, sizeof(info));
if(StrEqual(info, "new")) {
FakeClientCommand(client, "sm_rbuild scenes variants new");
} else {
FakeClientCommand(client, "sm_rbuild scenes variants select %s", info);
}
OpenVariantsMenu(client);
} else if(action == MenuAction_Cancel) {
if(param2 == MenuCancel_ExitBack) {
}
} else if (action == MenuAction_End) {
delete menu;
}
return 0;
}

View file

@ -249,8 +249,8 @@ void Event_PlayerDisconnect(Event event, const char[] name, bool dontBroadcast)
CPrintChatToAdmins("{olive}%N{default} has been banned for %d minutes (marked as troll). If this was a mistake, you can discard their ban from the admin panel at {yellow}https://admin.jackz.me", client, hBanTime.IntValue);
else
CPrintChatToAdmins("{olive}%N{default} has been permanently banned (marked as troll). If this was a mistake, you can discard their ban from the admin panel at {yellow}https://admin.jackz.me", client);
pData[client].isTroll = false;
}
pData[client].isTroll = false;
if(!IsFakeClient(client)) {
float minutesSinceiLastFFTime = GetLastFFMinutes(client);

View file

@ -90,7 +90,7 @@ ConVar cvEPICommonCountScale, cvEPICommonCountScaleMax;
ConVar g_ffFactorCvar, hExtraTankThreshold;
ConVar cvZCommonLimit; int zCommonLimitPrevValue;
ConVar cvZCommonLimit; int commonLimitBase; bool isSettingLimit;
int g_extraKitsAmount, g_extraKitsStart, g_saferoomDoorEnt, g_prevPlayerCount;
bool g_forcedSurvivorCount, g_extraKitsSpawnedFinale;
@ -306,25 +306,29 @@ public void OnPluginStart() {
hExtraItemBasePercentage = CreateConVar("epi_item_chance", "0.034", "The base chance (multiplied by player count) of an extra item being spawned.", FCVAR_NONE, true, 0.0, true, 1.0);
hExtraSpawnBasePercentage = CreateConVar("epi_spawn_chance", "0.01", "The base chance (multiplied by player count) of an extra item spawner being created.", FCVAR_NONE, true, 0.0, true, 1.0);
hAddExtraKits = CreateConVar("epi_kitmode", "0", "Decides how extra kits should be added.\n0 -> Overwrites previous extra kits\n1 -> Adds onto previous extra kits", FCVAR_NONE, true, 0.0, true, 1.0);
hUpdateMinPlayers = CreateConVar("epi_updateminplayers", "1", "Should the plugin update abm\'s cvar min_players convar to the player count?\n 0 -> NO\n1 -> YES", FCVAR_NONE, true, 0.0, true, 1.0);
hMinPlayersSaferoomDoor = CreateConVar("epi_doorunlock_percent", "0.75", "The percent of players that need to be loaded in before saferoom door is opened.\n 0 to disable", FCVAR_NONE, true, 0.0, true, 1.0);
hSaferoomDoorWaitSeconds = CreateConVar("epi_doorunlock_wait", "25", "How many seconds after to unlock saferoom door. 0 to disable", FCVAR_NONE, true, 0.0);
hSaferoomDoorAutoOpen = CreateConVar("epi_doorunlock_open", "0", "Controls when the door automatically opens after unlocked. Add bits together.\n0 = Never, 1 = When timer expires, 2 = When all players loaded in", FCVAR_NONE, true, 0.0);
hEPIHudState = CreateConVar("epi_hudstate", "1", "Controls when the hud displays.\n0 -> OFF, 1 = When 5+ players, 2 = ALWAYS", FCVAR_NONE, true, 0.0, true, 3.0);
hExtraFinaleTank = CreateConVar("epi_extra_tanks", "3", "Add bits together. 0 = Normal tank spawning, 1 = 50% tank split on non-finale (half health), 2 = Tank split (full health) on finale ", FCVAR_NONE, true, 0.0, true, 3.0);
hExtraTankThreshold = CreateConVar("epi_extra_tanks_min_players", "6", "The minimum number of players for extra tanks to spawn. When disabled, normal 5+ tank health applies", FCVAR_NONE, true, 0.0);
hSplitTankChance = CreateConVar("epi_splittank_chance", "0.65", "The % chance of a split tank occurring in non-finales", FCVAR_NONE, true, 0.0, true, 1.0);
cvDropDisconnectTime = CreateConVar("epi_disconnect_time", "120.0", "The amount of seconds after a player has actually disconnected, where their character slot will be void. 0 to disable", FCVAR_NONE, true, 0.0);
cvFFDecreaseRate = CreateConVar("epi_ff_decrease_rate", "0.3", "The friendly fire factor is subtracted from the formula (playerCount-4) * this rate. Effectively reduces ff penalty when more players. 0.0 to subtract none", FCVAR_NONE, true, 0.0);
cvEPIHudFlags = CreateConVar("epi_hud_flags", "3", "Add together.\n1 = Scrolling hud, 2 = Show ping", FCVAR_NONE, true, 0.0);
cvEPISpecialSpawning = CreateConVar("epi_sp_spawning", "2", "Determines what specials are spawned. Add bits together.\n1 = Normal specials\n2 = Witches\n4 = Tanks", FCVAR_NONE, true, 0.0);
cvEPITankHealth = CreateConVar("epi_tank_chunkhp", "2500", "The amount of health added to tank, for each extra player", FCVAR_NONE, true, 0.0);
cvEPIGamemodes = CreateConVar("epi_gamemodes", "coop,realism,versus", "Gamemodes where plugin is active. Comma-separated", FCVAR_NONE);
cvEPIEnabledMode = CreateConVar("epi_enabled", "1", "Is EPI enabled?\n0=OFF\n1=Auto (Official Maps Only)(5+)\n2=Auto (Any map) (5+)\n3=Forced on", FCVAR_NONE, true, 0.0, true, 3.0);
cvEPICommonCountScale = CreateConVar("epi_commons_scale_multiplier", "0", "This value is multiplied by the number of extra players playing. It's then added to z_common_limit. 5 players with value 5 would be z_common_limit + ", FCVAR_NONE, true, 0.0);
cvEPICommonCountScaleMax = CreateConVar("epi_commons_scale_max", "60", "The maximum amount that z_common_limit can be scaled to.", FCVAR_NONE, true, 0.0);
hAddExtraKits = CreateConVar("epi_kitmode", "0", "Decides how extra kits should be added.\n0 -> Overwrites previous extra kits\n1 -> Adds onto previous extra kits", FCVAR_NONE, true, 0.0, true, 1.0);
hUpdateMinPlayers = CreateConVar("epi_updateminplayers", "1", "Should the plugin update abm\'s cvar min_players convar to the player count?\n 0 -> NO\n1 -> YES", FCVAR_NONE, true, 0.0, true, 1.0);
hMinPlayersSaferoomDoor = CreateConVar("epi_doorunlock_percent", "0.75", "The percent of players that need to be loaded in before saferoom door is opened.\n 0 to disable", FCVAR_NONE, true, 0.0, true, 1.0);
hSaferoomDoorWaitSeconds = CreateConVar("epi_doorunlock_wait", "25", "How many seconds after to unlock saferoom door. 0 to disable", FCVAR_NONE, true, 0.0);
hSaferoomDoorAutoOpen = CreateConVar("epi_doorunlock_open", "0", "Controls when the door automatically opens after unlocked. Add bits together.\n0 = Never, 1 = When timer expires, 2 = When all players loaded in", FCVAR_NONE, true, 0.0);
hEPIHudState = CreateConVar("epi_hudstate", "1", "Controls when the hud displays.\n0 -> OFF, 1 = When 5+ players, 2 = ALWAYS", FCVAR_NONE, true, 0.0, true, 3.0);
hExtraFinaleTank = CreateConVar("epi_extra_tanks", "3", "Add bits together. 0 = Normal tank spawning, 1 = 50% tank split on non-finale (half health), 2 = Tank split (full health) on finale ", FCVAR_NONE, true, 0.0, true, 3.0);
hExtraTankThreshold = CreateConVar("epi_extra_tanks_min_players", "6", "The minimum number of players for extra tanks to spawn. When disabled, normal 5+ tank health applies", FCVAR_NONE, true, 0.0);
hSplitTankChance = CreateConVar("epi_splittank_chance", "0.65", "The % chance of a split tank occurring in non-finales", FCVAR_NONE, true, 0.0, true, 1.0);
cvDropDisconnectTime = CreateConVar("epi_disconnect_time", "120.0", "The amount of seconds after a player has actually disconnected, where their character slot will be void. 0 to disable", FCVAR_NONE, true, 0.0);
cvFFDecreaseRate = CreateConVar("epi_ff_decrease_rate", "0.3", "The friendly fire factor is subtracted from the formula (playerCount-4) * this rate. Effectively reduces ff penalty when more players. 0.0 to subtract none", FCVAR_NONE, true, 0.0);
cvEPIHudFlags = CreateConVar("epi_hud_flags", "3", "Add together.\n1 = Scrolling hud, 2 = Show ping", FCVAR_NONE, true, 0.0);
cvEPISpecialSpawning = CreateConVar("epi_sp_spawning", "2", "Determines what specials are spawned. Add bits together.\n1 = Normal specials\n2 = Witches\n4 = Tanks", FCVAR_NONE, true, 0.0);
cvEPITankHealth = CreateConVar("epi_tank_chunkhp", "2500", "The amount of health added to tank, for each extra player", FCVAR_NONE, true, 0.0);
cvEPIGamemodes = CreateConVar("epi_gamemodes", "coop,realism,versus", "Gamemodes where plugin is active. Comma-separated", FCVAR_NONE);
cvEPIEnabledMode = CreateConVar("epi_enabled", "1", "Is EPI enabled?\n0=OFF\n1=Auto (Official Maps Only)(5+)\n2=Auto (Any map) (5+)\n3=Forced on", FCVAR_NONE, true, 0.0, true, 3.0);
cvEPICommonCountScale = CreateConVar("epi_commons_scale_multiplier", "0", "This value is multiplied by the number of extra players playing. It's then added to z_common_limit. 5 players with value 5 would be z_common_limit + ", FCVAR_NONE, true, 0.0);
cvEPICommonCountScaleMax = CreateConVar("epi_commons_scale_max", "60", "The maximum amount that z_common_limit can be scaled to.", FCVAR_NONE, true, 0.0);
cvZCommonLimit = FindConVar("z_common_limit");
cvEPICommonCountScale.AddChangeHook(Cvar_CommonScaleChange);
cvEPICommonCountScaleMax.AddChangeHook(Cvar_CommonScaleChange);
cvZCommonLimit.AddChangeHook(Cvar_CommonScaleChange);
// TODO: hook flags, reset name index / ping mode
cvEPIHudFlags.AddChangeHook(Cvar_HudStateChange);
@ -428,6 +432,7 @@ public void OnPluginEnd() {
delete weaponMaxClipSizes;
delete g_ammoPacks;
L4D2_ExecVScriptCode(HUD_SCRIPT_CLEAR);
_UnsetCommonLimit();
}
///////////////////////////////////////////////////////////////////////////////
@ -482,6 +487,19 @@ void Cvar_HudStateChange(ConVar convar, const char[] oldValue, const char[] newV
TryStartHud();
}
}
void Cvar_CommonScaleChange(ConVar convar, const char[] oldValue, const char[] newValue) {
if(convar == cvZCommonLimit) {
PrintToServer("z_common_limit changed [value=%d] [isSettingLimit=%b]", convar.IntValue, isSettingLimit);
// Ignore our own changes:
if(isSettingLimit) {
isSettingLimit = false;
return;
}
commonLimitBase = convar.IntValue;
}
_SetCommonLimit();
}
void TryStartHud() {
int threshold = 0;
// Default to 0 for state == 2 (force)
@ -572,6 +590,7 @@ Action Command_EpiVal(int client, int args) {
PrintToConsole(client, "restCount = %d", g_restCount);
PrintToConsole(client, "extraFinaleTankEnabled = %b", g_extraFinaleTankEnabled);
PrintToConsole(client, "g_areItemsPopulated = %b", g_areItemsPopulated);
PrintToConsole(client, "commonLimitBase = %d", commonLimitBase);
ReplyToCommand(client, "Values printed to console");
return Plugin_Handled;
}
@ -1343,7 +1362,7 @@ public void OnMapStart() {
if(L4D_IsMissionFinalMap()) {
// Disable tank split on hard rain finale
g_extraFinaleTankEnabled = true;
if(StrEqual(map, "c4m5_milltown_escape")) {
if(StrEqual(map, "c4m5_milltown_escape") || StrEqual(map, "c14m2_lighthouse")) {
g_extraFinaleTankEnabled = false;
}
}
@ -1454,6 +1473,7 @@ public void OnConfigsExecuted() {
if(hUpdateMinPlayers.BoolValue && hMinPlayers != null) {
hMinPlayers.IntValue = g_realSurvivorCount;
}
_SetCommonLimit();
}
public void OnMapEnd() {
@ -2102,29 +2122,42 @@ void UpdateSurvivorCount() {
} else if(wasActive) {
OnEPIInactive();
}
if(isActive)
SetFFFactor(g_epiEnabled);
if(isActive) {
SetFFFactor(g_epiEnabled);
_SetCommonLimit();
}
}
void OnEPIActive() {
zCommonLimitPrevValue = cvZCommonLimit.IntValue;
_SetCommonLimit();
}
void OnEPIInactive() {
_UnsetCommonLimit();
}
void _UnsetCommonLimit() {
if(commonLimitBase > 0) {
cvZCommonLimit.IntValue = commonLimitBase;
}
commonLimitBase = 0;
}
void _SetCommonLimit() {
if(!g_epiEnabled || commonLimitBase <= 0) return;
// TODO: lag check for common limit
if(cvEPICommonCountScale.IntValue > 0) {
int newLimit = zCommonLimitPrevValue + RoundFloat(cvEPICommonCountScale.FloatValue * float(g_realSurvivorCount));
if(cvEPICommonCountScale.IntValue > 0 && commonLimitBase > 0) {
int newLimit = commonLimitBase + RoundFloat(cvEPICommonCountScale.FloatValue * float(g_realSurvivorCount - 4));
PrintDebug(DEBUG_INFO, "Setting common scale: %d + (%f * %d) [max=%d] = %d", commonLimitBase, cvEPICommonCountScale.FloatValue, g_realSurvivorCount - 4, cvEPICommonCountScaleMax.IntValue, newLimit);
if(newLimit > 0) {
if(newLimit > cvEPICommonCountScaleMax.IntValue) {
newLimit = cvEPICommonCountScaleMax.IntValue;
}
isSettingLimit = true;
cvZCommonLimit.IntValue = newLimit;
}
cvZCommonLimit.IntValue = newLimit;
}
}
void OnEPIInactive() {
cvZCommonLimit.IntValue = zCommonLimitPrevValue;
}
void SetFFFactor(bool enabled) {
static float prevValue;
// Restore the previous value (we use the value for the calculations of new value)

View file

@ -213,6 +213,7 @@ public void Event_GamemodeChange(ConVar cvar, const char[] oldValue, const char[
HookEvent("player_bot_replace", Event_PlayerToBot);
HookEvent("player_ledge_grab", Event_LedgeGrab);
AddCommandListener(OnGoAwayFromKeyboard, "go_away_from_keyboard");
InitGamemode();
} else if(!lateLoaded) {
cvarStorage.Restore();
delete cvarStorage;
@ -462,7 +463,8 @@ Action Timer_SpawnBots(Handle h, int max) {
if(AddSurvivor()) {
count++;
return Plugin_Continue;
} else {
} else if(count < 0) {
// Fail if we couldnt make enough bots
PrintToChatAll("GUESS WHO: FATAL ERROR: AddSurvivor() failed");
LogError("Guess Who: Fatal Error: AddSurvivor() failed");
count = 0;
@ -561,6 +563,7 @@ Action Timer_WaitForStart(Handle h) {
Game.State = State_Starting;
Game.Tick = 0;
Game.MapTime = RoundFloat(seedTime);
Game.PopulateCoins();
CreateTimer(seedTime, Timer_StartSeeker);
return Plugin_Stop;
}
@ -585,6 +588,7 @@ Action Timer_StartSeeker(Handle h) {
Action Timer_TimesUp(Handle h) {
Game.Broadcast("The seeker ran out of time. Hiders win!");
Game.End(State_HidersWin);
timesUpTimer = null;
return Plugin_Handled;
}

View file

@ -46,6 +46,8 @@ char g_currentMap[64];
#include <hats/hats.sp>
#include <hats/hat_presets.sp>
#include <hats/props/base.sp>
#include <hats/natives.sp>
#include <hats_editor>
public Plugin myinfo = {
name = "L4D2 Hats & Editor",
@ -56,6 +58,32 @@ public Plugin myinfo = {
};
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() {
@ -116,6 +144,7 @@ public void OnPluginStart() {
for(int i = 1; i <= MaxClients; i++) {
Editor[i].client = i;
Editor[i].Reset(true);
g_PropData[i].Init(i);
hatData[i].yeetGroundTimer = null;
}
@ -456,6 +485,7 @@ ArrayList GetSpawnLocations() {
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));
@ -578,34 +608,34 @@ public Action OnPlayerRunCmd(int client, int& buttons, int& impulse, float vel[3
if(g_PropData[client].pendingSaveType == Save_Schematic) {
// move cursor? or should be editor anyway
}
} else if(g_PropData[client].markedProps != null) {
} 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) {
g_PropData[client].markedProps.Push(EntIndexToEntRef(entity));
GlowEntity(entity, 0.0, GLOW_RED);
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) {
int ref = EntIndexToEntRef(entity);
int index = g_PropData[client].markedProps.FindValue(ref);
if(index > -1) {
g_PropData[client].markedProps.Erase(index);
L4D2_RemoveEntityGlow(entity);
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
EndDeleteTool(client, true);
g_PropData[client].Selector.End();
} else if(buttons & IN_DUCK) {
//Cancel
EndDeleteTool(client, false);
g_PropData[client].Selector.Cancel();
}
}
cmdThrottle[client] = tick;
@ -628,31 +658,37 @@ public Action OnPlayerRunCmd(int client, int& buttons, int& impulse, float vel[3
}
if(!(oldButtons & IN_JUMP) && (buttons & IN_JUMP)) {
buttons &= ~IN_JUMP;
Editor[client].CycleStacker(tick);
Editor[client].CycleStacker();
} else if(!(oldButtons & IN_SPEED) && (buttons & IN_SPEED)) {
Editor[client].ToggleCollision(tick);
Editor[client].ToggleCollision();
return Plugin_Handled;
} else if(!(oldButtons & IN_DUCK) && (buttons & IN_DUCK)) {
Editor[client].ToggleCollisionRotate(tick);
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(tick);
else if(buttons & IN_ATTACK2) Editor[client].CycleSnapAngle(tick);
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 == 2) {
if(mouse[1] > 10) Editor[client].angles[2] += Editor[client].snapAngle;
else if(mouse[1] < -10) Editor[client].angles[2] -= Editor[client].snapAngle;
} else {
if(mouse[0] > 10) Editor[client].angles[Editor[client].axis] += Editor[client].snapAngle;
else if(mouse[0] < -10) Editor[client].angles[Editor[client].axis] -= Editor[client].snapAngle;
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;
}
@ -701,24 +737,27 @@ public Action OnPlayerRunCmd(int client, int& buttons, int& impulse, float vel[3
}
case COLOR: {
SetWeaponDelay(client, 0.5);
PrintCenterText(client, "%d %d %d %d", Editor[client].color[0], Editor[client].color[1], Editor[client].color[2], Editor[client].color[3]);
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_ATTACK) {
} else if(buttons & IN_ATTACK2) {
Editor[client].IncreaseColor(1);
allowMove = false;
} else if(buttons & IN_ATTACK2) {
} else if(buttons & IN_ATTACK) {
Editor[client].IncreaseColor(-1);
allowMove = false;
}
}
}
if(!(oldButtons & IN_USE) && buttons & IN_USE) {
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) {
if(Editor[client].flags & Edit_Preview)
Editor[client].CycleBuildType();
Editor[client].CycleBuildType();
// Editor[client].ShowExtraOptions();
} else {
int entity;
Editor[client].Done(entity);
@ -735,6 +774,12 @@ public Action OnPlayerRunCmd(int client, int& buttons, int& impulse, float vel[3
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) {
@ -914,6 +959,7 @@ bool Filter_ValidHats(int entity, int mask, int data) {
}
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));
@ -1024,4 +1070,4 @@ stock bool CalculateEditorPosition(int client, TraceEntityFilter filter) {
return true;
}
return false;
}
}

View file

@ -11,9 +11,12 @@
#include <sdktools>
//#include <sdkhooks>
#include <profiler>
#include <json>
// #include <json>
#include <ripext>
#include <jutils>
#include <entitylump>
#undef REQUIRE_PLUGIN
#include <hats_editor>
int g_iLaserIndex;
#if defined DEBUG_BLOCKERS
@ -27,6 +30,77 @@ int g_iLaserIndex;
#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<JSONObject>(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<JSONArray>(this.selectedSceneData.Get("variants"));
if(index >= variants.Length) return false;
else if(index < -1) return false;
else if(index > -1) {
this.selectedVariantData = view_as<JSONObject>(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 <scene>.entities if doesn't exist:
if(!g_builder.selectedSceneData.HasKey("entities")) {
g_builder.selectedSceneData.Set("entities", new JSONArray());
}
entities = view_as<JSONArray>(g_builder.selectedSceneData.Get("entities"));
} else {
entities = view_as<JSONArray>(g_builder.selectedVariantData.Get("entities"));
}
JSONObject entityData = ExportEntity(entity, Export_Model);
entities.Push(entityData);
}
}
#include <randomizer/rbuild.sp>
public Plugin myinfo =
{
name = "L4D2 Randomizer",
@ -36,13 +110,6 @@ public Plugin myinfo =
url = "https://github.com/Jackzmc/sourcemod-plugins"
};
ConVar cvarEnabled;
enum struct ActiveSceneData {
char name[MAX_SCENE_NAME_LENGTH];
int variantIndex;
}
MapData g_MapData;
public void OnPluginStart() {
EngineVersion g_Game = GetEngineVersion();
if(g_Game != Engine_Left4Dead && g_Game != Engine_Left4Dead2) {
@ -51,15 +118,14 @@ public void OnPluginStart() {
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));
}
char currentMap[64];
bool hasRan;
// TODO: on round start
public void OnMapStart() {
g_iLaserIndex = PrecacheModel("materials/sprites/laserbeam.vmt", true);
@ -69,6 +135,7 @@ public void OnMapStart() {
}
public void OnMapEnd() {
g_builder.Cleanup();
Cleanup();
}
@ -124,9 +191,7 @@ public Action Command_CycleRandom(int client, int args) {
if(args > 0) {
DeleteCustomEnts();
char arg1[8];
GetCmdArg(1, arg1, sizeof(arg1));
int flags = StringToInt(arg1) | view_as<int>(FLAG_REFRESH);
int flags = GetCmdArgInt(1) | view_as<int>(FLAG_REFRESH);
RunMap(currentMap, flags);
if(client > 0)
PrintCenterText(client, "Cycled flags=%d", flags);
@ -141,7 +206,7 @@ public Action Command_CycleRandom(int client, int args) {
return Plugin_Handled;
}
public Action Command_ExportEnt(int client, int args) {
Action Command_ExportEnt(int client, int args) {
float origin[3];
int entity = GetLookingPosition(client, Filter_IgnorePlayer, origin);
float angles[3];
@ -188,6 +253,271 @@ public Action Command_ExportEnt(int client, int args) {
}
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<JSONArray>(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<JSONArray>(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 <name> <chance 0.0-1.0>");
} 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<JSONArray>(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 <group>");
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<JSONArray>(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<JSONArray>(g_builder.selectedSceneData.Get("variants"));
for(int i = 0; i < variants.Length; i++) {
variantObj = view_as<JSONObject>(variants.Get(i));
int weight = 1;
if(variantObj.HasKey("weight"))
weight = variantObj.GetInt("weight");
JSONArray entities = view_as<JSONArray>(variantObj.Get("entities"));
ReplyToCommand(client, " #%d. [W:%d] [#E:%d]", i, weight, entities.Length);
}
}
}
enum struct SceneData {
@ -211,10 +541,12 @@ enum struct SceneVariantData {
int weight;
ArrayList inputsList;
ArrayList entities;
ArrayList forcedScenes;
void Cleanup() {
delete this.inputsList;
delete this.entities;
delete this.forcedScenes;
}
}
@ -366,6 +698,7 @@ enum struct LumpEditData {
}
enum struct MapData {
StringMap scenesKv;
ArrayList scenes;
ArrayList lumpEdits;
ArrayList activeScenes;
@ -376,30 +709,40 @@ enum loadFlags {
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 bool LoadMapData(const char[] map, int flags) {
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 false;
return null;
}
char buffer[65536];
File file = OpenFile(filePath, "r");
if(file == null) {
LogError("Could not open map config file (data/randomizer/%s.json)", map);
return false;
}
file.ReadString(buffer, sizeof(buffer));
JSON_Object data = json_decode(buffer);
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) {
json_get_last_error(buffer, sizeof(buffer));
LogError("Could not parse map config file (data/randomizer/%s.json): %s", map, buffer);
delete file;
return false;
}
@ -407,51 +750,51 @@ public bool LoadMapData(const char[] map, int flags) {
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();
int length = data.Length;
JSONObjectKeys iterator = data.Keys();
char key[32];
for (int i = 0; i < length; i += 1) {
data.GetKey(i, key, sizeof(key));
while(iterator.ReadKey(key, sizeof(key))) {
if(key[0] == '_') {
if(StrEqual(key, "_lumps")) {
JSON_Array lumpsList = view_as<JSON_Array>(data.GetObject(key));
JSONArray lumpsList = view_as<JSONArray>(data.Get(key));
if(lumpsList != null) {
for(int l = 0; l < lumpsList.Length; l++) {
loadLumpData(g_MapData.lumpEdits, lumpsList.GetObject(l));
loadLumpData(g_MapData.lumpEdits, view_as<JSONObject>(lumpsList.Get(l)));
}
}
} else {
Debug("Unknown special entry \"%s\", skipping", key);
}
} else {
if(data.GetType(key) != JSON_Type_Object) {
Debug("Invalid normal entry \"%s\" (not an object), skipping", key);
continue;
}
JSON_Object scene = data.GetObject(key);
// if(data.GetType(key) != JSONType_Object) {
// Debug("Invalid normal entry \"%s\" (not an object), skipping", key);
// continue;
// }
JSONObject scene = view_as<JSONObject>(data.Get(key));
// Parses scene data and inserts to scenes
loadScene(key, scene);
}
}
json_cleanup_and_delete(data);
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;
delete file;
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<int>(FLAG_REFRESH)) {
LoadMapData(map, flags);
if(!LoadMapData(map, flags)) {
return false;
}
}
Profiler profiler = new Profiler();
@ -463,7 +806,7 @@ public bool RunMap(const char[] map, int flags) {
return true;
}
void loadScene(const char key[MAX_SCENE_NAME_LENGTH], JSON_Object sceneData) {
void loadScene(const char key[MAX_SCENE_NAME_LENGTH], JSONObject sceneData) {
SceneData scene;
scene.name = key;
scene.chance = sceneData.GetFloat("chance");
@ -471,38 +814,73 @@ void loadScene(const char key[MAX_SCENE_NAME_LENGTH], JSON_Object sceneData) {
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));
JSON_Array variants = view_as<JSON_Array>(sceneData.GetObject("variants"));
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<JSONArray>(sceneData.Get("entities"));
}
JSONArray variants = view_as<JSONArray>(sceneData.Get("variants"));
for(int i = 0; i < variants.Length; i++) {
// Parses choice and loads to scene.choices
loadChoice(scene, variants.GetObject(i));
loadChoice(scene, view_as<JSONObject>(variants.Get(i)), entities);
}
g_MapData.scenes.PushArray(scene);
g_MapData.scenesKv.SetArray(scene.name, scene, sizeof(scene));
}
void loadChoice(SceneData scene, JSON_Object choiceData) {
void loadChoice(SceneData scene, JSONObject choiceData, JSONArray extraEntities) {
SceneVariantData choice;
choice.weight = choiceData.GetInt("weight", 1);
choice.weight = 1;
if(choiceData.HasKey("weight"))
choice.weight = choiceData.GetInt("weight");
choice.entities = new ArrayList(sizeof(VariantEntityData));
choice.inputsList = new ArrayList(sizeof(VariantInputData));
JSON_Array entities = view_as<JSON_Array>(choiceData.GetObject("entities"));
if(entities != null) {
choice.forcedScenes = new ArrayList(ByteCountToCells(MAX_SCENE_NAME_LENGTH));
// Load in any variant-based entities
if(choiceData.HasKey("entities")) {
JSONArray entities = view_as<JSONArray>(choiceData.Get("entities"));
for(int i = 0; i < entities.Length; i++) {
// Parses entities and loads to choice.entities
loadChoiceEntity(choice.entities, entities.GetObject(i));
loadChoiceEntity(choice.entities, view_as<JSONObject>(entities.Get(i)));
}
delete entities;
}
JSON_Array inputsList = view_as<JSON_Array>(choiceData.GetObject("inputs"));
if(inputsList != null) {
for(int i = 0; i < inputsList.Length; i++) {
loadChoiceInput(choice.inputsList, inputsList.GetObject(i));
// 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<JSONObject>(extraEntities.Get(i)));
}
delete extraEntities;
}
// Load all inputs
if(choiceData.HasKey("inputs")) {
JSONArray inputsList = view_as<JSONArray>(choiceData.Get("inputs"));
for(int i = 0; i < inputsList.Length; i++) {
loadChoiceInput(choice.inputsList, view_as<JSONObject>(inputsList.Get(i)));
}
delete inputsList;
}
if(choiceData.HasKey("force_scenes")) {
JSONArray scenes = view_as<JSONArray>(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, JSON_Object inputData) {
void loadChoiceInput(ArrayList list, JSONObject inputData) {
VariantInputData input;
// Check classname -> targetname -> hammerid
if(!inputData.GetString("classname", input.name, sizeof(input.name))) {
@ -527,7 +905,7 @@ void loadChoiceInput(ArrayList list, JSON_Object inputData) {
list.PushArray(input);
}
void loadLumpData(ArrayList list, JSON_Object inputData) {
void loadLumpData(ArrayList list, JSONObject inputData) {
LumpEditData input;
// Check classname -> targetname -> hammerid
if(!inputData.GetString("classname", input.name, sizeof(input.name))) {
@ -553,7 +931,7 @@ void loadLumpData(ArrayList list, JSON_Object inputData) {
list.PushArray(input);
}
void loadChoiceEntity(ArrayList list, JSON_Object entityData) {
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))) {
@ -569,18 +947,20 @@ void loadChoiceEntity(ArrayList list, JSON_Object entityData) {
list.PushArray(entity);
}
void GetVector(JSON_Object obj, const char[] key, float out[3]) {
JSON_Array vecArray = view_as<JSON_Array>(obj.GetObject(key));
bool GetVector(JSONObject obj, const char[] key, float out[3]) {
if(!obj.HasKey(key)) return false;
JSONArray vecArray = view_as<JSONArray>(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(JSON_Object obj, const char[] key, int out[4]) {
JSON_Array vecArray = view_as<JSON_Array>(obj.GetObject(key));
if(vecArray != null) {
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<JSONArray>(obj.Get(key));
out[0] = vecArray.GetInt(0);
out[1] = vecArray.GetInt(1);
out[2] = vecArray.GetInt(2);
@ -589,10 +969,7 @@ void GetColor(JSON_Object obj, const char[] key, int out[4]) {
else
out[3] = 255;
} else {
out[0] = 255;
out[1] = 255;
out[2] = 255;
out[3] = 255;
out = defaultColor;
}
}
@ -600,6 +977,8 @@ 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
@ -607,6 +986,7 @@ void selectScenes(int flags = 0) {
if(scene.group[0] == '\0') {
selectScene(scene, flags);
} else {
// Load it into group list
if(!groups.GetValue(scene.group, list)) {
list = new ArrayList();
}
@ -615,25 +995,61 @@ void selectScenes(int flags = 0) {
}
}
// 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<int>(FLAG_FORCE_ACTIVE));
}
delete forcedScenes;
delete snapshot;
delete groups;
}
void selectScene(SceneData scene, int flags) {
// Use the .chance field unless FLAG_ALL_SCENES is set
if(~flags & view_as<int>(FLAG_ALL_SCENES) && GetURandomFloat() > scene.chance) {
// Use the .chance field unless FLAG_ALL_SCENES or FLAG_FORCE_ACTIVE is set
if(~flags & view_as<int>(FLAG_ALL_SCENES) && ~flags & view_as<int>(FLAG_FORCE_ACTIVE) && GetURandomFloat() > scene.chance) {
return;
}
@ -645,20 +1061,26 @@ void selectScene(SceneData scene, int flags) {
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<int>(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<int>(FLAG_ALL_VARIANTS)) {
delete choices;
} else {
} else if(choices.Length > 0) {
index = GetURandomInt() % choices.Length;
index = choices.Get(index);
delete choices;
@ -666,7 +1088,6 @@ void selectScene(SceneData scene, int flags) {
scene.variants.GetArray(index, choice);
spawnVariant(choice);
}
ActiveSceneData aScene;
strcopy(aScene.name, sizeof(aScene.name), scene.name);
aScene.variantIndex = index;

View file

@ -20,6 +20,9 @@ int connectAttempts;
authState g_authState;
JSONObject g_globalVars;
StringMap actionFallbackHandlers; // namespace -> action name has no handler, falls to this.
StringMap actionNamespaceHandlers; // { namespace: { [action name] -> handler } }
enum authState {
AuthError = -1,
NotAuthorized,
@ -53,8 +56,16 @@ char OUT_EVENT_IDS[view_as<int>(Event_Invalid)][] = {
char steamidCache[MAXPLAYERS+1][32];
public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) {
CreateNative("UIElement.Send", Native_UpdateUI);
CreateNative("TempUI.Send", Native_UpdateTempUI);
RegPluginLibrary("overlay");
CreateNative("IsOverlayConnected", Native_IsOverlayConnected);
CreateNative("RegisterActionAnyHandler", Native_ActionHandler);
CreateNative("RegisterActionHandler", Native_ActionHandler);
CreateNative("UIElement.SendAll", Native_UpdateUI);
CreateNative("UIElement.SendTo", Native_UpdateUI);
CreateNative("TempUI.SendAll", Native_UpdateTempUI);
CreateNative("TempUI.SendTo", Native_UpdateTempUI);
return APLRes_Success;
}
@ -64,6 +75,9 @@ public void OnPluginStart() {
SetFailState("This plugin is for L4D/L4D2 only.");
}
actionFallbackHandlers = new StringMap();
actionNamespaceHandlers = new StringMap();
g_globalVars = new JSONObject();
cvarManagerUrl = CreateConVar("sm_overlay_manager_url", "ws://localhost:3011/socket", "");
@ -171,6 +185,7 @@ void Event_PlayerDisconnect(Event event, const char[] name, bool dontBroadcast)
if(GetClientCount(false) == 0) {
DisconnectManager();
}
steamidCache[client][0] = '\0';
}
void OnWSConnect(WebSocket ws, any arg) {
@ -210,12 +225,78 @@ void OnWSJson(WebSocket ws, JSON message, any data) {
message.ToString(buffer, sizeof(buffer));
PrintToServer("[Overlay] Error: %s", buffer);
} else {
char type[32];
obj.GetString("type", type, sizeof(type));
if(StrEqual(type, "action")) {
OnAction(obj);
}
char buffer[2048];
message.ToString(buffer, sizeof(buffer));
PrintToServer("[Overlay] Got JSON: %s", buffer);
}
}
stock int ExplodeStringToArrayList(const char[] text, const char[] split, ArrayList buffers, int maxStringLength) {
int reloc_idx, idx, total;
if (buffers == null || !split[0]) {
return 0;
}
char[] item = new char[maxStringLength];
while ((idx = SplitString(text[reloc_idx], split, item, maxStringLength)) != -1) {
reloc_idx += idx;
++total;
buffers.PushString(item);
}
++total;
buffers.PushString(text[reloc_idx]);
return buffers.Length;
}
void OnAction(JSONObject obj) {
char steamid[32];
obj.GetString("steamid", steamid, sizeof(steamid));
char ns[64];
obj.GetString("namespace", ns, sizeof(ns));
char id[64];
obj.GetString("elem_id", id, sizeof(id));
char action[256];
obj.GetString("action", action, sizeof(action));
int client = FindClientBySteamId2(steamid);
StringMap nsHandler;
PrivateForward fwd;
if(!actionNamespaceHandlers.GetValue(ns, nsHandler) || !nsHandler.GetValue(id, fwd)) {
if(!actionFallbackHandlers.GetValue(ns, fwd)) {
// No handler or catch all namespace handler
return;
}
}
ArrayList args = new ArrayList(ACTION_ARG_LENGTH);
ExplodeStringToArrayList(action, " ", args, ACTION_ARG_LENGTH);
UIActionEvent event = UIActionEvent(args);
Call_StartForward(fwd);
Call_PushCell(event);
Call_PushCell(client);
Call_Finish();
event._Delete();
}
int FindClientBySteamId2(const char[] steamid) {
for(int i = 1; i <= MaxClients; i++) {
if(StrEqual(steamidCache[i], steamid)) {
return i;
}
}
return -1;
}
void ConnectManager() {
DisconnectManager();
@ -281,11 +362,15 @@ void SendEvent_UpdateUI(const char[] elemNamespace, const char[] elemId, bool vi
}
//SendTempUI(int client, const char[] id, int lifetime, JSONObject element);
bool Native_SendTempUI(Handle plugin, int numParams) {
any Native_SendTempUI(Handle plugin, int numParams) {
if(!isManagerReady()) return false;
int client = GetNativeCell(1);
if(steamidCache[client][0] == '\0') return false;
if (client <= 0 || client > MaxClients) {
return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index (%d)", client);
} else if (!IsClientConnected(client) || steamidCache[client][0] == '\0') {
return ThrowNativeError(SP_ERROR_NATIVE, "Client %d is not connected/authorized yet", client);
}
char id[64];
GetNativeString(2, id, sizeof(id));
@ -299,7 +384,7 @@ bool Native_SendTempUI(Handle plugin, int numParams) {
}
//ShowUI(int client, const char[] elemNamespace, const char[] elemId, JSONObject variables);
bool Native_ShowUI(Handle plugin, int numParams) {
any Native_ShowUI(Handle plugin, int numParams) {
if(!isManagerReady()) return false;
char elemNamespace[64], id[64];
@ -313,7 +398,7 @@ bool Native_ShowUI(Handle plugin, int numParams) {
}
//HideUI(int client, const char[] elemNamespace, const char[] elemId);
bool Native_HideUI(Handle plugin, int numParams) {
any Native_HideUI(Handle plugin, int numParams) {
if(!isManagerReady()) return false;
char elemNamespace[64], id[64];
@ -325,7 +410,7 @@ bool Native_HideUI(Handle plugin, int numParams) {
}
//PlayAudio(int client, const char[] url);
bool Native_PlayAudio(Handle plugin, int numParams) {
any Native_PlayAudio(Handle plugin, int numParams) {
if(!isManagerReady()) return false;
char url[256];
@ -335,27 +420,92 @@ bool Native_PlayAudio(Handle plugin, int numParams) {
return true;
}
bool Native_UpdateUI(Handle plugin, int numParams) {
any Native_UpdateUI(Handle plugin, int numParams) {
if(!isManagerReady()) return false;
UIElement elem = GetNativeCell(1);
JSONObject obj = view_as<JSONObject>(elem);
JSONArray arr = view_as<JSONArray>(obj.Get("steamids"));
if(numParams == 0) {
arr.Clear();
} else if(numParams == 1) {
int client = GetNativeCell(2);
if (client <= 0 || client > MaxClients) {
return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index (%d)", client);
} else if (!IsClientConnected(client) || steamidCache[client][0] == '\0') {
return ThrowNativeError(SP_ERROR_NATIVE, "Client %d is not connected/authorized yet", client);
}
arr.PushString(steamidCache[client]);
}
g_ws.Write(view_as<JSON>(elem));
arr.Clear();
return true;
}
bool Native_UpdateTempUI(Handle plugin, int numParams) {
any Native_UpdateTempUI(Handle plugin, int numParams) {
if(!isManagerReady()) return false;
TempUI elem = GetNativeCell(1);
JSONObject obj = view_as<JSONObject>(elem);
JSONArray arr = view_as<JSONArray>(obj.Get("steamids"));
if(numParams == 0) {
arr.Clear();
} else if(numParams == 1) {
int client = GetNativeCell(2);
if (client <= 0 || client > MaxClients) {
return ThrowNativeError(SP_ERROR_NATIVE, "Invalid client index (%d)", client);
} else if (!IsClientConnected(client) || steamidCache[client][0] == '\0') {
return ThrowNativeError(SP_ERROR_NATIVE, "Client %d is not connected/authorized yet", client);
}
arr.PushString(steamidCache[client]);
}
g_ws.Write(view_as<JSON>(elem));
arr.Clear();
return true;
}
//IsOverlayConnected();
bool Native_IsOverlayConnected(Handle plugin, int numParams) {
any Native_IsOverlayConnected(Handle plugin, int numParams) {
return isManagerReady();
}
//RegisterActionHandler
//RegisterActionAnyHandler
any Native_ActionHandler(Handle plugin, int numParams) {
char ns[64];
GetNativeString(1, ns, sizeof(ns));
if(numParams == 3) {
// RegisterActionHandler
StringMap nsHandlers;
if(!actionNamespaceHandlers.GetValue(ns, nsHandlers)) {
nsHandlers = new StringMap();
}
char actionId[64];
GetNativeString(2, actionId, sizeof(actionId));
PrivateForward fwd;
if(!nsHandlers.GetValue(actionId, fwd)) {
fwd = new PrivateForward(ET_Ignore, Param_Cell);
}
fwd.AddFunction(INVALID_HANDLE, GetNativeFunction(3));
nsHandlers.SetValue(actionId, fwd);
} else {
// RegisterActionAnyHandler
PrivateForward fwd;
if(!actionFallbackHandlers.GetValue(ns, fwd)) {
fwd = new PrivateForward(ET_Ignore, Param_Cell);
}
fwd.AddFunction(INVALID_HANDLE, GetNativeFunction(2));
actionFallbackHandlers.SetValue(ns, fwd);
}
return 1;
}

View file

@ -242,15 +242,12 @@ public Action OnClientSayCommand(int client, const char[] command, const char[]
PrintToChat(client, "Note cancelled.");
} else {
int size = 2 * strlen(sArgs) + 1;
char[] sArgsTrimmed = new char[size];
DB.Escape(sArgs, sArgsTrimmed, size);
TrimString(sArgsTrimmed);
char buffer[32];
GetClientAuthId(client, AuthId_Steam2, buffer, sizeof(buffer));
// TODO: escape content
DB.Format(query, sizeof(query), "INSERT INTO `notes` (steamid, markedBy, content) VALUES ('%s', '%s', '%s')", menuNoteTarget, buffer, sArgsTrimmed);
DB.Format(query, sizeof(query), "INSERT INTO `notes` (steamid, markedBy, content) VALUES ('%s', '%s', '%s')", menuNoteTarget, buffer, sArgs);
DB.Query(DB_AddNote, query);
LogAction(client, -1, "added note for \"%s\" (%s): \"%s\"", client, menuNoteTargetName, menuNoteTarget, sArgsTrimmed);
LogAction(client, -1, "added note for \"%s\" (%s): \"%s\"", client, menuNoteTargetName, menuNoteTarget, sArgs);
Format(buffer, sizeof(buffer), "%N: ", client);
CShowActivity2(client, buffer, "added a note for {green}%s: {default}\"%s\"", menuNoteTargetName, sArgs);
}
@ -310,10 +307,7 @@ public Action Command_AddNote(int client, int args) {
char authMarker[32];
if(client > 0)
GetClientAuthId(client, AuthId_Steam2, authMarker, sizeof(authMarker));
int size = 2 * strlen(reason) + 1;
char[] content = new char[size];
DB.Escape(reason, content, size);
DB.Format(query, sizeof(query), "INSERT INTO `notes` (steamid, markedBy, content) VALUES ('%s', '%s', '%s')", auth, authMarker, content);
DB.Format(query, sizeof(query), "INSERT INTO `notes` (steamid, markedBy, content) VALUES ('%s', '%s', '%s')", auth, authMarker, reason);
DB.Query(DB_AddNote, query);
LogAction(client, target_list[0], "\"%L\" added note for \"%L\": \"%s\"", client, target_list[0], reason);
CShowActivity(client, "added a note for {green}%N: {default}\"%s\"", target_list[0], reason);